-
Notifications
You must be signed in to change notification settings - Fork 4.8k
docs(store): propose project-scoped store registry discovery #1956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-09-22 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| ## Context | ||
|
|
||
| Store resolution today (`src/core/root-selection.ts`, `resolveOpenSpecRoot`) follows a strict precedence chain: `--store <id>` → nearest `openspec/` root (with optional `store:` pointer in config.yaml) → global `defaultStore` → registered-stores hint → implicit root. All store ID lookups go through one global registry file at `~/.local/share/openspec/stores/registry.yaml` (`src/core/store/foundation.ts`, `getStoreRegistryPath`). Every registry function accepts an optional `globalDataDir` (`StorePathOptions`) but always resolves to that single global location. | ||
|
|
||
| The existing `findQualifyingRootSync` in `root-selection.ts` walks up the directory tree to find the nearest `openspec/` root, using `findRepoPlanningRootSync` from `planning-home.ts` as the underlying walk. This same pattern can be reused to discover a project-scoped registry file. | ||
|
|
||
| ## Goals / Non-Goals | ||
|
|
||
| **Goals:** | ||
| - Let a project declare store bindings in a committed file, discovered automatically by walking up from cwd. | ||
| - Resolve store paths relative to the registry file's directory, using `path.join` / `path.resolve` for cross-platform safety. | ||
| - Preserve full backward compatibility — no behavior change when the project-scoped registry file does not exist. | ||
|
|
||
| **Non-Goals:** | ||
| - Automatic creation of the project-scoped registry file. Users create it manually or via an optional `--scope project` flag on `store register`. | ||
| - Merging or layering multiple project-scoped registries. Only the nearest one (closest to cwd) is used. | ||
| - Changing the global registry format or location. | ||
| - Adding clone/pull/push/sync for stores. The project-scoped registry only maps IDs to local paths. | ||
|
|
||
| ## Decisions | ||
|
|
||
| ### D1: Registry file location and name | ||
|
|
||
| **Decision:** `.openspec-store/registry.yaml` in the project root. | ||
|
|
||
| **Rationale:** The `.openspec-store/` directory is already established by store identity metadata (`store.yaml`). Placing the project-scoped registry there keeps store infrastructure in one place. The name `registry.yaml` mirrors the global registry file name. | ||
|
|
||
| **Alternative considered:** `.openspec/registry.yaml` — rejected because `.openspec/` is the planning directory (specs, changes, config), not store infrastructure. | ||
|
|
||
| ### D2: Registry file format | ||
|
|
||
| **Decision:** A YAML file with a `version` field and a `stores` map. Each store entry maps a store ID to a relative path: | ||
|
|
||
| ```yaml | ||
| version: 1 | ||
| stores: | ||
| platform-specs: | ||
| path: platform-specs | ||
| design-system-specs: | ||
| path: design-system-specs | ||
| ``` | ||
|
|
||
| **Rationale:** This is the format described in issue #1950 and in the spec. It is simpler than the global registry's `backend: { type: git, local_path: ... }` shape because project-scoped entries only need a path — backend type, remote, and branch are irrelevant when the store is already on disk. The `version` field allows future schema evolution. | ||
|
|
||
| **Alternative considered:** Reusing the global `StoreRegistryState` schema with `backend.type` / `backend.local_path` — rejected because it carries fields (`remote`, `branch`) that have no meaning in a project-scoped context, and the path field name (`local_path`) is confusing when the value is relative. A dedicated parser for the simpler format is straightforward. | ||
|
|
||
| ### D3: Path resolution | ||
|
|
||
| **Decision:** `path.resolve(registryDir, entry.path)` where `registryDir` is the directory containing `registry.yaml` and `entry.path` is the relative path from the store entry. Always use `path.resolve` / `path.join` — never string concatenation. | ||
|
|
||
| **Rationale:** Cross-platform requirement (config rule). `path.resolve` handles platform separators and normalizes `..` segments correctly. | ||
|
|
||
| ### D4: Discovery walk | ||
|
|
||
| **Decision:** Walk up from `process.cwd()`, checking for `.openspec-store/registry.yaml` at each level. Stop at the first match (nearest wins). Reuse the existing `findRepoPlanningRootSync` pattern from `planning-home.ts`. | ||
|
|
||
| **Rationale:** Same pattern npm/yarn/pnpm use for config file discovery, and that OpenSpec already uses for `openspec/` root discovery. Nearest-wins avoids ambiguity. The walk reuses the pattern from `findRepoPlanningRootSync` (checking for a specific file at each level) but is a separate function because the target file (`.openspec-store/registry.yaml`) differs from the planning-root marker. | ||
|
|
||
| **Alternative considered:** Walking up to the filesystem root and collecting all registries — rejected as unnecessary complexity. The nearest registry is sufficient for all identified use cases. | ||
|
|
||
| ### D5: Precedence in resolveOpenSpecRoot | ||
|
|
||
| **Decision:** Insert the project-scoped registry lookup between the nearest-root walk (step 2) and the global `defaultStore` fallback (step 3). When a store ID is looked up, the project-scoped and global registries are merged (project-scoped wins on conflict — see D8 for the full merge semantics and alternatives): | ||
|
|
||
| 1. `--store <id>` → merged registry (project-scoped + global, project-scoped wins on conflict — see D8) | ||
| 2. Nearest `openspec/` root (with `store:` pointer → merged registry) | ||
| 3. Project-scoped registry discovery (any store, not just a named one) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '45,85p' openspec/changes/project-scoped-store-discovery/design.md
sed -n '1,125p' openspec/changes/project-scoped-store-discovery/specs/store-discovery/spec.md
sed -n '380,470p' src/core/root-selection.tsRepository: Fission-AI/OpenSpec Length of output: 13480 🏁 Script executed: #!/bin/bash
set -o pipefail
printf '%s\n' '--- change files ---'
git ls-files 'openspec/changes/project-scoped-store-discovery/*'
printf '%s\n' '--- design relevant sections ---'
wc -l openspec/changes/project-scoped-store-discovery/design.md
sed -n '1,220p' openspec/changes/project-scoped-store-discovery/design.md
printf '%s\n' '--- tasks ---'
wc -l openspec/changes/project-scoped-store-discovery/tasks.md
cat -n openspec/changes/project-scoped-store-discovery/tasks.md
printf '%s\n' '--- root-selection symbols ---'
rg -n 'resolveOpenSpecRoot|resolveStoreRoot|resolveNearestOrDeclaredRoot|resolveDefaultStoreRoot|project|registry|registeredIds|allowImplicitRoot' src/core/root-selection.ts
printf '%s\n' '--- root-selection opening ---'
sed -n '1,430p' src/core/root-selection.ts
printf '%s\n' '--- related tests ---'
rg -n -g '*.{ts,tsx,js}' 'resolveOpenSpecRoot|project_store|project-scoped|registry' src test tests 2>/dev/null | head -240Repository: Fission-AI/OpenSpec Length of output: 42277 🏁 Script executed: git ls-files 'openspec/changes/project-scoped-store-discovery/*'
sed -n '1,220p' openspec/changes/project-scoped-store-discovery/design.md
cat -n openspec/changes/project-scoped-store-discovery/tasks.md
rg -n 'resolveOpenSpecRoot|resolveStoreRoot|resolveNearestOrDeclaredRoot|resolveDefaultStoreRoot|project|registry|registeredIds|allowImplicitRoot' src/core/root-selection.ts
sed -n '1,430p' src/core/root-selection.ts
rg -n -g '*.{ts,tsx,js}' 'resolveOpenSpecRoot|project_store|project-scoped|registry' src test tests 2>/dev/null | head -240Repository: Fission-AI/OpenSpec Length of output: 41782 Define the unnamed project-registry selection rule. When no Specify whether this branch requires an explicit selector, uses a declared default, or only contributes a hint. Add the same rule to the requirements and tests. 🤖 Prompt for AI Agents |
||
| 4. Global `defaultStore` | ||
| 5. Registered-stores hint / implicit root | ||
|
|
||
| **Rationale:** Project-scoped bindings are more specific than machine-global defaults but less specific than an explicit `--store` flag or a root found by walking up. This preserves backward compatibility: without a project-scoped registry, the chain is unchanged. | ||
|
|
||
| **Alternative considered:** Project-scoped registry before nearest root — rejected because a local `openspec/` root with a planning shape is the most specific signal and should win. | ||
|
|
||
| ### D6: Threading scope through existing functions | ||
|
|
||
| **Decision:** Extend `StorePathOptions` with an optional `projectRoot` field. When set, `getStoreRegistryPath` resolves to `path.join(projectRoot, STORE_METADATA_DIR_NAME, STORE_REGISTRY_FILE_NAME)` using existing constants, instead of the global path. Registry read functions (`readStoreRegistryState`, `listRegisteredStores`) accept the extended options. Registration and conflict-detection functions (`commitStoreRegistration`, `assertNoRegisteredStoreConflict`) also accept `projectRoot` but write the simpler `{ path: ... }` entry format (D2) rather than the global `backend` shape. | ||
|
|
||
| **Rationale:** Minimal API surface change. The `StorePathOptions` threading pattern is already established. Functions that call `readStoreRegistryState()` with no options (in `operations.ts`) will need explicit propagation, but the signature stays the same. | ||
|
|
||
| ### D7: New OpenSpecRootSource and diagnostic codes | ||
|
|
||
| **Decision:** Add `'project_store'` to the `OpenSpecRootSource` union. Add diagnostic codes: `project_registry_malformed`, `project_registry_not_found` (informational, not an error). | ||
|
|
||
| **Rationale:** The `source` field is how JSON output tells consumers where a root came from. New diagnostic codes follow the existing taxonomy pattern in `RootSelectionDiagnostic`. | ||
|
|
||
| ### D8: Resolution when project-scoped registry exists but store ID is absent | ||
|
|
||
| **Decision:** Merge project-scoped and global registries. When a store ID is present in both, the project-scoped entry wins. When a store ID is present only in the global registry, it resolves from the global registry with a warning in human mode and the existing `source: 'store'` marker in JSON mode. When a store ID is present only in the project-scoped registry, it resolves from the project-scoped registry with the new `source: 'project_store'` marker. | ||
|
|
||
| **Three approaches considered:** | ||
|
|
||
| **Variant A — Merge (npm-style).** Project-scoped and global registries are merged. Project-scoped overrides on ID conflict. Store IDs absent from the project-scoped registry but present in the global registry resolve from the global registry silently. | ||
| - *Pros:* Backward-compatible — global stores always accessible. Familiar pattern (npm/yarn/pnpm merge config files). | ||
| - *Cons:* Isolation is leaky — a store ID resolves from the machine's global registry, which may point to a different store on another machine. Developer may not realize where the store came from. | ||
|
|
||
| **Variant B — Nearest wins (isolation).** When a project-scoped registry is found, it fully replaces the global registry for resolution. A store ID absent from the project-scoped registry is an error, even if it exists globally. | ||
| - *Pros:* Full isolation. Explicit — developer knows all stores are declared in the project. | ||
| - *Cons:* Breaks backward compatibility — `--store <global-id>` stops working when a project-scoped registry exists. Not consistent with the npm-style merge pattern referenced in the proposal. | ||
|
|
||
| **Variant C — Merge with source indication (chosen).** Same merge as Variant A, but the resolution source is reported: `source: 'project_store'` or `source: 'store'` (global) in JSON output, and a warning in human mode when a store ID falls back to the global registry while a project-scoped registry is present. | ||
| - *Pros:* Backward-compatible. Predictable — developer sees where the store came from. Isolation is preserved for IDs declared in the project-scoped registry (project-scoped wins on conflict). Closest to the npm-style merge pattern. | ||
| - *Cons:* Slightly more complex implementation — resolution must track and report the source of each store lookup. | ||
|
|
||
| **Why C over A:** Silent fallback (A) creates a surprise — a store resolves from an unknown location. The source indication in C makes the behavior visible without blocking it. | ||
|
|
||
| **Why C over B:** B breaks backward compatibility — a developer who uses `--store <global-id>` alongside a project-scoped registry would get an error. C preserves their workflow while making the resolution path visible. | ||
|
|
||
| ## Risks / Trade-offs | ||
|
|
||
| - **[Stale project-scoped registry pointing at a moved directory]** → The `inspectRegisteredStore` health check already validates that a resolved store root exists and has a healthy `openspec/` shape. A stale entry produces the same `unhealthy_store_root` diagnostic as a stale global registration. | ||
| - **[Registry file committed with machine-specific paths]** → The spec recommends relative paths. If a user commits absolute paths, `path.resolve` still works — absolute paths are returned as-is. No validation rejects them, but documentation should recommend relative paths. | ||
| - **[Two project-scoped registries in the same ancestor chain]** → Nearest wins, by design. This is documented in the spec and matches the behavior of package manager config discovery. | ||
| - **[Performance of the discovery walk]** → The walk is bounded by filesystem depth and stops at the first match. The same cost as the existing `openspec/` root walk. No measurable impact. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| ## Why | ||
|
|
||
| OpenSpec stores (beta) use a machine-level registry that maps store IDs to absolute filesystem paths. After cloning a repository that references a store, every developer must manually run `openspec store register <path>` before the store is discoverable. Registering a second copy of the same store on the same machine under the same ID fails — only one checkout per store ID is supported. This makes stores impractical for teams that use meta-repositories, side-by-side clones, or any workflow where store bindings should travel with the repository rather than live on each machine. | ||
|
|
||
| ## What Changes | ||
|
|
||
| - Add an optional **project-scoped store registry** file (`.openspec-store/registry.yaml`) that maps store IDs to paths relative to that file. | ||
| - Discover this file automatically by searching from the current working directory and walking up the directory tree. This is a well-established discovery pattern used by package managers (npm, yarn, pnpm discover config files like `package.json`, `.npmrc`, `.yarnrc` by walking up the directory tree). OpenSpec uses a similar mechanism to find the nearest `openspec/` root. | ||
| - When a store ID is resolved (`--store`, `references:`, `store:` in config.yaml), a project-scoped registry will take precedence over the global registry. The exact resolution mechanism is a design decision — see design.md. Existing setups without a project-scoped registry will not be affected. | ||
| - The project-scoped registry file can be created manually or generated with `openspec store register --scope project`. No new commands — a new `--scope` parameter will be added to the existing `store register` command. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### New Capabilities | ||
|
|
||
| - `store-discovery`: Project-scoped store registry discovery and resolution — how OpenSpec finds a store by ID when a project-level registry file exists, how relative paths are resolved, and how project-scoped and global registries interact. | ||
|
|
||
| ## Impact | ||
|
|
||
| - `src/core/store/foundation.ts` — path resolution for the registry file; support for a project-scoped registry location alongside the global one. | ||
| - `src/core/store/registry.ts` — store lookup, conflict detection, and listing operations must accept and propagate a project scope option. | ||
| - `src/core/store/operations.ts` — bare `readStoreRegistryState()` calls propagate scope options from callers. | ||
| - `src/core/root-selection.ts` — the resolution chain in `resolveOpenSpecRoot` gains a project-scoped registry step between the nearest-root walk and the global `defaultStore` fallback. | ||
| - `src/commands/store.ts` — new `--scope project` flag for `store register`, `store list`, and `store unregister`. | ||
| - `src/core/references.ts` — referenced-store index assembly resolves store IDs through the merged registry (project-scoped + global) when a project-scoped registry is available. | ||
| - Tests across `test/core/root-selection.test.ts`, `test/core/store/`, and `test/cli-e2e/` — new test coverage for project-scoped discovery, relative path resolution, and precedence over the global registry. | ||
| - `docs/stores-beta/user-guide.md` — documentation of the project-scoped registry feature. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| ## Purpose | ||
|
|
||
| Lets teams declare store bindings inside their repository so that OpenSpec discovers stores automatically after clone, without requiring each developer to run `openspec store register` on their machine. | ||
|
|
||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: Project-scoped store registry discovery | ||
|
|
||
| The system SHALL discover a project-scoped store registry by walking up from the current working directory, looking for a `.openspec-store/registry.yaml` file. When found, the system SHALL merge it with the global registry, with project-scoped entries taking precedence on store ID conflicts. | ||
|
|
||
| #### Scenario: Store resolved from project-scoped registry | ||
| - **WHEN** a project contains `.openspec-store/registry.yaml` mapping a store ID to a relative path | ||
| - **AND** the user runs a command with `--store <id>` from within that project | ||
| - **THEN** the system resolves the store to the path relative to the registry file's directory | ||
| - **AND** no global registry registration is required | ||
|
|
||
| #### Scenario: Project-scoped registry not found | ||
| - **WHEN** no `.openspec-store/registry.yaml` exists in the current directory or any ancestor | ||
| - **THEN** the system resolves store IDs from the global registry | ||
| - **AND** existing behavior is unchanged | ||
|
|
||
| #### Scenario: Store ID not in project-scoped registry, present in global | ||
| - **WHEN** a project-scoped registry exists but does not contain the requested store ID | ||
| - **AND** the global registry contains the requested store ID | ||
| - **THEN** the system resolves the store from the global registry | ||
| - **AND** in JSON output, sets `source` to `'store'`; in human output, displays a warning that the store was resolved from the global registry | ||
|
|
||
| #### Scenario: Store ID not in project-scoped registry, not in global | ||
| - **WHEN** a project-scoped registry exists but does not contain the requested store ID | ||
| - **AND** the global registry also does not contain the requested store ID | ||
| - **THEN** the system reports an error listing available stores from both registries | ||
|
|
||
| #### Scenario: Project-scoped registry takes precedence over global registry | ||
| - **WHEN** a store ID is registered in both the project-scoped registry and the global registry | ||
| - **AND** the project-scoped registry maps the ID to a different path than the global registry | ||
| - **THEN** the system uses the path from the project-scoped registry | ||
| - **AND** in JSON output, sets `source` to `'project_store'` | ||
|
|
||
| #### Scenario: Project-scoped registry discovered from subdirectory | ||
| - **WHEN** `.openspec-store/registry.yaml` exists at the project root | ||
| - **AND** the user runs a command from a subdirectory of the project | ||
| - **THEN** the system discovers the registry by walking up to the project root | ||
|
|
||
| ### Requirement: Relative path resolution in project-scoped registry | ||
|
|
||
| The system SHALL resolve store paths in a project-scoped registry relative to the directory containing the registry file, using platform-appropriate path joining. | ||
|
|
||
| #### Scenario: Relative path resolved from registry directory | ||
| - **WHEN** `.openspec-store/registry.yaml` at `/project/` maps store ID `specs` to path `specs` | ||
| - **THEN** the system resolves the store root to `/project/specs` | ||
|
Comment on lines
+49
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Correct the relative-path example. The registry file is 🤖 Prompt for AI Agents |
||
|
|
||
| #### Scenario: Parent directory relative path | ||
| - **WHEN** `.openspec-store/registry.yaml` at `/project/app/` maps a store ID to path `../specs` | ||
| - **THEN** the system resolves the store root to `/project/specs` | ||
|
|
||
| #### Scenario: Cross-platform path handling | ||
| - **WHEN** the registry file is on Windows at `C:\project\` | ||
| - **AND** the store path is `specs` | ||
| - **THEN** the system resolves the store root using platform-appropriate path separators (`C:\project\specs`) | ||
|
|
||
| ### Requirement: Project-scoped registry file format | ||
|
|
||
| The system SHALL accept a YAML file with a `version` field and a `stores` map. Each store entry maps a store ID to a path relative to the registry file's directory. | ||
|
|
||
| #### Scenario: Valid registry file | ||
| - **WHEN** `.openspec-store/registry.yaml` contains: | ||
| ```yaml | ||
| version: 1 | ||
| stores: | ||
| platform-specs: | ||
| path: platform-specs | ||
| ``` | ||
| - **THEN** the system parses the file and makes store ID `platform-specs` resolvable | ||
|
|
||
| #### Scenario: Malformed registry file | ||
| - **WHEN** `.openspec-store/registry.yaml` exists but contains invalid YAML | ||
| - **THEN** the system reports an error identifying the file and the parse failure | ||
| - **AND** resolves store IDs from the global registry | ||
|
Comment on lines
+77
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '60,110p' openspec/changes/project-scoped-store-discovery/design.md
sed -n '60,90p' openspec/changes/project-scoped-store-discovery/specs/store-discovery/spec.md
rg -n "diagnostic|malformed|exitCode|process.exit|json" src/core/root-selection.ts src/commands | head -120Repository: Fission-AI/OpenSpec Length of output: 15268 🏁 Script executed: #!/bin/bash
set -o pipefail
printf '%s\n' '--- change files ---'
git ls-files 'openspec/changes/project-scoped-store-discovery/*'
printf '%s\n' '--- spec ---'
cat -n openspec/changes/project-scoped-store-discovery/specs/store-discovery/spec.md
printf '%s\n' '--- design decisions and tasks ---'
rg -n -C 4 'malformed|unsupported|diagnostic|exit|JSON|json|fatal|fallback|task|TODO' openspec/changes/project-scoped-store-discovery/design.md openspec/changes/project-scoped-store-discovery/tasks.md
printf '%s\n' '--- root selection definitions and output ---'
cat -n src/core/root-selection.ts | sed -n '1,130p'
cat -n src/core/root-selection.ts | sed -n '220,410p'
cat -n src/core/root-selection.ts | sed -n '500,575p'
printf '%s\n' '--- root-selection contracts and tests ---'
rg -n -C 3 'RootSelectionDiagnostic|RootSelectionError|toRootOutput|resolveRootForCommand|status:|process.exitCode|--json|diagnostic' src tests 2>/dev/null | head -260Repository: Fission-AI/OpenSpec Length of output: 42098 🤖 get_repo_knowledge executed:
Length of output: 11767 🏁 Script executed: #!/bin/bash
set -o pipefail
printf '%s\n' '--- files ---'
git ls-files 'openspec/changes/project-scoped-store-discovery/*'
printf '%s\n' '--- complete spec ---'
cat -n openspec/changes/project-scoped-store-discovery/specs/store-discovery/spec.md
printf '%s\n' '--- design/task contract references ---'
rg -n -C 5 'malformed|unsupported|diagnostic|exit|JSON|json|fatal|fallback' openspec/changes/project-scoped-store-discovery/design.md openspec/changes/project-scoped-store-discovery/tasks.md
printf '%s\n' '--- root-selection source ---'
cat -n src/core/root-selection.ts | sed -n '1,130p'
cat -n src/core/root-selection.ts | sed -n '220,410p'
cat -n src/core/root-selection.ts | sed -n '500,575p'
printf '%s\n' '--- related contracts/tests ---'
rg -n -C 3 'RootSelectionDiagnostic|RootSelectionError|toRootOutput|resolveRootForCommand|process.exitCode|status:' src test tests 2>/dev/null | head -300Repository: Fission-AI/OpenSpec Length of output: 41870 🏁 Script executed: sed -n '1,120p' src/core/root-selection.ts; sed -n '500,575p' src/core/root-selection.ts; cat -n openspec/changes/project-scoped-store-discovery/specs/store-discovery/spec.md; rg -n -C 4 'malformed|unsupported|diagnostic|exit|JSON|fallback' openspec/changes/project-scoped-store-discovery/design.md openspec/changes/project-scoped-store-discovery/tasks.mdRepository: Fission-AI/OpenSpec Length of output: 27276 Define recoverable project-registry diagnostics.
Define malformed and unsupported project registries as recoverable diagnostics. Specify the human-mode warning, the exit status when global fallback succeeds, and the JSON shape containing both the diagnostic and the selected global root ( 🤖 Prompt for AI Agents |
||
|
|
||
| #### Scenario: Registry file with unsupported version | ||
| - **WHEN** `.openspec-store/registry.yaml` contains `version: 2` | ||
| - **THEN** the system reports an error identifying the file and the unsupported version | ||
| - **AND** resolves store IDs from the global registry | ||
|
|
||
| ### Requirement: Backward compatibility with existing setups | ||
|
|
||
| The system SHALL NOT change behavior when no project-scoped registry file exists. All existing store resolution through the global registry SHALL continue to work without modification. | ||
|
|
||
| #### Scenario: No project-scoped registry, global registry used | ||
| - **WHEN** no `.openspec-store/registry.yaml` exists in the current directory or any ancestor | ||
| - **AND** a store is registered in the global registry | ||
| - **THEN** the system resolves the store from the global registry exactly as before | ||
|
|
||
| #### Scenario: No project-scoped registry, no global registration | ||
| - **WHEN** no `.openspec-store/registry.yaml` exists | ||
| - **AND** no store is registered in the global registry | ||
| - **THEN** the system reports the same error as before this feature was introduced | ||
|
|
||
| ### Requirement: Project-scoped registry with multiple stores | ||
|
|
||
| The system SHALL support multiple store entries in a single project-scoped registry file, each mapping to an independent path. | ||
|
|
||
| #### Scenario: Multiple stores in one registry | ||
| - **WHEN** `.openspec-store/registry.yaml` contains: | ||
| ```yaml | ||
| version: 1 | ||
| stores: | ||
| platform-specs: | ||
| path: platform-specs | ||
| design-system-specs: | ||
| path: design-system-specs | ||
| ``` | ||
| - **THEN** both store IDs are resolvable from within the project | ||
|
|
||
| #### Scenario: Multiple clones of the same repository | ||
| - **WHEN** two clones of the same repository exist on the same machine | ||
| - **AND** each clone has its own `.openspec-store/registry.yaml` with the same store IDs | ||
| - **THEN** each clone resolves store IDs from its own project-scoped registry | ||
| - **AND** no conflict occurs between the clones, even if the global registry contains the same store IDs | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Fission-AI/OpenSpec
Length of output: 21159
🏁 Script executed:
Repository: Fission-AI/OpenSpec
Length of output: 45547
🤖 get_repo_knowledge executed:
get_repo_knowledge Fission-AI/OpenSpec /tmp/coderabbit-repo-knowledge/fission-ai-openspec-724e7abf/learningsLength of output: 8777
🏁 Script executed:
Repository: Fission-AI/OpenSpec
Length of output: 41901
Use one resolved project root for discovery and project-scoped writes.
store ... --scope projectis planned to setprojectRoot: process.cwd(). From a project subdirectory, this writes.openspec-store/registry.yamlunder that subdirectory. Because discovery uses nearest-wins semantics, the new registry can shadow the ancestor project's registry.The existing root-selection contract supports
startPath, but the planned store CLI flow does not use it. Thread the resolved project root, or one sharedstartPath, through discovery and all project-scoped store operations. Do not deriveprojectRootdirectly fromprocess.cwd().🤖 Prompt for AI Agents