@fission-ai/openspec 0.1.0 β 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +240 -76
- package/dist/cli/index.js +14 -14
- package/dist/core/config.js +1 -1
- package/dist/core/configurators/slash/base.d.ts +17 -0
- package/dist/core/configurators/slash/base.js +61 -0
- package/dist/core/configurators/slash/claude.d.ts +9 -0
- package/dist/core/configurators/slash/claude.js +37 -0
- package/dist/core/configurators/slash/cursor.d.ts +9 -0
- package/dist/core/configurators/slash/cursor.js +37 -0
- package/dist/core/configurators/slash/registry.d.ts +8 -0
- package/dist/core/configurators/slash/registry.js +21 -0
- package/dist/core/init.js +6 -1
- package/dist/core/templates/agents-template.d.ts +2 -0
- package/dist/core/templates/agents-template.js +455 -0
- package/dist/core/templates/claude-template.d.ts +1 -1
- package/dist/core/templates/claude-template.js +14 -4
- package/dist/core/templates/index.d.ts +3 -0
- package/dist/core/templates/index.js +7 -3
- package/dist/core/templates/slash-command-templates.d.ts +4 -0
- package/dist/core/templates/slash-command-templates.js +40 -0
- package/dist/core/update.js +31 -5
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +148 -0
- package/dist/utils/file-system.d.ts +1 -0
- package/dist/utils/file-system.js +16 -0
- package/package.json +1 -2
- package/dist/core/diff.d.ts +0 -11
- package/dist/core/diff.js +0 -193
- package/dist/core/templates/readme-template.d.ts +0 -2
- package/dist/core/templates/readme-template.js +0 -519
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OpenSpec Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -1,119 +1,283 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/Fission-AI/OpenSpec">
|
|
3
|
+
<picture>
|
|
4
|
+
<source srcset="assets/openspec_pixel_dark.svg" media="(prefers-color-scheme: dark)">
|
|
5
|
+
<source srcset="assets/openspec_pixel_light.svg" media="(prefers-color-scheme: light)">
|
|
6
|
+
<img src="assets/openspec_pixel_light.svg" alt="OpenSpec logo" height="64">
|
|
7
|
+
</picture>
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
</p>
|
|
11
|
+
<p align="center">Spec-driven development for AI coding assistants.</p>
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml/badge.svg" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@fission-ai/openspec"><img alt="npm version" src="https://img.shields.io/npm/v/@fission-ai/openspec?style=flat-square" /></a>
|
|
15
|
+
<a href="https://nodejs.org/"><img alt="node version" src="https://img.shields.io/node/v/@fission-ai/openspec?style=flat-square" /></a>
|
|
16
|
+
<a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" /></a>
|
|
17
|
+
<a href="https://conventionalcommits.org"><img alt="Conventional Commits" src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=flat-square" /></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="assets/openspec_dashboard.png" alt="OpenSpec dashboard preview" width="90%">
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
Follow <a href="https://x.com/0xTab">@0xTab on X</a> for updates.
|
|
26
|
+
</p>
|
|
27
|
+
|
|
1
28
|
# OpenSpec
|
|
2
29
|
|
|
3
|
-
|
|
30
|
+
**Supported AI Tools:** β
Claude Code | π Cursor (coming soon) | β
AGENTS.md instructions
|
|
31
|
+
|
|
32
|
+
Create **alignment** between humans and AI coding assistants through spec-driven development. **No API keys required.**
|
|
33
|
+
|
|
34
|
+
OpenSpec ensures you and your AI assistant agree on what to build before any code is written. By discussing and refining specifications first, you bring determinism to AI code generation, getting exactly what you want, not what the AI thinks you might want.
|
|
35
|
+
|
|
36
|
+
## Why OpenSpec?
|
|
37
|
+
|
|
38
|
+
**The Problem:** AI coding assistants are powerful but unpredictable. Without clear specifications, they generate code based on assumptions, often missing requirements or adding unwanted features. Teams waste time in review cycles because humans and AI aren't aligned on what to build.
|
|
39
|
+
|
|
40
|
+
**The Solution:** OpenSpec creates alignment BEFORE code is written:
|
|
41
|
+
- **Human-AI Alignment** - You and your AI agree on specifications before implementation
|
|
42
|
+
- **Deterministic, Predictable Output** - Clear specs lead to reliable, repeatable code generation
|
|
43
|
+
- **Team Alignment via Spec Reviews** - Everyone reviews intentions, not code surprises
|
|
44
|
+
- **Clear Feature Scope** - Know exactly what you're buildingβand what you're not
|
|
45
|
+
- **Progress Tracking** - See what's proposed, in progress, or completed at a glance
|
|
46
|
+
- **Living Documentation** - Specs evolve with your code as a natural byproduct
|
|
47
|
+
- **Universal Tool Support** - Works with any AI assistant (Claude Code, Cursor, and more)
|
|
48
|
+
- **No API Keys Required** - Integrates through context rules, not external services
|
|
49
|
+
|
|
50
|
+
## How It Works
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
βββββββββββββββ βββββββββββββββ ββββββββββββββββ
|
|
54
|
+
β SPECS β β CHANGES β β ARCHIVE β
|
|
55
|
+
β (Truth) βββββββββ (Proposals) ββββββββΆβ (Completed) β
|
|
56
|
+
βββββββββββββββ βββββββββββββββ ββββββββββββββββ
|
|
57
|
+
β² β β
|
|
58
|
+
β βΌ β
|
|
59
|
+
β βββββββββββββββ β
|
|
60
|
+
βββββββββββββββββ CODE βββββββββββββββββ
|
|
61
|
+
βββββββββββββββ
|
|
62
|
+
|
|
63
|
+
1. SPECS define current capabilities (what IS built)
|
|
64
|
+
2. CHANGES propose modifications using deltas (what SHOULD change)
|
|
65
|
+
3. CODE implements the changes following tasks
|
|
66
|
+
4. ARCHIVE preserves completed changes after deployment
|
|
67
|
+
```
|
|
4
68
|
|
|
5
69
|
## Installation
|
|
6
70
|
|
|
71
|
+
### Prerequisites
|
|
72
|
+
|
|
73
|
+
- Node.js >= 20.19.0
|
|
74
|
+
|
|
75
|
+
### Install OpenSpec
|
|
76
|
+
|
|
77
|
+
Install globally:
|
|
78
|
+
|
|
7
79
|
```bash
|
|
8
|
-
npm install -g openspec
|
|
80
|
+
npm install -g @fission-ai/openspec
|
|
9
81
|
```
|
|
10
82
|
|
|
11
|
-
##
|
|
83
|
+
## Getting Started
|
|
84
|
+
|
|
85
|
+
### 1. Initialize OpenSpec in Your Project
|
|
12
86
|
|
|
13
87
|
```bash
|
|
14
|
-
#
|
|
88
|
+
# Navigate to your project
|
|
89
|
+
cd my-project
|
|
90
|
+
|
|
91
|
+
# Initialize OpenSpec
|
|
15
92
|
openspec init
|
|
16
93
|
|
|
17
|
-
#
|
|
18
|
-
|
|
94
|
+
# Select your AI tool (more coming soon!):
|
|
95
|
+
# "Which AI tool do you use?"
|
|
96
|
+
# > Claude Code
|
|
97
|
+
# Cursor (coming soon)
|
|
98
|
+
|
|
99
|
+
# This creates:
|
|
100
|
+
# openspec/
|
|
101
|
+
# βββ specs/ # Current specifications (truth)
|
|
102
|
+
# βββ changes/ # Proposed changes
|
|
103
|
+
# βββ AGENTS.md # AI instructions for your tool
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Create Your First Change
|
|
107
|
+
|
|
108
|
+
Jump straight into creating a change proposal with your AI assistant (works with Claude Code, Cursor, or any AI tool):
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+
// Quick win - Add a simple new feature:
|
|
112
|
+
You: "I want to add a user profile API endpoint.
|
|
113
|
+
Please create an OpenSpec change proposal for this."
|
|
19
114
|
|
|
20
|
-
|
|
21
|
-
openspec
|
|
22
|
-
|
|
115
|
+
AI: "I'll create an OpenSpec change proposal for the user profile API..."
|
|
116
|
+
*Creates openspec/changes/add-user-profile-api/ with:*
|
|
117
|
+
- proposal.md (why this feature is needed)
|
|
118
|
+
- tasks.md (implementation checklist)
|
|
119
|
+
- design.md (API design decisions)
|
|
120
|
+
- specs/user-profile/spec.md (new requirements)
|
|
23
121
|
|
|
24
|
-
|
|
25
|
-
openspec diff [change-name]
|
|
122
|
+
You: "The proposal looks good. Let's implement it."
|
|
26
123
|
|
|
27
|
-
|
|
28
|
-
|
|
124
|
+
AI: "Following the tasks in openspec/changes/add-user-profile-api/tasks.md:
|
|
125
|
+
Task 1.1: Create user profile model..."
|
|
126
|
+
*Implements each task systematically*
|
|
29
127
|
```
|
|
30
128
|
|
|
31
|
-
|
|
129
|
+
### 3. Track Your Work
|
|
32
130
|
|
|
33
|
-
|
|
131
|
+
```bash
|
|
132
|
+
# View active changes (what's being worked on)
|
|
133
|
+
openspec list
|
|
34
134
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- `openspec/README.md` with OpenSpec instructions
|
|
38
|
-
- AI tool configuration files (based on your selection)
|
|
135
|
+
# Validate your changes are properly formatted
|
|
136
|
+
openspec validate add-2fa --strict
|
|
39
137
|
|
|
40
|
-
|
|
138
|
+
# After deployment, archive the completed change
|
|
139
|
+
openspec archive add-2fa
|
|
140
|
+
# This moves the change to archive/ and updates specs/
|
|
141
|
+
```
|
|
41
142
|
|
|
42
|
-
|
|
143
|
+
## Common Commands
|
|
43
144
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
145
|
+
```bash
|
|
146
|
+
# Most used:
|
|
147
|
+
openspec list # See what changes you're working on
|
|
148
|
+
openspec archive <change> # Mark a change as complete after deployment
|
|
48
149
|
|
|
49
|
-
|
|
150
|
+
# Also useful:
|
|
151
|
+
openspec validate <change> # Check formatting before committing
|
|
152
|
+
openspec show <change> # View change details
|
|
153
|
+
```
|
|
50
154
|
|
|
51
|
-
|
|
155
|
+
## Example: How AI Creates OpenSpec Files
|
|
52
156
|
|
|
53
|
-
|
|
157
|
+
When you ask your AI assistant to "add two-factor authentication", it creates:
|
|
54
158
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
159
|
+
```
|
|
160
|
+
openspec/
|
|
161
|
+
βββ specs/
|
|
162
|
+
β βββ auth/
|
|
163
|
+
β βββ spec.md # Current auth spec (if exists)
|
|
164
|
+
βββ changes/
|
|
165
|
+
βββ add-2fa/ # AI creates this entire structure
|
|
166
|
+
βββ proposal.md # Why and what changes
|
|
167
|
+
βββ tasks.md # Implementation checklist
|
|
168
|
+
βββ design.md # Technical decisions (optional)
|
|
169
|
+
βββ specs/
|
|
170
|
+
βββ auth/
|
|
171
|
+
βββ spec.md # Delta showing additions
|
|
172
|
+
```
|
|
66
173
|
|
|
67
|
-
### `openspec
|
|
174
|
+
### AI-Generated Spec (created in `openspec/specs/auth/spec.md`):
|
|
68
175
|
|
|
69
|
-
|
|
176
|
+
```markdown
|
|
177
|
+
# Auth Specification
|
|
70
178
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- Text mode: prints raw `proposal.md` content
|
|
74
|
-
- JSON mode (`--json`): `{ id, title, deltaCount, deltas }`
|
|
75
|
-
- Filtering is JSON-only: `--deltas-only` (alias: `--requirements-only`, deprecated)
|
|
76
|
-
- `openspec change list`
|
|
77
|
-
- Prints IDs only by default
|
|
78
|
-
- Use `--long` to include `title` and counts `[deltas N] [tasks x/y]`
|
|
79
|
-
- `openspec change validate <change-id>`
|
|
80
|
-
- Text: human-readable result
|
|
81
|
-
- `--json` for structured report
|
|
179
|
+
## Purpose
|
|
180
|
+
Authentication and session management.
|
|
82
181
|
|
|
83
|
-
|
|
182
|
+
## Requirements
|
|
183
|
+
### Requirement: User Authentication
|
|
184
|
+
The system SHALL issue a JWT on successful login.
|
|
84
185
|
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
186
|
+
#### Scenario: Valid credentials
|
|
187
|
+
- WHEN a user submits valid credentials
|
|
188
|
+
- THEN a JWT is returned
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### AI-Generated Change Delta (created in `openspec/changes/add-2fa/specs/auth/spec.md`):
|
|
89
192
|
|
|
90
|
-
|
|
193
|
+
```markdown
|
|
194
|
+
# Delta for Auth
|
|
91
195
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- Updates specs to reflect the new state
|
|
96
|
-
- Use `--skip-specs` to archive without updating specs (for abandoned changes)
|
|
196
|
+
## ADDED Requirements
|
|
197
|
+
### Requirement: Two-Factor Authentication
|
|
198
|
+
The system MUST require a second factor during login.
|
|
97
199
|
|
|
98
|
-
|
|
200
|
+
#### Scenario: OTP required
|
|
201
|
+
- WHEN a user submits valid credentials
|
|
202
|
+
- THEN an OTP challenge is required
|
|
203
|
+
```
|
|
99
204
|
|
|
100
|
-
|
|
205
|
+
### AI-Generated Tasks (created in `openspec/changes/add-2fa/tasks.md`):
|
|
101
206
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
207
|
+
```markdown
|
|
208
|
+
## 1. Database Setup
|
|
209
|
+
- [ ] 1.1 Add OTP secret column to users table
|
|
210
|
+
- [ ] 1.2 Create OTP verification logs table
|
|
211
|
+
|
|
212
|
+
## 2. Backend Implementation
|
|
213
|
+
- [ ] 2.1 Add OTP generation endpoint
|
|
214
|
+
- [ ] 2.2 Modify login flow to require OTP
|
|
215
|
+
- [ ] 2.3 Add OTP verification endpoint
|
|
216
|
+
|
|
217
|
+
## 3. Frontend Updates
|
|
218
|
+
- [ ] 3.1 Create OTP input component
|
|
219
|
+
- [ ] 3.2 Update login flow UI
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Important:** You don't create these files manually. Your AI assistant generates them based on your requirements and the existing codebase.
|
|
223
|
+
|
|
224
|
+
## Understanding OpenSpec Files
|
|
225
|
+
|
|
226
|
+
### Delta Format
|
|
227
|
+
|
|
228
|
+
Deltas are "patches" that show how specs change:
|
|
229
|
+
|
|
230
|
+
- **`## ADDED Requirements`** - New capabilities
|
|
231
|
+
- **`## MODIFIED Requirements`** - Changed behavior (include complete updated text)
|
|
232
|
+
- **`## REMOVED Requirements`** - Deprecated features
|
|
233
|
+
|
|
234
|
+
**Format requirements:**
|
|
235
|
+
- Use `### Requirement: <name>` for headers
|
|
236
|
+
- Every requirement needs at least one `#### Scenario:` block
|
|
237
|
+
- Use SHALL/MUST in requirement text
|
|
106
238
|
|
|
107
|
-
## Contributing
|
|
108
239
|
|
|
109
|
-
|
|
240
|
+
## Why OpenSpec Works
|
|
110
241
|
|
|
111
|
-
|
|
242
|
+
OpenSpec creates **alignment** between you and your AI coding assistant:
|
|
243
|
+
|
|
244
|
+
1. **You describe** what you want to build
|
|
245
|
+
2. **AI creates specs** before writing any code
|
|
246
|
+
3. **You review and adjust** the specifications
|
|
247
|
+
4. **AI implements** exactly what was specified
|
|
248
|
+
5. **Everyone understands** what's being built through clear specs
|
|
249
|
+
|
|
250
|
+
**True Interoperability:** OpenSpec is designed to be universal. No API keys, no vendor lock-in. It works by adding context rules to ANY AI coding tool - whether you use Claude Code today, switch to Cursor tomorrow, or adopt the next breakthrough AI assistant. Your specs remain portable and your workflow stays consistent.
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
## How OpenSpec Compares
|
|
254
|
+
|
|
255
|
+
### vs. Kiro.dev
|
|
256
|
+
OpenSpec groups all changes for a feature in one place (`openspec/changes/feature-name/`), making it easy to track what needs to be done. Kiro spreads changes across multiple spec folders, making feature tracking harder.
|
|
257
|
+
|
|
258
|
+
### vs. No Specs
|
|
259
|
+
Without specs, AI coding assistants generate code based on vague prompts, often missing requirements or adding unwanted features. OpenSpec ensures alignment before any code is written.
|
|
260
|
+
|
|
261
|
+
## Team Adoption
|
|
262
|
+
|
|
263
|
+
### Getting Started with Your Team
|
|
264
|
+
|
|
265
|
+
1. **Initialize OpenSpec** - Run `openspec init` in your project
|
|
266
|
+
2. **Start with new features** - Use OpenSpec for your next change proposal
|
|
267
|
+
3. **Build incrementally** - Each new feature adds to your spec library
|
|
268
|
+
4. **Future capability** - We're working on tools to generate specs from existing code
|
|
269
|
+
|
|
270
|
+
**Tool Freedom:** Your team can use different AI assistants. One developer might use Claude Code while another uses Cursor - OpenSpec keeps everyone aligned through shared specifications. Run `openspec update` to configure for any supported tool without affecting others.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
## Contributing
|
|
112
274
|
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
275
|
+
- Install dependencies: `npm install`
|
|
276
|
+
- Build: `npm run build`
|
|
277
|
+
- Test: `npm test`
|
|
278
|
+
- Develop CLI locally: `npm run dev` or `npm run dev:cli`
|
|
279
|
+
- Conventional commits (one-line): `type(scope): subject`
|
|
116
280
|
|
|
117
281
|
## License
|
|
118
282
|
|
|
119
|
-
MIT
|
|
283
|
+
MIT
|
package/dist/cli/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import path from 'path';
|
|
|
5
5
|
import { promises as fs } from 'fs';
|
|
6
6
|
import { InitCommand } from '../core/init.js';
|
|
7
7
|
import { UpdateCommand } from '../core/update.js';
|
|
8
|
-
import { DiffCommand } from '../core/diff.js';
|
|
9
8
|
import { ListCommand } from '../core/list.js';
|
|
10
9
|
import { ArchiveCommand } from '../core/archive.js';
|
|
10
|
+
import { ViewCommand } from '../core/view.js';
|
|
11
11
|
import { registerSpecCommand } from '../commands/spec.js';
|
|
12
12
|
import { ChangeCommand } from '../commands/change.js';
|
|
13
13
|
import { ValidateCommand } from '../commands/validate.js';
|
|
@@ -78,12 +78,15 @@ program
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
program
|
|
81
|
-
.command('
|
|
82
|
-
.description('
|
|
83
|
-
.
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('List items (changes by default). Use --specs to list specs.')
|
|
83
|
+
.option('--specs', 'List specs instead of changes')
|
|
84
|
+
.option('--changes', 'List changes explicitly (default)')
|
|
85
|
+
.action(async (options) => {
|
|
84
86
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
+
const listCommand = new ListCommand();
|
|
88
|
+
const mode = options?.specs ? 'specs' : 'changes';
|
|
89
|
+
await listCommand.execute('.', mode);
|
|
87
90
|
}
|
|
88
91
|
catch (error) {
|
|
89
92
|
console.log(); // Empty line for spacing
|
|
@@ -92,15 +95,12 @@ program
|
|
|
92
95
|
}
|
|
93
96
|
});
|
|
94
97
|
program
|
|
95
|
-
.command('
|
|
96
|
-
.description('
|
|
97
|
-
.
|
|
98
|
-
.option('--changes', 'List changes explicitly (default)')
|
|
99
|
-
.action(async (options) => {
|
|
98
|
+
.command('view')
|
|
99
|
+
.description('Display an interactive dashboard of specs and changes')
|
|
100
|
+
.action(async () => {
|
|
100
101
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
await listCommand.execute('.', mode);
|
|
102
|
+
const viewCommand = new ViewCommand();
|
|
103
|
+
await viewCommand.execute('.');
|
|
104
104
|
}
|
|
105
105
|
catch (error) {
|
|
106
106
|
console.log(); // Empty line for spacing
|
package/dist/core/config.js
CHANGED
|
@@ -5,7 +5,7 @@ export const OPENSPEC_MARKERS = {
|
|
|
5
5
|
};
|
|
6
6
|
export const AI_TOOLS = [
|
|
7
7
|
{ name: 'Claude Code', value: 'claude', available: true },
|
|
8
|
-
{ name: 'Cursor', value: 'cursor', available:
|
|
8
|
+
{ name: 'Cursor', value: 'cursor', available: true },
|
|
9
9
|
{ name: 'Aider', value: 'aider', available: false },
|
|
10
10
|
{ name: 'Continue', value: 'continue', available: false }
|
|
11
11
|
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
2
|
+
export interface SlashCommandTarget {
|
|
3
|
+
id: SlashCommandId;
|
|
4
|
+
path: string;
|
|
5
|
+
kind: 'slash';
|
|
6
|
+
}
|
|
7
|
+
export declare abstract class SlashCommandConfigurator {
|
|
8
|
+
abstract readonly toolId: string;
|
|
9
|
+
abstract readonly isAvailable: boolean;
|
|
10
|
+
getTargets(): SlashCommandTarget[];
|
|
11
|
+
generateAll(projectPath: string, _openspecDir: string): Promise<string[]>;
|
|
12
|
+
updateExisting(projectPath: string, _openspecDir: string): Promise<string[]>;
|
|
13
|
+
protected abstract getRelativePath(id: SlashCommandId): string;
|
|
14
|
+
protected abstract getFrontmatter(id: SlashCommandId): string | undefined;
|
|
15
|
+
private updateBody;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FileSystemUtils } from '../../../utils/file-system.js';
|
|
3
|
+
import { TemplateManager } from '../../templates/index.js';
|
|
4
|
+
import { OPENSPEC_MARKERS } from '../../config.js';
|
|
5
|
+
const ALL_COMMANDS = ['proposal', 'apply', 'archive'];
|
|
6
|
+
export class SlashCommandConfigurator {
|
|
7
|
+
getTargets() {
|
|
8
|
+
return ALL_COMMANDS.map((id) => ({
|
|
9
|
+
id,
|
|
10
|
+
path: this.getRelativePath(id),
|
|
11
|
+
kind: 'slash'
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
async generateAll(projectPath, _openspecDir) {
|
|
15
|
+
const createdOrUpdated = [];
|
|
16
|
+
for (const target of this.getTargets()) {
|
|
17
|
+
const body = TemplateManager.getSlashCommandBody(target.id).trim();
|
|
18
|
+
const filePath = path.join(projectPath, target.path);
|
|
19
|
+
if (await FileSystemUtils.fileExists(filePath)) {
|
|
20
|
+
await this.updateBody(filePath, body);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const frontmatter = this.getFrontmatter(target.id);
|
|
24
|
+
const sections = [];
|
|
25
|
+
if (frontmatter) {
|
|
26
|
+
sections.push(frontmatter.trim());
|
|
27
|
+
}
|
|
28
|
+
sections.push(`${OPENSPEC_MARKERS.start}\n${body}\n${OPENSPEC_MARKERS.end}`);
|
|
29
|
+
const content = sections.join('\n') + '\n';
|
|
30
|
+
await FileSystemUtils.writeFile(filePath, content);
|
|
31
|
+
}
|
|
32
|
+
createdOrUpdated.push(target.path);
|
|
33
|
+
}
|
|
34
|
+
return createdOrUpdated;
|
|
35
|
+
}
|
|
36
|
+
async updateExisting(projectPath, _openspecDir) {
|
|
37
|
+
const updated = [];
|
|
38
|
+
for (const target of this.getTargets()) {
|
|
39
|
+
const filePath = path.join(projectPath, target.path);
|
|
40
|
+
if (await FileSystemUtils.fileExists(filePath)) {
|
|
41
|
+
const body = TemplateManager.getSlashCommandBody(target.id).trim();
|
|
42
|
+
await this.updateBody(filePath, body);
|
|
43
|
+
updated.push(target.path);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return updated;
|
|
47
|
+
}
|
|
48
|
+
async updateBody(filePath, body) {
|
|
49
|
+
const content = await FileSystemUtils.readFile(filePath);
|
|
50
|
+
const startIndex = content.indexOf(OPENSPEC_MARKERS.start);
|
|
51
|
+
const endIndex = content.indexOf(OPENSPEC_MARKERS.end);
|
|
52
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
53
|
+
throw new Error(`Missing OpenSpec markers in ${filePath}`);
|
|
54
|
+
}
|
|
55
|
+
const before = content.slice(0, startIndex + OPENSPEC_MARKERS.start.length);
|
|
56
|
+
const after = content.slice(endIndex);
|
|
57
|
+
const updatedContent = `${before}\n${body}\n${after}`;
|
|
58
|
+
await FileSystemUtils.writeFile(filePath, updatedContent);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
+
export declare class ClaudeSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
4
|
+
readonly toolId = "claude";
|
|
5
|
+
readonly isAvailable = true;
|
|
6
|
+
protected getRelativePath(id: SlashCommandId): string;
|
|
7
|
+
protected getFrontmatter(id: SlashCommandId): string;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
const FILE_PATHS = {
|
|
3
|
+
proposal: '.claude/commands/openspec/proposal.md',
|
|
4
|
+
apply: '.claude/commands/openspec/apply.md',
|
|
5
|
+
archive: '.claude/commands/openspec/archive.md'
|
|
6
|
+
};
|
|
7
|
+
const FRONTMATTER = {
|
|
8
|
+
proposal: `---
|
|
9
|
+
name: OpenSpec: Proposal
|
|
10
|
+
description: Scaffold a new OpenSpec change and validate strictly.
|
|
11
|
+
category: OpenSpec
|
|
12
|
+
tags: [openspec, change]
|
|
13
|
+
---`,
|
|
14
|
+
apply: `---
|
|
15
|
+
name: OpenSpec: Apply
|
|
16
|
+
description: Implement an approved OpenSpec change and keep tasks in sync.
|
|
17
|
+
category: OpenSpec
|
|
18
|
+
tags: [openspec, apply]
|
|
19
|
+
---`,
|
|
20
|
+
archive: `---
|
|
21
|
+
name: OpenSpec: Archive
|
|
22
|
+
description: Archive a deployed OpenSpec change and update specs.
|
|
23
|
+
category: OpenSpec
|
|
24
|
+
tags: [openspec, archive]
|
|
25
|
+
---`
|
|
26
|
+
};
|
|
27
|
+
export class ClaudeSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
28
|
+
toolId = 'claude';
|
|
29
|
+
isAvailable = true;
|
|
30
|
+
getRelativePath(id) {
|
|
31
|
+
return FILE_PATHS[id];
|
|
32
|
+
}
|
|
33
|
+
getFrontmatter(id) {
|
|
34
|
+
return FRONTMATTER[id];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
import { SlashCommandId } from '../../templates/index.js';
|
|
3
|
+
export declare class CursorSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
4
|
+
readonly toolId = "cursor";
|
|
5
|
+
readonly isAvailable = true;
|
|
6
|
+
protected getRelativePath(id: SlashCommandId): string;
|
|
7
|
+
protected getFrontmatter(id: SlashCommandId): string;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
const FILE_PATHS = {
|
|
3
|
+
proposal: '.cursor/commands/openspec-proposal.md',
|
|
4
|
+
apply: '.cursor/commands/openspec-apply.md',
|
|
5
|
+
archive: '.cursor/commands/openspec-archive.md'
|
|
6
|
+
};
|
|
7
|
+
const FRONTMATTER = {
|
|
8
|
+
proposal: `---
|
|
9
|
+
name: /openspec-proposal
|
|
10
|
+
id: openspec-proposal
|
|
11
|
+
category: OpenSpec
|
|
12
|
+
description: Scaffold a new OpenSpec change and validate strictly.
|
|
13
|
+
---`,
|
|
14
|
+
apply: `---
|
|
15
|
+
name: /openspec-apply
|
|
16
|
+
id: openspec-apply
|
|
17
|
+
category: OpenSpec
|
|
18
|
+
description: Implement an approved OpenSpec change and keep tasks in sync.
|
|
19
|
+
---`,
|
|
20
|
+
archive: `---
|
|
21
|
+
name: /openspec-archive
|
|
22
|
+
id: openspec-archive
|
|
23
|
+
category: OpenSpec
|
|
24
|
+
description: Archive a deployed OpenSpec change and update specs.
|
|
25
|
+
---`
|
|
26
|
+
};
|
|
27
|
+
export class CursorSlashCommandConfigurator extends SlashCommandConfigurator {
|
|
28
|
+
toolId = 'cursor';
|
|
29
|
+
isAvailable = true;
|
|
30
|
+
getRelativePath(id) {
|
|
31
|
+
return FILE_PATHS[id];
|
|
32
|
+
}
|
|
33
|
+
getFrontmatter(id) {
|
|
34
|
+
return FRONTMATTER[id];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SlashCommandConfigurator } from './base.js';
|
|
2
|
+
export declare class SlashCommandRegistry {
|
|
3
|
+
private static configurators;
|
|
4
|
+
static register(configurator: SlashCommandConfigurator): void;
|
|
5
|
+
static get(toolId: string): SlashCommandConfigurator | undefined;
|
|
6
|
+
static getAll(): SlashCommandConfigurator[];
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ClaudeSlashCommandConfigurator } from './claude.js';
|
|
2
|
+
import { CursorSlashCommandConfigurator } from './cursor.js';
|
|
3
|
+
export class SlashCommandRegistry {
|
|
4
|
+
static configurators = new Map();
|
|
5
|
+
static {
|
|
6
|
+
const claude = new ClaudeSlashCommandConfigurator();
|
|
7
|
+
const cursor = new CursorSlashCommandConfigurator();
|
|
8
|
+
this.configurators.set(claude.toolId, claude);
|
|
9
|
+
this.configurators.set(cursor.toolId, cursor);
|
|
10
|
+
}
|
|
11
|
+
static register(configurator) {
|
|
12
|
+
this.configurators.set(configurator.toolId, configurator);
|
|
13
|
+
}
|
|
14
|
+
static get(toolId) {
|
|
15
|
+
return this.configurators.get(toolId);
|
|
16
|
+
}
|
|
17
|
+
static getAll() {
|
|
18
|
+
return Array.from(this.configurators.values());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=registry.js.map
|