Skip to content

Apply and archive

cospec apply is the gate you clear before writing code. cospec archive is the verified ship step. Both are deterministic, and both are safe to script against: obey the exit code, and you never have to re-derive what "clear" means by reading files yourself.

Exit codes

codemeaning
0success (including "nothing to do")
1failure — validation errors, verification failure, unknown item, parse error, drift/usage error
2blocked (apply only) — missing required artifacts or unchecked hard blockers
3soft-blocked (apply only) — unconfirmed soft blockers; re-run with --allow-soft

There is no code that means "probably fine." If apply doesn't exit 0, stop and resolve what it reported before writing code.

cospec apply <change> [--allow-soft] [--json]

In order:

  1. Resolve the change. An unknown slug exits 1 with a fuzzy suggestion.
  2. Validate. Full validation runs in fast mode; errors exit 1 with the report.
  3. Required artifacts. Missing artifacts for the change's type exit 2 with gate.reason: "missing-artifacts". See Types and artifacts for which artifacts each type requires.
  4. The blocker gate. blocking-changes.md is parsed and checked against the archive index (dirs under openspec/changes/archive/ matching YYYY-MM-DD-<slug>):
    • Self-heal first. Any unchecked entry whose slug is already archived gets rewritten to [x] with an *(archived <date>)* note, recorded under gate.synced in the JSON output. You never have to hand-edit a stale checkbox.
    • Remaining unchecked Blocked by entries are hard blockers — exit 2 with gate.reason: "hard-blockers".
    • Remaining unchecked Soft-blocked by entries exit 3 unless you pass --allow-soft, in which case they're recorded under gate.softAcknowledged and apply proceeds.
  5. Delegate. OpenSpec's own instructions apply --json is fetched for context.
  6. Emit and exit 0.
json
{
  "change": "add-widget",
  "type": "feat",
  "gate": {
    "state": "clear",
    "hardBlockers": [],
    "softAcknowledged": [],
    "synced": []
  },
  "apply": {
    "state": "...",
    "contextFiles": [],
    "progress": {},
    "tasks": [],
    "instruction": "..."
  }
}

apply.contextFiles in the --json output is the file list an agent should load before implementing — proposal, design, specs deltas, whatever the type requires — so you don't have to guess what's relevant.

--allow-soft only waives soft blockers. Hard blockers have no

override — the change they name has to actually land first. :::

cospec archive <change> [--skip-specs] [--force-incomplete] [--json]

Archive is the step that moves a change out of openspec/changes/ and merges its spec deltas into the living specs. Its steps run in a fixed order, and two of them are hard gates with no --force flag:

Pre-flight

  1. Resolve the change and its schema.
  2. Run full validation — errors exit 1.
  3. Tasks gate. Any unchecked task in tasks.md exits 1 unless you pass --force-incomplete. Note that -y alone does not waive this — an automated caller can't skip real work just by auto-confirming prompts.
  4. Self-blocker sanity check: unchecked hard blockers pointing at this change's own file print a warning, not a failure (aborted or superseded work still needs to be archivable).
  5. Collision pre-check against an existing archive/YYYY-MM-DD-<slug> dir dated today.
  6. Decide whether to pass --skip-specs to OpenSpec — forced by the flag, by the type having no specs artifact, or by the change having no specs/**/spec.md files.

The two hard gates, both run before delegation, both exit 1 with no override:

  • archive/verification-incomplete — fires whenever verification is required for this change's type and schema version, regardless of whether the change carries specs. Every row in verification.md must resolve to [x] with a recorded result, or [~] defer: <reason>. There is no --force for this gate — deferring on the record is the escape hatch. See Verification for the row grammar.
  • archive/scenario-preservation — fires only for specs-bearing changes, right before delegating. It re-parses each ## MODIFIED Requirements delta against the current living spec and refuses if a scenario count drops without either a Scenario removed: <reason> note or a matching REMOVED operation.

Execute and verify

  1. Delegate to openspec archive <name> -y [--skip-specs] and capture its stdout, stderr, and exit code.
  2. Verify on the filesystem — never trust the exit code alone. OpenSpec can print Aborted (or thin a spec's scenarios during merge) and still exit 0. cospec's verifier checks directly: the source change directory is gone, and a dated target directory with its .openspec.yaml exists. Any mismatch — a clean abort, or a half-moved state — exits 1 with an honest message instead of a false success.
  3. For specs-bearing changes, a post-merge spot-check confirms each delta actually landed as expected in the living spec (ADDED present, REMOVED absent, RENAMED correctly, MODIFIED applied). Any miss exits 1.

Post

  1. Blocker fan-out: sync-blockers runs in fix mode across every remaining active change, checking off any entries that reference the change just archived, and reporting which changes became fully unblocked as a result.
  2. Print the flywheel summary and exit 0:
Archived: add-widget (feat) → openspec/changes/archive/2026-07-03-add-widget/
Specs:    +2 ~1 -0 →0 applied and verified
Blockers: checked off in 1 change(s): add-dashboard
Now unblocked: add-dashboard → next: cospec apply add-dashboard

Why filesystem checks, not exit codes The wrapped openspec binary

is trusted for its output, never for its exit code alone — it can abort or silently thin a spec while still reporting success. Every gate above that touches the filesystem re-checks the actual state on disk before reporting 0.

Blocker sync outside archive

cospec sync-blockers [--check] [--change <id>] [--json] runs the same fix logic as archive's final step, standalone. Use --check in CI to fail on drift without mutating anything.

  • Types and artifacts — which artifacts each type requires, and the required-vs-triggered artifact matrix.
  • Verification — the row grammar and layer/owner vocabulary archive/verification-incomplete enforces.
  • The workflow loop — where apply and archive sit in the end-to-end flow.
  • How it relates to OpenSpec — the pinned OpenSpec version cospec wraps and why exit codes from it aren't trusted alone.