Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ openspec init [path] [options]
| `--tools <list>` | Configure AI tools non-interactively. Use `all`, `none`, or comma-separated list |
| `--force` | Auto-cleanup legacy files without prompting |

**Supported tools:** `amazon-q`, `antigravity`, `auggie`, `claude`, `cline`, `codex`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `opencode`, `qoder`, `qwen`, `roocode`, `windsurf`
**Supported tools:** `amazon-q`, `antigravity`, `auggie`, `claude`, `cline`, `codex`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `lingma`, `opencode`, `qoder`, `qwen`, `roocode`, `windsurf`

**Examples:**

Expand Down
3 changes: 2 additions & 1 deletion docs/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ For each tool you select, OpenSpec installs:
| GitHub Copilot | `.github/skills/` | `.github/prompts/`\*\* |
| iFlow | `.iflow/skills/` | `.iflow/commands/` |
| Kilo Code | `.kilocode/skills/` | `.kilocode/workflows/` |
| Lingma | `.lingma/skills/` | `.lingma/commands/opsx/` |
| OpenCode | `.opencode/skills/` | `.opencode/command/` |
| Qoder | `.qoder/skills/` | `.qoder/commands/opsx/` |
| Qwen Code | `.qwen/skills/` | `.qwen/commands/` |
Expand All @@ -55,7 +56,7 @@ openspec init --tools all
openspec init --tools none
```

**Available tool IDs:** `amazon-q`, `antigravity`, `auggie`, `claude`, `cline`, `codebuddy`, `codex`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `opencode`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf`
**Available tool IDs:** `amazon-q`, `antigravity`, `auggie`, `claude`, `cline`, `codebuddy`, `codex`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `kilocode`, `lingma`, `opencode`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf`

## What Gets Installed

Expand Down
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 @@ -20,6 +20,7 @@ export { geminiAdapter } from './gemini.js';
export { githubCopilotAdapter } from './github-copilot.js';
export { iflowAdapter } from './iflow.js';
export { kilocodeAdapter } from './kilocode.js';
export { lingmaAdapter } from './lingma.js';
export { opencodeAdapter } from './opencode.js';
export { qoderAdapter } from './qoder.js';
export { qwenAdapter } from './qwen.js';
Expand Down
33 changes: 33 additions & 0 deletions src/core/command-generation/adapters/lingma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Lingma Command Adapter
*
* Formats commands for Lingma following its skill specification.
*/

import path from 'path';
import type { CommandContent, ToolCommandAdapter } from '../types.js';

/**
* Lingma adapter for command generation.
* File path: .lingma/commands/opsx/<id>.md
* Structure: Command markdown files with YAML frontmatter
*/
export const lingmaAdapter: ToolCommandAdapter = {
toolId: 'lingma',

getFilePath(commandId: string): string {
return path.join('.lingma', '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}`;
},
};
2 changes: 2 additions & 0 deletions src/core/command-generation/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { geminiAdapter } from './adapters/gemini.js';
import { githubCopilotAdapter } from './adapters/github-copilot.js';
import { iflowAdapter } from './adapters/iflow.js';
import { kilocodeAdapter } from './adapters/kilocode.js';
import { lingmaAdapter } from './adapters/lingma.js';
import { opencodeAdapter } from './adapters/opencode.js';
import { qoderAdapter } from './adapters/qoder.js';
import { qwenAdapter } from './adapters/qwen.js';
Expand Down Expand Up @@ -52,6 +53,7 @@ export class CommandAdapterRegistry {
CommandAdapterRegistry.register(githubCopilotAdapter);
CommandAdapterRegistry.register(iflowAdapter);
CommandAdapterRegistry.register(kilocodeAdapter);
CommandAdapterRegistry.register(lingmaAdapter);
CommandAdapterRegistry.register(opencodeAdapter);
CommandAdapterRegistry.register(qoderAdapter);
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 @@ -34,6 +34,7 @@ export const AI_TOOLS: AIToolOption[] = [
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github' },
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
Expand Down
30 changes: 29 additions & 1 deletion test/core/command-generation/adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { geminiAdapter } from '../../../src/core/command-generation/adapters/gem
import { githubCopilotAdapter } from '../../../src/core/command-generation/adapters/github-copilot.js';
import { iflowAdapter } from '../../../src/core/command-generation/adapters/iflow.js';
import { kilocodeAdapter } from '../../../src/core/command-generation/adapters/kilocode.js';
import { lingmaAdapter } from '../../../src/core/command-generation/adapters/lingma.js';
import { opencodeAdapter } from '../../../src/core/command-generation/adapters/opencode.js';
import { qoderAdapter } from '../../../src/core/command-generation/adapters/qoder.js';
import { qwenAdapter } from '../../../src/core/command-generation/adapters/qwen.js';
Expand Down Expand Up @@ -542,6 +543,33 @@ describe('command-generation/adapters', () => {
});
});

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

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

it('should generate correct file paths for different commands', () => {
expect(lingmaAdapter.getFilePath('new')).toBe(path.join('.lingma', 'commands', 'opsx', 'new.md'));
expect(lingmaAdapter.getFilePath('apply')).toBe(path.join('.lingma', 'commands', 'opsx', 'apply.md'));
});

it('should format file with name, description, category, and tags', () => {
const output = lingmaAdapter.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.');
});
});

describe('cross-platform path handling', () => {
it('Claude adapter uses path.join for paths', () => {
// path.join handles platform-specific separators
Expand All @@ -566,7 +594,7 @@ describe('command-generation/adapters', () => {
amazonQAdapter, antigravityAdapter, auggieAdapter, clineAdapter,
codexAdapter, codebuddyAdapter, continueAdapter, costrictAdapter,
crushAdapter, factoryAdapter, geminiAdapter, githubCopilotAdapter,
iflowAdapter, kilocodeAdapter, opencodeAdapter, qoderAdapter,
iflowAdapter, kilocodeAdapter, lingmaAdapter, opencodeAdapter, qoderAdapter,
qwenAdapter, roocodeAdapter
];
for (const adapter of adapters) {
Expand Down