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
| code | meaning |
|---|---|
0 | success (including "nothing to do") |
1 | failure — validation errors, verification failure, unknown item, parse error, drift/usage error |
2 | blocked (apply only) — missing required artifacts or unchecked hard blockers |
3 | soft-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:
- Resolve the change. An unknown slug exits
1with a fuzzy suggestion. - Validate. Full validation runs in fast mode; errors exit
1with the report. - Required artifacts. Missing artifacts for the change's type exit
2withgate.reason: "missing-artifacts". See Types and artifacts for which artifacts each type requires. - The blocker gate.
blocking-changes.mdis parsed and checked against the archive index (dirs underopenspec/changes/archive/matchingYYYY-MM-DD-<slug>):- Self-heal first. Any unchecked entry whose slug is already archived gets rewritten to
[x]with an*(archived <date>)*note, recorded undergate.syncedin the JSON output. You never have to hand-edit a stale checkbox. - Remaining unchecked Blocked by entries are hard blockers — exit
2withgate.reason: "hard-blockers". - Remaining unchecked Soft-blocked by entries exit
3unless you pass--allow-soft, in which case they're recorded undergate.softAcknowledgedand apply proceeds.
- Self-heal first. Any unchecked entry whose slug is already archived gets rewritten to
- Delegate. OpenSpec's own
instructions apply --jsonis fetched for context. - Emit and exit
0.
{
"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
- Resolve the change and its schema.
- Run full validation — errors exit
1. - Tasks gate. Any unchecked task in
tasks.mdexits1unless you pass--force-incomplete. Note that-yalone does not waive this — an automated caller can't skip real work just by auto-confirming prompts. - 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).
- Collision pre-check against an existing
archive/YYYY-MM-DD-<slug>dir dated today. - Decide whether to pass
--skip-specsto OpenSpec — forced by the flag, by the type having nospecsartifact, or by the change having nospecs/**/spec.mdfiles.
The two hard gates, both run before delegation, both exit 1 with no override:
archive/verification-incomplete— fires wheneververificationis required for this change's type and schema version, regardless of whether the change carries specs. Every row inverification.mdmust resolve to[x]with a recorded result, or[~] defer: <reason>. There is no--forcefor 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 Requirementsdelta against the current living spec and refuses if a scenario count drops without either aScenario removed: <reason>note or a matchingREMOVEDoperation.
Execute and verify
- Delegate to
openspec archive <name> -y [--skip-specs]and capture its stdout, stderr, and exit code. - Verify on the filesystem — never trust the exit code alone. OpenSpec can print
Aborted(or thin a spec's scenarios during merge) and still exit0. cospec's verifier checks directly: the source change directory is gone, and a dated target directory with its.openspec.yamlexists. Any mismatch — a clean abort, or a half-moved state — exits1with an honest message instead of a false success. - 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
- Blocker fan-out:
sync-blockersruns 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. - 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-dashboardWhy 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.
Related
- 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-incompleteenforces. - The workflow loop — where
applyandarchivesit 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.