diff --git a/.changeset/curly-months-go.md b/.changeset/curly-months-go.md new file mode 100644 index 00000000..3213f618 --- /dev/null +++ b/.changeset/curly-months-go.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix bedrock issues diff --git a/.changeset/dirty-webs-jam.md b/.changeset/dirty-webs-jam.md new file mode 100644 index 00000000..575019d0 --- /dev/null +++ b/.changeset/dirty-webs-jam.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix MCP tool calls logging errors diff --git a/.changeset/loud-dolls-yell.md b/.changeset/loud-dolls-yell.md new file mode 100644 index 00000000..4cc1ae18 --- /dev/null +++ b/.changeset/loud-dolls-yell.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Update rules for new directory structure diff --git a/.changeset/odd-banks-fly.md b/.changeset/odd-banks-fly.md new file mode 100644 index 00000000..68e8d2b1 --- /dev/null +++ b/.changeset/odd-banks-fly.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix bug in expand_all mcp tool diff --git a/.changeset/tall-symbols-call.md b/.changeset/tall-symbols-call.md new file mode 100644 index 00000000..a6087653 --- /dev/null +++ b/.changeset/tall-symbols-call.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix MCP crashing after certain commands due to console logs diff --git a/.cursor/rules/taskmaster.mdc b/.cursor/rules/taskmaster.mdc index 767f5f58..b067738a 100644 --- a/.cursor/rules/taskmaster.mdc +++ b/.cursor/rules/taskmaster.mdc @@ -45,7 +45,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` * **Key Parameters/Options:** * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input `) - * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to 'tasks/tasks.json'.` (CLI: `-o, --output `) + * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output `) * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks `) * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) * **Usage:** Useful for bootstrapping a project from an existing requirements document. diff --git a/.taskmaster/config.json b/.taskmaster/config.json index 83cedf70..a61d10d5 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -26,6 +26,7 @@ "defaultPriority": "medium", "projectName": "Taskmaster", "ollamaBaseURL": "http://localhost:11434/api", + "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", "userId": "1234567890", "azureBaseURL": "https://your-endpoint.azure.com/" } diff --git a/.taskmasterconfig b/.taskmasterconfig deleted file mode 100644 index 83cedf70..00000000 --- a/.taskmasterconfig +++ /dev/null @@ -1,32 +0,0 @@ -{ - "models": { - "main": { - "provider": "anthropic", - "modelId": "claude-sonnet-4-20250514", - "maxTokens": 50000, - "temperature": 0.2 - }, - "research": { - "provider": "perplexity", - "modelId": "sonar-pro", - "maxTokens": 8700, - "temperature": 0.1 - }, - "fallback": { - "provider": "anthropic", - "modelId": "claude-3-7-sonnet-20250219", - "maxTokens": 128000, - "temperature": 0.2 - } - }, - "global": { - "logLevel": "info", - "debug": false, - "defaultSubtasks": 5, - "defaultPriority": "medium", - "projectName": "Taskmaster", - "ollamaBaseURL": "http://localhost:11434/api", - "userId": "1234567890", - "azureBaseURL": "https://your-endpoint.azure.com/" - } -} diff --git a/assets/AGENTS.md b/assets/AGENTS.md index 4e43ccf7..83f3f786 100644 --- a/assets/AGENTS.md +++ b/assets/AGENTS.md @@ -56,20 +56,24 @@ task-master generate # Update task markd ``` project/ -├── tasks/ -│ ├── tasks.json # Main task database -│ ├── task-1.md # Individual task files -│ └── task-2.md -├── scripts/ -│ ├── prd.txt # Product requirements -│ └── task-complexity-report.json +├── .taskmaster/ +│ ├── tasks/ # Task files directory +│ │ ├── tasks.json # Main task database +│ │ ├── task-1.md # Individual task files +│ │ └── task-2.md +│ ├── docs/ # Documentation directory +│ │ ├── prd.txt # Product requirements +│ ├── reports/ # Analysis reports directory +│ │ └── task-complexity-report.json +│ ├── templates/ # Template files +│ │ └── example_prd.txt # Example PRD template +│ └── config.json # AI models & settings ├── .claude/ -│ ├── settings.json # Claude Code configuration -│ └── commands/ # Custom slash commands -├── .taskmasterconfig # AI models & settings -├── .env # API keys -├── .mcp.json # MCP configuration -└── CLAUDE.md # This file - auto-loaded by Claude Code +│ ├── settings.json # Claude Code configuration +│ └── commands/ # Custom slash commands +├── .env # API keys +├── .mcp.json # MCP configuration +└── CLAUDE.md # This file - auto-loaded by Claude Code ``` ## MCP Integration @@ -384,7 +388,7 @@ These commands make AI calls and may take up to a minute: ### File Management - Never manually edit `tasks.json` - use commands instead -- Never manually edit `.taskmasterconfig` - use `task-master models` +- Never manually edit `.taskmaster/config.json` - use `task-master models` - Task markdown files in `tasks/` are auto-generated - Run `task-master generate` after manual changes to tasks.json diff --git a/assets/.taskmasterconfig b/assets/config.json similarity index 91% rename from assets/.taskmasterconfig rename to assets/config.json index 907f4290..8385534a 100644 --- a/assets/.taskmasterconfig +++ b/assets/config.json @@ -26,6 +26,7 @@ "defaultPriority": "medium", "projectName": "Taskmaster", "ollamaBaseURL": "http://localhost:11434/api", - "azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/" + "azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/", + "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com" } } diff --git a/assets/env.example b/assets/env.example index c7142879..41a8faec 100644 --- a/assets/env.example +++ b/assets/env.example @@ -5,5 +5,5 @@ OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI/Ope GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models. MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models. XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models. -AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmasterconfig). +AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmaster/config.json). OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication. \ No newline at end of file diff --git a/assets/scripts_README.md b/assets/scripts_README.md index 1e76856f..dea4c585 100644 --- a/assets/scripts_README.md +++ b/assets/scripts_README.md @@ -20,7 +20,7 @@ In an AI-driven development process—particularly with tools like [Cursor](http Task Master configuration is now managed through two primary methods: -1. **`.taskmasterconfig` File (Project Root - Primary)** +1. **`.taskmaster/config.json` File (Project Root - Primary)** - Stores AI model selections (`main`, `research`, `fallback`), model parameters (`maxTokens`, `temperature`), `logLevel`, `defaultSubtasks`, `defaultPriority`, `projectName`, etc. - Managed using the `task-master models --setup` command or the `models` MCP tool. @@ -192,7 +192,7 @@ Notes: ## AI Integration (Updated) - The script now uses a unified AI service layer (`ai-services-unified.js`). -- Model selection (e.g., Claude vs. Perplexity for `--research`) is determined by the configuration in `.taskmasterconfig` based on the requested `role` (`main` or `research`). +- Model selection (e.g., Claude vs. Perplexity for `--research`) is determined by the configuration in `.taskmaster/config.json` based on the requested `role` (`main` or `research`). - API keys are automatically resolved from your `.env` file (for CLI) or MCP session environment. - To use the research capabilities (e.g., `expand --research`), ensure you have: 1. Configured a model for the `research` role using `task-master models --setup` (Perplexity models are recommended). @@ -357,25 +357,25 @@ The output report structure is: ```json { - "meta": { - "generatedAt": "2023-06-15T12:34:56.789Z", - "tasksAnalyzed": 20, - "thresholdScore": 5, - "projectName": "Your Project Name", - "usedResearch": true - }, - "complexityAnalysis": [ - { - "taskId": 8, - "taskTitle": "Develop Implementation Drift Handling", - "complexityScore": 9.5, - "recommendedSubtasks": 6, - "expansionPrompt": "Create subtasks that handle detecting...", - "reasoning": "This task requires sophisticated logic...", - "expansionCommand": "task-master expand --id=8 --num=6 --prompt=\"Create subtasks...\" --research" - } - // More tasks sorted by complexity score (highest first) - ] + "meta": { + "generatedAt": "2023-06-15T12:34:56.789Z", + "tasksAnalyzed": 20, + "thresholdScore": 5, + "projectName": "Your Project Name", + "usedResearch": true + }, + "complexityAnalysis": [ + { + "taskId": 8, + "taskTitle": "Develop Implementation Drift Handling", + "complexityScore": 9.5, + "recommendedSubtasks": 6, + "expansionPrompt": "Create subtasks that handle detecting...", + "reasoning": "This task requires sophisticated logic...", + "expansionCommand": "task-master expand --id=8 --num=6 --prompt=\"Create subtasks...\" --research" + } + // More tasks sorted by complexity score (highest first) + ] } ``` diff --git a/mcp-server/src/core/direct-functions/complexity-report.js b/mcp-server/src/core/direct-functions/complexity-report.js index 5b34f678..2edfc7fa 100644 --- a/mcp-server/src/core/direct-functions/complexity-report.js +++ b/mcp-server/src/core/direct-functions/complexity-report.js @@ -28,8 +28,7 @@ export async function complexityReportDirect(args, log) { log.error('complexityReportDirect called without reportPath'); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' } }; } @@ -111,8 +110,7 @@ export async function complexityReportDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/expand-all-tasks.js b/mcp-server/src/core/direct-functions/expand-all-tasks.js index 6f9dc3cb..4d1a8a74 100644 --- a/mcp-server/src/core/direct-functions/expand-all-tasks.js +++ b/mcp-server/src/core/direct-functions/expand-all-tasks.js @@ -60,7 +60,8 @@ export async function expandAllTasksDirect(args, log, context = {}) { useResearch, additionalContext, forceFlag, - { session, mcpLog, projectRoot } + { session, mcpLog, projectRoot }, + 'json' ); // Core function now returns a summary object including the *aggregated* telemetryData diff --git a/mcp-server/src/core/direct-functions/expand-task.js b/mcp-server/src/core/direct-functions/expand-task.js index 1ba53337..6c98dd91 100644 --- a/mcp-server/src/core/direct-functions/expand-task.js +++ b/mcp-server/src/core/direct-functions/expand-task.js @@ -29,7 +29,7 @@ import { createLogWrapper } from '../../tools/utils.js'; * @param {Object} log - Logger object * @param {Object} context - Context object containing session * @param {Object} [context.session] - MCP Session object - * @returns {Promise} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function expandTaskDirect(args, log, context = {}) { const { session } = context; // Extract session @@ -54,8 +54,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -73,8 +72,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -105,8 +103,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'INVALID_TASKS_FILE', message: `No valid tasks found in ${tasksPath}. readJSON returned: ${JSON.stringify(data)}` - }, - fromCache: false + } }; } @@ -121,8 +118,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'TASK_NOT_FOUND', message: `Task with ID ${taskId} not found` - }, - fromCache: false + } }; } @@ -133,8 +129,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'TASK_COMPLETED', message: `Task ${taskId} is already marked as ${task.status} and cannot be expanded` - }, - fromCache: false + } }; } @@ -151,8 +146,7 @@ export async function expandTaskDirect(args, log, context = {}) { task, subtasksAdded: 0, hasExistingSubtasks - }, - fromCache: false + } }; } @@ -232,8 +226,7 @@ export async function expandTaskDirect(args, log, context = {}) { subtasksAdded, hasExistingSubtasks, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { // Make sure to restore normal logging even if there's an error @@ -245,8 +238,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'CORE_FUNCTION_ERROR', message: error.message || 'Failed to expand task' - }, - fromCache: false + } }; } } catch (error) { @@ -256,8 +248,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'CORE_FUNCTION_ERROR', message: error.message || 'Failed to expand task' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/generate-task-files.js b/mcp-server/src/core/direct-functions/generate-task-files.js index 8a88e0da..e9b61dcc 100644 --- a/mcp-server/src/core/direct-functions/generate-task-files.js +++ b/mcp-server/src/core/direct-functions/generate-task-files.js @@ -28,8 +28,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } if (!outputDir) { @@ -37,8 +36,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(`Error in generateTaskFiles: ${genError.message}`); return { success: false, - error: { code: 'GENERATE_FILES_ERROR', message: genError.message }, - fromCache: false + error: { code: 'GENERATE_FILES_ERROR', message: genError.message } }; } @@ -79,8 +76,7 @@ export async function generateTaskFilesDirect(args, log) { outputDir: resolvedOutputDir, taskFiles: 'Individual task files have been generated in the output directory' - }, - fromCache: false // This operation always modifies state and should never be cached + } }; } catch (error) { // Make sure to restore normal logging if an outer error occurs @@ -92,8 +88,7 @@ export async function generateTaskFilesDirect(args, log) { error: { code: 'GENERATE_TASKS_ERROR', message: error.message || 'Unknown error generating task files' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/initialize-project.js b/mcp-server/src/core/direct-functions/initialize-project.js index a382837c..bb736d75 100644 --- a/mcp-server/src/core/direct-functions/initialize-project.js +++ b/mcp-server/src/core/direct-functions/initialize-project.js @@ -41,8 +41,7 @@ export async function initializeProjectDirect(args, log, context = {}) { code: 'INVALID_TARGET_DIRECTORY', message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`, details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received - }, - fromCache: false + } }; } @@ -97,8 +96,8 @@ export async function initializeProjectDirect(args, log, context = {}) { } if (success) { - return { success: true, data: resultData, fromCache: false }; + return { success: true, data: resultData }; } else { - return { success: false, error: errorResult, fromCache: false }; + return { success: false, error: errorResult }; } } diff --git a/mcp-server/src/core/direct-functions/list-tasks.js b/mcp-server/src/core/direct-functions/list-tasks.js index 49aa42bf..2a0133a3 100644 --- a/mcp-server/src/core/direct-functions/list-tasks.js +++ b/mcp-server/src/core/direct-functions/list-tasks.js @@ -14,7 +14,7 @@ import { * * @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly). * @param {Object} log - Logger object. - * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }. + * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }. */ export async function listTasksDirect(args, log) { // Destructure the explicit tasksJsonPath from args @@ -27,8 +27,7 @@ export async function listTasksDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } diff --git a/mcp-server/src/core/direct-functions/next-task.js b/mcp-server/src/core/direct-functions/next-task.js index 3bc80d48..bb1f0e33 100644 --- a/mcp-server/src/core/direct-functions/next-task.js +++ b/mcp-server/src/core/direct-functions/next-task.js @@ -19,7 +19,7 @@ import { * @param {Object} args - Command arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {Object} log - Logger object - * @returns {Promise} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function nextTaskDirect(args, log) { // Destructure expected args @@ -32,8 +32,7 @@ export async function nextTaskDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -121,7 +120,7 @@ export async function nextTaskDirect(args, log) { // Use the caching utility try { const result = await coreNextTaskAction(); - log.info(`nextTaskDirect completed.`); + log.info('nextTaskDirect completed.'); return result; } catch (error) { log.error(`Unexpected error during nextTask: ${error.message}`); @@ -130,8 +129,7 @@ export async function nextTaskDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/parse-prd.js b/mcp-server/src/core/direct-functions/parse-prd.js index 7ae84722..ffe74cd2 100644 --- a/mcp-server/src/core/direct-functions/parse-prd.js +++ b/mcp-server/src/core/direct-functions/parse-prd.js @@ -77,7 +77,7 @@ export async function parsePRDDirect(args, log, context = {}) { ? path.isAbsolute(outputArg) ? outputArg : path.resolve(projectRoot, outputArg) - : resolveProjectPath(TASKMASTER_TASKS_FILE, session) || + : resolveProjectPath(TASKMASTER_TASKS_FILE, args) || path.resolve(projectRoot, TASKMASTER_TASKS_FILE); // Check if input file exists diff --git a/mcp-server/src/core/direct-functions/remove-task.js b/mcp-server/src/core/direct-functions/remove-task.js index 2fb17099..26684817 100644 --- a/mcp-server/src/core/direct-functions/remove-task.js +++ b/mcp-server/src/core/direct-functions/remove-task.js @@ -21,7 +21,7 @@ import { * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple). * @param {Object} log - Logger object - * @returns {Promise} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false } + * @returns {Promise} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function removeTaskDirect(args, log) { // Destructure expected args @@ -35,8 +35,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -48,8 +47,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -68,8 +66,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INVALID_TASKS_FILE', message: `No valid tasks found in ${tasksJsonPath}` - }, - fromCache: false + } }; } @@ -83,8 +80,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INVALID_TASK_ID', message: `The following tasks were not found: ${invalidTasks.join(', ')}` - }, - fromCache: false + } }; } @@ -133,8 +129,7 @@ export async function removeTaskDirect(args, log) { details: failedRemovals .map((r) => `${r.taskId}: ${r.error}`) .join('; ') - }, - fromCache: false + } }; } @@ -147,8 +142,7 @@ export async function removeTaskDirect(args, log) { failed: failedRemovals.length, results: results, tasksPath: tasksJsonPath - }, - fromCache: false + } }; } catch (error) { // Ensure silent mode is disabled even if an outer error occurs @@ -161,8 +155,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/set-task-status.js b/mcp-server/src/core/direct-functions/set-task-status.js index 9711b771..ae9dddf9 100644 --- a/mcp-server/src/core/direct-functions/set-task-status.js +++ b/mcp-server/src/core/direct-functions/set-task-status.js @@ -29,8 +29,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -41,8 +40,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_TASK_ID', message: errorMessage } }; } @@ -52,8 +50,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_STATUS', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_STATUS', message: errorMessage } }; } @@ -82,8 +79,7 @@ export async function setTaskStatusDirect(args, log) { taskId, status: newStatus, tasksPath: tasksPath // Return the path used - }, - fromCache: false // This operation always modifies state and should never be cached + } }; // If the task was completed, attempt to fetch the next task @@ -126,8 +122,7 @@ export async function setTaskStatusDirect(args, log) { error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' - }, - fromCache: false + } }; } finally { // ALWAYS restore normal logging in finally block @@ -145,8 +140,7 @@ export async function setTaskStatusDirect(args, log) { error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/update-subtask-by-id.js b/mcp-server/src/core/direct-functions/update-subtask-by-id.js index f2c433cf..0df637d9 100644 --- a/mcp-server/src/core/direct-functions/update-subtask-by-id.js +++ b/mcp-server/src/core/direct-functions/update-subtask-by-id.js @@ -42,8 +42,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -54,8 +53,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_PROMPT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_PROMPT', message: errorMessage } }; } @@ -77,8 +74,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { log.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage } }; } @@ -88,8 +84,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { log.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage } }; } @@ -128,8 +123,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(message); return { success: false, - error: { code: 'SUBTASK_NOT_FOUND', message: message }, - fromCache: false + error: { code: 'SUBTASK_NOT_FOUND', message: message } }; } @@ -146,8 +140,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { tasksPath, useResearch, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { logWrapper.error(`Error updating subtask by ID: ${error.message}`); @@ -156,8 +149,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { error: { code: 'UPDATE_SUBTASK_CORE_ERROR', message: error.message || 'Unknown error updating subtask' - }, - fromCache: false + } }; } finally { if (!wasSilent && isSilentMode()) { @@ -174,8 +166,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { error: { code: 'DIRECT_FUNCTION_SETUP_ERROR', message: error.message || 'Unknown setup error' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/update-task-by-id.js b/mcp-server/src/core/direct-functions/update-task-by-id.js index 459ff0bc..1d3d8753 100644 --- a/mcp-server/src/core/direct-functions/update-task-by-id.js +++ b/mcp-server/src/core/direct-functions/update-task-by-id.js @@ -42,8 +42,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -54,8 +53,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_TASK_ID', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_PROMPT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_PROMPT', message: errorMessage } }; } @@ -84,8 +81,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'INVALID_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_TASK_ID', message: errorMessage } }; } } @@ -137,8 +133,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { taskId: taskId, updated: false, telemetryData: coreResult?.telemetryData - }, - fromCache: false + } }; } @@ -155,8 +150,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { updated: true, updatedTask: coreResult.updatedTask, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { logWrapper.error(`Error updating task by ID: ${error.message}`); @@ -165,8 +159,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { error: { code: 'UPDATE_TASK_CORE_ERROR', message: error.message || 'Unknown error updating task' - }, - fromCache: false + } }; } finally { if (!wasSilent && isSilentMode()) { @@ -181,8 +174,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { error: { code: 'DIRECT_FUNCTION_SETUP_ERROR', message: error.message || 'Unknown setup error' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/utils/path-utils.js b/mcp-server/src/core/utils/path-utils.js index 8126276f..5971a5df 100644 --- a/mcp-server/src/core/utils/path-utils.js +++ b/mcp-server/src/core/utils/path-utils.js @@ -1,5 +1,4 @@ import path from 'path'; -import fs from 'fs'; import { findTasksPath as coreFindTasksPath, findPRDPath as coreFindPrdPath, @@ -13,22 +12,22 @@ import { PROJECT_MARKERS } from '../../../../src/constants/paths.js'; * This module handles session-specific path resolution for the MCP server */ +/** + * Silent logger for MCP context to prevent console output + */ +const silentLogger = { + info: () => {}, + warn: () => {}, + error: () => {}, + debug: () => {}, + success: () => {} +}; + /** * Cache for last found project root to improve performance */ export const lastFoundProjectRoot = null; -/** - * Find tasks.json file with MCP support - * @param {string} [explicitPath] - Explicit path to tasks.json (highest priority) - * @param {Object} [args] - Arguments object for context - * @param {Object} [log] - Logger object to prevent console logging - * @returns {string|null} - Resolved path to tasks.json or null if not found - */ -export function findTasksPathCore(explicitPath, args = null, log = null) { - return coreFindTasksPath(explicitPath, args, log); -} - /** * Find PRD file with MCP support * @param {string} [explicitPath] - Explicit path to PRD file (highest priority) @@ -36,25 +35,10 @@ export function findTasksPathCore(explicitPath, args = null, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function findPrdPath(explicitPath, args = null, log = null) { +export function findPrdPath(explicitPath, args = null, log = silentLogger) { return coreFindPrdPath(explicitPath, args, log); } -/** - * Find complexity report file with MCP support - * @param {string} [explicitPath] - Explicit path to complexity report (highest priority) - * @param {Object} [args] - Arguments object for context - * @param {Object} [log] - Logger object to prevent console logging - * @returns {string|null} - Resolved path to complexity report or null if not found - */ -export function findComplexityReportPathCore( - explicitPath, - args = null, - log = null -) { - return coreFindComplexityReportPath(explicitPath, args, log); -} - /** * Resolve tasks.json path from arguments * Prioritizes explicit path parameter, then uses fallback logic @@ -62,7 +46,7 @@ export function findComplexityReportPathCore( * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to tasks.json or null if not found */ -export function resolveTasksPath(args, log = null) { +export function resolveTasksPath(args, log = silentLogger) { // Get explicit path from args.file if provided const explicitPath = args?.file; const projectRoot = args?.projectRoot; @@ -92,7 +76,7 @@ export function resolveTasksPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function resolvePrdPath(args, log = null) { +export function resolvePrdPath(args, log = silentLogger) { // Get explicit path from args.input if provided const explicitPath = args?.input; const projectRoot = args?.projectRoot; @@ -122,7 +106,7 @@ export function resolvePrdPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to complexity report or null if not found */ -export function resolveComplexityReportPath(args, log = null) { +export function resolveComplexityReportPath(args, log = silentLogger) { // Get explicit path from args.complexityReport if provided const explicitPath = args?.complexityReport; const projectRoot = args?.projectRoot; @@ -184,7 +168,7 @@ export function findProjectRoot(startDir) { * @param {Object} [log] - Log function to prevent console logging * @returns {string|null} - Resolved path to tasks.json or null if not found */ -export function findTasksPath(args, log = null) { +export function findTasksPath(args, log = silentLogger) { return resolveTasksPath(args, log); } @@ -194,7 +178,7 @@ export function findTasksPath(args, log = null) { * @param {Object} [log] - Log function to prevent console logging * @returns {string|null} - Resolved path to complexity report or null if not found */ -export function findComplexityReportPath(args, log = null) { +export function findComplexityReportPath(args, log = silentLogger) { return resolveComplexityReportPath(args, log); } @@ -205,7 +189,7 @@ export function findComplexityReportPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function findPRDPath(explicitPath, args = null, log = null) { +export function findPRDPath(explicitPath, args = null, log = silentLogger) { return findPrdPath(explicitPath, args, log); } diff --git a/mcp-server/src/tools/complexity-report.js b/mcp-server/src/tools/complexity-report.js index 1e51e7ca..dbc03a86 100644 --- a/mcp-server/src/tools/complexity-report.js +++ b/mcp-server/src/tools/complexity-report.js @@ -51,9 +51,7 @@ export function registerComplexityReportTool(server) { ); if (result.success) { - log.info( - `Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}` - ); + log.info('Successfully retrieved complexity report'); } else { log.error( `Failed to retrieve complexity report: ${result.error.message}` diff --git a/mcp-server/src/tools/get-task.js b/mcp-server/src/tools/get-task.js index 80593d57..b723347e 100644 --- a/mcp-server/src/tools/get-task.js +++ b/mcp-server/src/tools/get-task.js @@ -116,9 +116,7 @@ export function registerShowTaskTool(server) { ); if (result.success) { - log.info( - `Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}` - ); + log.info(`Successfully retrieved task details for ID: ${args.id}`); } else { log.error(`Failed to get task: ${result.error.message}`); } diff --git a/mcp-server/src/tools/get-tasks.js b/mcp-server/src/tools/get-tasks.js index bc2fe784..b8618268 100644 --- a/mcp-server/src/tools/get-tasks.js +++ b/mcp-server/src/tools/get-tasks.js @@ -58,7 +58,7 @@ export function registerListTasksTool(server) { // Resolve the path to tasks.json using new path utilities let tasksJsonPath; try { - tasksJsonPath = resolveTasksPath(args, session); + tasksJsonPath = resolveTasksPath(args, log); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( @@ -87,7 +87,7 @@ export function registerListTasksTool(server) { ); log.info( - `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}` + `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks` ); return handleApiResult(result, log, 'Error getting tasks'); } catch (error) { diff --git a/mcp-server/src/tools/models.js b/mcp-server/src/tools/models.js index 3267ee65..3eb71877 100644 --- a/mcp-server/src/tools/models.js +++ b/mcp-server/src/tools/models.js @@ -47,7 +47,6 @@ export function registerModelsTool(server) { ), projectRoot: z .string() - .optional() .describe('The directory of the project. Must be an absolute path.'), openrouter: z .boolean() diff --git a/mcp-server/src/tools/move-task.js b/mcp-server/src/tools/move-task.js index 6744b4ba..98c9a864 100644 --- a/mcp-server/src/tools/move-task.js +++ b/mcp-server/src/tools/move-task.js @@ -34,7 +34,6 @@ export function registerMoveTaskTool(server) { file: z.string().optional().describe('Custom path to tasks.json file'), projectRoot: z .string() - .optional() .describe( 'Root directory of the project (typically derived from session)' ) @@ -95,13 +94,16 @@ export function registerMoveTaskTool(server) { } } - return { - success: true, - data: { - moves: results, - message: `Successfully moved ${results.length} tasks` - } - }; + return handleApiResult( + { + success: true, + data: { + moves: results, + message: `Successfully moved ${results.length} tasks` + } + }, + log + ); } else { // Moving a single task return handleApiResult( diff --git a/mcp-server/src/tools/parse-prd.js b/mcp-server/src/tools/parse-prd.js index 18c6b7f7..421402cf 100644 --- a/mcp-server/src/tools/parse-prd.js +++ b/mcp-server/src/tools/parse-prd.js @@ -32,7 +32,6 @@ export function registerParsePRDTool(server) { .describe('Absolute path to the PRD document file (.txt, .md, etc.)'), projectRoot: z .string() - .optional() .describe('The directory of the project. Must be an absolute path.'), output: z .string() diff --git a/mcp-server/src/tools/utils.js b/mcp-server/src/tools/utils.js index b0cb9abf..b8e00914 100644 --- a/mcp-server/src/tools/utils.js +++ b/mcp-server/src/tools/utils.js @@ -7,6 +7,7 @@ import { spawnSync } from 'child_process'; import path from 'path'; import fs from 'fs'; import { contextManager } from '../core/context-manager.js'; // Import the singleton +import { fileURLToPath } from 'url'; // Import path utilities to ensure consistent path resolution import { @@ -14,6 +15,50 @@ import { PROJECT_MARKERS } from '../core/utils/path-utils.js'; +const __filename = fileURLToPath(import.meta.url); + +// Cache for version info to avoid repeated file reads +let cachedVersionInfo = null; + +/** + * Get version information from package.json + * @returns {Object} Version information + */ +function getVersionInfo() { + // Return cached version if available + if (cachedVersionInfo) { + return cachedVersionInfo; + } + + try { + // Navigate to the project root from the tools directory + const packageJsonPath = path.join( + path.dirname(__filename), + '../../../package.json' + ); + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + cachedVersionInfo = { + version: packageJson.version, + name: packageJson.name + }; + return cachedVersionInfo; + } + cachedVersionInfo = { + version: 'unknown', + name: 'task-master-ai' + }; + return cachedVersionInfo; + } catch (error) { + // Fallback version info if package.json can't be read + cachedVersionInfo = { + version: 'unknown', + name: 'task-master-ai' + }; + return cachedVersionInfo; + } +} + /** * Get normalized project root path * @param {string|undefined} projectRootRaw - Raw project root from arguments @@ -199,17 +244,19 @@ function getProjectRootFromSession(session, log) { * @param {Function} processFunction - Optional function to process successful result data * @returns {Object} - Standardized MCP response object */ -function handleApiResult( +async function handleApiResult( result, log, errorPrefix = 'API error', processFunction = processMCPResponseData ) { + // Get version info for every response + const versionInfo = getVersionInfo(); + if (!result.success) { const errorMsg = result.error?.message || `Unknown ${errorPrefix}`; - // Include cache status in error logs - log.error(`${errorPrefix}: ${errorMsg}. From cache: ${result.fromCache}`); // Keep logging cache status on error - return createErrorResponse(errorMsg); + log.error(`${errorPrefix}: ${errorMsg}`); + return createErrorResponse(errorMsg, versionInfo); } // Process the result data if needed @@ -217,16 +264,14 @@ function handleApiResult( ? processFunction(result.data) : result.data; - // Log success including cache status - log.info(`Successfully completed operation. From cache: ${result.fromCache}`); // Add success log with cache status + log.info('Successfully completed operation'); - // Create the response payload including the fromCache flag + // Create the response payload including version info const responsePayload = { - fromCache: result.fromCache, // Get the flag from the original 'result' - data: processedData // Nest the processed data under a 'data' key + data: processedData, + version: versionInfo }; - // Pass this combined payload to createContentResponse return createContentResponse(responsePayload); } @@ -320,8 +365,8 @@ function executeTaskMasterCommand( * @param {Function} options.actionFn - The async function to execute if the cache misses. * Should return an object like { success: boolean, data?: any, error?: { code: string, message: string } }. * @param {Object} options.log - The logger instance. - * @returns {Promise} - An object containing the result, indicating if it was from cache. - * Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - An object containing the result. + * Format: { success: boolean, data?: any, error?: { code: string, message: string } } */ async function getCachedOrExecute({ cacheKey, actionFn, log }) { // Check cache first @@ -329,11 +374,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { if (cachedResult !== undefined) { log.info(`Cache hit for key: ${cacheKey}`); - // Return the cached data in the same structure as a fresh result - return { - ...cachedResult, // Spread the cached result to maintain its structure - fromCache: true // Just add the fromCache flag - }; + return cachedResult; } log.info(`Cache miss for key: ${cacheKey}. Executing action function.`); @@ -341,12 +382,10 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { // Execute the action function if cache missed const result = await actionFn(); - // If the action was successful, cache the result (but without fromCache flag) + // If the action was successful, cache the result if (result.success && result.data !== undefined) { log.info(`Action successful. Caching result for key: ${cacheKey}`); - // Cache the entire result structure (minus the fromCache flag) - const { fromCache, ...resultToCache } = result; - contextManager.setCachedData(cacheKey, resultToCache); + contextManager.setCachedData(cacheKey, result); } else if (!result.success) { log.warn( `Action failed for cache key ${cacheKey}. Result not cached. Error: ${result.error?.message}` @@ -357,11 +396,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { ); } - // Return the fresh result, indicating it wasn't from cache - return { - ...result, - fromCache: false - }; + return result; } /** @@ -460,14 +495,22 @@ function createContentResponse(content) { /** * Creates error response for tools * @param {string} errorMessage - Error message to include in response + * @param {Object} [versionInfo] - Optional version information object * @returns {Object} - Error content response object in FastMCP format */ -function createErrorResponse(errorMessage) { +function createErrorResponse(errorMessage, versionInfo) { + // Provide fallback version info if not provided + if (!versionInfo) { + versionInfo = getVersionInfo(); + } + return { content: [ { type: 'text', - text: `Error: ${errorMessage}` + text: `Error: ${errorMessage} +Version: ${versionInfo.version} +Name: ${versionInfo.name}` } ], isError: true diff --git a/scripts/init.js b/scripts/init.js index 4cb3b272..5db49c35 100755 --- a/scripts/init.js +++ b/scripts/init.js @@ -509,9 +509,9 @@ function createProjectStructure(addAliases, dryRun, options) { replacements ); - // Copy .taskmasterconfig with project name to NEW location + // Copy config.json with project name to NEW location copyTemplateFile( - '.taskmasterconfig', + 'config.json', path.join(targetDir, TASKMASTER_CONFIG_FILE), { ...replacements diff --git a/scripts/modules/ai-services-unified.js b/scripts/modules/ai-services-unified.js index 59cde801..ca702701 100644 --- a/scripts/modules/ai-services-unified.js +++ b/scripts/modules/ai-services-unified.js @@ -22,6 +22,7 @@ import { isApiKeySet, getOllamaBaseURL, getAzureBaseURL, + getBedrockBaseURL, getVertexProjectId, getVertexLocation } from './config-manager.js'; @@ -410,6 +411,10 @@ async function _unifiedServiceRunner(serviceType, params) { // For Ollama, use the global Ollama base URL if role-specific URL is not configured baseURL = getOllamaBaseURL(effectiveProjectRoot); log('debug', `Using global Ollama base URL: ${baseURL}`); + } else if (providerName?.toLowerCase() === 'bedrock' && !baseURL) { + // For Bedrock, use the global Bedrock base URL if role-specific URL is not configured + baseURL = getBedrockBaseURL(effectiveProjectRoot); + log('debug', `Using global Bedrock base URL: ${baseURL}`); } // Get AI parameters for the current role diff --git a/scripts/modules/config-manager.js b/scripts/modules/config-manager.js index 1ca4734b..c4a52f70 100644 --- a/scripts/modules/config-manager.js +++ b/scripts/modules/config-manager.js @@ -61,7 +61,8 @@ const DEFAULTS = { defaultSubtasks: 5, defaultPriority: 'medium', projectName: 'Task Master', - ollamaBaseURL: 'http://localhost:11434/api' + ollamaBaseURL: 'http://localhost:11434/api', + bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com' } }; @@ -382,6 +383,11 @@ function getAzureBaseURL(explicitRoot = null) { return getGlobalConfig(explicitRoot).azureBaseURL; } +function getBedrockBaseURL(explicitRoot = null) { + // Directly return value from config + return getGlobalConfig(explicitRoot).bedrockBaseURL; +} + /** * Gets the Google Cloud project ID for Vertex AI from configuration * @param {string|null} explicitRoot - Optional explicit path to the project root. @@ -779,6 +785,7 @@ export { getProjectName, getOllamaBaseURL, getAzureBaseURL, + getBedrockBaseURL, getParametersForRole, getUserId, // API Key Checkers (still relevant) diff --git a/scripts/modules/task-manager/models.js b/scripts/modules/task-manager/models.js index f7aa57f0..8416c616 100644 --- a/scripts/modules/task-manager/models.js +++ b/scripts/modules/task-manager/models.js @@ -482,6 +482,11 @@ async function setModel(role, modelId, options = {}) { `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` ); } + } else if (providerHint === 'bedrock') { + // Set provider without model validation since Bedrock models are managed by AWS + determinedProvider = 'bedrock'; + warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`; + report('warn', warningMessage); } else { // Invalid provider hint - should not happen throw new Error(`Invalid provider hint received: ${providerHint}`); diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index f0bea64a..c17d6b80 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -24,7 +24,10 @@ import { } from './task-manager.js'; import { getProjectName, getDefaultSubtasks } from './config-manager.js'; import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; -import { TASKMASTER_TASKS_FILE } from '../../src/constants/paths.js'; +import { + TASKMASTER_CONFIG_FILE, + TASKMASTER_TASKS_FILE +} from '../../src/constants/paths.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; // Create a color gradient for the banner @@ -687,7 +690,7 @@ function displayHelp() { configTable.push( [ - `${chalk.yellow('.taskmasterconfig')}${chalk.reset('')}`, + `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`, `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`, `${chalk.dim('Managed by models cmd')}${chalk.reset('')}` ], @@ -1851,7 +1854,7 @@ function displayApiKeyStatus(statusReport) { console.log(table.toString()); console.log( chalk.gray( - ' Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in .taskmasterconfig.' + ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` ) ); } diff --git a/scripts/modules/utils.js b/scripts/modules/utils.js index 33a1c724..ab6fe422 100644 --- a/scripts/modules/utils.js +++ b/scripts/modules/utils.js @@ -155,8 +155,17 @@ function log(level, ...args) { return; } - // Get log level dynamically from config-manager - const configLevel = getLogLevel() || 'info'; // Use getter + // GUARD: Prevent circular dependency during config loading + // Use a simple fallback log level instead of calling getLogLevel() + let configLevel = 'info'; // Default fallback + try { + // Only try to get config level if we're not in the middle of config loading + configLevel = getLogLevel() || 'info'; + } catch (error) { + // If getLogLevel() fails (likely due to circular dependency), + // use default 'info' level and continue + configLevel = 'info'; + } // Use text prefixes instead of emojis const prefixes = { @@ -190,8 +199,17 @@ function log(level, ...args) { * @returns {Object|null} Parsed JSON data or null if error occurs */ function readJSON(filepath) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { const rawData = fs.readFileSync(filepath, 'utf8'); return JSON.parse(rawData); @@ -212,8 +230,17 @@ function readJSON(filepath) { * @param {Object} data - Data to write */ function writeJSON(filepath, data) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { const dir = path.dirname(filepath); if (!fs.existsSync(dir)) { @@ -246,8 +273,17 @@ function sanitizePrompt(prompt) { * @returns {Object|null} The parsed complexity report or null if not found */ function readComplexityReport(customPath = null) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { let reportPath; if (customPath) { diff --git a/src/utils/logger-utils.js b/src/utils/logger-utils.js new file mode 100644 index 00000000..70dcfaf3 --- /dev/null +++ b/src/utils/logger-utils.js @@ -0,0 +1,31 @@ +/** + * Logger utility functions for Task Master + * Provides standardized logging patterns for both CLI and utility contexts + */ + +import { log as utilLog } from '../../scripts/modules/utils.js'; + +/** + * Creates a standard logger object that wraps the utility log function + * This provides a consistent logger interface across different parts of the application + * @returns {Object} A logger object with standard logging methods (info, warn, error, debug, success) + */ +export function createStandardLogger() { + return { + info: (msg, ...args) => utilLog('info', msg, ...args), + warn: (msg, ...args) => utilLog('warn', msg, ...args), + error: (msg, ...args) => utilLog('error', msg, ...args), + debug: (msg, ...args) => utilLog('debug', msg, ...args), + success: (msg, ...args) => utilLog('success', msg, ...args) + }; +} + +/** + * Creates a logger using either the provided logger or a default standard logger + * This is the recommended pattern for functions that accept an optional logger parameter + * @param {Object|null} providedLogger - Optional logger object passed from caller + * @returns {Object} A logger object with standard logging methods + */ +export function getLoggerOrDefault(providedLogger = null) { + return providedLogger || createStandardLogger(); +} diff --git a/src/utils/path-utils.js b/src/utils/path-utils.js index a21bfe4e..c4f22cb0 100644 --- a/src/utils/path-utils.js +++ b/src/utils/path-utils.js @@ -14,6 +14,7 @@ import { TASKMASTER_CONFIG_FILE, LEGACY_CONFIG_FILE } from '../constants/paths.js'; +import { getLoggerOrDefault } from './logger-utils.js'; /** * Find the project root directory by looking for project markers @@ -59,7 +60,8 @@ export function findProjectRoot(startDir = process.cwd()) { * @returns {string|null} - Resolved tasks.json path or null if not found */ export function findTasksPath(explicitPath = null, args = null, log = null) { - const logger = log || console; + // Use the passed logger if available, otherwise use the default logger + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it (highest priority) if (explicitPath) { @@ -130,7 +132,7 @@ export function findTasksPath(explicitPath = null, args = null, log = null) { * @returns {string|null} - Resolved PRD document path or null if not found */ export function findPRDPath(explicitPath = null, args = null, log = null) { - const logger = log || console; + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it (highest priority) if (explicitPath) { @@ -199,7 +201,7 @@ export function findComplexityReportPath( args = null, log = null ) { - const logger = log || console; + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it (highest priority) if (explicitPath) { @@ -268,7 +270,7 @@ export function resolveTasksOutputPath( args = null, log = null ) { - const logger = log || console; + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it if (explicitPath) { @@ -309,7 +311,7 @@ export function resolveComplexityReportOutputPath( args = null, log = null ) { - const logger = log || console; + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it if (explicitPath) { @@ -348,7 +350,7 @@ export function resolveComplexityReportOutputPath( * @returns {string|null} - Resolved config file path or null if not found */ export function findConfigPath(explicitPath = null, args = null, log = null) { - const logger = log || console; + const logger = getLoggerOrDefault(log); // 1. If explicit path is provided, use it (highest priority) if (explicitPath) { @@ -382,12 +384,6 @@ export function findConfigPath(explicitPath = null, args = null, log = null) { for (const configPath of possiblePaths) { if (fs.existsSync(configPath)) { - try { - logger.info?.(`Found config file at: ${configPath}`); - } catch (error) { - // Silently handle logging errors during testing - } - // Issue deprecation warning for legacy paths if (configPath?.endsWith(LEGACY_CONFIG_FILE)) { logger.warn?.( diff --git a/tests/integration/mcp-server/direct-functions.test.js b/tests/integration/mcp-server/direct-functions.test.js index d7c18822..640df127 100644 --- a/tests/integration/mcp-server/direct-functions.test.js +++ b/tests/integration/mcp-server/direct-functions.test.js @@ -253,8 +253,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'FILE_NOT_FOUND_ERROR', message: 'Tasks file not found' - }, - fromCache: false + } }; } @@ -288,8 +287,7 @@ describe('MCP Server Direct Functions', () => { .length, pending: tasksData.filter((t) => t.status === 'pending').length } - }, - fromCache: false + } }; } @@ -305,8 +303,7 @@ describe('MCP Server Direct Functions', () => { total: tasksData.length, filtered: filteredTasks.length } - }, - fromCache: false + } }; } @@ -320,8 +317,7 @@ describe('MCP Server Direct Functions', () => { stats: { total: tasksData.length } - }, - fromCache: false + } }; } @@ -441,8 +437,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -454,8 +449,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'TASK_NOT_FOUND', message: `Task with ID ${args.id} not found` - }, - fromCache: false + } }; } @@ -469,8 +463,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'TASK_COMPLETED', message: `Task ${args.id} is already marked as done and cannot be expanded` - }, - fromCache: false + } }; } @@ -495,8 +488,7 @@ describe('MCP Server Direct Functions', () => { task: expandedTask, subtasksAdded: expandedTask.subtasks.length, hasExistingSubtasks: false - }, - fromCache: false + } }; } diff --git a/tests/unit/ai-services-unified.test.js b/tests/unit/ai-services-unified.test.js index 36da3756..10d094de 100644 --- a/tests/unit/ai-services-unified.test.js +++ b/tests/unit/ai-services-unified.test.js @@ -43,6 +43,7 @@ const mockGetBaseUrlForRole = jest.fn(); const mockGetAllProviders = jest.fn(); const mockGetOllamaBaseURL = jest.fn(); const mockGetAzureBaseURL = jest.fn(); +const mockGetBedrockBaseURL = jest.fn(); const mockGetVertexProjectId = jest.fn(); const mockGetVertexLocation = jest.fn(); const mockGetAvailableModels = jest.fn(); @@ -113,6 +114,7 @@ jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({ getAllProviders: mockGetAllProviders, getOllamaBaseURL: mockGetOllamaBaseURL, getAzureBaseURL: mockGetAzureBaseURL, + getBedrockBaseURL: mockGetBedrockBaseURL, getVertexProjectId: mockGetVertexProjectId, getVertexLocation: mockGetVertexLocation, getMcpApiKeyStatus: mockGetMcpApiKeyStatus diff --git a/tests/unit/config-manager.test.js b/tests/unit/config-manager.test.js index 313ed32b..95686903 100644 --- a/tests/unit/config-manager.test.js +++ b/tests/unit/config-manager.test.js @@ -139,7 +139,8 @@ const DEFAULT_CONFIG = { defaultSubtasks: 5, defaultPriority: 'medium', projectName: 'Task Master', - ollamaBaseURL: 'http://localhost:11434/api' + ollamaBaseURL: 'http://localhost:11434/api', + bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com' } }; diff --git a/tests/unit/scripts/modules/task-manager/analyze-task-complexity.test.js b/tests/unit/scripts/modules/task-manager/analyze-task-complexity.test.js index 8902e61c..a555a488 100644 --- a/tests/unit/scripts/modules/task-manager/analyze-task-complexity.test.js +++ b/tests/unit/scripts/modules/task-manager/analyze-task-complexity.test.js @@ -127,6 +127,7 @@ jest.unstable_mockModule( getProjectName: jest.fn(() => 'Test Project'), getOllamaBaseURL: jest.fn(() => 'http://localhost:11434/api'), getAzureBaseURL: jest.fn(() => undefined), + getBedrockBaseURL: jest.fn(() => undefined), getParametersForRole: jest.fn(() => ({ maxTokens: 4000, temperature: 0.7