@fission-ai/openspec 0.2.0 β†’ 0.3.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/README.md CHANGED
@@ -27,129 +27,181 @@
27
27
 
28
28
  # OpenSpec
29
29
 
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.
30
+ OpenSpec aligns humans and AI coding assistants with spec-driven development so you agree on what to build before any code is written. **No API keys required.**
35
31
 
36
32
  ## Why OpenSpec?
37
33
 
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.
34
+ AI coding assistants are powerful but unpredictable when requirements live in chat history. OpenSpec adds a lightweight specification workflow that locks intent before implementation, giving you deterministic, reviewable outputs.
39
35
 
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
36
+ Key outcomes:
37
+ - Human and AI stakeholders agree on specs before work begins.
38
+ - Structured change folders (proposals, tasks, and spec updates) keep scope explicit and auditable.
39
+ - Shared visibility into what's proposed, active, or archived.
40
+ - Works with the AI tools you already use: custom slash commands where supported, context rules everywhere else.
49
41
 
50
42
  ## How It Works
51
43
 
52
44
  ```
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
45
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
46
+ β”‚ Draft Change β”‚
47
+ β”‚ Proposal β”‚
48
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
49
+ β”‚ share intent with your AI
50
+ β–Ό
51
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
52
+ β”‚ Review & Align β”‚
53
+ β”‚ (edit specs/tasks) │◀──── feedback loop ──────┐
54
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
55
+ β”‚ approved plan β”‚
56
+ β–Ό β”‚
57
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
58
+ β”‚ Implement Tasks β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
59
+ β”‚ (AI writes code) β”‚
60
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
61
+ β”‚ ship the change
62
+ β–Ό
63
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
64
+ β”‚ Archive & Update β”‚
65
+ β”‚ Specs (source) β”‚
66
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
67
+
68
+ 1. Draft a change proposal that captures the spec updates you want.
69
+ 2. Review the proposal with your AI assistant until everyone agrees.
70
+ 3. Implement tasks that reference the agreed specs.
71
+ 4. Archive the change to merge the approved updates back into the source-of-truth specs.
67
72
  ```
68
73
 
69
- ## Installation
74
+ ## Getting Started
75
+
76
+ ### Supported AI Tools
77
+
78
+ #### Native Slash Commands
79
+ These tools have built-in OpenSpec commands. Select the OpenSpec integration when prompted.
70
80
 
71
- ### Prerequisites
81
+ | Tool | Commands |
82
+ |------|----------|
83
+ | **Claude Code** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` |
84
+ | **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
72
85
 
73
- - Node.js >= 20.19.0
86
+ #### AGENTS.md Compatible
87
+ These tools automatically read workflow instructions from `openspec/AGENTS.md`. Ask them to follow the OpenSpec workflow if they need a reminder. Learn more about the [AGENTS.md convention](https://agents.md/).
74
88
 
75
- ### Install OpenSpec
89
+ | Tools |
90
+ |-------|
91
+ | Codex β€’ Amp β€’ Jules β€’ OpenCode β€’ Gemini CLI β€’ GitHub Copilot β€’ Others |
76
92
 
77
- Install globally:
93
+ ### Install & Initialize
94
+
95
+ #### Prerequisites
96
+ - **Node.js >= 20.19.0** - Check your version with `node --version`
97
+
98
+ #### Step 1: Install the CLI globally
78
99
 
79
100
  ```bash
80
- npm install -g @fission-ai/openspec
101
+ npm install -g @fission-ai/openspec@latest
81
102
  ```
82
103
 
83
- ## Getting Started
104
+ Verify installation:
105
+ ```bash
106
+ openspec --version
107
+ ```
84
108
 
85
- ### 1. Initialize OpenSpec in Your Project
109
+ #### Step 2: Initialize OpenSpec in your project
86
110
 
111
+ Navigate to your project directory:
87
112
  ```bash
88
- # Navigate to your project
89
113
  cd my-project
114
+ ```
90
115
 
91
- # Initialize OpenSpec
116
+ Run the initialization:
117
+ ```bash
92
118
  openspec init
119
+ ```
93
120
 
94
- # Select your AI tool (more coming soon!):
95
- # "Which AI tool do you use?"
96
- # > Claude Code
97
- # Cursor (coming soon)
121
+ **What happens during initialization:**
122
+ - You'll be prompted to select your AI tool (Claude Code, Cursor, etc.)
123
+ - OpenSpec automatically configures slash commands or `AGENTS.md` based on your selection
124
+ - A new `openspec/` directory structure is created in your project
98
125
 
99
- # This creates:
100
- # openspec/
101
- # β”œβ”€β”€ specs/ # Current specifications (truth)
102
- # β”œβ”€β”€ changes/ # Proposed changes
103
- # └── AGENTS.md # AI instructions for your tool
104
- ```
126
+ **After setup:**
127
+ - Primary AI tools can trigger `/openspec` workflows without additional configuration
128
+ - Run `openspec list` to verify the setup and view any active changes
105
129
 
106
- ### 2. Create Your First Change
130
+ ### Create Your First Change
107
131
 
108
- Jump straight into creating a change proposal with your AI assistant (works with Claude Code, Cursor, or any AI tool):
132
+ Here's a real example showing the complete OpenSpec workflow. This works with any AI tool. Those with native slash commands will recognize the shortcuts automatically.
109
133
 
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."
114
-
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)
121
-
122
- You: "The proposal looks good. Let's implement it."
123
-
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*
134
+ #### 1. Draft the Proposal
135
+ Start by asking your AI to create a change proposal:
136
+
137
+ ```text
138
+ You: Create an OpenSpec change proposal for adding profile search filters by role and team
139
+ (Shortcut for tools with slash commands: /openspec:proposal Add profile search filters)
140
+
141
+ AI: I'll create an OpenSpec change proposal for profile filters.
142
+ *Scaffolds openspec/changes/add-profile-filters/ with proposal.md, tasks.md, spec deltas.*
127
143
  ```
128
144
 
129
- ### 3. Track Your Work
145
+ #### 2. Verify & Review
146
+ Check that the change was created correctly and review the proposal:
130
147
 
131
148
  ```bash
132
- # View active changes (what's being worked on)
133
- openspec list
149
+ $ openspec list # Confirm the change folder exists
150
+ $ openspec validate add-profile-filters # Validate spec formatting
151
+ $ openspec show add-profile-filters # Review proposal, tasks, and spec delta
152
+ ```
153
+
154
+ #### 3. Refine the Specs
155
+ Iterate on the specifications until they match your needs:
134
156
 
135
- # Validate your changes are properly formatted
136
- openspec validate add-2fa --strict
157
+ ```text
158
+ You: Can you add acceptance criteria for the role and team filters?
137
159
 
138
- # After deployment, archive the completed change
139
- openspec archive add-2fa
140
- # This moves the change to archive/ and updates specs/
160
+ AI: I'll update the spec delta with scenarios for role and team filters.
161
+ *Edits openspec/changes/add-profile-filters/specs/profile/spec.md and tasks.md.*
141
162
  ```
142
163
 
143
- ## Common Commands
164
+ #### 4. Implement the Change
165
+ Once specs look good, start implementation:
144
166
 
167
+ ```text
168
+ You: The specs look good. Let's implement this change.
169
+ (Shortcut for tools with slash commands: /openspec:apply add-profile-filters)
170
+
171
+ AI: I'll work through the tasks in the add-profile-filters change.
172
+ *Implements tasks from openspec/changes/add-profile-filters/tasks.md*
173
+ *Marks tasks complete: Task 1.1 βœ“, Task 1.2 βœ“, Task 2.1 βœ“...*
174
+ ```
175
+
176
+ #### 5. Archive the Completed Change
177
+ After implementation is complete, archive the change:
178
+
179
+ ```text
180
+ AI: All tasks are complete. The implementation is ready.
181
+
182
+ You: Please archive the change
183
+ (Shortcut for tools with slash commands: /openspec:archive add-profile-filters)
184
+
185
+ AI: I'll archive the add-profile-filters change.
186
+ *Runs: openspec archive add-profile-filters*
187
+ βœ“ Change archived successfully. Specs updated. Ready for the next feature!
188
+ ```
189
+
190
+ Or run the command yourself in terminal:
145
191
  ```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
192
+ $ openspec archive add-profile-filters # Archive the completed change
193
+ ```
149
194
 
150
- # Also useful:
151
- openspec validate <change> # Check formatting before committing
152
- openspec show <change> # View change details
195
+ **Note:** Tools with native slash commands (Claude Code, Cursor) can use the shortcuts shown. All other tools work with natural language requests to "create an OpenSpec proposal", "apply the OpenSpec change", or "archive the change".
196
+
197
+ ## Command Reference
198
+
199
+ ```bash
200
+ openspec list # View active change folders
201
+ openspec view # Interactive dashboard of specs and changes
202
+ openspec show <change> # Display change details (proposal, tasks, spec updates)
203
+ openspec validate <change> # Check spec formatting and structure
204
+ openspec archive <change> # Move a completed change into archive/
153
205
  ```
154
206
 
155
207
  ## Example: How AI Creates OpenSpec Files
@@ -236,46 +288,38 @@ Deltas are "patches" that show how specs change:
236
288
  - Every requirement needs at least one `#### Scenario:` block
237
289
  - Use SHALL/MUST in requirement text
238
290
 
239
-
240
- ## Why OpenSpec Works
241
-
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
291
  ## How OpenSpec Compares
254
292
 
255
293
  ### 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.
294
+ OpenSpec groups every change for a feature in one folder (`openspec/changes/feature-name/`), making it easy to track related specs, tasks, and designs together. Kiro spreads updates across multiple spec folders, which can make feature tracking harder.
257
295
 
258
296
  ### 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.
297
+ Without specs, AI coding assistants generate code from vague prompts, often missing requirements or adding unwanted features. OpenSpec brings predictability by agreeing on the desired behavior before any code is written.
260
298
 
261
299
  ## Team Adoption
262
300
 
263
- ### Getting Started with Your Team
301
+ 1. **Initialize OpenSpec** – Run `openspec init` in your repo.
302
+ 2. **Start with new features** – Ask your AI to capture upcoming work as change proposals.
303
+ 3. **Grow incrementally** – Each change archives into living specs that document your system.
304
+ 4. **Stay flexible** – Different teammates can use Claude Code, Cursor, or any AGENTS.md-compatible tool while sharing the same specs.
264
305
 
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
306
+ Run `openspec update` whenever someone switches tools so your agents pick up the latest instructions and slash-command bindings.
269
307
 
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.
308
+ ## Updating OpenSpec
271
309
 
310
+ 1. **Upgrade the package**
311
+ ```bash
312
+ npm install -g @fission-ai/openspec@latest
313
+ ```
314
+ 2. **Refresh agent instructions**
315
+ - Run `openspec update` inside each project to regenerate AI guidance and ensure the latest slash commands are active.
272
316
 
273
317
  ## Contributing
274
318
 
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`
319
+ - Install dependencies: `pnpm install`
320
+ - Build: `pnpm run build`
321
+ - Test: `pnpm test`
322
+ - Develop CLI locally: `pnpm run dev` or `pnpm run dev:cli`
279
323
  - Conventional commits (one-line): `type(scope): subject`
280
324
 
281
325
  ## License
@@ -1,14 +1,16 @@
1
1
  export declare const OPENSPEC_DIR_NAME = "openspec";
2
- export interface OpenSpecConfig {
3
- aiTools: string[];
4
- }
5
2
  export declare const OPENSPEC_MARKERS: {
6
3
  start: string;
7
4
  end: string;
8
5
  };
9
- export declare const AI_TOOLS: {
6
+ export interface OpenSpecConfig {
7
+ aiTools: string[];
8
+ }
9
+ export interface AIToolOption {
10
10
  name: string;
11
11
  value: string;
12
12
  available: boolean;
13
- }[];
13
+ successLabel?: string;
14
+ }
15
+ export declare const AI_TOOLS: AIToolOption[];
14
16
  //# sourceMappingURL=config.d.ts.map
@@ -4,9 +4,8 @@ export const OPENSPEC_MARKERS = {
4
4
  end: '<!-- OPENSPEC:END -->'
5
5
  };
6
6
  export const AI_TOOLS = [
7
- { name: 'Claude Code', value: 'claude', available: true },
8
- { name: 'Cursor', value: 'cursor', available: true },
9
- { name: 'Aider', value: 'aider', available: false },
10
- { name: 'Continue', value: 'continue', available: false }
7
+ { name: 'Claude Code (βœ… OpenSpec custom slash commands available)', value: 'claude', available: true, successLabel: 'Claude Code' },
8
+ { name: 'Cursor (βœ… OpenSpec custom slash commands available)', value: 'cursor', available: true, successLabel: 'Cursor' },
9
+ { name: 'AGENTS.md (works with Codex, Amp, Copilot, …)', value: 'agents', available: true, successLabel: 'your AGENTS.md-compatible assistant' }
11
10
  ];
12
11
  //# sourceMappingURL=config.js.map
@@ -0,0 +1,8 @@
1
+ import { ToolConfigurator } from './base.js';
2
+ export declare class AgentsStandardConfigurator implements ToolConfigurator {
3
+ name: string;
4
+ configFileName: string;
5
+ isAvailable: boolean;
6
+ configure(projectPath: string, _openspecDir: string): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1,15 @@
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
+ export class AgentsStandardConfigurator {
6
+ name = 'AGENTS.md standard';
7
+ configFileName = 'AGENTS.md';
8
+ isAvailable = true;
9
+ async configure(projectPath, _openspecDir) {
10
+ const filePath = path.join(projectPath, this.configFileName);
11
+ const content = TemplateManager.getAgentsStandardTemplate();
12
+ await FileSystemUtils.updateFileWithMarkers(filePath, content, OPENSPEC_MARKERS.start, OPENSPEC_MARKERS.end);
13
+ }
14
+ }
15
+ //# sourceMappingURL=agents.js.map
@@ -1,10 +1,13 @@
1
1
  import { ClaudeConfigurator } from './claude.js';
2
+ import { AgentsStandardConfigurator } from './agents.js';
2
3
  export class ToolRegistry {
3
4
  static tools = new Map();
4
5
  static {
5
6
  const claudeConfigurator = new ClaudeConfigurator();
7
+ const agentsConfigurator = new AgentsStandardConfigurator();
6
8
  // Register with the ID that matches the checkbox value
7
9
  this.tools.set('claude', claudeConfigurator);
10
+ this.tools.set('agents', agentsConfigurator);
8
11
  }
9
12
  static register(tool) {
10
13
  this.tools.set(tool.name.toLowerCase().replace(/\s+/g, '-'), tool);
@@ -1,10 +1,38 @@
1
+ type ToolLabel = {
2
+ primary: string;
3
+ annotation?: string;
4
+ };
5
+ type ToolWizardChoice = {
6
+ value: string;
7
+ label: ToolLabel;
8
+ configured: boolean;
9
+ };
10
+ type ToolWizardConfig = {
11
+ extendMode: boolean;
12
+ baseMessage: string;
13
+ choices: ToolWizardChoice[];
14
+ initialSelected?: string[];
15
+ };
16
+ type ToolSelectionPrompt = (config: ToolWizardConfig) => Promise<string[]>;
17
+ type InitCommandOptions = {
18
+ prompt?: ToolSelectionPrompt;
19
+ };
1
20
  export declare class InitCommand {
21
+ private readonly prompt;
22
+ constructor(options?: InitCommandOptions);
2
23
  execute(targetPath: string): Promise<void>;
3
24
  private validate;
4
25
  private getConfiguration;
26
+ private promptForAITools;
27
+ private getExistingToolStates;
28
+ private isToolConfigured;
5
29
  private createDirectoryStructure;
6
30
  private generateFiles;
7
31
  private configureAITools;
8
32
  private displaySuccessMessage;
33
+ private formatToolNames;
34
+ private renderBanner;
35
+ private startSpinner;
9
36
  }
37
+ export {};
10
38
  //# sourceMappingURL=init.d.ts.map
package/dist/core/init.js CHANGED
@@ -1,58 +1,335 @@
1
1
  import path from 'path';
2
- import { select } from '@inquirer/prompts';
2
+ import { createPrompt, isBackspaceKey, isDownKey, isEnterKey, isSpaceKey, isUpKey, useKeypress, usePagination, useState } from '@inquirer/core';
3
+ import chalk from 'chalk';
3
4
  import ora from 'ora';
4
5
  import { FileSystemUtils } from '../utils/file-system.js';
5
6
  import { TemplateManager } from './templates/index.js';
6
7
  import { ToolRegistry } from './configurators/registry.js';
7
8
  import { SlashCommandRegistry } from './configurators/slash/registry.js';
8
9
  import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
10
+ const PROGRESS_SPINNER = {
11
+ interval: 80,
12
+ frames: ['β–‘β–‘β–‘', 'β–’β–‘β–‘', 'β–’β–’β–‘', 'β–’β–’β–’', 'β–“β–’β–’', 'β–“β–“β–’', 'β–“β–“β–“', 'β–’β–“β–“', 'β–‘β–’β–“']
13
+ };
14
+ const PALETTE = {
15
+ white: chalk.hex('#f4f4f4'),
16
+ lightGray: chalk.hex('#c8c8c8'),
17
+ midGray: chalk.hex('#8a8a8a'),
18
+ darkGray: chalk.hex('#4a4a4a')
19
+ };
20
+ const LETTER_MAP = {
21
+ O: [
22
+ ' β–ˆβ–ˆβ–ˆβ–ˆ ',
23
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
24
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
25
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
26
+ ' β–ˆβ–ˆβ–ˆβ–ˆ '
27
+ ],
28
+ P: [
29
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ ',
30
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
31
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ ',
32
+ 'β–ˆβ–ˆ ',
33
+ 'β–ˆβ–ˆ '
34
+ ],
35
+ E: [
36
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ',
37
+ 'β–ˆβ–ˆ ',
38
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ ',
39
+ 'β–ˆβ–ˆ ',
40
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ'
41
+ ],
42
+ N: [
43
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
44
+ 'β–ˆβ–ˆβ–ˆ β–ˆβ–ˆ',
45
+ 'β–ˆβ–ˆ β–ˆβ–ˆβ–ˆ',
46
+ 'β–ˆβ–ˆ β–ˆβ–ˆ',
47
+ 'β–ˆβ–ˆ β–ˆβ–ˆ'
48
+ ],
49
+ S: [
50
+ ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ',
51
+ 'β–ˆβ–ˆ ',
52
+ ' β–ˆβ–ˆβ–ˆβ–ˆ ',
53
+ ' β–ˆβ–ˆ',
54
+ 'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ '
55
+ ],
56
+ C: [
57
+ ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ',
58
+ 'β–ˆβ–ˆ ',
59
+ 'β–ˆβ–ˆ ',
60
+ 'β–ˆβ–ˆ ',
61
+ ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ'
62
+ ],
63
+ ' ': [
64
+ ' ',
65
+ ' ',
66
+ ' ',
67
+ ' ',
68
+ ' '
69
+ ]
70
+ };
71
+ const sanitizeToolLabel = (raw) => raw.replace(/βœ…/gu, 'βœ”').trim();
72
+ const parseToolLabel = (raw) => {
73
+ const sanitized = sanitizeToolLabel(raw);
74
+ const match = sanitized.match(/^(.*?)\s*\((.+)\)$/u);
75
+ if (!match) {
76
+ return { primary: sanitized };
77
+ }
78
+ return {
79
+ primary: match[1].trim(),
80
+ annotation: match[2].trim()
81
+ };
82
+ };
83
+ const toolSelectionWizard = createPrompt((config, done) => {
84
+ const totalSteps = 3;
85
+ const [step, setStep] = useState('intro');
86
+ const [cursor, setCursor] = useState(0);
87
+ const [selected, setSelected] = useState(() => config.initialSelected ?? []);
88
+ const [error, setError] = useState(null);
89
+ const selectedSet = new Set(selected);
90
+ const pageSize = Math.max(Math.min(config.choices.length, 7), 1);
91
+ const updateSelected = (next) => {
92
+ const ordered = config.choices
93
+ .map((choice) => choice.value)
94
+ .filter((value) => next.has(value));
95
+ setSelected(ordered);
96
+ };
97
+ const page = usePagination({
98
+ items: config.choices,
99
+ active: cursor,
100
+ pageSize,
101
+ loop: config.choices.length > 1,
102
+ renderItem: ({ item, isActive }) => {
103
+ const isSelected = selectedSet.has(item.value);
104
+ const cursorSymbol = isActive ? PALETTE.white('β€Ί') : PALETTE.midGray(' ');
105
+ const indicator = isSelected ? PALETTE.white('β—‰') : PALETTE.midGray('β—‹');
106
+ const nameColor = isActive ? PALETTE.white : PALETTE.midGray;
107
+ const label = `${nameColor(item.label.primary)}${item.configured ? PALETTE.midGray(' (already configured)') : ''}`;
108
+ return `${cursorSymbol} ${indicator} ${label}`;
109
+ }
110
+ });
111
+ useKeypress((key) => {
112
+ if (step === 'intro') {
113
+ if (isEnterKey(key)) {
114
+ setStep('select');
115
+ }
116
+ return;
117
+ }
118
+ if (step === 'select') {
119
+ if (isUpKey(key)) {
120
+ const previousIndex = cursor <= 0 ? config.choices.length - 1 : cursor - 1;
121
+ setCursor(previousIndex);
122
+ setError(null);
123
+ return;
124
+ }
125
+ if (isDownKey(key)) {
126
+ const nextIndex = cursor >= config.choices.length - 1 ? 0 : cursor + 1;
127
+ setCursor(nextIndex);
128
+ setError(null);
129
+ return;
130
+ }
131
+ if (isSpaceKey(key)) {
132
+ const current = config.choices[cursor];
133
+ if (!current)
134
+ return;
135
+ const next = new Set(selected);
136
+ if (next.has(current.value)) {
137
+ next.delete(current.value);
138
+ }
139
+ else {
140
+ next.add(current.value);
141
+ }
142
+ updateSelected(next);
143
+ setError(null);
144
+ return;
145
+ }
146
+ if (isEnterKey(key)) {
147
+ if (selected.length === 0) {
148
+ setError('Select at least one AI tool to continue.');
149
+ return;
150
+ }
151
+ setStep('review');
152
+ setError(null);
153
+ return;
154
+ }
155
+ if (key.name === 'escape') {
156
+ setSelected([]);
157
+ setError(null);
158
+ }
159
+ return;
160
+ }
161
+ if (step === 'review') {
162
+ if (isEnterKey(key)) {
163
+ const finalSelection = config.choices
164
+ .map((choice) => choice.value)
165
+ .filter((value) => selectedSet.has(value));
166
+ done(finalSelection);
167
+ return;
168
+ }
169
+ if (isBackspaceKey(key) || key.name === 'escape') {
170
+ setStep('select');
171
+ setError(null);
172
+ }
173
+ }
174
+ });
175
+ const selectedNames = config.choices
176
+ .filter((choice) => selectedSet.has(choice.value))
177
+ .map((choice) => choice.label.primary);
178
+ const stepIndex = step === 'intro' ? 1 : step === 'select' ? 2 : 3;
179
+ const lines = [];
180
+ lines.push(PALETTE.midGray(`Step ${stepIndex}/${totalSteps}`));
181
+ lines.push('');
182
+ if (step === 'intro') {
183
+ const introHeadline = config.extendMode
184
+ ? 'Extend your OpenSpec tooling'
185
+ : 'Configure your OpenSpec tooling';
186
+ const introBody = config.extendMode
187
+ ? 'We detected an existing setup. We will help you refresh or add integrations.'
188
+ : "Let's get your AI assistants connected so they understand OpenSpec.";
189
+ lines.push(PALETTE.white(introHeadline));
190
+ lines.push(PALETTE.midGray(introBody));
191
+ lines.push('');
192
+ lines.push(PALETTE.midGray('Press Enter to continue.'));
193
+ }
194
+ else if (step === 'select') {
195
+ lines.push(PALETTE.white(config.baseMessage));
196
+ lines.push(PALETTE.midGray('Use ↑/↓ to move Β· Space to toggle Β· Enter to review selections.'));
197
+ lines.push('');
198
+ lines.push(page);
199
+ lines.push('');
200
+ if (selectedNames.length === 0) {
201
+ lines.push(`${PALETTE.midGray('Selected')}: ${PALETTE.midGray('None selected yet')}`);
202
+ }
203
+ else {
204
+ lines.push(PALETTE.midGray('Selected:'));
205
+ selectedNames.forEach((name) => {
206
+ lines.push(` ${PALETTE.white('-')} ${PALETTE.white(name)}`);
207
+ });
208
+ }
209
+ }
210
+ else {
211
+ lines.push(PALETTE.white('Review selections'));
212
+ lines.push(PALETTE.midGray('Press Enter to confirm or Backspace to adjust.'));
213
+ lines.push('');
214
+ if (selectedNames.length === 0) {
215
+ lines.push(PALETTE.midGray('No tools selected. Press Backspace to return.'));
216
+ }
217
+ else {
218
+ selectedNames.forEach((name) => {
219
+ lines.push(`${PALETTE.white('β–Œ')} ${PALETTE.white(name)}`);
220
+ });
221
+ }
222
+ }
223
+ if (error) {
224
+ return [lines.join('\n'), chalk.red(error)];
225
+ }
226
+ return lines.join('\n');
227
+ });
9
228
  export class InitCommand {
229
+ prompt;
230
+ constructor(options = {}) {
231
+ this.prompt = options.prompt ?? ((config) => toolSelectionWizard(config));
232
+ }
10
233
  async execute(targetPath) {
11
234
  const projectPath = path.resolve(targetPath);
12
235
  const openspecDir = OPENSPEC_DIR_NAME;
13
236
  const openspecPath = path.join(projectPath, openspecDir);
14
237
  // Validation happens silently in the background
15
- await this.validate(projectPath, openspecPath);
238
+ const extendMode = await this.validate(projectPath, openspecPath);
239
+ const existingToolStates = await this.getExistingToolStates(projectPath);
240
+ this.renderBanner(extendMode);
16
241
  // Get configuration (after validation to avoid prompts if validation fails)
17
- const config = await this.getConfiguration();
242
+ const config = await this.getConfiguration(existingToolStates, extendMode);
243
+ if (config.aiTools.length === 0) {
244
+ if (extendMode) {
245
+ throw new Error(`OpenSpec seems to already be initialized at ${openspecPath}.\n` +
246
+ `Use 'openspec update' to update the structure.`);
247
+ }
248
+ throw new Error('You must select at least one AI tool to configure.');
249
+ }
250
+ const availableTools = AI_TOOLS.filter(tool => tool.available);
251
+ const selectedIds = new Set(config.aiTools);
252
+ const selectedTools = availableTools.filter(tool => selectedIds.has(tool.value));
253
+ const created = selectedTools.filter(tool => !existingToolStates[tool.value]);
254
+ const refreshed = selectedTools.filter(tool => existingToolStates[tool.value]);
255
+ const skippedExisting = availableTools.filter(tool => !selectedIds.has(tool.value) && existingToolStates[tool.value]);
256
+ const skipped = availableTools.filter(tool => !selectedIds.has(tool.value) && !existingToolStates[tool.value]);
18
257
  // Step 1: Create directory structure
19
- const structureSpinner = ora({ text: 'Creating OpenSpec structure...', stream: process.stdout }).start();
20
- await this.createDirectoryStructure(openspecPath);
21
- await this.generateFiles(openspecPath, config);
22
- structureSpinner.succeed('OpenSpec structure created');
258
+ if (!extendMode) {
259
+ const structureSpinner = this.startSpinner('Creating OpenSpec structure...');
260
+ await this.createDirectoryStructure(openspecPath);
261
+ await this.generateFiles(openspecPath, config);
262
+ structureSpinner.stopAndPersist({
263
+ symbol: PALETTE.white('β–Œ'),
264
+ text: PALETTE.white('OpenSpec structure created')
265
+ });
266
+ }
267
+ else {
268
+ ora({ stream: process.stdout }).info(PALETTE.midGray('β„Ή OpenSpec already initialized. Skipping base scaffolding.'));
269
+ }
23
270
  // Step 2: Configure AI tools
24
- const toolSpinner = ora({ text: 'Configuring AI tools...', stream: process.stdout }).start();
271
+ const toolSpinner = this.startSpinner('Configuring AI tools...');
25
272
  await this.configureAITools(projectPath, openspecDir, config.aiTools);
26
- toolSpinner.succeed('AI tools configured');
273
+ toolSpinner.stopAndPersist({
274
+ symbol: PALETTE.white('β–Œ'),
275
+ text: PALETTE.white('AI tools configured')
276
+ });
27
277
  // Success message
28
- this.displaySuccessMessage(openspecDir, config);
278
+ this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode);
29
279
  }
30
- async validate(projectPath, openspecPath) {
31
- // Check if OpenSpec already exists
32
- if (await FileSystemUtils.directoryExists(openspecPath)) {
33
- throw new Error(`OpenSpec seems to already be initialized at ${openspecPath}.\n` +
34
- `Use 'openspec update' to update the structure.`);
35
- }
280
+ async validate(projectPath, _openspecPath) {
281
+ const extendMode = await FileSystemUtils.directoryExists(_openspecPath);
36
282
  // Check write permissions
37
283
  if (!await FileSystemUtils.ensureWritePermissions(projectPath)) {
38
284
  throw new Error(`Insufficient permissions to write to ${projectPath}`);
39
285
  }
286
+ return extendMode;
40
287
  }
41
- async getConfiguration() {
42
- const config = {
43
- aiTools: []
44
- };
45
- // Single-select for better UX
46
- const selectedTool = await select({
47
- message: 'Which AI tool do you use?',
48
- choices: AI_TOOLS.map(tool => ({
49
- name: tool.available ? tool.name : `${tool.name} (coming soon)`,
288
+ async getConfiguration(existingTools, extendMode) {
289
+ const selectedTools = await this.promptForAITools(existingTools, extendMode);
290
+ return { aiTools: selectedTools };
291
+ }
292
+ async promptForAITools(existingTools, extendMode) {
293
+ const availableTools = AI_TOOLS.filter(tool => tool.available);
294
+ if (availableTools.length === 0) {
295
+ return [];
296
+ }
297
+ const baseMessage = extendMode
298
+ ? 'Which AI tools would you like to add or refresh?'
299
+ : 'Which AI tools do you use?';
300
+ const initialSelected = extendMode
301
+ ? availableTools.filter(tool => existingTools[tool.value]).map(tool => tool.value)
302
+ : [];
303
+ return this.prompt({
304
+ extendMode,
305
+ baseMessage,
306
+ choices: availableTools.map((tool) => ({
50
307
  value: tool.value,
51
- disabled: !tool.available
52
- }))
308
+ label: parseToolLabel(tool.name),
309
+ configured: Boolean(existingTools[tool.value])
310
+ })),
311
+ initialSelected
53
312
  });
54
- config.aiTools = [selectedTool];
55
- return config;
313
+ }
314
+ async getExistingToolStates(projectPath) {
315
+ const states = {};
316
+ for (const tool of AI_TOOLS) {
317
+ states[tool.value] = await this.isToolConfigured(projectPath, tool.value);
318
+ }
319
+ return states;
320
+ }
321
+ async isToolConfigured(projectPath, toolId) {
322
+ const configFile = ToolRegistry.get(toolId)?.configFileName;
323
+ if (configFile && await FileSystemUtils.fileExists(path.join(projectPath, configFile)))
324
+ return true;
325
+ const slashConfigurator = SlashCommandRegistry.get(toolId);
326
+ if (!slashConfigurator)
327
+ return false;
328
+ for (const target of slashConfigurator.getTargets()) {
329
+ if (await FileSystemUtils.fileExists(path.join(projectPath, target.path)))
330
+ return true;
331
+ }
332
+ return false;
56
333
  }
57
334
  async createDirectoryStructure(openspecPath) {
58
335
  const directories = [
@@ -90,25 +367,83 @@ export class InitCommand {
90
367
  }
91
368
  }
92
369
  }
93
- displaySuccessMessage(openspecDir, config) {
370
+ displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode) {
94
371
  console.log(); // Empty line for spacing
95
- ora().succeed('OpenSpec initialized successfully!');
96
- // Get the selected tool name for display
97
- const selectedToolId = config.aiTools[0];
98
- const selectedTool = AI_TOOLS.find(t => t.value === selectedToolId);
99
- const toolName = selectedTool ? selectedTool.name : 'your AI assistant';
100
- console.log(`\nNext steps - Copy these prompts to ${toolName}:\n`);
101
- console.log('────────────────────────────────────────────────────────────');
102
- console.log('1. Populate your project context:');
103
- console.log(' "Please read openspec/project.md and help me fill it out');
104
- console.log(' with details about my project, tech stack, and conventions"\n');
105
- console.log('2. Create your first change proposal:');
106
- console.log(' "I want to add [YOUR FEATURE HERE]. Please create an');
107
- console.log(' OpenSpec change proposal for this feature"\n');
108
- console.log('3. Learn the OpenSpec workflow:');
109
- console.log(' "Please explain the OpenSpec workflow from openspec/AGENTS.md');
110
- console.log(' and how I should work with you on this project"');
111
- console.log('────────────────────────────────────────────────────────────\n');
372
+ const successHeadline = extendMode
373
+ ? 'OpenSpec tool configuration updated!'
374
+ : 'OpenSpec initialized successfully!';
375
+ ora().succeed(PALETTE.white(successHeadline));
376
+ console.log();
377
+ console.log(PALETTE.lightGray('Tool summary:'));
378
+ const summaryLines = [
379
+ created.length ? `${PALETTE.white('β–Œ')} ${PALETTE.white('Created:')} ${this.formatToolNames(created)}` : null,
380
+ refreshed.length ? `${PALETTE.lightGray('β–Œ')} ${PALETTE.lightGray('Refreshed:')} ${this.formatToolNames(refreshed)}` : null,
381
+ skippedExisting.length ? `${PALETTE.midGray('β–Œ')} ${PALETTE.midGray('Skipped (already configured):')} ${this.formatToolNames(skippedExisting)}` : null,
382
+ skipped.length ? `${PALETTE.darkGray('β–Œ')} ${PALETTE.darkGray('Skipped:')} ${this.formatToolNames(skipped)}` : null
383
+ ].filter((line) => Boolean(line));
384
+ for (const line of summaryLines) {
385
+ console.log(line);
386
+ }
387
+ console.log();
388
+ console.log(PALETTE.midGray('Use `openspec update` to refresh shared OpenSpec instructions in the future.'));
389
+ // Get the selected tool name(s) for display
390
+ const toolName = this.formatToolNames(selectedTools);
391
+ console.log();
392
+ console.log(`Next steps - Copy these prompts to ${toolName}:`);
393
+ console.log(chalk.gray('────────────────────────────────────────────────────────────'));
394
+ console.log(PALETTE.white('1. Populate your project context:'));
395
+ console.log(PALETTE.lightGray(' "Please read openspec/project.md and help me fill it out'));
396
+ console.log(PALETTE.lightGray(' with details about my project, tech stack, and conventions"\n'));
397
+ console.log(PALETTE.white('2. Create your first change proposal:'));
398
+ console.log(PALETTE.lightGray(' "I want to add [YOUR FEATURE HERE]. Please create an'));
399
+ console.log(PALETTE.lightGray(' OpenSpec change proposal for this feature"\n'));
400
+ console.log(PALETTE.white('3. Learn the OpenSpec workflow:'));
401
+ console.log(PALETTE.lightGray(' "Please explain the OpenSpec workflow from openspec/AGENTS.md'));
402
+ console.log(PALETTE.lightGray(' and how I should work with you on this project"'));
403
+ console.log(PALETTE.darkGray('────────────────────────────────────────────────────────────\n'));
404
+ }
405
+ formatToolNames(tools) {
406
+ const names = tools
407
+ .map((tool) => tool.successLabel ?? tool.name)
408
+ .filter((name) => Boolean(name));
409
+ if (names.length === 0)
410
+ return PALETTE.lightGray('your AI assistant');
411
+ if (names.length === 1)
412
+ return PALETTE.white(names[0]);
413
+ const base = names.slice(0, -1).map((name) => PALETTE.white(name));
414
+ const last = PALETTE.white(names[names.length - 1]);
415
+ return `${base.join(PALETTE.midGray(', '))}${base.length ? PALETTE.midGray(', and ') : ''}${last}`;
416
+ }
417
+ renderBanner(_extendMode) {
418
+ const rows = ['', '', '', '', ''];
419
+ for (const char of 'OPENSPEC') {
420
+ const glyph = LETTER_MAP[char] ?? LETTER_MAP[' '];
421
+ for (let i = 0; i < rows.length; i += 1) {
422
+ rows[i] += `${glyph[i]} `;
423
+ }
424
+ }
425
+ const rowStyles = [
426
+ PALETTE.white,
427
+ PALETTE.lightGray,
428
+ PALETTE.midGray,
429
+ PALETTE.lightGray,
430
+ PALETTE.white
431
+ ];
432
+ console.log();
433
+ rows.forEach((row, index) => {
434
+ console.log(rowStyles[index](row.replace(/\s+$/u, '')));
435
+ });
436
+ console.log();
437
+ console.log(PALETTE.white('Welcome to OpenSpec!'));
438
+ console.log();
439
+ }
440
+ startSpinner(text) {
441
+ return ora({
442
+ text,
443
+ stream: process.stdout,
444
+ color: 'gray',
445
+ spinner: PROGRESS_SPINNER
446
+ }).start();
112
447
  }
113
448
  }
114
449
  //# sourceMappingURL=init.js.map
@@ -1,2 +1,2 @@
1
- export declare const claudeTemplate = "# OpenSpec Project\n\nThis project uses OpenSpec for spec-driven development. Specifications are the source of truth.\n\nSee @openspec/AGENTS.md for detailed conventions and guidelines.\n\n## Three-Stage Workflow\n\n### Stage 1: Creating Changes\nCreate proposal for: features, breaking changes, architecture changes\nSkip proposal for: bug fixes, typos, non-breaking updates\n\n### Stage 2: Implementing Changes\n1. Read proposal.md to understand the change\n2. Read design.md if it exists for technical context\n3. Read tasks.md for implementation checklist\n4. Complete tasks one by one\n5. Mark each task complete immediately: `- [x]`\n6. Validate strictly: `openspec validate [change] --strict`\n7. Approval gate: Do not start implementation until the proposal is approved\n\n### Stage 3: Archiving\nAfter deployment, use `openspec archive [change]` (add `--skip-specs` for tooling-only changes)\n\n## Before Any Task\n\n**Always:**\n- Check existing specs: `openspec list --specs`\n- Check active changes: `openspec list`\n- Read relevant specs before creating new ones\n- Prefer modifying existing specs over creating duplicates\n\n## CLI Quick Reference\n\n```bash\n# Essential\nopenspec list # Active changes\nopenspec list --specs # Existing specifications\nopenspec show [item] # View details\nopenspec validate --strict # Validate thoroughly\nopenspec archive [change] # Archive after deployment\n\n# Interactive\nopenspec show # Prompts for selection\nopenspec validate # Bulk validation\n\n# Debugging\nopenspec show [change] --json --deltas-only\n```\n\n## Creating Changes\n\n1. **Directory:** `changes/[change-id]/`\n - Change ID naming: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`), unique (append `-2`, `-3` if needed)\n2. **Files:**\n - `proposal.md` - Why, what, impact\n - `tasks.md` - Implementation checklist\n - `design.md` - Only if needed (cross-cutting, new deps/data model, security/perf/migration complexity, or high ambiguity)\n - `specs/[capability]/spec.md` - Delta changes (ADDED/MODIFIED/REMOVED). For multiple capabilities, include multiple files.\n3. **If ambiguous:** ask 1\u20132 clarifying questions before scaffolding\n\n## Search Guidance\n- Enumerate specs: `openspec spec list --long` (or `--json`)\n- Enumerate changes: `openspec list`\n- Show details: `openspec show <spec-id> --type spec`, `openspec show <change-id> --json --deltas-only`\n- Full-text search (use ripgrep): `rg -n \"Requirement:|Scenario:\" openspec/specs`\n\n## Critical: Scenario Format\n\n**CORRECT:**\n```markdown\n#### Scenario: User login\n- **WHEN** valid credentials\n- **THEN** return token\n```\n\n**WRONG:** Using bullets (- **Scenario**), bold (**Scenario:**), or ### headers\n\nEvery requirement MUST have scenarios using `#### Scenario:` format.\n\n## Complexity Management\n\n**Default to minimal:**\n- <100 lines of new code\n- Single-file implementations\n- No frameworks without justification\n- Boring, proven patterns\n\n**Only add complexity with:**\n- Performance data showing need\n- Concrete scale requirements (>1000 users)\n- Multiple proven use cases\n\n## Troubleshooting\n\n**\"Change must have at least one delta\"**\n- Check `changes/[name]/specs/` exists\n- Verify operation prefixes (## ADDED Requirements)\n\n**\"Requirement must have at least one scenario\"**\n- Use `#### Scenario:` format (4 hashtags)\n- Don't use bullets or bold\n\n**Debug:** `openspec show [change] --json --deltas-only`\n";
1
+ export { agentsTemplate as claudeTemplate } from './agents-template.js';
2
2
  //# sourceMappingURL=claude-template.d.ts.map
@@ -1,106 +1,2 @@
1
- export const claudeTemplate = `# OpenSpec Project
2
-
3
- This project uses OpenSpec for spec-driven development. Specifications are the source of truth.
4
-
5
- See @openspec/AGENTS.md for detailed conventions and guidelines.
6
-
7
- ## Three-Stage Workflow
8
-
9
- ### Stage 1: Creating Changes
10
- Create proposal for: features, breaking changes, architecture changes
11
- Skip proposal for: bug fixes, typos, non-breaking updates
12
-
13
- ### Stage 2: Implementing Changes
14
- 1. Read proposal.md to understand the change
15
- 2. Read design.md if it exists for technical context
16
- 3. Read tasks.md for implementation checklist
17
- 4. Complete tasks one by one
18
- 5. Mark each task complete immediately: \`- [x]\`
19
- 6. Validate strictly: \`openspec validate [change] --strict\`
20
- 7. Approval gate: Do not start implementation until the proposal is approved
21
-
22
- ### Stage 3: Archiving
23
- After deployment, use \`openspec archive [change]\` (add \`--skip-specs\` for tooling-only changes)
24
-
25
- ## Before Any Task
26
-
27
- **Always:**
28
- - Check existing specs: \`openspec list --specs\`
29
- - Check active changes: \`openspec list\`
30
- - Read relevant specs before creating new ones
31
- - Prefer modifying existing specs over creating duplicates
32
-
33
- ## CLI Quick Reference
34
-
35
- \`\`\`bash
36
- # Essential
37
- openspec list # Active changes
38
- openspec list --specs # Existing specifications
39
- openspec show [item] # View details
40
- openspec validate --strict # Validate thoroughly
41
- openspec archive [change] # Archive after deployment
42
-
43
- # Interactive
44
- openspec show # Prompts for selection
45
- openspec validate # Bulk validation
46
-
47
- # Debugging
48
- openspec show [change] --json --deltas-only
49
- \`\`\`
50
-
51
- ## Creating Changes
52
-
53
- 1. **Directory:** \`changes/[change-id]/\`
54
- - Change ID naming: kebab-case, verb-led (\`add-\`, \`update-\`, \`remove-\`, \`refactor-\`), unique (append \`-2\`, \`-3\` if needed)
55
- 2. **Files:**
56
- - \`proposal.md\` - Why, what, impact
57
- - \`tasks.md\` - Implementation checklist
58
- - \`design.md\` - Only if needed (cross-cutting, new deps/data model, security/perf/migration complexity, or high ambiguity)
59
- - \`specs/[capability]/spec.md\` - Delta changes (ADDED/MODIFIED/REMOVED). For multiple capabilities, include multiple files.
60
- 3. **If ambiguous:** ask 1–2 clarifying questions before scaffolding
61
-
62
- ## Search Guidance
63
- - Enumerate specs: \`openspec spec list --long\` (or \`--json\`)
64
- - Enumerate changes: \`openspec list\`
65
- - Show details: \`openspec show <spec-id> --type spec\`, \`openspec show <change-id> --json --deltas-only\`
66
- - Full-text search (use ripgrep): \`rg -n "Requirement:|Scenario:" openspec/specs\`
67
-
68
- ## Critical: Scenario Format
69
-
70
- **CORRECT:**
71
- \`\`\`markdown
72
- #### Scenario: User login
73
- - **WHEN** valid credentials
74
- - **THEN** return token
75
- \`\`\`
76
-
77
- **WRONG:** Using bullets (- **Scenario**), bold (**Scenario:**), or ### headers
78
-
79
- Every requirement MUST have scenarios using \`#### Scenario:\` format.
80
-
81
- ## Complexity Management
82
-
83
- **Default to minimal:**
84
- - <100 lines of new code
85
- - Single-file implementations
86
- - No frameworks without justification
87
- - Boring, proven patterns
88
-
89
- **Only add complexity with:**
90
- - Performance data showing need
91
- - Concrete scale requirements (>1000 users)
92
- - Multiple proven use cases
93
-
94
- ## Troubleshooting
95
-
96
- **"Change must have at least one delta"**
97
- - Check \`changes/[name]/specs/\` exists
98
- - Verify operation prefixes (## ADDED Requirements)
99
-
100
- **"Requirement must have at least one scenario"**
101
- - Use \`#### Scenario:\` format (4 hashtags)
102
- - Don't use bullets or bold
103
-
104
- **Debug:** \`openspec show [change] --json --deltas-only\`
105
- `;
1
+ export { agentsTemplate as claudeTemplate } from './agents-template.js';
106
2
  //# sourceMappingURL=claude-template.js.map
@@ -7,6 +7,7 @@ export interface Template {
7
7
  export declare class TemplateManager {
8
8
  static getTemplates(context?: ProjectContext): Template[];
9
9
  static getClaudeTemplate(): string;
10
+ static getAgentsStandardTemplate(): string;
10
11
  static getSlashCommandBody(id: SlashCommandId): string;
11
12
  }
12
13
  export { ProjectContext } from './project-template.js';
@@ -18,6 +18,9 @@ export class TemplateManager {
18
18
  static getClaudeTemplate() {
19
19
  return claudeTemplate;
20
20
  }
21
+ static getAgentsStandardTemplate() {
22
+ return agentsTemplate;
23
+ }
21
24
  static getSlashCommandBody(id) {
22
25
  return getSlashCommandBody(id);
23
26
  }
@@ -1,7 +1,8 @@
1
1
  import path from 'path';
2
2
  import { FileSystemUtils } from '../utils/file-system.js';
3
- import { OPENSPEC_DIR_NAME } from './config.js';
3
+ import { OPENSPEC_DIR_NAME, OPENSPEC_MARKERS } from './config.js';
4
4
  import { agentsTemplate } from './templates/agents-template.js';
5
+ import { TemplateManager } from './templates/index.js';
5
6
  import { ToolRegistry } from './configurators/registry.js';
6
7
  import { SlashCommandRegistry } from './configurators/slash/registry.js';
7
8
  export class UpdateCommand {
@@ -15,7 +16,11 @@ export class UpdateCommand {
15
16
  }
16
17
  // 2. Update AGENTS.md (full replacement)
17
18
  const agentsPath = path.join(openspecPath, 'AGENTS.md');
19
+ const rootAgentsPath = path.join(resolvedProjectPath, 'AGENTS.md');
20
+ const rootAgentsExisted = await FileSystemUtils.fileExists(rootAgentsPath);
18
21
  await FileSystemUtils.writeFile(agentsPath, agentsTemplate);
22
+ const agentsStandardContent = TemplateManager.getAgentsStandardTemplate();
23
+ await FileSystemUtils.updateFileWithMarkers(rootAgentsPath, agentsStandardContent, OPENSPEC_MARKERS.start, OPENSPEC_MARKERS.end);
19
24
  // 3. Update existing AI tool configuration files only
20
25
  const configurators = ToolRegistry.getAll();
21
26
  const slashConfigurators = SlashCommandRegistry.getAll();
@@ -54,7 +59,9 @@ export class UpdateCommand {
54
59
  }
55
60
  }
56
61
  // 4. Success message (ASCII-safe)
57
- const messages = ['Updated OpenSpec instructions (AGENTS.md)'];
62
+ const instructionUpdates = ['openspec/AGENTS.md'];
63
+ instructionUpdates.push(`AGENTS.md${rootAgentsExisted ? '' : ' (created)'}`);
64
+ const messages = [`Updated OpenSpec instructions (${instructionUpdates.join(', ')})`];
58
65
  if (updatedFiles.length > 0) {
59
66
  messages.push(`Updated AI tool files: ${updatedFiles.join(', ')}`);
60
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fission-ai/openspec",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",
@@ -48,6 +48,7 @@
48
48
  "vitest": "^3.2.4"
49
49
  },
50
50
  "dependencies": {
51
+ "@inquirer/core": "^10.2.2",
51
52
  "@inquirer/prompts": "^7.8.0",
52
53
  "chalk": "^5.5.0",
53
54
  "commander": "^14.0.0",