Skip to content

Feature: Add defaultStore to global config as machine-level fallback in root resolution #1359

Description

@roosteer

Problem

OpenSpec stores are great for cross-repo planning, but there is no way to set a machine-level default store. Today, the only way to make a repo use a store without --store on every command is to add store: <id> to that repo's openspec/config.yaml:

# web-app/openspec/config.yaml
store: team-plans

If you work across many repos that all share the same planning store, you must repeat this line in every repo. There is no global equivalent.

The global config (~/.config/openspec/config.json, managed by openspec config) has no store or defaultStore field — verified in src/core/config-schema.ts and src/core/global-config.ts. The GlobalConfig interface only defines featureFlags, profile, delivery, workflows, and openers. Even if you manually add a store key to the JSON, no code path reads it: root-selection.ts only consults the project-level config.yaml for store pointers.

Proposal

Add a defaultStore field to the global config that acts as a fallback in root resolution — consulted only when no --store flag, no local planning root, and no project-level store: pointer resolve.

Root resolution order (proposed change)

1. --store <id>             explicit                     → that store
2. nearest openspec/        local planning root          → this repo
   (walk-up from cwd,
    qualified)
3. store: pointer           project config.yaml          → that store
4. defaultStore (NEW)       global config.json           → that store
   (machine-level fallback)
5. none of the above        stores registered?            → error with
                                                          selection hint

Step 4 is the only new step. It preserves all existing precedence: --store still wins, local planning roots still win, project-level pointers still win. It only changes the failure path — instead of erroring, it tries the global default first.

Configuration

// ~/.config/openspec/config.json
{
  "profile": "core",
  "delivery": "both",
  "defaultStore": "team-plans"
}

Or via CLI:

openspec config set defaultStore team-plans

Schema change

Add to GlobalConfigSchema in src/core/config-schema.ts:

defaultStore: z
  .string()
  .optional()
  .describe('Store id used as fallback root when no explicit --store, local root, or project-level store: pointer resolves'),

And to the GlobalConfig interface in src/core/global-config.ts:

defaultStore?: string;

Resolution logic

In root-selection.ts, after the nearest-root and project-level pointer checks fail, before the final error, consult getGlobalConfig().defaultStore:

// Step 4 (new): global defaultStore fallback
const globalConfig = getGlobalConfig();
if (globalConfig.defaultStore) {
  return resolveStoreRoot(globalConfig.defaultStore, globalDataDir, 'declared');
}

// Step 5: error with selection hint (existing behavior)

If the defaultStore id is not registered, it should degrade to the existing error with a hint to either register it or clear the stale global default — same pattern as unresolvable project-level store: pointers.

Why this matters

  • Reduces friction for single-store workflows. A solo developer or small team with one planning repo should not need to touch every code repo's openspec/config.yaml just to point at the same store.
  • Makes stores more approachable. Today the cognitive load is: "set up store → register → remember --store everywhere OR edit every repo's config.yaml." With this, it becomes: "set up store → register → set global default → done."
  • Non-breaking. It is purely additive to the resolution chain, inserted before the error case. Existing users see no behavior change unless they opt in.

Design considerations

  1. Why not reuse the store key name? Using defaultStore makes the intent clear and avoids confusion with the project-level store: pointer (which is a direct declaration, not a fallback). Different name, different semantics.

  2. Should this be in the stores beta docs? Yes — the Stores User Guide would get a short section under "Declaring a default store" covering both per-repo (store: in config.yaml) and per-machine (defaultStore in global config) options.

  3. Interaction with openspec config. openspec config set defaultStore <id> and openspec config get defaultStore should work. validateConfigKeyPath in config-schema.ts would need defaultStore added to KNOWN_TOP_LEVEL_KEYS.

  4. What if the store is not registered? Same as an unresolvable project-level pointer: degrade to the error with a selection hint, plus a note that the global defaultStore is stale and how to clear it (openspec config set defaultStore "" or similar).

Scope

This is a small change:

  • src/core/config-schema.ts — add field to schema + known keys
  • src/core/global-config.ts — add field to interface + defaults
  • src/core/root-selection.ts — insert fallback step before error
  • docs/stores-beta/user-guide.md — document the new option
  • docs/cli.md — mention in config section

No changes to store registration, metadata, or existing resolution semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions