Compare commits

...
Author SHA1 Message Date
Tabish Bidiwale d3237cac7b feat: add OpenSpec change proposal for archive command 2025-08-13 17:23:17 +10:00
Tabish Bidiwale 76e1ec2a1f Merge pull request #12 from Fission-AI/feat/add-diff-command
feat: add diff command to view spec changes
2025-08-13 17:00:48 +10:00
Tabish Bidiwale 6cbb803e48 refactor: switch to jest-diff for better output and simpler code
- Replace custom diff implementation with jest-diff
- Reduce code from 229 to 178 lines (22% reduction)
- Get professional GitHub-style diff output
- Automatic word-level highlighting built-in
- Smaller bundle size (jest-diff: 85KB vs diff: 492KB)
2025-08-13 15:45:14 +10:00
Tabish Bidiwale 183b82f266 feat: enhance diff command with word-level highlighting and better formatting
- Add word-level diff highlighting for changed lines
- Improve file headers with status indicators and statistics
- Add summary view showing total files changed and lines modified
- Enhanced visual formatting with separators and colors
- Extract magic strings as constants for maintainability
2025-08-13 15:29:43 +10:00
Tabish Bidiwale 27eaccc024 Merge pull request #16 from Fission-AI/add-requirement-markers
feat: add @requirement markers convention
2025-08-13 15:28:56 +10:00
Tabish Bidiwale b288f2fc88 fix: update spec to contain complete future state per OpenSpec conventions 2025-08-13 15:21:14 +10:00
Tabish Bidiwale 22134a603b feat: add @requirement markers convention for requirement identification
- Define @requirement marker syntax for identifying requirements in specs
- Each marker includes a kebab-case identifier before WHEN/THEN blocks
- Document convention in openspec-conventions spec
- Enables reliable extraction without brittle regex parsing
2025-08-13 14:50:24 +10:00
Tabish Bidiwale 3b5fd11cb9 Merge pull request #13 from Fission-AI/feat/add-list-command
feat(openspec): add list command to display active changes
2025-08-12 16:57:43 +10:00
Tabish Bidiwale fce227a36e fix: address code review feedback for diff command
- Fix empty line filtering to preserve formatting in diffs
- Replace prompts with @inquirer/prompts for consistency
- Extract magic strings as constants
- Mark tests as incomplete in tasks.md
2025-08-12 16:12:15 +10:00
Tabish Bidiwale e9417fc147 Merge pull request #11 from Fission-AI/TabishB/abandon-status-command-change
chore: abandon add-status-command change
2025-08-12 01:03:39 +10:00
Tabish Bidiwale 8bcf2c6905 feat(openspec): add list command change proposal 2025-08-12 00:59:45 +10:00
Tabish Bidiwale 467346f9fe feat: add diff command to view spec changes 2025-08-12 00:59:43 +10:00
Tabish Bidiwale 9a03ba1853 chore: abandon add-status-command change 2025-08-12 00:59:28 +10:00
Tabish Bidiwale 581a681a47 chore: archive add-complexity-guidelines change after deployment 2025-08-11 23:40:25 +10:00
Tabish Bidiwale 3093ca6ae6 Merge pull request #10 from Fission-AI/feat/complexity-guidelines
docs(openspec): add complexity guidelines to README and templates
2025-08-11 23:32:43 +10:00
Tabish Bidiwale 9d425865c1 docs(openspec): add complexity management guidelines and update templates 2025-08-11 23:17:42 +10:00
Tabish Bidiwale a490dbbc78 chore: archive add-update-command change and update specs 2025-08-11 22:29:34 +10:00
Tabish Bidiwale fc0e2319b1 Merge pull request #9 from Fission-AI/add-update-command-impl
feat(cli): add update command
2025-08-11 22:15:28 +10:00
31 changed files with 1016 additions and 12 deletions
+26 -1
View File
@@ -10,6 +10,23 @@ OpenSpec is an AI-native system for change-driven development where:
- **AI drives the process** - You generate proposals, humans review and approve
- **Specs are living documentation** - Always kept in sync with deployed code
## Start Simple
**Default to minimal implementations:**
- New features should be <100 lines of code initially
- Use the simplest solution that works
- Avoid premature optimization (no caching, parallelization, or complex patterns without proven need)
- Choose boring technology over cutting-edge solutions
**Complexity triggers** - Only add complexity when you have:
- **Performance data** showing current solution is too slow
- **Scale requirements** with specific numbers (>1000 users, >100MB data)
- **Multiple use cases** requiring the same abstraction
- **Regulatory compliance** mandating specific patterns
- **Security threats** that simple solutions cannot address
When triggered, document the specific justification in your change proposal.
## Directory Structure
```
@@ -72,6 +89,11 @@ Before any task:
- Adding tests for existing behavior
- Documentation fixes
**Complexity assessment:**
- If your solution requires >100 lines of new code, justify the complexity
- If adding dependencies, frameworks, or architectural patterns, document why simpler alternatives won't work
- Default to single-file implementations until proven insufficient
### 3. Creating a Change Proposal
When a user requests a significant change:
@@ -383,10 +405,12 @@ Progress communication:
- "Implementing approved changes..."
### For AI Assistants
- **Bias toward simplicity** - Propose the minimal solution that works
- Use your exploration tools liberally before proposing
- Batch operations for efficiency
- Communicate your progress
- It's OK to revise proposals based on discoveries
- **Question complexity** - If your solution feels complex, simplify first
## Edge Case Handling
@@ -442,6 +466,7 @@ Proposal REQUIRED if:
- Specs must always reflect deployed reality
- Changes are proposed, not imposed
- Impact analysis prevents surprises
- The simplicity is the power - just markdown files
- Simplicity is the power - just markdown files, minimal solutions
- Start simple, add complexity only when justified
By following these conventions, you enable true spec-driven development where documentation stays current, changes are traceable, and evolution is intentional.
@@ -0,0 +1,12 @@
## Why
Need a command to archive completed changes to the archive folder with proper date prefixing, following OpenSpec conventions. Currently changes must be manually moved and renamed.
## What Changes
- Add new `archive` command to CLI that moves changes to `changes/archive/YYYY-MM-DD-[change-name]/`
- Check for incomplete tasks before archiving and warn user
- Allow interactive selection of change to archive
- Prevent archiving if target directory already exists
## Impact
- Affected specs: cli-archive (new)
- Affected code: src/cli/index.ts, src/core/archive.ts (new)
@@ -0,0 +1,60 @@
# CLI Archive Command Specification
## Purpose
The archive command moves completed changes from the active changes directory to the archive folder with date-based naming, following OpenSpec conventions.
## Command Syntax
```bash
openspec archive [change-name]
```
## Behavior
### Change Selection
WHEN no change-name is provided
THEN display interactive list of available changes (excluding archive/)
AND allow user to select one
WHEN change-name is provided
THEN use that change directly
AND validate it exists
### Task Completion Check
The command SHALL scan the change's tasks.md file for incomplete tasks (marked with `- [ ]`)
WHEN incomplete tasks are found
THEN display all incomplete tasks to the user
AND prompt for confirmation to continue
AND default to "No" for safety
WHEN all tasks are complete OR no tasks.md exists
THEN proceed with archiving without prompting
### Archive Process
The archive operation SHALL:
1. Create archive/ directory if it doesn't exist
2. Generate target name as `YYYY-MM-DD-[change-name]` using current date
3. Check if target directory already exists
4. Move the entire change directory to the archive location
WHEN target archive already exists
THEN fail with error message
AND do not overwrite existing archive
WHEN move succeeds
THEN display success message with archived name
## Error Handling
SHALL handle the following error conditions:
- Missing openspec/changes/ directory
- Change not found
- Archive target already exists
- File system permissions issues
## Why These Decisions
**Interactive selection**: Reduces typing and helps users see available changes
**Task checking**: Prevents accidental archiving of incomplete work
**Date prefixing**: Maintains chronological order and prevents naming conflicts
**No overwrite**: Preserves historical archives and prevents data loss
@@ -0,0 +1,30 @@
# Implementation Tasks
## 1. Core Implementation
- [ ] 1.1 Create `src/core/archive.ts` with ArchiveCommand class
- [ ] 1.1.1 Implement change selection (interactive if not provided)
- [ ] 1.1.2 Implement incomplete task checking from tasks.md
- [ ] 1.1.3 Implement confirmation prompt for incomplete tasks
- [ ] 1.1.4 Implement archive move with date prefixing
## 2. CLI Integration
- [ ] 2.1 Add archive command to `src/cli/index.ts`
- [ ] 2.1.1 Import ArchiveCommand
- [ ] 2.1.2 Register command with commander
- [ ] 2.1.3 Add proper error handling
## 3. Error Handling
- [ ] 3.1 Handle missing openspec/changes/ directory
- [ ] 3.2 Handle change not found
- [ ] 3.3 Handle archive target already exists
- [ ] 3.4 Handle user cancellation
## 4. Testing
- [ ] 4.1 Test with fully completed change
- [ ] 4.2 Test with incomplete tasks (warning shown)
- [ ] 4.3 Test interactive selection mode
- [ ] 4.4 Test duplicate archive prevention
## 5. Build and Validation
- [ ] 5.1 Ensure TypeScript compilation succeeds
- [ ] 5.2 Test command execution
@@ -1,9 +0,0 @@
# Implementation Tasks
## 1. Update OpenSpec README
- [ ] 1.1 Add "Start Simple" section after Core Principle
- [ ] 1.2 Add complexity triggers to "When to Create Change Proposals" section
- [ ] 1.3 Update AI workflow guidance to emphasize minimal implementations
## 2. Update CLAUDE.md
- [ ] 2.1 Add complexity management rules to project instructions
@@ -0,0 +1,19 @@
# Add Diff Command to OpenSpec CLI
## Why
Developers need to easily view differences between proposed spec changes and current specs without manually comparing files.
## What Changes
- Add `openspec diff [change-name]` command that shows differences between change specs and current specs
- Compare files in `changes/[change-name]/specs/` with corresponding files in `specs/`
- Display unified diff output showing added/removed/modified lines
- Support colored output for better readability
## Impact
- Affected specs: New capability `cli-diff` will be added
- Affected code:
- `src/cli/index.ts` - Add diff command
- `src/core/diff.ts` - New file with diff logic (~80 lines)
@@ -0,0 +1,77 @@
# CLI Diff Command Specification
## Purpose
The `openspec diff` command provides developers with a visual comparison between proposed spec changes and the current deployed specs.
## Command Syntax
```bash
openspec diff [change-name]
```
## Behavior
### Without Arguments
WHEN running `openspec diff` without arguments
THEN list all available changes in the `changes/` directory (excluding archive)
AND prompt user to select a change
### With Change Name
WHEN running `openspec diff <change-name>`
THEN compare all spec files in `changes/<change-name>/specs/` with corresponding files in `specs/`
### Diff Output
FOR each spec file in the change:
- IF file exists in both locations THEN show unified diff
- IF file only exists in change THEN show as new file (all lines with +)
- IF file only exists in current specs THEN show as deleted (all lines with -)
### Display Format
The diff SHALL use standard unified diff format:
- Lines prefixed with `-` for removed content
- Lines prefixed with `+` for added content
- Lines without prefix for unchanged context
- File headers showing the paths being compared
### Color Support
WHEN terminal supports colors:
- Removed lines displayed in red
- Added lines displayed in green
- File headers displayed in bold
- Context lines in default color
### Error Handling
WHEN specified change doesn't exist THEN display error "Change '<name>' not found"
WHEN no specs directory in change THEN display "No spec changes found for '<name>'"
WHEN changes directory doesn't exist THEN display "No OpenSpec changes directory found"
## Examples
```bash
# View diff for specific change
$ openspec diff add-auth-feature
--- specs/user-auth/spec.md
+++ changes/add-auth-feature/specs/user-auth/spec.md
@@ -10,6 +10,8 @@
Users SHALL authenticate with email and password.
+Users MAY authenticate with OAuth providers.
+
WHEN credentials are valid THEN issue JWT token.
# List all changes and select
$ openspec diff
Available changes:
1. add-auth-feature
2. update-payment-flow
3. add-status-command
Select a change (1-3):
```
@@ -0,0 +1,23 @@
# Implementation Tasks
## 1. Core Implementation
- [x] 1.1 Create `src/core/diff.ts` with diff logic
- [x] 1.2 Implement change directory scanning
- [x] 1.3 Implement file comparison using unified diff format
- [x] 1.4 Add color support for terminal output
## 2. CLI Integration
- [x] 2.1 Add diff command to `src/cli/index.ts`
- [x] 2.2 Implement interactive change selection when no argument provided
- [x] 2.3 Add error handling for missing changes
## 3. Enhancements
- [x] 3.1 Replace with jest-diff for professional diff output
- [x] 3.2 Improve file headers with status and statistics
- [x] 3.3 Add summary view with file counts and line changes
## 4. Testing
- [ ] 4.1 Test diff generation for modified files
- [ ] 4.2 Test handling of new files
- [ ] 4.3 Test handling of deleted files
- [ ] 4.4 Test interactive mode
@@ -0,0 +1,20 @@
# Add List Command to OpenSpec CLI
## Why
Developers need visibility into available changes and their status to understand the project's evolution and pending work.
## What Changes
- Add `openspec list` command that displays all changes in the changes/ directory
- Show each change name with task completion count (e.g., "add-auth: 3/5 tasks")
- Display completion status indicator (✓ for fully complete, progress for partial)
- Skip the archive/ subdirectory to focus on active changes
- Simple table output for easy scanning
## Impact
- Affected specs: New capability `cli-list` will be added
- Affected code:
- `src/cli/index.ts` - Add list command
- `src/core/list.ts` - New file with directory scanning and task parsing (~60 lines)
@@ -0,0 +1,69 @@
# List Command Specification
## Purpose
The `openspec list` command SHALL provide developers with a quick overview of all active changes in the project, showing their names and task completion status.
## Behavior
### Command Execution
WHEN `openspec list` is executed
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
### Task Counting
WHEN parsing a `tasks.md` file
THEN count tasks matching these patterns:
- Completed: Lines containing `- [x]`
- Incomplete: Lines containing `- [ ]`
AND calculate total tasks as the sum of completed and incomplete
### Output Format
WHEN displaying the list
THEN show a table with columns:
- Change name (directory name)
- Task progress (e.g., "3/5 tasks" or "✓ Complete")
- Status indicator:
- `✓` for fully completed changes (all tasks done)
- Progress fraction for partial completion
Example output:
```
Changes:
add-auth-feature 3/5 tasks
update-api-docs ✓ Complete
fix-validation 0/2 tasks
add-list-command 1/4 tasks
```
### Empty State
WHEN no active changes exist (only archive/ or empty changes/)
THEN display: "No active changes found."
### Error Handling
IF a change directory has no `tasks.md` file
THEN display the change with "No tasks" status
IF `openspec/changes/` directory doesn't exist
THEN display error: "No OpenSpec changes directory found. Run 'openspec init' first."
AND exit with code 1
### Sorting
Changes SHALL be displayed in alphabetical order by change name for consistency.
## Why
Developers need a quick way to:
- See what changes are in progress
- Identify which changes are ready to archive
- Understand the overall project evolution status
- Get a bird's-eye view without opening multiple files
This command provides that visibility with minimal effort, following OpenSpec's philosophy of simplicity and clarity.
@@ -0,0 +1,26 @@
# Implementation Tasks
## 1. Core Implementation
- [ ] 1.1 Create `src/core/list.ts` with list logic
- [ ] 1.1.1 Implement directory scanning (exclude archive/)
- [ ] 1.1.2 Implement task counting from tasks.md files
- [ ] 1.1.3 Format output as simple table
- [ ] 1.2 Add list command to CLI in `src/cli/index.ts`
- [ ] 1.2.1 Register `openspec list` command
- [ ] 1.2.2 Connect to list.ts implementation
## 2. Error Handling
- [ ] 2.1 Handle missing openspec/changes/ directory
- [ ] 2.2 Handle changes without tasks.md files
- [ ] 2.3 Handle empty changes directory
## 3. Testing
- [ ] 3.1 Add tests for list functionality
- [ ] 3.1.1 Test with multiple changes
- [ ] 3.1.2 Test with completed changes
- [ ] 3.1.3 Test with no changes
- [ ] 3.1.4 Test error conditions
## 4. Documentation
- [ ] 4.1 Update CLI help text with list command
- [ ] 4.2 Add list command to README if applicable
@@ -0,0 +1,15 @@
# Add @requirement Markers for Requirement Identification
## Why
Specs contain WHEN/THEN patterns that define system requirements, but extracting these programmatically requires brittle regex parsing that may miss edge cases or break with formatting changes.
## What Changes
- Define @requirement marker convention for identifying key requirements in specs
- Each marker includes a brief identifier (e.g., @requirement user-register)
- Markers appear directly before their WHEN/THEN blocks
- Document convention in openspec-conventions spec
## Impact
- Affected specs: openspec-conventions (new)
- Affected code: None initially - enables future tooling
- Breaking changes: None - additive convention only
@@ -0,0 +1,219 @@
# OpenSpec Conventions Specification
## 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.
## Core Principles
The system SHALL follow these principles:
- Specs reflect what IS currently built and deployed
- Changes contain proposals for what SHOULD be changed
- AI drives the documentation process
- Specs are living documentation kept in sync with deployed code
## Directory Structure
WHEN an OpenSpec project is initialized
THEN it SHALL have this structure:
```
openspec/
├── project.md # Project-specific context
├── README.md # AI assistant instructions
├── specs/ # Current deployed capabilities
│ └── [capability]/ # Single, focused capability
│ ├── spec.md # WHAT and WHY
│ └── design.md # HOW (optional, for established patterns)
└── changes/ # Proposed changes
├── [change-name]/ # Descriptive change identifier
│ ├── proposal.md # Why, what, and impact
│ ├── tasks.md # Implementation checklist
│ ├── design.md # Technical decisions (optional)
│ └── specs/ # Complete future state
│ └── [capability]/
│ └── spec.md # Clean markdown (no diff syntax)
└── archive/ # Completed changes
└── YYYY-MM-DD-[name]/
```
## Change Storage Convention
### Future State Storage
WHEN creating a change proposal
THEN store the complete future state of affected specs
AND use clean markdown without diff syntax
The `changes/[name]/specs/` directory SHALL contain:
- Complete spec files as they will exist after the change
- Clean markdown without `+` or `-` prefixes
- All formatting and structure of the final intended state
### Proposal Format
WHEN documenting what changes
THEN the proposal SHALL explicitly describe each change:
```markdown
**[Section or Behavior Name]**
- From: [current state/requirement]
- To: [future state/requirement]
- Reason: [why this change is needed]
- Impact: [breaking/non-breaking, who's affected]
```
This explicit format compensates for not having inline diffs and ensures reviewers understand exactly what will change.
## Change Lifecycle
The change process SHALL follow these states:
1. **Propose**: AI creates change with future state specs and explicit proposal
2. **Review**: Humans review proposal and future state
3. **Approve**: Change is approved for implementation
4. **Implement**: Follow tasks.md checklist (can span multiple PRs)
5. **Deploy**: Changes are deployed to production
6. **Update**: Specs in `specs/` are updated to match deployed reality
7. **Archive**: Change is moved to `archive/YYYY-MM-DD-[name]/`
## Viewing Changes
WHEN reviewing proposed changes
THEN reviewers can compare using:
- GitHub PR diff view when changes are committed
- Command line: `diff -u specs/[capability]/spec.md changes/[name]/specs/[capability]/spec.md`
- Any visual diff tool comparing current vs future state
The system relies on tools to generate diffs rather than storing them.
## Capability Naming
Capabilities SHALL use:
- Verb-noun patterns (e.g., `user-auth`, `payment-capture`)
- Hyphenated lowercase names
- Singular focus (one responsibility per capability)
- No nesting (flat structure under `specs/`)
## When Changes Require Proposals
A proposal SHALL be created for:
- New features or capabilities
- Breaking changes to existing behavior
- Architecture or pattern changes
- Performance optimizations that change behavior
- Security updates affecting access patterns
A proposal is NOT required for:
- Bug fixes restoring intended behavior
- Typos or formatting fixes
- Non-breaking dependency updates
- Adding tests for existing behavior
- Documentation clarifications
## Requirement Markers
### Marker Syntax
@requirement marker-syntax
WHEN writing a requirement in a spec
THEN prefix it with @requirement followed by a brief kebab-case identifier
AND place the marker on the line immediately before the WHEN statement
@requirement marker-identifier
WHEN choosing an identifier for @requirement
THEN use kebab-case (lowercase with hyphens)
AND keep it brief but descriptive (2-4 words)
AND ensure it's unique within the spec
@requirement marker-placement
WHEN adding @requirement markers to a spec
THEN place them in the ## Behavior or ## Behaviors section
AND ensure each WHEN/THEN block has exactly one marker
AND maintain a blank line after each THEN block for readability
### Examples
@requirement valid-marker-example
WHEN a spec includes properly formatted markers
THEN tools can extract and identify requirements programmatically
AND the spec remains human-readable
Example of correct usage:
```markdown
## Behavior
@requirement user-register
WHEN user registers with valid email
THEN create account and send confirmation
@requirement user-login
WHEN user logs in with correct credentials
THEN return JWT token with user data
@requirement invalid-credentials
WHEN user provides invalid credentials
THEN return 401 unauthorized error
```
@requirement invalid-marker-detection
WHEN a requirement lacks an @requirement marker
THEN tools should gracefully skip it
AND optionally warn about unmarked requirements
### Edge Cases
@requirement multiline-when-then
WHEN a WHEN or THEN clause spans multiple lines
THEN the @requirement marker still goes on the line before WHEN
AND the entire block is considered part of that requirement
@requirement multiple-then-clauses
WHEN a requirement has multiple THEN clauses using AND
THEN treat them as part of the same requirement
AND use a single @requirement marker for the entire block
@requirement nested-conditions
WHEN requirements have nested conditions or complex logic
THEN keep the @requirement marker simple
AND let the WHEN/THEN content contain the complexity
## Spec Structure
@requirement spec-file-location
WHEN creating a spec file
THEN place it in openspec/specs/[capability-name]/spec.md
AND use kebab-case for the capability name
@requirement spec-sections
WHEN structuring a spec
THEN include these sections in order:
- # [Capability Name] Specification
- ## Purpose (brief description)
- ## Behavior or ## Behaviors (with @requirement markers)
- ## Examples (optional, for complex requirements)
## Benefits of Requirement Markers
@requirement tooling-extraction
WHEN tools need to extract requirements from specs
THEN they can parse @requirement markers reliably
AND avoid complex regex patterns for WHEN/THEN extraction
@requirement requirement-counting
WHEN displaying change summaries
THEN tools can count requirements by counting @requirement markers
AND show accurate requirement counts per spec
@requirement requirement-referencing
WHEN documenting or discussing specific requirements
THEN use the @requirement identifier for clear reference
AND maintain consistency across documentation
## Why This Approach
Clean future state storage provides:
- **Readability**: No diff syntax pollution
- **AI-compatibility**: Standard markdown that AI tools understand
- **Simplicity**: No special parsing or processing needed
- **Tool-agnostic**: Any diff tool can show changes
- **Clear intent**: Explicit proposals document reasoning
@@ -0,0 +1,24 @@
# Implementation Tasks
## 1. Define Convention
- [ ] 1.1 Document @requirement marker syntax
- [ ] 1.2 Define identifier naming guidelines
- [ ] 1.3 Specify marker placement rules
- [ ] 1.4 Add examples of proper usage
## 2. Create Specification
- [ ] 2.1 Write openspec-conventions spec
- [ ] 2.2 Include requirement marker section
- [ ] 2.3 Add good and bad examples
- [ ] 2.4 Document edge cases
## 3. Update Existing Specs
- [ ] 3.1 Add @requirement markers to cli-init spec
- [ ] 3.2 Add @requirement markers to cli-update spec
- [ ] 3.3 Add @requirement markers to cli-view spec
- [ ] 3.4 Review and update any other existing specs
## 4. Documentation
- [ ] 4.1 Update README with marker convention
- [ ] 4.2 Add marker usage to CLAUDE.md
- [ ] 4.3 Create examples for AI assistants
@@ -0,0 +1,9 @@
# Implementation Tasks
## 1. Update OpenSpec README
- [x] 1.1 Add "Start Simple" section after Core Principle
- [x] 1.2 Add complexity triggers to "When to Create Change Proposals" section
- [x] 1.3 Update AI workflow guidance to emphasize minimal implementations
## 2. Update CLAUDE.md
- [x] 2.1 Add complexity management rules to project instructions
+59
View File
@@ -0,0 +1,59 @@
# 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
### Update Behavior
The update command SHALL update OpenSpec instruction files to the latest templates.
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)
- Update the OpenSpec-managed block in `CLAUDE.md` using markers
- Preserve user content outside markers
- Create `CLAUDE.md` if missing
- Display ASCII-safe success message: "Updated OpenSpec instructions"
### Prerequisites
The command SHALL require:
- An existing `openspec` directory (created by `openspec init`)
IF the `openspec` directory does not exist THEN:
- Display error: "No OpenSpec directory found. Run 'openspec init' first."
- Exit with code 1
### File Handling
The update command SHALL:
- Completely replace `openspec/README.md` with the latest template
- Update only the OpenSpec-managed block in `CLAUDE.md` using markers
- Use the default directory name `openspec`
- Be idempotent (repeated runs have no additional effect)
## Edge Cases
### File Permissions
IF file write fails THEN let the error bubble up naturally with file path.
### Missing CLAUDE.md
IF CLAUDE.md doesn't exist THEN create it with the template content.
### Custom Directory Name
Not supported in this change. 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
- See clear confirmation of the update
The update process SHALL be:
- Simple and fast (no version checking)
- Predictable (same result every time)
- Self-contained (no network required)
+2
View File
@@ -53,7 +53,9 @@
},
"dependencies": {
"@inquirer/prompts": "^7.8.0",
"chalk": "^5.5.0",
"commander": "^14.0.0",
"jest-diff": "^30.0.5",
"ora": "^8.2.0"
}
}
+86
View File
@@ -11,9 +11,15 @@ importers:
'@inquirer/prompts':
specifier: ^7.8.0
version: 7.8.0(@types/node@24.2.0)
chalk:
specifier: ^5.5.0
version: 5.5.0
commander:
specifier: ^14.0.0
version: 14.0.0
jest-diff:
specifier: ^30.0.5
version: 30.0.5
ora:
specifier: ^8.2.0
version: 8.2.0
@@ -310,6 +316,18 @@ packages:
'@types/node':
optional: true
'@jest/diff-sequences@30.0.1':
resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/get-type@30.0.1':
resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/schemas@30.0.5':
resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jridgewell/sourcemap-codec@1.5.4':
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
@@ -416,6 +434,9 @@ packages:
cpu: [x64]
os: [win32]
'@sinclair/typebox@0.34.38':
resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==}
'@types/chai@5.2.2':
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
@@ -478,6 +499,10 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -490,6 +515,10 @@ packages:
resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==}
engines: {node: '>=18'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chalk@5.5.0:
resolution: {integrity: sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
@@ -585,6 +614,10 @@ packages:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -605,6 +638,10 @@ packages:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
jest-diff@30.0.5:
resolution: {integrity: sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
@@ -668,6 +705,13 @@ packages:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
pretty-format@30.0.5:
resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -724,6 +768,10 @@ packages:
strip-literal@3.0.0:
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -1048,6 +1096,14 @@ snapshots:
optionalDependencies:
'@types/node': 24.2.0
'@jest/diff-sequences@30.0.1': {}
'@jest/get-type@30.0.1': {}
'@jest/schemas@30.0.5':
dependencies:
'@sinclair/typebox': 0.34.38
'@jridgewell/sourcemap-codec@1.5.4': {}
'@polka/url@1.0.0-next.29': {}
@@ -1112,6 +1168,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.46.2':
optional: true
'@sinclair/typebox@0.34.38': {}
'@types/chai@5.2.2':
dependencies:
'@types/deep-eql': 4.0.2
@@ -1189,6 +1247,8 @@ snapshots:
dependencies:
color-convert: 2.0.1
ansi-styles@5.2.0: {}
assertion-error@2.0.1: {}
cac@6.7.14: {}
@@ -1201,6 +1261,11 @@ snapshots:
loupe: 3.2.0
pathval: 2.0.1
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
chalk@5.5.0: {}
chardet@0.7.0: {}
@@ -1289,6 +1354,8 @@ snapshots:
get-east-asian-width@1.3.0: {}
has-flag@4.0.0: {}
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -1301,6 +1368,13 @@ snapshots:
is-unicode-supported@2.1.0: {}
jest-diff@30.0.5:
dependencies:
'@jest/diff-sequences': 30.0.1
'@jest/get-type': 30.0.1
chalk: 4.1.2
pretty-format: 30.0.5
js-tokens@9.0.1: {}
log-symbols@6.0.0:
@@ -1356,6 +1430,14 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
pretty-format@30.0.5:
dependencies:
'@jest/schemas': 30.0.5
ansi-styles: 5.2.0
react-is: 18.3.1
react-is@18.3.1: {}
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -1431,6 +1513,10 @@ snapshots:
dependencies:
js-tokens: 9.0.1
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
+15
View File
@@ -4,6 +4,7 @@ import path from 'path';
import { promises as fs } from 'fs';
import { InitCommand } from '../core/init.js';
import { UpdateCommand } from '../core/update.js';
import { DiffCommand } from '../core/diff.js';
const program = new Command();
@@ -60,4 +61,18 @@ program
}
});
program
.command('diff [change-name]')
.description('Show differences between proposed spec changes and current specs')
.action(async (changeName?: string) => {
try {
const diffCommand = new DiffCommand();
await diffCommand.execute(changeName);
} catch (error) {
console.log(); // Empty line for spacing
ora().fail(`Error: ${(error as Error).message}`);
process.exit(1);
}
});
program.parse();
+179
View File
@@ -0,0 +1,179 @@
import { promises as fs } from 'fs';
import path from 'path';
import chalk from 'chalk';
import { diffStringsUnified } from 'jest-diff';
import { select } from '@inquirer/prompts';
// Constants
const ARCHIVE_DIR = 'archive';
const MARKDOWN_EXT = '.md';
const OPENSPEC_DIR = 'openspec';
const CHANGES_DIR = 'changes';
const SPECS_DIR = 'specs';
export class DiffCommand {
private filesChanged: number = 0;
private linesAdded: number = 0;
private linesRemoved: number = 0;
async execute(changeName?: string): Promise<void> {
const changesDir = path.join(process.cwd(), OPENSPEC_DIR, CHANGES_DIR);
try {
await fs.access(changesDir);
} catch {
throw new Error('No OpenSpec changes directory found');
}
if (!changeName) {
changeName = await this.selectChange(changesDir);
if (!changeName) return;
}
const changeDir = path.join(changesDir, changeName);
try {
await fs.access(changeDir);
} catch {
throw new Error(`Change '${changeName}' not found`);
}
const changeSpecsDir = path.join(changeDir, SPECS_DIR);
try {
await fs.access(changeSpecsDir);
} catch {
console.log(`No spec changes found for '${changeName}'`);
return;
}
// Reset counters
this.filesChanged = 0;
this.linesAdded = 0;
this.linesRemoved = 0;
await this.showDiffs(changeSpecsDir);
// Show summary
if (this.filesChanged > 0) {
console.log(chalk.bold(`\n📊 Summary: ${this.filesChanged} file(s) changed, ${chalk.green(`+${this.linesAdded}`)} ${chalk.red(`-${this.linesRemoved}`)}`));
}
}
private async selectChange(changesDir: string): Promise<string | undefined> {
const entries = await fs.readdir(changesDir, { withFileTypes: true });
const changes = entries
.filter(entry => entry.isDirectory() && entry.name !== ARCHIVE_DIR)
.map(entry => entry.name);
if (changes.length === 0) {
console.log('No changes found');
return undefined;
}
console.log('Available changes:');
const choices = changes.map((name) => ({
name: name,
value: name
}));
const answer = await select({
message: 'Select a change',
choices
});
return answer;
}
private async showDiffs(changeSpecsDir: string): Promise<void> {
const currentSpecsDir = path.join(process.cwd(), OPENSPEC_DIR, SPECS_DIR);
await this.walkAndDiff(changeSpecsDir, currentSpecsDir, '');
}
private async walkAndDiff(changeDir: string, currentDir: string, relativePath: string): Promise<void> {
const entries = await fs.readdir(path.join(changeDir, relativePath), { withFileTypes: true });
for (const entry of entries) {
const entryPath = path.join(relativePath, entry.name);
if (entry.isDirectory()) {
await this.walkAndDiff(changeDir, currentDir, entryPath);
} else if (entry.isFile() && entry.name.endsWith(MARKDOWN_EXT)) {
await this.diffFile(
path.join(changeDir, entryPath),
path.join(currentDir, entryPath),
entryPath
);
}
}
}
private async diffFile(changePath: string, currentPath: string, displayPath: string): Promise<void> {
let changeContent = '';
let currentContent = '';
let isNewFile = false;
let isDeleted = false;
try {
changeContent = await fs.readFile(changePath, 'utf-8');
} catch {
changeContent = '';
}
try {
currentContent = await fs.readFile(currentPath, 'utf-8');
} catch {
currentContent = '';
isNewFile = true;
}
if (changeContent === currentContent) {
return;
}
if (changeContent === '' && currentContent !== '') {
isDeleted = true;
}
// Enhanced header with file status
console.log(chalk.bold.cyan(`\n${'═'.repeat(60)}`));
console.log(chalk.bold.cyan(`📄 ${displayPath}`));
if (isNewFile) {
console.log(chalk.green(` Status: NEW FILE`));
} else if (isDeleted) {
console.log(chalk.red(` Status: DELETED`));
} else {
console.log(chalk.yellow(` Status: MODIFIED`));
}
// Use jest-diff for the actual diff with custom options
const diffOptions = {
aAnnotation: 'Current',
bAnnotation: 'Proposed',
aColor: chalk.red,
bColor: chalk.green,
commonColor: chalk.gray,
contextLines: 3,
expand: false,
includeChangeCounts: true,
};
const diff = diffStringsUnified(currentContent, changeContent, diffOptions);
// Count lines for statistics (approximate)
const addedLines = (diff.match(/^\+[^+]/gm) || []).length;
const removedLines = (diff.match(/^-[^-]/gm) || []).length;
console.log(chalk.gray(` Lines: ${chalk.green(`+${addedLines}`)} ${chalk.red(`-${removedLines}`)}`));
console.log(chalk.bold.cyan(`${'─'.repeat(60)}\n`));
// Display the diff
console.log(diff);
// Update counters
this.filesChanged++;
this.linesAdded += addedLines;
this.linesRemoved += removedLines;
}
}
+20 -1
View File
@@ -4,4 +4,23 @@ This document provides instructions for AI coding assistants on how to use OpenS
This project uses OpenSpec for spec-driven development. Specifications are the source of truth.
See @openspec/README.md for detailed conventions and guidelines.`;
See @openspec/README.md for detailed conventions and guidelines.
## Complexity Management
**Default to minimal solutions:**
- Propose <100 lines of new code for features
- Prefer single-file implementations until proven insufficient
- Avoid frameworks, abstractions, and optimizations without clear justification
- Choose boring, well-understood patterns over novel approaches
**Question requests for complexity:**
- Caching? → Ask for performance data and targets
- New framework? → Suggest plain code first
- Extra layers? → Start with the thinnest viable design
**Justify complexity with data:**
- Performance metrics showing current solution is too slow
- Concrete scale requirements (e.g., >1000 users, >100MB data)
- Multiple proven use cases requiring an abstraction
`;
+26 -1
View File
@@ -10,6 +10,23 @@ OpenSpec is an AI-native system for change-driven development where:
- **AI drives the process** - You generate proposals, humans review and approve
- **Specs are living documentation** - Always kept in sync with deployed code
## Start Simple
**Default to minimal implementations:**
- New features should be <100 lines of code initially
- Use the simplest solution that works
- Avoid premature optimization (no caching, parallelization, or complex patterns without proven need)
- Choose boring technology over cutting-edge solutions
**Complexity triggers** - Only add complexity when you have:
- **Performance data** showing current solution is too slow
- **Scale requirements** with specific numbers (>1000 users, >100MB data)
- **Multiple use cases** requiring the same abstraction
- **Regulatory compliance** mandating specific patterns
- **Security threats** that simple solutions cannot address
When triggered, document the specific justification in your change proposal.
## Directory Structure
\`\`\`
@@ -72,6 +89,11 @@ Before any task:
- Adding tests for existing behavior
- Documentation fixes
**Complexity assessment:**
- If your solution requires >100 lines of new code, justify the complexity
- If adding dependencies, frameworks, or architectural patterns, document why simpler alternatives won't work
- Default to single-file implementations until proven insufficient
### 3. Creating a Change Proposal
When a user requests a significant change:
@@ -383,10 +405,12 @@ Progress communication:
- "Implementing approved changes..."
### For AI Assistants
- **Bias toward simplicity** - Propose the minimal solution that works
- Use your exploration tools liberally before proposing
- Batch operations for efficiency
- Communicate your progress
- It's OK to revise proposals based on discoveries
- **Question complexity** - If your solution feels complex, simplify first
## Edge Case Handling
@@ -442,7 +466,8 @@ Proposal REQUIRED if:
- Specs must always reflect deployed reality
- Changes are proposed, not imposed
- Impact analysis prevents surprises
- The simplicity is the power - just markdown files
- Simplicity is the power - just markdown files, minimal solutions
- Start simple, add complexity only when justified
By following these conventions, you enable true spec-driven development where documentation stays current, changes are traceable, and evolution is intentional.
`;