# CLI (/docs/cli) ## Commands [#commands] **Set up** | Command | What it does | | ------------------------------------- | ---------------------------------------------- | | [`openspec init`](#openspec-init) | Initialize OpenSpec in a project. | | [`openspec update`](#openspec-update) | Update OpenSpec's installed instruction files. | | [`openspec config`](#openspec-config) | View and change global configuration. | **Changes and specs** | Command | What it does | | ----------------------------------------- | ----------------------------------------------------------------- | | [`openspec list`](#openspec-list) | List changes, or specs with `--specs`. | | [`openspec show`](#openspec-show) | Print a change or spec, as markdown or JSON. | | [`openspec view`](#openspec-view) | One-screen dashboard of specs and changes. | | [`openspec validate`](#openspec-validate) | Check changes and specs for structural issues. | | [`openspec archive`](#openspec-archive) | Move a completed change to the archive and update the main specs. | **Workflows and schemas** Your agent runs most of these during the workflow. | Command | What it does | | ------------------------------------------------- | -------------------------------------------------------------- | | [`openspec new`](#openspec-new) | Create a new change directory. | | [`openspec status`](#openspec-status) | Artifact completion status for one or every active change. | | [`openspec instructions`](#openspec-instructions) | Instructions for creating an artifact, applying, or archiving. | | [`openspec templates`](#openspec-templates) | Resolved template paths for a schema's artifacts. | | [`openspec schemas`](#openspec-schemas) | List available workflow schemas. | | [`openspec schema`](#openspec-schema) | Inspect, fork, or create a schema (experimental). | **Multi-repo (beta)** | Command | What it does | | --------------------------------------- | ------------------------------------------------------------------------------- | | [`openspec store`](#openspec-store) | Create and manage stores: standalone OpenSpec repos registered on your machine. | | [`openspec doctor`](#openspec-doctor) | Report relationship health for the resolved OpenSpec root. | | [`openspec context`](#openspec-context) | Print the working context for the resolved OpenSpec root. | | [`openspec workset`](#openspec-workset) | Compose, keep, and open personal working views. | **Utilities** | Command | What it does | | --------------------------------------------- | -------------------------------------- | | [`openspec feedback`](#openspec-feedback) | Submit feedback about OpenSpec. | | [`openspec completion`](#openspec-completion) | Install or generate shell completions. | **Deprecated** | Command | What it does | | ------------------------------------- | ------------------------------------------------------------------------------------------------------- | | [`openspec change`](#openspec-change) | Noun form of show, list, and validate for changes. The CLI warns and points to the verb-first commands. | | [`openspec spec`](#openspec-spec) | Noun form of show, list, and validate for specs, with the same warning. | Every command takes `-h, --help`. The bare `openspec` command also takes: * `-V, --version`: print the CLI version. * `--no-color`: disable colored output. ## openspec init [#openspec-init] Initializes OpenSpec in a project. ```bash openspec init # current directory, interactive tool picker openspec init --tools claude,cursor # set up specific tools, no prompts openspec init --tools none # openspec/ structure only, no tool files ``` With no `--tools`, init prompts you to pick tools in an interactive terminal. Outside one, it sets up the tools it detects in the project. With none detected it exits 1 and lists the valid ids. **Arguments** | Argument | What it is | | -------- | ------------------------------------------------------------------------------------ | | `path` | The project directory to initialize. Default: current directory. Created if missing. | **Options** | Flag | Effect | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `--tools ` | Comma-separated tool ids, `all`, or `none`. Skips the picker. Ids are listed in [Supported tools](/docs/supported-tools). | | `--force` | Remove files from older OpenSpec layouts without asking. Interactive runs otherwise confirm the cleanup first. | | `--profile ` | Override the global config profile for this run: `core` (the standard workflow set) or `custom` (the workflows saved in global config). | | `--no-animation` | Show a static welcome screen instead of the animated one. | **Output** Each selected tool gets OpenSpec's skills and commands in its own directory: ``` ▌ OpenSpec structure created ✔ Setup complete for Claude Code OpenSpec Setup Complete Created: Claude Code 6 skills and 6 commands in .claude/ Config: openspec/config.yaml (schema: spec-driven) Getting started: Start your first change: /opsx:propose "your idea" Restart your IDE for the new commands to take effect. ``` `--tools none` creates only `openspec/config.yaml`. On an already-initialized project, init rewrites the installed files in place and the summary reads `Refreshed: Claude Code` with `Config: openspec/config.yaml (exists)`. **Exit codes** * `0`: setup completed. * `1`: invalid `--tools` or `--profile` value, or a non-interactive run with no tools detected and no `--tools`. ## openspec update [#openspec-update] Updates OpenSpec's installed instruction files. ```bash openspec update # refresh tools whose files are older than the CLI openspec update --force # rewrite files even when they're current ``` update finds the tools init configured and compares their generated files against the CLI's version. When a newer OpenSpec release exists, it first offers to upgrade the CLI, then reruns with the upgraded version. Set `OPENSPEC_NO_UPDATE_CHECK=1` to skip the check. **Arguments** | Argument | What it is | | -------- | ------------------------------------------------------------ | | `path` | The project directory to update. Default: current directory. | **Options** | Flag | Effect | | --------- | ------------------------------------------------------------------- | | `--force` | Rewrite every configured tool's files even when they're up to date. | **Output** When every tool's files match the CLI version: ``` ✓ All 1 tool(s) up to date (v1.7.0) Tools: claude Use --force to refresh files anyway. ``` When a tool's files came from an older CLI (or with `--force`, which prints `Force updating 1 tool(s): claude` instead): ``` Updating 1 tool(s): claude (1.6.0 → 1.7.0) ✔ Updated Claude Code ✓ Updated: Claude Code (v1.7.0) Tools: Claude Code Restart your IDE for changes to take effect. ``` In a directory without OpenSpec, update refuses: ``` ✖ Error: No OpenSpec directory found. Run 'openspec init' first. ``` **Exit codes** * `0`: files updated, or everything already up to date. * `1`: no OpenSpec directory at the path, or the update failed. ## openspec config [#openspec-config] Views and changes global configuration. ```bash openspec config list # see current settings openspec config set delivery skills # change one value openspec config profile # interactive workflow picker ``` | Subcommand | What it does | | ------------------- | -------------------------------------- | | `path` | Print the config file location. | | `list` | Show all current settings. | | `get ` | Print one value, raw and scriptable. | | `set ` | Set a value, coercing its type. | | `unset ` | Remove a key so its default applies. | | `reset` | Reset all configuration to defaults. | | `edit` | Open the config file in `$EDITOR`. | | `profile [preset]` | Configure delivery mode and workflows. | Config is global to your machine, stored as JSON where `config path` points: `$XDG_CONFIG_HOME/openspec/config.json` if set, else `~/.config/openspec/config.json` (macOS, Linux) or `%APPDATA%\openspec\config.json` (Windows). Every subcommand accepts `--scope `, but only `global` works today. Any other scope exits 1 with `Error: Project-local config is not yet implemented`. ### openspec config path [#openspec-config-path] ```bash openspec config path ``` ``` /Users/you/.config/openspec/config.json ``` ### openspec config list [#openspec-config-list] ```bash openspec config list # readable settings plus profile summary openspec config list --json # raw config as JSON ``` **Options** | Flag | Effect | | -------- | -------------------------------- | | `--json` | Print the config object as JSON. | **Output** Each setting, then a profile summary that marks values as explicit or default: ``` featureFlags: {} profile: core delivery: both Profile settings: profile: core (default) delivery: both (default) workflows: propose, explore, apply, update, sync, archive (from core profile) ``` ### openspec config get [#openspec-config-get] ```bash openspec config get delivery ``` **Arguments** | Argument | What it is | | -------- | ---------------------------------------------------------------------- | | `key` | The key to read. Dots reach nested values (`featureFlags.workspaces`). | **Output** The bare value, ready for scripts. Objects print as compact JSON: ``` both ``` **Exit codes** * `0`: value printed. * `1`: key has no value, and nothing is printed. ### openspec config set [#openspec-config-set] ```bash openspec config set delivery skills openspec config set featureFlags.workspaces true ``` **Arguments** | Argument | What it is | | -------- | ------------------------------------------------------------------------------ | | `key` | The key to write, dotted for nested values. | | `value` | The new value. `true`/`false` become booleans, numeric strings become numbers. | **Options** | Flag | Effect | | ----------------- | ---------------------------------------------------- | | `--string` | Store the value as a string, skipping type coercion. | | `--allow-unknown` | Permit keys the schema doesn't know. | **Output** ``` Set featureFlags.workspaces = true ``` Unknown keys and invalid values fail with exit 1 before anything is saved: ``` Error: Invalid configuration key "bogus.key". Unknown top-level key "bogus". Use "openspec config list" to see available keys. Pass --allow-unknown to bypass this check. ``` ``` Error: Invalid configuration - delivery: Invalid option: expected one of "both"|"skills"|"commands" ``` ### openspec config unset [#openspec-config-unset] ```bash openspec config unset delivery ``` Removes the key so the default applies again. Keys with built-in defaults always count as set, so this reports success even if you never set them: ``` Unset delivery (reverted to default) ``` A key with no value at all prints `Key "featureFlags.nothere" was not set`. Both cases exit 0. ### openspec config reset [#openspec-config-reset] ```bash openspec config reset --all # asks for confirmation openspec config reset --all -y # no prompt ``` **Options** | Flag | Effect | | ----------- | ----------------------------- | | `--all` | Required. Reset everything. | | `-y, --yes` | Skip the confirmation prompt. | **Output** ``` Configuration reset to defaults ``` Without `--all` it exits 1 and prints the usage line. **Exit codes** * `0`: reset done, or you answered no at the prompt. * `1`: `--all` missing. * `130`: prompt cancelled with Ctrl-C. ### openspec config edit [#openspec-config-edit] ```bash openspec config edit ``` Opens the config file in `$EDITOR` (falling back to `$VISUAL`), creating it with defaults first if missing. When the editor closes, the file is validated. Invalid JSON or an invalid config exits 1. The editor value may carry arguments and quoted paths, for example `code --wait` or `"/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" -w`. It is split into words without a shell, so `$VAR`, `~` and `;` are passed through literally. An editor that cannot start, or exits non-zero, prints a one-line error and exits 1. With no editor configured it exits 1: ``` Error: No editor configured Set the EDITOR or VISUAL environment variable to your preferred editor Example: export EDITOR=vim ``` ### openspec config profile [#openspec-config-profile] ```bash openspec config profile # interactive picker (needs a terminal) openspec config profile core # apply the core preset directly ``` **Arguments** | Argument | What it is | | -------- | -------------------------------------------------------------------------------------------------------- | | `preset` | Optional preset name. Only `core` exists. It selects the core workflows and keeps your delivery setting. | With no preset, an interactive picker shows your current delivery and workflows, lets you change either or both (delivery: both, skills only, or commands only; workflows: a checkbox list), prints the diff, and inside an OpenSpec project offers to run `openspec update` for you. Outside a terminal it exits 1: ``` Interactive mode required. Use `openspec config profile core` or set config via environment/flags. ``` **Output** Changed config doesn't reach projects until they update: ``` Config updated. Run `openspec update` in your projects to apply. ``` **Exit codes** * `0`: profile saved, or you kept current settings. * `1`: unknown preset, no terminal, or the offered `openspec update` failed. * `130`: picker cancelled with Ctrl-C. ## openspec list [#openspec-list] Lists changes, or specs with `--specs`. ```bash openspec list # changes, most recently modified first openspec list --specs # specs with requirement counts openspec list --json # machine-readable, includes the resolved root ``` Rows come from `openspec/changes/` and `openspec/specs/` under the resolved root. The `archive/` folder is skipped. **Options** | Flag | Effect | | ---------------- | --------------------------------------------------------------------------------------- | | `--specs` | List specs instead of changes. | | `--changes` | List changes. This is the default. | | `--sort ` | `recent` (last modified first) or `name`. Default: `recent`. Specs always sort by name. | | `--json` | Print JSON instead of the table. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** One row per change: name, task status, last modified. The status column reads `No tasks`, `2/5 tasks`, or `✓ Complete`. ``` Changes: add-rate-limit No tasks just now ``` ``` Specs: api requirements 1 ``` `--json` adds task counts and a `status` of `no-tasks`, `in-progress`, or `complete`: ```json { "changes": [ { "name": "add-rate-limit", "completedTasks": 0, "totalTasks": 0, "lastModified": "2026-08-11T13:44:40.171Z", "status": "no-tasks" } ], "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` An empty listing prints `No active changes found.` or `No specs found.` and still exits 0. A change is a directory directly under `openspec/changes/`. Unlike specs, changes cannot be nested in a namespace folder. A folder like `changes/mobile/` that only wraps a change (`changes/mobile/refresh-token/`) is listed with the status `not a change`, followed by a warning that names the nested directories. `--json` marks that entry with a `nested` array and adds a top-level `warnings` array. `show`, `status`, `validate` and `archive` refuse the folder with the same message. To fix it, move the change up and fold the namespace into its name: ```bash mv openspec/changes/mobile/refresh-token openspec/changes/mobile-refresh-token rmdir openspec/changes/mobile ``` **Exit codes** * `0`: listing printed, even when empty. * `1`: no OpenSpec root found (outside a project, no `--store`). ## openspec show [#openspec-show] Prints a change or spec, as markdown or JSON. ```bash openspec show add-rate-limit # change: prints proposal.md openspec show add-rate-limit --diff # change: append requirement diffs openspec show api # spec: prints spec.md openspec show api --json --no-scenarios # spec JSON without scenario text ``` With no name, show asks change or spec, then lists items to pick from. Outside an interactive terminal it exits 1 and prints the direct forms instead. **Arguments** | Argument | What it is | | ----------- | --------------------------------------------------------------------- | | `item-name` | The change or spec to show, by folder name (`add-rate-limit`, `api`). | **Options** | Flag | Effect | | ------------------------ | ------------------------------------------------------------------------------------------------------------- | | `--json` | Print structured JSON instead of raw markdown. | | `--type ` | Pick the type when a change and a spec share a name. | | `--no-interactive` | Never prompt: a missing name becomes an error. | | `--deltas-only` | JSON, change: restrict output to deltas. Change JSON is already delta-only, so output matches plain `--json`. | | `--requirements-only` | Deprecated alias for `--deltas-only`. Warns on stderr. | | `--diff` | Change: append per-requirement delta diffs. Ignored with a warning for specs. | | `--requirements` | JSON, spec: keep requirement text, empty the `scenarios` arrays. | | `--no-scenarios` | JSON, spec: same output as `--requirements`. | | `-r, --requirement ` | JSON, spec: output one requirement by 1-based position. Can't combine with `--requirements`. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | Flags that don't apply to the resolved type are ignored with a warning on stderr. **Output** Text mode is a raw passthrough: a change prints its `proposal.md`, a spec prints its `spec.md`. ``` # Add rate limiting ## Why Unauthenticated clients can exhaust the API. ## What Changes - Add per-client rate limiting to the public API. ``` For a change, `--diff` prints the proposal first, then a `Specifications Changed (diffs)` section. ADDED requirements include their full text. REMOVED requirements retain the authored Reason and Migration. RENAMED requirements show FROM and TO. MODIFIED requirements show a unified diff against the matching main requirement. If a MODIFIED header matches only after folding case or whitespace, the output includes both the diff and a warning that archive matching is exact. If the main spec or requirement is missing, the output warns and prints the full delta block. A MODIFIED block with no textual difference prints `(no textual changes)`. A change with `--json` is delta-shaped: ```json { "id": "add-rate-limit", "title": "Add rate limiting", "deltaCount": 1, "deltas": [ { "spec": "api", "operation": "ADDED", "description": "Add requirement: The API SHALL limit each client to 100 requests per minute.", "requirement": { "text": "The API SHALL limit each client to 100 requests per minute.", "scenarios": [ { "rawText": "- **WHEN** a client sends its 101st request within a minute\n- **THEN** the API responds 429" } ] }, ... } ], "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` `--json --diff` keeps this top-level shape. A MODIFIED delta gains a `diff` string, a `warning` string, or both. Other operations are unchanged. An empty `diff` string means the main and delta blocks are textually identical. A spec with `--json` lists its requirements with scenarios: ```json { "id": "api", "title": "api", "overview": "Public HTTP API behavior.", "requirementCount": 1, "requirements": [ { "text": "The API SHALL expose a health endpoint.", "scenarios": [ { "rawText": "- **WHEN** a client requests GET /health\n- **THEN** the API responds 200" } ] } ], "metadata": { "version": "1.0.0", "format": "openspec" }, "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` An unknown name suggests near matches: `Unknown item 'does-not-exist'. Did you mean: add-rate-limit, api?`. A name that matches both a change and a spec errors and asks for `--type`. **Exit codes** * `0`: item printed. * `1`: unknown or ambiguous name, no name outside a terminal, an out-of-range `-r` index, `--requirements` combined with `-r`, or a delta or main spec cannot be read for `--diff`. ## openspec view [#openspec-view] Prints a one-screen dashboard of specs and changes. ```bash openspec view # project summary in one screen ``` view prints the dashboard once and exits. It reads no keystrokes. Changes group by task progress: Draft (no tasks yet), Active (tasks underway, with a progress bar and percent), Completed (every task checked). Specs list with requirement counts, largest first. **Options** | Flag | Effect | | -------------- | --------------------------------------------------------------------------- | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** ``` OpenSpec Dashboard ════════════════════════════════════════════════════════════ Summary: ● Specifications: 1 specs, 1 requirements ● Draft Changes: 1 ● Active Changes: 0 in progress ● Completed Changes: 0 Draft Changes ──────────────────────────────────────────────────────────── ○ add-rate-limit Specifications ──────────────────────────────────────────────────────────── ▪ api 1 requirement ════════════════════════════════════════════════════════════ Use openspec list --changes or openspec list --specs for detailed views ``` A `Task Progress` summary line appears when any change has tasks underway. **Exit codes** * `0`: dashboard printed. * `1`: no OpenSpec root found (outside a project, no `--store`). ## openspec validate [#openspec-validate] Checks changes and specs for structural issues. ```bash openspec validate add-rate-limit # one change or spec, by name openspec validate --all # every change and spec ``` With no name and no bulk flag, validate prompts you to pick items. Outside an interactive terminal it exits 1 and prints the bulk flags instead. **Arguments** | Argument | What it is | | ----------- | ------------------------------------------------------------------------- | | `item-name` | The change or spec to validate, by folder name (`add-rate-limit`, `api`). | **Options** | Flag | Effect | | ----------------------- | ---------------------------------------------------------------------------------------- | | `--all` | Validate every change and spec. | | `--changes` | Validate every change. | | `--specs` | Validate every spec. | | `--archived` | Check task completion in archived changes, without validating their applied spec deltas. | | `--strict` | Treat warnings as failures. | | `--type ` | Pick the type when a change and a spec share a name. | | `--json` | Print a structured report instead of text. | | `--report ` | Bulk output: `full` (default) or `findings`. Requires an explicit bulk scope. | | `--concurrency ` | Max parallel validations in bulk runs. Default: `OPENSPEC_CONCURRENCY`, else 6. | | `--no-interactive` | Never prompt: a missing or ambiguous name becomes an error. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** Bulk runs print one status line per item, followed by any findings, and end with totals: ``` ✓ change/add-rate-limit ✓ spec/api Totals: 2 passed, 0 failed (2 items) ``` **Task checkbox findings** Progress counts checkboxes and nothing else, so a task file written as plain bullets reads as zero tasks: `openspec list` and `openspec status` report no work, and `openspec archive` has nothing to flag as incomplete. Validate reports a `WARNING` on each tracked task file that lists work without a checkbox: ```text ⚠ [WARNING] tasks.md: This change counts as 0 tasks: no line in its tracked task files is a checkbox, so "openspec list" and "openspec status" report no work and "openspec archive" has nothing to flag as incomplete. Write each task as "- [ ] 1.1 Description". ``` The warning fires only when the change's whole tracked set holds no checkbox at all. One file of prose beside a real checklist is not reported, and a change mid-authoring keeps its progress the moment a single checkbox exists. `--strict` turns the warning into a failure. The line number is in the `--json` report. Fenced blocks, HTML comments, YAML front matter and indented code are not scanned, so a pasted terminal sample is never mistaken for a task list. **Archive merge findings** For changes, validate runs archive's merge builder against the current main specs without writing files. It reports merge conflicts, such as a missing `MODIFIED` target or a conflicting `ADDED` requirement, as `INFO`: ```text ℹ [INFO] api/spec.md: Archive would refuse this delta: api MODIFIED failed for header "### Requirement: Rate limiting" - not found ``` These findings appear even when validation passes, in both text and JSON output. `INFO` never changes the exit code, including under `--strict`: a missing target may belong to a sibling change that has not archived yet. Deltas already synced into the main specs follow archive's existing merge rules. This check does not run archive's later merged-spec validation or retirement checks. A clean report does not guarantee that archive will succeed. If the merge preflight cannot start, an `INFO` finding explains why. Existing validation findings and the exit code stay unchanged. A failing item lists each issue and the fix: ``` Change 'add-rate-limit' has issues ✗ [ERROR] api/spec.md: ADDED "Rate limiting" must include at least one scenario Next steps: - Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements - Each requirement MUST include at least one #### Scenario: block - Debug parsed deltas: openspec show add-rate-limit --json --deltas-only ``` `--json` prints one report for the run: ```json { "items": [ { "id": "add-rate-limit", "type": "change", "valid": true, "issues": [], "durationMs": 2 } ], "summary": { "totals": { "items": 1, "passed": 1, "failed": 0 }, "byType": { "change": { "items": 1, "passed": 1, "failed": 0 } } }, "version": "1.0", "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` `issues` entries carry a `level` of `ERROR`, `WARNING`, or `INFO`. **Exit codes** * `0`: every validated item passed, including an empty bulk scope. * `1`: an item failed, the report request is invalid, or the run failed (for example an unknown name or no OpenSpec root). ### --report full|findings [#--report-fullfindings] Selects the output for an explicit bulk validation scope: * **`full`**: every item. This is the default. Explicit `--report full` keeps the existing output shape without adding report metadata. * **`findings`**: only items with issues, including passing items with warnings or information. Every item is still validated. Totals, strict-mode behavior, and exit codes are unchanged. ```bash openspec validate --all --report findings openspec validate --archived --report findings --json ``` **Scopes** | Flags | Findings `report.scope` | | ------------------------------------------------ | ----------------------- | | `--all` | `all` | | `--changes` | `changes` | | `--specs` | `specs` | | `--changes --specs`, or `--all` with either flag | `all` | | `--archived` | `archived` | Both explicit report modes reject a positional item name, a missing bulk scope, or archive and active scopes combined. #### Findings text output [#findings-text-output] **Human output**: stdout prints `Scope: ( items)`, then totals. With no issue-bearing items: ```text Scope: all (2 items) No item findings. Totals: 2 passed, 0 failed (2 items) ``` Issue-bearing item labels, severity labels, paths, and messages print to stderr. Active-scope failures keep the `Details:` rerun hint after totals. The existing root banner and progress output may precede the report. #### Findings JSON output [#findings-json-output] `--report findings --json` prints one document. This example has two clean items: ```json { "report": { "kind": "validation-findings", "version": "1.0", "scope": "all", "returnedItems": 0, "totalItems": 2 }, "itemFindings": [], "summary": { "totals": { "items": 2, "passed": 2, "failed": 0 }, "byType": { "change": { "items": 1, "passed": 1, "failed": 0 }, "spec": { "items": 1, "passed": 1, "failed": 0 } } }, "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` * **`report.kind` and `report.version`**: identify the `validation-findings` shape, version `1.0`. There is no top-level `version` or `items`. * **`report.returnedItems` and `report.totalItems`**: count the returned records and all validated items, respectively. * **`itemFindings`**: complete item records whose `issues` array is nonempty. Includes `ERROR`, `WARNING`, and `INFO` issues. Each record retains `id`, `type`, `valid`, `issues`, and `durationMs`. Archived items use `type: "change"`. * **`summary`**: full-run totals and per-type counts, not counts of the returned subset. An empty scope has zero totals and exits 0. * **`root`**: the same selected-root metadata as the full report. **Record preservation**: returned items keep their full-report order and any additive fields on items or issues. Filtering does not rewrite messages or locations, including optional `line` and `column` fields. **Command failures**: root-selection or item-discovery failures retain the existing `status` diagnostic and exit 1. They do not return a completed findings report or a successful empty report. #### Invalid report requests [#invalid-report-requests] Both explicit report modes reject these requests before root selection or item discovery: * An unsupported report value, including an empty string. * A positional item name, even with a bulk flag. * No explicit bulk scope. * `--archived` combined with `--all`, `--changes`, or `--specs`. In JSON mode, a rejected request exits 1 with only a single-element `status` array. It has no `root` or report payload: ```bash openspec validate --all --report bogus --json ``` ```json { "status": [ { "severity": "error", "code": "invalid_validation_report_request", "message": "Unknown validation report 'bogus'.", "fix": "Use --report full|findings with --all, --changes, --specs, or --archived, without an item name. Do not combine archived and active scopes." } ] } ``` Human mode prints the error to stderr. A bare `--report` with no value is a Commander syntax error on stderr, including with `--json`; it does not use this diagnostic envelope. #### Filter a full report externally [#filter-a-full-report-externally] For a custom JSON view, filter the full report with `jq` or PowerShell. These script examples preserve the validation exit code and leave command-error documents intact. In Bash with `jq`: ```bash if validation_json=$(openspec validate --all --json); then validation_exit=0 else validation_exit=$? fi printf '%s\n' "$validation_json" | jq 'if has("items") then .items |= map(select(.issues | length > 0)) else . end' exit "$validation_exit" ``` In PowerShell: ```powershell $validationJson = openspec validate --all --json $validationExit = $LASTEXITCODE $validationReport = $validationJson | ConvertFrom-Json if ($validationReport.PSObject.Properties.Name -contains 'items') { $validationReport.items = @($validationReport.items | Where-Object { $_.issues.Count -gt 0 }) } $validationReport | ConvertTo-Json -Depth 100 exit $validationExit ``` These custom views keep the full report's keys but omit clean items. They are neither complete full-v1 reports nor the versioned `--report findings` shape. ## openspec archive [#openspec-archive] Moves a completed change to the archive and updates the main specs. ```bash openspec archive add-rate-limit -y # archive one change, merge its deltas openspec archive add-rate-limit -y --skip-specs # archive without touching the specs ``` With no name, archive prompts you to pick a change. Outside an interactive terminal it exits 1 and prints the rerun command instead. **Arguments** | Argument | What it is | | ------------- | --------------------------------------------------------- | | `change-name` | The change to archive, by folder name (`add-rate-limit`). | **Options** | Flag | Effect | | --------------- | --------------------------------------------------------------------------------------- | | `-y, --yes` | Answer yes to every confirmation: spec updates, incomplete tasks, skipped validation. | | `--skip-specs` | Archive without touching the main specs (infrastructure, tooling, or doc-only changes). | | `--no-validate` | Skip validation. Archive asks you to confirm first, and `-y` answers it. | | `--json` | Print a structured result instead of text. Needs `--yes` to confirm spec updates. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** A successful run reports task status, previews the spec updates, applies them, and names the archive folder: ``` Task status: ✓ Complete Specs to update: api: update Applying changes to openspec/specs/api/spec.md: + 1 added Totals: + 1, ~ 0, - 0, → 0 Specs updated successfully. Change 'add-rate-limit' archived as '2026-08-11-add-rate-limit'. ``` The change folder moves whole to `openspec/changes/archive/2026-08-11-add-rate-limit/`, today's date prefixed to its name. Each delta merges into its main spec: the ADDED requirement above was appended to `openspec/specs/api/spec.md`. Without `-y`, archive shows the preview and asks before updating. Declining still archives the change and leaves the specs alone. With `--json --yes`: ```json { "archive": { "change": "add-rate-limit", "archivedAs": "2026-08-11-add-rate-limit", "path": "/Users/you/projects/my-app/openspec/changes/archive/2026-08-11-add-rate-limit", "specsUpdated": true, "totals": { "added": 1, "modified": 0, "removed": 0, "renamed": 0 } }, "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` Archive validates the change first and refuses one that fails: ``` Validation errors in change delta specs: ✗ ADDED "Rate limiting" must include at least one scenario Validation failed. Please fix the errors before archiving. To skip validation (not recommended), use --no-validate flag. ``` Incomplete tasks warn but don't block. Interactively archive asks whether to continue, and `-y` continues on its own: ``` Task status: 1/2 tasks Warning: 1 incomplete task(s) found. Continuing due to --yes flag. ``` **Exit codes** * `0`: the change was archived, with or without spec updates. * `1`: validation failed, the change name is unknown, or a confirmation was needed and no answer could be read. ## openspec new [#openspec-new] Creates a new change directory. ```bash openspec new change add-caching # metadata only openspec new change add-search --goal "Users can search docs" # record a goal ``` `new` has one subcommand, `new change `. It creates `openspec/changes//` containing a single [`.openspec.yaml` metadata file](/docs/configuration/change-metadata): ```yaml schema: spec-driven created: 2026-08-11 ``` Artifacts (proposal, specs, design, tasks) aren't scaffolded here. You write them later, and `openspec status` tells you which one is next. **Arguments** | Argument | What it is | | -------- | ------------------------------------------- | | `name` | Folder name for the change (`add-caching`). | **Options** | Flag | Effect | | ---------------------- | ----------------------------------------------------------------------------------- | | `--description ` | Also create a `README.md` in the change directory with this text. | | `--goal ` | Store a `goal:` line in `.openspec.yaml`. | | `--schema ` | Workflow schema for the change. Default: `spec-driven`, the only schema that ships. | | `--json` | Print the created change as JSON instead of text. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** ``` Created change 'add-caching' at openspec/changes/add-caching/ Schema: spec-driven Next: openspec status --change add-caching ``` With `--json`: ```json { "change": { "id": "add-caching", "path": "/Users/you/projects/my-app/openspec/changes/add-caching", "metadataPath": "/Users/you/projects/my-app/openspec/changes/add-caching/.openspec.yaml", "schema": "spec-driven" }, "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` **Exit codes** * `0`: change created. * `1`: the change already exists, or the schema is unknown. ## openspec status [#openspec-status] Reports artifact completion status for one change or every active change. ```bash openspec status --change add-rate-limit # checklist view openspec status --change add-rate-limit --json # structured report openspec status --all # every active change openspec status --all --json # one batch report ``` When active changes exist, use exactly one of `--change` or `--all`. Without either, status exits 1 and lists the available changes, even when only one exists: ```text ✖ Error: Missing required option --change (or --all for every active change). Available changes: add-rate-limit ``` When the project has no active changes, status prints `No active changes. Create one with: openspec new change ` and exits 0 even without either flag. With `--all --json`, the same empty state is `{ "changes": [], "message": "No active changes.", "root": ... }`. **Options** | Flag | Effect | | ----------------- | ------------------------------------------------------------------------------------------- | | `--change ` | The change to report on, by folder name. | | `--all` | Report every active change, sorted by name. Can't be combined with `--change`. | | `--schema ` | Override the schema auto-detected from `openspec/config.yaml`. An unknown name is an error. | | `--json` | Print a structured report instead of text. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** A checklist of the schema's artifacts: `[x]` done, `[ ]` ready to write, `[-]` blocked until the artifacts it depends on exist. ``` Change: add-rate-limit Schema: spec-driven Change root: /Users/you/projects/my-app/openspec/changes/add-rate-limit Progress: 2/4 artifacts complete [x] proposal [x] specs [ ] design [-] tasks (blocked by: design) Next: openspec instructions design --change "add-rate-limit" --json ``` The `Next:` line names the one command that moves the change forward, so `openspec status` is enough to pick a change back up in a fresh session. It names the next ready artifact while planning is unfinished, and `openspec instructions apply` once every planning artifact exists: ``` [x] proposal [x] specs [x] design [x] tasks All planning artifacts complete! Next: openspec instructions apply --change "add-rate-limit" --json ``` It carries `--store ` whenever the resolved root is a store, and names the same command as the JSON `nextSteps` sentence. `--json` adds per-artifact dependencies, resolved file paths, and a suggested next step. Trimmed: ```json { "changeName": "add-rate-limit", "schemaName": "spec-driven", "isComplete": false, "nextSteps": [ "Run openspec instructions design --change \"add-rate-limit\" --json before writing that artifact." ], "artifacts": [ { "id": "proposal", "outputPath": "proposal.md", "status": "done", "requires": [] }, { "id": "design", "outputPath": "design.md", "status": "ready", "requires": [ "proposal" ] }, { "id": "tasks", "outputPath": "tasks.md", "status": "blocked", "requires": [ "specs", "design" ], "missingDeps": [ "design" ] } ] } ``` With `--all --json`, `changes` contains the same status object for each change, without a per-change `root`. The selected root appears once on the envelope. This example trims the per-change status fields shown above: ```json { "changes": [ { "changeName": "add-rate-limit", "schemaName": "spec-driven", "artifacts": [] } ], "root": { "path": "/Users/you/projects/my-app", "source": "nearest" } } ``` If one change can't load, the batch continues. Its entry contains `changeName` and a `status` diagnostic while the other entries remain available. The command exits 1, including in JSON mode, so CI doesn't accept an incomplete report as successful. JSON output remains one parseable document. **Exit codes** * `0`: every requested status printed; an empty `--all` report also exits 0. * `1`: a requested change failed to load, `--change` or `--all` is missing, the two flags were combined, the change doesn't exist, or the schema override is unknown. ## openspec instructions [#openspec-instructions] Prints instructions for creating an artifact, applying, or archiving. Your agent runs this during the workflow to fetch the instruction text for its next step. ```bash openspec instructions proposal --change add-rate-limit # how to write one artifact openspec instructions apply --change add-rate-limit # how to implement the change openspec instructions archive --change add-rate-limit # inputs for archiving ``` **Arguments** | Argument | What it is | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `artifact` | An artifact id from the schema (`proposal`, `specs`, `design`, `tasks` in `spec-driven`), or the reserved words `apply` and `archive`. | **Options** | Flag | Effect | | ----------------- | --------------------------------------------------------------------------- | | `--change ` | The change to generate instructions for. Required. | | `--schema ` | Override the schema. Auto-detected from `config.yaml` otherwise. | | `--json` | Print a structured object instead of text. | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | **Output** The artifact form prints one instruction block: the task, the file to write, how to write it, the artifact's template, and what completing it unlocks. ``` Create the proposal artifact for change "add-rate-limit". Initial proposal document outlining the change Write to: /Users/you/projects/my-app/openspec/changes/add-rate-limit/proposal.md Create the proposal document that establishes WHY this change is needed. ... ``` `apply` prints context files, task progress, and the working instruction: ``` ## Apply: add-rate-limit Schema: spec-driven ### Context Files - proposal: /Users/you/projects/my-app/openspec/changes/add-rate-limit/proposal.md - specs: /Users/you/projects/my-app/openspec/changes/add-rate-limit/specs/api/spec.md - tasks: /Users/you/projects/my-app/openspec/changes/add-rate-limit/tasks.md ### Progress 1/3 complete ### Tasks - [x] 1.1 Add rate limit middleware - [ ] 1.2 Return 429 with Retry-After header - [ ] 1.3 Add tests for burst traffic ### Instruction Read context files, work through pending tasks, mark complete as you go. Pause if you hit blockers or need clarification. No project context or operation guidance configured. ``` When required artifacts are missing, `apply` reports `### ⚠️ Blocked` and names them instead. `archive` prints the change name plus any project context and operation guidance from config. With none configured it says so and nothing more. With `--json`, each form returns one object. The artifact form starts: ```json { "changeName": "add-rate-limit", "artifactId": "proposal", "schemaName": "spec-driven", "changeDir": "/Users/you/projects/my-app/openspec/changes/add-rate-limit", ... ``` and continues with `outputPath`, `existingOutputPaths`, the full `instruction` and `template` strings, `dependencies`, `unlocks`, and `root`. The `apply` form carries `contextFiles`, `progress`, `tasks`, `state` (`blocked`, `ready`, `all_done`), and `instruction`. **Exit codes** * `0`: instructions printed. * `1`: unknown artifact, unknown change, unknown schema, or missing `--change`. Each error lists the valid values. ## openspec templates [#openspec-templates] Prints the resolved template paths for a schema's artifacts. ```bash openspec templates # default schema: spec-driven openspec templates --json # map of artifact ids to paths ``` **Options** | Flag | Effect | | ----------------- | --------------------------------------------------- | | `--schema ` | Schema to resolve. Default: `spec-driven`. | | `--json` | Print a JSON map of artifact ids to template paths. | **Output** ``` Schema: spec-driven Source: package proposal: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/proposal.md specs: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/spec.md design: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/design.md tasks: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/tasks.md ``` `Source` names where the schema resolved from: `project` (`openspec/schemas/` in your project), `user` (a global override), or `package` (built into the CLI). Project wins over user, user over package. ```json { "proposal": { "path": "/usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/proposal.md", "source": "package" }, "specs": { "path": "/usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/spec.md", "source": "package" }, "design": { "path": "/usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/design.md", "source": "package" }, "tasks": { "path": "/usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven/templates/tasks.md", "source": "package" } } ``` **Exit codes** * `0`: paths printed. * `1`: unknown schema. The error lists available schemas. ## openspec schemas [#openspec-schemas] Lists available workflow schemas. ```bash openspec schemas # names, descriptions, artifact order openspec schemas --json # machine-readable, for agent use ``` **Options** | Flag | Effect | | -------- | ------------------------------- | | `--json` | Output as JSON (for agent use). | **Output** ``` Available schemas: spec-driven Default OpenSpec workflow - proposal → specs → design → tasks Artifacts: proposal → specs → design → tasks ``` Schemas from your project are labeled `(project)`, and global overrides are labeled `(user override)`. ```json [ { "name": "spec-driven", "description": "Default OpenSpec workflow - proposal → specs → design → tasks", "artifacts": [ "proposal", "specs", "design", "tasks" ], "source": "package" } ] ``` **Exit codes** * `0`: schemas listed. * `1`: the schema list couldn't be read. ## openspec schema [#openspec-schema] Inspects, forks, or creates a schema (experimental). Every subcommand first prints `Note: Schema commands are experimental and may change.` on stderr. ```bash openspec schema which spec-driven # where a schema resolves from openspec schema fork spec-driven my-flow # copy a schema into the project openspec schema init my-schema # create a schema from scratch ``` | Subcommand | What it does | | ---------- | ----------------------------------------------------------- | | `which` | Show where a schema resolves from. | | `validate` | Check a schema's structure and templates. | | `fork` | Copy an existing schema into the project for customization. | | `init` | Create a new project-local schema. | Schemas resolve from three locations. The first match wins: | Source | Location | | --------- | -------------------------------------------------------------------------------------------- | | `project` | `openspec/schemas/` in the current project. | | `user` | `~/.local/share/openspec/schemas/` (`XDG_DATA_HOME` and Windows `%LOCALAPPDATA%` respected). | | `package` | The schemas shipped with the CLI. `spec-driven` lives here. | ### openspec schema which [#openspec-schema-which] Shows which copy of a schema the CLI will use. ```bash openspec schema which spec-driven openspec schema which --all # every schema, grouped by source ``` **Arguments** | Argument | What it is | | -------- | ------------------------------------------------------------------------------------- | | `name` | The schema to look up. Required unless `--all` is set. Without either, which exits 1. | **Options** | Flag | Effect | | -------- | --------------------------------------------- | | `--all` | List every schema with its resolution source. | | `--json` | Print the resolution as JSON. | **Output** ``` Schema: spec-driven Source: package Path: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven ``` When a higher-priority copy hides another, a `Shadows:` section lists the hidden copies. With `--json`: ```json { "name": "my-flow", "source": "project", "path": "/Users/you/projects/my-app/openspec/schemas/my-flow", "shadows": [] } ``` An unknown name exits 1 and lists the available schemas. ### openspec schema validate [#openspec-schema-validate] Checks a schema's structure and templates. ```bash openspec schema validate spec-driven # one schema, from any source openspec schema validate # every project-local schema ``` It verifies that `schema.yaml` exists and parses, that the structure matches the schema format, that every artifact's template file exists inside the schema's `templates/` directory, and that the dependency graph has no cycles or unknown references. **Options** | Flag | Effect | | ----------- | ------------------------------------------ | | `--json` | Print a structured report instead of text. | | `--verbose` | Print each validation step. | **Output** ``` ✓ Schema 'spec-driven' is valid ``` With no name, each project schema gets one line under a `Validation Results:` header. A failing schema lists its issues and the run exits 1: ``` ✗ Schema 'my-schema' has errors: error: Template file 'tasks.md' not found for artifact 'tasks' ``` ### openspec schema fork [#openspec-schema-fork] Copies an existing schema into the project so you can customize it. ```bash openspec schema fork spec-driven my-flow ``` **Arguments** | Argument | What it is | | -------- | -------------------------------------------------------------------------- | | `source` | The schema to copy, from any source location. | | `name` | Name for the copy. Kebab-case (`my-workflow`). Default: `-custom`. | **Options** | Flag | Effect | | --------- | ----------------------------------------- | | `--force` | Overwrite an existing destination schema. | | `--json` | Print the result as JSON. | **Output** ``` ✔ Forked 'spec-driven' to 'my-flow' Source: /usr/local/lib/node_modules/@fission-ai/openspec/schemas/spec-driven (package) Destination: /Users/you/projects/my-app/openspec/schemas/my-flow ``` The fork lands in `openspec/schemas/`, and the `name:` field in its `schema.yaml` is rewritten to the new name: ``` openspec/schemas/my-flow/ ├── schema.yaml └── templates/ ├── design.md ├── proposal.md ├── spec.md └── tasks.md ``` An existing destination is an error unless you pass `--force`. A fork that keeps the source's name shadows the original. ### openspec schema init [#openspec-schema-init] Creates a new project-local schema with starter templates. ```bash openspec schema init my-schema --description "Lightweight flow" --artifacts proposal,tasks ``` With no `--description` and no `--artifacts` in an interactive terminal, init prompts for a description, an artifact checklist, and whether to make the schema the project default. Outside a terminal it uses the defaults below. **Arguments** | Argument | What it is | | -------- | ---------------------------------------------------- | | `name` | Name for the new schema. Kebab-case (`my-workflow`). | **Options** | Flag | Effect | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--description ` | Schema description. Default: `Custom workflow schema for `. | | `--artifacts ` | Comma-separated artifact IDs from `proposal`, `specs`, `design`, `tasks`. Default: all four. | | `--default` | Writes `schema: ` to the existing `openspec/config.yaml` or `openspec/config.yml`. Creates `openspec/config.yaml` if neither exists. New changes use this schema. | | `--no-default` | Skip the prompt about the default. | | `--force` | Overwrite an existing schema with the same name. | | `--json` | Print the result as JSON. | Schema creation and the `--default` config update are one operation. If OpenSpec cannot validate or write the config, it leaves both the config and any existing schema unchanged. **Output** ``` ✔ Created schema 'my-schema' Schema created at: /Users/you/projects/my-app/openspec/schemas/my-schema Artifacts: proposal, tasks ``` The layout on disk: ``` openspec/schemas/my-schema/ ├── schema.yaml └── templates/ ├── proposal.md └── tasks.md ``` `schema.yaml` wires the selected artifacts with their dependencies. When `tasks` is included it also gets an `apply` phase that tracks `tasks.md`. Use the schema with `openspec new --schema my-schema`. ## openspec store [#openspec-store] Creates and manages stores: standalone OpenSpec repos registered on your machine. ```bash openspec store setup team-context --path ~/openspec/team-context # create and register openspec store register ~/stores/design-system # register an existing checkout openspec store list # see what's registered ``` Registrations live in a per-machine registry: `~/.local/share/openspec/stores/registry.yaml`, or `$XDG_DATA_HOME/openspec/stores/registry.yaml` when `XDG_DATA_HOME` is set. Every subcommand takes `--json` to print a structured report instead of text. Running `openspec store` with a missing or unknown subcommand exits 1 and lists the subcommands. | Subcommand | What it does | | ------------------- | ------------------------------------------------------------------ | | `setup [id]` | Create a store folder and register it. | | `register [path]` | Register an existing store folder. | | `unregister ` | Forget the registration. The folder stays on disk. | | `remove ` | Forget the registration and delete the folder. | | `list` (alias `ls`) | List registered stores. | | `doctor [id]` | Check registration, metadata, and Git state for registered stores. | ### openspec store setup [#openspec-store-setup] Creates a store folder and registers it. ```bash openspec store setup team-context --path ~/openspec/team-context ``` In an interactive terminal, setup prompts for a missing name and location and confirms before creating anything. Outside one, a missing name or `--path` exits 1 with the flag to pass. Rerunning setup for a registered store reports `Registry: already registered`. Setup exits 1 with `store_setup_inside_git_repo` when `--path` is inside another Git repository, because initializing the store there would nest one repository in another. `--no-init-git` creates no repository, so it skips that check. Use it to keep a store at `~/openspec/` when your home directory is itself a Git repository, such as a dotfiles repo. **Arguments** | Argument | What it is | | -------- | -------------------------------------------------------- | | `id` | The store name. It becomes the id you pass to `--store`. | **Options** | Flag | Effect | | ---------------- | ------------------------------------------------------------ | | `--path ` | Folder where the store should live (`~` expands). | | `--init-git` | Initialize a Git repository with an initial commit. Default. | | `--no-init-git` | Skip every Git action: no init, no initial commit. | | `--remote ` | Canonical clone source recorded in `store.yaml`. | **Output** ``` Store ready: team-context Location: /Users/you/stores/team-context OpenSpec root: ready Registry: registered Next: run normal OpenSpec commands against this store, for example: openspec new change --store team-context Share this store by committing and pushing it like any Git repo. ``` `--json` reports what was created and where it was registered: ```json { "store": { "id": "design-system", "root": "/Users/you/stores/design-system", "metadata_path": "/Users/you/stores/design-system/.openspec-store/store.yaml" }, "registry": { "path": "/Users/you/.local/share/openspec/stores/registry.yaml", "registered": true, "already_registered": false }, "git": { "is_repository": true, "initialized": true, "committed": true }, "created_files": [ "openspec/", "openspec/specs/", "openspec/changes/", "openspec/changes/archive/", "openspec/config.yaml", "openspec/specs/.gitkeep", "openspec/changes/archive/.gitkeep", ".openspec-store/store.yaml" ], "status": [] } ``` ### openspec store register [#openspec-store-register] Registers an existing store folder, for example a teammate's store you cloned. ```bash openspec store register ~/stores/design-system ``` The folder must contain a healthy `openspec/` root. With `.openspec-store/store.yaml` present, register reuses the recorded id. Without it, register asks before creating that metadata. Outside an interactive terminal, pass `--yes` instead. A machine can register one checkout per store id. A second path under the same id, or the same path under a second id, exits 1. **Arguments** | Argument | What it is | | -------- | ----------------------------------------------------- | | `path` | The store folder to register (`~` expands). Required. | **Options** | Flag | Effect | | ----------- | --------------------------------------------------------------------- | | `--id ` | Store id. Defaults to metadata or folder name. | | `--yes` | Confirm creating store identity metadata for a healthy OpenSpec root. | **Output** ``` Store registered: design-system Location: /Users/you/stores/design-system OpenSpec root: ready Registry: registered ``` `--json` prints the same document shape as `store setup --json`. ### openspec store unregister [#openspec-store-unregister] Forgets the registration. The folder stays on disk. ```bash openspec store unregister design-system ``` ``` Unregistered store: design-system Files kept at: /Users/you/stores/design-system ``` ### openspec store remove [#openspec-store-remove] Forgets the registration and deletes the folder. ```bash openspec store remove design-system --yes ``` Interactively, remove asks before deleting. With `--json` or outside an interactive terminal, deletion requires `--yes`: ``` Error: Pass --yes to delete store files non-interactively. Fix: openspec store remove design-system --yes ``` Remove exits 1 and deletes nothing when the folder lacks matching store metadata, or when it contains another registered store (for example a store vendored as a Git submodule). In that case the error is `store_remove_contains_registered_store`: run `openspec store unregister ` first, or `openspec store unregister ` to forget the store without deleting files. **Options** | Flag | Effect | | ------- | ------------------------------------ | | `--yes` | Confirm local store folder deletion. | **Output** ``` Removed store: design-system Deleted: /Users/you/stores/design-system ``` ### openspec store list [#openspec-store-list] Lists registered stores. `ls` is an alias. ```bash openspec store list ``` ``` OpenSpec stores (2) ID Location design-system /Users/you/stores/design-system team-context /Users/you/stores/team-context ``` With nothing registered, list prints `No stores registered.` and the setup and register commands to run next. ### openspec store doctor [#openspec-store-doctor] Checks registration, metadata, and Git state for registered stores. ```bash openspec store doctor # every registered store openspec store doctor team-context # one store ``` **Output** ``` Store doctor team-context Location: /Users/you/stores/team-context OpenSpec root: ok Metadata: ok Git: repository detected (commits: yes, uncommitted changes: no, remote: none) Issues: none ``` **Exit codes** * `0`: the report printed, even when a store reports issues. * `1`: the report couldn't run (for example an unknown store id). ## openspec doctor [#openspec-doctor] Reports relationship health for the resolved OpenSpec root. ```bash openspec doctor # nearest openspec/ root above your cwd openspec doctor --store team-context # a registered store as the root ``` Doctor is read-only: it never clones, syncs, or repairs. It reports whether the root is healthy and whether each reference declared in `openspec/config.yaml` resolves on this machine. With no root above your cwd and no `--store`, it exits 1 and names your registered stores. **Options** | Flag | Effect | | -------------- | --------------------------------------------------------------------------- | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | | `--json` | Print the health report as JSON. | **Output** ``` Doctor Root Location: /Users/you/projects/my-app OpenSpec root: ok References - team-context: ok (/Users/you/stores/team-context) ``` With `--store`, the root is the store and the report adds a store line: ``` Using OpenSpec root: team-context (/Users/you/stores/team-context) Doctor Root Location: /Users/you/stores/team-context OpenSpec root: ok Store: team-context (metadata ok) References (none declared) ``` ```json { "root": { "path": "/Users/you/projects/my-app", "source": "nearest", "healthy": true, "status": [] }, "store": null, "references": [ { "store_id": "team-context", "root": "/Users/you/stores/team-context", "status": [] } ], "status": [] } ``` **Exit codes** * `0`: the report printed, including when it lists issues. * `1`: no root resolved (no `openspec/` above your cwd and no `--store`), or an unknown `--store` id. ## openspec context [#openspec-context] Prints the working context for the resolved OpenSpec root: the root plus every referenced store declared in `openspec/config.yaml`, each with a fetch command. ```bash openspec context # nearest openspec/ root above your cwd openspec context --store team-context # a registered store as the root openspec context --json # agent brief ``` References that don't resolve on this machine land in a `Not available on this machine` section, each with a fix. **Options** | Flag | Effect | | ------------------------- | --------------------------------------------------------------------------- | | `--store ` | Use a registered store as the OpenSpec root instead of the current project. | | `--json` | Print the agent brief as JSON. | | `--code-workspace ` | Also write a VS Code workspace file for the set. | | `--force` | Overwrite an existing `--code-workspace` file. | **Output** ``` Working context for my-app (/Users/you/projects/my-app) OpenSpec root my-app /Users/you/projects/my-app Referenced stores team-context /Users/you/stores/team-context Fetch: openspec show --type spec --store team-context ``` With `--store`, the store is the whole set: ``` Using OpenSpec root: team-context (/Users/you/stores/team-context) Working context for team-context (/Users/you/stores/team-context) OpenSpec root team-context /Users/you/stores/team-context No references declared; the working set is this root alone. ``` ```json { "root": { "path": "/Users/you/projects/my-app", "source": "nearest", "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id": "team-context", "path": "/Users/you/stores/team-context", "fetch": "openspec show --type spec --store team-context", "status": [] } ], "status": [] } ``` **Writing a workspace file** `--code-workspace` writes a VS Code workspace file at the path you give: one folder for the root, one `ref:` folder per available referenced store. Unavailable references are skipped and named in the summary line, `Wrote /Users/you/projects/my-app/openspec.code-workspace (2 folders)`. The summary prints on stderr, so `--json` stdout stays one JSON document. An existing file exits 1 unless you pass `--force`. ```json { "folders": [ { "name": "my-app", "path": "/Users/you/projects/my-app" }, { "name": "ref:team-context", "path": "/Users/you/stores/team-context" } ] } ``` **Exit codes** * `0`: the report printed. * `1`: no root resolved (no `openspec/` above your cwd and no `--store`), or the `--code-workspace` write was refused. ## openspec workset [#openspec-workset] Composes, keeps, and opens personal working views. A workset is a saved, named list of folders you work across together. ```bash openspec workset create checkout --member ~/projects/checkout-api --member web=~/projects/checkout-web openspec workset list openspec workset remove checkout --yes ``` | Subcommand | What it does | | --------------- | ------------------------------------------------------------------- | | `create [name]` | Compose and save a named working view of folders you choose. | | `list`, `ls` | Show saved worksets with their members. | | `open ` | Open a saved workset in your tool (editor window or agent session). | | `remove ` | Delete a saved workset (member folders are never touched). | A workset is purely local: * Its state lives in one folder: `~/.local/share/openspec/worksets/` (`$XDG_DATA_HOME/openspec/worksets/` when set; `%LOCALAPPDATA%\openspec\worksets\` on Windows). * Nothing is written into the member folders, and nothing is committed or shared. * Deleting that one folder removes every trace. ### openspec workset create [#openspec-workset-create] Saves a named working view of folders. ```bash openspec workset create checkout \ --member ~/projects/checkout-api \ --member web=~/projects/checkout-web ``` In an interactive terminal, create prompts for whatever the flags didn't provide: the name, folders one at a time, a tool, then an offer to open the workset now. Outside one, a missing name or member is an error. A name that's already saved is always an error. Remove it first. **Arguments** | Argument | What it is | | -------- | ------------------------------------------------------------------------------------------------------------------- | | `name` | The workset name. Kebab-case: lowercase letters, numbers, single hyphens. Required outside an interactive terminal. | **Options** | Flag | Effect | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--member ` | Member folder as `` or `=`, repeatable. The first is the primary. The path must be an existing folder, and the label defaults to the folder's own name. | | `--tool ` | Preferred tool to open this workset with. Built-in ids: `code` (VS Code), `cursor` (Cursor). `claude` and `codex` are temporarily disabled. | | `--json` | Print the saved workset as JSON. | **Output** ``` Saved workset 'checkout' (2 members) to your machine. Open it any time with: openspec workset open checkout ``` ### openspec workset list [#openspec-workset-list] Shows saved worksets with their members, sorted by name. ```bash openspec workset list # alias: ls ``` **Options** | Flag | Effect | | -------- | --------------------------- | | `--json` | Print the worksets as JSON. | **Output** One block per workset: the name, its tool when it has one, then one `name path` row per member. With nothing saved, list prints `No worksets saved. Create one with: openspec workset create`. ``` checkout checkout-api /Users/you/projects/checkout-api web /Users/you/projects/checkout-web checkout-tool (opens in VS Code) checkout-api /Users/you/projects/checkout-api ``` With `--json`: ```json { "worksets": [ { "name": "checkout", "members": [ { "name": "checkout-api", "path": "/Users/you/projects/checkout-api" }, { "name": "web", "path": "/Users/you/projects/checkout-web" } ] } ], "status": [] } ``` ### openspec workset open [#openspec-workset-open] Opens a saved workset in your tool. Editor tools (`code`, `cursor`) get a generated `.code-workspace` file. A window opens and the command returns. CLI agent tools (`claude`, `codex`) would take over this terminal with every member attached. They are temporarily disabled while that flow is reworked, so worksets open in an IDE for now. ```bash openspec workset open checkout # saved tool, or a prompt openspec workset open checkout --tool cursor # this tool just this once ``` **Arguments** | Argument | What it is | | -------- | -------------------- | | `name` | The workset to open. | **Options** | Flag | Effect | | ------------- | ----------------------------------- | | `--tool ` | Open with this tool just this once. | With no `--tool` and no saved tool, open prompts you to pick an installed tool. Outside an interactive terminal it exits 1 instead. * A member folder that no longer exists is skipped with a warning. When the primary is missing, the next surviving member becomes the primary for this open. When no member folder exists, the open fails. * `--json` is rejected: open hands the terminal to the tool and has no JSON mode. * When the launch fails, the error ends with the manual route: the workspace file's path and the member list. **Exit codes** * Mirrors the tool: the command exits with the tool's own exit code, and a signal becomes `128+n` (`130` after Ctrl-C). * `1`: unknown workset, no member folder available, or no usable tool. ### openspec workset remove [#openspec-workset-remove] Deletes a saved workset and its generated `.code-workspace` file. Member folders are never touched. ```bash openspec workset remove checkout --yes ``` In an interactive terminal, remove shows the workset and asks you to confirm. With `--json`, or outside a terminal, it requires `--yes` and exits 1 without it. **Options** | Flag | Effect | | -------- | ---------------------------------- | | `--yes` | Confirm removal non-interactively. | | `--json` | Print the removal as JSON. | **Output** ``` Removed workset 'checkout'. Member folders were not touched. ``` ## openspec feedback [#openspec-feedback] Submits feedback about OpenSpec. ```bash openspec feedback "Validate output is hard to scan" openspec feedback "Archive fails on Windows" --body "Steps: init, propose, archive. Error: EPERM." ``` The CLI files your message as a GitHub issue on the `Fission-AI/OpenSpec` repo through your `gh` CLI. The title becomes `Feedback: `. The body holds your `--body` text plus a footer with CLI version, platform, and timestamp. The issue gets the `feedback` label. If the repo doesn't define that label, the CLI retries without it and says so. **Arguments** | Argument | What it is | | --------- | ---------------------------------------------------- | | `message` | One-line summary. Becomes the issue title. Required. | **Options** | Flag | Effect | | --------------- | ------------------------------------------- | | `--body ` | Longer description added to the issue body. | **Output** On success: ``` ✓ Feedback submitted successfully! Issue URL: https://github.com/Fission-AI/OpenSpec/issues/1234 ``` Without `gh` installed, or with `gh` not logged in, nothing is submitted. The CLI prints your formatted feedback between `--- FORMATTED FEEDBACK ---` markers, then a prefilled new-issue URL to open in the browser. The not-logged-in path adds `To auto-submit in the future: gh auth login`. **Exit codes** * `0`: issue created, or the manual-submission fallback ran (no `gh`, or `gh` not logged in). * `1`: no message given. * `gh`'s own code: `gh` failed after authentication (network, rate limit, issues disabled). The CLI reprints your feedback and the manual-submission URL first. ## openspec completion [#openspec-completion] Installs or generates shell completions. ```bash openspec completion install # detect your shell, install, wire up config openspec completion generate zsh # print the script to stdout ``` Supported shells: `zsh`, `bash`, `fish`, `powershell`. Every subcommand takes an optional shell argument. Omit it and the CLI detects your shell from the environment. | Subcommand | What it does | | ------------------- | ------------------------------------------------------- | | `generate [shell]` | Print the completion script to stdout. | | `install [shell]` | Write the script and configure your shell startup file. | | `uninstall [shell]` | Remove the script and the config block. | Installed with Nix, completions are already in place: the flake package ships the Bash, Fish, and Zsh scripts at the standard locations, so `install` is not needed ([Installation](/docs/installation#nix)). ### openspec completion generate [#openspec-completion-generate] Prints the script and writes nothing. ``` #compdef openspec # Zsh completion script for OpenSpec CLI # Auto-generated - do not edit manually _openspec() { local context state line typeset -A opt_args ... ``` ### openspec completion install [#openspec-completion-install] Writes the script and edits your shell config. Config edits sit between `# OPENSPEC:START` and `# OPENSPEC:END` markers. An existing script is backed up first (`.backup-` copy). | Shell | Script location | Config edited | | ---------- | ----------------------------------------------------- | ------------------------- | | zsh | `~/.zsh/completions/_openspec` | `~/.zshrc` | | bash | `~/.local/share/bash-completion/completions/openspec` | `~/.bashrc` | | fish | `~/.config/fish/completions/openspec.fish` | None: fish auto-loads it. | | powershell | `OpenSpecCompletion.ps1` beside your profile | `$PROFILE` | With Oh My Zsh installed, the script lands in `$ZSH_CUSTOM/completions/_openspec` instead (default `~/.oh-my-zsh/custom/completions/_openspec`). **Options** | Flag | Effect | | ----------- | --------------------------------------------------------------------------------- | | `--verbose` | Also print the installed path, any backup path, and which config file was edited. | **Output** ``` ✓ Completion script installed and .zshrc configured successfully Restart your shell or run: exec zsh ``` ### openspec completion uninstall [#openspec-completion-uninstall] Removes the script and the marked config block. It asks before touching your config (default: No). **Options** | Flag | Effect | | ----------- | -------------------------- | | `-y, --yes` | Skip confirmation prompts. | **Output** ``` ✓ Completion script removed from /Users/you/.zsh/completions/_openspec. Removed OpenSpec configuration from ~/.zshrc ``` **Exit codes** * `0`: script generated, installed, or removed. A cancelled uninstall also exits 0. * `1`: shell not supported or not detected, or an install or uninstall step failed. ## openspec change [#openspec-change] Deprecated noun form of `show`, `list`, and `validate`. Every run warns and points to the verb-first commands, then runs anyway: ``` Warning: The "openspec change ..." commands are deprecated. Prefer verb-first commands (e.g., "openspec list", "openspec validate --changes"). Warning: "openspec change list" is deprecated. Use "openspec list". add-rate-limit ``` | Deprecated | Use instead | | --------------------------------- | ----------------------------------------------------------------------- | | `openspec change show ` | `openspec show ` | | `openspec change list` | `openspec list` | | `openspec change validate ` | `openspec validate ` (all changes: `openspec validate --changes`) | The verb-first sections document the flags. ## openspec spec [#openspec-spec] Deprecated noun form of `show`, `list`, and `validate`. Every run warns and points to the verb-first commands, then runs anyway: ``` Warning: The "openspec spec ..." commands are deprecated. Prefer verb-first commands (e.g., "openspec show", "openspec validate --specs"). api ``` | Deprecated | Use instead | | ----------------------------- | ----------------------------------------------------------------- | | `openspec spec show ` | `openspec show ` | | `openspec spec list` | `openspec list --specs` | | `openspec spec validate ` | `openspec validate ` (all specs: `openspec validate --specs`) | The verb-first sections document the flags. # Schemas (/docs/customize-schemas) A schema defines what a change proposal produces: which artifacts, in what order, from which templates. For example, [spec-driven](/docs/schemas/spec-driven), the default bundled schema, produces these four in roughly this order, each building on what came before: ``` proposal → specs → design → tasks ``` Fork a schema when you want these to be different documents, whether that means fewer of them, different names, or a different structure. ## Where schemas live [#where-schemas-live] OpenSpec looks for a schema in three places, in order, and uses the first one it finds: 1. **Your project**: `openspec/schemas/`, committed with the repo so your whole team gets it. 2. **Your machine**: `~/.local/share/openspec/schemas` on macOS and Linux (or under `$XDG_DATA_HOME` if you set it), or `%LOCALAPPDATA%\openspec\schemas` on Windows. Schemas here are available in every project you work in. 3. **The package**: the built-ins, like `spec-driven`, ship inside openspec itself. The same name can exist in more than one place, and the more specific location wins. `openspec schema which` shows which copy is in use: ``` $ openspec schema which spec-driven Schema: spec-driven Source: project Path: /your-project/openspec/schemas/spec-driven Shadows: package: .../openspec/schemas/spec-driven ``` ## What's in a schema [#whats-in-a-schema] A schema is defined by a folder of plain files: one schema.yaml that declares the artifacts, and a template for each of them. Here's the built-in `spec-driven`: ``` spec-driven/ ├── schema.yaml └── templates/ ├── proposal.md ├── spec.md ├── design.md └── tasks.md ``` * **schema.yaml**: declares each artifact, the file it generates, the template it starts from, what it requires first, and the instruction the agent receives when creating it. Every field's contract is in [schema.yaml](/docs/schemas/schema-yaml). * **templates/**: one markdown skeleton per artifact, which the agent fills in. Here's the tasks artifact's entry in schema.yaml, trimmed: ```yaml artifacts: - id: tasks generates: tasks.md description: Implementation checklist with trackable tasks template: tasks.md instruction: | ...what the agent is told when creating tasks.md... requires: - specs - design ``` The built-in schemas ship inside the openspec package, so you never edit them in place. You get your own copy by forking. ## Creating your own custom schema [#creating-your-own-custom-schema] There are two ways to get your own schema: 1. **Fork an existing schema** and edit your copy. Start here when an existing schema is close to what you want, because everything in it already works. 2. **Start from scratch** when none of them fit, scaffolding an empty schema with `openspec schema init`. ### Fork an existing schema [#fork-an-existing-schema] 1. Fork the schema you want to start from, running from your project root: ```console $ openspec schema fork spec-driven Note: Schema commands are experimental and may change. ✔ Forked 'spec-driven' to 'spec-driven-custom' Source: .../openspec/schemas/spec-driven (package) Destination: /your-project/openspec/schemas/spec-driven-custom ``` Pass a second argument to pick the name (`openspec schema fork spec-driven team-flow`). Names are kebab-case. 2. Edit the copy: schema.yaml and the templates. [Editing your fork](#editing-your-fork) covers what to change. 3. Validate it: ```bash openspec schema validate spec-driven-custom ``` This is the one command that catches a broken schema (missing templates, bad YAML, dependency cycles) before you're in the middle of a change. 4. Point your project at it in openspec/config.yaml. This step is yours to do because fork leaves config.yaml untouched: ```yaml schema: spec-driven-custom ``` 5. New change proposals now follow your schema. Changes created earlier keep the schema they started with. To replace the default everywhere without touching config.yaml, fork to the same name: `openspec schema fork spec-driven spec-driven`. Your project's copy then shadows the built-in, as [Where schemas live](#where-schemas-live) explains. ### Start from scratch [#start-from-scratch] `openspec schema init` scaffolds a new schema instead of copying one: ```console $ openspec schema init lite --description "Lite flow" --artifacts proposal,tasks ✔ Created schema 'lite' Schema created at: /your-project/openspec/schemas/lite Artifacts: proposal, tasks ``` The scaffold is bare. Artifacts come from the built-in four ids only, and the generated templates carry no instructions, so the agent gets less guidance until you write your own. From there the fork steps apply unchanged: validate it, then point config.yaml at it. ## Editing your fork [#editing-your-fork] A fork has two kinds of files to edit: * **templates/** change the skeleton of each document. Add a section to the tasks template and every new tasks.md starts with it. * **schema.yaml** changes the workflow itself: which artifacts exist, what each one requires first, and the instruction the agent gets when creating it. For example, to drop the design document for a leaner flow: 1. Delete the `design` entry from schema.yaml. 2. Remove `design` from the `requires` list of `tasks`. 3. Validate: ```console $ openspec schema validate spec-driven-custom ✓ Schema 'spec-driven-custom' is valid ``` Skip step 2 and validate catches it: ```console ✗ Schema 'spec-driven-custom' has errors: error: Invalid dependency reference in artifact 'tasks': 'design' does not exist ``` Validate after every hand-edit. A broken schema otherwise surfaces in the middle of a change, when a workflow asks for a file that isn't there. Like config.yaml, schema edits reach the agent on the next run. ## A fork is a snapshot [#a-fork-is-a-snapshot] `openspec update` refreshes the installed skills and commands, and it never touches `openspec/schemas/`. Your fork keeps working exactly as you left it, which also means it stops receiving improvements when the built-in schema evolves. To pick those up later, fork the built-in again under a new name and port the differences across. ## Sharing schemas [#sharing-schemas] Sharing a schema means copying its folder. * **With your team**: commit `openspec/schemas/` and everyone on the repo uses it. * **Across your projects**: put the folder in the user-level directory from [Where schemas live](#where-schemas-live). * **From the community**: the [community catalog](https://github.com/Fission-AI/OpenSpec/blob/main/docs/customization.md#community-schemas) lists shared schemas. Copy one into `openspec/schemas/` and it works like your own. We're working on a schema registry, public and private, so schemas can be installed by name instead of copied by hand. # Overview (/docs/customize) OpenSpec supports multiple customization options. This page shows what each one changes and when to use it. ## What you can customize [#what-you-can-customize] | Option | What it changes | Use it when | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | [Profiles](/docs/profiles) | Which workflows are installed, and whether as skills, commands, or both | You want additional workflows and working patterns, or to remove workflows you don't need | | [Project configuration](/docs/project-config) | The instructions injected into every workflow run: context, rules, and operation guidance (`config.yaml`) | You want changes planned your way, like tasks always including Playwright tests | | [Schemas](/docs/customize-schemas) | What OpenSpec produces: the artifacts, their order, and their templates | Changes should produce different planning files, sections, or formats | ## Not sure which to use? [#not-sure-which-to-use] Config and schemas are two levels of customization. Pick by how hands-on you want to get: * **Start with [project configuration](/docs/project-config)**: it's lighter, and for most projects it's enough. You keep the standard artifacts and add your own context and rules on top. * **Fork a [schema](/docs/customize-schemas) when adding isn't enough**: config only adds on top of the core workflow. It can add a rule like "tasks always include tests," but it can't drop the design doc or rename a file. That's schema territory. Forking gives you your own copy to edit. *"Fork" here means the `openspec schema fork` command, not forking a git repo. [Schemas](/docs/customize-schemas) has the details.* ```mermaid flowchart LR a["The workflows should know my stack and conventions"] --> config b["One artifact needs an extra rule, like tasks always including tests"] --> config c["Different artifacts, file names, or document structure"] --> schema d["The built-in instructions say things my team does differently"] --> schema config["Project configuration
(config.yaml)"] schema["Fork a schema
(openspec schema fork)"] ``` # Glossary (/docs/glossary) OpenSpec reuses words that mean something else in git, CI, and agent tooling. Each row gives the OpenSpec meaning, and the last column links to the page that teaches the term. | Term | Definition | More | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | **Apply** | Implement the tasks in a change proposal. Skill: `openspec-apply-change`. | [Apply a change](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/apply.md) | | **Archive** | Complete a change proposal: merge its deltas into the main specs and move its folder to `openspec/changes/archive/`. | [Quickstart](/docs/quickstart) | | **Artifact** | A planning document inside a change proposal: `proposal.md`, delta specs, `design.md`, `tasks.md`. Not a build output. | [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) | | **Capability** | One behavior area of your system. Each has one spec at `openspec/specs//spec.md`. | [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) | | **Change proposal** | One unit of work: a folder under `openspec/changes//` holding its planning artifacts. Often shortened to "change". Not a git commit. | [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) | | **Command** | A typed entry point for a workflow. Spelling varies per tool (`/opsx:propose`, `/opsx-propose`). The docs name workflows by skill instead. | [Supported tools](/docs/supported-tools) | | **Continue** | Create the next planning artifact for an existing change proposal. Skill: `openspec-continue-change`. | [Skills](/docs/skills) | | **Delivery** | How workflows are installed: as skills, commands, or both. | [Set up your project](/docs/setup) | | **Delta spec** | A spec inside a change proposal listing only what changes, under `ADDED`, `MODIFIED`, `REMOVED`, and `RENAMED` headers. | [Delta specs](/docs/schemas/spec-driven#delta-specs-specmd) | | **Explore** | Think an idea through with the agent before proposing. Writes no code. Skill: `openspec-explore`. | [Explore an idea](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/explore.md) | | **Fast-forward** | Create a change proposal with every planning artifact in one pass, ready to implement. Skill: `openspec-ff-change`. Not a git fast-forward. | [Skills](/docs/skills) | | **Legacy workflow** | The pre-OPSX `/openspec:*` commands. | [Migration](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/help/legacy/migration.md) | | **Loop** | The cycle a change proposal moves through: explore, propose, review, apply, archive. | [Quickstart](/docs/quickstart) | | **Main specs** | The `openspec/specs/` tree: the current, agreed behavior of your system. Archiving merges deltas into it. | [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) | | **OpenSpec root** | The `openspec/` tree a command resolves to and operates on: your repo's, or a store's. | [Stores](/docs/stores#where-artifacts-get-created-when-using-stores) | | **OPSX** | The current OpenSpec workflow system, and the command prefix it installs (`/opsx:`). | [Architecture](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/reference/architecture/index.md) | | **Profile** | Which workflows init installs: `core` or `custom`. | [Profiles](/docs/profiles) | | **Propose** | Create a change proposal and generate all its planning artifacts in one step. Skill: `openspec-propose`. | [Quickstart](/docs/quickstart) | | **Registry** | The machine-level list of registered stores, in `registry.yaml`. Not a package registry. | [CLI](/docs/cli#openspec-store) | | **Requirement** | One behavior the system must have, written with SHALL: `### Requirement:` in a spec. | [Delta specs](/docs/schemas/spec-driven#delta-specs-specmd) | | **Scenario** | A testable example under a requirement, in WHEN/THEN form. | [Delta specs](/docs/schemas/spec-driven#delta-specs-specmd) | | **Schema** | The definition of which artifacts a change proposal produces, and in what order. Not JSON Schema. | [Schemas](/docs/schemas) | | **Skill** | A workflow's instructions, installed where your AI tool reads them (`.agents/skills/`, ...). | [Skills](/docs/skills) | | **Spec** | A file describing how one capability behaves today, at `openspec/specs//spec.md`. | [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) | | **spec-driven** | The default schema: proposal, then delta specs, then design, then tasks. | [spec-driven](/docs/schemas/spec-driven) | | **Store** | A standalone OpenSpec repo registered on your machine, for planning that spans repositories. Not a data store. | [Stores (beta)](/docs/stores) | | **Sync** | Merge implemented deltas into the main specs without archiving. Skill: `openspec-sync-specs`. | [Skills](/docs/skills) | | **Template** | The starting content a schema gives each artifact. | [Schemas](/docs/customize-schemas) | | **Update** | As a skill (`openspec-update-change`): revise a change proposal's planning artifacts. As a CLI command (`openspec update`): refresh OpenSpec's installed files. | [Change course](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/change-course.md), [CLI](/docs/cli) | | **Verify** | Check the implementation matches a change proposal's artifacts before archiving. Skill: `openspec-verify-change`. | [Skills](/docs/skills) | | **Workflow** | A named OpenSpec action (propose, apply, archive, ...), installed into your AI tool as a skill or command. | [Set up your project](/docs/setup) | | **Workset** | A personal, local group of folders opened together in one tool. Not a store, and nothing is shared. | [Worksets (beta)](/docs/worksets) | # Installation (/docs/installation) ## Prerequisites [#prerequisites] OpenSpec is a Node.js CLI. You need version 20.19.0 or newer. In your terminal: ```bash node --version ``` If that prints `v20.19.0` or higher, you're set. If not, install a newer Node from [nodejs.org](https://nodejs.org) or through your version manager (nvm, fnm, asdf, volta). The workflow itself runs inside an AI coding tool: Claude Code, Cursor, or any other tool on the [supported list](/docs/supported-tools). ## Install with your AI assistant [#install-with-your-ai-assistant] Paste this into your AI chat: ```text Fetch https://raw.githubusercontent.com/Fission-AI/OpenSpec/main/install.md and follow it. ``` Or, in your terminal, pipe it into a CLI agent (Claude Code shown): ```bash curl -fsSL https://raw.githubusercontent.com/Fission-AI/OpenSpec/main/install.md | claude ``` That fetches [install.md at the repo root](https://github.com/Fission-AI/OpenSpec/blob/main/install.md), a prompt written for any agent that can run shell commands (a few IDE integrations can't). Expect your assistant to: 1. Check your Node version, and stop if it's older than 20.19.0. 2. Skip the install if the CLI is already on your machine. Otherwise, show you the install command and wait for your confirmation before running it. 3. Verify `openspec` is on your PATH. 4. Name the folder it thinks you mean, suggest the AI tool you're already talking to, and ask which others you use, then run `openspec init` there (the [project setup](/docs/setup) step). 5. Report what init created and the exact spelling to invoke OpenSpec in your tool. It stops before anything privileged and never edits your shell startup files. The [manual methods below](#install-methods) are the source of truth, and the prompt runs them for you. This install method is new and can have varying results depending on model used. Only use if you're comfortable correcting AI mistakes. Otherwise we recommend following the standard method below. ## Install methods [#install-methods] Install the CLI globally; [setting up your project](/docs/setup) comes after. In your terminal: npm pnpm yarn bun ```bash npm install -g @fission-ai/openspec@latest ``` ```bash pnpm add -g @fission-ai/openspec@latest ``` ```bash yarn global add @fission-ai/openspec@latest ``` ```bash bun add --global @fission-ai/openspec@latest ``` ### Yarn [#yarn] `yarn global add` is Yarn Classic (1.x) only. Modern Yarn removed global installs, so use npm, pnpm, or bun instead. A global CLI doesn't have to share your project's package manager. ### Bun [#bun] Bun installs OpenSpec but doesn't run it, so you still need Node on your machine (the [prerequisite](#prerequisites) above). Without it, every command fails with `env: node: No such file or directory`. Bun treats [every Node CLI](https://bun.com/docs/pm/bunx#shebangs) this way. ### Deno [#deno] Deno installs the CLI from npm and needs explicit permission flags. In your terminal: ```bash deno install --global \ --allow-read --allow-write --allow-env --allow-sys=cpus,homedir --allow-net=edge.openspec.dev \ npm:@fission-ai/openspec@latest ``` Some commands launch another program: [`openspec config edit`](/docs/cli) opens your editor. Deno interrupts those with a permission prompt on every run. To stop it asking, add a scoped `--allow-run=` to the install command. > [!NOTE] > If Deno can't resolve `@latest`, pin a version range instead: `npm:@fission-ai/openspec@^1.7.0`. ### Nix [#nix] The OpenSpec repo ships a Nix flake. Install it into your profile. In your terminal: ```bash nix profile install github:Fission-AI/OpenSpec ``` Or run a one-off command first, without installing: ```bash nix run github:Fission-AI/OpenSpec -- --version ``` That leaves nothing on your PATH, so there's no install to check afterward. To put OpenSpec in a project dev shell instead, add the flake as an input and use its default package; [flake.nix](https://github.com/Fission-AI/OpenSpec/blob/main/flake.nix) lists the outputs. The Nix package ships the Bash, Fish, and Zsh completion scripts at the standard locations (`share/bash-completion/completions`, `share/fish/vendor_completions.d`, `share/zsh/site-functions`), so they load with the package and there is no need to run `openspec completion install`. ### Check it worked [#check-it-worked] Whichever method you used, in your terminal: ```bash openspec --version ``` If that prints a version number, the CLI is on your PATH. It installs once per machine. Next, [set up your project](/docs/setup). If your assistant already ran init, that page shows what it wrote and how to adjust it. ## Updating [#updating] In your terminal, in each project where you ran init: ```bash openspec update ``` When a newer CLI is out, [`openspec update`](/docs/cli#openspec-update) says so and can install it for you; that upgrade is once per machine. Every run refreshes the project's generated skills and commands, which never update on their own. A current project prints `✓ All 2 tool(s) up to date (v1.7.0)`. > [!WARNING] > On Deno, re-run the [Deno install](#deno) with `-f`; it won't overwrite the installed command without it. On Nix, use `nix profile upgrade openspec`. > [!NOTE] > A global npm install belongs to one Node installation. Switch Node versions with nvm and the `openspec` command doesn't come along, so install it again under the new version. ## Uninstalling [#uninstalling] To uninstall OpenSpec, run through the steps below; none of them touch your source code. You can also point your agent at this section and let it handle the removal. **1. Remove [shell completions](/docs/cli#openspec-completion)**, if you set them up, while the CLI can still do it. In your terminal: ```bash openspec completion uninstall ``` **2. Remove the package.** In your terminal: npm pnpm yarn bun ```bash npm uninstall -g @fission-ai/openspec ``` ```bash pnpm remove -g @fission-ai/openspec ``` ```bash yarn global remove @fission-ai/openspec ``` ```bash bun remove --global @fission-ai/openspec ``` On Deno: `deno uninstall --global openspec`. On Nix: `nix profile remove openspec`. Your shell should no longer find `openspec`. **3. Delete what's left, or keep it.** * Generated agent files: `openspec-*` skills and `opsx` commands under directories like `.claude/` or `.agents/`, per project. [Supported tools](/docs/supported-tools) lists each tool's paths; MiniMax Code keeps skills in `~/.minimax/skills`. * Leftovers from older versions: marker blocks in `CLAUDE.md` or `AGENTS.md` (delete the block, keep the file) and `opsx-*.md` prompts in `~/.codex/prompts`. * The `openspec/` folder: pause first. `specs/` and `changes/archive/` are your record of the system, plain Markdown that reads fine without OpenSpec. * Per-machine state: settings and the telemetry id in `~/.config/openspec/`; schema overrides and store registrations in `~/.local/share/openspec/` (Windows: `%APPDATA%\openspec`, `%LOCALAPPDATA%\openspec`). Registrations are pointers; the store repos they point to are untouched. # Profiles (/docs/profiles) A profile is your preference for which OpenSpec workflows (the [skills and commands](/docs/setup#the-workflow-files-skills-and-commands) in your AI tool) are installed across your machine. The default profile is `core`. Include or exclude workflows and your selection is saved as the `custom` profile. ## The core set [#the-core-set] The `core` profile installs six workflows, covering the whole loop from idea to archive: | Workflow | What it's for | | ------------------------------------------------- | ---------------------------------------------------------------------------- | | [`explore`](/docs/skills#openspec-explore) | Think through an idea before it becomes a change proposal | | [`propose`](/docs/skills#openspec-propose) | Create a change proposal and generate all its planning artifacts in one step | | [`apply`](/docs/skills#openspec-apply-change) | Implement a change proposal's tasks | | [`update`](/docs/skills#openspec-update-change) | Revise a change proposal's existing planning artifacts | | [`sync`](/docs/skills#openspec-sync-specs) | Merge a change proposal's spec updates into `specs/` without archiving it | | [`archive`](/docs/skills#openspec-archive-change) | Move a finished change proposal to the archive | Each links to its full contract: arguments, what it creates, and what it responds with. ## Expanding the set: optional workflows [#expanding-the-set-optional-workflows] Six more workflows are available beyond the core set. Three of them (`new`, `continue`, `ff`) create a change proposal artifact by artifact, instead of all at once like `propose`. | Workflow | What it's for | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [`new`](/docs/skills#openspec-new-change) | Start a change proposal as an empty scaffold | | [`continue`](/docs/skills#openspec-continue-change) | Create the next planning artifact in a change proposal, one at a time | | [`ff`](/docs/skills#openspec-ff-change) | Create a change proposal and every planning artifact implementation needs, in one pass | | [`verify`](/docs/skills#openspec-verify-change) | Check that the implementation matches the change proposal's artifacts | | [`bulk-archive`](/docs/skills#openspec-bulk-archive-change) | Archive several change proposals at once | | [`onboard`](/docs/skills#openspec-onboard) | Learn the workflow by doing one real change proposal end to end | To change the set, run the interactive picker: ```bash openspec config profile ``` The picker asks what to configure ([delivery](#delivery-skills-commands-or-both), workflows, or both), then lists all twelve workflows as checkboxes, with the installed ones checked. Any selection that isn't exactly the core six is saved as the `custom` profile, so you can also uncheck core workflows you don't use. ## Delivery: skills, commands, or both [#delivery-skills-commands-or-both] Delivery is a profile setting that lets you choose to have only skills or only commands installed. The default is `both`. [Set up your project](/docs/setup#the-workflow-files-skills-and-commands) explains the two forms and why both exist. The field's exact contract is in [CLI settings (config.json)](/docs/configuration/config-json#delivery). Two ways to change it: **Interactively**: run `openspec config profile` and choose "Delivery only". Here's switching to skills only: ``` Current profile settings Delivery: both ? What do you want to configure? Delivery only ? Delivery mode (how workflows are installed): Skills only Config changes: delivery: both -> skills ? Apply changes to this project now? (Y/n) y ``` **Directly**: one command, no prompts: ```bash openspec config set delivery skills # or: both, commands ``` Delivery never changes the profile name. `core` and `custom` describe the workflow set only, and switching back to `core` keeps your delivery setting. ## Switching profiles [#switching-profiles] Switching is two steps: change the profile on your machine, then update each project to apply it. 1. Change the profile: ```bash openspec config profile # interactive openspec config profile core # reset to the core six (keeps delivery) ``` 2. Run the update in each project you work in: ```bash openspec update ``` When your current directory is an existing OpenSpec project, the interactive flow offers to run step 2 there for you. # Project configuration (/docs/project-config) `openspec/config.yaml` tells the workflows how you want changes planned. For example, the following configuration updates the creation rules for the [tasks.md](/docs/schemas/spec-driven) artifact: ```yaml rules: tasks: - End every task with a commit ``` When the agent runs, it pulls from these rules and ensures every task ends with a commit step. Keep rules short. Everything here lands in the agent's context, and verbose rules can make the output worse. ## How it works [#how-it-works] config.yaml holds instructions the agent receives when it creates artifacts or works through the workflow. Here's what happens on every run: 1. You run a workflow (e.g. `/openspec-propose`). 2. The agent calls the [`openspec instructions`](/docs/cli) command. 3. The command reads your context and rules from config.yaml. 4. OpenSpec's built-in instructions and your customizations are combined into a single prompt for the agent. 5. The agent follows that prompt to write the artifact. For example, with a `context` field and the rule from the top of this page, here's what [`openspec instructions`](/docs/cli) returns for tasks.md (trimmed and annotated): ```xml Tech stack: TypeScript, Node.js Domain: e-commerce platform - End every task with a commit ...how to write a good tasks.md... ``` Your config arrives first, then OpenSpec's built-in instruction and template. Rules add to the built-ins and never replace them. Edits to config.yaml reach the agent on the next run. [Workflow runs](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/reference/architecture/workflow-runs.md) covers the full run, from invocation to written artifacts. ## The fields [#the-fields] Three fields shape what the agent receives. Each field's exact contract (types, limits, validation) is in [Project configuration (config.yaml)](/docs/configuration/config-yaml). | Field | What it does | Injected into | | ------------ | ----------------------------------------------- | ---------------------------------------------- | | `context` | Instructions the agent always receives | Everything: every artifact, `apply`, `archive` | | `rules` | Extra instructions for one artifact | Only that artifact's creation | | `operations` | Guidance for how a workflow step is carried out | Only `apply` and `archive` | config.yaml's other fields (`schema`, `store`, `references`) select which schema and which OpenSpec root a project uses. The contract page covers them. The last column is exact, so a field reaches only the steps listed there. In particular, `verify` never receives `rules`. It checks the implementation against the artifacts as written. ### context [#context] `context` is what the agent should know up front when planning a change, whether it's creating an artifact, applying tasks, or archiving: ```yaml context: | We ship cross-platform; designs and tasks must cover Windows, macOS, and Linux Tech stack: TypeScript, Node.js, Commander.js We use conventional commits ``` This is planning context, not project documentation. Add a fact when it should shape every plan, like the cross-platform line above. Leave out anything the agent can learn by reading the code. **Another language**: because context reaches every artifact, it's also how you change the output language. One line, like `Write all artifacts in Spanish.`, switches every proposal, spec, and tasks file the workflows write. ### rules [#rules] `rules` attach to one artifact, keyed by artifact id. Each line is added to that artifact's built-in guidance: ```yaml rules: proposal: - Keep proposals under 500 words tasks: - Every UI task includes a Playwright test ``` Proposals now stay short and tasks.md always plans browser tests. Every other artifact is untouched. ### operations [#operations] `operations` guides how the agent carries out `apply` and `archive`, rather than what artifacts say: ```yaml operations: apply: guidance: - Run the linter before marking a task complete archive: guidance: - Summarize what shipped before archiving ``` During apply, the agent lints as it completes tasks. During archive, it closes with a summary. ## When config.yaml isn't enough [#when-configyaml-isnt-enough] Config adds instructions on top of the standard workflow, but it can't change which artifacts exist or how they're structured. When you want that level of control, or rules aren't steering behavior consistently, [fork a schema](/docs/customize-schemas). # Quickstart (/docs/quickstart) Before you start, you need the CLI on your machine ([Installation](/docs/installation)) and OpenSpec initialized in your project ([Set up your project](/docs/setup)). ## The loop at a glance [#the-loop-at-a-glance] Every change moves through the same five steps: you think the idea through with your agent, it drafts a plan, you correct the plan before any code exists, the agent builds from it, and archiving updates your specs with what shipped. ```mermaid flowchart LR explore["1 · Explore
think it through together"] --> propose["2 · Propose
agent drafts the plan"] propose --> review["3 · Review
you correct the plan"] review --> apply["4 · Apply
agent builds, task by task"] apply --> archive["5 · Archive
specs absorb the change"] archive -. "next change" .-> explore ``` Every prompt below goes in your AI chat, the same place you ask for code. Each invokes an OpenSpec skill by name, the same spelling in every tool. A plain ask works too ("propose a change to add rate limiting"). Some tools add shorter command aliases (`/opsx:propose` in Claude Code, [other tools vary](/docs/supported-tools)). ## Step 1: Explore [#step-1-explore] Think the idea through with your agent before you ask for a plan. In your AI chat: ```text /openspec-explore how rate limiting should work in this app ``` Explore is a thinking mode. The agent investigates your codebase, asks the questions that matter, sketches options, and challenges assumptions. It never writes code. It writes nothing else unless you ask it to capture what you decided, or say yes when it offers. The output is a sharper idea. Stay here as long as the problem needs. When the shape feels right, hand it off: ```text /openspec-propose ``` That line starts propose for you, carrying everything you settled. Skip the first prompt in step 2. ## Step 2: Propose [#step-2-propose] Propose turns the idea into a reviewable plan. Coming from explore, it's already running. Starting cold, when the change is clear in your head, ask directly. In your AI chat: ```text /openspec-propose add rate limiting ``` The agent asks what it needs to, then writes a change folder: ``` openspec/changes/add-rate-limiting/ ├── proposal.md why, and what changes ├── specs/ what "done" means, as testable requirements ├── design.md technical decisions (only when the change needs one) └── tasks.md the implementation checklist ``` No code yet. Propose stops at the plan. ## Step 3: Review and correct the plan [#step-3-review-and-correct-the-plan] Fix the plan while it's still words and nothing is built yet. Read in this order: * **`proposal.md`**: is this the right problem, at the right size? * **`specs/`**: the highest-value read. Would you accept these requirements as done? * **`tasks.md`**: do the tasks cover the specs, and nothing more? To fix something, either works: * Edit the file yourself. The artifacts are plain markdown, and the files are the plan. * Tell your agent what's wrong ("the spec is missing the unauthenticated case"). It revises the artifacts. ## Step 4: Apply [#step-4-apply] Apply turns the plan into code. Start a fresh chat session, since implementation goes better on a clean context window. In your AI chat: ```text /openspec-apply-change add-rate-limiting ``` The agent reads the change folder, then works through `tasks.md`, checking off each task as it lands. * **Interrupted, or out of context?** Open a new session and ask it to apply again. It resumes at the first unchecked task. * **Plan turned out wrong?** Fix the artifacts (either way from step 3), then continue applying. * **Progress** lives in the `tasks.md` checkboxes. There is no hidden state. ## Step 5: Archive [#step-5-archive] Archiving does two things: it updates your main specs with the change's requirements, and it moves the change folder into the archive folder (in `/openspec/changes/archive/*`). When every box in `tasks.md` is checked, in your AI chat: ```text /openspec-archive-change add-rate-limiting ``` Step through what archiving does: ```file-steps ## The finished change > Implementation is done. The delta spec (what this change adds) still sits inside the change folder; specs/ doesn't know about rate limiting yet. openspec/ ├── specs/ (no rate-limiting spec yet) └── changes/ └── add-rate-limiting/ ├── proposal.md ├── tasks.md every box checked └── specs/ └── rate-limiting/ └── spec.md the delta: ADDED requirements ## Requirements land in specs/ > Each requirement in the delta lands in the main spec: added ones append, modified ones replace their old version. A new capability gets a new spec file. openspec/ ├── specs/ + │ └── rate-limiting/ + │ └── spec.md gains "Requirement: Rate limiting" └── changes/ └── add-rate-limiting/ └── specs/ └── rate-limiting/ └── spec.md the delta, source of the merge ## The folder moves to archive/ > The whole change folder, delta included, moves into the archive under a date prefix. Nothing is deleted. openspec/ ├── specs/ │ └── rate-limiting/ │ └── spec.md └── changes/ - └── add-rate-limiting/ + └── archive/ + └── 2026-08-08-add-rate-limiting/ + ├── proposal.md + ├── tasks.md + └── specs/rate-limiting/spec.md ## Specs describe the system as built > changes/ is clear for the next change. specs/ is the source of truth for what the system does; archive/ is the history of how it got there. openspec/ ├── specs/ │ └── rate-limiting/ │ └── spec.md the spec as built └── changes/ └── archive/ └── 2026-08-08-add-rate-limiting/ ``` Git is a separate concern. Commit the change folder with the code, and nothing else about your workflow changes. When to archive relative to a PR is a team convention; the [Teams](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/teams.md) guide has the tradeoff. ## Going further [#going-further] * [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md): what the two artifacts are, and how a delta describes a change. * [Explore](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/explore.md): getting more out of explore mode. * [Apply](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/apply.md): pacing, context windows, resuming long changes. * [Review the plan](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/review-the-plan.md): what to look for in specs before you build. * [Profiles](/docs/profiles): optional workflows beyond the core set (verify before archive, incremental planning). ## Advanced guides [#advanced-guides] Not written yet; guides we plan to add: * **Prototype first**: spike the code before any spec, then backfill the proposal from what the prototype taught you. * **Building iteratively**: a sequence of small changes instead of one big proposal. * **Revising an implemented change**: the plan needs to move again after apply, but the change hasn't merged or archived yet. # Set up your project (/docs/setup) ## Pick where OpenSpec lives [#pick-where-openspec-lives] * **In your repo (the default)**: specs and changes sit next to the code they describe and are versioned with it. The rest of this page follows this path. * **In a store**: a separate planning repo shared by the repos that use it, for multi-repo setups or keeping planning out of the repo entirely. [Stores (beta)](/docs/stores) covers when that's worth it and how to set one up. ## Initialize your project [#initialize-your-project] With the CLI installed ([Installation](/docs/installation)), run init at the root of your project. In your terminal: ```bash cd openspec init ``` Init asks which AI tools you use, writes the workflow files for the ones you pick, and reports what you got: ``` OpenSpec Setup Complete Created: Claude Code 6 skills and 6 commands in .claude/ Config: openspec/config.yaml (schema: spec-driven) ``` Restart your IDE for the new commands to take effect. Re-running init is safe: * Tools you already set up print `Refreshed` instead of `Created`. * Running init again with a new tool selected adds that tool. * The `--tools` flag skips the picker ([CLI reference](/docs/cli)). ## What init installs [#what-init-installs] Running init creates two things in your project: * An `openspec/` folder at the repo root * Workflow files (skills and commands) added to your AI tool's folder (`.agents/`, `.claude/`, etc.) Commit all of it like the rest of your source ([FAQ](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/help/faq.md) covers why). Init changes nothing else in your repo (if it finds leftovers from an older OpenSpec version, it asks before cleaning them up). ### The `openspec/` folder [#the-openspec-folder] Every OpenSpec artifact lives here, at the root of your project. Here's what that looks like: ``` openspec/ ├── config.yaml project settings and context for the AI ├── specs/ your specs (empty for now) └── changes/ in-motion changes (empty for now) └── archive/ completed changes move here ``` [Concepts](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/guides/concepts.md) explains both artifacts; [Project config](/docs/project-config) covers `config.yaml`. ### The workflow files (skills and commands) [#the-workflow-files-skills-and-commands] These are the OpenSpec workflows, the actions you'll use as you work. Here they are as installed skills, in the shared `.agents/` folder most tools use: ``` .agents/skills/ ├── openspec-explore/ think through an idea first ├── openspec-propose/ propose a change ├── openspec-apply-change/ implement a change's tasks ├── openspec-update-change/ revise a change's plan ├── openspec-sync-specs/ sync a change's spec updates into specs/ ├── openspec-archive-change/ move a finished change to the archive ├── openspec-verify-change/ check the implementation matches the plan (not included by default) └── openspec-bulk-archive-change/ archive several changes at once (not included by default) ``` This is the default set plus two optional workflows. [Profiles](/docs/profiles) lists all twelve. By default each workflow installs in two forms: * **Skill** (`openspec-apply-change`): instructions your agent picks up on its own when you ask for the work. * **Command** (`/opsx:apply` in Claude Code): a typed entry point for the same workflow, under a shorter name. The two are functionally identical. A workflow's skill and its command carry the same instructions. Why two: commands came first, and every tool spells them its own way. Skills are the newer standard shared across tools, but not every tool can invoke a skill directly, so commands stay as those tools' entry point. Some tools install in skill form only. Where the tool runs skills directly, init skips commands and says so (`Commands skipped for: codex (uses skills)`). We prefer skills and expect to retire commands eventually. #### Change what gets installed [#change-what-gets-installed] The interactive picker changes the delivery form and the workflow set ([Profiles](/docs/profiles)). In your terminal: ```bash openspec config profile ``` Here's switching to skills only: ``` Current profile settings Delivery: both ? What do you want to configure? Delivery only ? Delivery mode (how workflows are installed): Skills only Config changes: delivery: both -> skills ? Apply changes to this project now? (Y/n) y ``` Answering yes applies it to the current project on the spot. Other projects pick it up on their next `openspec update`. The setting is global, per machine. Setup is done. The [Quickstart](/docs/quickstart) takes your first change from here. # Skills (/docs/skills) The skills come in two sets: * **Core**: installed by default, the main planning loop. * **Optional**: installed only when you add them, via [Profiles](/docs/profiles). | Skill | Job | Type | | ------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------- | | [openspec-explore](#openspec-explore) | Think through an idea before it becomes a change proposal | Core | | [openspec-propose](#openspec-propose) | Create a change proposal with all its planning artifacts in one step | Core | | [openspec-apply-change](#openspec-apply-change) | Implement a change proposal's tasks | Core | | [openspec-update-change](#openspec-update-change) | Revise a change proposal's plan | Core | | [openspec-sync-specs](#openspec-sync-specs) | Merge a change proposal's spec updates into `specs/` | Core | | [openspec-archive-change](#openspec-archive-change) | Move a finished change proposal to the archive | Core | | [openspec-new-change](#openspec-new-change) | Start a change proposal as an empty scaffold | Optional | | [openspec-continue-change](#openspec-continue-change) | Create the next planning artifact, one at a time | Optional | | [openspec-ff-change](#openspec-ff-change) | Create a change proposal with every artifact implementation needs, in one pass | Optional | | [openspec-verify-change](#openspec-verify-change) | Check the implementation matches the plan | Optional | | [openspec-bulk-archive-change](#openspec-bulk-archive-change) | Archive several change proposals at once | Optional | | [openspec-onboard](#openspec-onboard) | Learn the workflow by doing one real change proposal end to end | Optional | ## openspec-explore [#openspec-explore] Think through an idea before it becomes a change proposal. | Contract | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A topic: an idea, a problem, a comparison, or the name of an existing change proposal to explore in context. With nothing given it enters explore mode. | | **Creates** | Nothing by default. It reads and investigates only. On request it captures insights: a new change proposal under `openspec/changes//`, or updates to an existing one's proposal, design, specs, or tasks. Never code. | | **Response** | An open conversation with no required output. When thinking crystallizes it summarizes the problem, approach, open questions, and next steps, and offers to capture them. You decide. Implementation never starts here. | ## openspec-propose [#openspec-propose] Create a change proposal and generate all its planning artifacts in one step. | Contract | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A kebab-case name (`add-dark-mode`) or a plain description. Asks if you give neither. | | **Creates** | `openspec/changes//` with every artifact the schema defines, in dependency order (spec-driven: proposal, spec deltas, design, tasks). Never code. | | **Response** | The created artifacts, ready for review, and the next step. Stops there; implementation waits for `openspec-apply-change`. | ## openspec-apply-change [#openspec-apply-change] Implement a change proposal's tasks, working through the list until done or blocked. | Contract | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name (`add-auth`), optional. If the target is ambiguous it lists the active change proposals and asks you to pick. | | **Creates** | Code: the minimal changes each task calls for, in your project files. In the change proposal it touches only the tasks file, checking off each finished task (`- [ ]` to `- [x]`). | | **Response** | Progress per task, then an overall count (N/M tasks complete). All done: suggests `openspec-archive-change`. Blocked by missing artifacts: points to `openspec-continue-change`. Unclear tasks or errors: pauses and asks. | ## openspec-update-change [#openspec-update-change] Revise a change proposal's existing planning artifacts and keep them coherent with each other. | Contract | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name, optional, plus the revision you want. With no revision stated it runs a coherence review: artifacts checked against each other for contradictions, gaps, and duplication. | | **Creates** | Nothing new. Edits only artifact files that already exist. Missing artifacts are `openspec-continue-change`'s job. Never code. | | **Response** | Shows each proposed revision and writes it only after you confirm, one artifact at a time. Ends with what was revised and the next step; implementation waits for `openspec-apply-change`. | ## openspec-sync-specs [#openspec-sync-specs] Merge a change proposal's spec updates into `specs/` without archiving it. | Contract | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name, optional. You can also name a subset of its delta specs, and only those sync. | | **Creates** | Edits or creates `openspec/specs//spec.md` for each delta spec, merging added, modified, removed, and renamed requirements into the main spec. Never code. | | **Response** | A per-capability summary of requirements added, modified, removed, or renamed, after the updated specs validate. The change proposal stays active; archiving waits for `openspec-archive-change`. | ## openspec-archive-change [#openspec-archive-change] Move a finished change proposal to the archive. | Contract | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name, optional. | | **Creates** | Moves the change proposal folder to `openspec/changes/archive/YYYY-MM-DD-/` (no date added if the name already starts with one). With your approval it first syncs outstanding delta specs via `openspec-sync-specs`. Never code. | | **Response** | Warns and asks before archiving with incomplete artifacts or tasks, and asks whether to sync when delta specs exist. Ends with a summary: name, schema, archive location, spec sync status, and any warnings. | ## openspec-new-change [#openspec-new-change] Start a change proposal as an empty scaffold. | Contract | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A kebab-case name (`add-user-auth`) or a plain description, plus a schema name only for a non-default workflow. Asks what you want to build if you give neither. | | **Creates** | `openspec/changes//` as an empty scaffold: no artifacts yet, never code. | | **Response** | The scaffold's name and location, the workflow's artifact sequence, status (0/N complete), and the first artifact's template. Drafting artifacts waits for `openspec-continue-change`. | ## openspec-continue-change [#openspec-continue-change] Create the next planning artifact in a change proposal, one at a time. | Contract | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name, optional. If still ambiguous it asks you to pick from the most recently modified. | | **Creates** | The single next ready artifact in the schema's sequence, written into the change proposal folder. One artifact per run, never code. | | **Response** | The created artifact, progress (N of M complete), and which artifacts that unlocked. When planning is complete it says so; implementation moves to `openspec-apply-change`. | ## openspec-ff-change [#openspec-ff-change] Create a change proposal and every planning artifact implementation needs, in one pass. | Contract | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Arguments** | A kebab-case name or a plain description. Asks if you give neither. If the named change proposal already exists it suggests continuing it instead. | | **Creates** | `openspec/changes//` and every planning artifact implementation requires, in dependency order (spec-driven: proposal, specs, design, tasks), leaving out only artifacts marked skipped or conditional. Never code. | | **Response** | The change proposal's name and location, each artifact created, and any conditional artifact skipped and why. Stops there; implementation waits for `openspec-apply-change`. | ## openspec-verify-change [#openspec-verify-change] Check that the implementation matches the change proposal's artifacts. | Contract | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | A change proposal name, optional. When ambiguous it asks, listing change proposals that have a tasks artifact. | | **Creates** | Nothing. It reads the change proposal's artifacts and the codebase. Verification is report-only. | | **Response** | A report: a scorecard for Completeness, Correctness, and Coherence, then CRITICAL, WARNING, and SUGGESTION issues with recommendations, and a final archive-readiness assessment. It changes nothing and does not archive. | ## openspec-bulk-archive-change [#openspec-bulk-archive-change] Archive several change proposals at once. | Contract | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | None. It lists the active change proposals and asks you to select any number, with an option for all. If none are active it says so and stops. | | **Creates** | `openspec/changes/archive/YYYY-MM-DD-/` per archived change proposal (already-dated names keep their prefix). Each one's spec deltas sync first via `openspec-sync-specs`. Never code. | | **Response** | A status table per change proposal and one confirmation for the whole batch, then a summary of archived, skipped, and failed, plus spec sync results. When two change proposals touch the same spec it checks the codebase and syncs implemented deltas oldest first. | ## openspec-onboard [#openspec-onboard] Learn the workflow by doing one real change proposal end to end. | Contract | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Arguments** | None. It scans your codebase for small starter tasks and asks you to pick one or describe your own. | | **Creates** | A real change proposal for the chosen task, one artifact at a time, then real code once you confirm implementation. Archives the change proposal at the end. | | **Response** | A narrated walkthrough of the full cycle with pauses for your input: explore, create, build each artifact, implement, archive. Ends with a recap and a pointer to `openspec-propose`. Takes about 15 to 20 minutes. | # Stores (beta) (/docs/stores) OpenSpec normally lives inside one repo: an `openspec/` folder next to the code it plans. A store moves that folder into a repository of its own, and several code repos can share it. After a one-time setup on each machine, commands like `status`, `new change`, and `archive` can work in the store from any directory. ``` team-plans (a store: OpenSpec in its own repo) ├── .openspec-store/store.yaml the store's name └── openspec/ ├── specs/ └── changes/ ▲ │ set up once on each machine, │ shared by pushing and cloning like any repo ┌─────────────┼─────────────┐ │ │ │ web-app api-server mobile-app (code repo) (code repo) (code repo) ``` You share a store with git, the same way you share code: commit, push, pull, and review it yourself. Specs and changes get branches and pull requests the same way code does. ## When you need one [#when-you-need-one] Two common reasons to use a store: * **Frontend and backend in separate repos**: one feature touches both, and the plan needs a single home instead of two halves. ``` shop-plans (store) └── openspec/changes/add-discounts/ one plan for the feature ▲ ┌─────────┴─────────┐ │ │ storefront api (frontend repo) (backend repo) ``` * **One product, several client repos**: Android, iOS, and web ship from their own repos but share one expected behavior. A spec describes behavior, not implementation, so one spec serves all three. ``` product-specs (store) └── openspec/specs/checkout/spec.md the expected behavior ▲ ┌─────────────┼─────────────┐ │ │ │ android-app ios-app web-app (code repo) (code repo) (code repo) ``` You can have more than one store, though we recommend keeping the count low. ## Set up a store [#set-up-a-store] One person creates the store, then everyone else joins it. 1. **Create the store** (one person, once per team). Run `openspec store setup` and answer the prompts: ```bash # run from anywhere; it asks what to create and where openspec store setup ``` It asks three questions: * **Store name**: `team-plans` * **Where should this store live?**: pre-filled with `~/openspec/`, press Enter to accept it or type another path * **Create this store?**: shows what it's about to make, answer `Yes` Then it reports what it created: ```yaml Store ready: team-plans Location: ~/openspec/team-plans OpenSpec root: ready Registry: registered Next: run normal OpenSpec commands against this store, for example: openspec new change --store team-plans Share this store by committing and pushing it like any Git repo. ``` 2. **Push it to your git host.** Create an empty `team-plans` repo on your host first. Setup doesn't add a git remote, so connect the store to that repo, then push: ```bash # connect the store to the empty repo on your git host cd ~/openspec/team-plans git remote add origin git@github.com:acme/team-plans.git # publish it git push -u origin main ``` 3. **Join the store** (every teammate, once per machine): ```bash # get the store onto your machine git clone git@github.com:acme/team-plans.git ~/openspec/team-plans # tell OpenSpec where it lives openspec store register ~/openspec/team-plans ``` ```yaml Store registered: team-plans Location: /Users/you/openspec/team-plans OpenSpec root: ready Registry: registered ``` Registering tells your machine where this store lives. The store's name is already committed inside it, in `.openspec-store/store.yaml`. Setup registered the creator's copy, so only cloned copies need this step. 4. **Confirm it worked**, from any directory: ```bash # any OpenSpec command reaches the store by name openspec status --store team-plans ``` ```yaml Using OpenSpec root: team-plans (/Users/you/openspec/team-plans) No active changes. Create one with: openspec new change --store team-plans ``` ## Types of setups [#types-of-setups] OpenSpec has three setups. The rest of this page uses these names: * **repo-local**: OpenSpec inside your repo, no store. The default. * **store-only**: your repo keeps no specs or changes of its own. Everything lives in the store. * **store-optional**: your project has its own `openspec/` folder and also reaches a store when you ask. ### The default: OpenSpec inside your repo (`repo-local`) [#the-default-openspec-inside-your-repo-repo-local] `openspec init` puts an `openspec/` folder next to your code, and that repo's specs and changes live there. No store is involved. This is the setup [Set up your project](/docs/setup) teaches, and most projects never need another. ``` web-app (code repo) └── openspec/ ├── specs/ └── changes/ ``` ### OpenSpec outside your repo, in a store (`store-only`) [#openspec-outside-your-repo-in-a-store-store-only] The repo keeps no specs or changes of its own. Everything it plans lives in the store, and one line in the repo's config connects the two. Common when one team builds all the repos and plans in one place. The [examples above](#when-you-need-one) all have this shape. ``` team-plans (store) └── openspec/ ├── specs/ the repo's specs live here └── changes/ its changes too ▲ │ store: team-plans (the connecting line) web-app (code repo) └── openspec/ └── config.yaml nothing else ``` ### OpenSpec in your repo and in a store (`store-optional`) [#openspec-in-your-repo-and-in-a-store-store-optional] The repo stays repo-local for its own work, while the store holds the shared specs and changes. Inside the repo, OpenSpec uses your project's `openspec/` folder, and reaches the store only when you pass `--store`. Common when a repo used OpenSpec before the store existed, or when a mostly independent repo only occasionally touches shared work. ``` team-plans (store) └── openspec/ the shared specs and changes ▲ │ only when you pass --store team-plans web-app (code repo) └── openspec/ this repo's own ├── config.yaml ├── specs/ └── changes/ ``` A repo can start repo-local and move its specs and changes into the store later. [Move a repo's specs and changes into the store](#move-a-repos-specs-and-changes-into-the-store) shows how. ## Where artifacts get created when using stores [#where-artifacts-get-created-when-using-stores] When you use a store, OpenSpec also has to decide where the artifacts get created. It depends on your setup: * **store-only** (your project only writes to the store): every artifact is created in the store. The `store:` line below records that. * **store-optional** (your project has its own `openspec/` folder and also uses a store): artifacts are created in your project, unless you name the store in your request or pass `--store` for that change. Your agent then carries the flag through the rest of the workflow. OpenSpec writes artifacts to one of two places: your project's `openspec/` folder, or the store's. It picks in this order, and the first option that applies wins: 1. **`--store ` on a command.** Always wins, from any directory. 2. **Your project's `openspec/` folder.** If your project has its own `specs/` or `changes/` folders, OpenSpec uses them. 3. **The `store:` line in your project.** How a store-only project records its store. 4. **`defaultStore` on your machine.** The fallback when none of the above applies. When OpenSpec selects a store, it prints `Using OpenSpec root: ...` before the command output. ### The `store:` line (store-only projects) [#the-store-line-store-only-projects] Add one line to your project's `openspec/config.yaml`: ```yaml # web-app/openspec/config.yaml store: team-plans ``` Everything you or your agent run inside your project now uses the store, with no flag to type: ```bash # inside web-app, connected openspec status ``` ```yaml Using OpenSpec root: team-plans (/Users/you/openspec/team-plans) No active changes. Create one with: openspec new change --store team-plans ``` * **Without the line**: run a plain command in a store-only project and OpenSpec stops with an error listing your registered stores. * **Commit it**: teammates who clone your project get the line too. They still need the store registered on their machine ([step 3 of Set up a store](#set-up-a-store)), or OpenSpec errors and tells them to register it. * **Next to real folders**: if your project also has `specs/` or `changes/` folders, OpenSpec uses those and ignores the line, with a warning. ### `defaultStore` on your machine [#defaultstore-on-your-machine] Set it once if every project you work in uses the same store. OpenSpec falls back to it when it finds no flag, no local `openspec/` folder, and no `store:` line: ```bash # use team-plans whenever nothing else names a store openspec config set defaultStore team-plans # undo it openspec config unset defaultStore ``` **Commands that stay local.** `init`, `update`, `templates`, `schemas`, and the `openspec schema` subcommands act on the current directory only and take no `--store`. ## Move a repo's specs and changes into the store [#move-a-repos-specs-and-changes-into-the-store] To take a repo from repo-local to store-only: 1. Move everything in the repo's `openspec/specs/` and `openspec/changes/` into the same folders in the store. 2. Delete the now-empty folders, so the repo's `openspec/` folder holds only `config.yaml`. 3. Add the `store:` line to that `config.yaml`. `openspec status` inside the repo now starts with `Using OpenSpec root: team-plans`. ## Work in the store [#work-in-the-store] The workflows don't change, for you or for your agent. Propose, apply, and archive run the way they always do. The only difference is where the artifacts get created, and [the section above](#where-artifacts-get-created-when-using-stores) covers that. Create a change from inside a store-only repo and it lands in the store: ```bash # inside web-app; the store: line routes this to team-plans openspec new change add-login ``` ```yaml Using OpenSpec root: team-plans (/Users/you/openspec/team-plans) Created change 'add-login' at /Users/you/openspec/team-plans/openspec/changes/add-login/ Schema: spec-driven Next: openspec status --change add-login --store team-plans ``` * **Where it went**: into the store repo, not next to your code. * **Sharing it**: the change exists only in your checkout until you commit and push the store repo. Teammates see it when they pull. The same goes for every artifact the workflows write. * **Paths in the docs**: wherever the docs show an `openspec/` path, in a store setup that folder is the store's. When artifacts get created somewhere you didn't expect, `openspec doctor` checks your setup without changing anything and prints a fix for each finding: ```bash # check the current root and its stores openspec doctor ``` ```yaml Doctor Root Location: /Users/you/openspec/team-plans OpenSpec root: ok Store: team-plans (metadata ok) References (none declared) ``` `openspec context` lists the root and stores your current directory works with, when you want the same picture without the checks. To open the store and a repo in one editor window, so your agent can read both, see [Worksets (beta)](/docs/worksets). ## Read specs from another store [#read-specs-from-another-store] Your repo can keep its own `openspec/` folder and still let your agent read another store's specs. Declare that store under `references:` in the repo's `openspec/config.yaml`: ```yaml # api-server/openspec/config.yaml references: - team-plans ``` References are read-only. Your work stays in your repo, and the reference only changes what your agent is told. When a workflow creates an artifact, its instructions gain an index of the referenced store's specs, each with a one-line summary and the exact command to fetch it: ```xml Store team-plans (/Users/you/openspec/team-plans): - payments: Rules for charging and refunding customers. Fetch: openspec show --type spec --store team-plans ``` A reference can also carry the store's clone URL, for machines that don't have that store yet: ```yaml references: - team-plans - { id: design-system, remote: "git@github.com:acme/design-system.git" } ``` With the URL declared, `openspec doctor` turns a missing store into a pasteable fix: ```yaml # output wrapped to fit References - team-plans: ok (/Users/you/openspec/team-plans) - design-system: Referenced store 'design-system' is not registered on this machine. Fix: git clone -- git@github.com:acme/design-system.git '/Users/you/openspec/design-system' && openspec store register '/Users/you/openspec/design-system' --id design-system ``` ## Beta limits [#beta-limits] * **The shape may change**: command names, flags, and file formats can change between releases. Re-read this page after upgrading. * **No sync, by design**: OpenSpec never clones, pulls, or pushes. A stale checkout shows stale specs until you pull, and references are read from whatever is on disk. * **One checkout per store name**: registering a second folder under a name that's already registered fails, with a hint to run `openspec store unregister` first. # Supported tools (/docs/supported-tools) Every tool in the matrix runs the same OpenSpec workflows. A skill and its command are the same workflow instructions. The only difference is what you type. Which form init installs is the delivery setting, covered in [Set up your project](/docs/setup#the-workflow-files-skills-and-commands). ## Support matrix [#support-matrix] Invocations are shown for the apply workflow. Every workflow follows the same shape. The id goes to `openspec init --tools ` to skip the picker ([CLI](/docs/cli)). | Tool | `--tools` id | Skills | Skill invocation | Commands | Command invocation | | --------------------------------- | ---------------- | ----------------------------- | ------------------------------ | ---------------------------- | ------------------ | | Amazon Q Developer | `amazon-q` | `.amazonq/skills/` | `/openspec-apply-change` | `.amazonq/prompts/` | `@opsx-apply` | | Antigravity | `antigravity` | `.agents/skills/` | `/openspec-apply-change` | `.agents/workflows/` | `/opsx-apply` | | Auggie (Augment CLI) | `auggie` | `.augment/skills/` | `/openspec-apply-change` | `.augment/commands/` | `/opsx-apply` | | Bob Shell | `bob` | `.bob/skills/` | `/openspec-apply-change` | `.bob/commands/` | `/opsx-apply` | | Claude Code | `claude` | `.claude/skills/` | `/openspec-apply-change` | `.claude/commands/opsx/` | `/opsx:apply` | | Cline | `cline` | `.cline/skills/` | `/openspec-apply-change` | `.clinerules/workflows/` | `/opsx-apply` | | CodeArts | `codeartsagent` | `.codeartsdoer/skills/` | `/openspec-apply-change` | none | none | | CodeBuddy Code (CLI) | `codebuddy` | `.codebuddy/skills/` | `/openspec-apply-change` | `.codebuddy/commands/opsx/` | `/opsx:apply` | | Codex | `codex` | `.agents/skills/` | `$openspec-apply-change` | none | none | | Continue | `continue` | `.continue/skills/` | `/openspec-apply-change` | `.continue/prompts/` | `/opsx-apply` | | CoStrict | `costrict` | `.cospec/skills/` | `/openspec-apply-change` | `.cospec/openspec/commands/` | `/opsx-apply` | | Crush | `crush` | `.crush/skills/` | `/openspec-apply-change` | `.crush/commands/opsx/` | `/opsx:apply` | | Cursor | `cursor` | `.cursor/skills/` | `/openspec-apply-change` | `.cursor/commands/` | `/opsx-apply` | | Devin Desktop (formerly Windsurf) | `devin` | `.devin/skills/` | `/openspec-apply-change` | `.devin/workflows/` | `/opsx-apply` | | Factory Droid | `factory` | `.factory/skills/` | `/openspec-apply-change` | `.factory/commands/` | `/opsx-apply` | | ForgeCode | `forgecode` | `.forge/skills/` | `/openspec-apply-change` | none | none | | Gemini CLI | `gemini` | `.gemini/skills/` | `/openspec-apply-change` | `.gemini/commands/opsx/` | `/opsx:apply` | | GitHub Copilot | `github-copilot` | `.github/skills/` | `/openspec-apply-change` | `.github/prompts/` | `/opsx-apply` | | Hermes Agent | `hermes` | `.hermes/skills/` | `/openspec-apply-change` | none | none | | iFlow | `iflow` | `.iflow/skills/` | `/openspec-apply-change` | `.iflow/commands/` | `/opsx-apply` | | Junie | `junie` | `.junie/skills/` | `/openspec-apply-change` | `.junie/commands/` | `/opsx-apply` | | Kilo Code | `kilocode` | `.kilocode/skills/` | `/openspec-apply-change` | `.kilocode/workflows/` | `/opsx-apply` | | Kimi Code | `kimi` | `.kimi-code/skills/` | `/skill:openspec-apply-change` | none | none | | Kiro | `kiro` | `.kiro/skills/` | `/openspec-apply-change` | `.kiro/prompts/` | `/opsx-apply` | | Lingma | `lingma` | `.lingma/skills/` | `/openspec-apply-change` | `.lingma/commands/opsx/` | `/opsx:apply` | | MiniMax Code | `minimax-code` | `~/.minimax/skills/` (global) | `/openspec-apply-change` | none | none | | Mistral Vibe | `vibe` | `.vibe/skills/` | `/openspec-apply-change` | none | none | | Oh My Pi | `oh-my-pi` | `.omp/skills/` | `/openspec-apply-change` | `.omp/commands/` | `/opsx-apply` | | OpenCode | `opencode` | `.opencode/skills/` | `/openspec-apply-change` | `.opencode/commands/` | `/opsx-apply` | | Pi | `pi` | `.pi/skills/` | `/openspec-apply-change` | `.pi/prompts/` | `/opsx-apply` | | Qoder | `qoder` | `.qoder/skills/` | `/openspec-apply-change` | `.qoder/commands/opsx/` | `/opsx:apply` | | Qwen Code | `qwen` | `.qwen/skills/` | `/openspec-apply-change` | `.qwen/commands/` | `/opsx-apply` | | Trae | `trae` | `.trae/skills/` | `/openspec-apply-change` | `.trae/commands/` | `/opsx-apply` | | ZCode | `zcode` | `.zcode/skills/` | `/openspec-apply-change` | `.zcode/commands/opsx/` | `/opsx:apply` | | Zoo Code | `roocode` | `.roo/skills/` | `/openspec-apply-change` | `.roo/commands/` | `/opsx-apply` | | Other / Universal | `agents` | `.agents/skills/` | `/openspec-apply-change` | none | none | * **Skill invocation**: whether a tool registers skills as typed entries is the tool's own behavior. The column shows the spelling OpenSpec uses in generated files and in the hint init prints. Check your tool's docs if typing it does nothing. * **Command file formats**: most tools take `.md` command files. Gemini CLI takes `.toml`, Continue `.prompt`, Kiro and GitHub Copilot `.prompt.md`. The spelling you type is the same either way. ## Per-tool notes [#per-tool-notes] A tool not listed here behaves exactly as its row reads. ### Antigravity [#antigravity] * **Current folder**: Antigravity v1.20.5 and later read workspace skills and workflows from `.agents/`. * **Legacy folder**: after OpenSpec writes replacements, it removes equivalent generated files from `.agent/`. Custom files and changed generated files stay in `.agent/` for you to review. * **Shared skills**: Antigravity shares `.agents/skills/` with Codex, Zed Agent, and the `agents` target. OpenSpec writes that skill tree once while still writing Antigravity commands to `.agents/workflows/`. ### Cline [#cline] Cline reads commands from `.clinerules/workflows/`, not from its `.cline/` folder. Skills stay in `.cline/skills/`. ### Codex [#codex] * **Invocation**: type `$openspec-`. Codex does not recognize the `/openspec-` form ([upstream issue](https://github.com/openai/codex/issues/11817)). * **No command files**: Codex runs skills directly, so init skips commands even when delivery includes them and prints `Commands skipped for: codex (uses skills)`. * **Shared folder**: Codex skills land in `.agents/skills/`, the same tree Antigravity, Zed Agent, and the `agents` target use. Selecting more than one keeps a single compatible tree, and its handoffs spell both `$openspec-*` and `/openspec-*` when Codex owns it. * **Legacy path**: skills installed under `.codex/skills/` by older versions are migrated on the next `openspec update`. ### Devin Desktop (formerly Windsurf) [#devin-desktop-formerly-windsurf] * **Two agents**: command files in `.devin/workflows/` work only in Devin Desktop. Devin Local runs skills only, so generated skills reference `/openspec-`, which works in both. * **Rename**: `--tools windsurf` still resolves to `devin`. A project holding OpenSpec files in the legacy `.windsurf/` folder is offered the move on the next `openspec update`. ### GitHub Copilot [#github-copilot] Prompt files register as slash commands in the Copilot IDE extensions (VS Code, JetBrains, Visual Studio). Copilot CLI does not read `.github/prompts/`. ### Hermes Agent [#hermes-agent] Hermes loads skills only from `~/.hermes/skills/` by default. Add the project's `.hermes/skills/` folder to `skills.external_dirs` in `~/.hermes/config.yaml`; init prints this reminder after install. ### MiniMax Code [#minimax-code] * **Global only**: skills go to `~/.minimax/skills/`. Nothing is written inside the repo. * **Safe across projects**: a commands-only delivery leaves the global skills in place, so one project's setting cannot remove skills another project uses. ### Other / Universal (shared `.agents` skills) [#other--universal-shared-agents-skills] * **When it fits**: any tool that reads the shared `.agents/skills/` folder, including tools with no row in the matrix. It is the entry to pick when your assistant is not listed. The init picker's search box finds it by `universal`, `other`, `generic`, `custom`, `proprietary`, `unlisted`, `unsupported`, `vendor-neutral`, or `agents.md`. * **Alongside other targets**: Antigravity, Codex, Zed Agent, and this target share one physical skill tree. OpenSpec records one writer in `.openspec-target` and writes the tree once per run. Each tool's separate command files are still generated. * **What OpenSpec claims**: only the `openspec-*` folders and the `.openspec-target` marker. Anything else under `.agents/` is left alone. * **`AGENTS.md`**: not created or edited. The target is the `.agents/` folder, not the file. # Worksets (beta) (/docs/worksets) With a store, the context your agent needs is split across folders. The specs and changes live in the store, and the code lives in each repo. An agent started in one repo can read and grep that repo and nothing else, so it works from half the picture. Worksets are the utility OpenSpec provides for this. A workset is a saved, named list of folders you open together. This page assumes the store is already set up and registered on your machine. [Stores (beta)](/docs/stores) covers that. ## How it works [#how-it-works] * **What it is**: a named list of folders, saved on your machine only. Nothing is written into the member folders, and nothing is committed. * **What opening does**: OpenSpec generates a `.code-workspace` file from the list and launches your editor on it. Every member folder sits in one window. * **What you get**: your editor's search, and any agent you run inside that window, can read every member folder. The agent can grep the store's specs and the repo's code in one session. * **What it doesn't change**: which `openspec/` folder a command uses. That still follows [Where artifacts get created](/docs/stores#where-artifacts-get-created-when-using-stores). ## Set it up [#set-it-up] 1. **Save the workset** (once per machine). List the repo and the store as members, and the tool to open them with: ```bash # save a named list of folders you open together openspec workset create platform \ --member ~/src/web-app \ --member ~/openspec/team-plans \ --tool code ``` ```yaml Saved workset 'platform' (2 members) to your machine. Open it any time with: openspec workset open platform ``` 2. **Open it** whenever you start work: ```bash # open every member in one VS Code window openspec workset open platform ``` `openspec workset list` shows what you saved, and `openspec workset remove ` deletes a workset without touching the member folders: ```yaml platform (opens in VS Code) web-app /Users/you/src/web-app team-plans /Users/you/openspec/team-plans ``` ## Use it: one change, two folders [#use-it-one-change-two-folders] Say the `add-login` change lives in the `team-plans` store, and the code for it lives in `web-app`. Open the `platform` workset and ask your agent to implement the change. In that one session it can: * read `team-plans/openspec/changes/add-login/` and the specs next to it * edit the code in `web-app/` * run `openspec` commands from inside `web-app` Without the workset, the agent only sees whichever folder it was started in. ## Tools out of the box [#tools-out-of-the-box] * **VS Code** (`--tool code`) and **Cursor** (`--tool cursor`): built in. Each opens one window with every member folder. * **Claude Code and Codex in the terminal**: temporarily disabled as workset openers while that flow is reworked. `--tool claude` or `--tool codex` stops with an error that says so and points you to VS Code or Cursor. * **Other editors**: add them under the `openers` key in [CLI settings (config.json)](/docs/configuration/config-json). # Change metadata (.openspec.yaml) (/docs/configuration/change-metadata) ## Location [#location] Each change keeps its metadata at `openspec/changes//.openspec.yaml`, next to its artifacts. Creating a change writes the file with `schema` and `created` filled in. ## Fields [#fields] | Key | Type | Required | Effect | | --------------------- | --------------------- | -------- | ----------------------------------------------------------------- | | `schema` | string | Yes | The workflow schema this change follows | | `created` | string, YYYY-MM-DD | No | Records the date the change was created | | `goal` | string | No | Records what the change sets out to do | | `affected_areas` | list of strings | No | Records the areas the change expects to touch | | `initiative` | map: `store` and `id` | No | Records the initiative this change belongs to | | `skip_specs` | boolean | No | Declares the change makes no spec deltas, so zero deltas validate | | `retire_capabilities` | boolean | No | Authorizes archive to delete a capability this change empties | ### schema [#schema] The workflow schema this change follows. It is set when the change is created and wins over the project config, so a change keeps its schema even if `openspec/config.yaml` changes afterwards. Valid names are listed in [Schemas](/docs/schemas). ### initiative [#initiative] The initiative this change belongs to, as a store id and an initiative id, both kebab-case: ```yaml initiative: store: platform-specs id: unify-billing ``` Keys other than `store` and `id` are rejected. No command reads the link today. ### skip\_specs [#skip_specs] Declares the change intentionally makes no spec deltas: a pure refactor, tooling, or docs change. With it set, validation accepts zero deltas, and artifacts that would generate spec files count as complete. Setting it while spec files exist under specs/ is a validation error. Its effect on deltas and archive is on [spec-driven](/docs/schemas/spec-driven). ### retire\_capabilities [#retire_capabilities] Authorizes archive to retire a capability. When this change's REMOVED deltas take away the last requirement a capability has, archive deletes that capability's main spec instead of stopping. The flag exists because the deletion is only recoverable from git, so it stays the author's call. The archive behavior is on [spec-driven](/docs/schemas/spec-driven). ## Example [#example] A filled-in .openspec.yaml: ```yaml schema: spec-driven created: 2026-08-14 goal: Add magic-link login to the API affected_areas: - auth - api ``` ## Validation [#validation] The file is validated whenever a command writes or reads it. A write that fails validation throws and writes nothing. Reading an existing file fails on invalid YAML, a field that breaks its contract, or a schema name that is not available. A missing file is not an error, and the change is treated as having no metadata. Unlike [config.yaml](/docs/configuration/config-yaml), bad values are never dropped with a warning. A metadata error stops the command. The one exception is unknown top-level keys, which are ignored rather than rejected. # CLI settings (config.json) (/docs/configuration/config-json) ## Location [#location] The CLI keeps its machine-level settings at `~/.config/openspec/config.json` on macOS and Linux, and `%APPDATA%\openspec\config.json` on Windows. `$XDG_CONFIG_HOME` wins on every platform when set. The `openspec config` command reads and edits it. ## Fields [#fields] | Key | Type | Required | Effect | | -------------- | --------------------------------------- | -------- | ----------------------------------------------------- | | `profile` | string: `core` or `custom` | No | Picks the workflow set `openspec init` installs | | `delivery` | string: `both`, `skills`, or `commands` | No | Whether init installs skills, slash commands, or both | | `workflows` | list of strings | No | The workflow list a `custom` profile installs | | `featureFlags` | map: flag → boolean | No | Boolean feature toggles | | `defaultStore` | string | No | Machine-level fallback store for root resolution | | `openers` | list | No | The tools worksets open in, and how each is launched | | `telemetry` | map | No | State the CLI keeps: anonymous id and notice-seen | ### profile [#profile] Which workflow set `openspec init` installs. Defaults to `core`: propose, explore, apply, update, sync, and archive. Setting `custom` installs exactly the `workflows` list instead. ### delivery [#delivery] Whether init installs workflows as skills, as slash commands, or both. Defaults to `both`. ### workflows [#workflows] The workflows a `custom` profile installs; ignored when the profile is `core`. Valid ids: `propose`, `explore`, `new`, `continue`, `apply`, `update`, `ff`, `sync`, `archive`, `bulk-archive`, `verify`, `onboard`. ### featureFlags [#featureflags] Boolean toggles keyed by flag name, set with `openspec config set featureFlags. true`. No flag is read by the CLI today. ### defaultStore [#defaultstore] The machine-level fallback store id for root resolution, consulted only when no `--store` flag, local `openspec/`, or project `store:` pointer resolves. The full ladder is [Root resolution](/docs/stores#where-artifacts-get-created-when-using-stores). ### openers [#openers] The tools a workset can open in, and how each is launched. Entries are hand-edited and validated on use. Each may set `style` (`workspace-file` or `attach-dirs`), `label`, `command`, `args`, and `attach_flag`, and is merged over the built-in defaults. ### telemetry [#telemetry] State the CLI writes for telemetry: your anonymous id and whether the first-run notice was shown. It is not the opt-out. Disabling telemetry is an environment variable, on [Environment variables](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/reference/configuration/environment-variables.md). ## Example [#example] A filled-in config.json: ```json { "profile": "core", "delivery": "both", "featureFlags": {}, "telemetry": { "anonymousId": "5f8a2c1e-4b6d-4f9a-9c3d-7e1b2a8d4c6f", "noticeSeen": true } } ``` # Project configuration (config.yaml) (/docs/configuration/config-yaml) ## Location [#location] Each OpenSpec project keeps its config file at `openspec/config.yaml`, in the project root. ## Fields [#fields] | Key | Type | Required | Effect | | ------------ | ---------------------------------- | -------- | --------------------------------------------------------- | | `schema` | string | Yes | The workflow schema this project's changes follow | | `context` | string | No | Injected into every artifact's instructions | | `rules` | map: artifact ID → list of strings | No | Extra rules added to one artifact's built-in guidance | | `operations` | map: operation → guidance list | No | Advisory guidance for apply and archive work | | `store` | string | No | Fallback OpenSpec root when this openspec/ is config-only | | `references` | list | No | Stores whose specs are indexed into instructions | Invalid fields never fail a command. Each field is validated on its own, and a bad value is dropped with a warning. What to write in these fields is covered in [Project configuration](/docs/project-config). ### schema [#schema] The workflow schema every change in this project follows. Valid values are `spec-driven` or a schema name the project defines. The names are listed in [Schemas](/docs/schemas). ### context [#context] Free text injected into every artifact's instructions. The limit is 50KB, and a larger value is ignored with a warning. ### rules [#rules] Extra rules for one artifact, added to the schema's built-in guidance: ```yaml rules: proposal: - Keep proposals under 500 words ``` Artifact IDs are not restricted to the built-in names, so artifacts from custom schemas work as keys. ### operations [#operations] Advisory guidance for how apply and archive work is conducted, separate from artifact rules: ```yaml operations: apply: guidance: - Keep test summaries concise ``` Only `apply` and `archive` are read. ### store [#store] A store id used as the OpenSpec root, consulted only when this openspec/ directory is config-only (no specs/ or changes/). It is a fallback, never an override. The full ladder is [Root resolution](/docs/stores#where-artifacts-get-created-when-using-stores). ### references [#references] Store ids whose specs this project's work draws on. An index of each store's specs (id, summary, fetch command) is added to instructions output. Spec content is never inlined, and root resolution is never affected. An entry is a store id or a map with `id` and an optional `remote` clone source: ```yaml references: - platform-specs - id: billing-specs remote: git@github.com:acme/billing-specs.git ``` ## Example [#example] A filled-in config.yaml: ```yaml schema: spec-driven context: | Tech stack: TypeScript, React, Node.js We use conventional commits Domain: e-commerce platform rules: proposal: - Keep proposals under 500 words - Always include a "Non-goals" section tasks: - Break tasks into chunks of max 2 hours operations: apply: guidance: - Keep test summaries concise archive: guidance: - Summarize the archive outcome before finishing ``` ## Legacy names [#legacy-names] `openspec/config.yml` is read as an alias when `config.yaml` does not exist. When both files exist, `config.yaml` wins and `config.yml` is ignored. `openspec init` creates `config.yaml`. # Overview (/docs/configuration) | File | Lives at | Controls | | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------- | | [Project configuration (config.yaml)](/docs/configuration/config-yaml) | `openspec/config.yaml` | The schema, context, and rules this project plans with | | [Change metadata (.openspec.yaml)](/docs/configuration/change-metadata) | `openspec/changes//.openspec.yaml` | The workflow schema, goal, scope, and spec exceptions for one change | | [CLI settings (config.json)](/docs/configuration/config-json) | `~/.config/openspec/config.json` (Windows varies) | How the openspec CLI behaves on your machine | | [Environment variables](https://github.com/Fission-AI/OpenSpec/blob/main/docs-lab/reference/configuration/environment-variables.md) | Your shell or CI environment | Telemetry opt-out, and where the config and data directories live | | Stores | `~/.local/share/openspec/stores/` (Windows varies) | The registry and metadata behind multi-repo stores | # Overview (/docs/schemas) A schema defines which artifacts a change proposal produces, and in what order. On disk it's a folder with a schema.yaml in it. Every field of that file is on the [schema.yaml](/docs/schemas/schema-yaml) page. ## Available schemas [#available-schemas] One schema ships with the CLI: | Schema | Artifacts | | -------------------------------------------------- | -------------------------------------- | | [spec-driven](/docs/schemas/spec-driven) (default) | `proposal`, `specs`, `design`, `tasks` | A project can add its own schemas, and a machine can override globally. Where those folders live and which copy wins is in schema.yaml's [Location](/docs/schemas/schema-yaml#location) section. In your terminal, [`openspec schemas`](/docs/cli#openspec-schemas) prints every schema your project can see. # schema.yaml (/docs/schemas/schema-yaml) `schema.yaml` lists the planning files a workflow creates. It also defines their order and the handoff to implementation. ## Location [#location] A project schema lives under `openspec/schemas//`: ```text openspec/schemas/review-first/ ├── schema.yaml └── templates/ ├── proposal.md └── tasks.md ``` OpenSpec checks three places for that directory. The first match wins. | Copy | Directory | | ---------------------------- | ----------------------------------------- | | **1. Project** | `/openspec/schemas//` | | **2. User, macOS and Linux** | `~/.local/share/openspec/schemas//` | | **2. User, Windows** | `%LOCALAPPDATA%\openspec\schemas\\` | | **3. Package** | The schemas installed with the CLI | If `XDG_DATA_HOME` is set, the user directory moves to `$XDG_DATA_HOME/openspec/schemas//` on every platform. The directory name is the lookup key used by `--schema`, `config.yaml`, and [`.openspec.yaml`](/docs/configuration/change-metadata#schema). If the `name` field differs from the directory name, OpenSpec still uses the directory name for lookup. [`openspec schema which `](/docs/cli#openspec-schema-which) prints the active directory and any lower-priority copies it hides. ## Top-level fields [#top-level-fields] | Field | Contract | | ------------- | ------------------------------------------------------------------------------------------------------------- | | `name` | **Required.** A non-empty string stored as the schema name. Lookup still uses the directory name. | | `version` | **Required.** A positive integer stored as the schema revision. The value doesn't change OpenSpec's behavior. | | `description` | An optional string printed by `openspec schemas`. With no value, the schema has no description. | | `artifacts` | **Required.** A non-empty list of [artifact entries](#artifact-fields). | | `apply` | Optional [apply settings](#apply-fields). With no block, OpenSpec uses the [apply defaults](#apply-defaults). | ## Artifact fields [#artifact-fields] Each entry under `artifacts` defines one planning file or set of files. | Field | Contract | | ------------- | ------------------------------------------------------------------------------------------------------------- | | `id` | **Required.** A unique, non-empty string used in dependencies, project rules, commands, and apply settings. | | `generates` | **Required.** A relative path or glob telling the agent where to write the artifact inside the change folder. | | `description` | **Required.** A string that labels the artifact in instructions sent to the agent. | | `template` | **Required.** A relative path to the artifact's format in the schema's `templates/` folder. | | `instruction` | Optional guidance telling the agent what content to produce. | | `requires` | A list of artifact IDs that must be complete first. Default: `[]`. | ### `generates` [#generates] The path starts from the change folder. For a change named `add-auth`: ```yaml generates: proposal.md ``` The artifact goes here: ```text openspec/changes/add-auth/proposal.md ``` A glob can match several files: ```yaml generates: specs/**/*.md ``` This matches Markdown files below `openspec/changes/add-auth/specs/`. OpenSpec treats a value containing `*`, `?`, or `[` as a glob. OpenSpec rejects absolute paths and paths containing a `..` segment. #### Completion [#completion] OpenSpec checks whether the output exists. It doesn't read the file to decide whether the artifact is complete. | `generates` value | Complete when | | ----------------- | ----------------------------------- | | `proposal.md` | That file exists. | | `specs/**/*.md` | The glob matches at least one file. | ### `template` [#template] The path starts from the schema's `templates/` folder. In the `review-first` schema: ```yaml template: proposal.md ``` OpenSpec reads this file: ```text openspec/schemas/review-first/templates/proposal.md ``` OpenSpec gives the template's contents to the agent as the output format. It doesn't copy the template into the change folder. OpenSpec rejects absolute paths and paths containing a `..` segment. ### `requires` [#requires] * **Dependencies**: every ID in `requires` must name another artifact in the same schema. * **Ready state**: an artifact becomes ready after all its dependencies are complete. * **Invalid graphs**: missing IDs, duplicate IDs, and dependency cycles fail validation. * **Ties**: when several artifacts are ready, their order in `artifacts` decides which one OpenSpec returns first. ## Apply fields [#apply-fields] `apply` defines what must exist before implementation starts. | Field | Contract | | ------------- | ---------------------------------------------------------------------------------------------------- | | `requires` | **Required.** A non-empty list of artifacts that must exist before apply instructions become ready. | | `tracks` | An optional relative path to a Markdown task file in the change folder. Default: `null`. | | `instruction` | Optional guidance sent to the agent when apply is ready. OpenSpec uses built-in guidance by default. | Artifact `requires` controls planning order. `apply.requires` controls when apply instructions become ready. ### `tracks` [#tracks] The path starts from the change folder. For a change named `add-auth`, `tracks: tasks.md` reads: ```text openspec/changes/add-auth/tasks.md ``` Apply stays blocked if that file is missing or contains no checkbox with task text. OpenSpec counts these checkbox forms: ```markdown - [ ] Pending task - [x] Completed task * [X] Completed task ``` Leading spaces are allowed. The [tasks.md section of the spec-driven page](/docs/schemas/spec-driven#tasksmd) defines the stricter format produced by the default schema. The tracked file drives the apply state: * **`blocked`**: the file is missing, or no checkbox has task text. * **`ready`**: at least one tracked task is pending. * **`all_done`**: every tracked task is checked. OpenSpec rejects absolute paths and paths containing a `..` segment. ### Apply defaults [#apply-defaults] | Behavior | Default | | ------------------ | ---------------------------- | | Required artifacts | Every artifact in the schema | | Progress tracking | No tracked file | | Agent guidance | Built-in apply guidance | ## Complete example [#complete-example] ```yaml name: review-first version: 1 description: Proposal and implementation checklist artifacts: - id: proposal generates: proposal.md description: Why the change is needed and what it affects template: proposal.md instruction: | Explain the problem, the proposed change, and its impact. requires: [] - id: tasks generates: tasks.md description: Trackable implementation checklist template: tasks.md instruction: | Break the approved proposal into ordered implementation tasks. requires: - proposal apply: requires: - tasks tracks: tasks.md instruction: | Work through the pending tasks and mark each one complete. ``` ## Validation [#validation] [`openspec schema validate `](/docs/cli#openspec-schema-validate) checks: * Field types and required fields * Relative paths * Artifact IDs, dependencies, and cycles * Template files Validation doesn't catch these mistakes: | Mistake | What happens | | --------------------------------------------- | --------------------------------------------------------------------- | | A field is misspelled, such as `instrution` | OpenSpec ignores it. Validation doesn't report the typo. | | `apply.requires` names an unknown artifact ID | Validation doesn't report the unknown ID. | | `name` differs from the schema directory | Validation passes. OpenSpec still uses the directory name for lookup. | # spec-driven (/docs/schemas/spec-driven) `spec-driven` is OpenSpec's built-in default schema. [schema.yaml](/docs/schemas/schema-yaml) defines the fields it sets. ## Artifacts [#artifacts] The workflow drafts four artifacts: | Artifact | File | Purpose | | ------------------------------ | ----------------------------------------------------- | ---------------------------- | | [`proposal`](#proposalmd) | `proposal.md` | Why the change is needed | | [`specs`](#delta-specs-specmd) | `specs//spec.md`, one per capability | What behavior changes | | [`design`](#designmd) | `design.md` | How to build it | | [`tasks`](#tasksmd) | `tasks.md` | The implementation checklist | ## Drafting order [#drafting-order] ```text ┌─ specs ──┐ proposal ────┤ ├── tasks ── apply └─ design ─┘ ``` Proposal comes first. Specs and design follow in either order, and tasks needs both. Implementation ([apply](#apply)) starts once `tasks.md` is in place. Two artifacts can be skipped: * **`design`**: when none of [its conditions](#designmd) apply, the agent leaves it out and drafts `tasks` anyway. * **`specs`**: set [`skip_specs: true`](/docs/configuration/change-metadata#skip_specs) in the change's `.openspec.yaml`. ## Example change folder [#example-change-folder] A change named `add-user-auth`, with every artifact drafted: ```text openspec/changes/add-user-auth/ ├── .openspec.yaml change metadata, written when the change is created ├── proposal.md ├── specs/ │ └── user-auth/ │ └── spec.md one delta spec per capability ├── design.md └── tasks.md ``` ## proposal.md [#proposalmd] Establishes why the change is needed. ### Structure [#structure] The template the agent receives as the output format ([templates/proposal.md](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/templates/proposal.md)): ```md ## Why ## What Changes ## Capabilities ### New Capabilities - ``: ### Modified Capabilities - ``: ## Impact ``` ### Instructions [#instructions] The instruction sent to the agent when it drafts this artifact (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ```md Create the proposal document that establishes WHY this change is needed. Sections: - **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now? - **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**. - **Capabilities**: Identify which specs will be created or modified: - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs//spec.md`. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization. - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes. - **Impact**: Affected code, APIs, dependencies, or systems. IMPORTANT: The Capabilities section is critical. It creates the contract between proposal and specs phases. Research existing specs before filling this in. Each capability listed here will need a corresponding spec file. Every change must either declare at least one capability (new or modified) or explicitly opt out of specs: `openspec validate` rejects a change with zero deltas unless the change's `.openspec.yaml` sets `skip_specs: true`. Use `skip_specs: true` only when no spec-level behavior changes (pure refactor, tooling, docs) - specs describe behavior, so if behavior does not change, no spec should change either. Do not invent a requirement just to satisfy validation. Keep it concise (1-2 pages). Focus on the "why" not the "how" - implementation details belong in design.md. This is the foundation - specs, design, and tasks all build on this. ``` ## Delta specs (spec.md) [#delta-specs-specmd] Defines what behavior changes, with one delta spec per capability the proposal lists. Each delta spec is the `spec.md` inside its capability folder. `openspec validate` and `openspec archive` reject delta sections written in any other file under `specs/`, such as `specs/user-auth.md`, because archive never merges them. ### Structure [#structure-1] The template the agent receives as the output format ([templates/spec.md](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/templates/spec.md)): ```md ## Purpose ## ADDED Requirements ### Requirement: #### Scenario: - **WHEN** - **THEN** ``` ### Instructions [#instructions-1] The instruction sent to the agent when it drafts this artifact (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ````md Create specification files that define WHAT the system should do. A spec is a behavior contract, not an implementation plan. Good spec content: - Observable behavior users or downstream systems rely on - Inputs, outputs, and error conditions - External constraints (security, privacy, reliability, compatibility) - Scenarios that can be tested or explicitly validated Avoid in specs: - Internal class/function names - Library or framework choices - Step-by-step implementation details - Detailed execution plans (those belong in design.md or tasks.md) Quick test: if the implementation can change without changing externally visible behavior, it likely does not belong in the spec. Create one spec file per capability listed in the proposal's Capabilities section. `` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path: - New capabilities: use the exact path from the proposal at `specs//spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout. - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Do not move or rename the capability. There must be at least one spec file unless the change's `.openspec.yaml` sets `skip_specs: true` (no spec-level behavior change) - `openspec validate` rejects a zero-delta change without that marker. If the proposal lists no capabilities and `skip_specs` is not set, revisit the proposal first. Delta operations (use ## headers): - **ADDED Requirements**: New capabilities - **MODIFIED Requirements**: Changed behavior - MUST include full updated content - **REMOVED Requirements**: Deprecated features - MUST include **Reason** and **Migration** - **RENAMED Requirements**: Name changes only - use FROM:/TO: format Format requirements: - Each requirement: `### Requirement: ` followed by description - Use SHALL/MUST for normative requirements (avoid should/may) - Each scenario: `#### Scenario: ` with WHEN/THEN format - **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently. - Every requirement MUST have at least one scenario. New capabilities only: start the delta spec with a `## Purpose` section - one or two sentences (50+ characters, or `openspec validate --strict` reports it as too brief) describing what the capability is for. Archive copies it into the main spec it creates; without it the new main spec is left with a `TBD ... Update Purpose after archive` placeholder to fill in by hand. Do NOT add `## Purpose` to a delta for an existing capability - that spec already has one and the delta's is ignored. To change an existing capability's Purpose - including a leftover `TBD` placeholder - edit `openspec/specs//spec.md` directly. MODIFIED requirements workflow: 1. Locate the existing requirement in openspec/specs//spec.md 2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios) 3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior 4. Ensure header text matches exactly (whitespace-insensitive) Common pitfall: Using MODIFIED with partial content loses detail at archive time. If adding new concerns without changing existing behavior, use ADDED instead. Example (a new capability, so it opens with `## Purpose`): ``` ## Purpose Lets users take their data out of the product in a portable format. ## ADDED Requirements ### Requirement: User can export data The system SHALL allow users to export their data in CSV format. #### Scenario: Successful export - **WHEN** user clicks "Export" button - **THEN** system downloads a CSV file with all user data ## REMOVED Requirements ### Requirement: Legacy export **Reason**: Replaced by new export system **Migration**: Use new export endpoint at /api/v2/export ``` Specs should be testable - each scenario is a potential test case. ```` ## design.md [#designmd] Explains how to implement the change. Drafted only when the change needs one. ### Structure [#structure-2] The template the agent receives as the output format ([templates/design.md](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/templates/design.md)): ```md ## Context ## Goals / Non-Goals **Goals:** **Non-Goals:** ## Decisions ## Risks / Trade-offs ``` ### Instructions [#instructions-2] The instruction sent to the agent when it drafts this artifact (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ```md Create the design document that explains HOW to implement the change. When to include design.md (create only if any apply): - Cross-cutting change (multiple services/modules) or new architectural pattern - New external dependency or significant data model changes - Security, performance, or migration complexity - Ambiguity that benefits from technical decisions before coding Sections: - **Context**: Only the current state and constraints needed to explain the approach. Reference the proposal for motivation instead of restating it (e.g., "See proposal.md - Why"). - **Goals / Non-Goals**: What this design achieves and explicitly excludes. Don't restate the proposal's scope - add only design-level boundaries. - **Decisions**: Key technical choices with rationale (why X over Y?). Include alternatives considered for each decision. - **Risks / Trade-offs**: Known limitations, things that could go wrong. Format: [Risk] → Mitigation - **Migration Plan**: Steps to deploy, rollback strategy (if applicable) - **Open Questions**: Unknowns that can safely be answered later without changing the specs, the approach, or the task breakdown. Omit if none. Open questions are for genuinely deferrable unknowns, not decisions you skipped. If a question would change the specs, the chosen approach, or the task breakdown, resolve it now - ask the user instead of guessing. Focus on architecture and approach, not line-by-line implementation. The proposal covers why and what; design covers how. Reference the proposal for motivation and, once written, the specs for requirements - if a section would only restate them, point to them instead. Good design docs explain the "why" behind technical decisions. ``` ## tasks.md [#tasksmd] Breaks the implementation into checkable tasks. [apply](#apply) tracks progress here. ### Structure [#structure-3] The template the agent receives as the output format ([templates/tasks.md](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/templates/tasks.md)): ```md ## 1. - [ ] 1.1 - [ ] 1.2 ## 2. - [ ] 2.1 - [ ] 2.2 ``` ### Instructions [#instructions-3] The instruction sent to the agent when it drafts this artifact (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ````md Create the task list that breaks down the implementation work. Before writing tasks, check design.md for Open Questions. If any of them would change what gets built, resolve them with the user first - do not bake an unstated assumption into the task list. **IMPORTANT: Follow the template below exactly.** The apply phase parses checkbox format to track progress. Tasks not using `- [ ]` won't be tracked. Guidelines: - Group related tasks under ## numbered headings - Each task MUST be a checkbox: `- [ ] X.Y Task description` - Tasks should be small enough to complete in one session - Order tasks by dependency (what must be done first?) Example: ``` ## 1. Setup - [ ] 1.1 Create new module structure - [ ] 1.2 Add dependencies to package.json ## 2. Core Implementation - [ ] 2.1 Implement data export function - [ ] 2.2 Add CSV formatting utilities ``` Reference specs for what needs to be built, design for how to build it. Each task should be verifiable - you know when it's done. ```` ## Apply [#apply] The handoff from planning to implementation. Apply is the phase that works through `tasks.md`, not an artifact. * **Starts**: once `tasks.md` exists and lists at least one task. * **Tracks**: the checkboxes in `tasks.md`. Checking them off is the progress record. * **Ends**: every checkbox checked. OpenSpec then suggests archiving the change. ### Settings [#settings] The apply settings (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ```yaml apply: requires: [tasks] tracks: tasks.md # instruction: shown below ``` ### Instructions [#instructions-4] The instruction sent to the agent when implementation starts (from [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml)): ```md Read context files, work through pending tasks, mark complete as you go. Pause if you hit blockers or need clarification. ``` ## schema.yaml [#schemayaml] The complete [schema.yaml](https://github.com/Fission-AI/OpenSpec/blob/main/schemas/spec-driven/schema.yaml), with instruction bodies elided. Each is shown in full in its section above. ```yaml name: spec-driven version: 1 description: Default OpenSpec workflow - proposal → specs → design → tasks artifacts: - id: proposal generates: proposal.md description: Initial proposal document outlining the change template: proposal.md # instruction: shown in full under proposal.md above requires: [] - id: specs generates: "specs/**/*.md" description: Detailed specifications for the change template: spec.md # instruction: shown in full under Delta specs above requires: - proposal - id: design generates: design.md description: Technical design document with implementation details template: design.md # instruction: shown in full under design.md above requires: - proposal - id: tasks generates: tasks.md description: Implementation checklist with trackable tasks template: tasks.md # instruction: shown in full under tasks.md above requires: - specs - design apply: requires: [tasks] tracks: tasks.md # instruction: shown in full under Apply above ```