Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
| **GitHub Copilot** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.github/prompts/`) |
| **Amazon Q Developer** | `@openspec-proposal`, `@openspec-apply`, `@openspec-archive` (`.amazonq/prompts/`) |
| **Auggie (Augment CLI)** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.augment/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`.

Expand Down
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface AIToolOption {
}

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: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor' },
{ name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid' },
Expand Down
37 changes: 37 additions & 0 deletions src/core/configurators/slash/auggie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { SlashCommandConfigurator } from './base.js';
import { SlashCommandId } from '../../templates/index.js';

const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: '.augment/commands/openspec-proposal.md',
apply: '.augment/commands/openspec-apply.md',
archive: '.augment/commands/openspec-archive.md'
};

const FRONTMATTER: Record<SlashCommandId, string> = {
proposal: `---
description: Scaffold a new OpenSpec change and validate strictly.
argument-hint: feature description or request
---`,
apply: `---
description: Implement an approved OpenSpec change and keep tasks in sync.
argument-hint: change-id
---`,
archive: `---
description: Archive a deployed OpenSpec change and update specs.
argument-hint: change-id
---`
};

export class AuggieSlashCommandConfigurator extends SlashCommandConfigurator {
readonly toolId = 'auggie';
readonly isAvailable = true;

protected getRelativePath(id: SlashCommandId): string {
return FILE_PATHS[id];
}

protected getFrontmatter(id: SlashCommandId): string {
return FRONTMATTER[id];
}
}

3 changes: 3 additions & 0 deletions src/core/configurators/slash/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CodexSlashCommandConfigurator } from './codex.js';
import { GitHubCopilotSlashCommandConfigurator } from './github-copilot.js';
import { AmazonQSlashCommandConfigurator } from './amazon-q.js';
import { FactorySlashCommandConfigurator } from './factory.js';
import { AuggieSlashCommandConfigurator } from './auggie.js';

export class SlashCommandRegistry {
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
Expand All @@ -22,6 +23,7 @@ export class SlashCommandRegistry {
const githubCopilot = new GitHubCopilotSlashCommandConfigurator();
const amazonQ = new AmazonQSlashCommandConfigurator();
const factory = new FactorySlashCommandConfigurator();
const auggie = new AuggieSlashCommandConfigurator();

this.configurators.set(claude.toolId, claude);
this.configurators.set(cursor.toolId, cursor);
Expand All @@ -32,6 +34,7 @@ export class SlashCommandRegistry {
this.configurators.set(githubCopilot.toolId, githubCopilot);
this.configurators.set(amazonQ.toolId, amazonQ);
this.configurators.set(factory.toolId, factory);
this.configurators.set(auggie.toolId, auggie);
}

static register(configurator: SlashCommandConfigurator): void {
Expand Down
54 changes: 54 additions & 0 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,60 @@ describe('InitCommand', () => {
);
expect(amazonQChoice.configured).toBe(true);
});

it('should create Auggie slash command files with templates', async () => {
queueSelections('auggie', DONE);

await initCommand.execute(testDir);

const auggieProposal = path.join(
testDir,
'.augment/commands/openspec-proposal.md'
);
const auggieApply = path.join(
testDir,
'.augment/commands/openspec-apply.md'
);
const auggieArchive = path.join(
testDir,
'.augment/commands/openspec-archive.md'
);

expect(await fileExists(auggieProposal)).toBe(true);
expect(await fileExists(auggieApply)).toBe(true);
expect(await fileExists(auggieArchive)).toBe(true);

const proposalContent = await fs.readFile(auggieProposal, 'utf-8');
expect(proposalContent).toContain('---');
expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
expect(proposalContent).toContain('argument-hint: feature description or request');
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
expect(proposalContent).toContain('**Guardrails**');

const applyContent = await fs.readFile(auggieApply, 'utf-8');
expect(applyContent).toContain('---');
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
expect(applyContent).toContain('argument-hint: change-id');
expect(applyContent).toContain('Work through tasks sequentially');

const archiveContent = await fs.readFile(auggieArchive, 'utf-8');
expect(archiveContent).toContain('---');
expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.');
expect(archiveContent).toContain('argument-hint: change-id');
expect(archiveContent).toContain('openspec archive <id> --yes');
});

it('should mark Auggie as already configured during extend mode', async () => {
queueSelections('auggie', DONE, 'auggie', DONE);
await initCommand.execute(testDir);
await initCommand.execute(testDir);

const secondRunArgs = mockPrompt.mock.calls[1][0];
const auggieChoice = secondRunArgs.choices.find(
(choice: any) => choice.value === 'auggie'
);
expect(auggieChoice.configured).toBe(true);
});
});

describe('non-interactive mode', () => {
Expand Down
61 changes: 61 additions & 0 deletions test/core/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,67 @@ Old body
await expect(FileSystemUtils.fileExists(aqArchive)).resolves.toBe(false);
});

it('should refresh existing Auggie slash command files', async () => {
const auggiePath = path.join(
testDir,
'.augment/commands/openspec-apply.md'
);
await fs.mkdir(path.dirname(auggiePath), { recursive: true });
const initialContent = `---
description: Implement an approved OpenSpec change and keep tasks in sync.
argument-hint: change-id
---
<!-- OPENSPEC:START -->
Old body
<!-- OPENSPEC:END -->`;
await fs.writeFile(auggiePath, initialContent);

const consoleSpy = vi.spyOn(console, 'log');

await updateCommand.execute(testDir);

const updatedContent = await fs.readFile(auggiePath, 'utf-8');
expect(updatedContent).toContain('**Guardrails**');
expect(updatedContent).toContain('<!-- OPENSPEC:START -->');
expect(updatedContent).toContain('<!-- OPENSPEC:END -->');
expect(updatedContent).not.toContain('Old body');

expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('.augment/commands/openspec-apply.md')
);

consoleSpy.mockRestore();
});

it('should not create missing Auggie slash command files on update', async () => {
const auggieApply = path.join(
testDir,
'.augment/commands/openspec-apply.md'
);

// Only create apply; leave proposal and archive missing
await fs.mkdir(path.dirname(auggieApply), { recursive: true });
await fs.writeFile(
auggieApply,
'---\ndescription: Old\nargument-hint: old\n---\n<!-- OPENSPEC:START -->\nOld\n<!-- OPENSPEC:END -->'
);

await updateCommand.execute(testDir);

const auggieProposal = path.join(
testDir,
'.augment/commands/openspec-proposal.md'
);
const auggieArchive = path.join(
testDir,
'.augment/commands/openspec-archive.md'
);

// Confirm they weren't created by update
await expect(FileSystemUtils.fileExists(auggieProposal)).resolves.toBe(false);
await expect(FileSystemUtils.fileExists(auggieArchive)).resolves.toBe(false);
});

it('should preserve Windsurf content outside markers during update', async () => {
const wsPath = path.join(
testDir,
Expand Down
Loading