feat: add universal logger instead of console.log.

- fixed esbuild issues
- converted to npm instead of pnpm since root project is npm (might switch whole project to pnpm later)
This commit is contained in:
Ralph Khreish
2025-07-28 17:03:35 +03:00
parent 662a4eaecb
commit 722b6c5836
20 changed files with 8232 additions and 6766 deletions

View File

@@ -69,8 +69,8 @@ async function main() {
bundle: true, bundle: true,
format: 'cjs', format: 'cjs',
minify: production, minify: production,
sourcemap: !production, sourcemap: !production ? 'inline' : false,
sourcesContent: false, sourcesContent: !production,
platform: 'node', platform: 'node',
outdir: 'dist', outdir: 'dist',
external: ['vscode'], external: ['vscode'],
@@ -90,8 +90,8 @@ async function main() {
format: 'iife', format: 'iife',
globalName: 'App', globalName: 'App',
minify: production, minify: production,
sourcemap: !production, sourcemap: !production ? 'inline' : false,
sourcesContent: false, sourcesContent: !production,
platform: 'browser', platform: 'browser',
outdir: 'dist', outdir: 'dist',
logLevel: 'silent', logLevel: 'silent',
@@ -99,6 +99,13 @@ async function main() {
jsx: 'automatic', jsx: 'automatic',
jsxImportSource: 'react', jsxImportSource: 'react',
external: ['*.css'], external: ['*.css'],
// Bundle React with webview since it's not available in the runtime
// This prevents the multiple React instances issue
// Ensure React is resolved from the workspace root to avoid duplicates
alias: {
react: path.resolve(__dirname, '../../node_modules/react'),
'react-dom': path.resolve(__dirname, '../../node_modules/react-dom')
},
define: { define: {
'process.env.NODE_ENV': production ? '"production"' : '"development"', 'process.env.NODE_ENV': production ? '"production"' : '"development"',
global: 'globalThis' global: 'globalThis'

View File

@@ -9,12 +9,7 @@
"engines": { "engines": {
"vscode": "^1.93.0" "vscode": "^1.93.0"
}, },
"categories": [ "categories": ["AI", "Visualization", "Education", "Other"],
"AI",
"Visualization",
"Education",
"Other"
],
"main": "./dist/extension.js", "main": "./dist/extension.js",
"contributes": { "contributes": {
"commands": [ "commands": [
@@ -48,11 +43,7 @@
"items": { "items": {
"type": "string" "type": "string"
}, },
"default": [ "default": ["-y", "--package=task-master-ai", "task-master-ai"],
"-y",
"--package=task-master-ai",
"task-master-ai"
],
"description": "An array of arguments to pass to the MCP server command." "description": "An array of arguments to pass to the MCP server command."
}, },
"taskmaster.mcp.cwd": { "taskmaster.mcp.cwd": {
@@ -112,11 +103,7 @@
}, },
"taskmaster.ui.theme": { "taskmaster.ui.theme": {
"type": "string", "type": "string",
"enum": [ "enum": ["auto", "light", "dark"],
"auto",
"light",
"dark"
],
"default": "auto", "default": "auto",
"description": "UI theme preference" "description": "UI theme preference"
}, },
@@ -177,12 +164,7 @@
}, },
"taskmaster.debug.logLevel": { "taskmaster.debug.logLevel": {
"type": "string", "type": "string",
"enum": [ "enum": ["error", "warn", "info", "debug"],
"error",
"warn",
"info",
"debug"
],
"default": "info", "default": "info",
"description": "Logging level" "description": "Logging level"
}, },
@@ -207,15 +189,15 @@
} }
}, },
"scripts": { "scripts": {
"vscode:prepublish": false, "vscode:prepublish": "npm run build",
"build": "pnpm run build:js && pnpm run build:css", "build": "npm run build:js && npm run build:css",
"build:js": "node ./esbuild.js --production", "build:js": "node ./esbuild.js --production",
"build:css": "npx @tailwindcss/cli -o ./dist/index.css --minify", "build:css": "npx @tailwindcss/cli -o ./dist/index.css --minify",
"package": "pnpm exec node ./package.mjs", "package": "npm exec node ./package.mjs",
"package:direct": "node ./package.mjs", "package:direct": "node ./package.mjs",
"debug:env": "node ./debug-env.mjs", "debug:env": "node ./debug-env.mjs",
"compile": "node ./esbuild.js", "compile": "node ./esbuild.js",
"watch": "pnpm run watch:js & pnpm run watch:css", "watch": "npm run watch:js & npm run watch:css",
"watch:js": "node ./esbuild.js --watch", "watch:js": "node ./esbuild.js --watch",
"watch:css": "npx @tailwindcss/cli -o ./dist/index.css --watch", "watch:css": "npx @tailwindcss/cli -o ./dist/index.css --watch",
"lint": "eslint src --ext ts,tsx", "lint": "eslint src --ext ts,tsx",
@@ -254,8 +236,6 @@
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "8.5.6", "postcss": "8.5.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "4.1.11", "tailwindcss": "4.1.11",
"typescript": "^5.8.3" "typescript": "^5.8.3"

View File

@@ -60,16 +60,25 @@ try {
const publishPackagePath = path.resolve(__dirname, 'package.publish.json'); const publishPackagePath = path.resolve(__dirname, 'package.publish.json');
const devPackage = JSON.parse(fs.readFileSync(devPackagePath, 'utf8')); const devPackage = JSON.parse(fs.readFileSync(devPackagePath, 'utf8'));
const publishPackage = JSON.parse(fs.readFileSync(publishPackagePath, 'utf8')); const publishPackage = JSON.parse(
fs.readFileSync(publishPackagePath, 'utf8')
);
// Check if versions are in sync // Check if versions are in sync
if (devPackage.version !== publishPackage.version) { if (devPackage.version !== publishPackage.version) {
console.log(` - Version sync needed: ${publishPackage.version}${devPackage.version}`); console.log(
` - Version sync needed: ${publishPackage.version}${devPackage.version}`
);
publishPackage.version = devPackage.version; publishPackage.version = devPackage.version;
// Update the source package.publish.json file // Update the source package.publish.json file
fs.writeFileSync(publishPackagePath, JSON.stringify(publishPackage, null, '\t') + '\n'); fs.writeFileSync(
console.log(` - Updated package.publish.json version to ${devPackage.version}`); publishPackagePath,
JSON.stringify(publishPackage, null, '\t') + '\n'
);
console.log(
` - Updated package.publish.json version to ${devPackage.version}`
);
} else { } else {
console.log(` - Versions already in sync: ${devPackage.version}`); console.log(` - Versions already in sync: ${devPackage.version}`);
} }

View File

@@ -8,12 +8,7 @@
"engines": { "engines": {
"vscode": "^1.93.0" "vscode": "^1.93.0"
}, },
"categories": [ "categories": ["AI", "Visualization", "Education", "Other"],
"AI",
"Visualization",
"Education",
"Other"
],
"keywords": [ "keywords": [
"kanban", "kanban",
"kanban board", "kanban board",
@@ -87,11 +82,7 @@
"items": { "items": {
"type": "string" "type": "string"
}, },
"default": [ "default": ["-y", "--package=task-master-ai", "task-master-ai"],
"-y",
"--package=task-master-ai",
"task-master-ai"
],
"description": "An array of arguments to pass to the MCP server command." "description": "An array of arguments to pass to the MCP server command."
}, },
"taskmaster.mcp.cwd": { "taskmaster.mcp.cwd": {
@@ -151,11 +142,7 @@
}, },
"taskmaster.ui.theme": { "taskmaster.ui.theme": {
"type": "string", "type": "string",
"enum": [ "enum": ["auto", "light", "dark"],
"auto",
"light",
"dark"
],
"default": "auto", "default": "auto",
"description": "UI theme preference" "description": "UI theme preference"
}, },
@@ -216,12 +203,7 @@
}, },
"taskmaster.debug.logLevel": { "taskmaster.debug.logLevel": {
"type": "string", "type": "string",
"enum": [ "enum": ["error", "warn", "info", "debug"],
"error",
"warn",
"info",
"debug"
],
"default": "info", "default": "info",
"description": "Logging level" "description": "Logging level"
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -261,8 +261,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
onNavigateToTask onNavigateToTask
}) => { }) => {
const context = useContext(VSCodeContext); const context = useContext(VSCodeContext);
if (!context) if (!context) {
{throw new Error('TaskDetailsView must be used within VSCodeContext');} throw new Error('TaskDetailsView must be used within VSCodeContext');
}
const { state, sendMessage } = context; const { state, sendMessage } = context;
const { tasks } = state; const { tasks } = state;
@@ -372,7 +373,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
// Handle running complexity analysis for a task // Handle running complexity analysis for a task
const handleRunComplexityAnalysis = useCallback(async () => { const handleRunComplexityAnalysis = useCallback(async () => {
if (!currentTask) {return;} if (!currentTask) {
return;
}
setIsLoadingComplexity(true); setIsLoadingComplexity(true);
try { try {
@@ -416,7 +419,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
// Function to fetch task file data (implementation details and test strategy only) // Function to fetch task file data (implementation details and test strategy only)
const fetchTaskFileData = async () => { const fetchTaskFileData = async () => {
if (!currentTask?.id) {return;} if (!currentTask?.id) {
return;
}
setIsLoadingTaskFileData(true); setIsLoadingTaskFileData(true);
setTaskFileDataError(null); setTaskFileDataError(null);
@@ -543,7 +548,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
// Handle AI Actions // Handle AI Actions
const handleRegenerate = async () => { const handleRegenerate = async () => {
if (!currentTask || !prompt.trim()) {return;} if (!currentTask || !prompt.trim()) {
return;
}
setIsRegenerating(true); setIsRegenerating(true);
try { try {
@@ -584,7 +591,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
}; };
const handleAppend = async () => { const handleAppend = async () => {
if (!currentTask || !prompt.trim()) {return;} if (!currentTask || !prompt.trim()) {
return;
}
setIsAppending(true); setIsAppending(true);
try { try {
@@ -626,7 +635,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
// Handle adding a new subtask // Handle adding a new subtask
const handleAddSubtask = async () => { const handleAddSubtask = async () => {
if (!currentTask || !newSubtaskTitle.trim() || isSubtask) {return;} if (!currentTask || !newSubtaskTitle.trim() || isSubtask) {
return;
}
setIsSubmittingSubtask(true); setIsSubmittingSubtask(true);
try { try {
@@ -672,7 +683,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
// Handle status change // Handle status change
const handleStatusChange = async (newStatus: TaskMasterTask['status']) => { const handleStatusChange = async (newStatus: TaskMasterTask['status']) => {
if (!currentTask) {return;} if (!currentTask) {
return;
}
try { try {
await sendMessage({ await sendMessage({

View File

@@ -25,6 +25,7 @@ import {
import { getToastDuration } from './utils/notificationPreferences'; import { getToastDuration } from './utils/notificationPreferences';
import { parseTaskFileData } from './utils/taskFileReader'; import { parseTaskFileData } from './utils/taskFileReader';
import { TaskMasterTask } from './utils/taskMasterApi'; import { TaskMasterTask } from './utils/taskMasterApi';
import { logger } from './utils/logger';
// Global MCP client manager instance // Global MCP client manager instance
let mcpClient: MCPClientManager | null = null; let mcpClient: MCPClientManager | null = null;
@@ -82,8 +83,8 @@ let pollingState: PollingState = {
// Initialize MCP components // Initialize MCP components
async function initializeMCPComponents(context: vscode.ExtensionContext) { async function initializeMCPComponents(context: vscode.ExtensionContext) {
try { try {
console.log('🔄 Initializing MCP components...'); logger.log('🔄 Initializing MCP components...');
console.log( logger.log(
'🔍 DEBUGGING: initializeMCPComponents started at', '🔍 DEBUGGING: initializeMCPComponents started at',
new Date().toISOString() new Date().toISOString()
); );
@@ -95,7 +96,7 @@ async function initializeMCPComponents(context: vscode.ExtensionContext) {
const mcpConfig = createMCPConfigFromSettings(); const mcpConfig = createMCPConfigFromSettings();
// Initialize MCP client // Initialize MCP client
console.log( logger.log(
'🔍 DEBUGGING: About to create MCPClientManager with config:', '🔍 DEBUGGING: About to create MCPClientManager with config:',
mcpConfig mcpConfig
); );
@@ -110,14 +111,14 @@ async function initializeMCPComponents(context: vscode.ExtensionContext) {
}); });
// Try to connect to MCP server // Try to connect to MCP server
console.log('🔗 Connecting to Task Master MCP server...'); logger.log('🔗 Connecting to Task Master MCP server...');
try { try {
await mcpClient.connect(); await mcpClient.connect();
// Test connection // Test connection
const connectionTest = await taskMasterApi.testConnection(); const connectionTest = await taskMasterApi.testConnection();
if (connectionTest.success && connectionTest.data) { if (connectionTest.success && connectionTest.data) {
console.log('✅ Task Master MCP connection established'); logger.log('✅ Task Master MCP connection established');
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
'Task Master connected successfully!' 'Task Master connected successfully!'
); );
@@ -125,8 +126,8 @@ async function initializeMCPComponents(context: vscode.ExtensionContext) {
throw new Error(connectionTest.error || 'Connection test failed'); throw new Error(connectionTest.error || 'Connection test failed');
} }
} catch (connectionError) { } catch (connectionError) {
console.error('❌ Task Master MCP connection failed:', connectionError); logger.error('❌ Task Master MCP connection failed:', connectionError);
console.error('Connection error details:', { logger.error('Connection error details:', {
message: message:
connectionError instanceof Error connectionError instanceof Error
? connectionError.message ? connectionError.message
@@ -165,7 +166,7 @@ async function initializeMCPComponents(context: vscode.ExtensionContext) {
// Initialize in offline mode // Initialize in offline mode
pollingState.isOfflineMode = true; pollingState.isOfflineMode = true;
console.log( logger.log(
'📴 Starting in offline mode - some features will be unavailable' '📴 Starting in offline mode - some features will be unavailable'
); );
} }
@@ -181,7 +182,7 @@ async function startPolling(): Promise<void> {
return; return;
} }
console.log('🔄 Starting task polling with interval:', pollingState.interval); logger.log('🔄 Starting task polling with interval:', pollingState.interval);
pollingState.isPolling = true; pollingState.isPolling = true;
pollingState.errorCount = 0; pollingState.errorCount = 0;
@@ -197,7 +198,7 @@ function stopPolling(): void {
return; return;
} }
console.log('⏹️ Stopping task polling'); logger.log('⏹️ Stopping task polling');
pollingState.isPolling = false; pollingState.isPolling = false;
if (pollingState.timer) { if (pollingState.timer) {
@@ -212,7 +213,7 @@ async function pollForUpdates(): Promise<void> {
} }
try { try {
console.log('📡 Polling for task updates...'); logger.log('📡 Polling for task updates...');
const tasksResult = await taskMasterApi.getTasks({ const tasksResult = await taskMasterApi.getTasks({
withSubtasks: true, withSubtasks: true,
@@ -223,7 +224,7 @@ async function pollForUpdates(): Promise<void> {
const hasChanges = detectTaskChanges(tasksResult.data); const hasChanges = detectTaskChanges(tasksResult.data);
if (hasChanges) { if (hasChanges) {
console.log('📋 Task changes detected, notifying webviews'); logger.log('📋 Task changes detected, notifying webviews');
// Track change for adaptive frequency // Track change for adaptive frequency
pollingState.changeDetectionWindow.push(Date.now()); pollingState.changeDetectionWindow.push(Date.now());
@@ -242,7 +243,7 @@ async function pollForUpdates(): Promise<void> {
}); });
}); });
} else { } else {
console.log('📋 No task changes detected'); logger.log('📋 No task changes detected');
pollingState.consecutiveNoChanges++; pollingState.consecutiveNoChanges++;
} }
@@ -260,7 +261,7 @@ async function pollForUpdates(): Promise<void> {
if (pollingState.isOfflineMode) { if (pollingState.isOfflineMode) {
pollingState.isOfflineMode = false; pollingState.isOfflineMode = false;
notifyConnectionStatus('online', 'Connected'); notifyConnectionStatus('online', 'Connected');
console.log('✅ Reconnected successfully from offline mode'); logger.log('✅ Reconnected successfully from offline mode');
} }
} else { } else {
throw new Error(tasksResult.error || 'Failed to fetch tasks'); throw new Error(tasksResult.error || 'Failed to fetch tasks');
@@ -291,7 +292,7 @@ function detectTaskChanges(newTasks: any[]): boolean {
); );
return newTasksStr !== oldTasksStr; return newTasksStr !== oldTasksStr;
} catch (error) { } catch (error) {
console.warn('⚠️ Error comparing tasks, assuming changed:', error); logger.warn('⚠️ Error comparing tasks, assuming changed:', error);
return true; return true;
} }
} }
@@ -343,11 +344,11 @@ function adjustPollingFrequency(): void {
pollingState.minInterval, pollingState.minInterval,
pollingState.baseInterval * 0.5 pollingState.baseInterval * 0.5
); );
console.log('📈 High activity detected, increasing polling frequency'); logger.log('📈 High activity detected, increasing polling frequency');
} else if (changesPerMinute > 0.5) { } else if (changesPerMinute > 0.5) {
// Moderate activity: use base interval // Moderate activity: use base interval
newInterval = pollingState.baseInterval; newInterval = pollingState.baseInterval;
console.log('📊 Moderate activity, using base polling interval'); logger.log('📊 Moderate activity, using base polling interval');
} else if (pollingState.consecutiveNoChanges > 3) { } else if (pollingState.consecutiveNoChanges > 3) {
// Low activity: reduce polling frequency with exponential backoff // Low activity: reduce polling frequency with exponential backoff
const backoffMultiplier = Math.min( const backoffMultiplier = Math.min(
@@ -358,7 +359,7 @@ function adjustPollingFrequency(): void {
pollingState.maxInterval, pollingState.maxInterval,
pollingState.baseInterval * backoffMultiplier pollingState.baseInterval * backoffMultiplier
); );
console.log( logger.log(
`📉 Low activity detected (${pollingState.consecutiveNoChanges} no-change cycles), reducing polling frequency` `📉 Low activity detected (${pollingState.consecutiveNoChanges} no-change cycles), reducing polling frequency`
); );
} }
@@ -368,7 +369,7 @@ function adjustPollingFrequency(): void {
Math.abs(newInterval - pollingState.interval) > 500 && Math.abs(newInterval - pollingState.interval) > 500 &&
pollingState.isPolling pollingState.isPolling
) { ) {
console.log( logger.log(
`🔄 Adjusting polling interval from ${pollingState.interval}ms to ${newInterval}ms` `🔄 Adjusting polling interval from ${pollingState.interval}ms to ${newInterval}ms`
); );
pollingState.interval = newInterval; pollingState.interval = newInterval;
@@ -388,7 +389,7 @@ function handleNetworkError(error: any): void {
pollingState.errorCount++; pollingState.errorCount++;
pollingState.reconnectAttempts++; pollingState.reconnectAttempts++;
console.error( logger.error(
`❌ Network error (attempt ${pollingState.reconnectAttempts}/${pollingState.maxReconnectAttempts}):`, `❌ Network error (attempt ${pollingState.reconnectAttempts}/${pollingState.maxReconnectAttempts}):`,
error error
); );
@@ -410,7 +411,7 @@ function handleNetworkError(error: any): void {
const maxBackoffDelay = pollingState.maxInterval; const maxBackoffDelay = pollingState.maxInterval;
const finalDelay = Math.min(backoffDelay, maxBackoffDelay); const finalDelay = Math.min(backoffDelay, maxBackoffDelay);
console.log( logger.log(
`🔄 Retrying connection in ${finalDelay}ms (attempt ${pollingState.reconnectAttempts})` `🔄 Retrying connection in ${finalDelay}ms (attempt ${pollingState.reconnectAttempts})`
); );
@@ -432,7 +433,7 @@ function handleNetworkError(error: any): void {
} }
function enterOfflineMode(): void { function enterOfflineMode(): void {
console.warn('⚠️ Entering offline mode due to persistent connection failures'); logger.warn('⚠️ Entering offline mode due to persistent connection failures');
pollingState.isOfflineMode = true; pollingState.isOfflineMode = true;
stopPolling(); stopPolling();
@@ -462,7 +463,7 @@ function attemptReconnection(): void {
return; return;
} }
console.log('🔄 Attempting to reconnect from offline mode...'); logger.log('🔄 Attempting to reconnect from offline mode...');
// Reset connection state // Reset connection state
pollingState.isOfflineMode = false; pollingState.isOfflineMode = false;
@@ -474,7 +475,7 @@ function attemptReconnection(): void {
// Try to restart polling // Try to restart polling
startPolling().catch((error) => { startPolling().catch((error) => {
console.error('Failed to reconnect:', error); logger.error('Failed to reconnect:', error);
enterOfflineMode(); enterOfflineMode();
}); });
} }
@@ -509,7 +510,7 @@ async function handleExtensionError(
...context ...context
}); });
console.error(`Extension Error [${operation}]:`, error); logger.error(`Extension Error [${operation}]:`, error);
await errorHandler.handleError( await errorHandler.handleError(
error instanceof Error ? error : new Error(String(error)), error instanceof Error ? error : new Error(String(error)),
context context
@@ -527,7 +528,7 @@ async function handleMCPError(
context context
); );
console.error(`MCP Error [${operation}]:`, error); logger.error(`MCP Error [${operation}]:`, error);
await errorHandler.handleError(mcpError, context); await errorHandler.handleError(mcpError, context);
} }
@@ -542,7 +543,7 @@ async function handleTaskLoadingError(
context context
); );
console.error(`Task Loading Error [${operation}]:`, error); logger.error(`Task Loading Error [${operation}]:`, error);
await errorHandler.handleError(taskError, context); await errorHandler.handleError(taskError, context);
} }
@@ -557,7 +558,7 @@ async function handleNetworkConnectionError(
context context
); );
console.error(`Network Error [${operation}]:`, error); logger.error(`Network Error [${operation}]:`, error);
await errorHandler.handleError(networkError, context); await errorHandler.handleError(networkError, context);
} }
@@ -572,16 +573,16 @@ async function handleUIError(
context context
); );
console.error(`UI Error [${operation}]:`, error); logger.error(`UI Error [${operation}]:`, error);
await errorHandler.handleError(uiError, context); await errorHandler.handleError(uiError, context);
} }
// This method is called when your extension is activated // This method is called when your extension is activated
// Your extension is activated the very first time the command is executed // Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
console.log('🎉 Task Master Kanban extension is now active!'); logger.log('🎉 Task Master Kanban extension is now active!');
console.log('🎉 Extension context:', context); logger.log('🎉 Extension context:', context);
console.log( logger.log(
'🔍 DEBUGGING: Extension activation started at', '🔍 DEBUGGING: Extension activation started at',
new Date().toISOString() new Date().toISOString()
); );
@@ -614,7 +615,7 @@ export function activate(context: vscode.ExtensionContext) {
const showKanbanCommand = vscode.commands.registerCommand( const showKanbanCommand = vscode.commands.registerCommand(
'taskr.showKanbanBoard', 'taskr.showKanbanBoard',
async () => { async () => {
console.log('🎯 Show Kanban command executed!'); logger.log('🎯 Show Kanban command executed!');
// Check if panel already exists // Check if panel already exists
const existingPanel = activeWebviewPanels.find( const existingPanel = activeWebviewPanels.find(
@@ -668,11 +669,11 @@ export function activate(context: vscode.ExtensionContext) {
// Handle messages from webview // Handle messages from webview
panel.webview.onDidReceiveMessage(async (message) => { panel.webview.onDidReceiveMessage(async (message) => {
console.log('📨 Received message from webview:', message); logger.log('📨 Received message from webview:', message);
switch (message.type) { switch (message.type) {
case 'ready': case 'ready':
console.log('🚀 Webview is ready!'); logger.log('🚀 Webview is ready!');
// Send initial configuration or data // Send initial configuration or data
panel.webview.postMessage({ panel.webview.postMessage({
type: 'init', type: 'init',
@@ -681,7 +682,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'getTasks': case 'getTasks':
console.log('📋 Getting tasks...'); logger.log('📋 Getting tasks...');
try { try {
if (!taskMasterApi) { if (!taskMasterApi) {
throw new Error( throw new Error(
@@ -707,14 +708,14 @@ export function activate(context: vscode.ExtensionContext) {
requestId: message.requestId, requestId: message.requestId,
data: tasksResult.data data: tasksResult.data
}); });
console.log( logger.log(
`✅ Retrieved ${tasksResult.data?.length || 0} tasks from Task Master` `✅ Retrieved ${tasksResult.data?.length || 0} tasks from Task Master`
); );
} else { } else {
throw new Error(tasksResult.error || 'Failed to get tasks'); throw new Error(tasksResult.error || 'Failed to get tasks');
} }
} catch (error) { } catch (error) {
console.error('❌ Error getting tasks:', error); logger.error('❌ Error getting tasks:', error);
// Send error to webview instead of falling back to sample data // Send error to webview instead of falling back to sample data
panel.webview.postMessage({ panel.webview.postMessage({
@@ -735,7 +736,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'updateTaskStatus': case 'updateTaskStatus':
console.log('🔄 Updating task status:', message.data); logger.log('🔄 Updating task status:', message.data);
try { try {
if ( if (
taskMasterApi && taskMasterApi &&
@@ -761,7 +762,7 @@ export function activate(context: vscode.ExtensionContext) {
newStatus: message.data.newStatus newStatus: message.data.newStatus
} }
}); });
console.log( logger.log(
`✅ Updated task ${message.data.taskId} status to ${message.data.newStatus}` `✅ Updated task ${message.data.taskId} status to ${message.data.newStatus}`
); );
} else { } else {
@@ -775,7 +776,7 @@ export function activate(context: vscode.ExtensionContext) {
); );
} }
} catch (error) { } catch (error) {
console.error('❌ Error updating task status:', error); logger.error('❌ Error updating task status:', error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId: message.requestId, requestId: message.requestId,
@@ -788,7 +789,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'updateTask': case 'updateTask':
console.log('📝 Updating task content:', message.data); logger.log('📝 Updating task content:', message.data);
try { try {
if ( if (
taskMasterApi && taskMasterApi &&
@@ -816,7 +817,7 @@ export function activate(context: vscode.ExtensionContext) {
updates: message.data.updates updates: message.data.updates
} }
}); });
console.log(`✅ Updated task ${message.data.taskId} content`); logger.log(`✅ Updated task ${message.data.taskId} content`);
} else { } else {
throw new Error( throw new Error(
updateResult.error || 'Failed to update task' updateResult.error || 'Failed to update task'
@@ -828,7 +829,7 @@ export function activate(context: vscode.ExtensionContext) {
); );
} }
} catch (error) { } catch (error) {
console.error('❌ Error updating task:', error); logger.error('❌ Error updating task:', error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId: message.requestId, requestId: message.requestId,
@@ -841,7 +842,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'updateSubtask': case 'updateSubtask':
console.log('📝 Updating subtask content:', message.data); logger.log('📝 Updating subtask content:', message.data);
try { try {
if ( if (
taskMasterApi && taskMasterApi &&
@@ -868,7 +869,7 @@ export function activate(context: vscode.ExtensionContext) {
prompt: message.data.prompt prompt: message.data.prompt
} }
}); });
console.log( logger.log(
`✅ Updated subtask ${message.data.taskId} content` `✅ Updated subtask ${message.data.taskId} content`
); );
} else { } else {
@@ -882,7 +883,7 @@ export function activate(context: vscode.ExtensionContext) {
); );
} }
} catch (error) { } catch (error) {
console.error('❌ Error updating subtask:', error); logger.error('❌ Error updating subtask:', error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId: message.requestId, requestId: message.requestId,
@@ -895,7 +896,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'addSubtask': case 'addSubtask':
console.log(' Adding new subtask:', message.data); logger.log(' Adding new subtask:', message.data);
try { try {
if ( if (
taskMasterApi && taskMasterApi &&
@@ -921,7 +922,7 @@ export function activate(context: vscode.ExtensionContext) {
subtaskData: message.data.subtaskData subtaskData: message.data.subtaskData
} }
}); });
console.log( logger.log(
`✅ Added subtask to task ${message.data.parentTaskId}` `✅ Added subtask to task ${message.data.parentTaskId}`
); );
} else { } else {
@@ -933,7 +934,7 @@ export function activate(context: vscode.ExtensionContext) {
); );
} }
} catch (error) { } catch (error) {
console.error('❌ Error adding subtask:', error); logger.error('❌ Error adding subtask:', error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId: message.requestId, requestId: message.requestId,
@@ -946,7 +947,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'startPolling': case 'startPolling':
console.log('🔄 Manual start polling requested'); logger.log('🔄 Manual start polling requested');
await startPolling(); await startPolling();
panel.webview.postMessage({ panel.webview.postMessage({
type: 'pollingStarted', type: 'pollingStarted',
@@ -956,7 +957,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'stopPolling': case 'stopPolling':
console.log('⏹️ Manual stop polling requested'); logger.log('⏹️ Manual stop polling requested');
stopPolling(); stopPolling();
panel.webview.postMessage({ panel.webview.postMessage({
type: 'pollingStopped', type: 'pollingStopped',
@@ -966,7 +967,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'getPollingStatus': case 'getPollingStatus':
console.log('📊 Polling status requested'); logger.log('📊 Polling status requested');
panel.webview.postMessage({ panel.webview.postMessage({
type: 'pollingStatus', type: 'pollingStatus',
requestId: message.requestId, requestId: message.requestId,
@@ -980,7 +981,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'attemptReconnection': case 'attemptReconnection':
console.log('🔄 Manual reconnection requested'); logger.log('🔄 Manual reconnection requested');
if (pollingState.isOfflineMode) { if (pollingState.isOfflineMode) {
attemptReconnection(); attemptReconnection();
panel.webview.postMessage({ panel.webview.postMessage({
@@ -999,7 +1000,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'getNetworkStatus': case 'getNetworkStatus':
console.log('📊 Network status requested'); logger.log('📊 Network status requested');
panel.webview.postMessage({ panel.webview.postMessage({
type: 'networkStatus', type: 'networkStatus',
requestId: message.requestId, requestId: message.requestId,
@@ -1014,7 +1015,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'reactError': case 'reactError':
console.log('🔥 React error reported from webview:', message.data); logger.log('🔥 React error reported from webview:', message.data);
try { try {
await handleUIError( await handleUIError(
new Error(message.data.message), new Error(message.data.message),
@@ -1026,12 +1027,12 @@ export function activate(context: vscode.ExtensionContext) {
} }
); );
} catch (error) { } catch (error) {
console.error('Failed to handle React error:', error); logger.error('Failed to handle React error:', error);
} }
break; break;
case 'readTaskFileData': case 'readTaskFileData':
console.log('📄 Reading task file data:', message.data); logger.log('📄 Reading task file data:', message.data);
{ {
const { requestId } = message; const { requestId } = message;
try { try {
@@ -1050,7 +1051,7 @@ export function activate(context: vscode.ExtensionContext) {
'tasks', 'tasks',
'tasks.json' 'tasks.json'
); );
console.log('🔍 Looking for tasks.json at:', tasksJsonPath); logger.log('🔍 Looking for tasks.json at:', tasksJsonPath);
// Check if file exists // Check if file exists
if (!fs.existsSync(tasksJsonPath)) { if (!fs.existsSync(tasksJsonPath)) {
@@ -1060,7 +1061,7 @@ export function activate(context: vscode.ExtensionContext) {
'tasks', 'tasks',
'tasks.json' 'tasks.json'
); );
console.log('🔍 Trying legacy path:', legacyPath); logger.log('🔍 Trying legacy path:', legacyPath);
if (!fs.existsSync(legacyPath)) { if (!fs.existsSync(legacyPath)) {
throw new Error( throw new Error(
'tasks.json not found in .taskmaster/tasks/ or tasks/ directory' 'tasks.json not found in .taskmaster/tasks/ or tasks/ directory'
@@ -1068,7 +1069,7 @@ export function activate(context: vscode.ExtensionContext) {
} }
// Use legacy path // Use legacy path
const content = fs.readFileSync(legacyPath, 'utf8'); const content = fs.readFileSync(legacyPath, 'utf8');
console.log( logger.log(
'📖 Read legacy tasks.json, content length:', '📖 Read legacy tasks.json, content length:',
content.length content.length
); );
@@ -1078,7 +1079,7 @@ export function activate(context: vscode.ExtensionContext) {
tagName, tagName,
workspaceFolder.uri.fsPath workspaceFolder.uri.fsPath
); );
console.log('✅ Parsed task data for legacy path:', taskData); logger.log('✅ Parsed task data for legacy path:', taskData);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'response', type: 'response',
requestId, requestId,
@@ -1089,7 +1090,7 @@ export function activate(context: vscode.ExtensionContext) {
// Read and parse tasks.json // Read and parse tasks.json
const content = fs.readFileSync(tasksJsonPath, 'utf8'); const content = fs.readFileSync(tasksJsonPath, 'utf8');
console.log( logger.log(
'📖 Read tasks.json, content length:', '📖 Read tasks.json, content length:',
content.length content.length
); );
@@ -1099,7 +1100,7 @@ export function activate(context: vscode.ExtensionContext) {
tagName, tagName,
workspaceFolder.uri.fsPath workspaceFolder.uri.fsPath
); );
console.log('✅ Parsed task data:', taskData); logger.log('✅ Parsed task data:', taskData);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'response', type: 'response',
@@ -1107,9 +1108,9 @@ export function activate(context: vscode.ExtensionContext) {
data: taskData data: taskData
}); });
console.log(`✅ Retrieved task file data for task ${taskId}`); logger.log(`✅ Retrieved task file data for task ${taskId}`);
} catch (error) { } catch (error) {
console.error('❌ Error reading task file data:', error); logger.error('❌ Error reading task file data:', error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId, requestId,
@@ -1123,7 +1124,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'mcpRequest': case 'mcpRequest':
console.log('📊 MCP Request:', message); logger.log('📊 MCP Request:', message);
const { requestId: mcpRequestId, tool, parameters } = message; const { requestId: mcpRequestId, tool, parameters } = message;
try { try {
if (!taskMasterApi) { if (!taskMasterApi) {
@@ -1140,7 +1141,7 @@ export function activate(context: vscode.ExtensionContext) {
switch (tool) { switch (tool) {
case 'complexity_report': case 'complexity_report':
console.log('📊 Calling complexity_report MCP tool'); logger.log('📊 Calling complexity_report MCP tool');
try { try {
// Use the private callMCPTool method via type assertion to access it // Use the private callMCPTool method via type assertion to access it
const mcpResult = await (taskMasterApi as any).callMCPTool( const mcpResult = await (taskMasterApi as any).callMCPTool(
@@ -1164,7 +1165,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
case 'analyze_project_complexity': case 'analyze_project_complexity':
console.log('🧮 Calling analyze_project_complexity MCP tool'); logger.log('🧮 Calling analyze_project_complexity MCP tool');
try { try {
// Use the private callMCPTool method via type assertion to access it // Use the private callMCPTool method via type assertion to access it
const mcpResult = await (taskMasterApi as any).callMCPTool( const mcpResult = await (taskMasterApi as any).callMCPTool(
@@ -1197,14 +1198,14 @@ export function activate(context: vscode.ExtensionContext) {
requestId: mcpRequestId, requestId: mcpRequestId,
data: result.data data: result.data
}); });
console.log(`✅ MCP tool ${tool} executed successfully`); logger.log(`✅ MCP tool ${tool} executed successfully`);
} else { } else {
throw new Error( throw new Error(
result.error || `Failed to execute MCP tool: ${tool}` result.error || `Failed to execute MCP tool: ${tool}`
); );
} }
} catch (error) { } catch (error) {
console.error(`❌ Error executing MCP tool ${tool}:`, error); logger.error(`❌ Error executing MCP tool ${tool}:`, error);
panel.webview.postMessage({ panel.webview.postMessage({
type: 'error', type: 'error',
requestId: mcpRequestId, requestId: mcpRequestId,
@@ -1217,7 +1218,7 @@ export function activate(context: vscode.ExtensionContext) {
break; break;
default: default:
console.log('❓ Unknown message type:', message.type); logger.log('❓ Unknown message type:', message.type);
} }
}); });
@@ -1228,7 +1229,7 @@ export function activate(context: vscode.ExtensionContext) {
const checkConnectionCommand = vscode.commands.registerCommand( const checkConnectionCommand = vscode.commands.registerCommand(
'taskr.checkConnection', 'taskr.checkConnection',
async () => { async () => {
console.log('🔗 Check connection command executed!'); logger.log('🔗 Check connection command executed!');
vscode.window.showInformationMessage('Check connection command works!'); vscode.window.showInformationMessage('Check connection command works!');
} }
); );
@@ -1236,7 +1237,7 @@ export function activate(context: vscode.ExtensionContext) {
const reconnectCommand = vscode.commands.registerCommand( const reconnectCommand = vscode.commands.registerCommand(
'taskr.reconnect', 'taskr.reconnect',
async () => { async () => {
console.log('🔄 Reconnect command executed!'); logger.log('🔄 Reconnect command executed!');
vscode.window.showInformationMessage('Reconnect command works!'); vscode.window.showInformationMessage('Reconnect command works!');
} }
); );
@@ -1244,7 +1245,7 @@ export function activate(context: vscode.ExtensionContext) {
const openSettingsCommand = vscode.commands.registerCommand( const openSettingsCommand = vscode.commands.registerCommand(
'taskr.openSettings', 'taskr.openSettings',
() => { () => {
console.log('⚙️ Open settings command executed!'); logger.log('⚙️ Open settings command executed!');
vscode.commands.executeCommand( vscode.commands.executeCommand(
'workbench.action.openSettings', 'workbench.action.openSettings',
'@ext:taskr taskmaster' '@ext:taskr taskmaster'
@@ -1259,7 +1260,7 @@ export function activate(context: vscode.ExtensionContext) {
openSettingsCommand openSettingsCommand
); );
console.log('✅ All commands registered successfully!'); logger.log('✅ All commands registered successfully!');
} }
// Generate webview HTML content // Generate webview HTML content
@@ -1393,7 +1394,7 @@ function getSampleTasks() {
// This method is called when your extension is deactivated // This method is called when your extension is deactivated
export function deactivate() { export function deactivate() {
console.log('👋 Task Master Kanban extension deactivated'); logger.log('👋 Task Master Kanban extension deactivated');
// Stop polling // Stop polling
stopPolling(); stopPolling();

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MCPConfig } from './mcpClient'; import { MCPConfig } from './mcpClient';
import { logger } from './logger';
export interface TaskMasterConfig { export interface TaskMasterConfig {
mcp: MCPServerConfig; mcp: MCPServerConfig;
@@ -469,7 +470,7 @@ export class ConfigManager {
private setupConfigWatcher(): void { private setupConfigWatcher(): void {
vscode.workspace.onDidChangeConfiguration((event) => { vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('taskmaster')) { if (event.affectsConfiguration('taskmaster')) {
console.log('Task Master configuration changed, reloading...'); logger.log('Task Master configuration changed, reloading...');
this.config = this.loadConfig(); this.config = this.loadConfig();
this.notifyConfigChange(); this.notifyConfigChange();
} }
@@ -499,7 +500,7 @@ export class ConfigManager {
try { try {
listener(this.config); listener(this.config);
} catch (error) { } catch (error) {
console.error('Error in configuration change listener:', error); logger.error('Error in configuration change listener:', error);
} }
}); });
} }

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MCPClientManager, MCPConfig, MCPServerStatus } from './mcpClient'; import { MCPClientManager, MCPConfig, MCPServerStatus } from './mcpClient';
import { logger } from './logger';
export interface ConnectionEvent { export interface ConnectionEvent {
type: 'connected' | 'disconnected' | 'error' | 'reconnecting'; type: 'connected' | 'disconnected' | 'error' | 'reconnecting';
@@ -82,7 +83,7 @@ export class ConnectionManager {
this.logEvent({ type: 'connected', timestamp: new Date() }); this.logEvent({ type: 'connected', timestamp: new Date() });
console.log('Connection manager: Successfully connected'); logger.log('Connection manager: Successfully connected');
} catch (error) { } catch (error) {
this.logEvent({ this.logEvent({
type: 'error', type: 'error',
@@ -216,11 +217,11 @@ export class ConnectionManager {
// Attempt reconnection if connection seems lost // Attempt reconnection if connection seems lost
if (this.health.consecutiveFailures >= 3 && !this.isReconnecting) { if (this.health.consecutiveFailures >= 3 && !this.isReconnecting) {
console.log( logger.log(
'Multiple consecutive failures detected, attempting reconnection...' 'Multiple consecutive failures detected, attempting reconnection...'
); );
this.reconnectWithBackoff().catch((err) => { this.reconnectWithBackoff().catch((err) => {
console.error('Reconnection failed:', err); logger.error('Reconnection failed:', err);
}); });
} }
@@ -242,7 +243,7 @@ export class ConnectionManager {
try { try {
await this.connect(); await this.connect();
} catch (error) { } catch (error) {
console.error('Failed to connect with new configuration:', error); logger.error('Failed to connect with new configuration:', error);
} }
} }
@@ -256,7 +257,7 @@ export class ConnectionManager {
try { try {
await this.testConnection(); await this.testConnection();
} catch (error) { } catch (error) {
console.error('Health check failed:', error); logger.error('Health check failed:', error);
} }
}, 15000); // Check every 15 seconds }, 15000); // Check every 15 seconds
} }
@@ -303,7 +304,7 @@ export class ConnectionManager {
this.maxBackoffMs this.maxBackoffMs
); );
console.log( logger.log(
`Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${backoffMs}ms...` `Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${backoffMs}ms...`
); );
@@ -312,7 +313,7 @@ export class ConnectionManager {
try { try {
await this.connect(); await this.connect();
} catch (error) { } catch (error) {
console.error( logger.error(
`Reconnection attempt ${this.reconnectAttempts} failed:`, `Reconnection attempt ${this.reconnectAttempts} failed:`,
error error
); );

View File

@@ -1,4 +1,5 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { logger } from './logger';
import { import {
shouldShowNotification, shouldShowNotification,
getNotificationType, getNotificationType,
@@ -322,7 +323,7 @@ export class ErrorHandler {
await errorDetails.recovery.action(); await errorDetails.recovery.action();
this.markErrorResolved(logEntry.id); this.markErrorResolved(logEntry.id);
} catch (recoveryError) { } catch (recoveryError) {
console.error('Error recovery failed:', recoveryError); logger.error('Error recovery failed:', recoveryError);
logEntry.attempts++; logEntry.attempts++;
logEntry.lastAttempt = new Date(); logEntry.lastAttempt = new Date();
} }
@@ -548,10 +549,10 @@ export class ErrorHandler {
switch (errorDetails.severity) { switch (errorDetails.severity) {
case ErrorSeverity.CRITICAL: case ErrorSeverity.CRITICAL:
case ErrorSeverity.HIGH: case ErrorSeverity.HIGH:
console.error(logMessage, errorDetails); logger.error(logMessage, errorDetails);
break; break;
case ErrorSeverity.MEDIUM: case ErrorSeverity.MEDIUM:
console.warn(logMessage, errorDetails); logger.warn(logMessage, errorDetails);
break; break;
case ErrorSeverity.LOW: case ErrorSeverity.LOW:
console.info(logMessage, errorDetails); console.info(logMessage, errorDetails);
@@ -634,7 +635,7 @@ ${errorDetails.context ? JSON.stringify(errorDetails.context, null, 2) : 'None'}
try { try {
listener(errorDetails); listener(errorDetails);
} catch (error) { } catch (error) {
console.error('Error in error listener:', error); logger.error('Error in error listener:', error);
} }
}); });
} }

View File

@@ -0,0 +1,86 @@
import * as vscode from 'vscode';
/**
* Logger that outputs to VS Code's output channel instead of console
* This prevents interference with MCP stdio communication
*/
export class ExtensionLogger {
private static instance: ExtensionLogger;
private outputChannel: vscode.OutputChannel;
private debugMode: boolean;
private constructor() {
this.outputChannel = vscode.window.createOutputChannel('Task Master');
const config = vscode.workspace.getConfiguration('taskmaster');
this.debugMode = config.get<boolean>('debug.enableLogging', true);
}
static getInstance(): ExtensionLogger {
if (!ExtensionLogger.instance) {
ExtensionLogger.instance = new ExtensionLogger();
}
return ExtensionLogger.instance;
}
log(message: string, ...args: any[]): void {
if (!this.debugMode) return;
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] ${formattedMessage}`);
}
error(message: string, ...args: any[]): void {
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] ERROR: ${formattedMessage}`);
}
warn(message: string, ...args: any[]): void {
if (!this.debugMode) return;
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] WARN: ${formattedMessage}`);
}
debug(message: string, ...args: any[]): void {
if (!this.debugMode) return;
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] DEBUG: ${formattedMessage}`);
}
private formatMessage(message: string, args: any[]): string {
if (args.length === 0) {
return message;
}
// Convert objects to JSON for better readability
const formattedArgs = args.map((arg) => {
if (typeof arg === 'object' && arg !== null) {
try {
return JSON.stringify(arg, null, 2);
} catch {
return String(arg);
}
}
return String(arg);
});
return `${message} ${formattedArgs.join(' ')}`;
}
show(): void {
this.outputChannel.show();
}
dispose(): void {
this.outputChannel.dispose();
}
setDebugMode(enabled: boolean): void {
this.debugMode = enabled;
}
}
// Export a singleton instance for convenience
export const logger = ExtensionLogger.getInstance();

View File

@@ -1,6 +1,7 @@
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { logger } from './logger';
export interface MCPConfig { export interface MCPConfig {
command: string; command: string;
@@ -23,7 +24,7 @@ export class MCPClientManager {
private connectionPromise: Promise<void> | null = null; private connectionPromise: Promise<void> | null = null;
constructor(config: MCPConfig) { constructor(config: MCPConfig) {
console.log( logger.log(
'🔍 DEBUGGING: MCPClientManager constructor called with config:', '🔍 DEBUGGING: MCPClientManager constructor called with config:',
config config
); );
@@ -55,11 +56,11 @@ export class MCPClientManager {
await this.disconnect(); await this.disconnect();
// Create the transport - it will handle spawning the server process internally // Create the transport - it will handle spawning the server process internally
console.log( logger.log(
`Starting MCP server: ${this.config.command} ${this.config.args?.join(' ') || ''}` `Starting MCP server: ${this.config.command} ${this.config.args?.join(' ') || ''}`
); );
console.log('🔍 DEBUGGING: Transport config cwd:', this.config.cwd); logger.log('🔍 DEBUGGING: Transport config cwd:', this.config.cwd);
console.log('🔍 DEBUGGING: Process cwd before spawn:', process.cwd()); logger.log('🔍 DEBUGGING: Process cwd before spawn:', process.cwd());
// Test if the target directory and .taskmaster exist // Test if the target directory and .taskmaster exist
const fs = require('fs'); const fs = require('fs');
@@ -69,19 +70,19 @@ export class MCPClientManager {
const taskmasterDir = path.join(targetDir, '.taskmaster'); const taskmasterDir = path.join(targetDir, '.taskmaster');
const tasksFile = path.join(taskmasterDir, 'tasks', 'tasks.json'); const tasksFile = path.join(taskmasterDir, 'tasks', 'tasks.json');
console.log( logger.log(
'🔍 DEBUGGING: Checking target directory:', '🔍 DEBUGGING: Checking target directory:',
targetDir, targetDir,
'exists:', 'exists:',
fs.existsSync(targetDir) fs.existsSync(targetDir)
); );
console.log( logger.log(
'🔍 DEBUGGING: Checking .taskmaster dir:', '🔍 DEBUGGING: Checking .taskmaster dir:',
taskmasterDir, taskmasterDir,
'exists:', 'exists:',
fs.existsSync(taskmasterDir) fs.existsSync(taskmasterDir)
); );
console.log( logger.log(
'🔍 DEBUGGING: Checking tasks.json:', '🔍 DEBUGGING: Checking tasks.json:',
tasksFile, tasksFile,
'exists:', 'exists:',
@@ -90,10 +91,10 @@ export class MCPClientManager {
if (fs.existsSync(tasksFile)) { if (fs.existsSync(tasksFile)) {
const stats = fs.statSync(tasksFile); const stats = fs.statSync(tasksFile);
console.log('🔍 DEBUGGING: tasks.json size:', stats.size, 'bytes'); logger.log('🔍 DEBUGGING: tasks.json size:', stats.size, 'bytes');
} }
} catch (error) { } catch (error) {
console.log('🔍 DEBUGGING: Error checking filesystem:', error); logger.log('🔍 DEBUGGING: Error checking filesystem:', error);
} }
this.transport = new StdioClientTransport({ this.transport = new StdioClientTransport({
@@ -108,12 +109,12 @@ export class MCPClientManager {
} }
}); });
console.log('🔍 DEBUGGING: Transport created, checking process...'); logger.log('🔍 DEBUGGING: Transport created, checking process...');
// Set up transport event handlers // Set up transport event handlers
this.transport.onerror = (error: Error) => { this.transport.onerror = (error: Error) => {
console.error('❌ MCP transport error:', error); logger.error('❌ MCP transport error:', error);
console.error('Transport error details:', { logger.error('Transport error details:', {
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
code: (error as any).code, code: (error as any).code,
@@ -127,7 +128,7 @@ export class MCPClientManager {
}; };
this.transport.onclose = () => { this.transport.onclose = () => {
console.log('🔌 MCP transport closed'); logger.log('🔌 MCP transport closed');
this.status = { isRunning: false }; this.status = { isRunning: false };
this.client = null; this.client = null;
this.transport = null; this.transport = null;
@@ -135,7 +136,7 @@ export class MCPClientManager {
// Add message handler like the working debug script // Add message handler like the working debug script
this.transport.onmessage = (message: any) => { this.transport.onmessage = (message: any) => {
console.log('📤 MCP server message:', message); logger.log('📤 MCP server message:', message);
}; };
// Create the client // Create the client
@@ -152,14 +153,14 @@ export class MCPClientManager {
); );
// Connect the client to the transport (this automatically starts the transport) // Connect the client to the transport (this automatically starts the transport)
console.log('🔄 Attempting MCP client connection...'); logger.log('🔄 Attempting MCP client connection...');
console.log('MCP config:', { logger.log('MCP config:', {
command: this.config.command, command: this.config.command,
args: this.config.args, args: this.config.args,
cwd: this.config.cwd cwd: this.config.cwd
}); });
console.log('Current working directory:', process.cwd()); logger.log('Current working directory:', process.cwd());
console.log( logger.log(
'VS Code workspace folders:', 'VS Code workspace folders:',
vscode.workspace.workspaceFolders?.map((f) => f.uri.fsPath) vscode.workspace.workspaceFolders?.map((f) => f.uri.fsPath)
); );
@@ -167,37 +168,37 @@ export class MCPClientManager {
// Check if process was created before connecting // Check if process was created before connecting
if (this.transport && (this.transport as any).process) { if (this.transport && (this.transport as any).process) {
const proc = (this.transport as any).process; const proc = (this.transport as any).process;
console.log('📝 MCP server process PID:', proc.pid); logger.log('📝 MCP server process PID:', proc.pid);
console.log('📝 Process working directory will be:', this.config.cwd); logger.log('📝 Process working directory will be:', this.config.cwd);
proc.on('exit', (code: number, signal: string) => { proc.on('exit', (code: number, signal: string) => {
console.log( logger.log(
`🔚 MCP server process exited with code ${code}, signal ${signal}` `🔚 MCP server process exited with code ${code}, signal ${signal}`
); );
if (code !== 0) { if (code !== 0) {
console.log('❌ Non-zero exit code indicates server failure'); logger.log('❌ Non-zero exit code indicates server failure');
} }
}); });
proc.on('error', (error: Error) => { proc.on('error', (error: Error) => {
console.log('❌ MCP server process error:', error); logger.log('❌ MCP server process error:', error);
}); });
// Listen to stderr to see server-side errors // Listen to stderr to see server-side errors
if (proc.stderr) { if (proc.stderr) {
proc.stderr.on('data', (data: Buffer) => { proc.stderr.on('data', (data: Buffer) => {
console.log('📥 MCP server stderr:', data.toString()); logger.log('📥 MCP server stderr:', data.toString());
}); });
} }
// Listen to stdout for server messages // Listen to stdout for server messages
if (proc.stdout) { if (proc.stdout) {
proc.stdout.on('data', (data: Buffer) => { proc.stdout.on('data', (data: Buffer) => {
console.log('📤 MCP server stdout:', data.toString()); logger.log('📤 MCP server stdout:', data.toString());
}); });
} }
} else { } else {
console.log('⚠️ No process found in transport before connection'); logger.log('⚠️ No process found in transport before connection');
} }
await this.client.connect(this.transport); await this.client.connect(this.transport);
@@ -208,12 +209,12 @@ export class MCPClientManager {
pid: this.transport.pid || undefined pid: this.transport.pid || undefined
}; };
console.log('MCP client connected successfully'); logger.log('MCP client connected successfully');
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
'Task Master connected successfully' 'Task Master connected successfully'
); );
} catch (error) { } catch (error) {
console.error('Failed to connect to MCP server:', error); logger.error('Failed to connect to MCP server:', error);
this.status = { this.status = {
isRunning: false, isRunning: false,
error: error instanceof Error ? error.message : 'Unknown error' error: error instanceof Error ? error.message : 'Unknown error'
@@ -232,13 +233,13 @@ export class MCPClientManager {
* Disconnect from the MCP server and clean up resources * Disconnect from the MCP server and clean up resources
*/ */
async disconnect(): Promise<void> { async disconnect(): Promise<void> {
console.log('Disconnecting from MCP server'); logger.log('Disconnecting from MCP server');
if (this.client) { if (this.client) {
try { try {
await this.client.close(); await this.client.close();
} catch (error) { } catch (error) {
console.error('Error closing MCP client:', error); logger.error('Error closing MCP client:', error);
} }
this.client = null; this.client = null;
} }
@@ -247,7 +248,7 @@ export class MCPClientManager {
try { try {
await this.transport.close(); await this.transport.close();
} catch (error) { } catch (error) {
console.error('Error closing MCP transport:', error); logger.error('Error closing MCP transport:', error);
} }
this.transport = null; this.transport = null;
} }
@@ -281,7 +282,7 @@ export class MCPClientManager {
return result; return result;
} catch (error) { } catch (error) {
console.error(`Error calling MCP tool "${toolName}":`, error); logger.error(`Error calling MCP tool "${toolName}":`, error);
throw error; throw error;
} }
} }
@@ -297,13 +298,13 @@ export class MCPClientManager {
} }
const result = await this.client.listTools(); const result = await this.client.listTools();
console.log( logger.log(
'Available MCP tools:', 'Available MCP tools:',
result.tools?.map((t) => t.name) || [] result.tools?.map((t) => t.name) || []
); );
return true; return true;
} catch (error) { } catch (error) {
console.error('Connection test failed:', error); logger.error('Connection test failed:', error);
return false; return false;
} }
} }
@@ -328,7 +329,7 @@ export class MCPClientManager {
* Create MCP configuration from VS Code settings * Create MCP configuration from VS Code settings
*/ */
export function createMCPConfigFromSettings(): MCPConfig { export function createMCPConfigFromSettings(): MCPConfig {
console.log( logger.log(
'🔍 DEBUGGING: createMCPConfigFromSettings called at', '🔍 DEBUGGING: createMCPConfigFromSettings called at',
new Date().toISOString() new Date().toISOString()
); );
@@ -347,7 +348,7 @@ export function createMCPConfigFromSettings(): MCPConfig {
const cwd = config.get<string>('mcp.cwd', defaultCwd); const cwd = config.get<string>('mcp.cwd', defaultCwd);
const env = config.get<Record<string, string>>('mcp.env'); const env = config.get<Record<string, string>>('mcp.env');
console.log('✅ Using workspace directory:', defaultCwd); logger.log('✅ Using workspace directory:', defaultCwd);
// If using default 'npx', try to find the full path on macOS/Linux // If using default 'npx', try to find the full path on macOS/Linux
if (command === 'npx') { if (command === 'npx') {
@@ -363,7 +364,7 @@ export function createMCPConfigFromSettings(): MCPConfig {
try { try {
if (path === 'npx' || fs.existsSync(path)) { if (path === 'npx' || fs.existsSync(path)) {
command = path; command = path;
console.log(`✅ Using npx at: ${path}`); logger.log(`✅ Using npx at: ${path}`);
break; break;
} }
} catch (error) { } catch (error) {

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { ErrorCategory, ErrorSeverity, NotificationType } from './errorHandler'; import { ErrorCategory, ErrorSeverity, NotificationType } from './errorHandler';
import { logger } from './logger';
export interface NotificationPreferences { export interface NotificationPreferences {
// Global notification toggles // Global notification toggles
@@ -240,9 +241,7 @@ export class NotificationPreferencesManager {
vscode.ConfigurationTarget.Global vscode.ConfigurationTarget.Global
); );
console.log( logger.log('Task Master Kanban notification preferences reset to defaults');
'Task Master Kanban notification preferences reset to defaults'
);
} }
/** /**

View File

@@ -1,5 +1,6 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { logger } from './logger';
export interface TaskFileData { export interface TaskFileData {
details?: string; details?: string;
@@ -85,7 +86,7 @@ export async function readTaskFileData(
return { details: undefined, testStrategy: undefined }; return { details: undefined, testStrategy: undefined };
} }
} catch (error) { } catch (error) {
console.error('Error reading task file data:', error); logger.error('Error reading task file data:', error);
return { details: undefined, testStrategy: undefined }; return { details: undefined, testStrategy: undefined };
} }
} }
@@ -103,21 +104,21 @@ export function findTaskById(
// Check if this is a subtask ID with dotted notation (e.g., "1.2") // Check if this is a subtask ID with dotted notation (e.g., "1.2")
if (taskId.includes('.')) { if (taskId.includes('.')) {
const [parentId, subtaskId] = taskId.split('.'); const [parentId, subtaskId] = taskId.split('.');
console.log('🔍 Looking for subtask:', { parentId, subtaskId, taskId }); logger.log('🔍 Looking for subtask:', { parentId, subtaskId, taskId });
// Find the parent task first // Find the parent task first
const parentTask = tasks.find((task) => String(task.id) === parentId); const parentTask = tasks.find((task) => String(task.id) === parentId);
if (!parentTask || !parentTask.subtasks) { if (!parentTask || !parentTask.subtasks) {
console.log('❌ Parent task not found or has no subtasks:', parentId); logger.log('❌ Parent task not found or has no subtasks:', parentId);
return undefined; return undefined;
} }
console.log( logger.log(
'📋 Parent task found with', '📋 Parent task found with',
parentTask.subtasks.length, parentTask.subtasks.length,
'subtasks' 'subtasks'
); );
console.log( logger.log(
'🔍 Subtask IDs in parent:', '🔍 Subtask IDs in parent:',
parentTask.subtasks.map((st) => st.id) parentTask.subtasks.map((st) => st.id)
); );
@@ -127,9 +128,9 @@ export function findTaskById(
(st) => String(st.id) === subtaskId (st) => String(st.id) === subtaskId
); );
if (subtask) { if (subtask) {
console.log('✅ Subtask found:', subtask.id); logger.log('✅ Subtask found:', subtask.id);
} else { } else {
console.log('❌ Subtask not found:', subtaskId); logger.log('❌ Subtask not found:', subtaskId);
} }
return subtask; return subtask;
} }
@@ -159,7 +160,7 @@ export function parseTaskFileData(
tagName: string, tagName: string,
workspacePath?: string workspacePath?: string
): TaskFileData { ): TaskFileData {
console.log('🔍 parseTaskFileData called with:', { logger.log('🔍 parseTaskFileData called with:', {
taskId, taskId,
tagName, tagName,
contentLength: content.length contentLength: content.length
@@ -167,17 +168,17 @@ export function parseTaskFileData(
try { try {
const tasksJson: TasksJsonStructure = JSON.parse(content); const tasksJson: TasksJsonStructure = JSON.parse(content);
console.log('📊 Available tags:', Object.keys(tasksJson)); logger.log('📊 Available tags:', Object.keys(tasksJson));
// Get the tag data // Get the tag data
const tagData = tasksJson[tagName]; const tagData = tasksJson[tagName];
if (!tagData || !tagData.tasks) { if (!tagData || !tagData.tasks) {
console.log('❌ Tag not found or no tasks in tag:', tagName); logger.log('❌ Tag not found or no tasks in tag:', tagName);
return { details: undefined, testStrategy: undefined }; return { details: undefined, testStrategy: undefined };
} }
console.log('📋 Tag found with', tagData.tasks.length, 'tasks'); logger.log('📋 Tag found with', tagData.tasks.length, 'tasks');
console.log( logger.log(
'🔍 Available task IDs:', '🔍 Available task IDs:',
tagData.tasks.map((t) => t.id) tagData.tasks.map((t) => t.id)
); );
@@ -185,18 +186,18 @@ export function parseTaskFileData(
// Find the task // Find the task
const task = findTaskById(tagData.tasks, taskId); const task = findTaskById(tagData.tasks, taskId);
if (!task) { if (!task) {
console.log('❌ Task not found:', taskId); logger.log('❌ Task not found:', taskId);
return { details: undefined, testStrategy: undefined }; return { details: undefined, testStrategy: undefined };
} }
console.log('✅ Task found:', task.id); logger.log('✅ Task found:', task.id);
console.log( logger.log(
'📝 Task has details:', '📝 Task has details:',
!!task.details, !!task.details,
'length:', 'length:',
task.details?.length task.details?.length
); );
console.log( logger.log(
'🧪 Task has testStrategy:', '🧪 Task has testStrategy:',
!!task.testStrategy, !!task.testStrategy,
'length:', 'length:',
@@ -208,7 +209,7 @@ export function parseTaskFileData(
testStrategy: task.testStrategy testStrategy: task.testStrategy
}; };
} catch (error) { } catch (error) {
console.error('❌ Error parsing tasks.json:', error); logger.error('❌ Error parsing tasks.json:', error);
return { details: undefined, testStrategy: undefined }; return { details: undefined, testStrategy: undefined };
} }
} }

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MCPClientManager } from './mcpClient'; import { MCPClientManager } from './mcpClient';
import { logger } from './logger';
// Task Master MCP API response types // Task Master MCP API response types
export interface MCPTaskResponse { export interface MCPTaskResponse {
@@ -155,7 +156,7 @@ export class TaskMasterApi {
this.initializeBackgroundRefresh(); this.initializeBackgroundRefresh();
} }
console.log('TaskMasterApi: Initialized with enhanced caching:', { logger.log('TaskMasterApi: Initialized with enhanced caching:', {
cacheDuration: this.config.cacheDuration, cacheDuration: this.config.cacheDuration,
maxSize: this.config.cache?.maxSize, maxSize: this.config.cache?.maxSize,
backgroundRefresh: this.config.cache?.enableBackgroundRefresh, backgroundRefresh: this.config.cache?.enableBackgroundRefresh,
@@ -200,7 +201,7 @@ export class TaskMasterApi {
mcpArgs.tag = options.tag; mcpArgs.tag = options.tag;
} }
console.log('TaskMasterApi: Calling get_tasks with args:', mcpArgs); logger.log('TaskMasterApi: Calling get_tasks with args:', mcpArgs);
// Call the MCP tool // Call the MCP tool
const mcpResponse = await this.callMCPTool('get_tasks', mcpArgs); const mcpResponse = await this.callMCPTool('get_tasks', mcpArgs);
@@ -217,7 +218,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Error getting tasks:', error); logger.error('TaskMasterApi: Error getting tasks:', error);
return { return {
success: false, success: false,
@@ -247,7 +248,7 @@ export class TaskMasterApi {
projectRoot: options?.projectRoot || this.getWorkspaceRoot() projectRoot: options?.projectRoot || this.getWorkspaceRoot()
}; };
console.log('TaskMasterApi: Calling set_task_status with args:', mcpArgs); logger.log('TaskMasterApi: Calling set_task_status with args:', mcpArgs);
await this.callMCPTool('set_task_status', mcpArgs); await this.callMCPTool('set_task_status', mcpArgs);
@@ -260,7 +261,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Error updating task status:', error); logger.error('TaskMasterApi: Error updating task status:', error);
return { return {
success: false, success: false,
@@ -331,7 +332,7 @@ export class TaskMasterApi {
mcpArgs.research = options.research; mcpArgs.research = options.research;
} }
console.log('TaskMasterApi: Calling update_task with args:', mcpArgs); logger.log('TaskMasterApi: Calling update_task with args:', mcpArgs);
await this.callMCPTool('update_task', mcpArgs); await this.callMCPTool('update_task', mcpArgs);
@@ -344,7 +345,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Error updating task:', error); logger.error('TaskMasterApi: Error updating task:', error);
return { return {
success: false, success: false,
@@ -380,7 +381,7 @@ export class TaskMasterApi {
mcpArgs.research = options.research; mcpArgs.research = options.research;
} }
console.log('TaskMasterApi: Calling update_subtask with args:', mcpArgs); logger.log('TaskMasterApi: Calling update_subtask with args:', mcpArgs);
await this.callMCPTool('update_subtask', mcpArgs); await this.callMCPTool('update_subtask', mcpArgs);
@@ -393,7 +394,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Error updating subtask:', error); logger.error('TaskMasterApi: Error updating subtask:', error);
return { return {
success: false, success: false,
@@ -439,7 +440,7 @@ export class TaskMasterApi {
mcpArgs.status = subtaskData.status; mcpArgs.status = subtaskData.status;
} }
console.log('TaskMasterApi: Calling add_subtask with args:', mcpArgs); logger.log('TaskMasterApi: Calling add_subtask with args:', mcpArgs);
await this.callMCPTool('add_subtask', mcpArgs); await this.callMCPTool('add_subtask', mcpArgs);
@@ -452,7 +453,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Error adding subtask:', error); logger.error('TaskMasterApi: Error adding subtask:', error);
return { return {
success: false, success: false,
@@ -489,7 +490,7 @@ export class TaskMasterApi {
requestDuration: Date.now() - startTime requestDuration: Date.now() - startTime
}; };
} catch (error) { } catch (error) {
console.error('TaskMasterApi: Connection test failed:', error); logger.error('TaskMasterApi: Connection test failed:', error);
return { return {
success: false, success: false,
@@ -529,7 +530,7 @@ export class TaskMasterApi {
this.performBackgroundRefresh(); this.performBackgroundRefresh();
}, interval); }, interval);
console.log( logger.log(
`TaskMasterApi: Background refresh initialized with ${interval}ms interval` `TaskMasterApi: Background refresh initialized with ${interval}ms interval`
); );
} }
@@ -542,7 +543,7 @@ export class TaskMasterApi {
return; return;
} }
console.log('TaskMasterApi: Starting background cache refresh'); logger.log('TaskMasterApi: Starting background cache refresh');
const startTime = Date.now(); const startTime = Date.now();
// Find frequently accessed entries that are close to expiration // Find frequently accessed entries that are close to expiration
@@ -565,7 +566,7 @@ export class TaskMasterApi {
const optionsMatch = key.match(/get_tasks_(.+)/); const optionsMatch = key.match(/get_tasks_(.+)/);
if (optionsMatch) { if (optionsMatch) {
const options = JSON.parse(optionsMatch[1]); const options = JSON.parse(optionsMatch[1]);
console.log(`TaskMasterApi: Background refreshing cache key: ${key}`); logger.log(`TaskMasterApi: Background refreshing cache key: ${key}`);
// Perform the refresh (this will update the cache) // Perform the refresh (this will update the cache)
await this.getTasks(options); await this.getTasks(options);
@@ -573,7 +574,7 @@ export class TaskMasterApi {
this.cacheAnalytics.refreshes++; this.cacheAnalytics.refreshes++;
} }
} catch (error) { } catch (error) {
console.warn( logger.warn(
`TaskMasterApi: Background refresh failed for key ${key}:`, `TaskMasterApi: Background refresh failed for key ${key}:`,
error error
); );
@@ -581,7 +582,7 @@ export class TaskMasterApi {
} }
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
console.log( logger.log(
`TaskMasterApi: Background refresh completed in ${duration}ms, refreshed ${refreshedCount} entries` `TaskMasterApi: Background refresh completed in ${duration}ms, refreshed ${refreshedCount} entries`
); );
} }
@@ -633,7 +634,7 @@ export class TaskMasterApi {
if (evictedCount > 0) { if (evictedCount > 0) {
this.cacheAnalytics.evictions += evictedCount; this.cacheAnalytics.evictions += evictedCount;
console.log( logger.log(
`TaskMasterApi: Evicted ${evictedCount} cache entries matching pattern: ${pattern}` `TaskMasterApi: Evicted ${evictedCount} cache entries matching pattern: ${pattern}`
); );
} }
@@ -661,14 +662,14 @@ export class TaskMasterApi {
} }
const accessTime = Date.now() - startTime; const accessTime = Date.now() - startTime;
console.log( logger.log(
`TaskMasterApi: Cache hit for ${key} (${accessTime}ms, ${cached.accessCount} accesses)` `TaskMasterApi: Cache hit for ${key} (${accessTime}ms, ${cached.accessCount} accesses)`
); );
return cached.data; return cached.data;
} else { } else {
// Remove expired entry // Remove expired entry
this.cache.delete(key); this.cache.delete(key);
console.log(`TaskMasterApi: Cache entry expired and removed: ${key}`); logger.log(`TaskMasterApi: Cache entry expired and removed: ${key}`);
} }
} }
@@ -676,7 +677,7 @@ export class TaskMasterApi {
this.cacheAnalytics.misses++; this.cacheAnalytics.misses++;
} }
console.log(`TaskMasterApi: Cache miss for ${key}`); logger.log(`TaskMasterApi: Cache miss for ${key}`);
return null; return null;
} }
@@ -709,7 +710,7 @@ export class TaskMasterApi {
} }
this.cache.set(key, entry); this.cache.set(key, entry);
console.log( logger.log(
`TaskMasterApi: Cached data for ${key} (size: ${dataSize} bytes, TTL: ${entry.ttl || this.config.cacheDuration}ms)` `TaskMasterApi: Cached data for ${key} (size: ${dataSize} bytes, TTL: ${entry.ttl || this.config.cacheDuration}ms)`
); );
@@ -733,7 +734,7 @@ export class TaskMasterApi {
} }
if (entries.length > 0) { if (entries.length > 0) {
console.log(`TaskMasterApi: Evicted ${entries.length} LRU cache entries`); logger.log(`TaskMasterApi: Evicted ${entries.length} LRU cache entries`);
} }
} }
@@ -755,7 +756,7 @@ export class TaskMasterApi {
// This is a simple implementation - in a more sophisticated system, // This is a simple implementation - in a more sophisticated system,
// we might prefetch related tasks, subtasks, or dependency data // we might prefetch related tasks, subtasks, or dependency data
if (key.includes('get_tasks') && Array.isArray(data)) { if (key.includes('get_tasks') && Array.isArray(data)) {
console.log( logger.log(
`TaskMasterApi: Scheduled prefetch for ${data.length} tasks related to ${key}` `TaskMasterApi: Scheduled prefetch for ${data.length} tasks related to ${key}`
); );
// Future enhancement: prefetch individual task details, related dependencies, etc. // Future enhancement: prefetch individual task details, related dependencies, etc.
@@ -771,7 +772,7 @@ export class TaskMasterApi {
this.backgroundRefreshTimer = undefined; this.backgroundRefreshTimer = undefined;
} }
this.clearCache(); this.clearCache();
console.log('TaskMasterApi: Destroyed and cleaned up resources'); logger.log('TaskMasterApi: Destroyed and cleaned up resources');
} }
/** /**
@@ -786,7 +787,7 @@ export class TaskMasterApi {
for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) { for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) {
try { try {
const rawResponse = await this.mcpClient.callTool(toolName, args); const rawResponse = await this.mcpClient.callTool(toolName, args);
console.log( logger.log(
`🔍 DEBUGGING: Raw MCP response for ${toolName}:`, `🔍 DEBUGGING: Raw MCP response for ${toolName}:`,
JSON.stringify(rawResponse, null, 2) JSON.stringify(rawResponse, null, 2)
); );
@@ -802,30 +803,30 @@ export class TaskMasterApi {
if (contentItem.type === 'text' && contentItem.text) { if (contentItem.type === 'text' && contentItem.text) {
try { try {
const parsedData = JSON.parse(contentItem.text); const parsedData = JSON.parse(contentItem.text);
console.log( logger.log(
`🔍 DEBUGGING: Parsed MCP data for ${toolName}:`, `🔍 DEBUGGING: Parsed MCP data for ${toolName}:`,
parsedData parsedData
); );
return parsedData; return parsedData;
} catch (parseError) { } catch (parseError) {
console.error( logger.error(
`TaskMasterApi: Failed to parse MCP response text for ${toolName}:`, `TaskMasterApi: Failed to parse MCP response text for ${toolName}:`,
parseError parseError
); );
console.error(`TaskMasterApi: Raw text was:`, contentItem.text); logger.error(`TaskMasterApi: Raw text was:`, contentItem.text);
return rawResponse; // Fall back to original response return rawResponse; // Fall back to original response
} }
} }
} }
// If not in expected format, return as-is // If not in expected format, return as-is
console.warn( logger.warn(
`TaskMasterApi: Unexpected MCP response format for ${toolName}, returning raw response` `TaskMasterApi: Unexpected MCP response format for ${toolName}, returning raw response`
); );
return rawResponse; return rawResponse;
} catch (error) { } catch (error) {
lastError = error instanceof Error ? error : new Error('Unknown error'); lastError = error instanceof Error ? error : new Error('Unknown error');
console.warn( logger.warn(
`TaskMasterApi: Attempt ${attempt}/${this.config.retryAttempts} failed for ${toolName}:`, `TaskMasterApi: Attempt ${attempt}/${this.config.retryAttempts} failed for ${toolName}:`,
lastError.message lastError.message
); );
@@ -856,7 +857,7 @@ export class TaskMasterApi {
// Validate response structure // Validate response structure
const validationResult = this.validateMCPResponse(mcpResponse); const validationResult = this.validateMCPResponse(mcpResponse);
if (!validationResult.isValid) { if (!validationResult.isValid) {
console.warn( logger.warn(
'TaskMasterApi: MCP response validation failed:', 'TaskMasterApi: MCP response validation failed:',
validationResult.errors validationResult.errors
); );
@@ -864,7 +865,7 @@ export class TaskMasterApi {
} }
const tasks = mcpResponse.data.tasks || []; const tasks = mcpResponse.data.tasks || [];
console.log( logger.log(
`TaskMasterApi: Transforming ${tasks.length} tasks from MCP response` `TaskMasterApi: Transforming ${tasks.length} tasks from MCP response`
); );
@@ -892,7 +893,7 @@ export class TaskMasterApi {
error: errorMsg, error: errorMsg,
task: tasks[i] task: tasks[i]
}); });
console.error( logger.error(
`TaskMasterApi: Failed to transform task at index ${i}:`, `TaskMasterApi: Failed to transform task at index ${i}:`,
errorMsg, errorMsg,
tasks[i] tasks[i]
@@ -902,7 +903,7 @@ export class TaskMasterApi {
// Log transformation summary // Log transformation summary
const transformDuration = Date.now() - transformStartTime; const transformDuration = Date.now() - transformStartTime;
console.log( logger.log(
`TaskMasterApi: Transformation completed in ${transformDuration}ms`, `TaskMasterApi: Transformation completed in ${transformDuration}ms`,
{ {
totalTasks: tasks.length, totalTasks: tasks.length,
@@ -917,7 +918,7 @@ export class TaskMasterApi {
return transformedTasks; return transformedTasks;
} catch (error) { } catch (error) {
console.error( logger.error(
'TaskMasterApi: Critical error during response transformation:', 'TaskMasterApi: Critical error during response transformation:',
error error
); );
@@ -972,7 +973,7 @@ export class TaskMasterApi {
*/ */
private transformSingleTask(task: any, index: number): TaskMasterTask | null { private transformSingleTask(task: any, index: number): TaskMasterTask | null {
if (!task || typeof task !== 'object') { if (!task || typeof task !== 'object') {
console.warn( logger.warn(
`TaskMasterApi: Task at index ${index} is not a valid object:`, `TaskMasterApi: Task at index ${index} is not a valid object:`,
task task
); );
@@ -1045,7 +1046,7 @@ export class TaskMasterApi {
dependencies.length > 0 || dependencies.length > 0 ||
complexityScore !== undefined complexityScore !== undefined
) { ) {
console.log( logger.log(
`TaskMasterApi: Successfully transformed complex task ${taskId}:`, `TaskMasterApi: Successfully transformed complex task ${taskId}:`,
{ {
subtaskCount: subtasks.length, subtaskCount: subtasks.length,
@@ -1059,7 +1060,7 @@ export class TaskMasterApi {
return transformedTask; return transformedTask;
} catch (error) { } catch (error) {
console.error( logger.error(
`TaskMasterApi: Error transforming task at index ${index}:`, `TaskMasterApi: Error transforming task at index ${index}:`,
error, error,
task task
@@ -1074,14 +1075,14 @@ export class TaskMasterApi {
private validateAndNormalizeId(id: any, fallbackIndex: number): string { private validateAndNormalizeId(id: any, fallbackIndex: number): string {
if (id === null || id === undefined) { if (id === null || id === undefined) {
const generatedId = `generated_${fallbackIndex}_${Date.now()}`; const generatedId = `generated_${fallbackIndex}_${Date.now()}`;
console.warn(`TaskMasterApi: Task missing ID, generated: ${generatedId}`); logger.warn(`TaskMasterApi: Task missing ID, generated: ${generatedId}`);
return generatedId; return generatedId;
} }
const stringId = String(id).trim(); const stringId = String(id).trim();
if (stringId === '') { if (stringId === '') {
const generatedId = `empty_${fallbackIndex}_${Date.now()}`; const generatedId = `empty_${fallbackIndex}_${Date.now()}`;
console.warn( logger.warn(
`TaskMasterApi: Task has empty ID, generated: ${generatedId}` `TaskMasterApi: Task has empty ID, generated: ${generatedId}`
); );
return generatedId; return generatedId;
@@ -1103,7 +1104,7 @@ export class TaskMasterApi {
} }
if (typeof value !== 'string') { if (typeof value !== 'string') {
console.warn( logger.warn(
`TaskMasterApi: ${fieldName} is not a string, converting:`, `TaskMasterApi: ${fieldName} is not a string, converting:`,
value value
); );
@@ -1127,7 +1128,7 @@ export class TaskMasterApi {
} }
if (!Array.isArray(dependencies)) { if (!Array.isArray(dependencies)) {
console.warn( logger.warn(
`TaskMasterApi: Dependencies for task ${taskId} is not an array:`, `TaskMasterApi: Dependencies for task ${taskId} is not an array:`,
dependencies dependencies
); );
@@ -1138,7 +1139,7 @@ export class TaskMasterApi {
for (let i = 0; i < dependencies.length; i++) { for (let i = 0; i < dependencies.length; i++) {
const dep = dependencies[i]; const dep = dependencies[i];
if (dep === null || dep === undefined) { if (dep === null || dep === undefined) {
console.warn( logger.warn(
`TaskMasterApi: Null dependency at index ${i} for task ${taskId}` `TaskMasterApi: Null dependency at index ${i} for task ${taskId}`
); );
continue; continue;
@@ -1146,7 +1147,7 @@ export class TaskMasterApi {
const stringDep = String(dep).trim(); const stringDep = String(dep).trim();
if (stringDep === '') { if (stringDep === '') {
console.warn( logger.warn(
`TaskMasterApi: Empty dependency at index ${i} for task ${taskId}` `TaskMasterApi: Empty dependency at index ${i} for task ${taskId}`
); );
continue; continue;
@@ -1154,7 +1155,7 @@ export class TaskMasterApi {
// Check for self-dependency // Check for self-dependency
if (stringDep === taskId) { if (stringDep === taskId) {
console.warn( logger.warn(
`TaskMasterApi: Self-dependency detected for task ${taskId}, skipping` `TaskMasterApi: Self-dependency detected for task ${taskId}, skipping`
); );
continue; continue;
@@ -1185,7 +1186,7 @@ export class TaskMasterApi {
} }
if (!Array.isArray(subtasks)) { if (!Array.isArray(subtasks)) {
console.warn( logger.warn(
`TaskMasterApi: Subtasks for task ${parentTaskId} is not an array:`, `TaskMasterApi: Subtasks for task ${parentTaskId} is not an array:`,
subtasks subtasks
); );
@@ -1197,7 +1198,7 @@ export class TaskMasterApi {
try { try {
const subtask = subtasks[i]; const subtask = subtasks[i];
if (!subtask || typeof subtask !== 'object') { if (!subtask || typeof subtask !== 'object') {
console.warn( logger.warn(
`TaskMasterApi: Invalid subtask at index ${i} for task ${parentTaskId}:`, `TaskMasterApi: Invalid subtask at index ${i} for task ${parentTaskId}:`,
subtask subtask
); );
@@ -1233,7 +1234,7 @@ export class TaskMasterApi {
validSubtasks.push(transformedSubtask); validSubtasks.push(transformedSubtask);
} catch (error) { } catch (error) {
console.error( logger.error(
`TaskMasterApi: Error transforming subtask at index ${i} for task ${parentTaskId}:`, `TaskMasterApi: Error transforming subtask at index ${i} for task ${parentTaskId}:`,
error error
); );
@@ -1285,7 +1286,7 @@ export class TaskMasterApi {
const result = statusMap[normalized] || 'pending'; const result = statusMap[normalized] || 'pending';
if (original && original !== result) { if (original && original !== result) {
console.log( logger.log(
`TaskMasterApi: Normalized status '${original}' -> '${result}'` `TaskMasterApi: Normalized status '${original}' -> '${result}'`
); );
} }
@@ -1330,7 +1331,7 @@ export class TaskMasterApi {
} }
if (original && original !== result) { if (original && original !== result) {
console.log( logger.log(
`TaskMasterApi: Normalized priority '${original}' -> '${result}'` `TaskMasterApi: Normalized priority '${original}' -> '${result}'`
); );
} }

View File

@@ -648,14 +648,21 @@ const TaskEditModal: React.FC<{
// Only include changed fields // Only include changed fields
const updates: TaskUpdates = {}; const updates: TaskUpdates = {};
if (formData.title !== task.title) {updates.title = formData.title;} if (formData.title !== task.title) {
if (formData.description !== task.description) updates.title = formData.title;
{updates.description = formData.description;} }
if (formData.details !== task.details) {updates.details = formData.details;} if (formData.description !== task.description) {
if (formData.priority !== task.priority) updates.description = formData.description;
{updates.priority = formData.priority;} }
if (formData.testStrategy !== task.testStrategy) if (formData.details !== task.details) {
{updates.testStrategy = formData.testStrategy;} updates.details = formData.details;
}
if (formData.priority !== task.priority) {
updates.priority = formData.priority;
}
if (formData.testStrategy !== task.testStrategy) {
updates.testStrategy = formData.testStrategy;
}
if ( if (
JSON.stringify(formData.dependencies) !== JSON.stringify(formData.dependencies) !==
JSON.stringify(task.dependencies) JSON.stringify(task.dependencies)
@@ -874,8 +881,9 @@ const TaskCard: React.FC<{
// Main Kanban Board Component // Main Kanban Board Component
const TaskMasterKanban: React.FC = () => { const TaskMasterKanban: React.FC = () => {
const context = useContext(VSCodeContext); const context = useContext(VSCodeContext);
if (!context) if (!context) {
{throw new Error('TaskMasterKanban must be used within VSCodeContext');} throw new Error('TaskMasterKanban must be used within VSCodeContext');
}
const { state, dispatch, sendMessage, availableHeight } = context; const { state, dispatch, sendMessage, availableHeight } = context;
const { const {
@@ -984,14 +992,18 @@ const TaskMasterKanban: React.FC = () => {
dispatch({ type: 'SET_USER_INTERACTING', payload: false }); dispatch({ type: 'SET_USER_INTERACTING', payload: false });
}, 1000); // 1 second delay to ensure smooth completion }, 1000); // 1 second delay to ensure smooth completion
if (!over) {return;} if (!over) {
return;
}
const taskId = active.id as string; const taskId = active.id as string;
const newStatus = over.id as TaskMasterTask['status']; const newStatus = over.id as TaskMasterTask['status'];
// Find the task that was moved // Find the task that was moved
const task = tasks.find((t) => t.id === taskId); const task = tasks.find((t) => t.id === taskId);
if (!task || task.status === newStatus) {return;} if (!task || task.status === newStatus) {
return;
}
console.log(`🔄 Moving task ${taskId} from ${task.status} to ${newStatus}`); console.log(`🔄 Moving task ${taskId} from ${task.status} to ${newStatus}`);
@@ -1382,7 +1394,9 @@ const App: React.FC = () => {
// Handle messages from extension // Handle messages from extension
useEffect(() => { useEffect(() => {
if (!vscode) {return;} if (!vscode) {
return;
}
const handleMessage = (event: MessageEvent) => { const handleMessage = (event: MessageEvent) => {
const message: WebviewMessage = event.data; const message: WebviewMessage = event.data;
@@ -1526,13 +1540,16 @@ const App: React.FC = () => {
// Map error severity to toast type // Map error severity to toast type
let toastType: ToastNotification['type'] = 'error'; let toastType: ToastNotification['type'] = 'error';
if (errorData.severity === 'low') {toastType = 'info';} if (errorData.severity === 'low') {
else if (errorData.severity === 'medium') {toastType = 'warning';} toastType = 'info';
else if ( } else if (errorData.severity === 'medium') {
toastType = 'warning';
} else if (
errorData.severity === 'high' || errorData.severity === 'high' ||
errorData.severity === 'critical' errorData.severity === 'critical'
) ) {
{toastType = 'error';} toastType = 'error';
}
// Create appropriate toast based on error category // Create appropriate toast based on error category
const title = const title =

7885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,10 +9,7 @@
"task-master-mcp": "mcp-server/server.js", "task-master-mcp": "mcp-server/server.js",
"task-master-ai": "mcp-server/server.js" "task-master-ai": "mcp-server/server.js"
}, },
"workspaces": [ "workspaces": ["apps/*", "."],
"apps/*",
"."
],
"scripts": { "scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest", "test": "node --experimental-vm-modules node_modules/.bin/jest",
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures", "test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
@@ -126,7 +123,8 @@
"jest-environment-node": "^29.7.0", "jest-environment-node": "^29.7.0",
"mock-fs": "^5.5.0", "mock-fs": "^5.5.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"react": "^18.3.1", "react": "^19.1.0",
"react-dom": "^19.1.0",
"supertest": "^7.1.0", "supertest": "^7.1.0",
"tsx": "^4.16.2" "tsx": "^4.16.2"
} }

View File

@@ -332,9 +332,9 @@
"input": 1.0, "input": 1.0,
"output": 3.0 "output": 3.0
}, },
"allowed_roles": ["main", "fallback"], "allowed_roles": ["main", "fallback"],
"max_tokens": 16384, "max_tokens": 16384,
"supported": true "supported": true
}, },
{ {
"id": "llama-3.3-70b-versatile", "id": "llama-3.3-70b-versatile",

View File

@@ -1,52 +1,47 @@
#!/usr/bin/env node #!/usr/bin/env node
import assert from 'node:assert/strict' import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process' import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs' import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path' import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename) const __dirname = dirname(__filename);
// Read the extension's publishing package.json for accurate version/name // Read the extension's publishing package.json for accurate version/name
const extensionDir = join(__dirname, '..', 'apps', 'extension') const extensionDir = join(__dirname, '..', 'apps', 'extension');
const publishPkgPath = join(extensionDir, 'package.publish.json') const publishPkgPath = join(extensionDir, 'package.publish.json');
let pkg let pkg;
try { try {
const pkgContent = readFileSync(publishPkgPath, 'utf8') const pkgContent = readFileSync(publishPkgPath, 'utf8');
pkg = JSON.parse(pkgContent) pkg = JSON.parse(pkgContent);
} catch (error) { } catch (error) {
console.error('Failed to read package.publish.json:', error.message) console.error('Failed to read package.publish.json:', error.message);
process.exit(1) process.exit(1);
} }
// Ensure we have required fields // Ensure we have required fields
assert(pkg.name, 'package.publish.json must have a name field') assert(pkg.name, 'package.publish.json must have a name field');
assert(pkg.version, 'package.publish.json must have a version field') assert(pkg.version, 'package.publish.json must have a version field');
assert(pkg.repository, 'package.publish.json must have a repository field') assert(pkg.repository, 'package.publish.json must have a repository field');
const tag = `${pkg.name}@${pkg.version}` const tag = `${pkg.name}@${pkg.version}`;
// Get repository URL - handle both string and object format // Get repository URL - handle both string and object format
const repoUrl = typeof pkg.repository === 'string' const repoUrl =
? pkg.repository typeof pkg.repository === 'string' ? pkg.repository : pkg.repository.url;
: pkg.repository.url
assert(repoUrl, 'Repository URL not found in package.publish.json') assert(repoUrl, 'Repository URL not found in package.publish.json');
const { status, stdout, error } = spawnSync('git', [ const { status, stdout, error } = spawnSync('git', ['ls-remote', repoUrl, tag]);
'ls-remote',
repoUrl,
tag
])
assert.equal(status, 0, error) assert.equal(status, 0, error);
const exists = String(stdout).trim() !== '' const exists = String(stdout).trim() !== '';
if (!exists) { if (!exists) {
console.log(`\nNew extension tag: ${tag}`) console.log(`\nNew extension tag: ${tag}`);
} else { } else {
console.log(`\nExtension tag already exists: ${tag}`) console.log(`\nExtension tag already exists: ${tag}`);
} }