implements complexity-report so it can show in the cli.

This commit is contained in:
Eyal Toledano
2025-03-22 03:16:12 -04:00
parent 2b2564a40a
commit 6a0517b4e5
4 changed files with 342 additions and 20 deletions

View File

@@ -366,6 +366,15 @@ npm run dev -- analyze-complexity --file=custom-tasks.json
npm run dev -- analyze-complexity --research npm run dev -- analyze-complexity --research
``` ```
### View Complexity Report
```bash
# Display the task complexity analysis report
npm run dev -- complexity-report
# View a report at a custom location
npm run dev -- complexity-report --file=my-report.json
```
### Managing Task Dependencies ### Managing Task Dependencies
```bash ```bash
# Add a dependency to a task # Add a dependency to a task
@@ -398,6 +407,16 @@ The generated report contains:
- AI-generated expansion prompts customized for each task - AI-generated expansion prompts customized for each task
- Ready-to-run expansion commands directly within each task analysis - Ready-to-run expansion commands directly within each task analysis
### Viewing Complexity Report
The `complexity-report` command:
- Displays a formatted, easy-to-read version of the complexity analysis report
- Shows tasks organized by complexity score (highest to lowest)
- Provides complexity distribution statistics (low, medium, high)
- Highlights tasks recommended for expansion based on threshold score
- Includes ready-to-use expansion commands for each complex task
- If no report exists, offers to generate one on the spot
### Smart Task Expansion ### Smart Task Expansion
The `expand` command automatically checks for and uses the complexity report: The `expand` command automatically checks for and uses the complexity report:
@@ -413,7 +432,8 @@ Example workflow:
# Generate the complexity analysis report with research capabilities # Generate the complexity analysis report with research capabilities
npm run dev -- analyze-complexity --research npm run dev -- analyze-complexity --research
# Review the report in scripts/task-complexity-report.json # Review the report in a readable format
npm run dev -- complexity-report
# Expand tasks using the optimized recommendations # Expand tasks using the optimized recommendations
npm run dev -- expand --id=8 npm run dev -- expand --id=8
@@ -503,3 +523,8 @@ Please mark it as complete and tell me what I should work on next.
``` ```
Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further? Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
``` ```
### Viewing complexity report
```
Can you show me the complexity report in a more readable format?
```

View File

@@ -399,6 +399,15 @@ npm run dev -- analyze-complexity --file=custom-tasks.json
npm run dev -- analyze-complexity --research npm run dev -- analyze-complexity --research
``` ```
### View Complexity Report
```bash
# Display the task complexity analysis report
npm run dev -- complexity-report
# View a report at a custom location
npm run dev -- complexity-report --file=my-report.json
```
### Managing Task Dependencies ### Managing Task Dependencies
```bash ```bash
# Add a dependency to a task # Add a dependency to a task
@@ -431,6 +440,16 @@ The generated report contains:
- AI-generated expansion prompts customized for each task - AI-generated expansion prompts customized for each task
- Ready-to-run expansion commands directly within each task analysis - Ready-to-run expansion commands directly within each task analysis
### Viewing Complexity Report
The `complexity-report` command:
- Displays a formatted, easy-to-read version of the complexity analysis report
- Shows tasks organized by complexity score (highest to lowest)
- Provides complexity distribution statistics (low, medium, high)
- Highlights tasks recommended for expansion based on threshold score
- Includes ready-to-use expansion commands for each complex task
- If no report exists, offers to generate one on the spot
### Smart Task Expansion ### Smart Task Expansion
The `expand` command automatically checks for and uses the complexity report: The `expand` command automatically checks for and uses the complexity report:
@@ -446,7 +465,8 @@ Example workflow:
# Generate the complexity analysis report with research capabilities # Generate the complexity analysis report with research capabilities
npm run dev -- analyze-complexity --research npm run dev -- analyze-complexity --research
# Review the report in scripts/task-complexity-report.json # Review the report in a readable format
npm run dev -- complexity-report
# Expand tasks using the optimized recommendations # Expand tasks using the optimized recommendations
npm run dev -- expand --id=8 npm run dev -- expand --id=8
@@ -537,6 +557,11 @@ Please mark it as complete and tell me what I should work on next.
Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further? Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
``` ```
### Viewing complexity report
```
Can you show me the complexity report in a more readable format?
```
## License ## License
MIT MIT

View File

@@ -95,6 +95,15 @@
* -> Regenerates task files with corrected dependencies * -> Regenerates task files with corrected dependencies
* -> Provides detailed report of all fixes made * -> Provides detailed report of all fixes made
* *
* 15) complexity-report [--file=path]
* -> Displays the task complexity analysis report in a readable format
* -> Shows tasks organized by complexity score with recommended actions
* -> Includes complexity distribution statistics
* -> Provides ready-to-use expansion commands
* -> If no report exists, offers to generate one
* -> Options:
* --file, -f <path>: Specify report file path (default: 'scripts/task-complexity-report.json')
*
* Usage examples: * Usage examples:
* node dev.js parse-prd --input=sample-prd.txt * node dev.js parse-prd --input=sample-prd.txt
* node dev.js parse-prd --input=sample-prd.txt --tasks=10 * node dev.js parse-prd --input=sample-prd.txt --tasks=10
@@ -118,6 +127,8 @@
* node dev.js remove-dependency --id=22 --depends-on=21 * node dev.js remove-dependency --id=22 --depends-on=21
* node dev.js validate-dependencies * node dev.js validate-dependencies
* node dev.js fix-dependencies * node dev.js fix-dependencies
* node dev.js complexity-report
* node dev.js complexity-report --file=custom-report.json
*/ */
import fs from 'fs'; import fs from 'fs';
@@ -196,7 +207,7 @@ const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
// Display a fancy banner // Display a fancy banner
function displayBanner() { function displayBanner() {
console.clear(); console.clear();
const bannerText = figlet.textSync('Claude Task Master', { const bannerText = figlet.textSync('Task Master AI', {
font: 'Standard', font: 'Standard',
horizontalLayout: 'default', horizontalLayout: 'default',
verticalLayout: 'default' verticalLayout: 'default'
@@ -204,6 +215,9 @@ function displayBanner() {
console.log(coolGradient(bannerText)); console.log(coolGradient(bannerText));
// Add creator credit line below the banner
console.log(chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano'));
// Read version directly from package.json // Read version directly from package.json
let version = "1.5.0"; // Default fallback let version = "1.5.0"; // Default fallback
try { try {
@@ -1215,6 +1229,66 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) {
? data.tasks.filter(t => t.status === statusFilter) ? data.tasks.filter(t => t.status === statusFilter)
: data.tasks; : data.tasks;
// Count statistics for metrics
const doneCount = data.tasks.filter(t => t.status === 'done' || t.status === 'completed').length;
const pendingCount = data.tasks.filter(t => t.status === 'pending').length;
const inProgressCount = data.tasks.filter(t => t.status === 'in-progress').length;
const deferredCount = data.tasks.filter(t => t.status === 'deferred').length;
const otherCount = data.tasks.length - doneCount - pendingCount - inProgressCount - deferredCount;
// Count tasks by priority
const highPriorityCount = data.tasks.filter(t => t.priority === 'high').length;
const mediumPriorityCount = data.tasks.filter(t => t.priority === 'medium').length;
const lowPriorityCount = data.tasks.filter(t => t.priority === 'low').length;
// Calculate progress percentage
const progressPercent = Math.round((doneCount / data.tasks.length) * 100);
const progressBar = createProgressBar(progressPercent, 30);
// Count blocked tasks (pending with dependencies that aren't done)
let blockedCount = 0;
data.tasks.filter(t => t.status === 'pending').forEach(task => {
if (task.dependencies && task.dependencies.length > 0) {
const hasPendingDeps = task.dependencies.some(depId => {
const depTask = data.tasks.find(t => t.id === depId);
return depTask && depTask.status !== 'done' && depTask.status !== 'completed';
});
if (hasPendingDeps) blockedCount++;
}
});
// Count subtasks
let totalSubtasks = 0;
let completedSubtasks = 0;
data.tasks.forEach(task => {
if (task.subtasks && Array.isArray(task.subtasks)) {
totalSubtasks += task.subtasks.length;
completedSubtasks += task.subtasks.filter(st =>
st.status === 'done' || st.status === 'completed'
).length;
}
});
// Calculate subtask progress
const subtaskProgressPercent = totalSubtasks > 0
? Math.round((completedSubtasks / totalSubtasks) * 100)
: 0;
const subtaskProgressBar = createProgressBar(subtaskProgressPercent, 30);
// Display the dashboard first
console.log(boxen(
chalk.white.bold('Project Dashboard\n') +
`${chalk.bold('Tasks Progress:')} ${progressBar} ${progressPercent}%\n` +
`${chalk.green.bold('Done:')} ${doneCount} ${chalk.blue.bold('In Progress:')} ${inProgressCount} ${chalk.yellow.bold('Pending:')} ${pendingCount} ${chalk.red.bold('Blocked:')} ${blockedCount} ${chalk.gray.bold('Deferred:')} ${deferredCount}\n` +
'\n' +
`${chalk.bold('Subtasks Progress:')} ${subtaskProgressBar} ${subtaskProgressPercent}%\n` +
`${chalk.green.bold('Completed:')} ${completedSubtasks}/${totalSubtasks} ${chalk.yellow.bold('Remaining:')} ${totalSubtasks - completedSubtasks}\n` +
'\n' +
`${chalk.bold('Priority Breakdown:')}\n` +
`${chalk.red.bold('High:')} ${highPriorityCount} ${chalk.yellow.bold('Medium:')} ${mediumPriorityCount} ${chalk.gray.bold('Low:')} ${lowPriorityCount}`,
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 0, bottom: 0 } }
));
// Get terminal width for dynamic sizing // Get terminal width for dynamic sizing
const terminalWidth = process.stdout.columns || 100; const terminalWidth = process.stdout.columns || 100;
@@ -1353,20 +1427,6 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) {
})); }));
console.log(table.toString()); console.log(table.toString());
// Summary box
const doneCount = data.tasks.filter(t => t.status === 'done' || t.status === 'completed').length;
const pendingCount = data.tasks.filter(t => t.status === 'pending').length;
const otherCount = data.tasks.length - doneCount - pendingCount;
const progressPercent = Math.round((doneCount / data.tasks.length) * 100);
const progressBar = createProgressBar(progressPercent, 30);
console.log(boxen(
`${chalk.bold('Project Progress:')} ${progressBar} ${progressPercent}%\n` +
`${chalk.green.bold('Done:')} ${doneCount} ${chalk.yellow.bold('Pending:')} ${pendingCount} ${chalk.gray.bold('Other:')} ${otherCount}`,
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } }
));
} }
} }
@@ -2597,6 +2657,15 @@ async function main() {
} }
}); });
program
.command('complexity-report')
.description('Display the complexity analysis report')
.option('-f, --file <path>', 'Path to the complexity report file', 'scripts/task-complexity-report.json')
.action(async (options) => {
const reportPath = options.file;
await displayComplexityReport(reportPath);
});
program program
.command('*') .command('*')
.description('Handle unknown commands') .description('Handle unknown commands')
@@ -2806,7 +2875,7 @@ function displayHelp() {
displayBanner(); displayBanner();
console.log(boxen( console.log(boxen(
chalk.white.bold('Claude Task Master CLI'), chalk.white.bold('Task Master CLI'),
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
)); ));
@@ -2844,6 +2913,8 @@ function displayHelp() {
commands: [ commands: [
{ name: 'analyze-complexity', args: '[--research] [--threshold=5]', { name: 'analyze-complexity', args: '[--research] [--threshold=5]',
desc: 'Analyze tasks and generate expansion recommendations' }, desc: 'Analyze tasks and generate expansion recommendations' },
{ name: 'complexity-report', args: '[--file=<path>]',
desc: 'Display the complexity analysis report' },
{ name: 'expand', args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', { name: 'expand', args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]',
desc: 'Break down tasks into detailed subtasks' }, desc: 'Break down tasks into detailed subtasks' },
{ name: 'expand --all', args: '[--force] [--research]', { name: 'expand --all', args: '[--force] [--research]',
@@ -5199,3 +5270,201 @@ function ensureAtLeastOneIndependentSubtask(tasksData) {
return changesDetected; return changesDetected;
} }
// Add the function to display complexity report (around line ~4850, before the main function)
/**
* Display the complexity analysis report in a nice format
* @param {string} reportPath - Path to the complexity report file
*/
async function displayComplexityReport(reportPath) {
displayBanner();
// Check if the report exists
if (!fs.existsSync(reportPath)) {
console.log(boxen(
chalk.yellow(`No complexity report found at ${reportPath}\n\n`) +
'Would you like to generate one now?',
{ padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }
));
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
const answer = await new Promise(resolve => {
readline.question(chalk.cyan('Generate complexity report? (y/n): '), resolve);
});
readline.close();
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
// Call the analyze-complexity command
console.log(chalk.blue('Generating complexity report...'));
await analyzeTaskComplexity({
output: reportPath,
research: false, // Default to no research for speed
file: 'tasks/tasks.json'
});
// Read the newly generated report
return displayComplexityReport(reportPath);
} else {
console.log(chalk.yellow('Report generation cancelled.'));
return;
}
}
// Read the report
let report;
try {
report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
} catch (error) {
log('error', `Error reading complexity report: ${error.message}`);
return;
}
// Display report header
console.log(boxen(
chalk.white.bold('Task Complexity Analysis Report'),
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
));
// Display metadata
const metaTable = new Table({
style: {
head: [],
border: [],
'padding-top': 0,
'padding-bottom': 0,
compact: true
},
chars: {
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
},
colWidths: [20, 50]
});
metaTable.push(
[chalk.cyan.bold('Generated:'), new Date(report.meta.generatedAt).toLocaleString()],
[chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed],
[chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore],
[chalk.cyan.bold('Project:'), report.meta.projectName],
[chalk.cyan.bold('Research-backed:'), report.meta.usedResearch ? 'Yes' : 'No']
);
console.log(metaTable.toString());
// Sort tasks by complexity score (highest first)
const sortedTasks = [...report.complexityAnalysis].sort((a, b) => b.complexityScore - a.complexityScore);
// Determine which tasks need expansion based on threshold
const tasksNeedingExpansion = sortedTasks.filter(task => task.complexityScore >= report.meta.thresholdScore);
const simpleTasks = sortedTasks.filter(task => task.complexityScore < report.meta.thresholdScore);
// Create progress bar to show complexity distribution
const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10)
sortedTasks.forEach(task => {
if (task.complexityScore < 5) complexityDistribution[0]++;
else if (task.complexityScore < 8) complexityDistribution[1]++;
else complexityDistribution[2]++;
});
const percentLow = Math.round((complexityDistribution[0] / sortedTasks.length) * 100);
const percentMedium = Math.round((complexityDistribution[1] / sortedTasks.length) * 100);
const percentHigh = Math.round((complexityDistribution[2] / sortedTasks.length) * 100);
console.log(boxen(
chalk.white.bold('Complexity Distribution\n\n') +
`${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` +
`${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` +
`${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`,
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
));
// Create table for tasks that need expansion
if (tasksNeedingExpansion.length > 0) {
console.log(boxen(
chalk.yellow.bold(`Tasks Recommended for Expansion (${tasksNeedingExpansion.length})`),
{ padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'yellow', borderStyle: 'round' }
));
const complexTable = new Table({
head: [
chalk.yellow.bold('ID'),
chalk.yellow.bold('Title'),
chalk.yellow.bold('Score'),
chalk.yellow.bold('Subtasks'),
chalk.yellow.bold('Expansion Command')
],
colWidths: [5, 40, 8, 10, 45],
style: { head: [], border: [] }
});
tasksNeedingExpansion.forEach(task => {
complexTable.push([
task.taskId,
truncate(task.taskTitle, 37),
getComplexityWithColor(task.complexityScore),
task.recommendedSubtasks,
chalk.cyan(`node scripts/dev.js expand --id=${task.taskId} --num=${task.recommendedSubtasks}`)
]);
});
console.log(complexTable.toString());
}
// Create table for simple tasks
if (simpleTasks.length > 0) {
console.log(boxen(
chalk.green.bold(`Simple Tasks (${simpleTasks.length})`),
{ padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'green', borderStyle: 'round' }
));
const simpleTable = new Table({
head: [
chalk.green.bold('ID'),
chalk.green.bold('Title'),
chalk.green.bold('Score'),
chalk.green.bold('Reasoning')
],
colWidths: [5, 40, 8, 50],
style: { head: [], border: [] }
});
simpleTasks.forEach(task => {
simpleTable.push([
task.taskId,
truncate(task.taskTitle, 37),
getComplexityWithColor(task.complexityScore),
truncate(task.reasoning, 47)
]);
});
console.log(simpleTable.toString());
}
// Show action suggestions
console.log(boxen(
chalk.white.bold('Suggested Actions:') + '\n\n' +
`${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`node scripts/dev.js expand --all`)}\n` +
`${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`node scripts/dev.js expand --id=<id>`)}\n` +
`${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`node scripts/dev.js analyze-complexity --research`)}`,
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } }
));
}
// Helper function to get complexity score with appropriate color
function getComplexityWithColor(score) {
if (score >= 8) {
return chalk.red.bold(score);
} else if (score >= 5) {
return chalk.yellow(score);
} else {
return chalk.green(score);
}
}
// Helper function to truncate text
function truncate(text, maxLength) {
if (!text) return '';
return text.length > maxLength ? text.substring(0, maxLength - 3) + '...' : text;
}

View File

@@ -40,7 +40,7 @@ const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
// Display a fancy banner // Display a fancy banner
function displayBanner() { function displayBanner() {
console.clear(); console.clear();
const bannerText = figlet.textSync('Claude Task Master', { const bannerText = figlet.textSync('Task Master AI', {
font: 'Standard', font: 'Standard',
horizontalLayout: 'default', horizontalLayout: 'default',
verticalLayout: 'default' verticalLayout: 'default'
@@ -48,6 +48,9 @@ function displayBanner() {
console.log(coolGradient(bannerText)); console.log(coolGradient(bannerText));
// Add creator credit line below the banner
console.log(chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano'));
console.log(boxen(chalk.white(`${chalk.bold('Initializing')} your new project`), { console.log(boxen(chalk.white(`${chalk.bold('Initializing')} your new project`), {
padding: 1, padding: 1,
margin: { top: 0, bottom: 1 }, margin: { top: 0, bottom: 1 },