mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-26 18:33:45 +00:00

- Enhance E2E testing and LLM analysis report and: - Add --analyze-log flag to run_e2e.sh to re-run LLM analysis on existing logs. - Add test:e2e and analyze-log scripts to package.json for easier execution. - Correct display errors and dependency validation output: - Update chalk usage in add-task.js to use bracket notation (chalk[color]) compatible with v5, resolving 'chalk.keyword is not a function' error. - Modify fix-dependencies command output to show red failure box with issue count instead of green success box when validation fails. - Refactor interactive model setup: - Verify inclusion of 'No change' option during interactive model setup flow (task-master models --setup). - Update model definitions: - Add max_tokens field for gpt-4o in supported-models.json. - Remove unused scripts: - Delete prepare-package.js and rule-transformer.test.js. Release candidate
267 lines
8.2 KiB
JavaScript
267 lines
8.2 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
// Note: We will use dynamic import() inside the async callback due to project being type: module
|
||
|
||
const readline = require('readline');
|
||
const path = require('path'); // Import path module
|
||
|
||
let inputData = '';
|
||
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
terminal: false
|
||
});
|
||
|
||
rl.on('line', (line) => {
|
||
inputData += line;
|
||
});
|
||
|
||
// Make the callback async to allow await for dynamic imports
|
||
rl.on('close', async () => {
|
||
let chalk, boxen, Table;
|
||
try {
|
||
// Dynamically import libraries
|
||
chalk = (await import('chalk')).default;
|
||
boxen = (await import('boxen')).default;
|
||
Table = (await import('cli-table3')).default;
|
||
|
||
// 1. Parse the initial API response body
|
||
const apiResponse = JSON.parse(inputData);
|
||
|
||
// 2. Extract the text content containing the nested JSON
|
||
// Robust check for content structure
|
||
const textContent = apiResponse?.content?.[0]?.text;
|
||
if (!textContent) {
|
||
console.error(
|
||
chalk.red(
|
||
"Error: Could not find '.content[0].text' in the API response JSON."
|
||
)
|
||
);
|
||
process.exit(1);
|
||
}
|
||
|
||
// 3. Find the start of the actual JSON block
|
||
const jsonStart = textContent.indexOf('{');
|
||
const jsonEnd = textContent.lastIndexOf('}');
|
||
|
||
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart) {
|
||
console.error(
|
||
chalk.red(
|
||
'Error: Could not find JSON block starting with { and ending with } in the extracted text content.'
|
||
)
|
||
);
|
||
process.exit(1);
|
||
}
|
||
const jsonString = textContent.substring(jsonStart, jsonEnd + 1);
|
||
|
||
// 4. Parse the extracted JSON string
|
||
let reportData;
|
||
try {
|
||
reportData = JSON.parse(jsonString);
|
||
} catch (parseError) {
|
||
console.error(
|
||
chalk.red('Error: Failed to parse the extracted JSON block.')
|
||
);
|
||
console.error(chalk.red('Parse Error:'), parseError.message);
|
||
process.exit(1);
|
||
}
|
||
|
||
// Ensure reportData is an object
|
||
if (typeof reportData !== 'object' || reportData === null) {
|
||
console.error(
|
||
chalk.red('Error: Parsed report data is not a valid object.')
|
||
);
|
||
process.exit(1);
|
||
}
|
||
|
||
// --- Get Log File Path and Format Timestamp ---
|
||
const logFilePath = process.argv[2]; // Get the log file path argument
|
||
let formattedTime = 'Unknown';
|
||
if (logFilePath) {
|
||
const logBasename = path.basename(logFilePath);
|
||
const timestampMatch = logBasename.match(/e2e_run_(\d{8}_\d{6})\.log$/);
|
||
if (timestampMatch && timestampMatch[1]) {
|
||
const ts = timestampMatch[1]; // YYYYMMDD_HHMMSS
|
||
// Format into YYYY-MM-DD HH:MM:SS
|
||
formattedTime = `${ts.substring(0, 4)}-${ts.substring(4, 6)}-${ts.substring(6, 8)} ${ts.substring(9, 11)}:${ts.substring(11, 13)}:${ts.substring(13, 15)}`;
|
||
}
|
||
}
|
||
// --------------------------------------------
|
||
|
||
// 5. Generate CLI Report (with defensive checks)
|
||
console.log(
|
||
'\n' +
|
||
chalk.cyan.bold(
|
||
boxen(
|
||
`TASKMASTER E2E Log Analysis Report\nRun Time: ${chalk.yellow(formattedTime)}`, // Display formatted time
|
||
{
|
||
padding: 1,
|
||
borderStyle: 'double',
|
||
borderColor: 'cyan',
|
||
textAlign: 'center' // Center align title
|
||
}
|
||
)
|
||
) +
|
||
'\n'
|
||
);
|
||
|
||
// Overall Status
|
||
let statusColor = chalk.white;
|
||
const overallStatus = reportData.overall_status || 'Unknown'; // Default if missing
|
||
if (overallStatus === 'Success') statusColor = chalk.green.bold;
|
||
if (overallStatus === 'Warning') statusColor = chalk.yellow.bold;
|
||
if (overallStatus === 'Failure') statusColor = chalk.red.bold;
|
||
console.log(
|
||
boxen(`Overall Status: ${statusColor(overallStatus)}`, {
|
||
padding: { left: 1, right: 1 },
|
||
margin: { bottom: 1 },
|
||
borderColor: 'blue'
|
||
})
|
||
);
|
||
|
||
// LLM Summary Points
|
||
console.log(chalk.blue.bold('📋 Summary Points:'));
|
||
if (
|
||
Array.isArray(reportData.llm_summary_points) &&
|
||
reportData.llm_summary_points.length > 0
|
||
) {
|
||
reportData.llm_summary_points.forEach((point) => {
|
||
console.log(chalk.white(` - ${point || 'N/A'}`)); // Handle null/undefined points
|
||
});
|
||
} else {
|
||
console.log(chalk.gray(' No summary points provided.'));
|
||
}
|
||
console.log();
|
||
|
||
// Verified Steps
|
||
console.log(chalk.green.bold('✅ Verified Steps:'));
|
||
if (
|
||
Array.isArray(reportData.verified_steps) &&
|
||
reportData.verified_steps.length > 0
|
||
) {
|
||
reportData.verified_steps.forEach((step) => {
|
||
console.log(chalk.green(` - ${step || 'N/A'}`)); // Handle null/undefined steps
|
||
});
|
||
} else {
|
||
console.log(chalk.gray(' No verified steps listed.'));
|
||
}
|
||
console.log();
|
||
|
||
// Provider Add-Task Comparison
|
||
console.log(chalk.magenta.bold('🔄 Provider Add-Task Comparison:'));
|
||
const comp = reportData.provider_add_task_comparison;
|
||
if (typeof comp === 'object' && comp !== null) {
|
||
console.log(
|
||
chalk.white(` Prompt Used: ${comp.prompt_used || 'Not specified'}`)
|
||
);
|
||
console.log();
|
||
|
||
if (
|
||
typeof comp.provider_results === 'object' &&
|
||
comp.provider_results !== null &&
|
||
Object.keys(comp.provider_results).length > 0
|
||
) {
|
||
const providerTable = new Table({
|
||
head: ['Provider', 'Status', 'Task ID', 'Score', 'Notes'].map((h) =>
|
||
chalk.magenta.bold(h)
|
||
),
|
||
colWidths: [15, 18, 10, 12, 45],
|
||
style: { head: [], border: [] },
|
||
wordWrap: true
|
||
});
|
||
|
||
for (const provider in comp.provider_results) {
|
||
const result = comp.provider_results[provider] || {}; // Default to empty object if provider result is null/undefined
|
||
const status = result.status || 'Unknown';
|
||
const isSuccess = status === 'Success';
|
||
const statusIcon = isSuccess ? chalk.green('✅') : chalk.red('❌');
|
||
const statusText = isSuccess
|
||
? chalk.green(status)
|
||
: chalk.red(status);
|
||
providerTable.push([
|
||
chalk.white(provider),
|
||
`${statusIcon} ${statusText}`,
|
||
chalk.white(result.task_id || 'N/A'),
|
||
chalk.white(result.score || 'N/A'),
|
||
chalk.dim(result.notes || 'N/A')
|
||
]);
|
||
}
|
||
console.log(providerTable.toString());
|
||
console.log();
|
||
} else {
|
||
console.log(chalk.gray(' No provider results available.'));
|
||
console.log();
|
||
}
|
||
console.log(chalk.white.bold(` Comparison Summary:`));
|
||
console.log(chalk.white(` ${comp.comparison_summary || 'N/A'}`));
|
||
} else {
|
||
console.log(chalk.gray(' Provider comparison data not found.'));
|
||
}
|
||
console.log();
|
||
|
||
// Detected Issues
|
||
console.log(chalk.red.bold('🚨 Detected Issues:'));
|
||
if (
|
||
Array.isArray(reportData.detected_issues) &&
|
||
reportData.detected_issues.length > 0
|
||
) {
|
||
reportData.detected_issues.forEach((issue, index) => {
|
||
if (typeof issue !== 'object' || issue === null) return; // Skip invalid issue entries
|
||
|
||
const severity = issue.severity || 'Unknown';
|
||
let boxColor = 'blue';
|
||
let icon = 'ℹ️';
|
||
if (severity === 'Error') {
|
||
boxColor = 'red';
|
||
icon = '❌';
|
||
}
|
||
if (severity === 'Warning') {
|
||
boxColor = 'yellow';
|
||
icon = '⚠️';
|
||
}
|
||
|
||
let issueContent = `${chalk.bold('Description:')} ${chalk.white(issue.description || 'N/A')}`;
|
||
// Only add log context if it exists and is not empty
|
||
if (issue.log_context && String(issue.log_context).trim()) {
|
||
issueContent += `\n${chalk.bold('Log Context:')} \n${chalk.dim(String(issue.log_context).trim())}`;
|
||
}
|
||
|
||
console.log(
|
||
boxen(issueContent, {
|
||
title: `${icon} Issue ${index + 1}: [${severity}]`,
|
||
padding: 1,
|
||
margin: { top: 1, bottom: 0 },
|
||
borderColor: boxColor,
|
||
borderStyle: 'round'
|
||
})
|
||
);
|
||
});
|
||
console.log(); // Add final newline if issues exist
|
||
} else {
|
||
console.log(chalk.green(' No specific issues detected by the LLM.'));
|
||
}
|
||
console.log();
|
||
|
||
console.log(chalk.cyan.bold('========================================'));
|
||
console.log(chalk.cyan.bold(' End of LLM Report'));
|
||
console.log(chalk.cyan.bold('========================================\n'));
|
||
} catch (error) {
|
||
// Ensure chalk is available for error reporting, provide fallback
|
||
const errorChalk = chalk || { red: (t) => t, yellow: (t) => t };
|
||
console.error(
|
||
errorChalk.red('Error processing LLM response:'),
|
||
error.message
|
||
);
|
||
// Avoid printing potentially huge inputData here unless necessary for debugging
|
||
// console.error(errorChalk.yellow('Raw input data (first 500 chars):'), inputData.substring(0, 500));
|
||
process.exit(1);
|
||
}
|
||
});
|
||
|
||
// Handle potential errors during stdin reading
|
||
process.stdin.on('error', (err) => {
|
||
console.error('Error reading standard input:', err);
|
||
process.exit(1);
|
||
});
|