From a6ed09d3a516f5260bfd28be3f2fde2737e03210 Mon Sep 17 00:00:00 2001 From: jax Date: Thu, 6 Nov 2025 10:58:35 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(roocode):=20=E6=B7=BB=E5=8A=A0RooCode?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=94=AF=E6=8C=81=E5=8F=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增RooCode工具集成,包括: - 添加RooCode配置器类 - 注册RooCode到工具注册表 - 实现RooCode模板文件 - 添加RooCode斜杠命令支持 - 更新README文档说明 - 添加相关测试用例 --- README.md | 3 +- openspec/specs/cli-init/spec.md | 14 ++ src/core/config.ts | 1 + src/core/configurators/registry.ts | 3 + src/core/configurators/roocode.ts | 23 ++++ src/core/configurators/slash/registry.ts | 3 + src/core/configurators/slash/roocode.ts | 27 ++++ src/core/templates/index.ts | 5 + src/core/templates/roocode-template.ts | 1 + test/core/init.test.ts | 79 +++++++++++ test/core/update.test.ts | 161 +++++++++++++++++++++++ 11 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 src/core/configurators/roocode.ts create mode 100644 src/core/configurators/slash/roocode.ts create mode 100644 src/core/templates/roocode-template.ts diff --git a/README.md b/README.md index 48a8ea42..b1db78b8 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe | **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` | | **Cline** | Rules in `.clinerules/` directory (`.clinerules/openspec-*.md`) | | **Crush** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.crush/commands/openspec/`) | +| **RooCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.roo/commands/`) | | **Factory Droid** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.factory/commands/`) | | **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` | | **Kilo Code** | `/openspec-proposal.md`, `/openspec-apply.md`, `/openspec-archive.md` (`.kilocode/workflows/`) | @@ -232,7 +233,7 @@ Or run the command yourself in terminal: $ openspec archive add-profile-filters --yes # Archive the completed change without prompts ``` -**Note:** Tools with native slash commands (Claude Code, CodeBuddy, Cursor, Codex, Qoder) can use the shortcuts shown. All other tools work with natural language requests to "create an OpenSpec proposal", "apply the OpenSpec change", or "archive the change". +**Note:** Tools with native slash commands (Claude Code, CodeBuddy, Cursor, Codex, Qoder, RooCode) can use the shortcuts shown. All other tools work with natural language requests to "create an OpenSpec proposal", "apply the OpenSpec change", or "archive the change". ## Command Reference diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index 7bfb49cb..6515d6ff 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -91,6 +91,12 @@ This project uses OpenSpec to manage AI assistant workflows. ``` +#### Scenario: Configuring RooCode + +- **WHEN** RooCode is selected +- **THEN** create or update `ROOCODE.md` in the project root directory (not inside `openspec/`) +- **AND** populate the managed block with a short stub that points teammates to `@/openspec/AGENTS.md` + ### Requirement: Interactive Mode The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions. #### Scenario: Displaying interactive menu @@ -231,6 +237,14 @@ The init command SHALL generate slash command files for supported editors using - **AND** wrap the shared template body with OpenSpec markers so `openspec update` can refresh the content - **AND** each template includes instructions for the relevant OpenSpec workflow stage +#### Scenario: Generating slash commands for RooCode +- **WHEN** the user selects RooCode during initialization +- **THEN** create `.roo/commands/openspec-proposal.md`, `.roo/commands/openspec-apply.md`, and `.roo/commands/openspec-archive.md` +- **AND** populate each file from shared templates so command text matches other tools +- **AND** include simple Markdown headings (e.g., `# OpenSpec: Proposal`) without YAML frontmatter +- **AND** wrap the generated content in OpenSpec managed markers where applicable so `openspec update` can safely refresh the commands +- **AND** each template includes instructions for the relevant OpenSpec workflow stage + ### Requirement: Non-Interactive Mode The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases. diff --git a/src/core/config.ts b/src/core/config.ts index ba60a427..d28e7baa 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -20,6 +20,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie' }, { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code' }, { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline' }, + { name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode' }, { name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code' }, { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict' }, { name: 'Crush', value: 'crush', available: true, successLabel: 'Crush' }, diff --git a/src/core/configurators/registry.ts b/src/core/configurators/registry.ts index b1be2072..97b47113 100644 --- a/src/core/configurators/registry.ts +++ b/src/core/configurators/registry.ts @@ -6,6 +6,7 @@ import { CostrictConfigurator } from './costrict.js'; import { QoderConfigurator } from './qoder.js'; import { AgentsStandardConfigurator } from './agents.js'; import { QwenConfigurator } from './qwen.js'; +import { RooCodeConfigurator } from './roocode.js'; export class ToolRegistry { private static tools: Map = new Map(); @@ -18,6 +19,7 @@ export class ToolRegistry { const qoderConfigurator = new QoderConfigurator(); const agentsConfigurator = new AgentsStandardConfigurator(); const qwenConfigurator = new QwenConfigurator(); + const roocodeConfigurator = new RooCodeConfigurator(); // Register with the ID that matches the checkbox value this.tools.set('claude', claudeConfigurator); this.tools.set('cline', clineConfigurator); @@ -26,6 +28,7 @@ export class ToolRegistry { this.tools.set('qoder', qoderConfigurator); this.tools.set('agents', agentsConfigurator); this.tools.set('qwen', qwenConfigurator); + this.tools.set('roocode', roocodeConfigurator); } static register(tool: ToolConfigurator): void { diff --git a/src/core/configurators/roocode.ts b/src/core/configurators/roocode.ts new file mode 100644 index 00000000..3969f0cf --- /dev/null +++ b/src/core/configurators/roocode.ts @@ -0,0 +1,23 @@ +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'; + +export class RooCodeConfigurator implements ToolConfigurator { + name = 'RooCode'; + configFileName = 'ROOCODE.md'; + isAvailable = true; + + async configure(projectPath: string, _openspecDir: string): Promise { + const filePath = path.join(projectPath, this.configFileName); + const content = TemplateManager.getRooCodeTemplate(); + + await FileSystemUtils.updateFileWithMarkers( + filePath, + content, + OPENSPEC_MARKERS.start, + OPENSPEC_MARKERS.end + ); + } +} diff --git a/src/core/configurators/slash/registry.ts b/src/core/configurators/slash/registry.ts index 1872b619..65d1bd2d 100644 --- a/src/core/configurators/slash/registry.ts +++ b/src/core/configurators/slash/registry.ts @@ -15,6 +15,7 @@ import { ClineSlashCommandConfigurator } from './cline.js'; import { CrushSlashCommandConfigurator } from './crush.js'; import { CostrictSlashCommandConfigurator } from './costrict.js'; import { QwenSlashCommandConfigurator } from './qwen.js'; +import { RooCodeSlashCommandConfigurator } from './roocode.js'; export class SlashCommandRegistry { private static configurators: Map = new Map(); @@ -36,6 +37,7 @@ export class SlashCommandRegistry { const crush = new CrushSlashCommandConfigurator(); const costrict = new CostrictSlashCommandConfigurator(); const qwen = new QwenSlashCommandConfigurator(); + const roocode = new RooCodeSlashCommandConfigurator(); this.configurators.set(claude.toolId, claude); this.configurators.set(codeBuddy.toolId, codeBuddy); @@ -53,6 +55,7 @@ export class SlashCommandRegistry { this.configurators.set(crush.toolId, crush); this.configurators.set(costrict.toolId, costrict); this.configurators.set(qwen.toolId, qwen); + this.configurators.set(roocode.toolId, roocode); } static register(configurator: SlashCommandConfigurator): void { diff --git a/src/core/configurators/slash/roocode.ts b/src/core/configurators/slash/roocode.ts new file mode 100644 index 00000000..faf89b41 --- /dev/null +++ b/src/core/configurators/slash/roocode.ts @@ -0,0 +1,27 @@ +import { SlashCommandConfigurator } from './base.js'; +import { SlashCommandId } from '../../templates/index.js'; + +const NEW_FILE_PATHS: Record = { + proposal: '.roo/commands/openspec-proposal.md', + apply: '.roo/commands/openspec-apply.md', + archive: '.roo/commands/openspec-archive.md' +}; + +export class RooCodeSlashCommandConfigurator extends SlashCommandConfigurator { + readonly toolId = 'roocode'; + readonly isAvailable = true; + + protected getRelativePath(id: SlashCommandId): string { + return NEW_FILE_PATHS[id]; + } + + protected getFrontmatter(id: SlashCommandId): string | undefined { + 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.' + }; + const description = descriptions[id]; + return `# OpenSpec: ${id.charAt(0).toUpperCase() + id.slice(1)}\n\n${description}`; + } +} diff --git a/src/core/templates/index.ts b/src/core/templates/index.ts index 8dab4b5f..cc110192 100644 --- a/src/core/templates/index.ts +++ b/src/core/templates/index.ts @@ -2,6 +2,7 @@ import { agentsTemplate } from './agents-template.js'; import { projectTemplate, ProjectContext } from './project-template.js'; import { claudeTemplate } from './claude-template.js'; import { clineTemplate } from './cline-template.js'; +import { roocodeTemplate } from './roocode-template.js'; import { costrictTemplate } from './costrict-template.js'; import { agentsRootStubTemplate } from './agents-root-stub.js'; import { getSlashCommandBody, SlashCommandId } from './slash-command-templates.js'; @@ -33,6 +34,10 @@ export class TemplateManager { return clineTemplate; } + static getRooCodeTemplate(): string { + return roocodeTemplate; + } + static getCostrictTemplate(): string { return costrictTemplate; } diff --git a/src/core/templates/roocode-template.ts b/src/core/templates/roocode-template.ts new file mode 100644 index 00000000..05cf0cb6 --- /dev/null +++ b/src/core/templates/roocode-template.ts @@ -0,0 +1 @@ +export { agentsRootStubTemplate as roocodeTemplate } from './agents-root-stub.js'; diff --git a/test/core/init.test.ts b/test/core/init.test.ts index dad27726..2a355236 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -1127,6 +1127,53 @@ describe('InitCommand', () => { expect(costrictChoice.configured).toBe(true); }); + it('should create RooCode slash command files with templates', async () => { + queueSelections('roocode', DONE); + + await initCommand.execute(testDir); + + const rooProposal = path.join( + testDir, + '.roo/commands/openspec-proposal.md' + ); + const rooApply = path.join( + testDir, + '.roo/commands/openspec-apply.md' + ); + const rooArchive = path.join( + testDir, + '.roo/commands/openspec-archive.md' + ); + + expect(await fileExists(rooProposal)).toBe(true); + expect(await fileExists(rooApply)).toBe(true); + expect(await fileExists(rooArchive)).toBe(true); + + const proposalContent = await fs.readFile(rooProposal, 'utf-8'); + expect(proposalContent).toContain('# OpenSpec: Proposal'); + expect(proposalContent).toContain('**Guardrails**'); + + const applyContent = await fs.readFile(rooApply, 'utf-8'); + expect(applyContent).toContain('# OpenSpec: Apply'); + expect(applyContent).toContain('Work through tasks sequentially'); + + const archiveContent = await fs.readFile(rooArchive, 'utf-8'); + expect(archiveContent).toContain('# OpenSpec: Archive'); + expect(archiveContent).toContain('openspec archive --yes'); + }); + + it('should mark RooCode as already configured during extend mode', async () => { + queueSelections('roocode', DONE, 'roocode', DONE); + await initCommand.execute(testDir); + await initCommand.execute(testDir); + + const secondRunArgs = mockPrompt.mock.calls[1][0]; + const rooChoice = secondRunArgs.choices.find( + (choice: any) => choice.value === 'roocode' + ); + expect(rooChoice.configured).toBe(true); + }); + it('should create Qoder slash command files with templates', async () => { queueSelections('qoder', DONE); @@ -1212,6 +1259,38 @@ describe('InitCommand', () => { expect(content).toContain(''); }); + it('should create ROOCODE.md when RooCode is selected', async () => { + queueSelections('roocode', DONE); + + await initCommand.execute(testDir); + + const roocodePath = path.join(testDir, 'ROOCODE.md'); + expect(await fileExists(roocodePath)).toBe(true); + + const content = await fs.readFile(roocodePath, 'utf-8'); + expect(content).toContain(''); + expect(content).toContain("@/openspec/AGENTS.md"); + expect(content).toContain('openspec update'); + expect(content).toContain(''); + }); + + it('should update existing ROOCODE.md with markers', async () => { + queueSelections('roocode', DONE); + + const roocodePath = path.join(testDir, 'ROOCODE.md'); + const existingContent = + '# My RooCode Instructions\nCustom instructions here'; + await fs.writeFile(roocodePath, existingContent); + + await initCommand.execute(testDir); + + const updatedContent = await fs.readFile(roocodePath, 'utf-8'); + expect(updatedContent).toContain(''); + expect(updatedContent).toContain("@/openspec/AGENTS.md"); + expect(updatedContent).toContain('openspec update'); + expect(updatedContent).toContain(''); + expect(updatedContent).toContain('Custom instructions here'); + }); it('should update existing COSTRICT.md with markers', async () => { queueSelections('costrict', DONE); diff --git a/test/core/update.test.ts b/test/core/update.test.ts index 9bb4a2f0..7beb7bf3 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -1023,6 +1023,79 @@ Old slash content consoleSpy.mockRestore(); }); + it('should refresh existing RooCode slash command files', async () => { + const rooPath = path.join( + testDir, + '.roo/commands/openspec-proposal.md' + ); + await fs.mkdir(path.dirname(rooPath), { recursive: true }); + const initialContent = `# OpenSpec: Proposal + +Old description + + +Old body +`; + await fs.writeFile(rooPath, initialContent); + + const consoleSpy = vi.spyOn(console, 'log'); + + await updateCommand.execute(testDir); + + const updated = await fs.readFile(rooPath, 'utf-8'); + // For RooCode, the header is Markdown, preserve it and update only managed block + expect(updated).toContain('# OpenSpec: Proposal'); + expect(updated).toContain('**Guardrails**'); + expect(updated).toContain( + 'Validate with `openspec validate --strict`' + ); + expect(updated).not.toContain('Old body'); + + const [logMessage] = consoleSpy.mock.calls[0]; + expect(logMessage).toContain( + 'Updated OpenSpec instructions (openspec/AGENTS.md' + ); + expect(logMessage).toContain('AGENTS.md (created)'); + expect(logMessage).toContain( + 'Updated slash commands: .roo/commands/openspec-proposal.md' + ); + + consoleSpy.mockRestore(); + }); + + it('should not create missing RooCode slash command files on update', async () => { + const rooApply = path.join( + testDir, + '.roo/commands/openspec-apply.md' + ); + + // Only create apply; leave proposal and archive missing + await fs.mkdir(path.dirname(rooApply), { recursive: true }); + await fs.writeFile( + rooApply, + `# OpenSpec: Apply + + +Old body +` + ); + + await updateCommand.execute(testDir); + + const rooProposal = path.join( + testDir, + '.roo/commands/openspec-proposal.md' + ); + const rooArchive = path.join( + testDir, + '.roo/commands/openspec-archive.md' + ); + + // Confirm they weren't created by update + await expect(FileSystemUtils.fileExists(rooProposal)).resolves.toBe(false); + await expect(FileSystemUtils.fileExists(rooArchive)).resolves.toBe(false); + }); + it('should not create missing CoStrict slash command files on update', async () => { const costrictApply = path.join( testDir, @@ -1133,6 +1206,56 @@ More instructions after.`; consoleSpy.mockRestore(); }); + it('should update only existing ROOCODE.md file', async () => { + // Create ROOCODE.md file with initial content + const roocodePath = path.join(testDir, 'ROOCODE.md'); + const initialContent = `# RooCode Instructions + +Some existing RooCode instructions here. + + +Old OpenSpec content + + +More instructions after.`; + await fs.writeFile(roocodePath, initialContent); + + const consoleSpy = vi.spyOn(console, 'log'); + + // Execute update command + await updateCommand.execute(testDir); + + // Check that ROOCODE.md was updated + const updatedContent = await fs.readFile(roocodePath, 'utf-8'); + expect(updatedContent).toContain(''); + expect(updatedContent).toContain(''); + expect(updatedContent).toContain("@/openspec/AGENTS.md"); + expect(updatedContent).toContain('openspec update'); + expect(updatedContent).toContain('Some existing RooCode instructions here'); + expect(updatedContent).toContain('More instructions after'); + + // Check console output + const [logMessage] = consoleSpy.mock.calls[0]; + expect(logMessage).toContain( + 'Updated OpenSpec instructions (openspec/AGENTS.md' + ); + expect(logMessage).toContain('AGENTS.md (created)'); + expect(logMessage).toContain('Updated AI tool files: ROOCODE.md'); + consoleSpy.mockRestore(); + }); + + it('should not create ROOCODE.md if it does not exist', async () => { + // Ensure ROOCODE.md does not exist + const roocodePath = path.join(testDir, 'ROOCODE.md'); + + // Execute update command + await updateCommand.execute(testDir); + + // Check that ROOCODE.md was not created + const fileExists = await FileSystemUtils.fileExists(roocodePath); + expect(fileExists).toBe(false); + }); + it('should not create COSTRICT.md if it does not exist', async () => { // Ensure COSTRICT.md does not exist const costrictPath = path.join(testDir, 'COSTRICT.md'); @@ -1442,4 +1565,42 @@ Old content errorSpy.mockRestore(); writeSpy.mockRestore(); }); + + it('should handle configurator errors gracefully for RooCode', async () => { + // Create ROOCODE.md file but simulate write error to cause a failure + const roocodePath = path.join(testDir, 'ROOCODE.md'); + await fs.writeFile( + roocodePath, + '\nOld\n' + ); + + const consoleSpy = vi.spyOn(console, 'log'); + const errorSpy = vi.spyOn(console, 'error'); + const originalWriteFile = FileSystemUtils.writeFile.bind(FileSystemUtils); + const writeSpy = vi + .spyOn(FileSystemUtils, 'writeFile') + .mockImplementation(async (filePath, content) => { + if (filePath.endsWith('ROOCODE.md')) { + throw new Error('EACCES: permission denied, open'); + } + + return originalWriteFile(filePath, content); + }); + + // Execute update command - should not throw + await updateCommand.execute(testDir); + + // Should report the failure + expect(errorSpy).toHaveBeenCalled(); + const [logMessage] = consoleSpy.mock.calls[0]; + expect(logMessage).toContain( + 'Updated OpenSpec instructions (openspec/AGENTS.md' + ); + expect(logMessage).toContain('AGENTS.md (created)'); + expect(logMessage).toContain('Failed to update: ROOCODE.md'); + + consoleSpy.mockRestore(); + errorSpy.mockRestore(); + writeSpy.mockRestore(); + }); }); From c5cd2b6edf9e9ad5f3b362af93ad45a4d27e27f0 Mon Sep 17 00:00:00 2001 From: jax Date: Fri, 14 Nov 2025 00:43:07 +0800 Subject: [PATCH 2/2] Remove RooCode related configurations from the project. This includes deleting the RooCode configurator, its template, and associated tests. --- openspec/specs/cli-init/spec.md | 6 -- src/core/configurators/registry.ts | 3 - src/core/configurators/roocode.ts | 23 ------- src/core/templates/index.ts | 5 -- src/core/templates/roocode-template.ts | 1 - test/core/init.test.ts | 33 ---------- test/core/update.test.ts | 87 -------------------------- 7 files changed, 158 deletions(-) delete mode 100644 src/core/configurators/roocode.ts delete mode 100644 src/core/templates/roocode-template.ts diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index e608bea8..4ccb0260 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -91,12 +91,6 @@ This project uses OpenSpec to manage AI assistant workflows. ``` -#### Scenario: Configuring RooCode - -- **WHEN** RooCode is selected -- **THEN** create or update `ROOCODE.md` in the project root directory (not inside `openspec/`) -- **AND** populate the managed block with a short stub that points teammates to `@/openspec/AGENTS.md` - ### Requirement: Interactive Mode The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions. #### Scenario: Displaying interactive menu diff --git a/src/core/configurators/registry.ts b/src/core/configurators/registry.ts index 97b47113..b1be2072 100644 --- a/src/core/configurators/registry.ts +++ b/src/core/configurators/registry.ts @@ -6,7 +6,6 @@ import { CostrictConfigurator } from './costrict.js'; import { QoderConfigurator } from './qoder.js'; import { AgentsStandardConfigurator } from './agents.js'; import { QwenConfigurator } from './qwen.js'; -import { RooCodeConfigurator } from './roocode.js'; export class ToolRegistry { private static tools: Map = new Map(); @@ -19,7 +18,6 @@ export class ToolRegistry { const qoderConfigurator = new QoderConfigurator(); const agentsConfigurator = new AgentsStandardConfigurator(); const qwenConfigurator = new QwenConfigurator(); - const roocodeConfigurator = new RooCodeConfigurator(); // Register with the ID that matches the checkbox value this.tools.set('claude', claudeConfigurator); this.tools.set('cline', clineConfigurator); @@ -28,7 +26,6 @@ export class ToolRegistry { this.tools.set('qoder', qoderConfigurator); this.tools.set('agents', agentsConfigurator); this.tools.set('qwen', qwenConfigurator); - this.tools.set('roocode', roocodeConfigurator); } static register(tool: ToolConfigurator): void { diff --git a/src/core/configurators/roocode.ts b/src/core/configurators/roocode.ts deleted file mode 100644 index 3969f0cf..00000000 --- a/src/core/configurators/roocode.ts +++ /dev/null @@ -1,23 +0,0 @@ -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'; - -export class RooCodeConfigurator implements ToolConfigurator { - name = 'RooCode'; - configFileName = 'ROOCODE.md'; - isAvailable = true; - - async configure(projectPath: string, _openspecDir: string): Promise { - const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getRooCodeTemplate(); - - await FileSystemUtils.updateFileWithMarkers( - filePath, - content, - OPENSPEC_MARKERS.start, - OPENSPEC_MARKERS.end - ); - } -} diff --git a/src/core/templates/index.ts b/src/core/templates/index.ts index cc110192..8dab4b5f 100644 --- a/src/core/templates/index.ts +++ b/src/core/templates/index.ts @@ -2,7 +2,6 @@ import { agentsTemplate } from './agents-template.js'; import { projectTemplate, ProjectContext } from './project-template.js'; import { claudeTemplate } from './claude-template.js'; import { clineTemplate } from './cline-template.js'; -import { roocodeTemplate } from './roocode-template.js'; import { costrictTemplate } from './costrict-template.js'; import { agentsRootStubTemplate } from './agents-root-stub.js'; import { getSlashCommandBody, SlashCommandId } from './slash-command-templates.js'; @@ -34,10 +33,6 @@ export class TemplateManager { return clineTemplate; } - static getRooCodeTemplate(): string { - return roocodeTemplate; - } - static getCostrictTemplate(): string { return costrictTemplate; } diff --git a/src/core/templates/roocode-template.ts b/src/core/templates/roocode-template.ts deleted file mode 100644 index 05cf0cb6..00000000 --- a/src/core/templates/roocode-template.ts +++ /dev/null @@ -1 +0,0 @@ -export { agentsRootStubTemplate as roocodeTemplate } from './agents-root-stub.js'; diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 45d5c903..a76b7fee 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -1325,39 +1325,6 @@ describe('InitCommand', () => { expect(content).toContain('openspec update'); expect(content).toContain(''); }); - - it('should create ROOCODE.md when RooCode is selected', async () => { - queueSelections('roocode', DONE); - - await initCommand.execute(testDir); - - const roocodePath = path.join(testDir, 'ROOCODE.md'); - expect(await fileExists(roocodePath)).toBe(true); - - const content = await fs.readFile(roocodePath, 'utf-8'); - expect(content).toContain(''); - expect(content).toContain("@/openspec/AGENTS.md"); - expect(content).toContain('openspec update'); - expect(content).toContain(''); - }); - - it('should update existing ROOCODE.md with markers', async () => { - queueSelections('roocode', DONE); - - const roocodePath = path.join(testDir, 'ROOCODE.md'); - const existingContent = - '# My RooCode Instructions\nCustom instructions here'; - await fs.writeFile(roocodePath, existingContent); - - await initCommand.execute(testDir); - - const updatedContent = await fs.readFile(roocodePath, 'utf-8'); - expect(updatedContent).toContain(''); - expect(updatedContent).toContain("@/openspec/AGENTS.md"); - expect(updatedContent).toContain('openspec update'); - expect(updatedContent).toContain(''); - expect(updatedContent).toContain('Custom instructions here'); - }); it('should update existing COSTRICT.md with markers', async () => { queueSelections('costrict', DONE); diff --git a/test/core/update.test.ts b/test/core/update.test.ts index aa27a8dc..d462d64e 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -1254,55 +1254,6 @@ More instructions after.`; consoleSpy.mockRestore(); }); - it('should update only existing ROOCODE.md file', async () => { - // Create ROOCODE.md file with initial content - const roocodePath = path.join(testDir, 'ROOCODE.md'); - const initialContent = `# RooCode Instructions - -Some existing RooCode instructions here. - - -Old OpenSpec content - - -More instructions after.`; - await fs.writeFile(roocodePath, initialContent); - - const consoleSpy = vi.spyOn(console, 'log'); - - // Execute update command - await updateCommand.execute(testDir); - - // Check that ROOCODE.md was updated - const updatedContent = await fs.readFile(roocodePath, 'utf-8'); - expect(updatedContent).toContain(''); - expect(updatedContent).toContain(''); - expect(updatedContent).toContain("@/openspec/AGENTS.md"); - expect(updatedContent).toContain('openspec update'); - expect(updatedContent).toContain('Some existing RooCode instructions here'); - expect(updatedContent).toContain('More instructions after'); - - // Check console output - const [logMessage] = consoleSpy.mock.calls[0]; - expect(logMessage).toContain( - 'Updated OpenSpec instructions (openspec/AGENTS.md' - ); - expect(logMessage).toContain('AGENTS.md (created)'); - expect(logMessage).toContain('Updated AI tool files: ROOCODE.md'); - consoleSpy.mockRestore(); - }); - - it('should not create ROOCODE.md if it does not exist', async () => { - // Ensure ROOCODE.md does not exist - const roocodePath = path.join(testDir, 'ROOCODE.md'); - - // Execute update command - await updateCommand.execute(testDir); - - // Check that ROOCODE.md was not created - const fileExists = await FileSystemUtils.fileExists(roocodePath); - expect(fileExists).toBe(false); - }); it('should not create COSTRICT.md if it does not exist', async () => { // Ensure COSTRICT.md does not exist @@ -1613,42 +1564,4 @@ Old content errorSpy.mockRestore(); writeSpy.mockRestore(); }); - - it('should handle configurator errors gracefully for RooCode', async () => { - // Create ROOCODE.md file but simulate write error to cause a failure - const roocodePath = path.join(testDir, 'ROOCODE.md'); - await fs.writeFile( - roocodePath, - '\nOld\n' - ); - - const consoleSpy = vi.spyOn(console, 'log'); - const errorSpy = vi.spyOn(console, 'error'); - const originalWriteFile = FileSystemUtils.writeFile.bind(FileSystemUtils); - const writeSpy = vi - .spyOn(FileSystemUtils, 'writeFile') - .mockImplementation(async (filePath, content) => { - if (filePath.endsWith('ROOCODE.md')) { - throw new Error('EACCES: permission denied, open'); - } - - return originalWriteFile(filePath, content); - }); - - // Execute update command - should not throw - await updateCommand.execute(testDir); - - // Should report the failure - expect(errorSpy).toHaveBeenCalled(); - const [logMessage] = consoleSpy.mock.calls[0]; - expect(logMessage).toContain( - 'Updated OpenSpec instructions (openspec/AGENTS.md' - ); - expect(logMessage).toContain('AGENTS.md (created)'); - expect(logMessage).toContain('Failed to update: ROOCODE.md'); - - consoleSpy.mockRestore(); - errorSpy.mockRestore(); - writeSpy.mockRestore(); - }); });