Skip to content

Proposal: a standard way for a store to describe its consuming repos (cross-repo task grouping) #1436

Description

@clay-good

Where this comes from

Discord feedback from henrik_denmark (thread), at the end of a longer conversation about running one store across several code repos:

One thing I doubt is the right choice […] is the notion that the store should not know anything about the repos involved. In order to effectively know which tasks group where, it needs to know about the purpose of each repository so that it does not need to be specified in each change. For now I put it in the context part of the openspec config and in the CLAUDE.md of the store repository. I would lean towards having a standard way of describing it, but one can also argue it just belongs in the context.

This issue generalizes that into a design discussion for the stores beta.

Problem

Stores are deliberately unaware of their consumers. All pointing goes one way — code repo → store, via store: and references: in openspec/config.yaml — and the user guide states the invariant plainly: "Declarations, not machinery." That keeps boundaries clean, but it leaves a gap on the planning side:

The repo landscape is stable per team, yet it must be restated per change. Every change planned in a store that fans out to multiple repos needs the same background — which repos exist, what each one is for, and therefore where each task belongs. Today the agent planning tasks.md either gets that respelled in every proposal, or it gets smuggled in as free text (the context: field, the store's CLAUDE.md).

What exists today (current main)

Mechanism Direction Shape What reads it
store: pointer (project-config.ts) code repo → store store id string root resolution (fallback)
references: code repo → store id + optional remote instructions (spec index), doctor, context
Store identity (.openspec-store/store.yaml, foundation.ts) store, about itself strict schema: version, id, remote — unknown keys are a parse error registration, health checks
context: in the resolved root's config.yaml free text, ≤50KB injected verbatim into every artifact instruction (instruction-loader.ts) planning agents
defaultStore (global config) machine → store store id string root resolution (last fallback)
Worksets personal, uncommitted member folder paths workset open

Two observations worth anchoring the discussion on:

  1. The workaround works — by accident of root resolution. When commands resolve to the store, it is the store's own openspec/config.yaml context: that gets injected into planning instructions. So describing your repos there does reach the agent. But it is an opaque blob: agents may use it, no command can. Nothing can group, validate, scope, or health-check against it.
  2. There is no structured, committed place for this even if we wanted one. The store's identity file has a strict schema, so a hand-added repos: key there fails parsing today. The project config schema has no repo-shaped field either.

Concept: a committed repo directory in the store

A standard, structured way for a store to say "here are the repos that build what I plan, and what each is for" — committed and shared like everything else in the store:

# lives in the store; exact home is design question 1
repos:
  - id: api-server
    purpose: REST API and business logic. Owns the OpenAPI contract.
    remote: git@github.com:acme/api-server.git
  - id: web-app
    purpose: Customer-facing SPA. Consumes the API; no business logic.

The "declarations, not machinery" invariant would hold: this changes what OpenSpec can tell you, never where commands act. No cloning, no orchestration, no cross-repo execution.

What it could unlock

Capability Surface
Repo directory injected once into planning instructions — changes stop restating it instructions in the store
Task grouping becomes a convention tooling knows about (## api-server sections in tasks.md), suggestable and checkable templates, validate
A code repo can identify its slice of a cross-repo change (match itself against a manifest entry) apply-side skills, status
"Declared but not registered / remote drift" checks with pasteable fixes doctor
Suggested members when assembling the working set context, workset

Open design questions

  1. Where does it live? The identity file (strict, currently minimal on purpose), the store's own openspec/config.yaml (already injected into instructions, but the project schema is repo-agnostic today), or a new committed file (e.g. openspec/repos.yaml)?
  2. Structured data or blessed convention? Minimum: a documented shape that renders into instruction context — no new readers. Maximum: a typed schema consumed by instructions, validate, doctor, and apply-side tooling.
  3. How does a code repo know which entry is itself? Match by git remote, an explicit repo: <id> alongside store:, or not at all (guidance-only)?
  4. Relationship to references:. This is the reverse edge of an existing declaration. Do they stay independent, or should doctor cross-check both directions?
  5. Does task grouping become semantic? Do validate/apply-side skills understand per-repo sections, or does grouping stay a documented convention the manifest merely feeds?
  6. Drift. The manifest is user-maintained and can go stale like any doc. What, if anything, does doctor verify?

Alternatives considered

Stores are beta and the file formats are explicitly allowed to change shape between releases — which makes now the cheap moment to settle this.

Related

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