diff --git a/mcp-server/src/core/utils/async-manager.js b/mcp-server/src/core/utils/async-manager.js deleted file mode 100644 index cf75c8b4..00000000 --- a/mcp-server/src/core/utils/async-manager.js +++ /dev/null @@ -1,251 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -class AsyncOperationManager { - constructor() { - this.operations = new Map(); // Stores active operation state - this.completedOperations = new Map(); // Stores completed operations - this.maxCompletedOperations = 100; // Maximum number of completed operations to store - this.listeners = new Map(); // For potential future notifications - } - - /** - * Adds an operation to be executed asynchronously. - * @param {Function} operationFn - The async function to execute (e.g., a Direct function). - * @param {Object} args - Arguments to pass to the operationFn. - * @param {Object} context - The MCP tool context { log, reportProgress, session }. - * @returns {string} The unique ID assigned to this operation. - */ - addOperation(operationFn, args, context) { - const operationId = `op-${uuidv4()}`; - const operation = { - id: operationId, - status: 'pending', - startTime: Date.now(), - endTime: null, - result: null, - error: null, - // Store necessary parts of context, especially log for background execution - log: context.log, - reportProgress: context.reportProgress, // Pass reportProgress through - session: context.session // Pass session through if needed by the operationFn - }; - this.operations.set(operationId, operation); - this.log(operationId, 'info', `Operation added.`); - - // Start execution in the background (don't await here) - this._runOperation(operationId, operationFn, args, context).catch((err) => { - // Catch unexpected errors during the async execution setup itself - this.log( - operationId, - 'error', - `Critical error starting operation: ${err.message}`, - { stack: err.stack } - ); - operation.status = 'failed'; - operation.error = { - code: 'MANAGER_EXECUTION_ERROR', - message: err.message - }; - operation.endTime = Date.now(); - - // Move to completed operations - this._moveToCompleted(operationId); - }); - - return operationId; - } - - /** - * Internal function to execute the operation. - * @param {string} operationId - The ID of the operation. - * @param {Function} operationFn - The async function to execute. - * @param {Object} args - Arguments for the function. - * @param {Object} context - The original MCP tool context. - */ - async _runOperation(operationId, operationFn, args, context) { - const operation = this.operations.get(operationId); - if (!operation) return; // Should not happen - - operation.status = 'running'; - this.log(operationId, 'info', `Operation running.`); - this.emit('statusChanged', { operationId, status: 'running' }); - - try { - // Pass the necessary context parts to the direct function - // The direct function needs to be adapted if it needs reportProgress - // We pass the original context's log, plus our wrapped reportProgress - const result = await operationFn(args, operation.log, { - reportProgress: (progress) => - this._handleProgress(operationId, progress), - mcpLog: operation.log, // Pass log as mcpLog if direct fn expects it - session: operation.session - }); - - operation.status = result.success ? 'completed' : 'failed'; - operation.result = result.success ? result.data : null; - operation.error = result.success ? null : result.error; - this.log( - operationId, - 'info', - `Operation finished with status: ${operation.status}` - ); - } catch (error) { - this.log( - operationId, - 'error', - `Operation failed with error: ${error.message}`, - { stack: error.stack } - ); - operation.status = 'failed'; - operation.error = { - code: 'OPERATION_EXECUTION_ERROR', - message: error.message - }; - } finally { - operation.endTime = Date.now(); - this.emit('statusChanged', { - operationId, - status: operation.status, - result: operation.result, - error: operation.error - }); - - // Move to completed operations if done or failed - if (operation.status === 'completed' || operation.status === 'failed') { - this._moveToCompleted(operationId); - } - } - } - - /** - * Move an operation from active operations to completed operations history. - * @param {string} operationId - The ID of the operation to move. - * @private - */ - _moveToCompleted(operationId) { - const operation = this.operations.get(operationId); - if (!operation) return; - - // Store only the necessary data in completed operations - const completedData = { - id: operation.id, - status: operation.status, - startTime: operation.startTime, - endTime: operation.endTime, - result: operation.result, - error: operation.error - }; - - this.completedOperations.set(operationId, completedData); - this.operations.delete(operationId); - - // Trim completed operations if exceeding maximum - if (this.completedOperations.size > this.maxCompletedOperations) { - // Get the oldest operation (sorted by endTime) - const oldest = [...this.completedOperations.entries()].sort( - (a, b) => a[1].endTime - b[1].endTime - )[0]; - - if (oldest) { - this.completedOperations.delete(oldest[0]); - } - } - } - - /** - * Handles progress updates from the running operation and forwards them. - * @param {string} operationId - The ID of the operation reporting progress. - * @param {Object} progress - The progress object { progress, total? }. - */ - _handleProgress(operationId, progress) { - const operation = this.operations.get(operationId); - if (operation && operation.reportProgress) { - try { - // Use the reportProgress function captured from the original context - operation.reportProgress(progress); - this.log( - operationId, - 'debug', - `Reported progress: ${JSON.stringify(progress)}` - ); - } catch (err) { - this.log( - operationId, - 'warn', - `Failed to report progress: ${err.message}` - ); - // Don't stop the operation, just log the reporting failure - } - } - } - - /** - * Retrieves the status and result/error of an operation. - * @param {string} operationId - The ID of the operation. - * @returns {Object | null} The operation details or null if not found. - */ - getStatus(operationId) { - // First check active operations - const operation = this.operations.get(operationId); - if (operation) { - return { - id: operation.id, - status: operation.status, - startTime: operation.startTime, - endTime: operation.endTime, - result: operation.result, - error: operation.error - }; - } - - // Then check completed operations - const completedOperation = this.completedOperations.get(operationId); - if (completedOperation) { - return completedOperation; - } - - // Operation not found in either active or completed - return { - error: { - code: 'OPERATION_NOT_FOUND', - message: `Operation ID ${operationId} not found. It may have been completed and removed from history, or the ID may be invalid.` - }, - status: 'not_found' - }; - } - - /** - * Internal logging helper to prefix logs with the operation ID. - * @param {string} operationId - The ID of the operation. - * @param {'info'|'warn'|'error'|'debug'} level - Log level. - * @param {string} message - Log message. - * @param {Object} [meta] - Additional metadata. - */ - log(operationId, level, message, meta = {}) { - const operation = this.operations.get(operationId); - // Use the logger instance associated with the operation if available, otherwise console - const logger = operation?.log || console; - const logFn = logger[level] || logger.log || console.log; // Fallback - logFn(`[AsyncOp ${operationId}] ${message}`, meta); - } - - // --- Basic Event Emitter --- - on(eventName, listener) { - if (!this.listeners.has(eventName)) { - this.listeners.set(eventName, []); - } - this.listeners.get(eventName).push(listener); - } - - emit(eventName, data) { - if (this.listeners.has(eventName)) { - this.listeners.get(eventName).forEach((listener) => listener(data)); - } - } -} - -// Export a singleton instance -const asyncOperationManager = new AsyncOperationManager(); - -// Export the manager and potentially the class if needed elsewhere -export { asyncOperationManager, AsyncOperationManager }; diff --git a/mcp-server/src/index.js b/mcp-server/src/index.js index a3fe5bd0..2ea14842 100644 --- a/mcp-server/src/index.js +++ b/mcp-server/src/index.js @@ -5,7 +5,6 @@ import { fileURLToPath } from 'url'; import fs from 'fs'; import logger from './logger.js'; import { registerTaskMasterTools } from './tools/index.js'; -import { asyncOperationManager } from './core/utils/async-manager.js'; // Load environment variables dotenv.config(); @@ -35,9 +34,6 @@ class TaskMasterMCPServer { this.server.addResourceTemplate({}); - // Make the manager accessible (e.g., pass it to tool registration) - this.asyncManager = asyncOperationManager; - // Bind methods this.init = this.init.bind(this); this.start = this.start.bind(this); @@ -88,7 +84,4 @@ class TaskMasterMCPServer { } } -// Export the manager from here as well, if needed elsewhere -export { asyncOperationManager }; - export default TaskMasterMCPServer; diff --git a/mcp-server/src/tools/index.js b/mcp-server/src/tools/index.js index 0ed3f22f..2fe97cb6 100644 --- a/mcp-server/src/tools/index.js +++ b/mcp-server/src/tools/index.js @@ -27,14 +27,12 @@ import { registerComplexityReportTool } from './complexity-report.js'; import { registerAddDependencyTool } from './add-dependency.js'; import { registerRemoveTaskTool } from './remove-task.js'; import { registerInitializeProjectTool } from './initialize-project.js'; -import { asyncOperationManager } from '../core/utils/async-manager.js'; /** * Register all Task Master tools with the MCP server * @param {Object} server - FastMCP server instance - * @param {asyncOperationManager} asyncManager - The async operation manager instance */ -export function registerTaskMasterTools(server, asyncManager) { +export function registerTaskMasterTools(server) { try { // Register each tool registerListTasksTool(server); @@ -47,7 +45,7 @@ export function registerTaskMasterTools(server, asyncManager) { registerShowTaskTool(server); registerNextTaskTool(server); registerExpandTaskTool(server); - registerAddTaskTool(server, asyncManager); + registerAddTaskTool(server); registerAddSubtaskTool(server); registerRemoveSubtaskTool(server); registerAnalyzeTool(server); diff --git a/scripts/modules/config-manager.js b/scripts/modules/config-manager.js index 2973af0d..f7b8a392 100644 --- a/scripts/modules/config-manager.js +++ b/scripts/modules/config-manager.js @@ -170,10 +170,14 @@ function _loadAndValidateConfig(explicitRoot = null) { } } else { // Config file doesn't exist - // **Strict Check**: Throw error if config file is missing - throw new ConfigurationError( - `${CONFIG_FILE_NAME} not found at project root (${rootToUse || 'unknown'}).` + // **Changed: Log warning instead of throwing error** + console.warn( + chalk.yellow( + `Warning: ${CONFIG_FILE_NAME} not found at project root (${rootToUse || 'unknown'}). Using default configuration. Run 'task-master models --setup' to configure.` + ) ); + // Return defaults instead of throwing error + return defaults; } return config; @@ -541,11 +545,26 @@ function writeConfig(config, explicitRoot = null) { } } +/** + * Checks if the .taskmasterconfig file exists at the project root + * @param {string|null} explicitRoot - Optional explicit path to the project root + * @returns {boolean} True if the file exists, false otherwise + */ +function isConfigFilePresent(explicitRoot = null) { + const rootPath = explicitRoot || findProjectRoot(); + if (!rootPath) { + return false; + } + const configPath = path.join(rootPath, CONFIG_FILE_NAME); + return fs.existsSync(configPath); +} + export { // Core config access getConfig, writeConfig, ConfigurationError, // Export custom error type + isConfigFilePresent, // Add the new function export // Validation validateProvider, diff --git a/scripts/modules/task-manager/list-tasks.js b/scripts/modules/task-manager/list-tasks.js index e63445c0..983d08ed 100644 --- a/scripts/modules/task-manager/list-tasks.js +++ b/scripts/modules/task-manager/list-tasks.js @@ -3,6 +3,7 @@ import boxen from 'boxen'; import Table from 'cli-table3'; import { log, readJSON, truncate } from '../utils.js'; +import findNextTask from './find-next-task.js'; import { displayBanner, diff --git a/tasks/task_061.txt b/tasks/task_061.txt index b2692940..79948668 100644 --- a/tasks/task_061.txt +++ b/tasks/task_061.txt @@ -1678,6 +1678,33 @@ The new AI architecture introduces a clear separation between sensitive credenti ### Details: + +I'll provide additional technical information to enhance the "Cleanup Old AI Service Files" subtask: + +## Implementation Details + +**Pre-Cleanup Verification Steps:** +- Run a comprehensive codebase search for any remaining imports or references to `ai-services.js` and `ai-client-factory.js` using grep or your IDE's search functionality[1][4] +- Check for any dynamic imports that might not be caught by static analysis tools +- Verify that all dependent modules have been properly migrated to the new AI service architecture + +**Cleanup Process:** +- Create a backup of the files before deletion in case rollback is needed +- Document the file removal in the migration changelog with timestamps and specific file paths[5] +- Update any build configuration files that might reference these files (webpack configs, etc.) +- Run a full test suite after removal to ensure no runtime errors occur[2] + +**Post-Cleanup Validation:** +- Implement automated tests to verify the application functions correctly without the removed files +- Monitor application logs and error reporting systems for 48-72 hours after deployment to catch any missed dependencies[3] +- Perform a final code review to ensure clean architecture principles are maintained in the new implementation + +**Technical Considerations:** +- Check for any circular dependencies that might have been created during the migration process +- Ensure proper garbage collection by removing any cached instances of the old services +- Verify that performance metrics remain stable after the removal of legacy code + + ## 34. Audit and Standardize Env Variable Access [done] ### Dependencies: None ### Description: Audit the entire codebase (core modules, provider modules, utilities) to ensure all accesses to environment variables (API keys, configuration flags) consistently use a standardized resolution function (like `resolveEnvVariable` or a new utility) that checks `process.env` first and then `session.env` if available. Refactor any direct `process.env` access where `session.env` should also be considered. diff --git a/tasks/tasks.json b/tasks/tasks.json index 8b4f9858..c3d98c5f 100644 --- a/tasks/tasks.json +++ b/tasks/tasks.json @@ -3095,9 +3095,10 @@ "id": 33, "title": "Cleanup Old AI Service Files", "description": "After all other migration subtasks (refactoring, provider implementation, testing, documentation) are complete and verified, remove the old `ai-services.js` and `ai-client-factory.js` files from the `scripts/modules/` directory. Ensure no code still references them.", - "details": "", + "details": "\n\n\nI'll provide additional technical information to enhance the \"Cleanup Old AI Service Files\" subtask:\n\n## Implementation Details\n\n**Pre-Cleanup Verification Steps:**\n- Run a comprehensive codebase search for any remaining imports or references to `ai-services.js` and `ai-client-factory.js` using grep or your IDE's search functionality[1][4]\n- Check for any dynamic imports that might not be caught by static analysis tools\n- Verify that all dependent modules have been properly migrated to the new AI service architecture\n\n**Cleanup Process:**\n- Create a backup of the files before deletion in case rollback is needed\n- Document the file removal in the migration changelog with timestamps and specific file paths[5]\n- Update any build configuration files that might reference these files (webpack configs, etc.)\n- Run a full test suite after removal to ensure no runtime errors occur[2]\n\n**Post-Cleanup Validation:**\n- Implement automated tests to verify the application functions correctly without the removed files\n- Monitor application logs and error reporting systems for 48-72 hours after deployment to catch any missed dependencies[3]\n- Perform a final code review to ensure clean architecture principles are maintained in the new implementation\n\n**Technical Considerations:**\n- Check for any circular dependencies that might have been created during the migration process\n- Ensure proper garbage collection by removing any cached instances of the old services\n- Verify that performance metrics remain stable after the removal of legacy code\n", "status": "pending", "dependencies": [ + "61.31", "61.32" ], "parentTaskId": 61 diff --git a/test-config-manager.js b/test-config-manager.js new file mode 100644 index 00000000..cf8b72f7 --- /dev/null +++ b/test-config-manager.js @@ -0,0 +1,48 @@ +// test-config-manager.js +console.log('=== ENVIRONMENT TEST ==='); +console.log('Working directory:', process.cwd()); +console.log('NODE_PATH:', process.env.NODE_PATH); + +// Test basic imports +try { + console.log('Importing config-manager'); + // Use dynamic import for ESM + const configManagerModule = await import( + './scripts/modules/config-manager.js' + ); + const configManager = configManagerModule.default || configManagerModule; + console.log('Config manager loaded successfully'); + + console.log('Loading supported models'); + // Add after line 14 (after "Config manager loaded successfully") + console.log('Config manager exports:', Object.keys(configManager)); +} catch (error) { + console.error('Import error:', error.message); + console.error(error.stack); +} + +// Test file access +try { + console.log('Checking for .taskmasterconfig'); + // Use dynamic import for ESM + const { readFileSync, existsSync } = await import('fs'); + const { resolve } = await import('path'); + + const configExists = existsSync('./.taskmasterconfig'); + console.log('.taskmasterconfig exists:', configExists); + + if (configExists) { + const config = JSON.parse(readFileSync('./.taskmasterconfig', 'utf-8')); + console.log('Config keys:', Object.keys(config)); + } + + console.log('Checking for supported-models.json'); + const modelsPath = resolve('./scripts/modules/supported-models.json'); + console.log('Models path:', modelsPath); + const modelsExists = existsSync(modelsPath); + console.log('supported-models.json exists:', modelsExists); +} catch (error) { + console.error('File access error:', error.message); +} + +console.log('=== TEST COMPLETE ===');