Skip to content

Configuration & customization

cospec draws a hard line between what it manages and what you own. There are exactly three tiers, and they're never mixed.

Tier 1: cospec-managed

openspec/schemas/**, the harness files under .claude/, .codex/, and .opencode/, and the gate files are generated by cospec init and regenerated by cospec update. Every managed file carries provenance frontmatter naming the generating version and a content hash of its body — that's what lets cospec update tell an untouched file from one you edited.

Never hand-edit a managed file. If you need different behavior, use tier 2 or tier 3 below — a header comment on each managed file points back here.

Hand-editing a managed file doesn't get silently overwritten, but it

does get flagged and shunted aside on the next update — see reconciliation below. :::

Tier 2: openspec/config.yaml

Your own change and spec content, plus openspec/config.yaml, is never touched by cospec. The config file gives you two levers, both delegated straight to the wrapped OpenSpec binary:

  • context — free-text project context injected into every generated instruction.
  • rules — per-artifact guidance (keyed by artifact id: proposal, tasks, verification, and so on), injected natively by OpenSpec into the instructions it renders.

For the full key reference and syntax, see OpenSpec's customization guide.

One extra vocabulary lever lives here too: verification.layers lets you extend the closed set of @<layer> tokens (@unit, @e2e, @manual, and so on) that verification rows can cite — see Verification for the full grammar.

A store: key points cospec (and OpenSpec) at a registered OpenSpec store by default, so you don't have to pass --store <id> on every command. It's a root-resolution input rather than a cospec-managed or validated field like context and rules above — see Stores for the full resolution order and what it changes.

Documented limitation rules are keyed by artifact id repo-wide

— they cannot vary per conventional-commit type. A proposal rule applies to every feat and every chore proposal alike. If you genuinely need different rules per type, that's what schema forking (tier 3) is for. :::

Tier 3: schema forking

For changes bigger than config can express — a wholly different artifact set, or type-specific rules — fork a schema:

sh
cospec schema fork <type> [name]      # name defaults to <type>-custom
cospec schema init <name>             # a schema with no cospec-type ancestor

then create a change against it:

sh
cospec new <name> <slug>

cospec schema fork/init are disciplined passthroughs to openspec schema fork/init — cospec adds exactly one guard on top: it refuses (exit 1, before ever spawning the wrapped binary) a destination name that collides with one of the eleven cospec types, since that would overwrite a canon-managed schema.yaml every other command reads. Any other destination name forks normally. cospec new <name> <slug> recognizes a resolved project-local schema the same way — it delegates to OpenSpec, skips the cospec-only schemaVersion stamp and typed artifact-plan output, and prints a "legacy schema — reduced cospec guarantees" note; a name that resolves to neither a cospec type nor a project-local schema still gets today's unknown-type error.

config.yaml can't reach what a fork can context/rules are

additive prose injected into an instruction — they cannot override which sections a template requires or replace an instruction's structure outright. If you need a genuinely different artifact set or template body, that's what forking is for; config alone can't get you there. :::

Once a fork exists, cospec's own read-only inspection commands work on it the same as on any of the eleven built-in schemas:

  • cospec schemas lists every resolvable schema — the eleven cospec types plus your fork — with its artifact chain.
  • cospec schema which <change> reports which schema a change resolves to.
  • cospec schema validate <name> validates a schema's own structure, including a forked one.
  • cospec templates [--schema <name>] shows the resolved per-artifact template paths a schema composes to.

Forked schemas are legacy as far as the change lifecycle is concerned: cospec validate runs only structural checks against a legacy change and delegates the rest to openspec validate (which validates the fork against its own artifact graph), cospec apply skips the cospec blocker gate for it, and cospec doctor notes the reduced guarantees. What cospec does not relax for a legacy change: archive still runs the tasks gate, the scenario-preservation gate, the filesystem-move verification, and blocker-ledger fan-out — only the verification-ledger gate is scoped out, since a legacy schema has no cospec-typed verification artifact to gate on. So a fork trades the eleven built-in schemas' mechanically-required artifacts for OpenSpec-delegated structural validation of its own graph, while keeping every schema-agnostic cospec hard gate at full strength. cospec never generates, regenerates, or otherwise manages a forked schema itself — it's entirely yours from the moment you fork it. See the type and artifact matrix for what you're diverging from.

The eleven built-in types can't be weakened by a fork

cospec schema fork/init refuse outright before touching disk if the destination collides with one of the eleven cospec types — that guard is the only thing standing between a fork and overwriting a canon-managed schema.yaml. If you bypass cospec and run the native openspec schema fork/init directly against a reserved name, cospec's drift check (cospec doctor, cospec update --check) still catches the clobbered file as a hand-edit the next time either runs — the same as any other file modified outside the managed-file protocol — but that's a reactive backstop, not a substitute for going through cospec schema. :::

The managed-file protocol

Every cospec update recomposes each managed file and decides its fate by comparing the file's current hash to the content hash recorded in its own frontmatter (or, for schema YAML with no frontmatter, a manifest at openspec/.cospec-manifest.json):

SituationOutcome
File doesn't exist yetCreated
File exists but isn't cospec's (no matching provenance)Left alone; new content written to <file>.cospec-new
Unmodified since last generation, content unchangedNo-op
Unmodified since last generation, content changedRewritten in place
Modified by you since last generationLeft alone; new content written to <file>.cospec-new

Every write is atomic. Files a newer cospec version no longer emits are deleted only if you never touched them — otherwise they're preserved and reported, same as any other conflict.

Reconciling .cospec-new

When your hand-edit collides with an update, cospec never clobbers your file — it writes the new managed content next to it as <file>.cospec-new. To reconcile:

  1. Diff <file> against <file>.cospec-new.
  2. Fold in whatever you want from the new version.
  3. Delete <file>.cospec-new.

cospec doctor flags stale .cospec-new files so they don't linger unnoticed. If you'd rather just take the update and discard your edits, cospec update --force overwrites in place — it prints a diff summary first so you know what you're losing.

Running cospec update twice in a row with no intervening changes is a no-op: every file reports unchanged and your working tree stays clean.