diff --git a/docs/supported-tools.md b/docs/supported-tools.md index 85d8e63d8..0464f12ec 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -25,6 +25,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch | 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` | | IBM Bob Shell (`bob`) | `.bob/skills/openspec-*/SKILL.md` | `.bob/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` | @@ -74,7 +75,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`, `opencode`, `pi`, `qoder`, `lingma`, `qwen`, `roocode`, `trae`, `windsurf` +**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `avian`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `opencode`, `pi`, `qoder`, `lingma`, `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 00fc75d5d..94c6e991a 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 { bobAdapter } from './bob.js'; export { claudeAdapter } from './claude.js'; export { clineAdapter } from './cline.js'; diff --git a/src/core/command-generation/registry.ts b/src/core/command-generation/registry.ts index 3b726d707..e9c256b3e 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 { bobAdapter } from './adapters/bob.js'; import { claudeAdapter } from './adapters/claude.js'; import { clineAdapter } from './adapters/cline.js'; @@ -44,6 +45,7 @@ export class CommandAdapterRegistry { CommandAdapterRegistry.register(amazonQAdapter); CommandAdapterRegistry.register(antigravityAdapter); CommandAdapterRegistry.register(auggieAdapter); + CommandAdapterRegistry.register(avianAdapter); CommandAdapterRegistry.register(bobAdapter); CommandAdapterRegistry.register(claudeAdapter); CommandAdapterRegistry.register(clineAdapter); diff --git a/src/core/config.ts b/src/core/config.ts index 68f1abd33..1925a4d21 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -22,6 +22,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: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' }, { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' }, { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' }, diff --git a/test/core/command-generation/adapters.test.ts b/test/core/command-generation/adapters.test.ts index b91dc024f..63e111d03 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 { bobAdapter } from '../../../src/core/command-generation/adapters/bob.js'; import { claudeAdapter } from '../../../src/core/command-generation/adapters/claude.js'; import { clineAdapter } from '../../../src/core/command-generation/adapters/cline.js'; @@ -184,6 +185,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('bobAdapter', () => { it('should have correct toolId', () => { @@ -694,7 +728,7 @@ describe('command-generation/adapters', () => { it('All adapters use path.join for paths', () => { // Verify all adapters produce valid paths const adapters = [ - amazonQAdapter, antigravityAdapter, auggieAdapter, bobAdapter, clineAdapter, + amazonQAdapter, antigravityAdapter, auggieAdapter, avianAdapter, bobAdapter, clineAdapter, codexAdapter, codebuddyAdapter, continueAdapter, costrictAdapter, crushAdapter, factoryAdapter, geminiAdapter, githubCopilotAdapter, iflowAdapter, kilocodeAdapter, opencodeAdapter, piAdapter, qoderAdapter,