diff --git a/.changeset/atomcode-support.md b/.changeset/atomcode-support.md new file mode 100644 index 000000000..41efb5ac3 --- /dev/null +++ b/.changeset/atomcode-support.md @@ -0,0 +1,7 @@ +--- +"@fission-ai/openspec": minor +--- + +### New Features + +- **AtomCode support** — OpenSpec can now initialize AtomCode as a supported tool using `.atomcode/skills/` for Agent Skills and `.atomcode/commands/` for slash commands diff --git a/docs/cli.md b/docs/cli.md index 103dd7d4f..c76d624d1 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -107,7 +107,7 @@ openspec init [path] [options] `--profile custom` uses whatever workflows are currently selected in global config (`openspec config profile`). -**Supported 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` +**Supported tool IDs (`--tools`):** `amazon-q`, `antigravity`, `atomcode`, `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` **Examples:** diff --git a/docs/supported-tools.md b/docs/supported-tools.md index b2ee30fb4..91907453b 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -24,6 +24,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` | +| AtomCode (`atomcode`) | `.atomcode/skills/openspec-*/SKILL.md` | `.atomcode/commands/opsx-.md` | | Auggie (`auggie`) | `.augment/skills/openspec-*/SKILL.md` | `.augment/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` | @@ -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`, `atomcode`, `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` ## Workflow-Dependent Installation diff --git a/src/core/command-generation/adapters/atomcode.ts b/src/core/command-generation/adapters/atomcode.ts new file mode 100644 index 000000000..1877b8e09 --- /dev/null +++ b/src/core/command-generation/adapters/atomcode.ts @@ -0,0 +1,40 @@ +/** + * AtomCode Command Adapter + * + * Formats commands for AtomCode following its frontmatter specification. + * AtomCode is an open-source terminal AI coding assistant that uses the same + * Agent Skills spec as Claude Code. + */ + +import path from 'path'; +import type { CommandContent, ToolCommandAdapter } from '../types.js'; +import { transformToHyphenCommands } from '../../../utils/command-references.js'; + +/** + * AtomCode adapter for command generation. + * File path: .atomcode/commands/opsx-.md + * Frontmatter: description + * + * AtomCode's frontmatter parser supports name, description, disable-model-invocation, + * user-invocable, argument-hint, and allowed-tools. We provide description as the + * primary field; other fields are optional and left to defaults. + */ +export const atomcodeAdapter: ToolCommandAdapter = { + toolId: 'atomcode', + + getFilePath(commandId: string): string { + return path.join('.atomcode', 'commands', `opsx-${commandId}.md`); + }, + + formatFile(content: CommandContent): string { + // Transform command references from colon to hyphen format for AtomCode + const transformedBody = transformToHyphenCommands(content.body); + + return `--- +description: ${content.description} +--- + +${transformedBody} +`; + }, +}; diff --git a/src/core/command-generation/adapters/index.ts b/src/core/command-generation/adapters/index.ts index 00fc75d5d..3f4b9bcb7 100644 --- a/src/core/command-generation/adapters/index.ts +++ b/src/core/command-generation/adapters/index.ts @@ -6,6 +6,7 @@ export { amazonQAdapter } from './amazon-q.js'; export { antigravityAdapter } from './antigravity.js'; +export { atomcodeAdapter } from './atomcode.js'; export { auggieAdapter } from './auggie.js'; export { bobAdapter } from './bob.js'; export { claudeAdapter } from './claude.js'; diff --git a/src/core/command-generation/registry.ts b/src/core/command-generation/registry.ts index 3b726d707..7cd2bf788 100644 --- a/src/core/command-generation/registry.ts +++ b/src/core/command-generation/registry.ts @@ -8,6 +8,7 @@ import type { ToolCommandAdapter } from './types.js'; import { amazonQAdapter } from './adapters/amazon-q.js'; import { antigravityAdapter } from './adapters/antigravity.js'; +import { atomcodeAdapter } from './adapters/atomcode.js'; import { auggieAdapter } from './adapters/auggie.js'; import { bobAdapter } from './adapters/bob.js'; import { claudeAdapter } from './adapters/claude.js'; @@ -43,6 +44,7 @@ export class CommandAdapterRegistry { static { CommandAdapterRegistry.register(amazonQAdapter); CommandAdapterRegistry.register(antigravityAdapter); + CommandAdapterRegistry.register(atomcodeAdapter); CommandAdapterRegistry.register(auggieAdapter); CommandAdapterRegistry.register(bobAdapter); CommandAdapterRegistry.register(claudeAdapter); diff --git a/src/core/config.ts b/src/core/config.ts index 3be428b26..fe1279015 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -21,6 +21,7 @@ export interface AIToolOption { 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: 'AtomCode', value: 'atomcode', available: true, successLabel: 'AtomCode', skillsDir: '.atomcode' }, { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' }, { name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' }, { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' }, diff --git a/test/core/command-generation/adapters.test.ts b/test/core/command-generation/adapters.test.ts index b91dc024f..d045911f9 100644 --- a/test/core/command-generation/adapters.test.ts +++ b/test/core/command-generation/adapters.test.ts @@ -3,6 +3,7 @@ import os from 'os'; 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 { atomcodeAdapter } from '../../../src/core/command-generation/adapters/atomcode.js'; import { auggieAdapter } from '../../../src/core/command-generation/adapters/auggie.js'; import { bobAdapter } from '../../../src/core/command-generation/adapters/bob.js'; import { claudeAdapter } from '../../../src/core/command-generation/adapters/claude.js'; @@ -164,6 +165,49 @@ describe('command-generation/adapters', () => { }); }); + describe('atomcodeAdapter', () => { + it('should have correct toolId', () => { + expect(atomcodeAdapter.toolId).toBe('atomcode'); + }); + + it('should generate correct file path', () => { + const filePath = atomcodeAdapter.getFilePath('explore'); + expect(filePath).toBe(path.join('.atomcode', 'commands', 'opsx-explore.md')); + }); + + it('should generate correct file paths for different commands', () => { + expect(atomcodeAdapter.getFilePath('new')).toBe(path.join('.atomcode', 'commands', 'opsx-new.md')); + expect(atomcodeAdapter.getFilePath('bulk-archive')).toBe(path.join('.atomcode', 'commands', 'opsx-bulk-archive.md')); + }); + + it('should format file with description frontmatter', () => { + const output = atomcodeAdapter.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.'); + }); + + it('should not include name, category, or tags', () => { + const output = atomcodeAdapter.formatFile(sampleContent); + expect(output).not.toContain('name:'); + expect(output).not.toContain('category:'); + expect(output).not.toContain('tags:'); + }); + + it('should transform command references from colon to hyphen format', () => { + const contentWithRefs: CommandContent = { + ...sampleContent, + body: 'Run /opsx:apply to implement. Then /opsx:archive when done.', + }; + + const output = atomcodeAdapter.formatFile(contentWithRefs); + expect(output).toContain('/opsx-apply'); + expect(output).toContain('/opsx-archive'); + expect(output).not.toContain('/opsx:apply'); + }); + }); + describe('auggieAdapter', () => { it('should have correct toolId', () => { expect(auggieAdapter.toolId).toBe('auggie'); @@ -694,7 +738,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, atomcodeAdapter, auggieAdapter, bobAdapter, clineAdapter, codexAdapter, codebuddyAdapter, continueAdapter, costrictAdapter, crushAdapter, factoryAdapter, geminiAdapter, githubCopilotAdapter, iflowAdapter, kilocodeAdapter, opencodeAdapter, piAdapter, qoderAdapter,