Compare commits

..
14 changed files with 14 additions and 240 deletions
-13
View File
@@ -1,18 +1,5 @@
# @fission-ai/openspec
## 0.16.0
### Minor Changes
- c08fbc1: Add new AI tool integrations and enhancements:
- **feat(iflow-cli)**: Add iFlow-cli integration with slash command support and documentation
- **feat(init)**: Add IDE restart instruction after init to inform users about slash command availability
**feat(antigravity)**: Add Antigravity slash command support
- **fix**: Generate TOML commands for Qwen Code (fixes #293)
- Clarify scaffold proposal documentation and enhance proposal guidelines
- Update proposal guidelines to emphasize design-first approach before implementation
## Unreleased
### Minor Changes
-1
View File
@@ -109,7 +109,6 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
| **Amazon Q Developer** | `@openspec-proposal`, `@openspec-apply`, `@openspec-archive` (`.amazonq/prompts/`) |
| **Auggie (Augment CLI)** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.augment/commands/`) |
| **Qwen Code** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.qwen/commands/`) |
| **iFlow (iflow-cli)** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.iflow/commands/`) |
Kilo Code discovers team workflows automatically. Save the generated files under `.kilocode/workflows/` and trigger them from the command palette with `/openspec-proposal.md`, `/openspec-apply.md`, or `/openspec-archive.md`.
-20
View File
@@ -76,12 +76,6 @@ The command SHALL properly configure selected AI tools with OpenSpec-specific in
- **THEN** create or update `CLINE.md` in the project root directory (not inside openspec/)
- **AND** populate the managed block with a short stub that points teammates to `@/openspec/AGENTS.md`
#### Scenario: Configuring iFlow CLI
- **WHEN** iFlow CLI is selected
- **THEN** create or update `IFLOW.md` in the project root directory (not inside openspec/)
- **AND** populate the managed block with a short stub that points teammates to `@/openspec/AGENTS.md`
#### Scenario: Creating new CLAUDE.md
- **WHEN** CLAUDE.md does not exist
@@ -124,12 +118,6 @@ The command SHALL provide clear, actionable next steps upon successful initializ
- **WHEN** initialization completes successfully
- **THEN** include prompt: "Please explain the OpenSpec workflow from openspec/AGENTS.md and how I should work with you on this project"
#### Scenario: Displaying restart instruction
- **WHEN** initialization completes successfully and tools were created or refreshed
- **THEN** display a prominent restart instruction before the "Next steps" section
- **AND** inform users that slash commands are loaded at startup
- **AND** instruct users to restart their coding assistant to ensure /openspec commands appear
### Requirement: Exit Codes
The command SHALL use consistent exit codes to indicate different failure modes.
@@ -250,14 +238,6 @@ The init command SHALL generate slash command files for supported editors using
- **AND** wrap the OpenSpec managed markers (`<!-- OPENSPEC:START -->` / `<!-- OPENSPEC:END -->`) inside the `prompt` value so `openspec update` can safely refresh the body between markers without touching the TOML framing
- **AND** ensure the slash-command copy matches the existing proposal/apply/archive templates used by other tools
#### Scenario: Generating slash commands for iFlow CLI
- **WHEN** the user selects iFlow CLI during initialization
- **THEN** create `.iflow/commands/openspec-proposal.md`, `.iflow/commands/openspec-apply.md`, and `.iflow/commands/openspec-archive.md`
- **AND** populate each file from shared templates so command text matches other tools
- **AND** include YAML frontmatter with `name`, `id`, `category`, and `description` fields for each command
- **AND** wrap the generated content in OpenSpec managed markers so `openspec update` can safely refresh the commands
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
#### Scenario: Generating slash commands for RooCode
- **WHEN** the user selects RooCode during initialization
- **THEN** create `.roo/commands/openspec-proposal.md`, `.roo/commands/openspec-apply.md`, and `.roo/commands/openspec-archive.md`
-7
View File
@@ -123,13 +123,6 @@ The update command SHALL refresh existing slash command files for configured too
- **AND** replace only the content between `<!-- OPENSPEC:START -->` and `<!-- OPENSPEC:END -->` markers inside the `prompt = """` block so the TOML framing (`description`, `prompt`) stays intact
- **AND** skip creating any missing `.toml` files during update; only pre-existing Gemini commands are refreshed
#### Scenario: Updating slash commands for iFlow CLI
- **WHEN** `.iflow/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
- **THEN** refresh each file using shared templates
- **AND** preserve the YAML frontmatter with `name`, `id`, `category`, and `description` fields
- **AND** update only the OpenSpec-managed block between markers
- **AND** ensure templates include instructions for the relevant workflow stage
#### Scenario: Missing slash command file
- **WHEN** a tool lacks a slash command file
- **THEN** do not create a new file during update
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@fission-ai/openspec",
"version": "0.16.0",
"version": "0.15.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
+7 -8
View File
@@ -17,25 +17,24 @@ export interface AIToolOption {
}
export const AI_TOOLS: AIToolOption[] = [
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity' },
{ name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie' },
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code' },
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline' },
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode' },
{ name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code' },
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict' },
{ name: 'Crush', value: 'crush', available: true, successLabel: 'Crush' },
{ name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor' },
{ name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid' },
{ name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI' },
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow' },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
{ name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' },
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code' },
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode' },
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity' },
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code' },
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
];
-23
View File
@@ -1,23 +0,0 @@
import path from "path";
import { ToolConfigurator } from "./base.js";
import { FileSystemUtils } from "../../utils/file-system.js";
import { TemplateManager } from "../templates/index.js";
import { OPENSPEC_MARKERS } from "../config.js";
export class IflowConfigurator implements ToolConfigurator {
name = "iFlow";
configFileName = "IFLOW.md";
isAvailable = true;
async configure(projectPath: string, openspecDir: string): Promise<void> {
const filePath = path.join(projectPath, this.configFileName);
const content = TemplateManager.getClaudeTemplate();
await FileSystemUtils.updateFileWithMarkers(
filePath,
content,
OPENSPEC_MARKERS.start,
OPENSPEC_MARKERS.end
);
}
}
-3
View File
@@ -4,7 +4,6 @@ import { ClineConfigurator } from './cline.js';
import { CodeBuddyConfigurator } from './codebuddy.js';
import { CostrictConfigurator } from './costrict.js';
import { QoderConfigurator } from './qoder.js';
import { IflowConfigurator } from './iflow.js';
import { AgentsStandardConfigurator } from './agents.js';
import { QwenConfigurator } from './qwen.js';
@@ -17,7 +16,6 @@ export class ToolRegistry {
const codeBuddyConfigurator = new CodeBuddyConfigurator();
const costrictConfigurator = new CostrictConfigurator();
const qoderConfigurator = new QoderConfigurator();
const iflowConfigurator = new IflowConfigurator();
const agentsConfigurator = new AgentsStandardConfigurator();
const qwenConfigurator = new QwenConfigurator();
// Register with the ID that matches the checkbox value
@@ -26,7 +24,6 @@ export class ToolRegistry {
this.tools.set('codebuddy', codeBuddyConfigurator);
this.tools.set('costrict', costrictConfigurator);
this.tools.set('qoder', qoderConfigurator);
this.tools.set('iflow', iflowConfigurator);
this.tools.set('agents', agentsConfigurator);
this.tools.set('qwen', qwenConfigurator);
}
-42
View File
@@ -1,42 +0,0 @@
import { SlashCommandConfigurator } from './base.js';
import { SlashCommandId } from '../../templates/index.js';
const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: '.iflow/commands/openspec-proposal.md',
apply: '.iflow/commands/openspec-apply.md',
archive: '.iflow/commands/openspec-archive.md'
};
const FRONTMATTER: Record<SlashCommandId, string> = {
proposal: `---
name: /openspec-proposal
id: openspec-proposal
category: OpenSpec
description: Scaffold a new OpenSpec change and validate strictly.
---`,
apply: `---
name: /openspec-apply
id: openspec-apply
category: OpenSpec
description: Implement an approved OpenSpec change and keep tasks in sync.
---`,
archive: `---
name: /openspec-archive
id: openspec-archive
category: OpenSpec
description: Archive a deployed OpenSpec change and update specs.
---`
};
export class IflowSlashCommandConfigurator extends SlashCommandConfigurator {
readonly toolId = 'iflow';
readonly isAvailable = true;
protected getRelativePath(id: SlashCommandId): string {
return FILE_PATHS[id];
}
protected getFrontmatter(id: SlashCommandId): string {
return FRONTMATTER[id];
}
}
+3
View File
@@ -11,6 +11,7 @@ const FILE_PATHS: Record<SlashCommandId, string> = {
const FRONTMATTER: Record<SlashCommandId, string> = {
proposal: `---
agent: build
description: Scaffold a new OpenSpec change and validate strictly.
---
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
@@ -19,6 +20,7 @@ The user has requested the following change proposal. Use the openspec instructi
</UserRequest>
`,
apply: `---
agent: build
description: Implement an approved OpenSpec change and keep tasks in sync.
---
The user has requested to implement the following change proposal. Find the change proposal and follow the instructions below. If you're not sure or if ambiguous, ask for clarification from the user.
@@ -27,6 +29,7 @@ The user has requested to implement the following change proposal. Find the chan
</UserRequest>
`,
archive: `---
agent: build
description: Archive a deployed OpenSpec change and update specs.
---
<ChangeId>
-3
View File
@@ -18,7 +18,6 @@ import { CostrictSlashCommandConfigurator } from './costrict.js';
import { QwenSlashCommandConfigurator } from './qwen.js';
import { RooCodeSlashCommandConfigurator } from './roocode.js';
import { AntigravitySlashCommandConfigurator } from './antigravity.js';
import { IflowSlashCommandConfigurator } from './iflow.js';
export class SlashCommandRegistry {
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
@@ -43,7 +42,6 @@ export class SlashCommandRegistry {
const qwen = new QwenSlashCommandConfigurator();
const roocode = new RooCodeSlashCommandConfigurator();
const antigravity = new AntigravitySlashCommandConfigurator();
const iflow = new IflowSlashCommandConfigurator();
this.configurators.set(claude.toolId, claude);
this.configurators.set(codeBuddy.toolId, codeBuddy);
@@ -64,7 +62,6 @@ export class SlashCommandRegistry {
this.configurators.set(qwen.toolId, qwen);
this.configurators.set(roocode.toolId, roocode);
this.configurators.set(antigravity.toolId, antigravity);
this.configurators.set(iflow.toolId, iflow);
}
static register(configurator: SlashCommandConfigurator): void {
-16
View File
@@ -862,22 +862,6 @@ export class InitCommand {
)
);
// Show restart instruction if any tools were configured
if (created.length > 0 || refreshed.length > 0) {
console.log();
console.log(PALETTE.white('Important: Restart your IDE'));
console.log(
PALETTE.midGray(
'Slash commands are loaded at startup. Please restart your coding assistant'
)
);
console.log(
PALETTE.midGray(
'to ensure the new /openspec commands appear in your command palette.'
)
);
}
// Get the selected tool name(s) for display
const toolName = this.formatToolNames(selectedTools);
+3 -56
View File
@@ -416,59 +416,6 @@ describe('InitCommand', () => {
expect(updatedContent).not.toContain('Custom instruction added by user');
});
it('should create IFlow CLI slash command files with templates', async () => {
queueSelections('iflow', DONE);
await initCommand.execute(testDir);
const iflowProposal = path.join(
testDir,
'.iflow/commands/openspec-proposal.md'
);
const iflowApply = path.join(
testDir,
'.iflow/commands/openspec-apply.md'
);
const iflowArchive = path.join(
testDir,
'.iflow/commands/openspec-archive.md'
);
expect(await fileExists(iflowProposal)).toBe(true);
expect(await fileExists(iflowApply)).toBe(true);
expect(await fileExists(iflowArchive)).toBe(true);
const proposalContent = await fs.readFile(iflowProposal, 'utf-8');
expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
expect(proposalContent).toContain('**Guardrails**');
expect(proposalContent).toContain('<!-- OPENSPEC:END -->');
const applyContent = await fs.readFile(iflowApply, 'utf-8');
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
expect(applyContent).toContain('Work through tasks sequentially');
const archiveContent = await fs.readFile(iflowArchive, 'utf-8');
expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.');
expect(archiveContent).toContain('openspec archive <id>');
});
it('should update existing IFLOW.md with markers', async () => {
queueSelections('iflow', DONE);
const iflowPath = path.join(testDir, 'IFLOW.md');
const existingContent = '# My IFLOW Instructions\nCustom instructions here';
await fs.writeFile(iflowPath, existingContent);
await initCommand.execute(testDir);
const updatedContent = await fs.readFile(iflowPath, 'utf-8');
expect(updatedContent).toContain('<!-- OPENSPEC:START -->');
expect(updatedContent).toContain("@/openspec/AGENTS.md");
expect(updatedContent).toContain('openspec update');
expect(updatedContent).toContain('<!-- OPENSPEC:END -->');
expect(updatedContent).toContain('Custom instructions here');
});
it('should create OpenCode slash command files with templates', async () => {
queueSelections('opencode', DONE);
@@ -492,21 +439,21 @@ describe('InitCommand', () => {
expect(await fileExists(openCodeArchive)).toBe(true);
const proposalContent = await fs.readFile(openCodeProposal, 'utf-8');
expect(proposalContent).not.toContain('agent:');
expect(proposalContent).toContain('agent: build');
expect(proposalContent).toContain(
'description: Scaffold a new OpenSpec change and validate strictly.'
);
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
const applyContent = await fs.readFile(openCodeApply, 'utf-8');
expect(applyContent).not.toContain('agent:');
expect(applyContent).toContain('agent: build');
expect(applyContent).toContain(
'description: Implement an approved OpenSpec change and keep tasks in sync.'
);
expect(applyContent).toContain('Work through tasks sequentially');
const archiveContent = await fs.readFile(openCodeArchive, 'utf-8');
expect(archiveContent).not.toContain('agent:');
expect(archiveContent).toContain('agent: build');
expect(archiveContent).toContain(
'description: Archive a deployed OpenSpec change and update specs.'
);
-47
View File
@@ -663,53 +663,6 @@ Old Gemini body
consoleSpy.mockRestore();
});
it('should refresh existing IFLOW slash commands', async () => {
const iflowProposal = path.join(
testDir,
'.iflow/commands/openspec-proposal.md'
);
await fs.mkdir(path.dirname(iflowProposal), { recursive: true });
const initialContent = `description: Scaffold a new OpenSpec change and validate strictly."
prompt = """
<!-- OPENSPEC:START -->
Old IFlow body
<!-- OPENSPEC:END -->
"""
`;
await fs.writeFile(iflowProposal, initialContent);
const consoleSpy = vi.spyOn(console, 'log');
await updateCommand.execute(testDir);
const updated = await fs.readFile(iflowProposal, 'utf-8');
expect(updated).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
expect(updated).toContain('<!-- OPENSPEC:START -->');
expect(updated).toContain('**Guardrails**');
expect(updated).toContain('<!-- OPENSPEC:END -->');
expect(updated).not.toContain('Old IFlow body');
const iflowApply = path.join(
testDir,
'.iflow/commands/openspec-apply.md'
);
const iflowArchive = path.join(
testDir,
'.iflow/commands/openspec-archive.md'
);
await expect(FileSystemUtils.fileExists(iflowApply)).resolves.toBe(false);
await expect(FileSystemUtils.fileExists(iflowArchive)).resolves.toBe(false);
const [logMessage] = consoleSpy.mock.calls[0];
expect(logMessage).toContain(
'Updated slash commands: .iflow/commands/openspec-proposal.md'
);
consoleSpy.mockRestore();
});
it('should refresh existing Factory slash commands', async () => {
const factoryPath = path.join(