diff --git a/.changeset/all-parks-sort.md b/.changeset/all-parks-sort.md new file mode 100644 index 00000000..849dca43 --- /dev/null +++ b/.changeset/all-parks-sort.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +- Fix expand-all command bugs that caused NaN errors with --all option and JSON formatting errors with research enabled. Improved error handling to provide clear feedback when subtask generation fails, including task IDs and actionable suggestions. diff --git a/scripts/modules/ai-services.js b/scripts/modules/ai-services.js index 2557f0fd..a2d358e3 100644 --- a/scripts/modules/ai-services.js +++ b/scripts/modules/ai-services.js @@ -897,8 +897,12 @@ function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { throw new Error('Parsed content is not an array'); } + // Set default values for optional parameters + startId = startId || 1; + expectedCount = expectedCount || subtasks.length; + // Log warning if count doesn't match expected - if (subtasks.length !== expectedCount) { + if (expectedCount && subtasks.length !== expectedCount) { log( 'warn', `Expected ${expectedCount} subtasks, but parsed ${subtasks.length}` @@ -908,10 +912,10 @@ function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { // Normalize subtask IDs if they don't match subtasks = subtasks.map((subtask, index) => { // Assign the correct ID if it doesn't match - if (subtask.id !== startId + index) { + if (!subtask.id || subtask.id !== startId + index) { log( 'warn', - `Correcting subtask ID from ${subtask.id} to ${startId + index}` + `Correcting subtask ID from ${subtask.id || 'undefined'} to ${startId + index}` ); subtask.id = startId + index; } @@ -928,8 +932,10 @@ function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { // Ensure status is 'pending' subtask.status = 'pending'; - // Add parentTaskId - subtask.parentTaskId = parentTaskId; + // Add parentTaskId if provided + if (parentTaskId) { + subtask.parentTaskId = parentTaskId; + } return subtask; }); @@ -937,26 +943,8 @@ function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { return subtasks; } catch (error) { log('error', `Error parsing subtasks: ${error.message}`); - - // Create a fallback array of empty subtasks if parsing fails - log('warn', 'Creating fallback subtasks'); - - const fallbackSubtasks = []; - - for (let i = 0; i < expectedCount; i++) { - fallbackSubtasks.push({ - id: startId + i, - title: `Subtask ${startId + i}`, - description: 'Auto-generated fallback subtask', - dependencies: [], - details: - 'This is a fallback subtask created because parsing failed. Please update with real details.', - status: 'pending', - parentTaskId: parentTaskId - }); - } - - return fallbackSubtasks; + // Re-throw the error to be handled by the caller + throw error; } } diff --git a/scripts/modules/task-manager.js b/scripts/modules/task-manager.js index cd73b10a..80fd4870 100644 --- a/scripts/modules/task-manager.js +++ b/scripts/modules/task-manager.js @@ -2711,6 +2711,9 @@ async function expandAllTasks( } report(`Expanding all pending tasks with ${numSubtasks} subtasks each...`); + if (useResearch) { + report('Using research-backed AI for more detailed subtasks'); + } // Load tasks let data; @@ -2772,6 +2775,7 @@ async function expandAllTasks( } let expandedCount = 0; + let expansionErrors = 0; try { // Sort tasks by complexity if report exists, otherwise by ID if (complexityReport && complexityReport.complexityAnalysis) { @@ -2852,12 +2856,12 @@ async function expandAllTasks( mcpLog ); - if (aiResponse && aiResponse.subtasks) { + if (aiResponse && aiResponse.subtasks && Array.isArray(aiResponse.subtasks) && aiResponse.subtasks.length > 0) { // Process and add the subtasks to the task task.subtasks = aiResponse.subtasks.map((subtask, index) => ({ id: index + 1, - title: subtask.title, - description: subtask.description, + title: subtask.title || `Subtask ${index + 1}`, + description: subtask.description || 'No description provided', status: 'pending', dependencies: subtask.dependencies || [], details: subtask.details || '' @@ -2865,11 +2869,24 @@ async function expandAllTasks( report(`Added ${task.subtasks.length} subtasks to task ${task.id}`); expandedCount++; + } else if (aiResponse && aiResponse.error) { + // Handle error response + const errorMsg = `Failed to generate subtasks for task ${task.id}: ${aiResponse.error}`; + report(errorMsg, 'error'); + + // Add task ID to error info and provide actionable guidance + const suggestion = aiResponse.suggestion.replace('', task.id); + report(`Suggestion: ${suggestion}`, 'info'); + + expansionErrors++; } else { report(`Failed to generate subtasks for task ${task.id}`, 'error'); + report(`Suggestion: Run 'task-master update-task --id=${task.id} --prompt="Generate subtasks for this task"' to manually create subtasks.`, 'info'); + expansionErrors++; } } catch (error) { report(`Error expanding task ${task.id}: ${error.message}`, 'error'); + expansionErrors++; } // Small delay to prevent rate limiting @@ -2891,7 +2908,8 @@ async function expandAllTasks( success: true, expandedCount, tasksToExpand: tasksToExpand.length, - message: `Successfully expanded ${expandedCount} out of ${tasksToExpand.length} tasks` + expansionErrors, + message: `Successfully expanded ${expandedCount} out of ${tasksToExpand.length} tasks${expansionErrors > 0 ? ` (${expansionErrors} errors)` : ''}` }; } catch (error) { report(`Error expanding tasks: ${error.message}`, 'error'); @@ -5609,6 +5627,8 @@ async function getSubtasksFromAI( mcpLog.info('Calling AI to generate subtasks'); } + let responseText; + // Call the AI - with research if requested if (useResearch && perplexity) { if (mcpLog) { @@ -5633,8 +5653,7 @@ async function getSubtasksFromAI( max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens }); - const responseText = result.choices[0].message.content; - return parseSubtasksFromText(responseText); + responseText = result.choices[0].message.content; } else { // Use regular Claude if (mcpLog) { @@ -5642,14 +5661,39 @@ async function getSubtasksFromAI( } // Call the streaming API - const responseText = await _handleAnthropicStream( + responseText = await _handleAnthropicStream( client, apiParams, { mcpLog, silentMode: isSilentMode() }, !isSilentMode() ); + } - return parseSubtasksFromText(responseText); + // Ensure we have a valid response + if (!responseText) { + throw new Error('Empty response from AI'); + } + + // Try to parse the subtasks + try { + const parsedSubtasks = parseSubtasksFromText(responseText); + if (!parsedSubtasks || !Array.isArray(parsedSubtasks) || parsedSubtasks.length === 0) { + throw new Error('Failed to parse valid subtasks array from AI response'); + } + return { subtasks: parsedSubtasks }; + } catch (parseError) { + if (mcpLog) { + mcpLog.error(`Error parsing subtasks: ${parseError.message}`); + mcpLog.error(`Response start: ${responseText.substring(0, 200)}...`); + } else { + log('error', `Error parsing subtasks: ${parseError.message}`); + } + // Return error information instead of fallback subtasks + return { + error: parseError.message, + taskId: null, // This will be filled in by the calling function + suggestion: "Use 'task-master update-task --id= --prompt=\"Generate subtasks for this task\"' to manually create subtasks." + }; } } catch (error) { if (mcpLog) { @@ -5657,7 +5701,12 @@ async function getSubtasksFromAI( } else { log('error', `Error generating subtasks: ${error.message}`); } - throw error; + // Return error information instead of fallback subtasks + return { + error: error.message, + taskId: null, // This will be filled in by the calling function + suggestion: "Use 'task-master update-task --id= --prompt=\"Generate subtasks for this task\"' to manually create subtasks." + }; } }