diff --git a/src/core/config.ts b/src/core/config.ts index a27d6eafb7..9713b2212e 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -33,6 +33,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' }, { name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow' }, { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' }, + { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma' }, { name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' }, { name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' }, { name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code' }, diff --git a/src/core/configurators/lingma.ts b/src/core/configurators/lingma.ts new file mode 100644 index 0000000000..d8ab037ac6 --- /dev/null +++ b/src/core/configurators/lingma.ts @@ -0,0 +1,62 @@ +import path from 'path'; +import { ToolConfigurator } from './base.js'; +import { FileSystemUtils } from '../../utils/file-system.js'; +import { TemplateManager } from '../templates/index.js'; +import { OPENSPEC_MARKERS } from '../config.js'; + +/** + * Lingma IDE AI Tool Configurator + * + * Configures OpenSpec integration for Lingma IDE AI coding assistant. + * Creates and manages .lingma/rules/openspec-rules.md configuration file with OpenSpec instructions. + * + * @implements {ToolConfigurator} + */ +export class LingmaConfigurator implements ToolConfigurator { + /** Display name for the Lingma tool */ + name = 'Lingma'; + + /** Configuration file name in .lingma/rules directory */ + configFileName = '.lingma/rules/openspec-rules.md'; + + /** Indicates tool is available for configuration */ + isAvailable = true; + + /** + * Configure Lingma integration for a project + * + * Creates or updates .lingma/rules/openspec-rules.md file with OpenSpec instructions. + * Includes trigger configuration for automatic application. + * Uses agent-standard template for instruction content. + * Wrapped with OpenSpec markers for future updates. + * + * @param {string} projectPath - Absolute path to project root directory + * @param {string} openspecDir - Path to openspec directory (unused but required by interface) + * @returns {Promise} Resolves when configuration is complete + */ + async configure(projectPath: string, openspecDir: string): Promise { + // Construct full path to .lingma/rules/openspec-rules.md + const filePath = path.join(projectPath, this.configFileName); + + // Combine trigger configuration with agent-standard instructions + const content = TemplateManager.getAgentsStandardTemplate(); + + // Write or update file with managed content between markers + // This allows future updates to refresh instructions automatically + await FileSystemUtils.updateFileWithMarkers( + filePath, + content, + OPENSPEC_MARKERS.start, + OPENSPEC_MARKERS.end + ); + + // Create trigger configuration for Lingma rules + const lingmaRulesTrigger = `---\ntrigger: always_on\nalwaysApply: true\n---\n`; + if (await FileSystemUtils.fileExists(filePath)) { + const existingContent = await FileSystemUtils.readFile(filePath); + if (!existingContent.startsWith(lingmaRulesTrigger)) { + await FileSystemUtils.writeFile(filePath, lingmaRulesTrigger + existingContent); + } + } + } +} \ No newline at end of file diff --git a/src/core/configurators/registry.ts b/src/core/configurators/registry.ts index 70a1a20763..a604ae01bd 100644 --- a/src/core/configurators/registry.ts +++ b/src/core/configurators/registry.ts @@ -7,6 +7,7 @@ import { QoderConfigurator } from './qoder.js'; import { IflowConfigurator } from './iflow.js'; import { AgentsStandardConfigurator } from './agents.js'; import { QwenConfigurator } from './qwen.js'; +import { LingmaConfigurator } from './lingma.js'; export class ToolRegistry { private static tools: Map = new Map(); @@ -20,6 +21,7 @@ export class ToolRegistry { const iflowConfigurator = new IflowConfigurator(); const agentsConfigurator = new AgentsStandardConfigurator(); const qwenConfigurator = new QwenConfigurator(); + const lingmaConfigurator = new LingmaConfigurator(); // Register with the ID that matches the checkbox value this.tools.set('claude', claudeConfigurator); this.tools.set('cline', clineConfigurator); @@ -29,6 +31,7 @@ export class ToolRegistry { this.tools.set('iflow', iflowConfigurator); this.tools.set('agents', agentsConfigurator); this.tools.set('qwen', qwenConfigurator); + this.tools.set('lingma', lingmaConfigurator); } static register(tool: ToolConfigurator): void { diff --git a/src/core/configurators/slash/lingma.ts b/src/core/configurators/slash/lingma.ts new file mode 100644 index 0000000000..a9208aa772 --- /dev/null +++ b/src/core/configurators/slash/lingma.ts @@ -0,0 +1,72 @@ +import { SlashCommandConfigurator } from './base.js'; +import { SlashCommandId } from '../../templates/index.js'; + +/** + * File paths for Lingma slash commands + * Maps each OpenSpec workflow stage to its command file location + * Commands are stored in .lingma/rules/workflows/ directory + */ +const FILE_PATHS: Record = { + // Create and validate new change proposals + proposal: '.lingma/rules/workflows/openspec-proposal.md', + + // Implement approved changes with task tracking + apply: '.lingma/rules/workflows/openspec-apply.md', + + // Archive completed changes and update specs + archive: '.lingma/rules/workflows/openspec-archive.md' +}; + +/** + * Lingma Slash Command Configurator + * + * Manages OpenSpec slash commands for Lingma IDE AI assistant. + * Creates three workflow commands: proposal, apply, and archive. + * Uses manual trigger configuration for command execution. + * + * @extends {SlashCommandConfigurator} + */ +export class LingmaSlashCommandConfigurator extends SlashCommandConfigurator { + /** Unique identifier for Lingma tool */ + readonly toolId = 'lingma'; + + /** Indicates slash commands are available for this tool */ + readonly isAvailable = true; + + /** + * Get relative file path for a slash command + * + * @param {SlashCommandId} id - Command identifier (proposal, apply, or archive) + * @returns {string} Relative path from project root to command file + */ + protected getRelativePath(id: SlashCommandId): string { + return FILE_PATHS[id]; + } + + /** + * Get frontmatter and header for a slash command + * + * Includes manual trigger configuration and OpenSpec command instructions. + * The trigger setting ensures commands are executed manually by the user. + * + * @param {SlashCommandId} id - Command identifier (proposal, apply, or archive) + * @returns {string | undefined} Manual trigger configuration and command header + */ + protected getFrontmatter(id: SlashCommandId): string | undefined { + // Define descriptions for each command type + const descriptions: Record = { + proposal: 'Scaffold a new OpenSpec change and validate strictly.', + apply: 'Implement an approved OpenSpec change and keep tasks in sync.', + archive: 'Archive a deployed OpenSpec change and update specs.' + }; + + // Create manual trigger configuration for Lingma rules + const lingmaRulesTrigger = `---\ntrigger: manual\n---\n`; + + // Get the appropriate description for the command + const description = descriptions[id]; + + // Combine trigger configuration with command header and description + return `${lingmaRulesTrigger}# OpenSpec: ${id.charAt(0).toUpperCase() + id.slice(1)}\n\n${description}`; + } +} \ No newline at end of file diff --git a/src/core/configurators/slash/registry.ts b/src/core/configurators/slash/registry.ts index 43fb245c7e..3b220d9207 100644 --- a/src/core/configurators/slash/registry.ts +++ b/src/core/configurators/slash/registry.ts @@ -20,6 +20,7 @@ import { RooCodeSlashCommandConfigurator } from './roocode.js'; import { AntigravitySlashCommandConfigurator } from './antigravity.js'; import { IflowSlashCommandConfigurator } from './iflow.js'; import { ContinueSlashCommandConfigurator } from './continue.js'; +import { LingmaSlashCommandConfigurator } from './lingma.js'; export class SlashCommandRegistry { private static configurators: Map = new Map(); @@ -46,6 +47,7 @@ export class SlashCommandRegistry { const antigravity = new AntigravitySlashCommandConfigurator(); const iflow = new IflowSlashCommandConfigurator(); const continueTool = new ContinueSlashCommandConfigurator(); + const lingma = new LingmaSlashCommandConfigurator(); this.configurators.set(claude.toolId, claude); this.configurators.set(codeBuddy.toolId, codeBuddy); @@ -68,6 +70,7 @@ export class SlashCommandRegistry { this.configurators.set(antigravity.toolId, antigravity); this.configurators.set(iflow.toolId, iflow); this.configurators.set(continueTool.toolId, continueTool); + this.configurators.set(lingma.toolId, lingma); } static register(configurator: SlashCommandConfigurator): void {