Skip to content
Closed
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
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
62 changes: 62 additions & 0 deletions src/core/configurators/lingma.ts
Original file line number Diff line number Diff line change
@@ -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<void>} Resolves when configuration is complete
*/
async configure(projectPath: string, openspecDir: string): Promise<void> {
// 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);
}
}
Comment on lines +53 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This logic is backwards and will fail. The frontmatter is only added if the file already exists, but should be added when creating a new file. Additionally, the file is rewritten after updateFileWithMarkers has already written it, causing duplication or incorrect output.

Compare with other configurators like qwen.ts which simply call updateFileWithMarkers - that method already handles both creating new files and updating existing ones with proper marker placement.

Suggested change
// Create trigger configuration for Lingma rules
const lingmaRulesTrigger = `---\ntrigger: always_on\nalwaysApply: true\n---\n`;
let existingContent = '';
if (await FileSystemUtils.fileExists(filePath)) {
existingContent = await FileSystemUtils.readFile(filePath);
existingContent = lingmaRulesTrigger + existingContent;
await FileSystemUtils.writeFile(filePath, existingContent);
}
// updateFileWithMarkers handles both file creation and updates
// For new files, it creates: frontmatter + markers + content
// For existing files, it updates content between markers
const frontmatter = `---\ntrigger: always_on\nalwaysApply: true\n---\n\n`;
const fullContent = frontmatter + content;
await FileSystemUtils.updateFileWithMarkers(
filePath,
fullContent,
OPENSPEC_MARKERS.start,
OPENSPEC_MARKERS.end
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/configurators/lingma.ts
Line: 53:60

Comment:
**logic:** This logic is backwards and will fail. The frontmatter is only added if the file already exists, but should be added when creating a new file. Additionally, the file is rewritten after `updateFileWithMarkers` has already written it, causing duplication or incorrect output.

Compare with other configurators like `qwen.ts` which simply call `updateFileWithMarkers` - that method already handles both creating new files and updating existing ones with proper marker placement.

```suggestion
    // updateFileWithMarkers handles both file creation and updates
    // For new files, it creates: frontmatter + markers + content
    // For existing files, it updates content between markers
    const frontmatter = `---\ntrigger: always_on\nalwaysApply: true\n---\n\n`;
    const fullContent = frontmatter + content;
    
    await FileSystemUtils.updateFileWithMarkers(
      filePath,
      fullContent,
      OPENSPEC_MARKERS.start,
      OPENSPEC_MARKERS.end
    );
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread
lifegoon marked this conversation as resolved.
}
Comment on lines +37 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Logic error causes file corruption on updates. After updateFileWithMarkers writes the file (line 46-51), the code reads it back and prepends frontmatter again (line 56-59). On subsequent runs, this creates duplicate frontmatter blocks.

Additionally, frontmatter is only added if file exists after the initial write, which is backwards - it should be added during initial creation.

Fix: Handle new files and updates separately, like SlashCommandConfigurator does (see slash/base.ts:32-43).

Suggested change
async configure(projectPath: string, openspecDir: string): Promise<void> {
// 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`;
let existingContent = '';
if (await FileSystemUtils.fileExists(filePath)) {
existingContent = await FileSystemUtils.readFile(filePath);
existingContent = lingmaRulesTrigger + existingContent;
await FileSystemUtils.writeFile(filePath, existingContent);
}
}
async configure(projectPath: string, openspecDir: string): Promise<void> {
const filePath = path.join(projectPath, this.configFileName);
const content = TemplateManager.getAgentsStandardTemplate();
const frontmatter = `---\ntrigger: always_on\nalwaysApply: true\n---\n`;
if (await FileSystemUtils.fileExists(filePath)) {
// Update existing file: only update content between markers
await FileSystemUtils.updateFileWithMarkers(
filePath,
content,
OPENSPEC_MARKERS.start,
OPENSPEC_MARKERS.end
);
} else {
// Create new file: frontmatter + markers + content
const fullContent = `${frontmatter}\n${OPENSPEC_MARKERS.start}\n${content}\n${OPENSPEC_MARKERS.end}\n`;
await FileSystemUtils.writeFile(filePath, fullContent);
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/configurators/lingma.ts
Line: 37:61

Comment:
**logic:** Logic error causes file corruption on updates. After `updateFileWithMarkers` writes the file (line 46-51), the code reads it back and prepends frontmatter again (line 56-59). On subsequent runs, this creates duplicate frontmatter blocks.

Additionally, frontmatter is only added if file exists after the initial write, which is backwards - it should be added during initial creation.

Fix: Handle new files and updates separately, like `SlashCommandConfigurator` does (see `slash/base.ts:32-43`).

```suggestion
  async configure(projectPath: string, openspecDir: string): Promise<void> {
    const filePath = path.join(projectPath, this.configFileName);
    const content = TemplateManager.getAgentsStandardTemplate();
    const frontmatter = `---\ntrigger: always_on\nalwaysApply: true\n---\n`;

    if (await FileSystemUtils.fileExists(filePath)) {
      // Update existing file: only update content between markers
      await FileSystemUtils.updateFileWithMarkers(
        filePath,
        content,
        OPENSPEC_MARKERS.start,
        OPENSPEC_MARKERS.end
      );
    } else {
      // Create new file: frontmatter + markers + content
      const fullContent = `${frontmatter}\n${OPENSPEC_MARKERS.start}\n${content}\n${OPENSPEC_MARKERS.end}\n`;
      await FileSystemUtils.writeFile(filePath, fullContent);
    }
  }
```

How can I resolve this? If you propose a fix, please make it concise.

}
3 changes: 3 additions & 0 deletions src/core/configurators/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ToolConfigurator> = new Map();
Expand All @@ -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);
Expand All @@ -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 {
Expand Down
72 changes: 72 additions & 0 deletions src/core/configurators/slash/lingma.ts
Original file line number Diff line number Diff line change
@@ -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<SlashCommandId, string> = {
// 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<SlashCommandId, string> = {
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}`;
}
}
3 changes: 3 additions & 0 deletions src/core/configurators/slash/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, SlashCommandConfigurator> = new Map();
Expand All @@ -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);
Expand All @@ -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 {
Expand Down