From b999dd1315b7ca87cac64558eb31e8d0084c8492 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Sun, 5 Oct 2025 20:13:11 -0700 Subject: [PATCH] refactor(ide): delegate detection to handlers (#680) --- tools/cli/installers/lib/ide/_base-ide.js | 36 +++++++++++++++++++++++ tools/cli/installers/lib/ide/auggie.js | 1 + tools/cli/installers/lib/ide/codex.js | 14 +++++++++ tools/cli/installers/lib/ide/manager.js | 36 ++--------------------- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index 4ff74731..03ee39de 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -15,6 +15,8 @@ class BaseIdeSetup { this.preferred = preferred; // Whether this IDE should be shown in preferred list this.configDir = null; // Override in subclasses this.rulesDir = null; // Override in subclasses + this.configFile = null; // Override in subclasses when detection is file-based + this.detectionPaths = []; // Additional paths that indicate the IDE is configured this.xmlHandler = new XmlHandler(); } @@ -46,6 +48,40 @@ class BaseIdeSetup { } } + /** + * Detect whether this IDE already has configuration in the project + * Subclasses can override for custom logic + * @param {string} projectDir - Project directory + * @returns {boolean} + */ + async detect(projectDir) { + const pathsToCheck = []; + + if (this.configDir) { + pathsToCheck.push(path.join(projectDir, this.configDir)); + } + + if (this.configFile) { + pathsToCheck.push(path.join(projectDir, this.configFile)); + } + + if (Array.isArray(this.detectionPaths)) { + for (const candidate of this.detectionPaths) { + if (!candidate) continue; + const resolved = path.isAbsolute(candidate) ? candidate : path.join(projectDir, candidate); + pathsToCheck.push(resolved); + } + } + + for (const candidate of pathsToCheck) { + if (await fs.pathExists(candidate)) { + return true; + } + } + + return false; + } + /** * Get list of agents from BMAD installation * @param {string} bmadDir - BMAD installation directory diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index 173056fd..00df56bb 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -16,6 +16,7 @@ class AuggieSetup extends BaseIdeSetup { { name: 'User Home (~/.auggie/commands)', value: path.join(os.homedir(), '.auggie', 'commands') }, { name: 'Custom Location', value: 'custom' }, ]; + this.detectionPaths = ['.auggie']; } /** diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 03f17818..6c6313df 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -80,6 +80,20 @@ class CodexSetup extends BaseIdeSetup { }; } + /** + * Detect Codex installation by checking for BMAD prompt exports + */ + async detect(_projectDir) { + const destDir = this.getCodexPromptDir(); + + if (!(await fs.pathExists(destDir))) { + return false; + } + + const entries = await fs.readdir(destDir); + return entries.some((entry) => entry.startsWith('bmad-')); + } + /** * Collect Claude-style artifacts for Codex export. * Returns the normalized artifact list for further processing. diff --git a/tools/cli/installers/lib/ide/manager.js b/tools/cli/installers/lib/ide/manager.js index a7677d70..b438aaca 100644 --- a/tools/cli/installers/lib/ide/manager.js +++ b/tools/cli/installers/lib/ide/manager.js @@ -1,7 +1,6 @@ const fs = require('fs-extra'); const path = require('node:path'); const chalk = require('chalk'); -const os = require('node:os'); /** * IDE Manager - handles IDE-specific setup @@ -166,38 +165,9 @@ class IdeManager { async detectInstalledIdes(projectDir) { const detected = []; - // Check for IDE-specific directories - const ideChecks = { - cursor: '.cursor', - 'claude-code': '.claude', - windsurf: '.windsurf', - cline: '.clinerules', - roo: '.roomodes', - trae: '.trae', - kilo: '.kilocodemodes', - gemini: '.gemini', - qwen: '.qwen', - crush: '.crush', - iflow: '.iflow', - auggie: '.auggie', - 'github-copilot': '.github/chatmodes', - vscode: '.vscode', - idea: '.idea', - }; - - for (const [ide, dir] of Object.entries(ideChecks)) { - const idePath = path.join(projectDir, dir); - if (await fs.pathExists(idePath)) { - detected.push(ide); - } - } - - // Check Codex prompt directory for BMAD exports - const codexPromptDir = path.join(os.homedir(), '.codex', 'prompts'); - if (await fs.pathExists(codexPromptDir)) { - const codexEntries = await fs.readdir(codexPromptDir); - if (codexEntries.some((file) => file.startsWith('bmad-'))) { - detected.push('codex'); + for (const [name, handler] of this.handlers) { + if (typeof handler.detect === 'function' && (await handler.detect(projectDir))) { + detected.push(name); } }