docs-lab/multi-repo/stores.md
Plan changes that span repositories: one store, many repos.
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.
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.
One person creates the store, then everyone else joins it.
Create the store (one person, once per team). Run openspec store setup and answer the prompts:
# run from anywhere; it asks what to create and where
openspec store setup
It asks three questions:
team-plans~/openspec/<name>, press Enter to accept it or type another pathYesThen it reports what it created:
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 <change-id> --store team-plans
Share this store by committing and pushing it like any Git repo.
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:
# connect the store to the empty repo on your git host
cd ~/openspec/team-plans
git remote add origin [email protected]:acme/team-plans.git
# publish it
git push -u origin main
Join the store (every teammate, once per machine):
# get the store onto your machine
git clone [email protected]:acme/team-plans.git ~/openspec/team-plans
# tell OpenSpec where it lives
openspec store register ~/openspec/team-plans
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.
Confirm it worked, from any directory:
# any OpenSpec command reaches the store by name
openspec status --store team-plans
Using OpenSpec root: team-plans (/Users/you/openspec/team-plans)
No active changes. Create one with: openspec new change <name> --store team-plans
OpenSpec has three setups. The rest of this page uses these names:
openspec/ folder and also reaches a store when you ask.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 teaches, and most projects never need another.
web-app (code repo)
└── openspec/
├── specs/
└── changes/
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 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
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 shows how.
When you use a store, OpenSpec also has to decide where the artifacts get created. It depends on your setup:
store: line below records that.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:
--store <id> on a command. Always wins, from any directory.openspec/ folder. If your project has its own specs/ or changes/ folders, OpenSpec uses them.store: line in your project. How a store-only project records its store.defaultStore on your machine. The fallback when none of the above applies.Whichever applied, OpenSpec's first output line names the folder it acted on (Using OpenSpec root: ...). The exact rules, including the error cases, are in Configuration › Stores.
store: line (store-only projects)Add one line to your project's openspec/config.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:
# inside web-app, connected
openspec status
Using OpenSpec root: team-plans (/Users/you/openspec/team-plans)
No active changes. Create one with: openspec new change <name> --store team-plans
specs/ or changes/ folders, OpenSpec uses those and ignores the line, with a warning.defaultStore on your machineSet 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:
# 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.
To take a repo from repo-local to store-only:
openspec/specs/ and openspec/changes/ into the same folders in the store.openspec/ folder holds only config.yaml.store: line to that config.yaml.openspec status inside the repo now starts with Using OpenSpec root: team-plans.
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 covers that.
Create a change from inside a store-only repo and it lands in the store:
# inside web-app; the store: line routes this to team-plans
openspec new change add-login
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
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:
# check the current root and its stores
openspec doctor
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).
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:
# 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:
<referenced_stores>
<!-- Read-only upstream context. Fetch what you need; cite what you use. -->
Store team-plans (/Users/you/openspec/team-plans):
- payments: Rules for charging and refunding customers.
Fetch: openspec show <spec-id> --type spec --store team-plans
</referenced_stores>
A reference can also carry the store's clone URL, for machines that don't have that store yet:
references:
- team-plans
- { id: design-system, remote: "[email protected]:acme/design-system.git" }
With the URL declared, openspec doctor turns a missing store into a pasteable fix:
# 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 -- [email protected]:acme/design-system.git '/Users/you/openspec/design-system' &&
openspec store register '/Users/you/openspec/design-system' --id design-system
openspec store unregister first.