2025-04-21 17:48:30 -04:00
import fs from 'fs' ;
import path from 'path' ;
import chalk from 'chalk' ;
import boxen from 'boxen' ;
import Table from 'cli-table3' ;
import {
getStatusWithColor ,
startLoadingIndicator ,
stopLoadingIndicator
} from '../ui.js' ;
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.
2025-04-21 21:30:12 -04:00
import {
2025-04-25 13:24:15 -04:00
log as consoleLog ,
readJSON ,
writeJSON ,
truncate ,
isSilentMode
} from '../utils.js' ;
import { generateTextService } from '../ai-services-unified.js' ;
import { getDebugFlag , isApiKeySet } from '../config-manager.js' ;
2025-04-21 17:48:30 -04:00
import generateTaskFiles from './generate-task-files.js' ;
/ * *
2025-04-25 13:24:15 -04:00
* Update a subtask by appending additional timestamped information using the unified AI service .
2025-04-21 17:48:30 -04:00
* @ param { string } tasksPath - Path to the tasks . json file
* @ param { string } subtaskId - ID of the subtask to update in format "parentId.subtaskId"
* @ param { string } prompt - Prompt for generating additional information
2025-04-25 13:24:15 -04:00
* @ param { boolean } [ useResearch = false ] - Whether to use the research AI role .
* @ param { Object } context - Context object containing session and mcpLog .
* @ param { Object } [ context . session ] - Session object from MCP server .
* @ param { Object } [ context . mcpLog ] - MCP logger object .
* @ param { string } [ outputFormat = 'text' ] - Output format ( 'text' or 'json' ) . Automatically 'json' if mcpLog is present .
* @ returns { Promise < Object | null > } - The updated subtask or null if update failed .
2025-04-21 17:48:30 -04:00
* /
async function updateSubtaskById (
tasksPath ,
subtaskId ,
prompt ,
useResearch = false ,
2025-04-25 13:24:15 -04:00
context = { } ,
outputFormat = context . mcpLog ? 'json' : 'text'
2025-04-21 17:48:30 -04:00
) {
2025-04-25 13:24:15 -04:00
const { session , mcpLog } = context ;
const logFn = mcpLog || consoleLog ;
const isMCP = ! ! mcpLog ;
// Report helper
const report = ( level , ... args ) => {
if ( isMCP ) {
if ( typeof logFn [ level ] === 'function' ) logFn [ level ] ( ... args ) ;
else logFn . info ( ... args ) ;
} else if ( ! isSilentMode ( ) ) {
logFn ( level , ... args ) ;
2025-04-21 17:48:30 -04:00
}
} ;
let loadingIndicator = null ;
2025-04-22 02:42:04 -04:00
2025-04-21 17:48:30 -04:00
try {
2025-04-25 13:24:15 -04:00
report ( 'info' , ` Updating subtask ${ subtaskId } with prompt: " ${ prompt } " ` ) ;
2025-04-21 17:48:30 -04:00
// Validate subtask ID format
if (
! subtaskId ||
typeof subtaskId !== 'string' ||
! subtaskId . includes ( '.' )
) {
throw new Error (
` Invalid subtask ID format: ${ subtaskId } . Subtask ID must be in format "parentId.subtaskId" `
) ;
}
// Validate prompt
if ( ! prompt || typeof prompt !== 'string' || prompt . trim ( ) === '' ) {
throw new Error (
'Prompt cannot be empty. Please provide context for the subtask update.'
) ;
}
// Validate tasks file exists
if ( ! fs . existsSync ( tasksPath ) ) {
throw new Error ( ` Tasks file not found at path: ${ tasksPath } ` ) ;
}
// Read the tasks file
const data = readJSON ( tasksPath ) ;
if ( ! data || ! data . tasks ) {
throw new Error (
` No valid tasks found in ${ tasksPath } . The file may be corrupted or have an invalid format. `
) ;
}
// Parse parent and subtask IDs
const [ parentIdStr , subtaskIdStr ] = subtaskId . split ( '.' ) ;
const parentId = parseInt ( parentIdStr , 10 ) ;
const subtaskIdNum = parseInt ( subtaskIdStr , 10 ) ;
if (
isNaN ( parentId ) ||
parentId <= 0 ||
isNaN ( subtaskIdNum ) ||
subtaskIdNum <= 0
) {
throw new Error (
` Invalid subtask ID format: ${ subtaskId } . Both parent ID and subtask ID must be positive integers. `
) ;
}
// Find the parent task
const parentTask = data . tasks . find ( ( task ) => task . id === parentId ) ;
if ( ! parentTask ) {
throw new Error (
` Parent task with ID ${ parentId } not found. Please verify the task ID and try again. `
) ;
}
// Find the subtask
if ( ! parentTask . subtasks || ! Array . isArray ( parentTask . subtasks ) ) {
throw new Error ( ` Parent task ${ parentId } has no subtasks. ` ) ;
}
2025-04-25 13:24:15 -04:00
const subtaskIndex = parentTask . subtasks . findIndex (
( st ) => st . id === subtaskIdNum
) ;
if ( subtaskIndex === - 1 ) {
2025-04-21 17:48:30 -04:00
throw new Error (
` Subtask with ID ${ subtaskId } not found. Please verify the subtask ID and try again. `
) ;
}
2025-04-25 13:24:15 -04:00
const subtask = parentTask . subtasks [ subtaskIndex ] ;
2025-04-21 17:48:30 -04:00
// Check if subtask is already completed
if ( subtask . status === 'done' || subtask . status === 'completed' ) {
report (
2025-04-25 13:24:15 -04:00
'warn' ,
` Subtask ${ subtaskId } is already marked as done and cannot be updated `
2025-04-21 17:48:30 -04:00
) ;
// Only show UI elements for text output (CLI)
if ( outputFormat === 'text' ) {
console . log (
boxen (
chalk . yellow (
` Subtask ${ subtaskId } is already marked as ${ subtask . status } and cannot be updated. `
) +
'\n\n' +
chalk . white (
'Completed subtasks are locked to maintain consistency. To modify a completed subtask, you must first:'
) +
'\n' +
chalk . white (
'1. Change its status to "pending" or "in-progress"'
) +
'\n' +
chalk . white ( '2. Then run the update-subtask command' ) ,
{ padding : 1 , borderColor : 'yellow' , borderStyle : 'round' }
)
) ;
}
return null ;
}
// Only show UI elements for text output (CLI)
if ( outputFormat === 'text' ) {
// Show the subtask that will be updated
const table = new Table ( {
head : [
chalk . cyan . bold ( 'ID' ) ,
chalk . cyan . bold ( 'Title' ) ,
chalk . cyan . bold ( 'Status' )
] ,
colWidths : [ 10 , 55 , 10 ]
} ) ;
table . push ( [
subtaskId ,
truncate ( subtask . title , 52 ) ,
getStatusWithColor ( subtask . status )
] ) ;
console . log (
boxen ( chalk . white . bold ( ` Updating Subtask # ${ subtaskId } ` ) , {
padding : 1 ,
borderColor : 'blue' ,
borderStyle : 'round' ,
margin : { top : 1 , bottom : 0 }
} )
) ;
console . log ( table . toString ( ) ) ;
// Start the loading indicator - only for text output
loadingIndicator = startLoadingIndicator (
'Generating additional information with AI...'
) ;
}
2025-04-22 02:42:04 -04:00
let additionalInformation = '' ;
try {
// Reverted: Keep the original system prompt
const systemPrompt = ` You are an AI assistant helping to update software development subtasks with additional information.
2025-04-21 17:48:30 -04:00
Given a subtask , you will provide additional details , implementation notes , or technical insights based on user request .
Focus only on adding content that enhances the subtask - don ' t repeat existing information .
Be technical , specific , and implementation - focused rather than general .
Provide concrete examples , code snippets , or implementation details when relevant . ` ;
2025-04-22 02:42:04 -04:00
// Reverted: Use the full JSON stringification for the user message
const subtaskData = JSON . stringify ( subtask , null , 2 ) ;
const userMessageContent = ` Here is the subtask to enhance: \n ${ subtaskData } \n \n Please provide additional information addressing this request: \n ${ prompt } \n \n Return ONLY the new information to add - do not repeat existing content. ` ;
2025-04-21 17:48:30 -04:00
2025-04-22 02:42:04 -04:00
const serviceRole = useResearch ? 'research' : 'main' ;
2025-04-25 13:24:15 -04:00
report ( 'info' , ` Calling AI text service with role: ${ serviceRole } ` ) ;
2025-04-21 17:48:30 -04:00
2025-04-22 02:42:04 -04:00
const streamResult = await generateTextService ( {
role : serviceRole ,
session : session ,
2025-04-25 13:24:15 -04:00
systemPrompt : systemPrompt ,
prompt : userMessageContent
2025-04-22 02:42:04 -04:00
} ) ;
2025-04-21 17:48:30 -04:00
2025-04-22 02:42:04 -04:00
if ( outputFormat === 'text' && loadingIndicator ) {
// Stop indicator immediately since generateText is blocking
stopLoadingIndicator ( loadingIndicator ) ;
loadingIndicator = null ;
}
2025-04-21 17:48:30 -04:00
2025-04-22 02:42:04 -04:00
// Assign the result directly (generateTextService returns the text string)
additionalInformation = streamResult ? streamResult . trim ( ) : '' ;
2025-04-21 17:48:30 -04:00
2025-04-22 02:42:04 -04:00
if ( ! additionalInformation ) {
throw new Error ( 'AI returned empty response.' ) ; // Changed error message slightly
2025-04-21 17:48:30 -04:00
}
2025-04-22 02:42:04 -04:00
report (
// Corrected log message to reflect generateText
2025-04-25 13:24:15 -04:00
'success' ,
` Successfully generated text using AI role: ${ serviceRole } . `
2025-04-21 17:48:30 -04:00
) ;
2025-04-22 02:42:04 -04:00
} catch ( aiError ) {
2025-04-25 13:24:15 -04:00
report ( 'error' , ` AI service call failed: ${ aiError . message } ` ) ;
2025-04-22 02:42:04 -04:00
throw aiError ;
} // Removed the inner finally block as streamingInterval is gone
2025-04-21 17:48:30 -04:00
const currentDate = new Date ( ) ;
// Format the additional information with timestamp
2025-04-22 02:42:04 -04:00
const formattedInformation = ` \n \n <info added on ${ currentDate . toISOString ( ) } > \n ${ additionalInformation } \n </info added on ${ currentDate . toISOString ( ) } > ` ;
2025-04-21 17:48:30 -04:00
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log (
'>>> DEBUG: formattedInformation:' ,
formattedInformation . substring ( 0 , 70 ) + '...'
) ;
}
// Append to subtask details and description
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log ( '>>> DEBUG: Subtask details BEFORE append:' , subtask . details ) ;
}
if ( subtask . details ) {
subtask . details += formattedInformation ;
} else {
subtask . details = ` ${ formattedInformation } ` ;
}
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log ( '>>> DEBUG: Subtask details AFTER append:' , subtask . details ) ;
}
if ( subtask . description ) {
// Only append to description if it makes sense (for shorter updates)
if ( additionalInformation . length < 200 ) {
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log (
'>>> DEBUG: Subtask description BEFORE append:' ,
subtask . description
) ;
}
subtask . description += ` [Updated: ${ currentDate . toLocaleDateString ( ) } ] ` ;
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log (
'>>> DEBUG: Subtask description AFTER append:' ,
subtask . description
) ;
}
}
}
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log ( '>>> DEBUG: About to call writeJSON with updated data...' ) ;
}
2025-04-25 13:24:15 -04:00
// Update the subtask in the parent task's array
parentTask . subtasks [ subtaskIndex ] = subtask ;
2025-04-21 17:48:30 -04:00
// Write the updated tasks to the file
writeJSON ( tasksPath , data ) ;
// Only show debug info for text output (CLI)
2025-04-25 13:24:15 -04:00
if ( outputFormat === 'text' && getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
console . log ( '>>> DEBUG: writeJSON call completed.' ) ;
}
2025-04-25 13:24:15 -04:00
report ( 'success' , ` Successfully updated subtask ${ subtaskId } ` ) ;
2025-04-21 17:48:30 -04:00
// Generate individual task files
await generateTaskFiles ( tasksPath , path . dirname ( tasksPath ) ) ;
// Stop indicator before final console output - only for text output (CLI)
if ( outputFormat === 'text' ) {
if ( loadingIndicator ) {
stopLoadingIndicator ( loadingIndicator ) ;
loadingIndicator = null ;
}
console . log (
boxen (
chalk . green ( ` Successfully updated subtask # ${ subtaskId } ` ) +
'\n\n' +
chalk . white . bold ( 'Title:' ) +
' ' +
subtask . title +
'\n\n' +
chalk . white . bold ( 'Information Added:' ) +
'\n' +
chalk . white ( truncate ( additionalInformation , 300 , true ) ) ,
{ padding : 1 , borderColor : 'green' , borderStyle : 'round' }
)
) ;
}
return subtask ;
} catch ( error ) {
// Outer catch block handles final errors after loop/attempts
// Stop indicator on error - only for text output (CLI)
if ( outputFormat === 'text' && loadingIndicator ) {
stopLoadingIndicator ( loadingIndicator ) ;
loadingIndicator = null ;
}
2025-04-25 13:24:15 -04:00
report ( 'error' , ` Error updating subtask: ${ error . message } ` ) ;
2025-04-21 17:48:30 -04:00
// Only show error UI for text output (CLI)
if ( outputFormat === 'text' ) {
console . error ( chalk . red ( ` Error: ${ error . message } ` ) ) ;
// Provide helpful error messages based on error type
if ( error . message ? . includes ( 'ANTHROPIC_API_KEY' ) ) {
console . log (
chalk . yellow ( '\nTo fix this issue, set your Anthropic API key:' )
) ;
console . log ( ' export ANTHROPIC_API_KEY=your_api_key_here' ) ;
} else if ( error . message ? . includes ( 'PERPLEXITY_API_KEY' ) ) {
console . log ( chalk . yellow ( '\nTo fix this issue:' ) ) ;
console . log (
' 1. Set your Perplexity API key: export PERPLEXITY_API_KEY=your_api_key_here'
) ;
console . log (
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.
2025-04-21 21:30:12 -04:00
' 2. Or run without the research flag: task-master update-subtask --id=<id> --prompt="..."'
2025-04-21 17:48:30 -04:00
) ;
} else if ( error . message ? . includes ( 'overloaded' ) ) {
// Catch final overload error
console . log (
chalk . yellow (
'\nAI model overloaded, and fallback failed or was unavailable:'
)
) ;
console . log ( ' 1. Try again in a few minutes.' ) ;
console . log ( ' 2. Ensure PERPLEXITY_API_KEY is set for fallback.' ) ;
console . log ( ' 3. Consider breaking your prompt into smaller updates.' ) ;
} else if ( error . message ? . includes ( 'not found' ) ) {
console . log ( chalk . yellow ( '\nTo fix this issue:' ) ) ;
console . log (
' 1. Run task-master list --with-subtasks to see all available subtask IDs'
) ;
console . log (
2025-04-22 02:42:04 -04:00
' 2. Use a valid subtask ID with the --id parameter in format "parentId.subtaskId"'
2025-04-21 17:48:30 -04:00
) ;
2025-04-22 02:42:04 -04:00
} else if ( error . message ? . includes ( 'empty stream response' ) ) {
2025-04-21 17:48:30 -04:00
console . log (
chalk . yellow (
'\nThe AI model returned an empty response. This might be due to the prompt or API issues. Try rephrasing or trying again later.'
)
) ;
}
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.
2025-04-21 21:30:12 -04:00
if ( getDebugFlag ( session ) ) {
2025-04-21 17:48:30 -04:00
// Use getter
console . error ( error ) ;
}
} else {
throw error ; // Re-throw for JSON output
}
return null ;
}
}
export default updateSubtaskById ;