Skip to content

Commit b899e0a

Browse files
committed
feat: add Avian coding assistant support
Add Avian as a supported AI coding tool with skill and command generation. Follows the same adapter pattern as Claude Code and Crush with nested opsx command directories and YAML frontmatter.
1 parent afdca0d commit b899e0a

6 files changed

Lines changed: 75 additions & 2 deletions

File tree

docs/supported-tools.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `sync`, `b
2424
| Amazon Q Developer (`amazon-q`) | `.amazonq/skills/openspec-*/SKILL.md` | `.amazonq/prompts/opsx-<id>.md` |
2525
| Antigravity (`antigravity`) | `.agent/skills/openspec-*/SKILL.md` | `.agent/workflows/opsx-<id>.md` |
2626
| Auggie (`auggie`) | `.augment/skills/openspec-*/SKILL.md` | `.augment/commands/opsx-<id>.md` |
27+
| Avian (`avian`) | `.avian/skills/openspec-*/SKILL.md` | `.avian/commands/opsx/<id>.md` |
2728
| Claude Code (`claude`) | `.claude/skills/openspec-*/SKILL.md` | `.claude/commands/opsx/<id>.md` |
2829
| Cline (`cline`) | `.cline/skills/openspec-*/SKILL.md` | `.clinerules/workflows/opsx-<id>.md` |
2930
| CodeBuddy (`codebuddy`) | `.codebuddy/skills/openspec-*/SKILL.md` | `.codebuddy/commands/opsx/<id>.md` |
@@ -68,7 +69,7 @@ openspec init --tools none
6869
openspec init --profile core
6970
```
7071

71-
**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`
72+
**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`
7273

7374
## Workflow-Dependent Installation
7475

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Avian Command Adapter
3+
*
4+
* Formats commands for Avian following its frontmatter specification.
5+
*/
6+
7+
import path from 'path';
8+
import type { CommandContent, ToolCommandAdapter } from '../types.js';
9+
10+
/**
11+
* Avian adapter for command generation.
12+
* File path: .avian/commands/opsx/<id>.md
13+
* Frontmatter: name, description, category, tags
14+
*/
15+
export const avianAdapter: ToolCommandAdapter = {
16+
toolId: 'avian',
17+
18+
getFilePath(commandId: string): string {
19+
return path.join('.avian', 'commands', 'opsx', `${commandId}.md`);
20+
},
21+
22+
formatFile(content: CommandContent): string {
23+
const tagsStr = content.tags.join(', ');
24+
return `---
25+
name: ${content.name}
26+
description: ${content.description}
27+
category: ${content.category}
28+
tags: [${tagsStr}]
29+
---
30+
31+
${content.body}
32+
`;
33+
},
34+
};

src/core/command-generation/adapters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
export { amazonQAdapter } from './amazon-q.js';
88
export { antigravityAdapter } from './antigravity.js';
99
export { auggieAdapter } from './auggie.js';
10+
export { avianAdapter } from './avian.js';
1011
export { claudeAdapter } from './claude.js';
1112
export { clineAdapter } from './cline.js';
1213
export { codexAdapter } from './codex.js';

src/core/command-generation/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { ToolCommandAdapter } from './types.js';
99
import { amazonQAdapter } from './adapters/amazon-q.js';
1010
import { antigravityAdapter } from './adapters/antigravity.js';
1111
import { auggieAdapter } from './adapters/auggie.js';
12+
import { avianAdapter } from './adapters/avian.js';
1213
import { claudeAdapter } from './adapters/claude.js';
1314
import { clineAdapter } from './adapters/cline.js';
1415
import { codexAdapter } from './adapters/codex.js';
@@ -41,6 +42,7 @@ export class CommandAdapterRegistry {
4142
CommandAdapterRegistry.register(amazonQAdapter);
4243
CommandAdapterRegistry.register(antigravityAdapter);
4344
CommandAdapterRegistry.register(auggieAdapter);
45+
CommandAdapterRegistry.register(avianAdapter);
4446
CommandAdapterRegistry.register(claudeAdapter);
4547
CommandAdapterRegistry.register(clineAdapter);
4648
CommandAdapterRegistry.register(codexAdapter);

src/core/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const AI_TOOLS: AIToolOption[] = [
2121
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' },
2222
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' },
2323
{ name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
24+
{ name: 'Avian', value: 'avian', available: true, successLabel: 'Avian', skillsDir: '.avian' },
2425
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
2526
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
2627
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' },

test/core/command-generation/adapters.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from 'path';
44
import { amazonQAdapter } from '../../../src/core/command-generation/adapters/amazon-q.js';
55
import { antigravityAdapter } from '../../../src/core/command-generation/adapters/antigravity.js';
66
import { auggieAdapter } from '../../../src/core/command-generation/adapters/auggie.js';
7+
import { avianAdapter } from '../../../src/core/command-generation/adapters/avian.js';
78
import { claudeAdapter } from '../../../src/core/command-generation/adapters/claude.js';
89
import { clineAdapter } from '../../../src/core/command-generation/adapters/cline.js';
910
import { codexAdapter } from '../../../src/core/command-generation/adapters/codex.js';
@@ -183,6 +184,39 @@ describe('command-generation/adapters', () => {
183184
});
184185
});
185186

187+
describe('avianAdapter', () => {
188+
it('should have correct toolId', () => {
189+
expect(avianAdapter.toolId).toBe('avian');
190+
});
191+
192+
it('should generate correct file path with nested opsx folder', () => {
193+
const filePath = avianAdapter.getFilePath('explore');
194+
expect(filePath).toBe(path.join('.avian', 'commands', 'opsx', 'explore.md'));
195+
});
196+
197+
it('should generate correct file paths for different commands', () => {
198+
expect(avianAdapter.getFilePath('new')).toBe(path.join('.avian', 'commands', 'opsx', 'new.md'));
199+
expect(avianAdapter.getFilePath('bulk-archive')).toBe(path.join('.avian', 'commands', 'opsx', 'bulk-archive.md'));
200+
});
201+
202+
it('should format file with name, description, category, and tags', () => {
203+
const output = avianAdapter.formatFile(sampleContent);
204+
expect(output).toContain('---\n');
205+
expect(output).toContain('name: OpenSpec Explore');
206+
expect(output).toContain('description: Enter explore mode for thinking');
207+
expect(output).toContain('category: Workflow');
208+
expect(output).toContain('tags: [workflow, explore, experimental]');
209+
expect(output).toContain('---\n\n');
210+
expect(output).toContain('This is the command body.\n\nWith multiple lines.');
211+
});
212+
213+
it('should handle empty tags', () => {
214+
const contentNoTags: CommandContent = { ...sampleContent, tags: [] };
215+
const output = avianAdapter.formatFile(contentNoTags);
216+
expect(output).toContain('tags: []');
217+
});
218+
});
219+
186220
describe('clineAdapter', () => {
187221
it('should have correct toolId', () => {
188222
expect(clineAdapter.toolId).toBe('cline');
@@ -606,7 +640,7 @@ describe('command-generation/adapters', () => {
606640
it('All adapters use path.join for paths', () => {
607641
// Verify all adapters produce valid paths
608642
const adapters = [
609-
amazonQAdapter, antigravityAdapter, auggieAdapter, clineAdapter,
643+
amazonQAdapter, antigravityAdapter, auggieAdapter, avianAdapter, clineAdapter,
610644
codexAdapter, codebuddyAdapter, continueAdapter, costrictAdapter,
611645
crushAdapter, factoryAdapter, geminiAdapter, githubCopilotAdapter,
612646
iflowAdapter, kilocodeAdapter, opencodeAdapter, piAdapter, qoderAdapter,

0 commit comments

Comments
 (0)