From e0b1ec5c9cde0e40f47f59fd90f2b191fcf7adf7 Mon Sep 17 00:00:00 2001 From: victorhsb Date: Thu, 12 Mar 2026 21:33:50 -0300 Subject: [PATCH 1/2] feat: Add hierarchical specs change proposal --- .../changes/hierarchical-specs/.openspec.yaml | 2 + openspec/changes/hierarchical-specs/design.md | 74 +++++++++++++++ .../changes/hierarchical-specs/proposal.md | 36 ++++++++ .../specs/cli-archive/spec.md | 55 +++++++++++ .../hierarchical-specs/specs/cli-list/spec.md | 51 +++++++++++ .../hierarchical-specs/specs/cli-show/spec.md | 50 ++++++++++ .../hierarchical-specs/specs/cli-spec/spec.md | 91 +++++++++++++++++++ .../specs/cli-validate/spec.md | 86 ++++++++++++++++++ .../hierarchical-specs/specs/cli-view/spec.md | 35 +++++++ .../specs/hierarchical-spec-discovery/spec.md | 56 ++++++++++++ .../hierarchical-spec-resolution/spec.md | 75 +++++++++++++++ openspec/changes/hierarchical-specs/tasks.md | 44 +++++++++ 12 files changed, 655 insertions(+) create mode 100644 openspec/changes/hierarchical-specs/.openspec.yaml create mode 100644 openspec/changes/hierarchical-specs/design.md create mode 100644 openspec/changes/hierarchical-specs/proposal.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-archive/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-list/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-show/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-spec/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-validate/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/cli-view/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/hierarchical-spec-discovery/spec.md create mode 100644 openspec/changes/hierarchical-specs/specs/hierarchical-spec-resolution/spec.md create mode 100644 openspec/changes/hierarchical-specs/tasks.md diff --git a/openspec/changes/hierarchical-specs/.openspec.yaml b/openspec/changes/hierarchical-specs/.openspec.yaml new file mode 100644 index 000000000..219e2a042 --- /dev/null +++ b/openspec/changes/hierarchical-specs/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-13 diff --git a/openspec/changes/hierarchical-specs/design.md b/openspec/changes/hierarchical-specs/design.md new file mode 100644 index 000000000..13388168d --- /dev/null +++ b/openspec/changes/hierarchical-specs/design.md @@ -0,0 +1,74 @@ +## Context + +OpenSpec currently stores specs in a flat directory structure at `openspec/specs//spec.md`. Spec IDs are simple strings (e.g., `cli-show`, `schema-resolution`). The `getSpecIds()` function in `item-discovery.ts` reads only one level deep, and all path construction uses `path.join(SPECS_DIR, specId, 'spec.md')`. + +As the project has grown to 38 specs, naming conventions like `cli-*`, `schema-*` have emerged organically — encoding hierarchy in flat names. This change introduces proper directory nesting so specs can be organized as `cli/show`, `schema/resolution`, etc. + +## Goals / Non-Goals + +**Goals:** +- Support arbitrary nesting depth for specs (e.g., `domain/project/feature`) +- Make spec IDs path-based, using `/` as the separator +- Maintain full backward compatibility with existing flat spec IDs +- Update all CLI commands to work with hierarchical spec IDs +- Support subtree operations (e.g., listing all specs under `cli/`) + +**Non-Goals:** +- Automatic migration of existing flat specs to hierarchical structure — users migrate at their own pace +- Cross-references or symbolic links between specs +- Spec inheritance or composition across hierarchy levels +- Namespace-level metadata or configuration (e.g., `cli/.openspec.yaml`) + +## Decisions + +### Decision 1: Spec IDs use forward slash as separator, regardless of OS + +Spec IDs use `/` as the canonical separator (e.g., `cli/show`), even on Windows. Internally, `path.join` is used for filesystem operations, but IDs are always stored and displayed with `/`. + +**Why**: Consistent cross-platform behavior. Spec IDs appear in change files, JSON output, and documentation — they must be portable. This matches how Go import paths and npm package scopes work. + +**Alternative considered**: Using the OS path separator — rejected because spec IDs would differ across platforms, breaking change portability. + +### Decision 2: Recursive discovery with `spec.md` as the leaf marker + +`getSpecIds()` walks the directory tree recursively. Any directory containing `spec.md` is a spec — its ID is the relative path from `openspec/specs/` to that directory. Directories without `spec.md` are treated as organizational containers. + +**Why**: Simple, unambiguous detection. No configuration files needed at intermediate directories. The existing convention (`spec.md` = spec exists) naturally extends to nested structures. + +**Alternative considered**: Requiring a manifest file listing specs — rejected as it adds maintenance burden and a sync problem. + +### Decision 3: Flat and hierarchical specs coexist + +Both `openspec/specs/cli-show/spec.md` (flat) and `openspec/specs/cli/show/spec.md` (hierarchical) are valid. They are different specs with different IDs (`cli-show` vs `cli/show`). + +**Why**: Zero-migration-cost adoption. Teams can gradually reorganize without a flag day. Existing tooling and change references continue to work. + +**Alternative considered**: Deprecating flat structure — rejected as it forces migration and breaks existing changes/archives. + +### Decision 4: Subtree filtering uses prefix matching on spec IDs + +`openspec spec list cli/` returns all specs whose ID starts with `cli/`. This is a simple string prefix match on the canonical `/`-separated ID. + +**Why**: Intuitive UX — `cli/` means "everything under cli". No glob syntax needed for the common case. + +### Decision 5: Delta specs in changes mirror the hierarchy + +Change delta specs at `changes//specs/` use the same hierarchy. For example, modifying spec `cli/show` means creating `changes//specs/cli/show/spec.md`. + +**Why**: Consistent mental model. The change's `specs/` directory is a mirror of the main `openspec/specs/` structure. The archive command can use the same relative path logic for both. + +### Decision 6: Fuzzy matching extends to hierarchical IDs + +The existing Levenshtein-based suggestion system works on the full path-based ID string. Additionally, partial path matching is supported — typing `show` suggests `cli/show` if it exists. + +**Why**: Discoverability. Users may not know the full path. Matching on the leaf segment (last component) helps find specs without knowing the full hierarchy. + +## Risks / Trade-offs + +**[Risk] Ambiguity between flat and nested IDs** → Flat ID `cli-show` and nested ID `cli/show` are distinct specs. If both exist, commands resolve them independently. Documentation should clarify naming conventions to avoid confusion. + +**[Risk] Deep nesting becomes unwieldy** → No technical limit on depth, but deeply nested IDs (`a/b/c/d/e/spec.md`) are cumbersome to type. Mitigation: document recommended max depth of 3 levels; fuzzy matching reduces typing burden. + +**[Risk] Performance with large spec trees** → Recursive directory walking is slower than single-level readdir. Mitigation: `fast-glob` (already a dependency) can handle thousands of entries efficiently. Benchmark if >500 specs. + +**[Risk] Cross-platform path handling bugs** → Mixing `/` in IDs with OS-specific `path.join` is error-prone. Mitigation: centralize ID↔path conversion in a single utility (`specIdToPath` / `pathToSpecId`), and add Windows path tests as per existing config rules. diff --git a/openspec/changes/hierarchical-specs/proposal.md b/openspec/changes/hierarchical-specs/proposal.md new file mode 100644 index 000000000..8cf1cf012 --- /dev/null +++ b/openspec/changes/hierarchical-specs/proposal.md @@ -0,0 +1,36 @@ +## Why + +OpenSpec stores all specs in a flat structure under `openspec/specs/`. As projects grow, this becomes difficult to organize and navigate. Teams working on different domains or features resort to long, prefixed names (e.g., `cli-show`, `cli-validate`, `schema-fork-command`) to avoid collisions and convey hierarchy through naming conventions alone. A proper hierarchical structure would make specs more discoverable, reduce naming collisions, and let teams organize specs by domain, project, or feature. + +## What Changes + +- Spec IDs become path-based (e.g., `cli/show` instead of `cli-show`), supporting arbitrary nesting depth +- `getSpecIds()` in `item-discovery.ts` becomes recursive, discovering specs at any depth +- All CLI commands that resolve spec IDs (`spec show`, `spec list`, `spec validate`, `show`, `validate`, `list`, `view`) accept path-based spec IDs +- Path construction throughout the codebase changes from `join(SPECS_DIR, specId, 'spec.md')` to handle `/`-separated IDs as nested directories +- `spec list` gains the ability to filter by subtree (e.g., `openspec spec list cli/` shows only CLI specs) +- Delta specs in changes mirror the hierarchical structure +- **BREAKING**: Existing flat spec IDs remain valid — no migration required. However, tools that parse spec IDs as simple strings may need updating. + +## Capabilities + +### New Capabilities +- `hierarchical-spec-discovery`: Recursive spec discovery that finds `spec.md` files at any nesting depth under `openspec/specs/`, deriving spec IDs from relative directory paths +- `hierarchical-spec-resolution`: Path-based spec ID resolution, allowing specs to be referenced as `domain/project/feature` with subtree filtering and disambiguation support + +### Modified Capabilities +- `cli-spec`: Spec commands (`show`, `list`, `validate`) must accept path-based spec IDs and support subtree listing +- `cli-show`: Show command must resolve hierarchical spec IDs +- `cli-validate`: Validate command must resolve hierarchical spec IDs +- `cli-list`: List command must display specs with their full hierarchical paths and support subtree filtering +- `cli-view`: View/dashboard must display specs organized by hierarchy +- `cli-archive`: Archive must handle delta specs in nested directory structures + +## Impact + +- **Core**: `item-discovery.ts` (`getSpecIds`), path construction in `spec.ts`, `show.ts`, `validate.ts` +- **Display**: `list.ts` and `view.ts` need tree-aware rendering +- **Archive**: `specs-apply.ts` and `archive.ts` need to handle nested delta spec paths +- **Change specs**: Delta specs within changes (`changes//specs/`) mirror the hierarchy +- **Tests**: All spec-related tests need updating for nested path scenarios +- **Cross-platform**: Path handling must use `path.join`/`path.posix` correctly — spec IDs use `/` as separator regardless of OS diff --git a/openspec/changes/hierarchical-specs/specs/cli-archive/spec.md b/openspec/changes/hierarchical-specs/specs/cli-archive/spec.md new file mode 100644 index 000000000..2e9a55463 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-archive/spec.md @@ -0,0 +1,55 @@ +## MODIFIED Requirements + +### Requirement: Spec Update Process + +Before moving the change to archive, the command SHALL apply delta changes to main specs to reflect the deployed reality. + +#### Scenario: Applying delta changes + +- **WHEN** archiving a change with delta-based specs +- **THEN** recursively discover delta specs within the change's `specs/` directory +- **AND** parse and apply delta changes as defined in openspec-conventions +- **AND** validate all operations before applying +- **AND** create nested directories under `openspec/specs/` as needed for new hierarchical specs + +#### Scenario: Applying hierarchical delta specs + +- **WHEN** a change contains delta spec at `changes//specs/cli/show/spec.md` +- **THEN** apply the delta to `openspec/specs/cli/show/spec.md` +- **AND** create intermediate directories (`cli/`) if they do not exist + +#### Scenario: Validating delta changes + +- **WHEN** processing delta changes +- **THEN** perform validations as specified in openspec-conventions +- **AND** if validation fails, show specific errors and abort + +#### Scenario: Conflict detection + +- **WHEN** applying deltas would create duplicate requirement headers +- **THEN** abort with error message showing the conflict +- **AND** suggest manual resolution + +### Requirement: Confirmation Behavior + +The spec update confirmation SHALL provide clear visibility into changes before they are applied. + +#### Scenario: Displaying confirmation with hierarchical paths + +- **WHEN** prompting for confirmation +- **THEN** display a clear summary showing: + - Which specs will be created (new capabilities) with full hierarchical paths + - Which specs will be updated (existing capabilities) with full hierarchical paths + - The source path for each spec +- **AND** format the confirmation prompt as: + ``` + The following specs will be updated: + + NEW specs to be created: + - cli/archive (from changes/add-archive-command/specs/cli/archive/spec.md) + + EXISTING specs to be updated: + - cli/init (from changes/update-init-command/specs/cli/init/spec.md) + + Update 2 specs and archive 'add-archive-command'? [y/N]: + ``` diff --git a/openspec/changes/hierarchical-specs/specs/cli-list/spec.md b/openspec/changes/hierarchical-specs/specs/cli-list/spec.md new file mode 100644 index 000000000..86b1af575 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-list/spec.md @@ -0,0 +1,51 @@ +## MODIFIED Requirements + +### Requirement: Command Execution +The command SHALL scan and analyze either active changes or specs based on the selected mode. + +#### Scenario: Scanning for changes (default) +- **WHEN** `openspec list` is executed without flags +- **THEN** scan the `openspec/changes/` directory for change directories +- **AND** exclude the `archive/` subdirectory from results +- **AND** parse each change's `tasks.md` file to count task completion + +#### Scenario: Scanning for specs +- **WHEN** `openspec list --specs` is executed +- **THEN** recursively scan the `openspec/specs/` directory tree for capabilities at any depth +- **AND** read each capability's `spec.md` +- **AND** parse requirements to compute requirement counts + +#### Scenario: Scanning for specs in subtree +- **WHEN** `openspec list --specs cli/` is executed +- **THEN** recursively scan only specs whose ID starts with `cli/` +- **AND** display them with their full hierarchical IDs + +### Requirement: Output Format +The command SHALL display items in a clear, readable table format with mode-appropriate progress or counts. + +#### Scenario: Displaying change list (default) +- **WHEN** displaying the list of changes +- **THEN** show a table with columns: + - Change name (directory name) + - Task progress (e.g., "3/5 tasks" or "✓ Complete") + +#### Scenario: Displaying spec list +- **WHEN** displaying the list of specs +- **THEN** show a table with columns: + - Spec id (full hierarchical path, e.g., `cli/show` or `cli-show`) + - Requirement count (e.g., "requirements 12") + +### Requirement: Sorting + +The command SHALL maintain consistent ordering of items for predictable output. + +#### Scenario: Ordering changes + +- **WHEN** displaying multiple changes +- **THEN** sort them in alphabetical order by change name + +#### Scenario: Ordering specs + +- **WHEN** displaying multiple specs +- **THEN** sort them in alphabetical order by full spec ID +- **AND** hierarchical IDs sort naturally (e.g., `cli/archive` before `cli/show`) diff --git a/openspec/changes/hierarchical-specs/specs/cli-show/spec.md b/openspec/changes/hierarchical-specs/specs/cli-show/spec.md new file mode 100644 index 000000000..03235562c --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-show/spec.md @@ -0,0 +1,50 @@ +## MODIFIED Requirements + +### Requirement: Top-level show command + +The CLI SHALL provide a top-level `show` command for displaying changes and specs with intelligent selection. + +#### Scenario: Interactive show selection + +- **WHEN** executing `openspec show` without arguments +- **THEN** prompt user to select type (change or spec) +- **AND** display list of available items for selected type, including hierarchical spec IDs +- **AND** show the selected item's content + +#### Scenario: Non-interactive environments do not prompt + +- **GIVEN** stdin is not a TTY or `--no-interactive` is provided or environment variable `OPEN_SPEC_INTERACTIVE=0` +- **WHEN** executing `openspec show` without arguments +- **THEN** do not prompt +- **AND** print a helpful hint with examples for `openspec show ` or `openspec change/spec show` +- **AND** exit with code 1 + +#### Scenario: Direct item display + +- **WHEN** executing `openspec show ` +- **THEN** automatically detect if item is a change or spec +- **AND** display the item's content +- **AND** use appropriate formatting based on item type + +#### Scenario: Direct hierarchical spec display + +- **WHEN** executing `openspec show cli/show` +- **THEN** detect that `cli/show` is a hierarchical spec ID +- **AND** resolve it at `openspec/specs/cli/show/spec.md` +- **AND** display the spec content + +#### Scenario: Type detection and ambiguity handling + +- **WHEN** executing `openspec show ` +- **THEN** if `` uniquely matches a change or a spec, show that item +- **AND** if it matches both, print an ambiguity error and suggest `--type change|spec` or using `openspec change show`/`openspec spec show` +- **AND** if it matches neither, print not-found with nearest-match suggestions including hierarchical specs + +#### Scenario: Explicit type override + +- **WHEN** executing `openspec show --type change ` +- **THEN** treat `` as a change ID and show it (skipping auto-detection) + +- **WHEN** executing `openspec show --type spec ` +- **THEN** treat `` as a spec ID and show it (skipping auto-detection) +- **AND** support hierarchical spec IDs (e.g., `openspec show --type spec cli/show`) diff --git a/openspec/changes/hierarchical-specs/specs/cli-spec/spec.md b/openspec/changes/hierarchical-specs/specs/cli-spec/spec.md new file mode 100644 index 000000000..d59ccf46c --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-spec/spec.md @@ -0,0 +1,91 @@ +## MODIFIED Requirements + +### Requirement: Spec Command + +The system SHALL provide a `spec` command with subcommands for displaying, listing, and validating specifications. + +#### Scenario: Show spec as JSON + +- **WHEN** executing `openspec spec show init --json` +- **THEN** parse the markdown spec file +- **AND** extract headings and content hierarchically +- **AND** output valid JSON to stdout + +#### Scenario: List all specs + +- **WHEN** executing `openspec spec list` +- **THEN** recursively scan the openspec/specs directory tree +- **AND** return list of all available capabilities with their full hierarchical IDs +- **AND** support JSON output with `--json` flag + +#### Scenario: List specs in subtree + +- **WHEN** executing `openspec spec list cli/` +- **THEN** return only specs whose ID starts with `cli/` +- **AND** display them with their full hierarchical IDs + +#### Scenario: Show hierarchical spec + +- **WHEN** executing `openspec spec show cli/show` +- **THEN** resolve the spec at `openspec/specs/cli/show/spec.md` +- **AND** display the spec content + +#### Scenario: Filter spec content + +- **WHEN** executing `openspec spec show init --requirements` +- **THEN** display only requirement names and SHALL statements +- **AND** exclude scenario content + +#### Scenario: Validate spec structure + +- **WHEN** executing `openspec spec validate init` +- **THEN** parse the spec file +- **AND** validate against Zod schema +- **AND** report any structural issues + +#### Scenario: Validate hierarchical spec + +- **WHEN** executing `openspec spec validate cli/show` +- **THEN** resolve the spec at `openspec/specs/cli/show/spec.md` +- **AND** validate against Zod schema +- **AND** report any structural issues + +### Requirement: Interactive spec show + +The spec show command SHALL support interactive selection when no spec-id is provided. + +#### Scenario: Interactive spec selection for show + +- **WHEN** executing `openspec spec show` without arguments +- **THEN** display an interactive list of available specs, including hierarchical IDs +- **AND** allow the user to select a spec to show +- **AND** display the selected spec content +- **AND** maintain all existing show options (--json, --requirements, --no-scenarios, -r) + +#### Scenario: Non-interactive fallback keeps current behavior + +- **GIVEN** stdin is not a TTY or `--no-interactive` is provided or environment variable `OPEN_SPEC_INTERACTIVE=0` +- **WHEN** executing `openspec spec show` without a spec-id +- **THEN** do not prompt interactively +- **AND** print the existing error message for missing spec-id +- **AND** set non-zero exit code + +### Requirement: Interactive spec validation + +The spec validate command SHALL support interactive selection when no spec-id is provided. + +#### Scenario: Interactive spec selection for validation + +- **WHEN** executing `openspec spec validate` without arguments +- **THEN** display an interactive list of available specs, including hierarchical IDs +- **AND** allow the user to select a spec to validate +- **AND** validate the selected spec +- **AND** maintain all existing validation options (--strict, --json) + +#### Scenario: Non-interactive fallback keeps current behavior + +- **GIVEN** stdin is not a TTY or `--no-interactive` is provided or environment variable `OPEN_SPEC_INTERACTIVE=0` +- **WHEN** executing `openspec spec validate` without a spec-id +- **THEN** do not prompt interactively +- **AND** print the existing error message for missing spec-id +- **AND** set non-zero exit code diff --git a/openspec/changes/hierarchical-specs/specs/cli-validate/spec.md b/openspec/changes/hierarchical-specs/specs/cli-validate/spec.md new file mode 100644 index 000000000..40eceea78 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-validate/spec.md @@ -0,0 +1,86 @@ +## MODIFIED Requirements + +### Requirement: Top-level validate command + +The CLI SHALL provide a top-level `validate` command for validating changes and specs with flexible selection options. + +#### Scenario: Interactive validation selection + +- **WHEN** executing `openspec validate` without arguments +- **THEN** prompt user to select what to validate (all, changes, specs, or specific item) +- **AND** perform validation based on selection +- **AND** display results with appropriate formatting + +#### Scenario: Non-interactive environments do not prompt + +- **GIVEN** stdin is not a TTY or `--no-interactive` is provided or environment variable `OPEN_SPEC_INTERACTIVE=0` +- **WHEN** executing `openspec validate` without arguments +- **THEN** do not prompt interactively +- **AND** print a helpful hint listing available commands/flags and exit with code 1 + +#### Scenario: Direct item validation + +- **WHEN** executing `openspec validate ` +- **THEN** automatically detect if item is a change or spec +- **AND** validate the specified item +- **AND** display validation results + +#### Scenario: Direct hierarchical spec validation + +- **WHEN** executing `openspec validate cli/show` +- **THEN** detect that `cli/show` is a hierarchical spec ID +- **AND** resolve and validate the spec at `openspec/specs/cli/show/spec.md` + +### Requirement: Bulk and filtered validation + +The validate command SHALL support flags for bulk validation (--all) and filtered validation by type (--changes, --specs). + +#### Scenario: Validate everything + +- **WHEN** executing `openspec validate --all` +- **THEN** validate all changes in openspec/changes/ (excluding archive) +- **AND** recursively validate all specs in openspec/specs/ (including hierarchical) +- **AND** display a summary showing passed/failed items +- **AND** exit with code 1 if any validation fails + +#### Scenario: Scope of bulk validation + +- **WHEN** validating with `--all` or `--changes` +- **THEN** include all change proposals under `openspec/changes/` +- **AND** exclude the `openspec/changes/archive/` directory + +- **WHEN** validating with `--specs` +- **THEN** recursively discover and include all specs that have a `spec.md` at any depth under `openspec/specs/` + +### Requirement: Item type detection and ambiguity handling + +The validate command SHALL handle ambiguous names and explicit type overrides to ensure clear, deterministic behavior. + +#### Scenario: Direct item validation with automatic type detection + +- **WHEN** executing `openspec validate ` +- **THEN** if `` uniquely matches a change or a spec (including hierarchical IDs), validate that item + +#### Scenario: Ambiguity between change and spec names + +- **GIVEN** `` exists both as a change and as a spec +- **WHEN** executing `openspec validate ` +- **THEN** print an ambiguity error explaining both matches +- **AND** suggest passing `--type change` or `--type spec`, or using `openspec change validate` / `openspec spec validate` +- **AND** exit with code 1 without performing validation + +#### Scenario: Unknown item name + +- **WHEN** the `` matches neither a change nor a spec +- **THEN** print a not-found error +- **AND** show nearest-match suggestions including hierarchical spec IDs +- **AND** exit with code 1 + +#### Scenario: Explicit type override + +- **WHEN** executing `openspec validate --type change ` +- **THEN** treat `` as a change ID and validate it (skipping auto-detection) + +- **WHEN** executing `openspec validate --type spec ` +- **THEN** treat `` as a spec ID and validate it (skipping auto-detection) +- **AND** support hierarchical spec IDs (e.g., `openspec validate --type spec cli/show`) diff --git a/openspec/changes/hierarchical-specs/specs/cli-view/spec.md b/openspec/changes/hierarchical-specs/specs/cli-view/spec.md new file mode 100644 index 000000000..a1f695729 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/cli-view/spec.md @@ -0,0 +1,35 @@ +## MODIFIED Requirements + +### Requirement: Specifications Display + +The dashboard SHALL display specifications sorted by requirement count, supporting hierarchical organization. + +#### Scenario: Specs listing with counts + +- **WHEN** specifications exist in the project +- **THEN** system shows specs sorted by requirement count (descending) with count labels +- **AND** displays full hierarchical spec IDs (e.g., `cli/show` instead of just `show`) + +#### Scenario: Specs with parsing errors + +- **WHEN** a spec file cannot be parsed +- **THEN** system includes it with 0 requirement count + +#### Scenario: Hierarchical specs in dashboard + +- **WHEN** both flat and hierarchical specs exist +- **THEN** system displays all specs with their full IDs +- **AND** uses consistent formatting regardless of nesting depth + +### Requirement: Summary Section + +The dashboard SHALL display a summary section with key project metrics, including draft change count. + +#### Scenario: Complete summary display + +- **WHEN** dashboard is rendered with specs and changes +- **THEN** system shows total number of specifications (including all hierarchical specs) and requirements +- **AND** shows number of draft changes +- **AND** shows number of active changes in progress +- **AND** shows number of completed changes +- **AND** shows overall task progress percentage diff --git a/openspec/changes/hierarchical-specs/specs/hierarchical-spec-discovery/spec.md b/openspec/changes/hierarchical-specs/specs/hierarchical-spec-discovery/spec.md new file mode 100644 index 000000000..311135b07 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/hierarchical-spec-discovery/spec.md @@ -0,0 +1,56 @@ +## ADDED Requirements + +### Requirement: Recursive spec discovery +The system SHALL recursively walk the `openspec/specs/` directory tree to discover all specs at any nesting depth. A directory is a spec if and only if it contains a `spec.md` file. The spec ID SHALL be the `/`-separated relative path from `openspec/specs/` to the directory containing `spec.md`. + +#### Scenario: Discover flat specs +- **WHEN** `openspec/specs/cli-show/spec.md` exists +- **THEN** the system discovers spec ID `cli-show` + +#### Scenario: Discover nested specs +- **WHEN** `openspec/specs/cli/show/spec.md` exists +- **THEN** the system discovers spec ID `cli/show` + +#### Scenario: Discover deeply nested specs +- **WHEN** `openspec/specs/domain/project/feature/spec.md` exists +- **THEN** the system discovers spec ID `domain/project/feature` + +#### Scenario: Ignore intermediate directories without spec.md +- **WHEN** `openspec/specs/cli/` exists as a directory without `spec.md` +- **AND** `openspec/specs/cli/show/spec.md` exists +- **THEN** `cli` is NOT discovered as a spec +- **AND** `cli/show` IS discovered as a spec + +#### Scenario: Skip hidden directories +- **WHEN** a directory under `openspec/specs/` starts with `.` +- **THEN** the system SHALL skip it and its children during discovery + +#### Scenario: Mixed flat and nested coexist +- **WHEN** both `openspec/specs/cli-show/spec.md` and `openspec/specs/cli/show/spec.md` exist +- **THEN** both `cli-show` and `cli/show` are discovered as separate specs + +### Requirement: Cross-platform spec ID normalization +Spec IDs SHALL always use `/` as the path separator, regardless of the operating system. The system SHALL normalize OS-specific path separators to `/` when constructing spec IDs from filesystem paths. + +#### Scenario: Windows path normalization +- **WHEN** running on Windows where filesystem paths use `\` +- **AND** `openspec\specs\cli\show\spec.md` exists +- **THEN** the spec ID SHALL be `cli/show` (not `cli\show`) + +#### Scenario: Spec ID to filesystem path conversion +- **WHEN** resolving spec ID `cli/show` to a filesystem path +- **THEN** the system SHALL use the OS-appropriate path separator for filesystem operations +- **AND** construct the path as `path.join(specsDir, 'cli', 'show', 'spec.md')` + +### Requirement: Discovery performance +The system SHALL discover specs efficiently, avoiding unnecessary filesystem operations. + +#### Scenario: Use fast-glob for recursive discovery +- **WHEN** discovering specs +- **THEN** the system SHALL use glob pattern `**/spec.md` under `openspec/specs/` +- **AND** exclude hidden directories from traversal + +#### Scenario: Return sorted results +- **WHEN** returning discovered spec IDs +- **THEN** the results SHALL be sorted alphabetically +- **AND** hierarchical specs sort naturally (e.g., `cli/archive` before `cli/show`, and `cli-show` before `cli/show`) diff --git a/openspec/changes/hierarchical-specs/specs/hierarchical-spec-resolution/spec.md b/openspec/changes/hierarchical-specs/specs/hierarchical-spec-resolution/spec.md new file mode 100644 index 000000000..5eabf1f97 --- /dev/null +++ b/openspec/changes/hierarchical-specs/specs/hierarchical-spec-resolution/spec.md @@ -0,0 +1,75 @@ +## ADDED Requirements + +### Requirement: Path-based spec ID resolution +The system SHALL resolve spec IDs that contain `/` by treating each segment as a directory level under `openspec/specs/`. The final segment is the directory containing `spec.md`. + +#### Scenario: Resolve hierarchical spec ID +- **WHEN** resolving spec ID `cli/show` +- **THEN** the system SHALL look for `openspec/specs/cli/show/spec.md` + +#### Scenario: Resolve flat spec ID +- **WHEN** resolving spec ID `cli-show` +- **THEN** the system SHALL look for `openspec/specs/cli-show/spec.md` +- **AND** maintain backward compatibility with existing flat structure + +#### Scenario: Spec not found +- **WHEN** resolving a spec ID that does not exist +- **THEN** the system SHALL report the spec as not found +- **AND** suggest nearest matches from all discovered specs (flat and hierarchical) + +### Requirement: Subtree filtering +The system SHALL support filtering specs by path prefix to show only specs within a given subtree. + +#### Scenario: Filter by prefix +- **WHEN** listing specs with prefix `cli/` +- **THEN** return only specs whose ID starts with `cli/` +- **AND** exclude specs like `cli-show` that share the prefix string but are flat IDs + +#### Scenario: Filter with trailing slash +- **WHEN** the user provides prefix `cli/` +- **THEN** the system treats it as a subtree filter +- **AND** returns specs like `cli/show`, `cli/validate`, `cli/list` + +#### Scenario: Filter without trailing slash resolves as spec first +- **WHEN** the user provides `cli` without trailing slash +- **AND** `cli` is a valid spec ID (has `openspec/specs/cli/spec.md`) +- **THEN** resolve it as that specific spec + +#### Scenario: Filter without trailing slash falls back to subtree +- **WHEN** the user provides `cli` without trailing slash +- **AND** `cli` is NOT a valid spec ID +- **AND** specs exist with prefix `cli/` +- **THEN** treat it as a subtree filter and list matching specs + +### Requirement: Fuzzy matching for hierarchical IDs +The system SHALL extend fuzzy matching to support both full path matching and leaf-segment matching for hierarchical spec IDs. + +#### Scenario: Full path fuzzy match +- **WHEN** the user types `cli/shw` (typo) +- **THEN** suggest `cli/show` based on edit distance on the full ID string + +#### Scenario: Leaf segment match +- **WHEN** the user types `show` +- **AND** no exact match exists for flat spec ID `show` +- **AND** `cli/show` exists as a hierarchical spec +- **THEN** suggest `cli/show` as a match + +#### Scenario: Multiple leaf matches +- **WHEN** the user types `show` +- **AND** both `cli/show` and `admin/show` exist +- **THEN** suggest both as matches, sorted alphabetically + +### Requirement: Delta spec path resolution for changes +The system SHALL resolve delta spec paths within changes using the same hierarchical structure as main specs. + +#### Scenario: Hierarchical delta spec +- **WHEN** a change modifies spec `cli/show` +- **THEN** the delta spec SHALL be located at `changes//specs/cli/show/spec.md` + +#### Scenario: Flat delta spec +- **WHEN** a change modifies spec `cli-show` +- **THEN** the delta spec SHALL be located at `changes//specs/cli-show/spec.md` + +#### Scenario: Discovery of delta specs in changes +- **WHEN** discovering delta specs within a change directory +- **THEN** the system SHALL recursively walk `changes//specs/` using the same discovery logic as main specs diff --git a/openspec/changes/hierarchical-specs/tasks.md b/openspec/changes/hierarchical-specs/tasks.md new file mode 100644 index 000000000..fe616d897 --- /dev/null +++ b/openspec/changes/hierarchical-specs/tasks.md @@ -0,0 +1,44 @@ +## 1. Core Discovery & Resolution + +- [ ] 1.1 Create `specIdToPath(specId: string, baseDir: string): string` and `pathToSpecId(filePath: string, specsDir: string): string` utility functions in `src/utils/` with `/`-normalization on all platforms +- [ ] 1.2 Refactor `getSpecIds()` in `src/utils/item-discovery.ts` to recursively discover specs using `fast-glob` pattern `**/spec.md` under `openspec/specs/`, deriving IDs from relative paths +- [ ] 1.3 Update all direct path construction (`path.join(SPECS_DIR, specId, 'spec.md')`) across the codebase to use `specIdToPath()` +- [ ] 1.4 Add unit tests for `specIdToPath` and `pathToSpecId` including Windows path separator normalization +- [ ] 1.5 Add unit tests for recursive `getSpecIds()` with flat, nested, mixed, and hidden directory fixtures + +## 2. CLI Commands — Spec Subcommands + +- [ ] 2.1 Update `src/commands/spec.ts` `show` subcommand to accept and resolve hierarchical spec IDs (e.g., `cli/show`) +- [ ] 2.2 Update `src/commands/spec.ts` `list` subcommand to display full hierarchical IDs and accept an optional subtree prefix argument +- [ ] 2.3 Update `src/commands/spec.ts` `validate` subcommand to accept and resolve hierarchical spec IDs +- [ ] 2.4 Update interactive selection prompts in `spec show` and `spec validate` to display hierarchical IDs + +## 3. CLI Commands — Top-Level Show, Validate, List + +- [ ] 3.1 Update `src/commands/show.ts` type detection to resolve hierarchical spec IDs and include them in fuzzy-match suggestions +- [ ] 3.2 Update `src/commands/validate.ts` type detection and bulk validation (`--all`, `--specs`) to use recursive spec discovery +- [ ] 3.3 Update `src/core/list.ts` to use recursive spec discovery and support subtree filtering argument for `--specs` +- [ ] 3.4 Update `src/core/view.ts` dashboard to display hierarchical spec IDs in the specifications section + +## 4. Fuzzy Matching + +- [ ] 4.1 Extend `src/utils/match.ts` to support leaf-segment matching — when no exact match, search for specs whose last path segment matches the query +- [ ] 4.2 Add tests for fuzzy matching with hierarchical IDs (full path typo, leaf match, multiple leaf matches) + +## 5. Archive & Delta Spec Handling + +- [ ] 5.1 Update delta spec discovery in `src/core/archive.ts` / `src/core/specs-apply.ts` to recursively walk `changes//specs/` for delta specs at any depth +- [ ] 5.2 Update archive confirmation display to show full hierarchical paths for new and updated specs +- [ ] 5.3 Ensure archive creates intermediate directories when applying delta specs to new hierarchical paths +- [ ] 5.4 Add tests for archiving changes with hierarchical delta specs + +## 6. Validation & Edge Cases + +- [ ] 6.1 Update `src/commands/validate.ts` error messages and file path references to include full hierarchical spec paths +- [ ] 6.2 Update `src/core/validation/validator.ts` to handle hierarchical spec IDs in structured location paths +- [ ] 6.3 Add integration tests: mixed flat + hierarchical specs coexisting, subtree listing, and cross-platform path handling + +## 7. Documentation & Cleanup + +- [ ] 7.1 Update `openspec/AGENTS.md` with examples of hierarchical spec paths in templates and references +- [ ] 7.2 Update CLI help text for `spec list` to document subtree filtering syntax From 597115f703e84b50800231d1dc88934660a50e80 Mon Sep 17 00:00:00 2001 From: victorhsb Date: Thu, 12 Mar 2026 23:16:37 -0300 Subject: [PATCH 2/2] address coderabit comments --- openspec/changes/hierarchical-specs/proposal.md | 2 +- openspec/changes/hierarchical-specs/specs/cli-list/spec.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openspec/changes/hierarchical-specs/proposal.md b/openspec/changes/hierarchical-specs/proposal.md index 8cf1cf012..a17133362 100644 --- a/openspec/changes/hierarchical-specs/proposal.md +++ b/openspec/changes/hierarchical-specs/proposal.md @@ -10,7 +10,7 @@ OpenSpec stores all specs in a flat structure under `openspec/specs/`. As projec - Path construction throughout the codebase changes from `join(SPECS_DIR, specId, 'spec.md')` to handle `/`-separated IDs as nested directories - `spec list` gains the ability to filter by subtree (e.g., `openspec spec list cli/` shows only CLI specs) - Delta specs in changes mirror the hierarchical structure -- **BREAKING**: Existing flat spec IDs remain valid — no migration required. However, tools that parse spec IDs as simple strings may need updating. +- **COMPATIBILITY**: Existing flat spec IDs remain valid — no migration required. Tools that treat spec IDs as opaque strings need no changes. Tools that parse or construct spec ID structure (e.g., splitting on `-` to infer hierarchy) must be updated to handle `/`-separated IDs. ## Capabilities diff --git a/openspec/changes/hierarchical-specs/specs/cli-list/spec.md b/openspec/changes/hierarchical-specs/specs/cli-list/spec.md index 86b1af575..bf9726cf9 100644 --- a/openspec/changes/hierarchical-specs/specs/cli-list/spec.md +++ b/openspec/changes/hierarchical-specs/specs/cli-list/spec.md @@ -17,8 +17,9 @@ The command SHALL scan and analyze either active changes or specs based on the s #### Scenario: Scanning for specs in subtree - **WHEN** `openspec list --specs cli/` is executed -- **THEN** recursively scan only specs whose ID starts with `cli/` +- **THEN** recursively scan only specs whose ID starts with `cli/` at a segment boundary (i.e., the character after the prefix must be a path separator or the prefix must end with `/`) - **AND** display them with their full hierarchical IDs +- **AND** `cli/` matches `cli/show` and `cli/bar/baz` but NOT `client/foo` ### Requirement: Output Format The command SHALL display items in a clear, readable table format with mode-appropriate progress or counts.