Compare commits

...

1 Commits

Author SHA1 Message Date
Ralph Khreish
bd258c2e9c Revert "Update analyze-complexity with realtime feedback and enhanced complex…"
This reverts commit 16f4d4b932.
2025-04-02 19:24:48 +02:00
4 changed files with 78 additions and 697 deletions

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Improve analyze-complexity UI with realtime feedback.

View File

@@ -41,7 +41,6 @@ import {
displayNextTask, displayNextTask,
displayTaskById, displayTaskById,
displayComplexityReport, displayComplexityReport,
displayComplexityAnalysisStart,
getStatusWithColor, getStatusWithColor,
confirmTaskOverwrite confirmTaskOverwrite
} from './ui.js'; } from './ui.js';
@@ -465,8 +464,12 @@ function registerCommands(programInstance) {
const thresholdScore = parseFloat(options.threshold); const thresholdScore = parseFloat(options.threshold);
const useResearch = options.research || false; const useResearch = options.research || false;
// Call the dedicated UI function to display complexity analysis start information console.log(chalk.blue(`Analyzing task complexity from: ${tasksPath}`));
displayComplexityAnalysisStart(tasksPath, outputPath, useResearch, modelOverride || CONFIG.model, CONFIG.temperature); console.log(chalk.blue(`Output report will be saved to: ${outputPath}`));
if (useResearch) {
console.log(chalk.blue('Using Perplexity AI for research-backed complexity analysis'));
}
await analyzeTaskComplexity(options); await analyzeTaskComplexity(options);
}); });

View File

@@ -30,9 +30,7 @@ import {
getComplexityWithColor, getComplexityWithColor,
startLoadingIndicator, startLoadingIndicator,
stopLoadingIndicator, stopLoadingIndicator,
createProgressBar, createProgressBar
displayAnalysisProgress,
formatComplexitySummary
} from './ui.js'; } from './ui.js';
import { import {
@@ -302,47 +300,12 @@ Return only the updated tasks as a valid JSON array.`
}); });
// Process the stream // Process the stream
let responseText = ''; // Define responseText variable
try {
let chunkCount = 0;
let isProcessing = true;
// Add a local check that gets set to false if SIGINT is received
const originalSigintHandler = sigintHandler;
// Enhance the SIGINT handler to set isProcessing to false
sigintHandler = () => {
isProcessing = false;
// Call original handler to do the rest of cleanup and exit
if (originalSigintHandler) originalSigintHandler();
};
for await (const chunk of stream) { for await (const chunk of stream) {
// Check if we should stop processing (SIGINT received)
if (!isProcessing) {
break;
}
if (chunk.type === 'content_block_delta' && chunk.delta.text) { if (chunk.type === 'content_block_delta' && chunk.delta.text) {
responseText += chunk.delta.text; responseText += chunk.delta.text;
chunkCount++;
} }
} }
// Restore original handler if we didn't get interrupted
if (isProcessing) {
sigintHandler = originalSigintHandler;
}
} catch (streamError) {
// Clean up the interval even if there's an error
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
throw streamError;
}
if (streamingInterval) clearInterval(streamingInterval); if (streamingInterval) clearInterval(streamingInterval);
log('info', "Completed streaming response from Claude API!"); log('info', "Completed streaming response from Claude API!");
@@ -2131,47 +2094,12 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
}, 500); }, 500);
// Process the stream // Process the stream
console.log(chalk.yellow('[DEBUG] Starting to process Claude stream'));
try {
let chunkCount = 0;
let isProcessing = true;
// Add a local check that gets set to false if SIGINT is received
const originalSigintHandler = sigintHandler;
// Enhance the SIGINT handler to set isProcessing to false
sigintHandler = () => {
isProcessing = false;
// Call original handler to do the rest of cleanup and exit
if (originalSigintHandler) originalSigintHandler();
};
for await (const chunk of stream) { for await (const chunk of stream) {
// Check if we should stop processing (SIGINT received)
if (!isProcessing) {
break;
}
if (chunk.type === 'content_block_delta' && chunk.delta.text) { if (chunk.type === 'content_block_delta' && chunk.delta.text) {
fullResponse += chunk.delta.text; fullResponse += chunk.delta.text;
chunkCount++;
} }
} }
// Restore original handler if we didn't get interrupted
if (isProcessing) {
sigintHandler = originalSigintHandler;
}
} catch (streamError) {
// Clean up the interval even if there's an error
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
throw streamError;
}
if (streamingInterval) clearInterval(streamingInterval); if (streamingInterval) clearInterval(streamingInterval);
stopLoadingIndicator(loadingIndicator); stopLoadingIndicator(loadingIndicator);
@@ -2255,67 +2183,22 @@ async function analyzeTaskComplexity(options) {
const tasksPath = options.file || 'tasks/tasks.json'; const tasksPath = options.file || 'tasks/tasks.json';
const outputPath = options.output || 'scripts/task-complexity-report.json'; const outputPath = options.output || 'scripts/task-complexity-report.json';
const modelOverride = options.model; const modelOverride = options.model;
// Define streamingInterval at the top level of the function so the handler can access it
let streamingInterval = null;
// Add a debug listener at the process level to see if SIGINT is being received
const debugSignalListener = () => {};
process.on('SIGINT', debugSignalListener);
// Set up SIGINT (Control-C) handler to cancel the operation gracefully
let sigintHandler;
const registerSigintHandler = () => {
// Only register if not already registered
if (!sigintHandler) {
sigintHandler = () => {
// Try to clear any intervals before exiting
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
// Clear any terminal state
process.stdout.write('\r\x1B[K'); // Clear current line
console.log(chalk.yellow('\n\nAnalysis cancelled by user.'));
// Make sure we remove our event listeners before exiting
cleanupSigintHandler();
// Force exit after giving time for cleanup
setTimeout(() => {
process.exit(0);
}, 100);
};
process.on('SIGINT', sigintHandler);
}
};
// Clean up function to remove the handler when done
const cleanupSigintHandler = () => {
if (sigintHandler) {
process.removeListener('SIGINT', sigintHandler);
sigintHandler = null;
}
// Also remove the debug listener
process.removeListener('SIGINT', debugSignalListener);
};
const thresholdScore = parseFloat(options.threshold || '5'); const thresholdScore = parseFloat(options.threshold || '5');
const useResearch = options.research || false; const useResearch = options.research || false;
// Initialize error tracking variable console.log(chalk.blue(`Analyzing task complexity and generating expansion recommendations...`));
let apiError = false;
try { try {
// Read tasks.json // Read tasks.json
console.log(chalk.blue(`Reading tasks from ${tasksPath}...`));
const tasksData = readJSON(tasksPath); const tasksData = readJSON(tasksPath);
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks) || tasksData.tasks.length === 0) { if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks) || tasksData.tasks.length === 0) {
throw new Error('No tasks found in the tasks file'); throw new Error('No tasks found in the tasks file');
} }
console.log(chalk.blue(`Found ${tasksData.tasks.length} tasks to analyze.`));
// Prepare the prompt for the LLM // Prepare the prompt for the LLM
const prompt = generateComplexityAnalysisPrompt(tasksData); const prompt = generateComplexityAnalysisPrompt(tasksData);
@@ -2323,51 +2206,13 @@ async function analyzeTaskComplexity(options) {
const loadingIndicator = startLoadingIndicator('Calling AI to analyze task complexity...'); const loadingIndicator = startLoadingIndicator('Calling AI to analyze task complexity...');
let fullResponse = ''; let fullResponse = '';
let streamingInterval = null;
try { try {
// If research flag is set, use Perplexity first // If research flag is set, use Perplexity first
if (useResearch) { if (useResearch) {
try { try {
// Register SIGINT handler to allow cancellation with Control-C console.log(chalk.blue('Using Perplexity AI for research-backed complexity analysis...'));
registerSigintHandler();
// Start tracking elapsed time and update information display
const startTime = Date.now();
const totalTaskCount = tasksData.tasks.length;
// IMPORTANT: Stop the loading indicator before showing the progress bar
stopLoadingIndicator(loadingIndicator);
// Set up the progress data
const progressData = {
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
contextTokens: 0,
elapsed: 0,
temperature: CONFIG.temperature,
tasksAnalyzed: 0,
totalTasks: totalTaskCount,
percentComplete: 0,
maxTokens: CONFIG.maxTokens
};
// Estimate context tokens (rough approximation - 1 token ~= 4 chars)
const estimatedContextTokens = Math.ceil(prompt.length / 4);
progressData.contextTokens = estimatedContextTokens;
// Display initial progress before API call begins
displayAnalysisProgress(progressData);
// Update progress display at regular intervals
streamingInterval = setInterval(() => {
// Update elapsed time
progressData.elapsed = (Date.now() - startTime) / 1000;
progressData.percentComplete = Math.min(90, (progressData.elapsed / 30) * 100); // Estimate based on typical 30s completion
// Estimate number of tasks analyzed based on percentage
progressData.tasksAnalyzed = Math.floor((progressData.percentComplete / 100) * totalTaskCount);
displayAnalysisProgress(progressData);
}, 100);
// Modify prompt to include more context for Perplexity and explicitly request JSON // Modify prompt to include more context for Perplexity and explicitly request JSON
const researchPrompt = `You are conducting a detailed analysis of software development tasks to determine their complexity and how they should be broken down into subtasks. const researchPrompt = `You are conducting a detailed analysis of software development tasks to determine their complexity and how they should be broken down into subtasks.
@@ -2413,36 +2258,17 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
fullResponse = result.choices[0].message.content; fullResponse = result.choices[0].message.content;
console.log(chalk.green('Successfully generated complexity analysis with Perplexity AI')); console.log(chalk.green('Successfully generated complexity analysis with Perplexity AI'));
// Clean up the interval if (streamingInterval) clearInterval(streamingInterval);
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
// Show completion
progressData.percentComplete = 100;
progressData.tasksAnalyzed = progressData.totalTasks;
progressData.completed = true;
displayAnalysisProgress(progressData);
stopLoadingIndicator(loadingIndicator); stopLoadingIndicator(loadingIndicator);
// Log the first part of the response for debugging // ALWAYS log the first part of the response for debugging
console.debug(chalk.gray('Response first 200 chars:')); console.log(chalk.gray('Response first 200 chars:'));
console.debug(chalk.gray(fullResponse.substring(0, 200))); console.log(chalk.gray(fullResponse.substring(0, 200)));
} catch (perplexityError) { } catch (perplexityError) {
console.error(chalk.yellow('Falling back to Claude for complexity analysis...')); console.log(chalk.yellow('Falling back to Claude for complexity analysis...'));
console.error(chalk.gray('Perplexity error:'), perplexityError.message); console.log(chalk.gray('Perplexity error:'), perplexityError.message);
// Clean up
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
cleanupSigintHandler();
// Continue to Claude as fallback // Continue to Claude as fallback
console.log(chalk.yellow('\nFalling back to Claude after Perplexity error: ' + perplexityError.message));
await useClaudeForComplexityAnalysis(); await useClaudeForComplexityAnalysis();
} }
} else { } else {
@@ -2452,12 +2278,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
// Helper function to use Claude for complexity analysis // Helper function to use Claude for complexity analysis
async function useClaudeForComplexityAnalysis() { async function useClaudeForComplexityAnalysis() {
// Register SIGINT handler to allow cancellation with Control-C
registerSigintHandler();
// Call the LLM API with streaming // Call the LLM API with streaming
// Add try-catch for better error handling specifically for API call
try {
const stream = await anthropic.messages.create({ const stream = await anthropic.messages.create({
max_tokens: CONFIG.maxTokens, max_tokens: CONFIG.maxTokens,
model: modelOverride || CONFIG.model, model: modelOverride || CONFIG.model,
@@ -2467,54 +2288,13 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
stream: true stream: true
}); });
// Stop the default loading indicator before showing our custom UI // Update loading indicator to show streaming progress
stopLoadingIndicator(loadingIndicator); let dotCount = 0;
// Start tracking elapsed time and update information display
const startTime = Date.now();
const totalTaskCount = tasksData.tasks.length;
// Set up the progress data
const progressData = {
model: modelOverride || CONFIG.model,
contextTokens: 0, // Will estimate based on prompt size
elapsed: 0,
temperature: CONFIG.temperature,
tasksAnalyzed: 0,
totalTasks: totalTaskCount,
percentComplete: 0,
maxTokens: CONFIG.maxTokens
};
// Estimate context tokens (rough approximation - 1 token ~= 4 chars)
const estimatedContextTokens = Math.ceil(prompt.length / 4);
progressData.contextTokens = estimatedContextTokens;
// Display initial progress before streaming begins
displayAnalysisProgress(progressData);
// Update progress display at regular intervals
streamingInterval = setInterval(() => { streamingInterval = setInterval(() => {
// Update elapsed time readline.cursorTo(process.stdout, 0);
progressData.elapsed = (Date.now() - startTime) / 1000; process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`);
dotCount = (dotCount + 1) % 4;
// Estimate completion percentage based on response length }, 500);
if (fullResponse.length > 0) {
// Estimate based on expected response size (approx. 500 chars per task)
const expectedResponseSize = totalTaskCount * 500;
const estimatedProgress = Math.min(95, (fullResponse.length / expectedResponseSize) * 100);
progressData.percentComplete = estimatedProgress;
// Estimate analyzed tasks based on JSON objects found
const taskMatches = fullResponse.match(/"taskId"\s*:\s*\d+/g);
if (taskMatches) {
progressData.tasksAnalyzed = Math.min(totalTaskCount, taskMatches.length);
}
}
// Display the progress information
displayAnalysisProgress(progressData);
}, 100); // Update much more frequently for smoother animation
// Process the stream // Process the stream
for await (const chunk of stream) { for await (const chunk of stream) {
@@ -2523,44 +2303,10 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
} }
} }
// Clean up the interval - stop updating progress
if (streamingInterval) {
clearInterval(streamingInterval); clearInterval(streamingInterval);
streamingInterval = null; stopLoadingIndicator(loadingIndicator);
}
// Show completion message immediately console.log(chalk.green("Completed streaming response from Claude API!"));
progressData.percentComplete = 100;
progressData.elapsed = (Date.now() - startTime) / 1000;
progressData.tasksAnalyzed = progressData.totalTasks;
progressData.completed = true;
progressData.contextTokens = Math.max(progressData.contextTokens, estimatedContextTokens); // Ensure the final token count is accurate
displayAnalysisProgress(progressData);
// Clear the line completely to remove any artifacts (after showing completion)
process.stdout.write('\r\x1B[K'); // Clear current line
process.stdout.write('\r'); // Move cursor to beginning of line
} catch (apiError) {
// Handle specific API errors here
if (streamingInterval) clearInterval(streamingInterval);
process.stdout.write('\r\x1B[K'); // Clear current line
console.error(chalk.red(`\nAPI Error: ${apiError.message || 'Unknown error'}\n`));
console.log(chalk.yellow('This might be a temporary issue with the Claude API.'));
console.log(chalk.yellow('Please try again in a few moments or check your API key.'));
// Rethrow to be caught by outer handler
throw apiError;
} finally {
// Clean up SIGINT handler
cleanupSigintHandler();
// Ensure the interval is cleared
if (streamingInterval) {
clearInterval(streamingInterval);
streamingInterval = null;
}
}
} }
// Parse the JSON response // Parse the JSON response
@@ -2574,7 +2320,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
const codeBlockMatch = fullResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/); const codeBlockMatch = fullResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
if (codeBlockMatch) { if (codeBlockMatch) {
cleanedResponse = codeBlockMatch[1]; cleanedResponse = codeBlockMatch[1];
console.debug(chalk.blue("Extracted JSON from code block")); console.log(chalk.blue("Extracted JSON from code block"));
} else { } else {
// Look for a complete JSON array pattern // Look for a complete JSON array pattern
// This regex looks for an array of objects starting with [ and ending with ] // This regex looks for an array of objects starting with [ and ending with ]
@@ -2598,17 +2344,17 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
} }
// Log the cleaned response for debugging // Log the cleaned response for debugging
console.debug(chalk.gray("Attempting to parse cleaned JSON...")); console.log(chalk.gray("Attempting to parse cleaned JSON..."));
console.debug(chalk.gray("Cleaned response (first 100 chars):")); console.log(chalk.gray("Cleaned response (first 100 chars):"));
console.debug(chalk.gray(cleanedResponse.substring(0, 100))); console.log(chalk.gray(cleanedResponse.substring(0, 100)));
console.debug(chalk.gray("Last 100 chars:")); console.log(chalk.gray("Last 100 chars:"));
console.debug(chalk.gray(cleanedResponse.substring(cleanedResponse.length - 100))); console.log(chalk.gray(cleanedResponse.substring(cleanedResponse.length - 100)));
// More aggressive cleaning - strip any non-JSON content at the beginning or end // More aggressive cleaning - strip any non-JSON content at the beginning or end
const strictArrayMatch = cleanedResponse.match(/(\[\s*\{[\s\S]*\}\s*\])/); const strictArrayMatch = cleanedResponse.match(/(\[\s*\{[\s\S]*\}\s*\])/);
if (strictArrayMatch) { if (strictArrayMatch) {
cleanedResponse = strictArrayMatch[1]; cleanedResponse = strictArrayMatch[1];
console.debug(chalk.blue("Applied strict JSON array extraction")); console.log(chalk.blue("Applied strict JSON array extraction"));
} }
try { try {
@@ -2724,45 +2470,6 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
// Use the same AI model as the original analysis // Use the same AI model as the original analysis
if (useResearch) { if (useResearch) {
// Register SIGINT handler again to make sure it's active for this phase
registerSigintHandler();
// Start tracking elapsed time for missing tasks
const missingTasksStartTime = Date.now();
// Stop the loading indicator before showing progress
stopLoadingIndicator(missingTasksLoadingIndicator);
// Set up progress tracking for missing tasks
const missingProgressData = {
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
contextTokens: 0,
elapsed: 0,
temperature: CONFIG.temperature,
tasksAnalyzed: 0,
totalTasks: missingTaskIds.length,
percentComplete: 0,
maxTokens: CONFIG.maxTokens
};
// Estimate context tokens
const estimatedMissingContextTokens = Math.ceil(missingTasksPrompt.length / 4);
missingProgressData.contextTokens = estimatedMissingContextTokens;
// Display initial progress
displayAnalysisProgress(missingProgressData);
// Update progress display regularly
const missingTasksInterval = setInterval(() => {
missingProgressData.elapsed = (Date.now() - missingTasksStartTime) / 1000;
missingProgressData.percentComplete = Math.min(90, (missingProgressData.elapsed / 20) * 100); // Estimate ~20s completion
// Estimate number of tasks analyzed based on percentage
missingProgressData.tasksAnalyzed = Math.floor((missingProgressData.percentComplete / 100) * missingTaskIds.length);
displayAnalysisProgress(missingProgressData);
}, 100);
// Create the same research prompt but for missing tasks // Create the same research prompt but for missing tasks
const missingTasksResearchPrompt = `You are conducting a detailed analysis of software development tasks to determine their complexity and how they should be broken down into subtasks. const missingTasksResearchPrompt = `You are conducting a detailed analysis of software development tasks to determine their complexity and how they should be broken down into subtasks.
@@ -2787,7 +2494,6 @@ 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.`; DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`;
try {
const result = await perplexity.chat.completions.create({ const result = await perplexity.chat.completions.create({
model: process.env.PERPLEXITY_MODEL || 'sonar-pro', model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
messages: [ messages: [
@@ -2806,26 +2512,6 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
// Extract the response // Extract the response
missingAnalysisResponse = result.choices[0].message.content; missingAnalysisResponse = result.choices[0].message.content;
// Stop interval and show completion
clearInterval(missingTasksInterval);
missingProgressData.percentComplete = 100;
missingProgressData.tasksAnalyzed = missingProgressData.totalTasks;
missingProgressData.completed = true;
displayAnalysisProgress(missingProgressData);
} catch (error) {
// Clean up on error
if (missingTasksInterval) {
clearInterval(missingTasksInterval);
}
throw error;
} finally {
// Always clean up SIGINT handler and interval
cleanupSigintHandler();
if (missingTasksInterval) {
clearInterval(missingTasksInterval);
}
}
} else { } else {
// Use Claude // Use Claude
const stream = await anthropic.messages.create({ const stream = await anthropic.messages.create({
@@ -2861,7 +2547,7 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
const codeBlockMatch = missingAnalysisResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/); const codeBlockMatch = missingAnalysisResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
if (codeBlockMatch) { if (codeBlockMatch) {
cleanedResponse = codeBlockMatch[1]; cleanedResponse = codeBlockMatch[1];
console.debug(chalk.blue("Extracted JSON from code block for missing tasks")); console.log(chalk.blue("Extracted JSON from code block for missing tasks"));
} else { } else {
// Look for a complete JSON array pattern // Look for a complete JSON array pattern
const jsonArrayMatch = missingAnalysisResponse.match(/(\[\s*\{\s*"[^"]*"\s*:[\s\S]*\}\s*\])/); const jsonArrayMatch = missingAnalysisResponse.match(/(\[\s*\{\s*"[^"]*"\s*:[\s\S]*\}\s*\])/);
@@ -2994,43 +2680,24 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
const lowComplexity = complexityAnalysis.filter(t => t.complexityScore < 5).length; const lowComplexity = complexityAnalysis.filter(t => t.complexityScore < 5).length;
const totalAnalyzed = complexityAnalysis.length; const totalAnalyzed = complexityAnalysis.length;
// Only show summary if we didn't encounter an API error console.log('\nComplexity Analysis Summary:');
if (!apiError) { console.log('----------------------------');
// Create a summary object for formatting console.log(`Tasks in input file: ${tasksData.tasks.length}`);
const summary = { console.log(`Tasks successfully analyzed: ${totalAnalyzed}`);
totalTasks: tasksData.tasks.length, console.log(`High complexity tasks: ${highComplexity}`);
analyzedTasks: totalAnalyzed, console.log(`Medium complexity tasks: ${mediumComplexity}`);
highComplexityCount: highComplexity, console.log(`Low complexity tasks: ${lowComplexity}`);
mediumComplexityCount: mediumComplexity, console.log(`Sum verification: ${highComplexity + mediumComplexity + lowComplexity} (should equal ${totalAnalyzed})`);
lowComplexityCount: lowComplexity, console.log(`Research-backed analysis: ${useResearch ? 'Yes' : 'No'}`);
researchBacked: useResearch console.log(`\nSee ${outputPath} for the full report and expansion commands.`);
};
// Use the new formatting function from UI module
console.log(formatComplexitySummary(summary));
}
} catch (error) { } catch (error) {
if (streamingInterval) clearInterval(streamingInterval); if (streamingInterval) clearInterval(streamingInterval);
stopLoadingIndicator(loadingIndicator); stopLoadingIndicator(loadingIndicator);
throw error;
// Mark that we encountered an API error
apiError = true;
// Display a user-friendly error message
console.error(chalk.red(`\nAPI Error: ${error.message || 'Unknown error'}\n`));
console.log(chalk.yellow('This might be a temporary issue with the Claude API.'));
console.log(chalk.yellow('Please try again in a few moments.'));
cleanupSigintHandler();
// We'll continue with any tasks we might have analyzed before the error
} }
} catch (error) { } catch (error) {
console.error(chalk.red(`Error analyzing task complexity: ${error.message}`)); console.error(chalk.red(`Error analyzing task complexity: ${error.message}`));
// Clean up SIGINT handler
cleanupSigintHandler();
process.exit(1); process.exit(1);
} }
} }

View File

@@ -1061,255 +1061,6 @@ async function displayComplexityReport(reportPath) {
)); ));
} }
/**
* Display real-time analysis progress with detailed information in a single line format
* @param {Object} progressData - Object containing progress information
* @param {string} progressData.model - Model name (e.g., 'claude-3-7-sonnet-20250219')
* @param {number} progressData.contextTokens - Context tokens used
* @param {number} progressData.elapsed - Elapsed time in seconds
* @param {number} progressData.temperature - Temperature setting
* @param {number} progressData.tasksAnalyzed - Number of tasks analyzed so far
* @param {number} progressData.totalTasks - Total number of tasks to analyze
* @param {number} progressData.percentComplete - Percentage complete (0-100)
* @param {number} progressData.maxTokens - Maximum tokens setting
* @param {boolean} progressData.completed - Whether the process is completed
* @returns {void}
*/
function displayAnalysisProgress(progressData) {
const {
model,
contextTokens = 0,
elapsed = 0,
temperature = 0.7,
tasksAnalyzed = 0,
totalTasks = 0,
percentComplete = 0,
maxTokens = 0,
completed = false
} = progressData;
// Format the elapsed time
const timeDisplay = formatElapsedTime(elapsed);
// Use static variables to track display state
if (displayAnalysisProgress.initialized === undefined) {
displayAnalysisProgress.initialized = false;
displayAnalysisProgress.lastUpdate = Date.now();
displayAnalysisProgress.statusLineStarted = false;
}
// Create progress bar (20 characters wide)
const progressBarWidth = 20;
const percentText = `${Math.round(percentComplete)}%`;
const percentTextLength = percentText.length;
// Calculate expected total tokens and current progress
const totalTokens = contextTokens; // Use the actual token count as the total
// Calculate current tokens based on percentage complete to show gradual increase from 0 to totalTokens
const currentTokens = completed ? totalTokens : Math.min(totalTokens, Math.round((percentComplete / 100) * totalTokens));
// Format token counts with proper padding
const totalTokenDigits = totalTokens.toString().length;
const currentTokensFormatted = currentTokens.toString().padStart(totalTokenDigits, '0');
const tokenDisplay = `${currentTokensFormatted}/${totalTokens}`;
// Calculate position for centered percentage
const halfBarWidth = Math.floor(progressBarWidth / 2);
const percentStartPos = Math.max(0, halfBarWidth - Math.floor(percentTextLength / 2));
// Calculate how many filled and empty chars to draw
const filledChars = Math.floor((percentComplete / 100) * progressBarWidth);
// Create the progress bar with centered percentage (without gradient)
let progressBar = '';
for (let i = 0; i < progressBarWidth; i++) {
// If we're at the start position for the percentage text
if (i === percentStartPos) {
// Apply bold white for percentage text to stand out
progressBar += chalk.bold.white(percentText);
// Skip ahead by the length of the percentage text
i += percentTextLength - 1;
} else if (i < filledChars) {
// Use a single color instead of gradient
progressBar += chalk.cyan('█');
} else {
// Use a subtle character for empty space
progressBar += chalk.gray('░');
}
}
// Use spinner from ora - these are the actual frames used in the default spinner
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
// Increment the counter faster to speed up the animation
if (!displayAnalysisProgress.frameCounter) {
displayAnalysisProgress.frameCounter = 0;
}
if (!displayAnalysisProgress.updateToggle) {
displayAnalysisProgress.updateToggle = false;
}
// Toggle between updating and not updating to halve the speed
displayAnalysisProgress.updateToggle = !displayAnalysisProgress.updateToggle;
// Only update every other call to make animation half as fast
if (displayAnalysisProgress.updateToggle) {
displayAnalysisProgress.frameCounter = (displayAnalysisProgress.frameCounter + 1) % spinnerFrames.length;
}
const spinner = chalk.cyan(spinnerFrames[displayAnalysisProgress.frameCounter]);
// Format status line based on whether we're complete or not
let statusLine;
if (completed) {
// For completed progress, show checkmark and "Complete" text
statusLine =
` ${chalk.cyan('⏱')} ${timeDisplay} ${chalk.gray('|')} ` +
`Tasks: ${chalk.bold(tasksAnalyzed)}/${totalTasks} ${chalk.gray('|')} ` +
`Tokens: ${tokenDisplay} ${chalk.gray('|')} ` +
`${progressBar} ${chalk.gray('|')} ` +
`${chalk.green('✅')} ${chalk.green('Complete')}`;
} else {
// For in-progress, show spinner and "Processing" text
statusLine =
` ${chalk.cyan('⏱')} ${timeDisplay} ${chalk.gray('|')} ` +
`Tasks: ${chalk.bold(tasksAnalyzed)}/${totalTasks} ${chalk.gray('|')} ` +
`Tokens: ${tokenDisplay} ${chalk.gray('|')} ` +
`${progressBar} ${chalk.gray('|')} ` +
`${chalk.cyan('Processing')} ${spinner}`;
}
// Clear the line and update the status
process.stdout.write('\r\x1B[K');
process.stdout.write(statusLine);
// Additional handling for completion
if (completed) {
// Move to next line and print completion message in a box
process.stdout.write('\n\n');
console.log(boxen(
chalk.green(`Task complexity analysis completed in ${timeDisplay}`) + '\n' +
chalk.green(`✅ Analyzed ${tasksAnalyzed} tasks successfully.`),
{
padding: { top: 1, bottom: 1, left: 2, right: 2 },
margin: { top: 0, bottom: 1 },
borderColor: 'green',
borderStyle: 'round'
}
));
// Reset initialization state for next run
displayAnalysisProgress.initialized = undefined;
displayAnalysisProgress.statusLineStarted = false;
}
}
/**
* Format elapsed time in the format shown in the screenshot (0m 00s)
* @param {number} seconds - Elapsed time in seconds
* @returns {string} Formatted time string
*/
function formatElapsedTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}m ${remainingSeconds.toString().padStart(2, '0')}s`;
}
/**
* Format a complexity summary from analyze-complexity with a neat boxed display
* @param {Object} summary The complexity analysis summary
* @returns {string} The formatted summary
*/
function formatComplexitySummary(summary) {
// Calculate verification sum
const sumTotal = summary.highComplexityCount + summary.mediumComplexityCount + summary.lowComplexityCount;
const verificationStatus = sumTotal === summary.analyzedTasks ? chalk.green('✅') : chalk.red('✗');
// Create a table for better alignment
const table = new Table({
chars: {
'top': '', 'top-mid': '', 'top-left': '', 'top-right': '',
'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '',
'right': '', 'right-mid': '', 'middle': ' '
},
style: { border: [], 'padding-left': 2 },
colWidths: [28, 50]
});
// Basic info
table.push(
[chalk.cyan('Tasks in input file:'), chalk.bold(summary.totalTasks)],
[chalk.cyan('Tasks analyzed:'), chalk.bold(summary.analyzedTasks)]
);
// Complexity distribution in one row
const percentHigh = Math.round((summary.highComplexityCount / summary.analyzedTasks) * 100);
const percentMed = Math.round((summary.mediumComplexityCount / summary.analyzedTasks) * 100);
const percentLow = Math.round((summary.lowComplexityCount / summary.analyzedTasks) * 100);
const complexityRow = [
chalk.cyan('Complexity distribution:'),
`${chalk.hex('#CC0000').bold(summary.highComplexityCount)} ${chalk.hex('#CC0000')('High')} (${percentHigh}%) · ` +
`${chalk.hex('#FF8800').bold(summary.mediumComplexityCount)} ${chalk.hex('#FF8800')('Medium')} (${percentMed}%) · ` +
`${chalk.yellow.bold(summary.lowComplexityCount)} ${chalk.yellow('Low')} (${percentLow}%)`
];
table.push(complexityRow);
// Visual bar representation of complexity distribution
const barWidth = 40; // Total width of the bar
// Only show bars for categories with at least 1 task
const highChars = summary.highComplexityCount > 0 ?
Math.max(1, Math.round((summary.highComplexityCount / summary.analyzedTasks) * barWidth)) : 0;
const medChars = summary.mediumComplexityCount > 0 ?
Math.max(1, Math.round((summary.mediumComplexityCount / summary.analyzedTasks) * barWidth)) : 0;
const lowChars = summary.lowComplexityCount > 0 ?
Math.max(1, Math.round((summary.lowComplexityCount / summary.analyzedTasks) * barWidth)) : 0;
// Adjust bar width if some categories have 0 tasks
const actualBarWidth = highChars + medChars + lowChars;
const distributionBar =
chalk.hex('#CC0000')('█'.repeat(highChars)) +
chalk.hex('#FF8800')('█'.repeat(medChars)) +
chalk.yellow('█'.repeat(lowChars)) +
// Add empty space if actual bar is shorter than expected
(actualBarWidth < barWidth ? chalk.gray('░'.repeat(barWidth - actualBarWidth)) : '');
table.push([chalk.cyan('Distribution:'), distributionBar]);
// Add verification and research status
table.push(
[chalk.cyan('Verification:'), `${verificationStatus} ${sumTotal}/${summary.analyzedTasks}`],
[chalk.cyan('Research-backed:'), summary.researchBacked ? chalk.green('✅') : 'No']
);
// Final string output with title and footer
const output = [
chalk.bold.underline('Complexity Analysis Summary'),
'',
table.toString(),
'',
`Report saved to: ${chalk.italic('scripts/task-complexity-report.json')}`
].join('\n');
// Return a boxed version
return boxen(output, {
padding: { top: 1, right: 1, bottom: 1, left: 1 },
borderColor: 'blue',
borderStyle: 'round',
margin: { top: 1, right: 1, bottom: 1, left: 0 }
});
}
/** /**
* Confirm overwriting existing tasks.json file * Confirm overwriting existing tasks.json file
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
@@ -1337,38 +1088,6 @@ async function confirmTaskOverwrite(tasksPath) {
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
} }
/**
* Display the start of complexity analysis with a boxen announcement
* @param {string} tasksPath - Path to the tasks file being analyzed
* @param {string} outputPath - Path where the report will be saved
* @param {boolean} useResearch - Whether Perplexity AI research is enabled
* @param {string} model - AI model name
* @param {number} temperature - AI temperature setting
*/
function displayComplexityAnalysisStart(tasksPath, outputPath, useResearch = false, model = CONFIG.model, temperature = CONFIG.temperature) {
// Create the message content with all information
let message = chalk.bold(`🤖 Analyzing Task Complexity`) + '\n' +
chalk.dim(`Model: ${model} | Temperature: ${temperature}`) + '\n\n' +
chalk.blue(`Input: ${tasksPath}`) + '\n' +
chalk.blue(`Output: ${outputPath}`);
// Add research info if enabled
if (useResearch) {
message += '\n' + chalk.blue('Using Perplexity AI for research-backed analysis');
}
// Display everything in a single boxen
console.log(boxen(
message,
{
padding: { top: 1, bottom: 1, left: 2, right: 2 },
margin: { top: 0, bottom: 0 },
borderColor: 'blue',
borderStyle: 'round'
}
));
}
// Export UI functions // Export UI functions
export { export {
displayBanner, displayBanner,
@@ -1381,9 +1100,6 @@ export {
getComplexityWithColor, getComplexityWithColor,
displayNextTask, displayNextTask,
displayTaskById, displayTaskById,
displayComplexityAnalysisStart,
displayComplexityReport, displayComplexityReport,
displayAnalysisProgress,
formatComplexitySummary,
confirmTaskOverwrite confirmTaskOverwrite
}; };