How cospec relates to OpenSpec
cospec is a wrapper, not a fork. It owns no spec-format logic that OpenSpec already implements correctly — it constrains, validates, and verifies the real OpenSpec binary, and closes a handful of failure modes that make raw OpenSpec unsafe to hand to an agent.
If you already know OpenSpec, most of what you know still applies: the same concepts, the same delta format, the same glossary. cospec adds typed schemas, a real gate, and a verified archive on top.
The version pin
cospec accepts OpenSpec >=1.0.0 <2.0.0 at runtime — that range is asserted the first time cospec calls out to it in a process, and an out-of-range binary is refused outright. cospec's own dev and CI pin one exact build inside that range, 1.13.1, which is the version its contract test suite runs against. Those two numbers are meant to drift apart over time (the accepted range is wide; the pin is narrow and load-bearing, and the floor is deliberately not raised just because the pin moved) but never to fall out of sync with each other — a version tripwire test fails first if they do.
A few cospec behaviors are explicitly version-scoped rather than uniform across the whole accepted range, because upstream itself changed between 1.0.0 and the current pin:
- The exit-0-on-abort failure mode is real only below 1.7.0. From 1.7.0 on, an aborted
openspec archiveexits1. cospec's filesystem-verification discipline (below) doesn't relax because of this — the accepted floor is still1.0.0, where the old behavior is real, so success is still computed from what's actually on disk, with the exit code ANDed in as one term, never trusted alone. - The scenario-preservation defense is cospec's sole guard only below 1.8.0. From 1.8.0 on,
openspec archiveships its own overlapping scenario-loss check, making cospec's gate defence-in-depth rather than the only thing standing between an author and a silently thinned spec. - From 1.12.0,
openspec validatedry-runs the archive merge and reports the result as INFO-level issues. cospec relays these (deduped against its ownarchive/*findings so a single upstream precondition never doubles up as a second, cospec-native finding); an INFO never movesvalidor the exit code. - From 1.13.0,
openspec archiverewrites specs with a fence-aware blank-line collapse and treats+-bulleted scenario steps as real content rather than refusing to merge them. cospec's post-merge filesystem verification and scenario-preservation check are unchanged by this — they re-verify whatever the merge actually produced, byte for byte. - From 1.13.1,
openspec archiverefuses four cases it previously merged silently or crashed on: an unpairedRENAMED(aFROM:with no matchingTO:, or vice versa); aRENAMEDtarget orADDEDname that collides with a living requirement name in case or whitespace only; a delta file that carries delta section headers but isn't namedspec.md; and a namespace folder mistaken for a change. cospec ports its own native ERROR for each of the first three (deltas/unpaired-rename, the widenedarchive/added-exists,deltas/unread-file— see Validation rules) socospec validate --strictcatches them beforecospec archiveever delegates. The fourth, a namespace folder (changes/mobile/refresh-token/), cospec detects natively with a port of OpenSpec's own detector, matching a schema'sgeneratesglobs as OpenSpec does (braces, ranges, extglobs, negation), so a change holding only its schema's outputs is never mistaken for a folder:statusrefuses it (--change) or reports it as a failure entry (--all),listmarks its rownot a change, andvalidatereports it as onemeta/nested-changeERROR — each carrying OpenSpec's explanation verbatim. - 1.13.1's change validator reports defects cospec's own rules already catch: empty delta sections and a change with no parsed delta, skipped
###headers, header-only and missing SHALL/MUST, a requirement with no scenario, a requirement one delta file both adds and removes, adds and modifies, or modifies and removes, duplicate ADDED, MODIFIED and REMOVED names, RENAMED pairs sharing a source or a target or landing on an ADDED name, a REMOVED of a RENAMED source, a MODIFIED of one, a requirement outside every delta section, and a delta with no delta section at all. On a cospec-typed change each delegated finding is deduped against its cospec twin (archive/no-ops,deltas/skipped-header,archive/split-requirement,deltas/scenario-depth,deltas/requirement-shape,deltas/orphaned-requirement,deltas/header-present,archive/added-exists,archive/target-missing,archive/op-conflict,archive/new-spec-non-added,archive/target-invalid) on the same file and the same name or header, and cospec keeps its own severity; a finding with no cospec twin is still relayed, and a legacy change relays all of them at OpenSpec's level. The pairing table is on Validation rules. - OpenSpec's archive re-validates the spec it rebuilt, which its
validatenever does: living content a delta never touches — a### Notesabove the first requirement, a surviving requirement with no scenario, no## Purposetext — passesopenspec validateand abortsopenspec archive. cospec rebuilds the spec the same way and runs the same validation, soarchive/rebuilt-spec-invalidrefuses those changes at validate time instead.
cospec is an opinionated implementation of OpenSpec
cospec spawns OpenSpec; it never imports it — functionally a drop-in replacement, with compatibility upheld. Two rules keep the boundary solid:
- Resolved by path, never
$PATH. cospec looks for a project-local install first, falls back to an embedded copy bundled into the compiled binary, and always spawns by resolved path with a fixed working directory — never whatever happens to be on your shell's$PATH. You get the same OpenSpec behavior regardless of what else is installed on the machine. - Version-asserted before every session. See the pin above — cospec refuses to drive a binary outside the accepted range rather than silently behaving unpredictably against it.
On top of that wrapped binary, cospec adds:
- Typed schemas — eleven schemas, one per conventional-commit type, each with its own required/optional/forbidden artifact matrix instead of one generic workflow. See Types and artifacts.
- Real validation — stable, greppable rule IDs as a superset of the OpenSpec checks that actually apply to your change.
- A gated
apply— a deterministic exit code an agent can't rationalize past. See Apply and archive. - A verified
archive— filesystem verification that catches the silent-abort failure mode below, plus a scenario-preservation check. - A verification ledger — a machine-parsed acceptance-evidence artifact that archive gates on. See Verification.
None of this touches how OpenSpec itself parses or merges specs — for that, OpenSpec's own commands reference and getting-started guide are the source of truth, and cospec links out to them rather than restating them.
cospec is also store-aware: the same typed workflow — schemas, the gate, the verified archive, the blocking-changes ledger — runs identically against a registered OpenSpec store as it does against the local repo, via a --store flag. Store lifecycle itself is now a first-class cospec command too — cospec store setup|register|unregister|remove|list|doctor, plus cospec context and cospec workset — each a disciplined wrap or passthrough rather than a bare openspec call. See Stores for the mechanics and the full split of what each CLI owns.
Every OpenSpec capability has a cospec counterpart — a passthrough, a mirror, or an improved version — so any OpenSpec user can switch with zero regressions: the change lifecycle, store/context/workset, show/view/schemas/schema/ templates, the machine-global config (cospec config <sub>), native shell completion, and feedback. init and update stay cospec-native by design — passing them through would write the opsx files cospec's own leftover scan flags — so there is never a reason to call bare openspec. See Configuration for config, and Installation for completion.
Upstream spellings cospec also accepts
Some of OpenSpec's own spellings differ from cospec's canonical ones — openspec init --tools, the hidden experimental alias of init, new change <name>, completion generate [shell]. cospec accepts each verbatim (so an existing openspec invocation keeps working after you swap in cospec) beside its own canonical spelling, and marks the pairing in the command table so the reachability test can never let one drift out of sync with the other:
openspec's sync workflow— same ascospec sync-specscospec init --tools— same ascospec init --harnesscospec experimental— same ascospec initcospec new change— same ascospec newcospec completion generate— same ascospec completion
One upstream flag the gate declines: openspec instructions apply --change <id> accepts --schema <name> and answers from that schema's apply requirements, while cospec instructions apply --change <id> is the gate, which enforces the change's own. cospec refuses --schema there before the gate runs rather than print a verdict and a payload that disagree — see Commands. cospec status --schema <name>, by contrast, takes OpenSpec's own meaning: a schema override for every change it reports, not a filter.
Named exceptions
"Every capability has a counterpart" is a checked claim, not a promise: a reachability contract test walks every command, flag, tool id and workflow the pinned OpenSpec binary exposes and fails the build the moment one stops resolving to a cospec surface. Two kinds of entry resolve to a named exception instead of a command:
cospec update— upstream update offers to install a newer OpenSpec and re-run; cospec pins the wrapped binary and the contract suite runs against that pin.cospec change— flagged deprecated in OpenSpec's own command registry upstream; cospec never had partial coverage of it and doesn't add one now.cospec spec— Warning: The "openspec spec ..." commands are deprecated. Prefer verb-first commands (e.g., "openspec show", "openspec validate --specs"). upstream; cospec never had partial coverage of it and doesn't add one now.
Where OpenSpec's own wording still reaches you
cospec prints each remedy OpenSpec gives as the cospec command of the same shape, so the next step you read names cospec. The one place it can't is a live terminal-handover session — cospec config profile with no preset, and cospec workset open, on a terminal (see Configuration) — where OpenSpec itself drives the terminal cospec handed over and nothing is relayed: config profile's menu path (its drift warning, its apply guidance, its other-projects line, and the output of the update it runs), and workset open when the workset's tool can't be found or launched (its install-or-rerun and alternative-tool lines). config edit and config reset --all print none. That is wording cospec can't respell, not a capability it lacks, so it is no named exception: the contract suite lists each such line of the pinned OpenSpec with the session that prints it (remedy-sources.ts), and a pin that adds one fails the build until it is classified.
Three failure modes cospec defends against
Raw OpenSpec has a few behaviors that are easy to miss in a terminal but dangerous when an agent — not a human — is reading the exit code.
1. It can exit 0 having done nothing. When an archive can't actually merge — say a delta targets a requirement that no longer exists — OpenSpec prints an abort notice and exits 0 anyway. An agent that trusts the exit code alone would report success on a change that never moved. cospec's archive checks the filesystem directly (the change directory must actually be gone, a dated archive entry must actually exist) and refuses to call it done until it verifies that.
2. It can quietly thin a spec. A modified requirement that drops acceptance scenarios merges cleanly with no complaint — the spec loses coverage and nothing in the output says so. cospec re-parses each delta against the current spec at archive time and refuses the merge unless every scenario that disappears is either explicitly marked removed or accounted for by a REMOVED operation.
3. It can leave a change half-verified. OpenSpec has no concept of acceptance evidence — nothing stops a change from being archived before anyone confirmed the behavior it claims to add. cospec's verification ledger requires every tracked behavior to resolve to evidence or an explicit, on-the-record deferral before archive will proceed.
We eat our own dogfood cospec self-hosts — this repo's own openspec/
tree is managed by cospec, using the same schemas, gates, and archive verifier documented here. :::