diff --git a/docs/supported-tools.md b/docs/supported-tools.md index 524e597f2..51bb6a5ff 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -24,6 +24,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `sync`, `b | Amazon Q Developer (`amazon-q`) | `.amazonq/skills/openspec-*/SKILL.md` | `.amazonq/prompts/opsx-.md` | | Antigravity (`antigravity`) | `.agent/skills/openspec-*/SKILL.md` | `.agent/workflows/opsx-.md` | | Auggie (`auggie`) | `.augment/skills/openspec-*/SKILL.md` | `.augment/commands/opsx-.md` | +| Avian (`avian`) | `.avian/skills/openspec-*/SKILL.md` | `.avian/commands/opsx/.md` | | Claude Code (`claude`) | `.claude/skills/openspec-*/SKILL.md` | `.claude/commands/opsx/.md` | | Cline (`cline`) | `.cline/skills/openspec-*/SKILL.md` | `.clinerules/workflows/opsx-.md` | | CodeBuddy (`codebuddy`) | `.codebuddy/skills/openspec-*/SKILL.md` | `.codebuddy/commands/opsx/.md` | @@ -68,7 +69,7 @@ openspec init --tools none openspec init --profile core ``` -**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `claude`, `cline`, `codex`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `kiro`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf` +**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `avian`, `claude`, `cline`, `codex`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `kiro`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf` ## Workflow-Dependent Installation diff --git a/src/core/command-generation/adapters/avian.ts b/src/core/command-generation/adapters/avian.ts new file mode 100644 index 000000000..2f9a09f7b --- /dev/null +++ b/src/core/command-generation/adapters/avian.ts @@ -0,0 +1,34 @@ +/** + * Avian Command Adapter + * + * Formats commands for Avian following its frontmatter specification. + */ + +import path from 'path'; +import type { CommandContent, ToolCommandAdapter } from '../types.js'; + +/** + * Avian adapter for command generation. + * File path: .avian/commands/opsx/.md + * Frontmatter: name, description, category, tags + */ +export const avianAdapter: ToolCommandAdapter = { + toolId: 'avian', + + getFilePath(commandId: string): string { + return path.join('.avian', 'commands', 'opsx', `${commandId}.md`); + }, + + formatFile(content: CommandContent): string { + const tagsStr = content.tags.join(', '); + return `--- +name: ${content.name} +description: ${content.description} +category: ${content.category} +tags: [${tagsStr}] +--- + +${content.body} +`; + }, +}; diff --git a/src/core/command-generation/adapters/index.ts b/src/core/command-generation/adapters/index.ts index 06f7a7ae7..1043128cd 100644 --- a/src/core/command-generation/adapters/index.ts +++ b/src/core/command-generation/adapters/index.ts @@ -7,6 +7,7 @@ export { amazonQAdapter } from './amazon-q.js'; export { antigravityAdapter } from './antigravity.js'; export { auggieAdapter } from './auggie.js'; +export { avianAdapter } from './avian.js'; export { claudeAdapter } from './claude.js'; export { clineAdapter } from './cline.js'; export { codexAdapter } from './codex.js'; diff --git a/src/core/command-generation/registry.ts b/src/core/command-generation/registry.ts index a69a98adc..93b70e34b 100644 --- a/src/core/command-generation/registry.ts +++ b/src/core/command-generation/registry.ts @@ -9,6 +9,7 @@ import type { ToolCommandAdapter } from './types.js'; import { amazonQAdapter } from './adapters/amazon-q.js'; import { antigravityAdapter } from './adapters/antigravity.js'; import { auggieAdapter } from './adapters/auggie.js'; +import { avianAdapter } from './adapters/avian.js'; import { claudeAdapter } from './adapters/claude.js'; import { clineAdapter } from './adapters/cline.js'; import { codexAdapter } from './adapters/codex.js'; @@ -41,6 +42,7 @@ export class CommandAdapterRegistry { CommandAdapterRegistry.register(amazonQAdapter); CommandAdapterRegistry.register(antigravityAdapter); CommandAdapterRegistry.register(auggieAdapter); + CommandAdapterRegistry.register(avianAdapter); CommandAdapterRegistry.register(claudeAdapter); CommandAdapterRegistry.register(clineAdapter); CommandAdapterRegistry.register(codexAdapter); diff --git a/src/core/config.ts b/src/core/config.ts index f35f92861..9d645348b 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -21,6 +21,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' }, { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' }, { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' }, + { name: 'Avian', value: 'avian', available: true, successLabel: 'Avian', skillsDir: '.avian' }, { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' }, { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' }, { name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' }, diff --git a/test/core/command-generation/adapters.test.ts b/test/core/command-generation/adapters.test.ts index dab19bf3d..319552250 100644 --- a/test/core/command-generation/adapters.test.ts +++ b/test/core/command-generation/adapters.test.ts @@ -4,6 +4,7 @@ import path from 'path'; import { amazonQAdapter } from '../../../src/core/command-generation/adapters/amazon-q.js'; import { antigravityAdapter } from '../../../src/core/command-generation/adapters/antigravity.js'; import { auggieAdapter } from '../../../src/core/command-generation/adapters/auggie.js'; +import { avianAdapter } from '../../../src/core/command-generation/adapters/avian.js'; import { claudeAdapter } from '../../../src/core/command-generation/adapters/claude.js'; import { clineAdapter } from '../../../src/core/command-generation/adapters/cline.js'; import { codexAdapter } from '../../../src/core/command-generation/adapters/codex.js'; @@ -183,6 +184,39 @@ describe('command-generation/adapters', () => { }); }); + describe('avianAdapter', () => { + it('should have correct toolId', () => { + expect(avianAdapter.toolId).toBe('avian'); + }); + + it('should generate correct file path with nested opsx folder', () => { + const filePath = avianAdapter.getFilePath('explore'); + expect(filePath).toBe(path.join('.avian', 'commands', 'opsx', 'explore.md')); + }); + + it('should generate correct file paths for different commands', () => { + expect(avianAdapter.getFilePath('new')).toBe(path.join('.avian', 'commands', 'opsx', 'new.md')); + expect(avianAdapter.getFilePath('bulk-archive')).toBe(path.join('.avian', 'commands', 'opsx', 'bulk-archive.md')); + }); + + it('should format file with name, description, category, and tags', () => { + const output = avianAdapter.formatFile(sampleContent); + expect(output).toContain('---\n'); + expect(output).toContain('name: OpenSpec Explore'); + expect(output).toContain('description: Enter explore mode for thinking'); + expect(output).toContain('category: Workflow'); + expect(output).toContain('tags: [workflow, explore, experimental]'); + expect(output).toContain('---\n\n'); + expect(output).toContain('This is the command body.\n\nWith multiple lines.'); + }); + + it('should handle empty tags', () => { + const contentNoTags: CommandContent = { ...sampleContent, tags: [] }; + const output = avianAdapter.formatFile(contentNoTags); + expect(output).toContain('tags: []'); + }); + }); + describe('clineAdapter', () => { it('should have correct toolId', () => { expect(clineAdapter.toolId).toBe('cline'); @@ -606,7 +640,7 @@ describe('command-generation/adapters', () => { it('All adapters use path.join for paths', () => { // Verify all adapters produce valid paths const adapters = [ - amazonQAdapter, antigravityAdapter, auggieAdapter, clineAdapter, + amazonQAdapter, antigravityAdapter, auggieAdapter, avianAdapter, clineAdapter, codexAdapter, codebuddyAdapter, continueAdapter, costrictAdapter, crushAdapter, factoryAdapter, geminiAdapter, githubCopilotAdapter, iflowAdapter, kilocodeAdapter, opencodeAdapter, piAdapter, qoderAdapter,