Update analyze-complexity with realtime feedback and enhanced complexity report (#70)
* Update analyze-complexity with realtime feedback * PR fixes * include changeset
This commit is contained in:
@@ -30,7 +30,9 @@ import {
|
||||
getComplexityWithColor,
|
||||
startLoadingIndicator,
|
||||
stopLoadingIndicator,
|
||||
createProgressBar
|
||||
createProgressBar,
|
||||
displayAnalysisProgress,
|
||||
formatComplexitySummary
|
||||
} from './ui.js';
|
||||
|
||||
import {
|
||||
@@ -300,10 +302,45 @@ Return only the updated tasks as a valid JSON array.`
|
||||
});
|
||||
|
||||
// Process the stream
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||
responseText += chunk.delta.text;
|
||||
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) {
|
||||
// Check if we should stop processing (SIGINT received)
|
||||
if (!isProcessing) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk.type === 'content_block_delta' && 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);
|
||||
@@ -2094,10 +2131,45 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
|
||||
}, 500);
|
||||
|
||||
// Process the stream
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||
fullResponse += chunk.delta.text;
|
||||
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) {
|
||||
// Check if we should stop processing (SIGINT received)
|
||||
if (!isProcessing) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk.type === 'content_block_delta' && 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);
|
||||
@@ -2183,22 +2255,67 @@ async function analyzeTaskComplexity(options) {
|
||||
const tasksPath = options.file || 'tasks/tasks.json';
|
||||
const outputPath = options.output || 'scripts/task-complexity-report.json';
|
||||
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 useResearch = options.research || false;
|
||||
|
||||
console.log(chalk.blue(`Analyzing task complexity and generating expansion recommendations...`));
|
||||
// Initialize error tracking variable
|
||||
let apiError = false;
|
||||
|
||||
try {
|
||||
// Read tasks.json
|
||||
console.log(chalk.blue(`Reading tasks from ${tasksPath}...`));
|
||||
const tasksData = readJSON(tasksPath);
|
||||
|
||||
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks) || tasksData.tasks.length === 0) {
|
||||
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
|
||||
const prompt = generateComplexityAnalysisPrompt(tasksData);
|
||||
|
||||
@@ -2206,13 +2323,51 @@ async function analyzeTaskComplexity(options) {
|
||||
const loadingIndicator = startLoadingIndicator('Calling AI to analyze task complexity...');
|
||||
|
||||
let fullResponse = '';
|
||||
let streamingInterval = null;
|
||||
|
||||
try {
|
||||
// If research flag is set, use Perplexity first
|
||||
if (useResearch) {
|
||||
try {
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed complexity analysis...'));
|
||||
// Register SIGINT handler to allow cancellation with Control-C
|
||||
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
|
||||
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.
|
||||
@@ -2258,17 +2413,36 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
fullResponse = result.choices[0].message.content;
|
||||
console.log(chalk.green('Successfully generated complexity analysis with Perplexity AI'));
|
||||
|
||||
if (streamingInterval) clearInterval(streamingInterval);
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
// Clean up the interval
|
||||
if (streamingInterval) {
|
||||
clearInterval(streamingInterval);
|
||||
streamingInterval = null;
|
||||
}
|
||||
|
||||
// ALWAYS log the first part of the response for debugging
|
||||
console.log(chalk.gray('Response first 200 chars:'));
|
||||
console.log(chalk.gray(fullResponse.substring(0, 200)));
|
||||
// Show completion
|
||||
progressData.percentComplete = 100;
|
||||
progressData.tasksAnalyzed = progressData.totalTasks;
|
||||
progressData.completed = true;
|
||||
displayAnalysisProgress(progressData);
|
||||
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
|
||||
// Log the first part of the response for debugging
|
||||
console.debug(chalk.gray('Response first 200 chars:'));
|
||||
console.debug(chalk.gray(fullResponse.substring(0, 200)));
|
||||
} catch (perplexityError) {
|
||||
console.log(chalk.yellow('Falling back to Claude for complexity analysis...'));
|
||||
console.log(chalk.gray('Perplexity error:'), perplexityError.message);
|
||||
console.error(chalk.yellow('Falling back to Claude for complexity analysis...'));
|
||||
console.error(chalk.gray('Perplexity error:'), perplexityError.message);
|
||||
|
||||
// Clean up
|
||||
if (streamingInterval) {
|
||||
clearInterval(streamingInterval);
|
||||
streamingInterval = null;
|
||||
}
|
||||
cleanupSigintHandler();
|
||||
|
||||
// Continue to Claude as fallback
|
||||
console.log(chalk.yellow('\nFalling back to Claude after Perplexity error: ' + perplexityError.message));
|
||||
await useClaudeForComplexityAnalysis();
|
||||
}
|
||||
} else {
|
||||
@@ -2278,8 +2452,13 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
|
||||
// Helper function to use Claude for complexity analysis
|
||||
async function useClaudeForComplexityAnalysis() {
|
||||
// Register SIGINT handler to allow cancellation with Control-C
|
||||
registerSigintHandler();
|
||||
|
||||
// Call the LLM API with streaming
|
||||
const stream = await anthropic.messages.create({
|
||||
// Add try-catch for better error handling specifically for API call
|
||||
try {
|
||||
const stream = await anthropic.messages.create({
|
||||
max_tokens: CONFIG.maxTokens,
|
||||
model: modelOverride || CONFIG.model,
|
||||
temperature: CONFIG.temperature,
|
||||
@@ -2288,13 +2467,54 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
stream: true
|
||||
});
|
||||
|
||||
// Update loading indicator to show streaming progress
|
||||
let dotCount = 0;
|
||||
// Stop the default loading indicator before showing our custom UI
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
|
||||
// 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(() => {
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`);
|
||||
dotCount = (dotCount + 1) % 4;
|
||||
}, 500);
|
||||
// Update elapsed time
|
||||
progressData.elapsed = (Date.now() - startTime) / 1000;
|
||||
|
||||
// Estimate completion percentage based on response length
|
||||
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
|
||||
for await (const chunk of stream) {
|
||||
@@ -2303,14 +2523,48 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
}
|
||||
}
|
||||
|
||||
clearInterval(streamingInterval);
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
// Clean up the interval - stop updating progress
|
||||
if (streamingInterval) {
|
||||
clearInterval(streamingInterval);
|
||||
streamingInterval = null;
|
||||
}
|
||||
|
||||
console.log(chalk.green("Completed streaming response from Claude API!"));
|
||||
// Show completion message immediately
|
||||
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
|
||||
console.log(chalk.blue(`Parsing complexity analysis...`));
|
||||
console.log(chalk.blue(` Parsing complexity analysis...`));
|
||||
let complexityAnalysis;
|
||||
try {
|
||||
// Clean up the response to ensure it's valid JSON
|
||||
@@ -2320,14 +2574,14 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
const codeBlockMatch = fullResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
||||
if (codeBlockMatch) {
|
||||
cleanedResponse = codeBlockMatch[1];
|
||||
console.log(chalk.blue("Extracted JSON from code block"));
|
||||
console.debug(chalk.blue("Extracted JSON from code block"));
|
||||
} else {
|
||||
// Look for a complete JSON array pattern
|
||||
// This regex looks for an array of objects starting with [ and ending with ]
|
||||
const jsonArrayMatch = fullResponse.match(/(\[\s*\{\s*"[^"]*"\s*:[\s\S]*\}\s*\])/);
|
||||
if (jsonArrayMatch) {
|
||||
cleanedResponse = jsonArrayMatch[1];
|
||||
console.log(chalk.blue("Extracted JSON array pattern"));
|
||||
console.log(chalk.blue(" Extracted JSON array pattern"));
|
||||
} else {
|
||||
// Try to find the start of a JSON array and capture to the end
|
||||
const jsonStartMatch = fullResponse.match(/(\[\s*\{[\s\S]*)/);
|
||||
@@ -2342,19 +2596,19 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Log the cleaned response for debugging
|
||||
console.log(chalk.gray("Attempting to parse cleaned JSON..."));
|
||||
console.log(chalk.gray("Cleaned response (first 100 chars):"));
|
||||
console.log(chalk.gray(cleanedResponse.substring(0, 100)));
|
||||
console.log(chalk.gray("Last 100 chars:"));
|
||||
console.log(chalk.gray(cleanedResponse.substring(cleanedResponse.length - 100)));
|
||||
console.debug(chalk.gray("Attempting to parse cleaned JSON..."));
|
||||
console.debug(chalk.gray("Cleaned response (first 100 chars):"));
|
||||
console.debug(chalk.gray(cleanedResponse.substring(0, 100)));
|
||||
console.debug(chalk.gray("Last 100 chars:"));
|
||||
console.debug(chalk.gray(cleanedResponse.substring(cleanedResponse.length - 100)));
|
||||
|
||||
// More aggressive cleaning - strip any non-JSON content at the beginning or end
|
||||
const strictArrayMatch = cleanedResponse.match(/(\[\s*\{[\s\S]*\}\s*\])/);
|
||||
if (strictArrayMatch) {
|
||||
cleanedResponse = strictArrayMatch[1];
|
||||
console.log(chalk.blue("Applied strict JSON array extraction"));
|
||||
console.debug(chalk.blue("Applied strict JSON array extraction"));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -2470,6 +2724,45 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
|
||||
// Use the same AI model as the original analysis
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -2494,24 +2787,45 @@ 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.`;
|
||||
|
||||
const result = await perplexity.chat.completions.create({
|
||||
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a technical analysis AI that only responds with clean, valid JSON. Never include explanatory text or markdown formatting in your response."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: missingTasksResearchPrompt
|
||||
}
|
||||
],
|
||||
temperature: CONFIG.temperature,
|
||||
max_tokens: CONFIG.maxTokens,
|
||||
});
|
||||
|
||||
// Extract the response
|
||||
missingAnalysisResponse = result.choices[0].message.content;
|
||||
try {
|
||||
const result = await perplexity.chat.completions.create({
|
||||
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a technical analysis AI that only responds with clean, valid JSON. Never include explanatory text or markdown formatting in your response."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: missingTasksResearchPrompt
|
||||
}
|
||||
],
|
||||
temperature: CONFIG.temperature,
|
||||
max_tokens: CONFIG.maxTokens,
|
||||
});
|
||||
|
||||
// Extract the response
|
||||
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 {
|
||||
// Use Claude
|
||||
const stream = await anthropic.messages.create({
|
||||
@@ -2547,7 +2861,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*```/);
|
||||
if (codeBlockMatch) {
|
||||
cleanedResponse = codeBlockMatch[1];
|
||||
console.log(chalk.blue("Extracted JSON from code block for missing tasks"));
|
||||
console.debug(chalk.blue("Extracted JSON from code block for missing tasks"));
|
||||
} else {
|
||||
// Look for a complete JSON array pattern
|
||||
const jsonArrayMatch = missingAnalysisResponse.match(/(\[\s*\{\s*"[^"]*"\s*:[\s\S]*\}\s*\])/);
|
||||
@@ -2669,10 +2983,10 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
||||
};
|
||||
|
||||
// Write the report to file
|
||||
console.log(chalk.blue(`Writing complexity report to ${outputPath}...`));
|
||||
console.log(chalk.blue(` Writing complexity report to ${outputPath}...`));
|
||||
writeJSON(outputPath, report);
|
||||
|
||||
console.log(chalk.green(`Task complexity analysis complete. Report written to ${outputPath}`));
|
||||
console.log(chalk.green(` Task complexity analysis complete. Report written to ${outputPath}`));
|
||||
|
||||
// Display a summary of findings
|
||||
const highComplexity = complexityAnalysis.filter(t => t.complexityScore >= 8).length;
|
||||
@@ -2680,24 +2994,43 @@ 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 totalAnalyzed = 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.`);
|
||||
// Only show summary if we didn't encounter an API error
|
||||
if (!apiError) {
|
||||
// Create a summary object for formatting
|
||||
const summary = {
|
||||
totalTasks: tasksData.tasks.length,
|
||||
analyzedTasks: totalAnalyzed,
|
||||
highComplexityCount: highComplexity,
|
||||
mediumComplexityCount: mediumComplexity,
|
||||
lowComplexityCount: lowComplexity,
|
||||
researchBacked: useResearch
|
||||
};
|
||||
|
||||
// Use the new formatting function from UI module
|
||||
console.log(formatComplexitySummary(summary));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (streamingInterval) clearInterval(streamingInterval);
|
||||
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) {
|
||||
console.error(chalk.red(`Error analyzing task complexity: ${error.message}`));
|
||||
|
||||
// Clean up SIGINT handler
|
||||
cleanupSigintHandler();
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user