Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion docs/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch
| Mistral Vibe (`vibe`) | `.vibe/skills/openspec-*/SKILL.md` | Not generated (no command adapter; use skill-based `/openspec-*` invocations) |
| OpenCode (`opencode`) | `.opencode/skills/openspec-*/SKILL.md` | `.opencode/commands/opsx-<id>.md` |
| Pi (`pi`) | `.pi/skills/openspec-*/SKILL.md` | `.pi/prompts/opsx-<id>.md` |
| SourceCraft Code Assistant (`codeassistant`) | `.codeassistant/skills/openspec-*/SKILL.md` | `.codeassistant/commands/opsx-<id>.md` |
Comment thread
aleksandr4842 marked this conversation as resolved.
| Qoder (`qoder`) | `.qoder/skills/openspec-*/SKILL.md` | `.qoder/commands/opsx/<id>.md` |
| Qwen Code (`qwen`) | `.qwen/skills/openspec-*/SKILL.md` | `.qwen/commands/opsx-<id>.toml` |
| RooCode (`roocode`) | `.roo/skills/openspec-*/SKILL.md` | `.roo/commands/opsx-<id>.md` |
Expand Down Expand Up @@ -75,7 +76,7 @@ openspec init --tools none
openspec init --profile core
```

**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `vibe`, `windsurf`
**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `codeassistant`, `trae`, `vibe`, `windsurf`

## Workflow-Dependent Installation

Expand Down
33 changes: 33 additions & 0 deletions src/core/command-generation/adapters/codeassistant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* SourceCraft Code Assistant Command Adapter
*
* Formats commands for SourceCraft Code Assistant following its frontmatter specification.
*/

import path from 'path';
import type { CommandContent, ToolCommandAdapter } from '../types.js';
import { transformToHyphenCommands } from '../../../utils/command-references.js';

/**
* SourceCraft Code Assistant adapter for command generation.
* File path: .codeassistant/commands/opsx-<id>.md
* Format: Markdown header with description
*/
export const codeassistantAdapter: ToolCommandAdapter = {
toolId: 'codeassistant',

getFilePath(commandId: string): string {
return path.join('.codeassistant', 'commands', `opsx-${commandId}.md`);
},

formatFile(content: CommandContent): string {
const transformedBody = transformToHyphenCommands(content.body);

return `---
description: ${content.description}
---
Comment thread
coderabbitai[bot] marked this conversation as resolved.

${transformedBody}
`;
},
};
1 change: 1 addition & 0 deletions src/core/command-generation/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export { kilocodeAdapter } from './kilocode.js';
export { kiroAdapter } from './kiro.js';
export { opencodeAdapter } from './opencode.js';
export { piAdapter } from './pi.js';
export { codeassistantAdapter } from './codeassistant.js';
export { qoderAdapter } from './qoder.js';
export { lingmaAdapter } from './lingma.js';
export { qwenAdapter } from './qwen.js';
Expand Down
2 changes: 2 additions & 0 deletions src/core/command-generation/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { kilocodeAdapter } from './adapters/kilocode.js';
import { kiroAdapter } from './adapters/kiro.js';
import { opencodeAdapter } from './adapters/opencode.js';
import { piAdapter } from './adapters/pi.js';
import { codeassistantAdapter } from './adapters/codeassistant.js';
import { qoderAdapter } from './adapters/qoder.js';
import { lingmaAdapter } from './adapters/lingma.js';
import { qwenAdapter } from './adapters/qwen.js';
Expand Down Expand Up @@ -62,6 +63,7 @@ export class CommandAdapterRegistry {
CommandAdapterRegistry.register(kiroAdapter);
CommandAdapterRegistry.register(opencodeAdapter);
CommandAdapterRegistry.register(piAdapter);
CommandAdapterRegistry.register(codeassistantAdapter);
CommandAdapterRegistry.register(qoderAdapter);
CommandAdapterRegistry.register(lingmaAdapter);
CommandAdapterRegistry.register(qwenAdapter);
Expand Down
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const AI_TOOLS: AIToolOption[] = [
{ name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
{ name: 'SourceCraft Code Assistant', value: 'codeassistant', available: true, successLabel: 'SourceCraft Code Assistant', skillsDir: '.codeassistant' },
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
{ name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' },
Expand Down
15 changes: 14 additions & 1 deletion test/core/available-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,24 @@ describe('available-tools', () => {
const tools = getAvailableTools(testDir);
const toolValues = tools.map((t) => t.value);
expect(toolValues).toContain('vibe');

const vibeTool = tools.find((t) => t.value === 'vibe');
expect(vibeTool).toBeDefined();
expect(vibeTool?.name).toBe('Mistral Vibe');
expect(vibeTool?.skillsDir).toBe('.vibe');
});

it('should detect SourceCraft Code Assistant when .codeassistant directory exists', async () => {
await fs.mkdir(path.join(testDir, '.codeassistant'), { recursive: true });

const tools = getAvailableTools(testDir);
const toolValues = tools.map((t) => t.value);
expect(toolValues).toContain('codeassistant');

const vibeTool = tools.find((t) => t.value === 'codeassistant');
expect(vibeTool).toBeDefined();
expect(vibeTool?.name).toBe('SourceCraft Code Assistant');
expect(vibeTool?.skillsDir).toBe('.codeassistant');
});
});
});
26 changes: 26 additions & 0 deletions test/core/command-generation/adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { qoderAdapter } from '../../../src/core/command-generation/adapters/qode
import { qwenAdapter } from '../../../src/core/command-generation/adapters/qwen.js';
import { roocodeAdapter } from '../../../src/core/command-generation/adapters/roocode.js';
import { windsurfAdapter } from '../../../src/core/command-generation/adapters/windsurf.js';
import { codeassistantAdapter } from '../../../src/core/command-generation/adapters/codeassistant.js';
import type { CommandContent } from '../../../src/core/command-generation/types.js';

describe('command-generation/adapters', () => {
Expand Down Expand Up @@ -707,4 +708,29 @@ describe('command-generation/adapters', () => {
}
});
});

describe('codeassistantAdapter', () => {
it('should have correct toolId', () => {
expect(codeassistantAdapter.toolId).toBe('codeassistant');
});

it('should generate correct file path', () => {
const filePath = codeassistantAdapter.getFilePath('explore');
expect(filePath).toBe(path.join('.codeassistant', 'commands', 'opsx-explore.md'));
});

it('should generate correct file path for different command IDs', () => {
expect(codeassistantAdapter.getFilePath('new')).toBe(path.join('.codeassistant', 'commands', 'opsx-new.md'));
expect(codeassistantAdapter.getFilePath('bulk-archive')).toBe(path.join('.codeassistant', 'commands', 'opsx-bulk-archive.md'));
});

it('should format file with correct YAML frontmatter', () => {
const output = codeassistantAdapter.formatFile(sampleContent);

expect(output).toContain('---\n');
expect(output).toContain('description: Enter explore mode for thinking');
expect(output).toContain('---\n\n');
expect(output).toContain('This is the command body.\n\nWith multiple lines.');
});
});
});