mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 06:18:24 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c1a90165 | ||
|
|
21b5a3e680 | ||
|
|
1bda5be96c | ||
|
|
0faf44807e | ||
|
|
f9c1d07edb | ||
|
|
2bd1a4417c | ||
|
|
1db19ac3d8 | ||
|
|
25018786e4 | ||
|
|
5fd9173ad9 | ||
|
|
0a611747bc | ||
|
|
49e422724f | ||
|
|
c22d6bce1c | ||
|
|
1c0dc09dc9 |
@@ -1,76 +0,0 @@
|
||||
## openspec change vs spec: behavior differences and recommendations
|
||||
|
||||
This document compares how `openspec change` and `openspec spec` behave today (focused on `show` and `list`) and recommends a raw-first, minimal standard to keep behavior simple and predictable before adding smarter formatting later.
|
||||
|
||||
## Summary of key differences and recommendations
|
||||
|
||||
| Area | Current: change | Current: spec | Recommendation |
|
||||
|---|---|---|---|
|
||||
| Invocation (show) | `openspec change show [change-name]` auto-picks when only one active change | `openspec spec show <spec-id>` requires id | Require explicit IDs for both. If `change-name` is omitted, print available IDs and a short hint (e.g., use `openspec change list`) and exit non-zero. No auto-pick. No interactive picker. |
|
||||
| Default text output (show) | Raw `proposal.md` content | Formatted summary | Default both to RAW: print the underlying Markdown file as-is. Provide a future `--pretty` flag (non-default) for formatted output. |
|
||||
| Filtering flags (show) | `--requirements-only` affects text and JSON | `--requirements`, `--no-scenarios`, `-r/--requirement` | Raw-first: in TEXT mode, no filtering. All filtering applies only to JSON output. Deprecate text-mode filters. Keep minimal JSON filters only. |
|
||||
| JSON shape (show) | Full change object; `--requirements-only` can return an array | Filtered object | Always return an OBJECT. Minimal, stable shape. Change: `{ id, title, deltaCount, deltas, taskStatus? }`. Spec: `{ id, title, overview, requirementCount, requirements, metadata }`. No top-level arrays. |
|
||||
| Text output (list) | "Active Changes" with progress | "Available Specifications" with teaser | Default both to RAW/minimal: print IDs only by default. Add `--long` to show `id + title` and minimal details (counts). No teasers. |
|
||||
| JSON shape (list) | `[{ name, title, deltas, taskStatus }]` | `[{ id, title, overview, requirementCount }]` | Unify minimal keys: `id`, `title`, counts only. Change: `deltaCount`, `taskStatus`. Spec: `requirementCount`. Drop `overview` from list JSON. Sort by `id`. |
|
||||
| Error/exit policy | Spinner + `process.exit(1)` in some paths | `exitCode` in others | Raw-first: no spinners in errors. Use `console.error` + `process.exitCode = 1` consistently. |
|
||||
| Empty states (list) | Graceful | Errors if specs missing | Raw-first: graceful empty state everywhere. Print "No items found" and exit 0. |
|
||||
| Multi-selection (change show) | Auto-pick single; error when multiple | N/A | Keep auto-pick single. If multiple, print IDs inline and exit non-zero. No interactive prompts. |
|
||||
| Colors/TTY | Mixed | Chalk only | Raw-first: minimal color; honor `NO_COLOR` and add `--no-color`. |
|
||||
|
||||
## Detailed guidance
|
||||
|
||||
### 1) Unify flags and semantics (raw-first)
|
||||
- Text mode: no filters; just raw file content.
|
||||
- JSON mode: allow minimal filters only.
|
||||
- Specs: `--json` returns the structured spec. Optional: `-r/--requirement <n>` and `--requirements-only` apply to JSON only.
|
||||
- Changes: `--json` returns the structured change. Optional: `--deltas-only` applies to JSON only.
|
||||
- Deprecate text-mode filtering flags across both commands.
|
||||
- Optional future: `--pretty` (text formatting) as a non-default enhancement.
|
||||
|
||||
### 2) Normalize JSON contracts (minimal and stable)
|
||||
- Show (change): `{ id, title, deltaCount, deltas: [...], taskStatus?: { total, completed } }`
|
||||
- Show (spec): `{ id, title, overview, requirementCount, requirements: [...], metadata: { format, version } }`
|
||||
- List (change): `[{ id, title, deltaCount, taskStatus }]`
|
||||
- List (spec): `[{ id, title, requirementCount }]`
|
||||
- Notes:
|
||||
- No top-level arrays for filtered show responses; always objects.
|
||||
- Avoid derived/pretty fields (e.g., teasers, percentages). Counts only.
|
||||
|
||||
### 3) Standardize text UI (minimal)
|
||||
- Show: print raw Markdown file contents.
|
||||
- List (default): print IDs only, one per line.
|
||||
- List (`--long`): print `id: title` plus minimal counts where relevant.
|
||||
- Keep colors minimal; support `--no-color` and respect `NO_COLOR`.
|
||||
|
||||
### 4) Consistent error handling and empty states
|
||||
- Use `console.error` + `process.exitCode = 1`. Avoid `process.exit(1)`.
|
||||
- No spinners (`ora`) in raw-first mode.
|
||||
- Empty states print a simple message and exit 0.
|
||||
|
||||
### 5) Discoverability and UX
|
||||
- No `change-name` provided: print IDs inline and a short hint; exit non-zero. No auto-pick. No interactive prompts.
|
||||
- Add `--no-color` for deterministic logs and pipelines.
|
||||
|
||||
### 6) Backwards compatibility and deprecation
|
||||
- Keep legacy flags as aliases for one minor release.
|
||||
- Print a clear deprecation warning when a legacy flag is used.
|
||||
- Update CLI docs/README/specs to reflect raw-first behavior.
|
||||
|
||||
### 7) Test coverage updates
|
||||
- Add tests asserting raw text outputs (file passthrough) for `show`.
|
||||
- Add tests for minimal list outputs (IDs by default, `--long` for details).
|
||||
- Add JSON contract tests asserting minimal, stable shapes and JSON-only filtering.
|
||||
|
||||
### 8) Library alignment with existing commands
|
||||
- No new dependencies.
|
||||
- Do not use `@inquirer/prompts` in `change`/`spec` show/list (keep non-interactive). Interaction remains limited to `init`, `diff`, and `archive` where already in use.
|
||||
- Do not use `ora` in `change`/`spec` (including validate). Use `console.error` and `process.exitCode`.
|
||||
- Use `chalk` minimally; support `--no-color` and respect `NO_COLOR`.
|
||||
- Keep using the shared `Validator` where applicable.
|
||||
- Do not introduce `jest-diff` into `change`/`spec` (remains specific to `diff`).
|
||||
|
||||
## Why this approach
|
||||
- Keeps the system raw and predictable; easy to compose in scripts.
|
||||
- Minimizes UI/formatting logic until real needs emerge.
|
||||
- Stabilizes JSON for tooling and avoids top-level arrays.
|
||||
- Simple to extend later with `--pretty` and richer filtering if needed.
|
||||
@@ -0,0 +1,343 @@
|
||||
# Comprehensive Retrospective: Creating an OpenSpec Change Proposal
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document consolidates learnings from creating the `bulk-validation-interactive-selection` change proposal for OpenSpec. The process revealed critical gaps in documentation, unhelpful error messages, and areas where the system could be more user-friendly. While OpenSpec's core functionality works correctly, the user experience for creating changes needs significant improvement.
|
||||
|
||||
## Table of Contents
|
||||
1. [Errors Encountered](#errors-encountered)
|
||||
2. [System Issues vs User Errors](#system-issues-vs-user-errors)
|
||||
3. [Documentation Gaps Analysis](#documentation-gaps-analysis)
|
||||
4. [Key Learnings](#key-learnings)
|
||||
5. [Recommendations](#recommendations)
|
||||
6. [Conclusion](#conclusion)
|
||||
|
||||
---
|
||||
|
||||
## Errors Encountered
|
||||
|
||||
### Error 1: Misunderstanding Delta Structure
|
||||
**Error Message:** `✗ [ERROR] deltas: Change must have at least one delta`
|
||||
|
||||
**What Happened:**
|
||||
Initially attempted to define deltas directly in the `proposal.md` file using markdown sections like:
|
||||
```markdown
|
||||
### Delta: Add validate-all command
|
||||
**Type**: Feature addition
|
||||
**Effort**: Small (< 100 lines)
|
||||
```
|
||||
|
||||
**Root Cause:**
|
||||
Fundamental misunderstanding of how OpenSpec processes deltas. Deltas are derived from spec files in the change's `specs/` directory, not from the proposal itself.
|
||||
|
||||
**Discovery Process:**
|
||||
- Examined `ChangeParser` class in `/src/core/parsers/change-parser.ts`
|
||||
- Found that `parseDeltaSpecs()` method looks for spec files in `specs/` subdirectory
|
||||
- Learned that deltas are extracted by comparing spec files against existing specs
|
||||
|
||||
### Error 2: Missing Operation Prefix in Section Headers
|
||||
**Error Message:** `✗ [ERROR] deltas: Change must have at least one delta`
|
||||
|
||||
**What Happened:**
|
||||
Created new spec files with standard `## Requirements` headers instead of operation-prefixed headers.
|
||||
|
||||
**Root Cause:**
|
||||
Failed to understand that ALL spec files in a change need operation prefixes (`ADDED`, `MODIFIED`, etc.) in their section headers, regardless of whether they're new specs or modifications.
|
||||
|
||||
**Discovery Process:**
|
||||
- Created new specs with `## Requirements` → No deltas detected
|
||||
- Changed to `## ADDED Requirements` → Deltas detected successfully!
|
||||
- Realized creating new specs works perfectly fine once properly formatted
|
||||
|
||||
**Important Clarification:**
|
||||
OpenSpec fully supports creating new specs. They appear as ADDED operations in the deltas. My initial analysis incorrectly suggested this was a limitation, but it was actually just a formatting issue.
|
||||
|
||||
### Error 3: Improper Scenario Formatting
|
||||
**Error Message:**
|
||||
```
|
||||
✗ [ERROR] deltas.0.requirements.0.scenarios: Requirement must have at least one scenario
|
||||
✗ [ERROR] deltas.1.requirements.0.scenarios: Requirement must have at least one scenario
|
||||
```
|
||||
|
||||
**What Happened:**
|
||||
Formatted scenarios as bullet lists under a bold "Scenarios:" label:
|
||||
```markdown
|
||||
**Scenarios:**
|
||||
- **WHEN** executing command
|
||||
- **THEN** expected behavior
|
||||
```
|
||||
|
||||
**Root Cause:**
|
||||
OpenSpec's parser expects scenarios to be defined as level 4 headers (`####`) with specific formatting:
|
||||
```markdown
|
||||
#### Scenario: Descriptive scenario name
|
||||
|
||||
- **WHEN** executing command
|
||||
- **THEN** expected behavior
|
||||
```
|
||||
|
||||
**Discovery Process:**
|
||||
- Checked parsed JSON output: `npx openspec change show bulk-validation-interactive-selection --json`
|
||||
- Saw `"scenarios": []` empty array despite having scenario content
|
||||
- Examined working spec files and found the `#### Scenario:` header pattern
|
||||
|
||||
### Error 4: File Path Confusion
|
||||
**Initial Confusion:**
|
||||
Wasn't clear whether to create specs that would become part of the main `openspec/specs/` or just define them in the change.
|
||||
|
||||
**Resolution:**
|
||||
Learned that changes can:
|
||||
1. Create new specs (they start in `changes/{change-name}/specs/` and move to `openspec/specs/` when archived)
|
||||
2. Modify existing specs (by creating a spec file with the same name as one in `openspec/specs/`)
|
||||
3. The validation system detects both patterns and creates appropriate deltas
|
||||
|
||||
---
|
||||
|
||||
## System Issues vs User Errors
|
||||
|
||||
### System Issues / Bugs
|
||||
|
||||
#### 1. Unhelpful Error Messages ⚠️
|
||||
**Issue:** `✗ [ERROR] deltas: Change must have at least one delta`
|
||||
|
||||
**Why This Is a System Problem:**
|
||||
- Error message provides no guidance on HOW to create deltas
|
||||
- Doesn't mention that deltas come from `specs/` subdirectory
|
||||
- Doesn't explain the required section headers
|
||||
- A better error would be: "No deltas found. Ensure your change has a specs/ directory with .md files containing sections like '## ADDED Requirements'"
|
||||
|
||||
#### 2. Silent Scenario Parsing Failures ⚠️
|
||||
**Issue:** When scenarios were formatted incorrectly, they were silently ignored
|
||||
|
||||
**Why This Is a System Problem:**
|
||||
- Parser silently returns empty scenarios array instead of warning
|
||||
- No validation error explaining the format issue
|
||||
- User gets "Requirement must have at least one scenario" without knowing their scenarios exist but aren't parsed
|
||||
|
||||
**Evidence:**
|
||||
```json
|
||||
{
|
||||
"text": "The CLI SHALL provide a top-level `show` command with interactive selection.",
|
||||
"scenarios": [] // Silent failure - scenarios existed but weren't parsed
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. No Validation for Proposal Structure During Creation
|
||||
**Issue:** System allows creating invalid proposals without early feedback
|
||||
|
||||
**Why This Is a System Problem:**
|
||||
- No scaffolding or template commands
|
||||
- No incremental validation as you build
|
||||
- Must fully create the change before discovering structural issues
|
||||
|
||||
### User Errors (My Mistakes)
|
||||
|
||||
#### 1. Trying to Define Deltas in Proposal.md
|
||||
- Incorrectly assumed deltas could be inline in the proposal
|
||||
- System correctly expects deltas in separate spec files
|
||||
|
||||
#### 2. Using Wrong Section Headers
|
||||
- Used `## Requirements` instead of `## ADDED Requirements`
|
||||
- Convention is documented in existing changes, but I didn't examine carefully
|
||||
|
||||
#### 3. Wrong Scenario Format
|
||||
- Used bullet lists instead of `#### Scenario:` headers
|
||||
- Made assumptions instead of checking existing patterns
|
||||
|
||||
### Gray Areas
|
||||
|
||||
1. **Documentation gaps** - While examples exist, there's no comprehensive "How to Create a Change" guide
|
||||
2. **Lack of tooling** - No scaffolding commands to create properly structured changes
|
||||
|
||||
---
|
||||
|
||||
## Documentation Gaps Analysis
|
||||
|
||||
### Critical Gaps in openspec/README.md
|
||||
|
||||
#### 1. Scenario Format - COMPLETELY MISSING ⚠️
|
||||
**What README Shows:** No scenario examples at all
|
||||
|
||||
**What's Actually Required:**
|
||||
```markdown
|
||||
#### Scenario: Descriptive name
|
||||
- **WHEN** condition
|
||||
- **THEN** expected outcome
|
||||
- **AND** additional outcomes
|
||||
```
|
||||
|
||||
**Impact:** This was the biggest struggle. The README mentions requirements but never shows how to write scenarios. Without this, requirements fail validation.
|
||||
|
||||
#### 2. Complete Spec File Example - MISSING
|
||||
**What README Shows:** Only fragments
|
||||
|
||||
**What's Actually Needed:** A complete working example showing:
|
||||
- Full spec file structure
|
||||
- Proper requirement format
|
||||
- Scenario formatting
|
||||
- All required elements
|
||||
|
||||
#### 3. Validation Commands - NOT MENTIONED
|
||||
**Missing from README:**
|
||||
- `npx openspec change validate <change-name>`
|
||||
- `npx openspec change show <change-name> --json`
|
||||
- The `--strict` flag for catching warnings
|
||||
|
||||
#### 4. Delta Detection Explanation - INCOMPLETE
|
||||
**What's Missing:**
|
||||
- WHERE the system looks for specs (specs/ subdirectory)
|
||||
- THAT deltas are automatically extracted
|
||||
- HOW to debug when deltas aren't detected
|
||||
- WHAT error messages mean
|
||||
|
||||
### Misleading Documentation
|
||||
|
||||
#### "Store only the changes" - MISLEADING
|
||||
**Line 145:** `# - Store only the changes (not complete future state)`
|
||||
|
||||
**Problem:** This suggests storing diffs or partial content. In reality, you need:
|
||||
- Complete requirements in their final form
|
||||
- Full scenario definitions
|
||||
- The entire requirement text
|
||||
|
||||
### Documentation That Was Helpful
|
||||
1. Delta section headers (`## ADDED Requirements`) - clearly documented
|
||||
2. Directory structure - excellent visualization
|
||||
3. When to create proposals - well defined
|
||||
|
||||
---
|
||||
|
||||
## Key Learnings
|
||||
|
||||
### 1. OpenSpec's Delta Detection Algorithm
|
||||
The system follows this process:
|
||||
1. Scans `openspec/changes/{change-name}/specs/` directory
|
||||
2. For each spec file found, parses for delta sections (`ADDED`, `MODIFIED`, `REMOVED`, `RENAMED`)
|
||||
3. Creates delta objects with operation type, affected spec, and requirements
|
||||
4. Validates that at least one delta exists for the change to be valid
|
||||
|
||||
**Important:** Creating entirely new specs is fully supported! New specs use `## ADDED Requirements` and appear as ADDED operations in the deltas.
|
||||
|
||||
### 2. Spec File Structure Requirements
|
||||
Valid spec files must follow this structure:
|
||||
```markdown
|
||||
# Spec Title
|
||||
|
||||
## [ADDED|MODIFIED|REMOVED|RENAMED] Requirements
|
||||
|
||||
### Requirement: Clear requirement statement
|
||||
|
||||
The requirement description using SHALL/SHOULD/MAY.
|
||||
|
||||
#### Scenario: Scenario name
|
||||
|
||||
- **WHEN** condition
|
||||
- **THEN** expected outcome
|
||||
- **AND** additional outcomes
|
||||
```
|
||||
|
||||
### 3. Change Proposal Structure
|
||||
A valid change must have:
|
||||
- `## Why` section - explaining the motivation
|
||||
- `## What Changes` section - summarizing the changes
|
||||
- `specs/` directory with properly formatted spec files containing deltas
|
||||
- Each delta must have at least one requirement with at least one scenario
|
||||
|
||||
### 4. Validation Commands Are Essential
|
||||
```bash
|
||||
# Basic validation
|
||||
npx openspec change validate {change-name}
|
||||
|
||||
# Strict validation (recommended)
|
||||
npx openspec change validate {change-name} --strict
|
||||
|
||||
# Debug delta detection
|
||||
npx openspec change show {change-name} --json | jq '.deltas'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### High Priority (System Bugs to Fix)
|
||||
|
||||
1. **Improve Error Messages**
|
||||
- Add actionable guidance to error messages
|
||||
- Example: "No deltas found. Check: 1) specs/ directory exists, 2) Files use ## ADDED Requirements headers, 3) Each requirement has #### Scenario: sections"
|
||||
|
||||
2. **Add Warnings for Malformed Content**
|
||||
- Warn when scenarios exist but aren't properly formatted
|
||||
- Show which line/file has the issue
|
||||
|
||||
3. **Add Delta Detection Debugging**
|
||||
- Command like `openspec change debug-deltas {change-name}`
|
||||
- Show which files were scanned, what was found, what was rejected
|
||||
|
||||
### Medium Priority (Documentation Improvements)
|
||||
|
||||
1. **Add Complete Working Example to README**
|
||||
- Full change proposal with all files
|
||||
- Properly formatted specs with scenarios
|
||||
- Show the validation output
|
||||
|
||||
2. **Add Troubleshooting Section**
|
||||
- Common errors and their solutions
|
||||
- How to debug delta detection
|
||||
- Scenario formatting requirements
|
||||
|
||||
3. **Add Validation Best Practices**
|
||||
- When to use `--strict`
|
||||
- How to use JSON output for debugging
|
||||
- Common validation patterns
|
||||
|
||||
### Low Priority (Developer Experience)
|
||||
|
||||
1. **Add Scaffolding Command**
|
||||
```bash
|
||||
openspec change scaffold {change-name}
|
||||
```
|
||||
- Creates proper directory structure
|
||||
- Includes template files with correct formatting
|
||||
- Adds example scenarios
|
||||
|
||||
2. **Add Interactive Creation Wizard**
|
||||
- Guide users through change creation
|
||||
- Validate as they go
|
||||
- Suggest fixes for common issues
|
||||
|
||||
3. **Add Auto-fix Capability**
|
||||
- `--fix` flag to correct common formatting issues
|
||||
- Convert bullet list scenarios to proper headers
|
||||
- Add missing operation prefixes
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The OpenSpec system works correctly for its intended design, but the user experience for creating changes needs significant improvement. The core issues stem from:
|
||||
|
||||
### System Issues
|
||||
- **Unhelpful error messages** that don't guide users to solutions
|
||||
- **Silent parsing failures** that provide no feedback about malformed content
|
||||
- **Lack of debugging tools** to understand what went wrong
|
||||
|
||||
### Documentation Issues
|
||||
- **Critical formatting requirements missing** (especially scenario format)
|
||||
- **No complete working examples** showing all required elements
|
||||
- **Validation commands not documented** despite being essential
|
||||
|
||||
### User Issues
|
||||
- **Incorrect assumptions** about how the system works
|
||||
- **Not examining existing patterns** carefully enough
|
||||
- **Trying to shortcut** instead of following established conventions
|
||||
|
||||
### The Path Forward
|
||||
|
||||
With better error messages, complete documentation, and basic tooling support, most of the errors encountered could be prevented. The system's delta-centric approach is powerful and ensures changes are atomic and trackable, but it needs to be more discoverable and user-friendly.
|
||||
|
||||
The most impactful improvements would be:
|
||||
1. Adding scenario format documentation to the README
|
||||
2. Improving error messages with actionable guidance
|
||||
3. Creating a scaffolding command for new changes
|
||||
|
||||
These changes would transform OpenSpec from a system that works correctly but is hard to use, into one that actively helps developers succeed.
|
||||
@@ -0,0 +1,20 @@
|
||||
## Why
|
||||
|
||||
Users frequently need to view changes and specs but must know in advance whether they're looking at a change or spec. The current subcommand structure (`change show`, `spec show`) creates friction when:
|
||||
- Users want to quickly view an item without remembering its type
|
||||
- Exploring the codebase requires switching between different show commands
|
||||
- Show commands without arguments return errors instead of helpful guidance
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add new top-level `show` command for displaying changes or specs with intelligent selection
|
||||
- Support direct item display: `openspec show <item>` with automatic type detection
|
||||
- Interactive selection when no arguments provided
|
||||
- Enhance existing `change show` and `spec show` to support interactive selection (backwards compatibility)
|
||||
- Maintain all existing format options (--json, --deltas-only, --requirements, etc.)
|
||||
|
||||
## Impact
|
||||
|
||||
- New specs to create: cli-show
|
||||
- Specs to enhance: cli-change, cli-spec (for backwards compatibility)
|
||||
- Affected code: src/cli/index.ts, src/commands/show.ts (new), src/commands/spec.ts, src/commands/change.ts
|
||||
@@ -0,0 +1,23 @@
|
||||
# CLI Change Command Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Interactive show selection
|
||||
|
||||
The change show command SHALL support interactive selection when no change name is provided.
|
||||
|
||||
#### Scenario: Interactive change selection for show
|
||||
|
||||
- **WHEN** executing `openspec change show` without arguments
|
||||
- **THEN** display an interactive list of available changes
|
||||
- **AND** allow the user to select a change to show
|
||||
- **AND** display the selected change content
|
||||
- **AND** maintain all existing show options (--json, --deltas-only)
|
||||
|
||||
#### 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 change show` without a change name
|
||||
- **THEN** do not prompt interactively
|
||||
- **AND** print the existing hint including available change IDs
|
||||
- **AND** set `process.exitCode = 1`
|
||||
@@ -0,0 +1,83 @@
|
||||
# CLI Show Command Spec
|
||||
|
||||
## ADDED 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
|
||||
- **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 <item>` or `openspec change/spec show`
|
||||
- **AND** exit with code 1
|
||||
|
||||
#### Scenario: Direct item display
|
||||
|
||||
- **WHEN** executing `openspec show <item-name>`
|
||||
- **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: Type detection and ambiguity handling
|
||||
|
||||
- **WHEN** executing `openspec show <item-name>`
|
||||
- **THEN** if `<item-name>` 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
|
||||
|
||||
#### Scenario: Explicit type override
|
||||
|
||||
- **WHEN** executing `openspec show --type change <item>`
|
||||
- **THEN** treat `<item>` as a change ID and show it (skipping auto-detection)
|
||||
|
||||
- **WHEN** executing `openspec show --type spec <item>`
|
||||
- **THEN** treat `<item>` as a spec ID and show it (skipping auto-detection)
|
||||
|
||||
### Requirement: Output format options
|
||||
|
||||
The show command SHALL support various output formats consistent with existing commands.
|
||||
|
||||
#### Scenario: JSON output
|
||||
|
||||
- **WHEN** executing `openspec show <item> --json`
|
||||
- **THEN** output the item in JSON format
|
||||
- **AND** include parsed metadata and structure
|
||||
- **AND** maintain format consistency with existing change/spec show commands
|
||||
|
||||
#### Scenario: Flag scoping and delegation
|
||||
|
||||
- **WHEN** showing a change or a spec via the top-level command
|
||||
- **THEN** accept common flags such as `--json`
|
||||
- **AND** pass through type-specific flags to the corresponding implementation
|
||||
- Change-only flags: `--deltas-only` (alias `--requirements-only` deprecated)
|
||||
- Spec-only flags: `--requirements`, `--no-scenarios`, `-r/--requirement`
|
||||
- **AND** ignore irrelevant flags for the detected type with a warning
|
||||
|
||||
### Requirement: Interactivity controls
|
||||
|
||||
- The CLI SHALL respect `--no-interactive` to disable prompts.
|
||||
- The CLI SHALL respect `OPEN_SPEC_INTERACTIVE=0` to disable prompts globally.
|
||||
- Interactive prompts SHALL only be shown when stdin is a TTY and interactivity is not disabled.
|
||||
|
||||
#### Scenario: Change-specific options
|
||||
|
||||
- **WHEN** showing a change with `openspec show <change-name> --deltas-only`
|
||||
- **THEN** display only the deltas in JSON format
|
||||
- **AND** maintain compatibility with existing change show options
|
||||
|
||||
#### Scenario: Spec-specific options
|
||||
|
||||
- **WHEN** showing a spec with `openspec show <spec-id> --requirements`
|
||||
- **THEN** display only requirements in JSON format
|
||||
- **AND** support other spec options (--no-scenarios, -r)
|
||||
- **AND** maintain compatibility with existing spec show options
|
||||
@@ -0,0 +1,23 @@
|
||||
# CLI Spec Command Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### 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
|
||||
- **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
|
||||
@@ -164,4 +164,28 @@ The command SHALL handle various error conditions gracefully.
|
||||
**Confirmation for spec updates**: Provides visibility into what will change, prevents accidental overwrites, and ensures users understand the impact before specs are modified
|
||||
**Non-blocking confirmation**: Declining spec updates doesn't cancel archiving - users can review specs and choose to update them separately if needed
|
||||
**--yes flag for automation**: Allows CI/CD pipelines to archive without interactive prompts while maintaining safety by default for manual use
|
||||
**--skip-specs flag**: Enables archiving of changes that don't modify specs (like infrastructure, tooling, or documentation changes) without unnecessary spec update prompts or operations
|
||||
**--skip-specs flag**: Enables archiving of changes that don't modify specs (like infrastructure, tooling, or documentation changes) without unnecessary spec update prompts or operations
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Skip Specs Option
|
||||
|
||||
The archive command SHALL support a `--skip-specs` flag that skips all spec update operations and proceeds directly to archiving.
|
||||
|
||||
#### Scenario: Skipping spec updates with flag
|
||||
|
||||
- **WHEN** executing `openspec archive <change> --skip-specs`
|
||||
- **THEN** skip spec discovery and update confirmation
|
||||
- **AND** proceed directly to moving the change to archive
|
||||
- **AND** display a message indicating specs were skipped
|
||||
|
||||
### Requirement: Non-blocking confirmation
|
||||
|
||||
The archive operation SHALL proceed when the user declines spec updates instead of cancelling the entire operation.
|
||||
|
||||
#### Scenario: User declines spec update confirmation
|
||||
|
||||
- **WHEN** the user declines spec update confirmation
|
||||
- **THEN** skip spec updates
|
||||
- **AND** continue with the archive operation
|
||||
- **AND** display a success message indicating specs were not updated
|
||||
@@ -16,4 +16,4 @@ Currently, OpenSpec specs can only be viewed as markdown files. This makes progr
|
||||
- **Affected specs**: None (new capability)
|
||||
- **Affected code**:
|
||||
- src/cli/index.ts (register new command)
|
||||
- package.json (add zod dependency)
|
||||
- package.json (add zod dependency)
|
||||
|
||||
@@ -4,8 +4,16 @@
|
||||
|
||||
### Requirement: Display Format
|
||||
|
||||
The diff command SHALL display unified diff output in text format.
|
||||
|
||||
**Reason for removal**: The standard unified diff format is replaced by requirement-level side-by-side comparison that better shows semantic changes rather than line-by-line text differences.
|
||||
|
||||
#### Scenario: Unified diff output (deprecated)
|
||||
|
||||
- **WHEN** running `openspec diff <change>`
|
||||
- **THEN** show a unified text diff of files
|
||||
- **AND** include `+`/`-` prefixed lines representing additions and removals
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Diff Output
|
||||
|
||||
@@ -104,6 +104,14 @@ The archive process SHALL programmatically apply delta changes to current specif
|
||||
|
||||
### Requirement: Future State Storage
|
||||
|
||||
The system SHALL no longer store complete future-state specifications in change proposals.
|
||||
|
||||
**Reason for removal**: Replaced by delta-based change storage which provides better review experience and clearer change tracking.
|
||||
|
||||
**Migration path**: All new changes must use delta format.
|
||||
**Migration path**: All new changes must use delta format.
|
||||
|
||||
#### Scenario: Deprecate future state storage
|
||||
|
||||
- **WHEN** creating a new change proposal
|
||||
- **THEN** do not include full future-state specs
|
||||
- **AND** include only ADDED/MODIFIED/REMOVED/RENAMED requirements under the change's `specs/` directory
|
||||
@@ -0,0 +1,20 @@
|
||||
## Why
|
||||
|
||||
Currently, users must validate changes and specs individually by specifying each ID. This creates friction when:
|
||||
- Teams want to validate all changes/specs before a release
|
||||
- Developers need to ensure consistency across multiple related changes
|
||||
- Users run validation commands without arguments and receive errors instead of helpful guidance
|
||||
- The subcommand structure requires users to know in advance whether they're validating a change or spec
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add new top-level `validate` command with intuitive flags (--all, --changes, --specs)
|
||||
- Enhance existing `change validate` and `spec validate` to support interactive selection (backwards compatibility)
|
||||
- Interactive selection by default when no arguments provided
|
||||
- Support direct item validation: `openspec validate <item>` with automatic type detection
|
||||
|
||||
## Impact
|
||||
|
||||
- New specs to create: cli-validate
|
||||
- Specs to enhance: cli-change, cli-spec (for backwards compatibility)
|
||||
- Affected code: src/cli/index.ts, src/commands/validate.ts (new), src/commands/spec.ts, src/commands/change.ts
|
||||
@@ -0,0 +1,22 @@
|
||||
# CLI Change Command Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Interactive validation selection
|
||||
|
||||
The change validate command SHALL support interactive selection when no change name is provided.
|
||||
|
||||
#### Scenario: Interactive change selection for validation
|
||||
|
||||
- **WHEN** executing `openspec change validate` without arguments
|
||||
- **THEN** display an interactive list of available changes
|
||||
- **AND** allow the user to select a change to validate
|
||||
- **AND** validate the selected change
|
||||
|
||||
#### 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 change validate` without a change name
|
||||
- **THEN** do not prompt interactively
|
||||
- **AND** print the existing hint including available change IDs
|
||||
- **AND** set `process.exitCode = 1`
|
||||
@@ -0,0 +1,23 @@
|
||||
# CLI Spec Command Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### 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
|
||||
- **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
|
||||
@@ -0,0 +1,141 @@
|
||||
# CLI Validate Command Spec
|
||||
|
||||
## ADDED 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 <item-name>`
|
||||
- **THEN** automatically detect if item is a change or spec
|
||||
- **AND** validate the specified item
|
||||
- **AND** display validation results
|
||||
|
||||
### 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** validate all specs in openspec/specs/
|
||||
- **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** include all specs that have a `spec.md` under `openspec/specs/<id>/spec.md`
|
||||
|
||||
#### Scenario: Validate all changes
|
||||
|
||||
- **WHEN** executing `openspec validate --changes`
|
||||
- **THEN** validate all changes in openspec/changes/ (excluding archive)
|
||||
- **AND** display results for each change
|
||||
- **AND** show summary statistics
|
||||
|
||||
#### Scenario: Validate all specs
|
||||
|
||||
- **WHEN** executing `openspec validate --specs`
|
||||
- **THEN** validate all specs in openspec/specs/
|
||||
- **AND** display results for each spec
|
||||
- **AND** show summary statistics
|
||||
|
||||
### Requirement: Validation options and progress indication
|
||||
|
||||
The validate command SHALL support standard validation options (--strict, --json) and display progress during bulk operations.
|
||||
|
||||
#### Scenario: Strict validation
|
||||
|
||||
- **WHEN** executing `openspec validate --all --strict`
|
||||
- **THEN** apply strict validation to all items
|
||||
- **AND** treat warnings as errors
|
||||
- **AND** fail if any item has warnings or errors
|
||||
|
||||
#### Scenario: JSON output
|
||||
|
||||
- **WHEN** executing `openspec validate --all --json`
|
||||
- **THEN** output validation results as JSON
|
||||
- **AND** include detailed issues for each item
|
||||
- **AND** include summary statistics
|
||||
|
||||
#### Scenario: JSON output schema for bulk validation
|
||||
|
||||
- **WHEN** executing `openspec validate --all --json` (or `--changes` / `--specs`)
|
||||
- **THEN** output a JSON object with the following shape:
|
||||
- `items`: Array of objects with fields `{ id: string, type: "change"|"spec", valid: boolean, issues: Issue[], durationMs: number }`
|
||||
- `summary`: Object `{ totals: { items: number, passed: number, failed: number }, byType: { change?: { items: number, passed: number, failed: number }, spec?: { items: number, passed: number, failed: number } } }`
|
||||
- `version`: String identifier for the schema (e.g., `"1.0"`)
|
||||
- **AND** exit with code 1 if any `items[].valid === false`
|
||||
|
||||
Where `Issue` follows the existing per-item validation report shape `{ level: "ERROR"|"WARNING"|"INFO", path: string, message: string }`.
|
||||
|
||||
#### Scenario: Show validation progress
|
||||
|
||||
- **WHEN** validating multiple items (--all, --changes, or --specs)
|
||||
- **THEN** show progress indicator or status updates
|
||||
- **AND** indicate which item is currently being validated
|
||||
- **AND** display running count of passed/failed items
|
||||
|
||||
#### Scenario: Concurrency limits for performance
|
||||
|
||||
- **WHEN** validating multiple items
|
||||
- **THEN** run validations with a bounded concurrency (e.g., 4–8 in parallel)
|
||||
- **AND** ensure progress indicators remain responsive
|
||||
|
||||
### Requirement: Item type detection and ambiguity handling
|
||||
|
||||
#### Scenario: Direct item validation with automatic type detection
|
||||
|
||||
- **WHEN** executing `openspec validate <item-name>`
|
||||
- **THEN** if `<item-name>` uniquely matches a change or a spec, validate that item
|
||||
|
||||
#### Scenario: Ambiguity between change and spec names
|
||||
|
||||
- **GIVEN** `<item-name>` exists both as a change and as a spec
|
||||
- **WHEN** executing `openspec validate <item-name>`
|
||||
- **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 `<item-name>` matches neither a change nor a spec
|
||||
- **THEN** print a not-found error
|
||||
- **AND** show nearest-match suggestions when available
|
||||
- **AND** exit with code 1
|
||||
|
||||
#### Scenario: Explicit type override
|
||||
|
||||
- **WHEN** executing `openspec validate --type change <item>`
|
||||
- **THEN** treat `<item>` as a change ID and validate it (skipping auto-detection)
|
||||
|
||||
- **WHEN** executing `openspec validate --type spec <item>`
|
||||
- **THEN** treat `<item>` as a spec ID and validate it (skipping auto-detection)
|
||||
|
||||
### Requirement: Interactivity controls
|
||||
|
||||
- The CLI SHALL respect `--no-interactive` to disable prompts.
|
||||
- The CLI SHALL respect `OPEN_SPEC_INTERACTIVE=0` to disable prompts globally.
|
||||
- Interactive prompts SHALL only be shown when stdin is a TTY and interactivity is not disabled.
|
||||
@@ -25,4 +25,16 @@ Modify the update command to:
|
||||
- Update command only modifies existing AI tool configuration files
|
||||
- No new AI tool files created during update
|
||||
- Team members can use different AI tools without conflicts
|
||||
- Existing projects continue to work (backward compatibility)
|
||||
- Existing projects continue to work (backward compatibility)
|
||||
|
||||
## Why
|
||||
|
||||
Users need predictable, tool-agnostic behavior from `openspec update`. Creating or forcing updates for AI tool files that a project does not use causes confusion and merge conflicts. Restricting updates to existing files and always updating core OpenSpec files keeps the workflow consistent for mixed-tool teams.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **cli-update:** Modify update behavior to update only existing AI tool configuration files and never create new ones; always update core OpenSpec files and display an ASCII-safe success message.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
Removed from proposal to follow conventions. See `specs/cli-update/spec.md` for the delta requirements content.
|
||||
@@ -1,113 +1,23 @@
|
||||
# Update Command Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
As a developer using OpenSpec, I want to update the OpenSpec instructions in my project when new versions are released, so that I can benefit from improvements to AI agent instructions.
|
||||
|
||||
## Core Requirements
|
||||
|
||||
### Requirement: Update Behavior
|
||||
|
||||
The update command SHALL update OpenSpec instruction files to the latest templates.
|
||||
|
||||
#### Scenario: Running update command
|
||||
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** the command SHALL:
|
||||
- Check if the `openspec` directory exists
|
||||
- Replace `openspec/README.md` with the latest template (complete replacement)
|
||||
- For each supported AI tool configuration file:
|
||||
- Check if the file exists (e.g., CLAUDE.md, COPILOT.md)
|
||||
- If it exists, update it using appropriate markers
|
||||
- If it doesn't exist, skip it (do NOT create)
|
||||
- Preserve user content outside markers
|
||||
- Display ASCII-safe success message: "Updated OpenSpec instructions"
|
||||
|
||||
### Requirement: Prerequisites
|
||||
|
||||
The command SHALL require an existing OpenSpec structure before allowing updates.
|
||||
|
||||
#### Scenario: Checking prerequisites
|
||||
|
||||
- **GIVEN** the command requires an existing `openspec` directory (created by `openspec init`)
|
||||
- **WHEN** the `openspec` directory does not exist
|
||||
- **THEN** display error: "No OpenSpec directory found. Run 'openspec init' first."
|
||||
- **AND** exit with code 1
|
||||
|
||||
### Requirement: File Handling
|
||||
|
||||
The update command SHALL handle file updates in a predictable and safe manner.
|
||||
|
||||
#### Scenario: Updating files
|
||||
|
||||
- **WHEN** updating files
|
||||
- **THEN** completely replace `openspec/README.md` with the latest template
|
||||
- **AND** update only the AI tool configuration files that already exist
|
||||
- **AND** use the default directory name `openspec`
|
||||
- **AND** be idempotent (repeated runs have no additional effect)
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tool-Agnostic Updates
|
||||
|
||||
The update command SHALL work for any team member regardless of their AI tool choice.
|
||||
The update command SHALL update only existing AI tool configuration files and SHALL NOT create new ones.
|
||||
|
||||
#### Scenario: Team member using Claude
|
||||
#### Scenario: Updating existing tool files
|
||||
|
||||
- **GIVEN** a team member has CLAUDE.md in their project
|
||||
- **WHEN** running `openspec update`
|
||||
- **THEN** update the CLAUDE.md file with the latest template
|
||||
- **AND** preserve user content outside OpenSpec markers
|
||||
- **AND** NOT create files for other tools
|
||||
|
||||
#### Scenario: Team member using different tool
|
||||
|
||||
- **GIVEN** a team member has COPILOT.md but no CLAUDE.md
|
||||
- **WHEN** running `openspec update`
|
||||
- **THEN** update the COPILOT.md file if implementation exists
|
||||
- **AND** NOT create CLAUDE.md
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** update each AI tool configuration file that exists (e.g., CLAUDE.md, COPILOT.md)
|
||||
- **AND** do not create missing tool configuration files
|
||||
- **AND** preserve user content outside OpenSpec markers
|
||||
|
||||
#### Scenario: Mixed team environment
|
||||
### Requirement: Core Files Always Updated
|
||||
|
||||
- **GIVEN** a repository with both CLAUDE.md and COPILOT.md (different team members)
|
||||
- **WHEN** any team member runs `openspec update`
|
||||
- **THEN** update all existing AI tool configuration files
|
||||
- **AND** NOT create new AI tool configuration files
|
||||
- **AND** each team member's preferred tool remains configured
|
||||
The update command SHALL always update the core OpenSpec files and display an ASCII-safe success message.
|
||||
|
||||
## Edge Cases
|
||||
#### Scenario: Successful update
|
||||
|
||||
### Requirement: Error Handling
|
||||
|
||||
The command SHALL handle edge cases gracefully.
|
||||
|
||||
#### Scenario: File permission errors
|
||||
|
||||
- **WHEN** file write fails
|
||||
- **THEN** let the error bubble up naturally with file path
|
||||
|
||||
#### Scenario: No AI tool files exist
|
||||
|
||||
- **GIVEN** no AI tool configuration files exist
|
||||
- **WHEN** running update
|
||||
- **THEN** only update openspec/README.md
|
||||
- **AND** display success message
|
||||
|
||||
#### Scenario: Custom directory names
|
||||
|
||||
- **WHEN** considering custom directory names
|
||||
- **THEN** not supported in this change
|
||||
- **AND** the default directory name `openspec` SHALL be used
|
||||
|
||||
## Success Criteria
|
||||
|
||||
Users SHALL be able to:
|
||||
- Update OpenSpec instructions with a single command
|
||||
- Get the latest AI agent instructions for their existing tools
|
||||
- Work in teams where members use different AI tools
|
||||
- NOT have unwanted AI tool configuration files created
|
||||
|
||||
The update process SHALL be:
|
||||
- Simple and fast (no version checking)
|
||||
- Predictable (same result every time)
|
||||
- Self-contained (no network required)
|
||||
- Team-friendly (respects individual tool choices)
|
||||
- **WHEN** the update completes successfully
|
||||
- **THEN** replace `openspec/README.md` with the latest template
|
||||
- **AND** update existing AI tool configuration files within markers
|
||||
- **AND** display the message: "Updated OpenSpec instructions"
|
||||
@@ -33,4 +33,4 @@ OpenSpec specifications lack a consistent structure that makes sections visually
|
||||
- Affected specs: openspec-conventions (enhancement to existing capability)
|
||||
- Affected code: None initially - this is a documentation standard enhancement
|
||||
- Migration: Gradual - existing specs migrate as they're modified
|
||||
- Tooling: Enables future parsing tools but doesn't require them
|
||||
- Tooling: Enables future parsing tools but doesn't require them
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# OpenSpec Conventions Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Structured Format Adoption
|
||||
|
||||
Behavioral specifications SHALL adopt the structured format with `### Requirement:` and `#### Scenario:` headers as the default.
|
||||
|
||||
#### Scenario: Use structured headings for behavior
|
||||
|
||||
- **WHEN** documenting behavioral requirements
|
||||
- **THEN** use `### Requirement:` for requirements
|
||||
- **AND** use `#### Scenario:` for scenarios with bold WHEN/THEN/AND keywords
|
||||
|
||||
## Purpose
|
||||
|
||||
OpenSpec conventions SHALL define how system capabilities are documented, how changes are proposed and tracked, and how specifications evolve over time. This meta-specification serves as the source of truth for OpenSpec's own conventions.
|
||||
|
||||
+2
-1
@@ -37,7 +37,8 @@
|
||||
"build": "node build.js",
|
||||
"dev": "tsc --watch",
|
||||
"dev:cli": "pnpm build && node bin/openspec.js",
|
||||
"test": "vitest",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"prepare": "npm run build"
|
||||
|
||||
+28
-26
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { select, confirm } from '@inquirer/prompts';
|
||||
import { FileSystemUtils } from '../utils/file-system.js';
|
||||
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
||||
import { Validator } from './validation/validator.js';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
@@ -138,10 +139,12 @@ export class ArchiveCommand {
|
||||
console.log(chalk.yellow(`Affected files: ${changeDir}`));
|
||||
}
|
||||
|
||||
// Check for incomplete tasks
|
||||
const tasksPath = path.join(changeDir, 'tasks.md');
|
||||
const incompleteTasks = await this.checkIncompleteTasks(tasksPath);
|
||||
|
||||
// Show progress and check for incomplete tasks
|
||||
const progress = await getTaskProgressForChange(changesDir, changeName);
|
||||
const status = formatTaskStatus(progress);
|
||||
console.log(`Task status: ${status}`);
|
||||
|
||||
const incompleteTasks = Math.max(progress.total - progress.completed, 0);
|
||||
if (incompleteTasks > 0) {
|
||||
if (!options.yes) {
|
||||
const proceed = await confirm({
|
||||
@@ -250,11 +253,24 @@ export class ArchiveCommand {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('Available changes:');
|
||||
const choices = changeDirs.map(name => ({
|
||||
name: name,
|
||||
value: name
|
||||
}));
|
||||
// Build choices with progress inline to avoid duplicate lists
|
||||
let choices: Array<{ name: string; value: string }> = changeDirs.map(name => ({ name, value: name }));
|
||||
try {
|
||||
const progressList: Array<{ id: string; status: string }> = [];
|
||||
for (const id of changeDirs) {
|
||||
const progress = await getTaskProgressForChange(changesDir, id);
|
||||
const status = formatTaskStatus(progress);
|
||||
progressList.push({ id, status });
|
||||
}
|
||||
const nameWidth = Math.max(...progressList.map(p => p.id.length));
|
||||
choices = progressList.map(p => ({
|
||||
name: `${p.id.padEnd(nameWidth)} ${p.status}`,
|
||||
value: p.id
|
||||
}));
|
||||
} catch {
|
||||
// If anything fails, fall back to simple names
|
||||
choices = changeDirs.map(name => ({ name, value: name }));
|
||||
}
|
||||
|
||||
try {
|
||||
const answer = await select({
|
||||
@@ -268,23 +284,9 @@ export class ArchiveCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkIncompleteTasks(tasksPath: string): Promise<number> {
|
||||
try {
|
||||
const content = await fs.readFile(tasksPath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
let incompleteTasks = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.includes('- [ ]')) {
|
||||
incompleteTasks++;
|
||||
}
|
||||
}
|
||||
|
||||
return incompleteTasks;
|
||||
} catch {
|
||||
// No tasks.md file or error reading it
|
||||
return 0;
|
||||
}
|
||||
// Deprecated: replaced by shared task-progress utilities
|
||||
private async checkIncompleteTasks(_tasksPath: string): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async findSpecUpdates(changeDir: string, mainSpecsDir: string): Promise<SpecUpdate[]> {
|
||||
|
||||
+5
-35
@@ -1,5 +1,6 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
||||
|
||||
interface ChangeInfo {
|
||||
name: string;
|
||||
@@ -33,35 +34,11 @@ export class ListCommand {
|
||||
const changes: ChangeInfo[] = [];
|
||||
|
||||
for (const changeDir of changeDirs) {
|
||||
const tasksPath = path.join(changesDir, changeDir, 'tasks.md');
|
||||
let completedTasks = 0;
|
||||
let incompleteTasks = 0;
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(tasksPath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.includes('- [x]')) {
|
||||
completedTasks++;
|
||||
} else if (line.includes('- [ ]')) {
|
||||
incompleteTasks++;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// No tasks.md file
|
||||
changes.push({
|
||||
name: changeDir,
|
||||
completedTasks: 0,
|
||||
totalTasks: 0
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const progress = await getTaskProgressForChange(changesDir, changeDir);
|
||||
changes.push({
|
||||
name: changeDir,
|
||||
completedTasks,
|
||||
totalTasks: completedTasks + incompleteTasks
|
||||
completedTasks: progress.completed,
|
||||
totalTasks: progress.total
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,14 +52,7 @@ export class ListCommand {
|
||||
const nameWidth = Math.max(...changes.map(c => c.name.length));
|
||||
const paddedName = change.name.padEnd(nameWidth);
|
||||
|
||||
let status: string;
|
||||
if (change.totalTasks === 0) {
|
||||
status = 'No tasks';
|
||||
} else if (change.completedTasks === change.totalTasks) {
|
||||
status = '✓ Complete';
|
||||
} else {
|
||||
status = `${change.completedTasks}/${change.totalTasks} tasks`;
|
||||
}
|
||||
const status = formatTaskStatus({ total: change.totalTasks, completed: change.completedTasks });
|
||||
|
||||
console.log(`${padding}${paddedName} ${status}`);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ export class ChangeParser extends MarkdownParser {
|
||||
spec: specName,
|
||||
operation: 'ADDED' as DeltaOperation,
|
||||
description: `Add requirement: ${req.text}`,
|
||||
requirement: req,
|
||||
// Use plural form to satisfy validators that expect an array
|
||||
requirements: [req],
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -108,7 +109,7 @@ export class ChangeParser extends MarkdownParser {
|
||||
spec: specName,
|
||||
operation: 'MODIFIED' as DeltaOperation,
|
||||
description: `Modify requirement: ${req.text}`,
|
||||
requirement: req,
|
||||
requirements: [req],
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -122,7 +123,7 @@ export class ChangeParser extends MarkdownParser {
|
||||
spec: specName,
|
||||
operation: 'REMOVED' as DeltaOperation,
|
||||
description: `Remove requirement: ${req.text}`,
|
||||
requirement: req,
|
||||
requirements: [req],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
|
||||
const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
|
||||
|
||||
export interface TaskProgress {
|
||||
total: number;
|
||||
completed: number;
|
||||
}
|
||||
|
||||
export function countTasksFromContent(content: string): TaskProgress {
|
||||
const lines = content.split('\n');
|
||||
let total = 0;
|
||||
let completed = 0;
|
||||
for (const line of lines) {
|
||||
if (line.match(TASK_PATTERN)) {
|
||||
total++;
|
||||
if (line.match(COMPLETED_TASK_PATTERN)) {
|
||||
completed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { total, completed };
|
||||
}
|
||||
|
||||
export async function getTaskProgressForChange(changesDir: string, changeName: string): Promise<TaskProgress> {
|
||||
const tasksPath = path.join(changesDir, changeName, 'tasks.md');
|
||||
try {
|
||||
const content = await fs.readFile(tasksPath, 'utf-8');
|
||||
return countTasksFromContent(content);
|
||||
} catch {
|
||||
return { total: 0, completed: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export function formatTaskStatus(progress: TaskProgress): string {
|
||||
if (progress.total === 0) return 'No tasks';
|
||||
if (progress.completed === progress.total) return '✓ Complete';
|
||||
return `${progress.completed}/${progress.total} tasks`;
|
||||
}
|
||||
|
||||
|
||||
@@ -569,14 +569,14 @@ E1 updated`);
|
||||
// Execute without change name
|
||||
await archiveCommand.execute(undefined, { yes: true });
|
||||
|
||||
// Verify select was called with correct options
|
||||
expect(mockSelect).toHaveBeenCalledWith({
|
||||
// Verify select was called with correct options (values matter, names may include progress)
|
||||
expect(mockSelect).toHaveBeenCalledWith(expect.objectContaining({
|
||||
message: 'Select a change to archive',
|
||||
choices: [
|
||||
{ name: change1, value: change1 },
|
||||
{ name: change2, value: change2 }
|
||||
]
|
||||
});
|
||||
choices: expect.arrayContaining([
|
||||
expect.objectContaining({ value: change1 }),
|
||||
expect.objectContaining({ value: change2 })
|
||||
])
|
||||
}));
|
||||
|
||||
// Verify the selected change was archived
|
||||
const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
|
||||
|
||||
Reference in New Issue
Block a user