mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-03 22:13:19 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5083dcb8 |
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-28
|
||||
@@ -0,0 +1,90 @@
|
||||
## Context
|
||||
|
||||
See `proposal.md` for motivation and `specs/custom-openspec-directory/spec.md` for the user contract.
|
||||
|
||||
The current root model has one overloaded path. `ResolvedOpenSpecRoot.path` names a repository or store root, while most consumers reconstruct its data directory with `path.join(root.path, 'openspec', ...)`. `init` and `update` use that same path for both planning files and project-local agent integrations. Stores work because their planning files retain the conventional `<store root>/openspec` layout; relocating only a normal project's planning directory exposes the missing distinction.
|
||||
|
||||
The implementation also contains command-local path construction and generated instructions that say `<planningHome.root>/openspec/...`. Adding an environment check at one entry point would therefore produce split-brain behavior: some commands would use the custom location while others and the coding agent would continue reading or writing the default one.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Represent the workspace and complete OpenSpec directory separately in one canonical root result.
|
||||
- Give every shipped command and generated workflow the same custom-directory behavior.
|
||||
- Preserve store validation, identity, precedence, diagnostics, and default project behavior.
|
||||
- Make selection visible and fail closed before mutations.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Configure `specs`, `changes`, `archive`, or `schemas` independently.
|
||||
- Add a committed root pointer, search descendants for candidate directories, or infer a directory from agent instructions.
|
||||
- Turn an in-repository custom directory into a registered store or change store metadata.
|
||||
- Make `OPENSPEC_DIR` a persistent team setting. Teams remain responsible for setting the environment consistently in shells, task runners, and CI.
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Select the complete directory with `OPENSPEC_DIR`
|
||||
|
||||
`OPENSPEC_DIR` names the directory that directly contains `config.yaml`, `specs/`, `changes/`, and optional `schemas/`. For example, `OPENSPEC_DIR=ai/openspec` selects `<workspace>/ai/openspec`.
|
||||
|
||||
The variable is the bootstrap mechanism because config inside the directory cannot locate itself. A new root-level YAML file was rejected because it replaces one unwanted root entry with another. Recursive discovery was rejected because it becomes ambiguous in monorepos and makes command behavior depend on unrelated descendants. Reusing store registration was rejected because a directory inside one code workspace is not a standalone planning repository and should not acquire store identity or machine-global registration.
|
||||
|
||||
### 2. Resolve a workspace-relative path by walking ancestors
|
||||
|
||||
`OPENSPEC_DIR` is a relative path within a workspace. Normal commands walk from the start directory toward the filesystem root and select the nearest ancestor where the relative candidate is an existing, qualifying OpenSpec directory. The matching ancestor is therefore the unambiguous workspace root, while the candidate is the OpenSpec directory. A closer ancestor with its own qualifying candidate is a nested workspace and wins for invocations inside it, matching existing nearest-root semantics. This mirrors nearest-root discovery without scanning descendants: `OPENSPEC_DIR=ai/openspec` works from both a workspace root and its subdirectories.
|
||||
|
||||
`init [path]` is the creation exception. It resolves the value against the explicit target workspace because the directory does not exist yet. `update [path]` resolves against the explicit target first and requires the result to exist. Unset, empty, and whitespace-only values mean no override so common shell and CI defaults preserve today's behavior. Non-empty absolute values, paths that escape the workspace lexically or after symlink canonicalization, filesystem roots, files, and directories without an OpenSpec config or planning shape fail with typed diagnostics. Existing path identity is canonicalized using the same utilities as store resolution, including symlink and Windows alias handling, before enforcing workspace containment.
|
||||
|
||||
Absolute and outside-workspace values are deliberately rejected: they select planning data without identifying the workspace that owns project-local agent files. A standalone planning repository already has an explicit, identity-preserving mechanism in stores. Keeping `OPENSPEC_DIR` workspace-relative makes `root.path` stable and avoids adding a second workspace variable or guessing from Git metadata.
|
||||
|
||||
### 3. Make selection explicit and non-ambiguous
|
||||
|
||||
An explicit `--store` and a non-empty `OPENSPEC_DIR` are mutually exclusive; supplying both fails before registry or filesystem mutation. Otherwise, a non-empty `OPENSPEC_DIR` is resolved before nearest-root and default-store fallback because setting it is an explicit process-level choice. An invalid non-empty value fails closed instead of silently falling back to another root.
|
||||
|
||||
The selected custom directory prints a root banner in human mode. JSON root output adds `openspec_dir` and the source value `environment`; `openspec_dir` is emitted for every resolved source so agents never reconstruct it. Existing `root.path` remains the workspace/store root for compatibility. Generated workflow instruction payloads likewise add `planningHome.openspecDir` while retaining existing fields.
|
||||
|
||||
The always-visible provenance addresses the main environment-variable risk: a long-lived shell can otherwise direct writes somewhere unexpected. This follows stores' established banner and machine-readable-root pattern rather than creating an invisible override.
|
||||
|
||||
### 4. Separate workspace paths from planning paths
|
||||
|
||||
The canonical result will carry at least:
|
||||
|
||||
- `path`: the existing workspace or store root contract.
|
||||
- `openspecDir`: the authoritative complete OpenSpec directory.
|
||||
- `changesDir`, `specsDir`, and `archiveDir`: derived once from `openspecDir`.
|
||||
- `source` and optional `storeId`: selection provenance.
|
||||
|
||||
For a default project, `openspecDir` is `<workspace>/openspec`. For a store, it is `<store root>/openspec`. For an environment-selected project, `path` is the ancestor that resolved the relative value (or the explicit init/update workspace), while `openspecDir` is the selected directory. Because the environment value must be relative and remain inside that workspace, both paths are defined for every successful resolution.
|
||||
|
||||
`init` and `update` continue using the workspace path for `.claude/`, `.codex/`, other project-local tool files, root stubs, detection, and cleanup. Config, OpenSpec-owned instructions, schemas, specs, changes, and archives use `openspecDir`. This distinction is passed explicitly; no consumer derives one path from the other.
|
||||
|
||||
### 5. Migrate consumers onto directories, not another helper that hides joins
|
||||
|
||||
The root resolver and planning-home adapter derive all OpenSpec subdirectories. Root-scoped command APIs receive the resolved root or the exact directory they need. Project-config and schema APIs gain directory-aware entry points while compatibility wrappers retain default behavior for external callers.
|
||||
|
||||
Every shipped command path is included, including `init`, `update`, `config`, `schema`, `templates`, `view`, `doctor`, and deprecated noun-form commands that remain executable. Leaving a cwd-based path in a deprecated command would still let the same invocation read one root and write another.
|
||||
|
||||
Generated workflow text must read `root.openspec_dir` or `planningHome.openspecDir`. Literal `openspec/` examples may remain only when they describe the default layout, never when they direct a file operation. Generated snapshots and parity hashes are refreshed from the shared templates.
|
||||
|
||||
### 6. Keep store structure and registration unchanged
|
||||
|
||||
Registered stores continue requiring their conventional `openspec/` child and identity metadata. `OPENSPEC_DIR` does not customize a store's internal layout, participate in the registry, or weaken health checks. Stores benefit only from the shared explicit `openspecDir` field and from consumers no longer reconstructing their paths.
|
||||
|
||||
This keeps #697 independent from project-scoped store discovery and configurable individual artifact paths. It also preserves the stores simplification principle: one root-selection path and one observable root contract.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **A partially migrated consumer could read or write the default directory.** → Inventory every hardcoded runtime and generated `openspec/` path, add command-parity tests, and make directory derivation a shared dependency-direction invariant.
|
||||
- **A stale environment variable could redirect writes.** → Emit provenance for every custom-root command, reject invalid values and `--store` conflicts, and document consistent environment use across a workflow.
|
||||
- **Relative resolution could differ by invocation directory.** → Use nearest-ancestor candidate resolution and cover workspace-root, nested-directory, spaces, Windows separators, and symlink aliases.
|
||||
- **Separating workspace and planning paths expands the init/update surface.** → Test that planning files move while every project-local tool file remains byte-for-byte at the workspace root.
|
||||
- **Adding JSON fields can affect strict consumers.** → Keep all existing keys and meanings, document the additive fields, and add agent-contract fixtures. This is a minor feature change, not a key rename.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Introduce the directory-aware root types and default/store adapters with compatibility tests while behavior is unchanged.
|
||||
2. Add environment resolution, diagnostics, provenance, and root-selection tests.
|
||||
3. Move runtime consumers, init/update, and generated workflows onto `openspecDir`, with parity coverage after each group.
|
||||
4. Document `OPENSPEC_DIR`, add a minor changeset, and run the complete macOS/Linux suite plus Windows CI.
|
||||
5. Rollback removes environment selection and the additive fields; default projects and stores retain their existing on-disk layouts throughout.
|
||||
@@ -0,0 +1,34 @@
|
||||
## Why
|
||||
|
||||
OpenSpec can select a standalone store anywhere on disk, but a normal project still has to keep its complete `openspec/` directory at the workspace root. This blocks teams that must place tool-owned content under an existing hierarchy such as `ai/openspec/`, even though the canonical root-selection layer already proves that commands can operate on planning data outside the current directory.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `OPENSPEC_DIR` as an explicit, process-scoped selection of the complete OpenSpec directory.
|
||||
- Make the canonical root result distinguish the workspace root, where project-local agent integrations live, from the OpenSpec directory, where config, schemas, specs, and changes live.
|
||||
- Route every shipped CLI command and generated workflow through that canonical directory instead of reconstructing `<root>/openspec` locally.
|
||||
- Preserve stores as standalone registered planning roots: a store still resolves to `<store root>/openspec`, but it uses the same directory-aware result as a normal or customized project.
|
||||
- Keep existing root selection, filesystem behavior, and human output unchanged when `OPENSPEC_DIR` is unset or empty; retain every existing JSON field and meaning while adding the authoritative directory field.
|
||||
- Reject invalid or conflicting custom-directory selection before reading or writing files, with the selected directory visible in human and JSON provenance.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `custom-openspec-directory`: Environment-based selection, precedence, diagnostics, provenance, and command parity for a relocated complete OpenSpec directory.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `cli-init`: Initialize planning files in the selected OpenSpec directory while keeping project-local agent integrations at the requested workspace root.
|
||||
- `cli-update`: Refresh planning instructions in the selected OpenSpec directory and agent integrations at the workspace root.
|
||||
- `config-loading`: Load project configuration from the selected OpenSpec directory.
|
||||
- `schema-resolution`: Resolve project-local schemas from the selected OpenSpec directory.
|
||||
- `ai-tool-paths`: Keep project-local tool paths anchored to the workspace when planning files are relocated.
|
||||
|
||||
## Impact
|
||||
|
||||
- Root contract: `ResolvedOpenSpecRoot`, `PlanningHome`, human root banners, and JSON root output gain an authoritative OpenSpec-directory path and environment provenance.
|
||||
- CLI: `init`, `update`, normal root-scoped commands, config/schema commands, view, doctor, and deprecated-but-shipped command paths must share the same directory resolution.
|
||||
- Generated content: workflow templates and GitHub Copilot agent files must consume the resolved directory rather than assume `openspec/` beneath the process working directory.
|
||||
- Tests and docs: cross-platform path, symlink identity, precedence, store parity, JSON, init/update, and generated-content parity coverage; CLI and agent-contract documentation; a minor changeset.
|
||||
- No new project file, registry format, dependency, directory scan, or `specsPath`-style partial layout is introduced.
|
||||
@@ -0,0 +1,19 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Planning relocation does not relocate project-local AI tools
|
||||
|
||||
Project-local AI tool skills, commands, prompts, workflows, and managed root stubs SHALL remain anchored to the workspace root when `OPENSPEC_DIR` relocates planning data.
|
||||
|
||||
#### Scenario: Tool paths remain at workspace root
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` selects `ai/openspec`
|
||||
- **WHEN** init or update generates project-local files for a selected AI tool
|
||||
- **THEN** those files SHALL remain under the tool's documented workspace-relative path
|
||||
- **AND** they SHALL NOT be generated beneath `ai/`
|
||||
|
||||
#### Scenario: Generated instructions use authoritative planning paths
|
||||
|
||||
- **GIVEN** planning data is outside the default root-level `openspec/` directory
|
||||
- **WHEN** an installed OpenSpec workflow directs an agent to read or write a planning artifact
|
||||
- **THEN** it SHALL obtain and use the authoritative OpenSpec directory reported by the CLI
|
||||
- **AND** it SHALL NOT infer the planning path from the workspace or process working directory
|
||||
@@ -0,0 +1,26 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Init separates workspace and planning destinations
|
||||
|
||||
`openspec init [path]` SHALL treat its path argument as the workspace root and, when `OPENSPEC_DIR` is set, create the complete OpenSpec structure at the selected directory while keeping workspace-scoped integrations at the workspace root.
|
||||
|
||||
#### Scenario: Initialize a custom relative directory
|
||||
|
||||
- **GIVEN** the user runs `openspec init .` with `OPENSPEC_DIR=ai/openspec`
|
||||
- **WHEN** initialization completes
|
||||
- **THEN** config, specs, changes, archive, and OpenSpec-owned instructions SHALL be created under `ai/openspec`
|
||||
- **AND** selected project-local AI tool files SHALL be created under the workspace root
|
||||
- **AND** no default root-level `openspec/` directory SHALL be created
|
||||
|
||||
#### Scenario: Custom directory already exists
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` selects an existing valid OpenSpec directory
|
||||
- **WHEN** the user runs `openspec init` to add or refresh tools
|
||||
- **THEN** init SHALL enter its existing extend behavior for that directory
|
||||
- **AND** it SHALL keep project-local tool discovery and generation anchored to the workspace root
|
||||
|
||||
#### Scenario: Invalid custom destination
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` resolves to a file or selects an unsafe filesystem root
|
||||
- **WHEN** the user runs `openspec init`
|
||||
- **THEN** init SHALL fail before creating planning or tool files
|
||||
@@ -0,0 +1,20 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Update separates workspace and planning destinations
|
||||
|
||||
`openspec update [path]` SHALL refresh OpenSpec-owned planning instructions in the environment-selected OpenSpec directory while detecting and refreshing project-local AI tool integrations at the workspace root.
|
||||
|
||||
#### Scenario: Update a relocated project
|
||||
|
||||
- **GIVEN** the workspace uses `OPENSPEC_DIR=ai/openspec`
|
||||
- **WHEN** the user runs `openspec update .`
|
||||
- **THEN** OpenSpec-owned files under `ai/openspec` SHALL be refreshed
|
||||
- **AND** existing project-local tool files under the workspace root SHALL be refreshed
|
||||
- **AND** update SHALL NOT create or refresh a default root-level `openspec/` directory
|
||||
|
||||
#### Scenario: Selected directory is missing
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` does not resolve to an existing valid OpenSpec directory
|
||||
- **WHEN** the user runs `openspec update`
|
||||
- **THEN** update SHALL fail with a diagnostic naming the selected directory
|
||||
- **AND** it SHALL NOT fall back to the default directory
|
||||
@@ -0,0 +1,19 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Load project config from the authoritative OpenSpec directory
|
||||
|
||||
Project configuration SHALL be loaded from `config.yaml` or `config.yml` inside the authoritative OpenSpec directory returned by root selection.
|
||||
|
||||
#### Scenario: Environment-selected config
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` selects `ai/openspec`
|
||||
- **AND** `ai/openspec/config.yaml` contains project context, rules, and a schema
|
||||
- **WHEN** a root-scoped command loads project configuration
|
||||
- **THEN** it SHALL use `ai/openspec/config.yaml`
|
||||
- **AND** it SHALL NOT read a config from the default root-level directory
|
||||
|
||||
#### Scenario: Store config remains conventional
|
||||
|
||||
- **GIVEN** a registered store is selected
|
||||
- **WHEN** a command loads project configuration
|
||||
- **THEN** it SHALL continue reading config from the store's authoritative `openspec` directory
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
## Purpose
|
||||
|
||||
Allow a project to relocate its complete OpenSpec directory without changing the workspace location of coding-agent integrations or adopting store machinery.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Environment selects the complete OpenSpec directory
|
||||
|
||||
OpenSpec SHALL accept `OPENSPEC_DIR` as a workspace-relative path to the complete directory for project config, specs, changes, and optional custom schemas, and SHALL use that directory consistently across every shipped command that reads or writes OpenSpec project data.
|
||||
|
||||
For discovery, a custom directory SHALL use the same qualification predicate as nearest-root selection: it qualifies when `specs/` or `changes/` is a planning directory that is not itself a store root, or when `config.yaml` or `config.yml` exists. A config-only directory therefore qualifies for selection, while config parsing and command-specific validation remain responsible for reporting malformed or incomplete content.
|
||||
|
||||
#### Scenario: Relative directory from workspace root
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is `ai/openspec`
|
||||
- **AND** `ai/openspec` qualifies under the established nearest-root predicate
|
||||
- **WHEN** the user runs a root-scoped command from the workspace root
|
||||
- **THEN** the command SHALL read and write planning data under `ai/openspec`
|
||||
|
||||
#### Scenario: Relative directory from a workspace subdirectory
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is `ai/openspec`
|
||||
- **AND** the user is inside a subdirectory of the same workspace
|
||||
- **AND** no closer ancestor has its own qualifying `ai/openspec` directory
|
||||
- **WHEN** the user runs a root-scoped command
|
||||
- **THEN** OpenSpec SHALL resolve the nearest ancestor whose `ai/openspec` qualifies under that predicate
|
||||
- **AND** the command SHALL use the same planning data as an invocation from the workspace root
|
||||
|
||||
#### Scenario: Nested workspace has its own custom directory
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is `ai/openspec`
|
||||
- **AND** both an outer workspace and a nested workspace contain a qualifying `ai/openspec` directory
|
||||
- **WHEN** the user runs a root-scoped command inside the nested workspace
|
||||
- **THEN** OpenSpec SHALL treat the nested workspace as a separate workspace
|
||||
- **AND** it SHALL select the nested workspace's `ai/openspec` directory
|
||||
|
||||
#### Scenario: Absolute directory is rejected
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is an absolute platform-native path to a valid OpenSpec directory
|
||||
- **WHEN** the user runs a root-scoped command
|
||||
- **THEN** the command SHALL fail with guidance to use a workspace-relative path
|
||||
- **AND** it SHALL explain that standalone planning repositories use stores
|
||||
|
||||
#### Scenario: Relative directory escapes the workspace
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` contains parent traversal that resolves outside the workspace candidate
|
||||
- **WHEN** the user runs an OpenSpec command
|
||||
- **THEN** the command SHALL fail before reading or writing project data
|
||||
|
||||
#### Scenario: Symlink escapes the workspace
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is a relative path whose existing candidate is a symlink
|
||||
- **AND** the candidate's canonical path is outside the workspace ancestor that resolved it
|
||||
- **WHEN** the user runs an OpenSpec command
|
||||
- **THEN** the command SHALL fail before reading or writing project data
|
||||
|
||||
#### Scenario: Environment is unset or empty
|
||||
|
||||
- **WHEN** `OPENSPEC_DIR` is unset, empty, or whitespace-only
|
||||
- **THEN** existing project and store root selection SHALL behave as before
|
||||
|
||||
#### Scenario: Config-only directory qualifies
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` resolves to a directory containing `config.yaml` but no `specs/` or `changes/` directory
|
||||
- **WHEN** OpenSpec performs root selection
|
||||
- **THEN** it SHALL select that custom directory
|
||||
- **AND** any config-content error SHALL be reported by the existing config validation path
|
||||
|
||||
### Requirement: Custom-directory selection is explicit and fail-closed
|
||||
|
||||
OpenSpec SHALL validate an environment-selected directory before a command mutates project data and SHALL NOT silently fall back to another project or store root when explicit custom-directory selection is invalid or conflicts with explicit store selection.
|
||||
|
||||
#### Scenario: Directory is missing
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` names a directory that cannot be resolved
|
||||
- **WHEN** the user runs a command other than `init`
|
||||
- **THEN** the command SHALL fail with an actionable custom-directory diagnostic
|
||||
- **AND** it SHALL NOT read from or write to the default `openspec/` directory
|
||||
|
||||
#### Scenario: Path is not a directory
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` resolves to a file or another non-directory object
|
||||
- **WHEN** the user runs an OpenSpec command
|
||||
- **THEN** the command SHALL fail before reading or writing project data
|
||||
|
||||
#### Scenario: Explicit store conflicts with environment selection
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` is set to a non-empty value
|
||||
- **WHEN** the user also passes `--store <id>`
|
||||
- **THEN** the command SHALL fail with guidance to choose one root source
|
||||
- **AND** it SHALL NOT mutate either location
|
||||
|
||||
### Requirement: Root provenance exposes the authoritative directory
|
||||
|
||||
OpenSpec SHALL make the authoritative OpenSpec directory available to humans and agents so neither has to reconstruct it from the workspace or store root.
|
||||
|
||||
#### Scenario: Human command uses custom directory
|
||||
|
||||
- **WHEN** a human-mode command resolves `OPENSPEC_DIR`
|
||||
- **THEN** stderr SHALL identify the selected custom OpenSpec directory before command-specific output
|
||||
|
||||
#### Scenario: JSON command reports custom directory
|
||||
|
||||
- **WHEN** a JSON command resolves `OPENSPEC_DIR`
|
||||
- **THEN** its root object SHALL report `source: "environment"`
|
||||
- **AND** it SHALL report the canonical absolute directory in `openspec_dir`
|
||||
|
||||
#### Scenario: Default project or store reports directory
|
||||
|
||||
- **WHEN** a JSON command resolves a default project root or registered store
|
||||
- **THEN** its root object SHALL report the canonical absolute OpenSpec directory in `openspec_dir`
|
||||
- **AND** existing root fields SHALL retain their meanings
|
||||
|
||||
### Requirement: Custom directories work across supported platforms
|
||||
|
||||
OpenSpec SHALL resolve custom-directory identity using platform-native path semantics and canonical existing-path identity on macOS, Linux, and Windows.
|
||||
|
||||
#### Scenario: Path contains spaces
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` resolves to a valid directory whose path contains spaces
|
||||
- **WHEN** the user completes a normal change lifecycle
|
||||
- **THEN** creation, status, instructions, validation, and archive SHALL all use that directory
|
||||
|
||||
#### Scenario: Windows path and alias
|
||||
|
||||
- **GIVEN** a Windows path uses supported native separators or an existing filesystem alias
|
||||
- **WHEN** OpenSpec resolves the custom directory
|
||||
- **THEN** all commands SHALL agree on one canonical directory identity
|
||||
- **AND** no command SHALL construct a mixed-separator filesystem path
|
||||
|
||||
### Requirement: Store behavior remains isolated
|
||||
|
||||
Custom project-directory selection SHALL reuse the canonical root contract without changing registered-store layout, identity, health, or registry behavior.
|
||||
|
||||
#### Scenario: Registered store is selected
|
||||
|
||||
- **WHEN** the user selects a registered store with `--store <id>` and `OPENSPEC_DIR` is unset or empty
|
||||
- **THEN** the store SHALL continue using `<store root>/openspec`
|
||||
- **AND** store identity and health validation SHALL remain unchanged
|
||||
|
||||
#### Scenario: Custom project needs no store metadata
|
||||
|
||||
- **WHEN** a project selects an in-workspace directory with `OPENSPEC_DIR`
|
||||
- **THEN** OpenSpec SHALL NOT require store registration or store identity metadata
|
||||
@@ -0,0 +1,17 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Project-local schemas follow the authoritative OpenSpec directory
|
||||
|
||||
Schema discovery and loading SHALL resolve project-local schemas beneath the authoritative OpenSpec directory rather than reconstructing an `openspec/schemas` path from the process working directory or workspace root.
|
||||
|
||||
#### Scenario: Environment-selected project schema
|
||||
|
||||
- **GIVEN** `OPENSPEC_DIR` selects `ai/openspec`
|
||||
- **AND** a project-local schema exists under `ai/openspec/schemas/<name>`
|
||||
- **WHEN** the user lists schemas or creates a change using that schema
|
||||
- **THEN** schema discovery and schema loading SHALL both use the selected project-local schema
|
||||
|
||||
#### Scenario: Default and store schemas retain precedence
|
||||
|
||||
- **WHEN** `OPENSPEC_DIR` is unset or empty
|
||||
- **THEN** project-local, user override, and package schema precedence SHALL remain unchanged for default projects and stores
|
||||
@@ -0,0 +1,33 @@
|
||||
## 1. Establish the directory-aware root contract
|
||||
|
||||
- [ ] 1.1 Add focused root-selection tests for default projects, stores, unset/empty compatibility, ancestor-resolved and nested-workspace relative `OPENSPEC_DIR` values, rejected absolute, lexical-escape, and symlink-escape values, other invalid values, `--store` conflicts, spaces, and canonical in-workspace alias identity; verify the new cases fail for the missing directory contract.
|
||||
- [ ] 1.2 Extend the canonical resolved-root and planning-home types with authoritative OpenSpec-directory fields, derive all planning subdirectories once, and verify existing default/store tests remain green.
|
||||
- [ ] 1.3 Implement environment selection, typed diagnostics, fail-closed precedence, human provenance, and additive JSON root output; verify focused human and JSON tests pass without changing existing field meanings.
|
||||
- [ ] 1.4 Update `docs/agent-contract.md` and root-selection CLI documentation in the same change, then verify their documented payloads and precedence against the focused tests.
|
||||
|
||||
## 2. Move runtime consumers onto the authoritative directory
|
||||
|
||||
- [ ] 2.1 Inventory every runtime construction of config, schemas, specs, changes, and archive paths; migrate normal and deprecated-but-shipped commands to the resolved directories and verify command-parity tests cover list, show, validate, status, instructions, new change, archive, doctor, context, schemas, templates, config, view, and noun-form compatibility paths.
|
||||
- [ ] 2.2 Add directory-aware project-config and schema-resolution APIs with compatibility wrappers for existing callers; verify config loading, rules/context injection, schema listing, schema loading, and custom-schema change creation against a custom directory.
|
||||
- [ ] 2.3 Update reference, health, discovery, archive, and spec-application consumers to accept authoritative directories; verify a complete create-to-archive lifecycle in a path containing spaces.
|
||||
- [ ] 2.4 Add a static regression guard for forbidden command-local `<root>/openspec` reconstruction and verify it permits only constants, default-layout descriptions, and store bootstrap code explicitly reviewed by name.
|
||||
|
||||
## 3. Separate init/update workspace and planning paths
|
||||
|
||||
- [ ] 3.1 Add failing init tests proving `OPENSPEC_DIR` creates the complete planning structure at the selected destination while project-local tool files, detection, cleanup, and managed root stubs stay at the explicit workspace path.
|
||||
- [ ] 3.2 Implement separate workspace and OpenSpec-directory inputs through init, including extend mode, pointer guards, config creation, anchors, Copilot cloud state, and rollback; verify focused init and cleanup suites pass.
|
||||
- [ ] 3.3 Add failing update tests for relocated planning instructions, workspace-local tool refresh, missing/invalid custom directories, and absence of default-directory writes.
|
||||
- [ ] 3.4 Implement the same separation through update and verify focused update, migration, shared-skill, and Copilot cloud suites pass.
|
||||
- [ ] 3.5 Update `cli-init`, `cli-update`, customization, and troubleshooting documentation with cross-platform examples and consistent-environment guidance; run every documented command in temporary fixtures.
|
||||
|
||||
## 4. Make generated workflows directory-aware
|
||||
|
||||
- [ ] 4.1 Add generated-content tests proving workflow instructions consume `root.openspec_dir` or `planningHome.openspecDir` for config, specs, changes, schemas, and archive operations under a custom directory.
|
||||
- [ ] 4.2 Replace operational hardcoded `openspec/` paths in shared workflow templates and GitHub Copilot agent content with authoritative-directory guidance while preserving default-layout examples; regenerate committed skills and parity hashes and verify generated-content equivalence.
|
||||
- [ ] 4.3 Extend cold-start and capstone agent journeys with a relocated directory and verify the agent creates, reads, validates, syncs, and archives only in the selected location.
|
||||
|
||||
## 5. Complete compatibility and release verification
|
||||
|
||||
- [ ] 5.1 Add a minor changeset and a migration note; verify unset-environment human output, filesystem effects, and existing JSON keys remain compatible for default projects and stores.
|
||||
- [ ] 5.2 Run `pnpm run build`, `pnpm exec tsc --noEmit`, `pnpm run lint`, `pnpm test`, strict OpenSpec validation, generated-content parity checks, and `git diff --check`.
|
||||
- [ ] 5.3 Verify Windows CI covers native separators, rejected drive-qualified absolute paths, spaces, and alias canonicalization before marking the implementation ready to merge.
|
||||
Reference in New Issue
Block a user