From 4a9f6cd5f5c8f9b4d6d2316eddd5cde16e69b686 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Mon, 21 Apr 2025 21:30:12 -0400 Subject: [PATCH] refactor: Standardize configuration and environment variable access This commit centralizes configuration and environment variable access across various modules by consistently utilizing getters from scripts/modules/config-manager.js. This replaces direct access to process.env and the global CONFIG object, leading to improved consistency, maintainability, testability, and better handling of session-specific configurations within the MCP context. Key changes include: - Centralized Getters: Replaced numerous instances of process.env.* and CONFIG.* with corresponding getter functions (e.g., getLogLevel, getMainModelId, getResearchMaxTokens, getMainTemperature, isApiKeySet, getDebugFlag, getDefaultSubtasks). - Session Awareness: Ensured that the session object is passed to config getters where necessary, particularly within AI service calls (ai-services.js, add-task.js) and error handling (ai-services.js), allowing for session-specific environment overrides. - API Key Checks: Standardized API key availability checks using isApiKeySet() instead of directly checking process.env.* (e.g., for Perplexity in commands.js and ai-services.js). - Client Instantiation Cleanup: Removed now-redundant/obsolete local client instantiation functions (getAnthropicClient, getPerplexityClient) from ai-services.js and the global Anthropic client initialization from dependency-manager.js. Client creation should now rely on the config manager and factory patterns. - Consistent Debug Flag Usage: Standardized calls to getDebugFlag() in commands.js, removing potentially unnecessary null arguments. - Accurate Progress Calculation: Updated AI stream progress reporting (ai-services.js, add-task.js) to use getMainMaxTokens(session) for more accurate calculations. - Minor Cleanup: Removed unused import from scripts/modules/commands.js. Specific module updates: - : - Uses getLogLevel() instead of process.env.LOG_LEVEL. - : - Replaced direct env/config access for model IDs, tokens, temperature, API keys, and default subtasks with appropriate getters. - Passed session to handleClaudeError. - Removed local getPerplexityClient and getAnthropicClient functions. - Updated progress calculations to use getMainMaxTokens(session). - : - Uses isApiKeySet('perplexity') for API key checks. - Uses getDebugFlag() consistently for debug checks. - Removed unused import. - : - Removed global Anthropic client initialization. - : - Uses config getters (getResearch..., getMain...) for Perplexity and Claude API call parameters, preserving customEnv override logic. This refactoring also resolves a potential SyntaxError: Identifier 'getPerplexityClient' has already been declared by removing the duplicated/obsolete function definition previously present in ai-services.js. --- mcp-server/src/logger.js | 7 +- scripts/modules/ai-services.js | 103 ++++---------- scripts/modules/commands.js | 16 +-- scripts/modules/dependency-manager.js | 11 +- scripts/modules/task-manager/add-task.js | 44 +++--- .../task-manager/analyze-task-complexity.js | 133 +++++++++++++++--- scripts/modules/task-manager/expand-task.js | 15 +- .../task-manager/get-subtasks-from-ai.js | 25 ++-- .../modules/task-manager/set-task-status.js | 3 +- .../task-manager/update-subtask-by-id.js | 44 +++--- .../modules/task-manager/update-task-by-id.js | 45 +++--- scripts/modules/task-manager/update-tasks.js | 49 +++---- scripts/modules/ui.js | 27 ++-- 13 files changed, 284 insertions(+), 238 deletions(-) diff --git a/mcp-server/src/logger.js b/mcp-server/src/logger.js index 63e2a865..cbd10bb8 100644 --- a/mcp-server/src/logger.js +++ b/mcp-server/src/logger.js @@ -1,5 +1,6 @@ import chalk from 'chalk'; import { isSilentMode } from '../../scripts/modules/utils.js'; +import { getLogLevel } from '../../scripts/modules/config-manager.js'; // Define log levels const LOG_LEVELS = { @@ -10,10 +11,8 @@ const LOG_LEVELS = { success: 4 }; -// Get log level from environment or default to info -const LOG_LEVEL = process.env.LOG_LEVEL - ? (LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()] ?? LOG_LEVELS.info) - : LOG_LEVELS.info; +// Get log level from config manager or default to info +const LOG_LEVEL = LOG_LEVELS[getLogLevel().toLowerCase()] ?? LOG_LEVELS.info; /** * Logs a message with the specified level diff --git a/scripts/modules/ai-services.js b/scripts/modules/ai-services.js index 17392d68..cae70a13 100644 --- a/scripts/modules/ai-services.js +++ b/scripts/modules/ai-services.js @@ -23,34 +23,14 @@ import { getDebugFlag, getResearchModelId, getResearchMaxTokens, - getResearchTemperature + getResearchTemperature, + getDefaultSubtasks, + isApiKeySet } from './config-manager.js'; // Load environment variables dotenv.config(); -/** - * Get or initialize the Perplexity client - * @param {object|null} [session=null] - Optional MCP session object. - * @returns {OpenAI} Perplexity client - */ -function getPerplexityClient(session = null) { - // Use resolveEnvVariable to get the key - const apiKey = resolveEnvVariable('PERPLEXITY_API_KEY', session); - if (!apiKey) { - throw new Error( - 'PERPLEXITY_API_KEY environment variable is missing. Set it to use research-backed features.' - ); - } - // Create and return a new client instance each time for now - // Caching can be handled by ai-client-factory later - return new OpenAI({ - apiKey: apiKey, - baseURL: 'https://api.perplexity.ai' - }); - // Removed the old caching logic using the global 'perplexity' variable -} - /** * Get the best available AI model for a given operation * @param {Object} options - Options for model selection @@ -134,15 +114,16 @@ function getAvailableAIModel(options = {}, session = null) { /** * Handle Claude API errors with user-friendly messages * @param {Error} error - The error from Claude API + * @param {object|null} [session=null] - The MCP session object (optional) * @returns {string} User-friendly error message */ -function handleClaudeError(error) { +function handleClaudeError(error, session = null) { // Check if it's a structured error response if (error.type === 'error' && error.error) { switch (error.error.type) { case 'overloaded_error': - // Check if we can use Perplexity as a fallback - if (process.env.PERPLEXITY_API_KEY) { + // Check if we can use Perplexity as a fallback using isApiKeySet + if (isApiKeySet('perplexity', session)) { return 'Claude is currently overloaded. Trying to fall back to Perplexity AI.'; } return 'Claude is currently experiencing high demand and is overloaded. Please wait a few minutes and try again.'; @@ -258,8 +239,8 @@ Important: Your response must be valid JSON only, with no additional explanation modelConfig ); } catch (error) { - // Get user-friendly error message - const userMessage = handleClaudeError(error); + // Get user-friendly error message, passing session + const userMessage = handleClaudeError(error, session); log('error', userMessage); // Retry logic for certain errors @@ -431,7 +412,7 @@ async function handleStreamingRequest( if (error.error?.type === 'overloaded_error') { claudeOverloaded = true; } - const userMessage = handleClaudeError(error); + const userMessage = handleClaudeError(error, session); report(userMessage, 'error'); throw error; @@ -728,10 +709,8 @@ async function generateSubtasksWithPerplexity( logFn('info', `Researching context for task ${task.id}: ${task.title}`); const perplexityClient = getPerplexityClient(session); - const PERPLEXITY_MODEL = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; + // Use getter for model ID + const PERPLEXITY_MODEL = getResearchModelId(session); // Only create loading indicators if not in silent mode let researchLoadingIndicator = null; @@ -763,7 +742,7 @@ Include concrete code examples and technical considerations where relevant.`; } ], temperature: 0.1, // Lower temperature for more factual responses - max_tokens: 8700, // Respect maximum input tokens for Perplexity (8719 max) + max_tokens: getResearchMaxTokens(session), // Respect maximum input tokens for Perplexity (8719 max) web_search_options: { search_context_size: 'high' }, @@ -867,7 +846,7 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use getAnthropicClient(session), { model: getMainModelId(session), - max_tokens: 8700, + max_tokens: getMainMaxTokens(session), temperature: getMainTemperature(session), system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] @@ -1035,7 +1014,7 @@ Analyze each task and return a JSON array with the following structure for each "taskId": number, "taskTitle": string, "complexityScore": number (1-10), - "recommendedSubtasks": number (${Math.max(3, CONFIG.defaultSubtasks - 1)}-${Math.min(8, CONFIG.defaultSubtasks + 2)}), + "recommendedSubtasks": number (${Math.max(3, getDefaultSubtasks() - 1)}-${Math.min(8, getDefaultSubtasks() + 2)}), "expansionPrompt": string (a specific prompt for generating good subtasks), "reasoning": string (brief explanation of your assessment) }, @@ -1144,7 +1123,8 @@ async function _handleAnthropicStream( } // Report progress - use only mcpLog in MCP context and avoid direct reportProgress calls - const maxTokens = params.max_tokens || CONFIG.maxTokens; + // Use getter for maxTokens + const maxTokens = params.max_tokens || getMainMaxTokens(session); const progressPercent = Math.min( 100, (responseText.length / maxTokens) * 100 @@ -1311,35 +1291,6 @@ function _buildAddTaskPrompt(prompt, contextTasks, { newTaskId } = {}) { return { systemPrompt, userPrompt }; } -/** - * Get an Anthropic client instance - * @param {Object} [session] - Optional session object from MCP - * @returns {Anthropic} Anthropic client instance - */ -function getAnthropicClient(session) { - // If we already have a global client and no session, use the global - // if (!session && anthropic) { - // return anthropic; - // } - - // Initialize a new client with API key from session or environment - const apiKey = resolveEnvVariable('ANTHROPIC_API_KEY', session); - - if (!apiKey) { - throw new Error( - 'ANTHROPIC_API_KEY environment variable is missing. Set it to use AI features.' - ); - } - - return new Anthropic({ - apiKey: apiKey, - // Add beta header for 128k token output - defaultHeaders: { - 'anthropic-beta': 'output-128k-2025-02-19' - } - }); -} - /** * Generate a detailed task description using Perplexity AI for research * @param {string} prompt - Task description prompt @@ -1358,10 +1309,8 @@ async function generateTaskDescriptionWithPerplexity( log('info', `Researching context for task prompt: "${prompt}"`); const perplexityClient = getPerplexityClient(session); - const PERPLEXITY_MODEL = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; + // Use getter for model ID + const PERPLEXITY_MODEL = getResearchModelId(session); const researchLoadingIndicator = startLoadingIndicator( 'Researching best practices with Perplexity AI...' ); @@ -1381,7 +1330,7 @@ Include concrete code examples and technical considerations where relevant.`; } ], temperature: 0.1, // Lower temperature for more factual responses - max_tokens: 8700, // Respect maximum input tokens for Perplexity (8719 max) + max_tokens: getResearchMaxTokens(session), // Respect maximum input tokens for Perplexity (8719 max) web_search_options: { search_context_size: 'high' }, @@ -1464,12 +1413,12 @@ Return a JSON object with the following structure: } if (reportProgress) { await reportProgress({ - progress: (responseText.length / CONFIG.maxTokens) * 100 + progress: (responseText.length / getMainMaxTokens(session)) * 100 }); } if (mcpLog) { mcpLog.info( - `Progress: ${(responseText.length / CONFIG.maxTokens) * 100}%` + `Progress: ${(responseText.length / getMainMaxTokens(session)) * 100}%` ); } } @@ -1587,8 +1536,8 @@ function parseTasksFromCompletion(completionText) { // Export AI service functions export { - getAnthropicClient, - getPerplexityClient, + // getAnthropicClient, // Removed - This name is not defined here. + // getPerplexityClient, // Removed - Not defined or imported here. callClaude, handleStreamingRequest, processClaudeResponse, @@ -1598,11 +1547,11 @@ export { parseSubtasksFromText, generateComplexityAnalysisPrompt, handleClaudeError, - getAvailableAIModel, + getAvailableAIModel, // Local function definition parseTaskJsonResponse, _buildAddTaskPrompt, _handleAnthropicStream, - getConfiguredAnthropicClient, + getConfiguredAnthropicClient, // Locally defined function sendChatWithContext, parseTasksFromCompletion }; diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index be0858aa..637f8380 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -13,7 +13,7 @@ import inquirer from 'inquirer'; import ora from 'ora'; import Table from 'cli-table3'; -import { log, readJSON, writeJSON } from './utils.js'; +import { log, readJSON } from './utils.js'; import { parsePRD, updateTasks, @@ -347,7 +347,7 @@ function registerCommands(programInstance) { if (useResearch) { // Verify Perplexity API key exists if using research - if (!process.env.PERPLEXITY_API_KEY) { + if (!isApiKeySet('perplexity')) { console.log( chalk.yellow( 'Warning: PERPLEXITY_API_KEY environment variable is missing. Research-backed updates will not be available.' @@ -400,7 +400,7 @@ function registerCommands(programInstance) { } // Use getDebugFlag getter instead of CONFIG.debug - if (getDebugFlag(null)) { + if (getDebugFlag()) { console.error(error); } @@ -500,7 +500,7 @@ function registerCommands(programInstance) { if (useResearch) { // Verify Perplexity API key exists if using research - if (!process.env.PERPLEXITY_API_KEY) { + if (!isApiKeySet('perplexity')) { console.log( chalk.yellow( 'Warning: PERPLEXITY_API_KEY environment variable is missing. Research-backed updates will not be available.' @@ -556,7 +556,7 @@ function registerCommands(programInstance) { } // Use getDebugFlag getter instead of CONFIG.debug - if (getDebugFlag(null)) { + if (getDebugFlag()) { console.error(error); } @@ -923,7 +923,7 @@ function registerCommands(programInstance) { console.log(chalk.gray('Next: Complete this task or add more tasks')); } catch (error) { console.error(chalk.red(`Error adding task: ${error.message}`)); - if (error.stack && getDebugFlag(null)) { + if (error.stack && getDebugFlag()) { console.error(error.stack); } process.exit(1); @@ -2105,7 +2105,7 @@ function registerCommands(programInstance) { } } catch (error) { log(`Error processing models command: ${error.message}`, 'error'); - if (error.stack && getDebugFlag(null)) { + if (error.stack && getDebugFlag()) { log(error.stack, 'debug'); } process.exit(1); @@ -2337,7 +2337,7 @@ async function runCLI(argv = process.argv) { } catch (error) { console.error(chalk.red(`Error: ${error.message}`)); - if (getDebugFlag(null)) { + if (getDebugFlag()) { console.error(error); } diff --git a/scripts/modules/dependency-manager.js b/scripts/modules/dependency-manager.js index af8904fb..80d00041 100644 --- a/scripts/modules/dependency-manager.js +++ b/scripts/modules/dependency-manager.js @@ -6,7 +6,8 @@ import path from 'path'; import chalk from 'chalk'; import boxen from 'boxen'; -import { Anthropic } from '@anthropic-ai/sdk'; +// Remove Anthropic import if client is no longer initialized globally +// import { Anthropic } from '@anthropic-ai/sdk'; import { log, @@ -22,10 +23,10 @@ import { displayBanner } from './ui.js'; import { generateTaskFiles } from './task-manager.js'; -// Initialize Anthropic client -const anthropic = new Anthropic({ - apiKey: process.env.ANTHROPIC_API_KEY -}); +// Remove global Anthropic client initialization +// const anthropic = new Anthropic({ +// apiKey: process.env.ANTHROPIC_API_KEY +// }); /** * Add a dependency to a task diff --git a/scripts/modules/task-manager/add-task.js b/scripts/modules/task-manager/add-task.js index 2113cbc6..8f79b31b 100644 --- a/scripts/modules/task-manager/add-task.js +++ b/scripts/modules/task-manager/add-task.js @@ -11,7 +11,15 @@ import { } from '../ui.js'; import { log, readJSON, writeJSON, truncate } from '../utils.js'; import { _handleAnthropicStream } from '../ai-services.js'; -import { getDefaultPriority } from '../config-manager.js'; +import { + getDefaultPriority, + getResearchModelId, + getResearchTemperature, + getResearchMaxTokens, + getMainModelId, + getMainTemperature, + getMainMaxTokens +} from '../config-manager.js'; /** * Add a new task using AI @@ -183,46 +191,26 @@ async function addTask( if (modelType === 'perplexity') { // Use Perplexity AI - const perplexityModel = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; const response = await client.chat.completions.create({ - model: perplexityModel, + model: getResearchModelId(session), messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt } ], - temperature: parseFloat( - process.env.TEMPERATURE || - session?.env?.TEMPERATURE || - CONFIG.temperature - ), - max_tokens: parseInt( - process.env.MAX_TOKENS || - session?.env?.MAX_TOKENS || - CONFIG.maxTokens - ) + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session) }); const responseText = response.choices[0].message.content; aiGeneratedTaskData = parseTaskJsonResponse(responseText); } else { // Use Claude (default) - // Prepare API parameters + // Prepare API parameters using getters, preserving customEnv override const apiParams = { - model: - session?.env?.ANTHROPIC_MODEL || - CONFIG.model || - customEnv?.ANTHROPIC_MODEL, - max_tokens: - session?.env?.MAX_TOKENS || - CONFIG.maxTokens || - customEnv?.MAX_TOKENS, + model: customEnv?.ANTHROPIC_MODEL || getMainModelId(session), + max_tokens: customEnv?.MAX_TOKENS || getMainMaxTokens(session), temperature: - session?.env?.TEMPERATURE || - CONFIG.temperature || - customEnv?.TEMPERATURE, + customEnv?.TEMPERATURE || getMainTemperature(session), system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] }; diff --git a/scripts/modules/task-manager/analyze-task-complexity.js b/scripts/modules/task-manager/analyze-task-complexity.js index b9c32509..33e616f0 100644 --- a/scripts/modules/task-manager/analyze-task-complexity.js +++ b/scripts/modules/task-manager/analyze-task-complexity.js @@ -8,7 +8,17 @@ import { startLoadingIndicator, stopLoadingIndicator } from '../ui.js'; import { generateComplexityAnalysisPrompt } from '../ai-services.js'; -import { getDebugFlag } from '../config-manager.js'; +import { + getDebugFlag, + getProjectName, + getMainModelId, + getMainMaxTokens, + getMainTemperature, + getResearchModelId, + getResearchMaxTokens, + getResearchTemperature, + getDefaultSubtasks +} from '../config-manager.js'; /** * Analyzes task complexity and generates expansion recommendations @@ -127,6 +137,83 @@ async function analyzeTaskComplexity( } } + // If after filtering, there are no tasks left to analyze, exit early. + if (tasksData.tasks.length === 0) { + const emptyReport = { + meta: { + generatedAt: new Date().toISOString(), + tasksAnalyzed: tasksData.tasks.length, + thresholdScore: thresholdScore, + projectName: getProjectName(session), + usedResearch: useResearch + }, + complexityAnalysis: [] + }; + // Write the report to file + reportLog(`Writing complexity report to ${outputPath}...`, 'info'); + writeJSON(outputPath, emptyReport); + + reportLog( + `Task complexity analysis complete. Report written to ${outputPath}`, + 'success' + ); + + // Only show UI elements for text output (CLI) + if (outputFormat === 'text') { + console.log( + chalk.green( + `Task complexity analysis complete. Report written to ${outputPath}` + ) + ); + + // Display a summary of findings + const highComplexity = emptyReport.complexityAnalysis.filter( + (t) => t.complexityScore >= 8 + ).length; + const mediumComplexity = emptyReport.complexityAnalysis.filter( + (t) => t.complexityScore >= 5 && t.complexityScore < 8 + ).length; + const lowComplexity = emptyReport.complexityAnalysis.filter( + (t) => t.complexityScore < 5 + ).length; + const totalAnalyzed = emptyReport.complexityAnalysis.length; + + console.log('\nComplexity Analysis Summary:'); + console.log('----------------------------'); + console.log(`Tasks in input file: ${tasksData.tasks.length}`); + console.log(`Tasks successfully analyzed: ${totalAnalyzed}`); + console.log(`High complexity tasks: ${highComplexity}`); + console.log(`Medium complexity tasks: ${mediumComplexity}`); + console.log(`Low complexity tasks: ${lowComplexity}`); + console.log( + `Sum verification: ${highComplexity + mediumComplexity + lowComplexity} (should equal ${totalAnalyzed})` + ); + console.log(`Research-backed analysis: ${useResearch ? 'Yes' : 'No'}`); + console.log( + `\nSee ${outputPath} for the full report and expansion commands.` + ); + + // Show next steps suggestions + console.log( + boxen( + chalk.white.bold('Suggested Next Steps:') + + '\n\n' + + `${chalk.cyan('1.')} Run ${chalk.yellow('task-master complexity-report')} to review detailed findings\n` + + `${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=')} to break down complex tasks\n` + + `${chalk.cyan('3.')} Run ${chalk.yellow('task-master expand --all')} to expand all pending tasks based on complexity`, + { + padding: 1, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + } + + return emptyReport; + } + // Prepare the prompt for the LLM const prompt = generateComplexityAnalysisPrompt(tasksData); @@ -183,11 +270,9 @@ Your response must be a clean JSON array only, following exactly this format: DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`; + // Keep the direct AI call for now, use config getters for parameters const result = await perplexity.chat.completions.create({ - model: - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro', + model: getResearchModelId(session), messages: [ { role: 'system', @@ -199,8 +284,8 @@ DO NOT include any text before or after the JSON array. No explanations, no mark content: researchPrompt } ], - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, - max_tokens: 8700, + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session), web_search_options: { search_context_size: 'high' }, @@ -236,6 +321,12 @@ DO NOT include any text before or after the JSON array. No explanations, no mark console.log(chalk.gray('Response first 200 chars:')); console.log(chalk.gray(fullResponse.substring(0, 200))); } + + if (getDebugFlag(session)) { + console.debug( + chalk.gray(`Raw response: ${fullResponse.substring(0, 500)}...`) + ); + } } catch (perplexityError) { reportLog( `Falling back to Claude for complexity analysis: ${perplexityError.message}`, @@ -287,12 +378,11 @@ DO NOT include any text before or after the JSON array. No explanations, no mark ); } - // Call the LLM API with streaming + // Keep the direct AI call for now, use config getters for parameters const stream = await anthropic.messages.create({ - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens, - model: - modelOverride || CONFIG.model || session?.env?.ANTHROPIC_MODEL, - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, + max_tokens: getMainMaxTokens(session), + model: modelOverride || getMainModelId(session), + temperature: getMainTemperature(session), messages: [{ role: 'user', content: prompt }], system: 'You are an expert software architect and project manager analyzing task complexity. Respond only with valid JSON.', @@ -318,12 +408,13 @@ DO NOT include any text before or after the JSON array. No explanations, no mark } if (reportProgress) { await reportProgress({ - progress: (fullResponse.length / CONFIG.maxTokens) * 100 + progress: + (fullResponse.length / getMainMaxTokens(session)) * 100 }); } if (mcpLog) { mcpLog.info( - `Progress: ${(fullResponse.length / CONFIG.maxTokens) * 100}%` + `Progress: ${(fullResponse.length / getMainMaxTokens(session)) * 100}%` ); } } @@ -797,7 +888,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark generatedAt: new Date().toISOString(), tasksAnalyzed: tasksData.tasks.length, thresholdScore: thresholdScore, - projectName: tasksData.meta?.projectName || 'Your Project Name', + projectName: getProjectName(session), usedResearch: useResearch }, complexityAnalysis: complexityAnalysis @@ -865,6 +956,12 @@ DO NOT include any text before or after the JSON array. No explanations, no mark } ) ); + + if (getDebugFlag(session)) { + console.debug( + chalk.gray(`Raw response: ${fullResponse.substring(0, 500)}...`) + ); + } } return finalReport; @@ -885,8 +982,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark console.error( chalk.red(`Error parsing complexity analysis: ${error.message}`) ); - if (getDebugFlag()) { - // Use getter + if (getDebugFlag(session)) { console.debug( chalk.gray(`Raw response: ${fullResponse.substring(0, 500)}...`) ); @@ -931,8 +1027,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark ); } - if (getDebugFlag()) { - // Use getter + if (getDebugFlag(session)) { console.error(error); } diff --git a/scripts/modules/task-manager/expand-task.js b/scripts/modules/task-manager/expand-task.js index 2b5011f3..4698d49f 100644 --- a/scripts/modules/task-manager/expand-task.js +++ b/scripts/modules/task-manager/expand-task.js @@ -12,7 +12,12 @@ import { parseSubtasksFromText } from '../ai-services.js'; -import { getDefaultSubtasks } from '../config-manager.js'; +import { + getDefaultSubtasks, + getMainModelId, + getMainMaxTokens, + getMainTemperature +} from '../config-manager.js'; import generateTaskFiles from './generate-task-files.js'; /** @@ -207,11 +212,11 @@ Return exactly ${subtaskCount} subtasks with the following JSON structure: Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use an empty array if there are no dependencies.`; - // Prepare API parameters + // Prepare API parameters using getters const apiParams = { - model: session?.env?.ANTHROPIC_MODEL || CONFIG.model, - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens, - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, + model: getMainModelId(session), + max_tokens: getMainMaxTokens(session), + temperature: getMainTemperature(session), system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] }; diff --git a/scripts/modules/task-manager/get-subtasks-from-ai.js b/scripts/modules/task-manager/get-subtasks-from-ai.js index be1cdf31..7d58bb3d 100644 --- a/scripts/modules/task-manager/get-subtasks-from-ai.js +++ b/scripts/modules/task-manager/get-subtasks-from-ai.js @@ -6,6 +6,16 @@ import { parseSubtasksFromText } from '../ai-services.js'; +// Import necessary config getters +import { + getMainModelId, + getMainMaxTokens, + getMainTemperature, + getResearchModelId, + getResearchMaxTokens, + getResearchTemperature +} from '../config-manager.js'; + /** * Call AI to generate subtasks based on a prompt * @param {string} prompt - The prompt to send to the AI @@ -26,9 +36,9 @@ async function getSubtasksFromAI( // Prepare API parameters const apiParams = { - model: session?.env?.ANTHROPIC_MODEL || CONFIG.model, - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens, - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, + model: getMainModelId(session), + max_tokens: getMainMaxTokens(session), + temperature: getMainTemperature(session), system: 'You are an AI assistant helping with task breakdown for software development.', messages: [{ role: 'user', content: prompt }] @@ -46,10 +56,7 @@ async function getSubtasksFromAI( mcpLog.info('Using Perplexity AI for research-backed subtasks'); } - const perplexityModel = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; + const perplexityModel = getResearchModelId(session); const result = await perplexity.chat.completions.create({ model: perplexityModel, messages: [ @@ -60,8 +67,8 @@ async function getSubtasksFromAI( }, { role: 'user', content: prompt } ], - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session) }); responseText = result.choices[0].message.content; diff --git a/scripts/modules/task-manager/set-task-status.js b/scripts/modules/task-manager/set-task-status.js index 196ab07e..f8b5fc3e 100644 --- a/scripts/modules/task-manager/set-task-status.js +++ b/scripts/modules/task-manager/set-task-status.js @@ -97,7 +97,8 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { if (!options?.mcpLog) { console.error(chalk.red(`Error: ${error.message}`)); - if (getDebugFlag()) { + // Pass session to getDebugFlag + if (getDebugFlag(options?.session)) { // Use getter console.error(error); } diff --git a/scripts/modules/task-manager/update-subtask-by-id.js b/scripts/modules/task-manager/update-subtask-by-id.js index c5940032..5648efe5 100644 --- a/scripts/modules/task-manager/update-subtask-by-id.js +++ b/scripts/modules/task-manager/update-subtask-by-id.js @@ -11,7 +11,15 @@ import { } from '../ui.js'; import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js'; import { getAvailableAIModel } from '../ai-services.js'; -import { getDebugFlag } from '../config-manager.js'; +import { + getDebugFlag, + getMainModelId, + getMainMaxTokens, + getMainTemperature, + getResearchModelId, + getResearchMaxTokens, + getResearchTemperature +} from '../config-manager.js'; import generateTaskFiles from './generate-task-files.js'; /** @@ -231,26 +239,15 @@ Provide concrete examples, code snippets, or implementation details when relevan if (modelType === 'perplexity') { // Construct Perplexity payload - const perplexityModel = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; + const perplexityModel = getResearchModelId(session); const response = await client.chat.completions.create({ model: perplexityModel, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userMessageContent } ], - temperature: parseFloat( - process.env.TEMPERATURE || - session?.env?.TEMPERATURE || - CONFIG.temperature - ), - max_tokens: parseInt( - process.env.MAX_TOKENS || - session?.env?.MAX_TOKENS || - CONFIG.maxTokens - ) + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session) }); additionalInformation = response.choices[0].message.content.trim(); } else { @@ -272,11 +269,11 @@ Provide concrete examples, code snippets, or implementation details when relevan }, 500); } - // Construct Claude payload + // Construct Claude payload using config getters const stream = await client.messages.create({ - model: CONFIG.model, - max_tokens: CONFIG.maxTokens, - temperature: CONFIG.temperature, + model: getMainModelId(session), + max_tokens: getMainMaxTokens(session), + temperature: getMainTemperature(session), system: systemPrompt, messages: [{ role: 'user', content: userMessageContent }], stream: true @@ -288,12 +285,13 @@ Provide concrete examples, code snippets, or implementation details when relevan } if (reportProgress) { await reportProgress({ - progress: (responseText.length / CONFIG.maxTokens) * 100 + progress: + (responseText.length / getMainMaxTokens(session)) * 100 }); } if (mcpLog) { mcpLog.info( - `Progress: ${(responseText.length / CONFIG.maxTokens) * 100}%` + `Progress: ${(responseText.length / getMainMaxTokens(session)) * 100}%` ); } } @@ -540,7 +538,7 @@ Provide concrete examples, code snippets, or implementation details when relevan ' 1. Set your Perplexity API key: export PERPLEXITY_API_KEY=your_api_key_here' ); console.log( - ' 2. Or run without the research flag: task-master update-subtask --id= --prompt=\"...\"' + ' 2. Or run without the research flag: task-master update-subtask --id= --prompt="..."' ); } else if (error.message?.includes('overloaded')) { // Catch final overload error @@ -568,7 +566,7 @@ Provide concrete examples, code snippets, or implementation details when relevan ); } - if (getDebugFlag()) { + if (getDebugFlag(session)) { // Use getter console.error(error); } diff --git a/scripts/modules/task-manager/update-task-by-id.js b/scripts/modules/task-manager/update-task-by-id.js index 48cbf3e8..b8f16834 100644 --- a/scripts/modules/task-manager/update-task-by-id.js +++ b/scripts/modules/task-manager/update-task-by-id.js @@ -13,7 +13,16 @@ import { } from '../ui.js'; import { _handleAnthropicStream } from '../ai-services.js'; -import { getDebugFlag } from '../config-manager.js'; +import { + getDebugFlag, + getMainModelId, + getMainMaxTokens, + getMainTemperature, + getResearchModelId, + getResearchMaxTokens, + getResearchTemperature, + isApiKeySet +} from '../config-manager.js'; import generateTaskFiles from './generate-task-files.js'; /** @@ -64,15 +73,10 @@ async function updateTaskById( ); } - // Validate research flag - if ( - useResearch && - (!perplexity || - !process.env.PERPLEXITY_API_KEY || - session?.env?.PERPLEXITY_API_KEY) - ) { + // Validate research flag and API key + if (useResearch && !isApiKeySet('perplexity', session)) { report( - 'Perplexity AI is not available. Falling back to Claude AI.', + 'Perplexity AI research requested but API key is not set. Falling back to main AI.', 'warn' ); @@ -274,7 +278,7 @@ The changes described in the prompt should be thoughtfully applied to make the t session?.env?.PERPLEXITY_MODEL || 'sonar-pro'; const result = await client.chat.completions.create({ - model: perplexityModel, + model: getResearchModelId(session), messages: [ { role: 'system', @@ -293,12 +297,8 @@ IMPORTANT: In the task JSON above, any subtasks with "status": "done" or "status Return only the updated task as a valid JSON object.` } ], - temperature: parseFloat( - process.env.TEMPERATURE || - session?.env?.TEMPERATURE || - CONFIG.temperature - ), - max_tokens: 8700 + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session) }); const responseText = result.choices[0].message.content; @@ -343,9 +343,9 @@ Return only the updated task as a valid JSON object.` // Use streaming API call const stream = await client.messages.create({ - model: session?.env?.ANTHROPIC_MODEL || CONFIG.model, - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens, - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, + model: getMainModelId(session), + max_tokens: getMainMaxTokens(session), + temperature: getMainTemperature(session), system: systemPrompt, messages: [ { @@ -371,12 +371,13 @@ Return only the updated task as a valid JSON object.` } if (reportProgress) { await reportProgress({ - progress: (responseText.length / CONFIG.maxTokens) * 100 + progress: + (responseText.length / getMainMaxTokens(session)) * 100 }); } if (mcpLog) { mcpLog.info( - `Progress: ${(responseText.length / CONFIG.maxTokens) * 100}%` + `Progress: ${(responseText.length / getMainMaxTokens(session)) * 100}%` ); } } @@ -667,7 +668,7 @@ Return only the updated task as a valid JSON object.` console.log(' 2. Use a valid task ID with the --id parameter'); } - if (getDebugFlag()) { + if (getDebugFlag(session)) { // Use getter console.error(error); } diff --git a/scripts/modules/task-manager/update-tasks.js b/scripts/modules/task-manager/update-tasks.js index 38164514..1a3c507b 100644 --- a/scripts/modules/task-manager/update-tasks.js +++ b/scripts/modules/task-manager/update-tasks.js @@ -11,7 +11,15 @@ import { stopLoadingIndicator } from '../ui.js'; -import { getDebugFlag } from '../config-manager.js'; +import { + getDebugFlag, + getResearchModelId, + getResearchTemperature, + getResearchMaxTokens, + getMainModelId, + getMainMaxTokens, + getMainTemperature +} from '../config-manager.js'; import generateTaskFiles from './generate-task-files.js'; /** @@ -204,13 +212,9 @@ The changes described in the prompt should be applied to ALL tasks in the list.` } if (modelType === 'perplexity') { - // Call Perplexity AI using proper format - const perplexityModel = - process.env.PERPLEXITY_MODEL || - session?.env?.PERPLEXITY_MODEL || - 'sonar-pro'; + // Call Perplexity AI using proper format and getters const result = await client.chat.completions.create({ - model: perplexityModel, + model: getResearchModelId(session), messages: [ { role: 'system', @@ -218,23 +222,11 @@ The changes described in the prompt should be applied to ALL tasks in the list.` }, { role: 'user', - content: `Here are the tasks to update: -${taskData} - -Please update these tasks based on the following new context: -${prompt} - -IMPORTANT: In the tasks JSON above, any subtasks with "status": "done" or "status": "completed" should be preserved exactly as is. Build your changes around these completed items. - -Return only the updated tasks as a valid JSON array.` + content: `Here are the tasks to update:\n${taskData}\n\nPlease update these tasks based on the following new context:\n${prompt}\n\nIMPORTANT: In the tasks JSON above, any subtasks with "status": "done" or "status": "completed" should be preserved exactly as is. Build your changes around these completed items.\n\nReturn only the updated tasks as a valid JSON array.` } ], - temperature: parseFloat( - process.env.TEMPERATURE || - session?.env?.TEMPERATURE || - CONFIG.temperature - ), - max_tokens: 8700 + temperature: getResearchTemperature(session), + max_tokens: getResearchMaxTokens(session) }); const responseText = result.choices[0].message.content; @@ -270,11 +262,11 @@ Return only the updated tasks as a valid JSON array.` }, 500); } - // Use streaming API call + // Use streaming API call with getters const stream = await client.messages.create({ - model: session?.env?.ANTHROPIC_MODEL || CONFIG.model, - max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens, - temperature: session?.env?.TEMPERATURE || CONFIG.temperature, + model: getMainModelId(session), + max_tokens: getMainMaxTokens(session), + temperature: getMainTemperature(session), system: systemPrompt, messages: [ { @@ -300,12 +292,13 @@ Return only the updated task as a valid JSON object.` } if (reportProgress) { await reportProgress({ - progress: (responseText.length / CONFIG.maxTokens) * 100 + progress: + (responseText.length / getMainMaxTokens(session)) * 100 }); } if (mcpLog) { mcpLog.info( - `Progress: ${(responseText.length / CONFIG.maxTokens) * 100}%` + `Progress: ${(responseText.length / getMainMaxTokens(session)) * 100}%` ); } } diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index e80ede1e..45df095d 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -19,7 +19,16 @@ import { import path from 'path'; import fs from 'fs'; import { findNextTask, analyzeTaskComplexity } from './task-manager.js'; -import { getProjectName, getDefaultSubtasks } from './config-manager.js'; +import { + getProjectName, + getDefaultSubtasks, + getMainModelId, + getMainMaxTokens, + getMainTemperature, + getDebugFlag, + getLogLevel, + getDefaultPriority +} from './config-manager.js'; // Create a color gradient for the banner const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']); @@ -591,17 +600,17 @@ function displayHelp() { [ `${chalk.yellow('MODEL')}${chalk.reset('')}`, `${chalk.white('Claude model to use')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.model}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getMainModelId()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('MAX_TOKENS')}${chalk.reset('')}`, `${chalk.white('Maximum tokens for responses')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.maxTokens}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getMainMaxTokens()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('TEMPERATURE')}${chalk.reset('')}`, `${chalk.white('Temperature for model responses')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.temperature}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getMainTemperature()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('PERPLEXITY_API_KEY')}${chalk.reset('')}`, @@ -616,27 +625,27 @@ function displayHelp() { [ `${chalk.yellow('DEBUG')}${chalk.reset('')}`, `${chalk.white('Enable debug logging')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.debug}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getDebugFlag()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('LOG_LEVEL')}${chalk.reset('')}`, `${chalk.white('Console output level (debug,info,warn,error)')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.logLevel}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getLogLevel()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('DEFAULT_SUBTASKS')}${chalk.reset('')}`, `${chalk.white('Default number of subtasks to generate')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.defaultSubtasks}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getDefaultSubtasks()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('DEFAULT_PRIORITY')}${chalk.reset('')}`, `${chalk.white('Default task priority')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.defaultPriority}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getDefaultPriority()}`)}${chalk.reset('')}` ], [ `${chalk.yellow('PROJECT_NAME')}${chalk.reset('')}`, `${chalk.white('Project name displayed in UI')}${chalk.reset('')}`, - `${chalk.dim(`Default: ${CONFIG.projectName}`)}${chalk.reset('')}` + `${chalk.dim(`Default: ${getProjectName()}`)}${chalk.reset('')}` ] );