diff --git a/bmad-copilot/eslint.config.mjs b/bmad-copilot/eslint.config.mjs new file mode 100644 index 000000000..8fd76d513 --- /dev/null +++ b/bmad-copilot/eslint.config.mjs @@ -0,0 +1,23 @@ +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + files: ['src/**/*.ts'], + extends: [ + ...tseslint.configs.recommended, + ], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + }, + }, + { + ignores: ['out/', 'node_modules/'], + } +); diff --git a/bmad-copilot/package.json b/bmad-copilot/package.json index 859482eb2..3aeb91a39 100644 --- a/bmad-copilot/package.json +++ b/bmad-copilot/package.json @@ -85,11 +85,13 @@ "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", - "lint": "eslint src --ext ts" + "lint": "eslint src/" }, "devDependencies": { "@types/vscode": "^1.93.0", "@types/node": "^20.0.0", - "typescript": "^5.5.0" + "eslint": "^9.0.0", + "typescript": "^5.5.0", + "typescript-eslint": "^8.0.0" } } \ No newline at end of file diff --git a/bmad-copilot/src/bmadIndex.ts b/bmad-copilot/src/bmadIndex.ts index 080a64c06..b718865eb 100644 --- a/bmad-copilot/src/bmadIndex.ts +++ b/bmad-copilot/src/bmadIndex.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; -import { logInfo, logWarn, logError } from './logger'; +import { logInfo, logWarn } from './logger'; /** * Well-known paths where BMAD is installed after `npx bmad-method install`. @@ -53,11 +53,17 @@ export function detectBmadRoot(): string | undefined { if (explicit) { const abs = path.isAbsolute(explicit) ? explicit : path.join(wsRoot, explicit); - if (fs.existsSync(abs)) { + try { + const stat = fs.statSync(abs); + if (!stat.isDirectory()) { + logWarn(`Configured bmad.rootPath "${explicit}" exists but is not a directory: ${abs}`); + return undefined; + } logInfo(`Using explicit bmad.rootPath: ${abs}`); return abs; + } catch { + logWarn(`Configured bmad.rootPath "${explicit}" not found at ${abs}`); } - logWarn(`Configured bmad.rootPath "${explicit}" not found at ${abs}`); } const autoDetect = config.get('autoDetect', true); diff --git a/bmad-copilot/src/chatHandler.ts b/bmad-copilot/src/chatHandler.ts index 0f971c6eb..417cadd22 100644 --- a/bmad-copilot/src/chatHandler.ts +++ b/bmad-copilot/src/chatHandler.ts @@ -145,6 +145,28 @@ function truncate(s: string, max: number): string { return s.length > max ? s.slice(0, max - 1) + '…' : s; } +/** + * Wrap `content` in a Markdown fenced code block using a fence that is + * guaranteed not to collide with any backtick sequence inside the content. + * + * Algorithm: find the longest run of consecutive backticks in `content`, + * then use a fence that is at least one backtick longer (minimum 3). + */ +function safeFence(content: string): string { + let maxRun = 0; + let run = 0; + for (const ch of content) { + if (ch === '`') { + run++; + if (run > maxRun) { maxRun = run; } + } else { + run = 0; + } + } + const fence = '`'.repeat(Math.max(3, maxRun + 1)); + return `${fence}\n${content}\n${fence}`; +} + // ─── Run agent / workflow ─────────────────────────────── async function executeRun( @@ -242,7 +264,7 @@ function fallbackPrompt( const assembled = `I want you to adopt the following ${kind} persona and follow its instructions exactly.\n\n--- BEGIN ${kind.toUpperCase()} DEFINITION ---\n${fileContent}\n--- END ${kind.toUpperCase()} DEFINITION ---\n\nNow respond to this task:\n${task}`; - stream.markdown('```\n' + assembled + '\n```\n'); + stream.markdown(safeFence(assembled) + '\n'); stream.button({ title: 'Copy Prompt',