Compare commits

..
Author SHA1 Message Date
Tabish Bidiwale 4afafcb2a3 Document Gemini CLI slash updates 2025-11-11 21:33:41 +11:00
13 changed files with 14 additions and 222 deletions
-11
View File
@@ -1,16 +1,5 @@
# @fission-ai/openspec
## 0.15.0
### Minor Changes
- 4758c5c: Add support for new AI tools with native slash command integration
- **Gemini CLI**: Add native TOML-based slash command support for Gemini CLI with `.gemini/commands/openspec/` integration
- **RooCode**: Add RooCode integration with configurator, slash commands, and templates
- **Cline**: Fix Cline to use workflows instead of rules for slash commands (`.clinerules/workflows/` paths)
- **Documentation**: Update documentation to reflect new integrations and workflow changes
## 0.14.0
### Minor Changes
+2 -3
View File
@@ -94,9 +94,8 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
| **CodeBuddy Code (CLI)** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.codebuddy/commands/`) — see [docs](https://www.codebuddy.ai/cli) |
| **CoStrict** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.cospec/openspec/commands/`) — see [docs](https://costrict.ai)|
| **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
| **Cline** | Workflows in `.clinerules/workflows/` directory (`.clinerules/workflows/openspec-*.md`) |
| **Cline** | Rules in `.clinerules/` directory (`.clinerules/openspec-*.md`) |
| **Crush** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.crush/commands/openspec/`) |
| **RooCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.roo/commands/`) |
| **Factory Droid** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.factory/commands/`) |
| **Gemini CLI** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.gemini/commands/openspec/`) |
| **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
@@ -234,7 +233,7 @@ Or run the command yourself in terminal:
$ openspec archive add-profile-filters --yes # Archive the completed change without prompts
```
**Note:** Tools with native slash commands (Claude Code, CodeBuddy, Cursor, Codex, Qoder, RooCode) 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".
**Note:** Tools with native slash commands (Claude Code, CodeBuddy, Cursor, Codex, Qoder) 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".
## Command Reference
@@ -1,13 +0,0 @@
## Why
The Cline implementation was architecturally incorrect. According to Cline's official documentation, Cline uses workflows for on-demand automation and rules for behavioral guidelines. The OpenSpec slash commands are procedural workflows (scaffold → implement → archive), not behavioral rules, so they should be placed in `.clinerules/workflows/` instead of `.clinerules/`.
## What Changes
- Update ClineSlashCommandConfigurator to use `.clinerules/workflows/` paths instead of `.clinerules/` paths
- Update all tests to expect the correct workflow file locations
- Update README.md documentation to reflect workflows instead of rules
- **BREAKING**: Existing Cline users will need to re-run `openspec init` to get the corrected workflow files
## Impact
- Affected specs: cli-init (corrected Cline workflow paths)
- Affected code: `src/core/configurators/slash/cline.ts`, test files, README.md
- Modified files: `.clinerules/workflows/openspec-*.md` (moved from `.clinerules/openspec-*.md`)
@@ -1,11 +0,0 @@
# Delta for CLI Init
## MODIFIED Requirements
### Requirement: Slash Command Configuration
#### Scenario: Generating slash commands for Cline
- **WHEN** the user selects Cline during initialization
- **THEN** create `.clinerules/workflows/openspec-proposal.md`, `.clinerules/workflows/openspec-apply.md`, and `.clinerules/workflows/openspec-archive.md`
- **AND** populate each file from shared templates so command text matches other tools
- **AND** include Cline-specific Markdown heading frontmatter
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
@@ -1,13 +0,0 @@
## 1. Update ClineSlashCommandConfigurator
- [x] Change FILE_PATHS in `src/core/configurators/slash/cline.ts` from `.clinerules/openspec-*.md` to `.clinerules/workflows/openspec-*.md`
## 2. Update Tests
- [x] Update "should refresh existing Cline rule files" test in `test/core/update.test.ts` to use workflow paths
- [x] Update "should create Cline rule files with templates" test in `test/core/init.test.ts` to use workflow paths
## 3. Update Documentation
- [x] Update README.md table to show "Workflows in `.clinerules/workflows/` directory" for Cline
## 4. Validate Changes
- [x] Ensure all tests pass with the new paths
- [x] Verify the change follows OpenSpec conventions
-8
View File
@@ -238,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 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`
- **AND** populate each file from shared templates so command text matches other tools
- **AND** include simple Markdown headings (e.g., `# OpenSpec: Proposal`) without YAML frontmatter
- **AND** wrap the generated content in OpenSpec managed markers where applicable so `openspec update` can safely refresh the commands
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
### Requirement: Non-Interactive Mode
The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@fission-ai/openspec",
"version": "0.15.0",
"version": "0.14.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
-1
View File
@@ -20,7 +20,6 @@ export const AI_TOOLS: AIToolOption[] = [
{ 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: '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' },
+3 -3
View File
@@ -2,9 +2,9 @@ import { SlashCommandConfigurator } from './base.js';
import { SlashCommandId } from '../../templates/index.js';
const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: '.clinerules/workflows/openspec-proposal.md',
apply: '.clinerules/workflows/openspec-apply.md',
archive: '.clinerules/workflows/openspec-archive.md'
proposal: '.clinerules/openspec-proposal.md',
apply: '.clinerules/openspec-apply.md',
archive: '.clinerules/openspec-archive.md'
};
export class ClineSlashCommandConfigurator extends SlashCommandConfigurator {
-3
View File
@@ -16,7 +16,6 @@ import { ClineSlashCommandConfigurator } from './cline.js';
import { CrushSlashCommandConfigurator } from './crush.js';
import { CostrictSlashCommandConfigurator } from './costrict.js';
import { QwenSlashCommandConfigurator } from './qwen.js';
import { RooCodeSlashCommandConfigurator } from './roocode.js';
export class SlashCommandRegistry {
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
@@ -39,7 +38,6 @@ export class SlashCommandRegistry {
const crush = new CrushSlashCommandConfigurator();
const costrict = new CostrictSlashCommandConfigurator();
const qwen = new QwenSlashCommandConfigurator();
const roocode = new RooCodeSlashCommandConfigurator();
this.configurators.set(claude.toolId, claude);
this.configurators.set(codeBuddy.toolId, codeBuddy);
@@ -58,7 +56,6 @@ export class SlashCommandRegistry {
this.configurators.set(crush.toolId, crush);
this.configurators.set(costrict.toolId, costrict);
this.configurators.set(qwen.toolId, qwen);
this.configurators.set(roocode.toolId, roocode);
}
static register(configurator: SlashCommandConfigurator): void {
-27
View File
@@ -1,27 +0,0 @@
import { SlashCommandConfigurator } from './base.js';
import { SlashCommandId } from '../../templates/index.js';
const NEW_FILE_PATHS: Record<SlashCommandId, string> = {
proposal: '.roo/commands/openspec-proposal.md',
apply: '.roo/commands/openspec-apply.md',
archive: '.roo/commands/openspec-archive.md'
};
export class RooCodeSlashCommandConfigurator extends SlashCommandConfigurator {
readonly toolId = 'roocode';
readonly isAvailable = true;
protected getRelativePath(id: SlashCommandId): string {
return NEW_FILE_PATHS[id];
}
protected getFrontmatter(id: SlashCommandId): string | undefined {
const descriptions: Record<SlashCommandId, string> = {
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
archive: 'Archive a deployed OpenSpec change and update specs.'
};
const description = descriptions[id];
return `# OpenSpec: ${id.charAt(0).toUpperCase() + id.slice(1)}\n\n${description}`;
}
}
+5 -51
View File
@@ -481,22 +481,22 @@ describe('InitCommand', () => {
expect(updatedContent).toContain('Custom instructions here');
});
it('should create Cline workflow files with templates', async () => {
it('should create Cline rule files with templates', async () => {
queueSelections('cline', DONE);
await initCommand.execute(testDir);
const clineProposal = path.join(
testDir,
'.clinerules/workflows/openspec-proposal.md'
'.clinerules/openspec-proposal.md'
);
const clineApply = path.join(
testDir,
'.clinerules/workflows/openspec-apply.md'
'.clinerules/openspec-apply.md'
);
const clineArchive = path.join(
testDir,
'.clinerules/workflows/openspec-archive.md'
'.clinerules/openspec-archive.md'
);
expect(await fileExists(clineProposal)).toBe(true);
@@ -1194,53 +1194,6 @@ describe('InitCommand', () => {
expect(costrictChoice.configured).toBe(true);
});
it('should create RooCode slash command files with templates', async () => {
queueSelections('roocode', DONE);
await initCommand.execute(testDir);
const rooProposal = path.join(
testDir,
'.roo/commands/openspec-proposal.md'
);
const rooApply = path.join(
testDir,
'.roo/commands/openspec-apply.md'
);
const rooArchive = path.join(
testDir,
'.roo/commands/openspec-archive.md'
);
expect(await fileExists(rooProposal)).toBe(true);
expect(await fileExists(rooApply)).toBe(true);
expect(await fileExists(rooArchive)).toBe(true);
const proposalContent = await fs.readFile(rooProposal, 'utf-8');
expect(proposalContent).toContain('# OpenSpec: Proposal');
expect(proposalContent).toContain('**Guardrails**');
const applyContent = await fs.readFile(rooApply, 'utf-8');
expect(applyContent).toContain('# OpenSpec: Apply');
expect(applyContent).toContain('Work through tasks sequentially');
const archiveContent = await fs.readFile(rooArchive, 'utf-8');
expect(archiveContent).toContain('# OpenSpec: Archive');
expect(archiveContent).toContain('openspec archive <id> --yes');
});
it('should mark RooCode as already configured during extend mode', async () => {
queueSelections('roocode', DONE, 'roocode', DONE);
await initCommand.execute(testDir);
await initCommand.execute(testDir);
const secondRunArgs = mockPrompt.mock.calls[1][0];
const rooChoice = secondRunArgs.choices.find(
(choice: any) => choice.value === 'roocode'
);
expect(rooChoice.configured).toBe(true);
});
it('should create Qoder slash command files with templates', async () => {
queueSelections('qoder', DONE);
@@ -1325,6 +1278,7 @@ describe('InitCommand', () => {
expect(content).toContain('openspec update');
expect(content).toContain('<!-- OPENSPEC:END -->');
});
it('should update existing COSTRICT.md with markers', async () => {
queueSelections('costrict', DONE);
+3 -77
View File
@@ -298,10 +298,10 @@ More rules after.`;
expect(fileExists).toBe(false);
});
it('should refresh existing Cline workflow files', async () => {
it('should refresh existing Cline rule files', async () => {
const proposalPath = path.join(
testDir,
'.clinerules/workflows/openspec-proposal.md'
'.clinerules/openspec-proposal.md'
);
await fs.mkdir(path.dirname(proposalPath), { recursive: true });
const initialContent = `# OpenSpec: Proposal
@@ -331,7 +331,7 @@ Old slash content
);
expect(logMessage).toContain('AGENTS.md (created)');
expect(logMessage).toContain(
'Updated slash commands: .clinerules/workflows/openspec-proposal.md'
'Updated slash commands: .clinerules/openspec-proposal.md'
);
consoleSpy.mockRestore();
@@ -1071,79 +1071,6 @@ Old slash content
consoleSpy.mockRestore();
});
it('should refresh existing RooCode slash command files', async () => {
const rooPath = path.join(
testDir,
'.roo/commands/openspec-proposal.md'
);
await fs.mkdir(path.dirname(rooPath), { recursive: true });
const initialContent = `# OpenSpec: Proposal
Old description
<!-- OPENSPEC:START -->
Old body
<!-- OPENSPEC:END -->`;
await fs.writeFile(rooPath, initialContent);
const consoleSpy = vi.spyOn(console, 'log');
await updateCommand.execute(testDir);
const updated = await fs.readFile(rooPath, 'utf-8');
// For RooCode, the header is Markdown, preserve it and update only managed block
expect(updated).toContain('# OpenSpec: Proposal');
expect(updated).toContain('**Guardrails**');
expect(updated).toContain(
'Validate with `openspec validate <id> --strict`'
);
expect(updated).not.toContain('Old body');
const [logMessage] = consoleSpy.mock.calls[0];
expect(logMessage).toContain(
'Updated OpenSpec instructions (openspec/AGENTS.md'
);
expect(logMessage).toContain('AGENTS.md (created)');
expect(logMessage).toContain(
'Updated slash commands: .roo/commands/openspec-proposal.md'
);
consoleSpy.mockRestore();
});
it('should not create missing RooCode slash command files on update', async () => {
const rooApply = path.join(
testDir,
'.roo/commands/openspec-apply.md'
);
// Only create apply; leave proposal and archive missing
await fs.mkdir(path.dirname(rooApply), { recursive: true });
await fs.writeFile(
rooApply,
`# OpenSpec: Apply
<!-- OPENSPEC:START -->
Old body
<!-- OPENSPEC:END -->`
);
await updateCommand.execute(testDir);
const rooProposal = path.join(
testDir,
'.roo/commands/openspec-proposal.md'
);
const rooArchive = path.join(
testDir,
'.roo/commands/openspec-archive.md'
);
// Confirm they weren't created by update
await expect(FileSystemUtils.fileExists(rooProposal)).resolves.toBe(false);
await expect(FileSystemUtils.fileExists(rooArchive)).resolves.toBe(false);
});
it('should not create missing CoStrict slash command files on update', async () => {
const costrictApply = path.join(
testDir,
@@ -1254,7 +1181,6 @@ More instructions after.`;
consoleSpy.mockRestore();
});
it('should not create COSTRICT.md if it does not exist', async () => {
// Ensure COSTRICT.md does not exist
const costrictPath = path.join(testDir, 'COSTRICT.md');