Task 104: Implement 'scope-up' and 'scope-down' CLI Commands for Dynamic Task Complexity Adjustment (#1069)
* feat(task-104): Complete task 104 - Implement scope-up and scope-down CLI Commands - Added new CLI commands 'scope-up' and 'scope-down' with comma-separated ID support - Implemented strength levels (light/regular/heavy) and custom prompt functionality - Created core complexity adjustment logic with AI integration - Added MCP tool equivalents for integrated environments - Comprehensive error handling and task validation - Full test coverage with TDD approach - Updated task manager core and UI components Task 104: Implement 'scope-up' and 'scope-down' CLI Commands for Dynamic Task Complexity Adjustment - Complete implementation with CLI, MCP integration, and testing * chore: Add changeset for scope-up and scope-down features - Comprehensive user-facing description with usage examples - Key features and benefits explanation - CLI and MCP integration details - Real-world use cases for agile workflows * feat(extension): Add scope-up and scope-down to VS Code extension task details - Added useScopeUpTask and useScopeDownTask hooks in useTaskQueries.ts - Enhanced AIActionsSection with Task Complexity Adjustment section - Added strength selection (light/regular/heavy) and custom prompt support - Integrated scope buttons with proper loading states and error handling - Uses existing mcpRequest handler for scope_up_task and scope_down_task tools - Maintains consistent UI patterns with existing AI actions Extension now supports dynamic task complexity adjustment directly from task details view.
This commit is contained in:
@@ -42,7 +42,10 @@ import {
|
||||
taskExists,
|
||||
moveTask,
|
||||
migrateProject,
|
||||
setResponseLanguage
|
||||
setResponseLanguage,
|
||||
scopeUpTask,
|
||||
scopeDownTask,
|
||||
validateStrength
|
||||
} from './task-manager.js';
|
||||
|
||||
import {
|
||||
@@ -1386,6 +1389,258 @@ function registerCommands(programInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
// scope-up command
|
||||
programInstance
|
||||
.command('scope-up')
|
||||
.description('Increase task complexity with AI assistance')
|
||||
.option(
|
||||
'-f, --file <file>',
|
||||
'Path to the tasks file',
|
||||
TASKMASTER_TASKS_FILE
|
||||
)
|
||||
.option(
|
||||
'-i, --id <ids>',
|
||||
'Comma-separated task/subtask IDs to scope up (required)'
|
||||
)
|
||||
.option(
|
||||
'-s, --strength <level>',
|
||||
'Complexity increase strength: light, regular, heavy',
|
||||
'regular'
|
||||
)
|
||||
.option(
|
||||
'-p, --prompt <text>',
|
||||
'Custom instructions for targeted scope adjustments'
|
||||
)
|
||||
.option('-r, --research', 'Use research AI for more informed adjustments')
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
// Validate required parameters
|
||||
if (!options.id) {
|
||||
console.error(chalk.red('Error: --id parameter is required'));
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Usage example: task-master scope-up --id=1,2,3 --strength=regular'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse and validate task IDs
|
||||
const taskIds = options.id.split(',').map((id) => {
|
||||
const parsed = parseInt(id.trim(), 10);
|
||||
if (Number.isNaN(parsed) || parsed <= 0) {
|
||||
console.error(chalk.red(`Error: Invalid task ID: ${id.trim()}`));
|
||||
process.exit(1);
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
// Validate strength level
|
||||
if (!validateStrength(options.strength)) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Error: Invalid strength level: ${options.strength}. Must be one of: light, regular, heavy`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate tasks file exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
console.error(
|
||||
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Scoping up ${taskIds.length} task(s): ${taskIds.join(', ')}`
|
||||
)
|
||||
);
|
||||
console.log(chalk.blue(`Strength level: ${options.strength}`));
|
||||
if (options.prompt) {
|
||||
console.log(chalk.blue(`Custom instructions: ${options.prompt}`));
|
||||
}
|
||||
|
||||
const context = {
|
||||
projectRoot: taskMaster.getProjectRoot(),
|
||||
tag,
|
||||
commandName: 'scope-up',
|
||||
outputType: 'cli'
|
||||
};
|
||||
|
||||
const result = await scopeUpTask(
|
||||
tasksPath,
|
||||
taskIds,
|
||||
options.strength,
|
||||
options.prompt || null,
|
||||
context,
|
||||
'text'
|
||||
);
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✅ Successfully scoped up ${result.updatedTasks.length} task(s)`
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
|
||||
if (error.message.includes('not found')) {
|
||||
console.log(chalk.yellow('\nTo fix this issue:'));
|
||||
console.log(
|
||||
' 1. Run task-master list to see all available task IDs'
|
||||
);
|
||||
console.log(' 2. Use valid task IDs with the --id parameter');
|
||||
}
|
||||
|
||||
if (getDebugFlag()) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// scope-down command
|
||||
programInstance
|
||||
.command('scope-down')
|
||||
.description('Decrease task complexity with AI assistance')
|
||||
.option(
|
||||
'-f, --file <file>',
|
||||
'Path to the tasks file',
|
||||
TASKMASTER_TASKS_FILE
|
||||
)
|
||||
.option(
|
||||
'-i, --id <ids>',
|
||||
'Comma-separated task/subtask IDs to scope down (required)'
|
||||
)
|
||||
.option(
|
||||
'-s, --strength <level>',
|
||||
'Complexity decrease strength: light, regular, heavy',
|
||||
'regular'
|
||||
)
|
||||
.option(
|
||||
'-p, --prompt <text>',
|
||||
'Custom instructions for targeted scope adjustments'
|
||||
)
|
||||
.option('-r, --research', 'Use research AI for more informed adjustments')
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
// Validate required parameters
|
||||
if (!options.id) {
|
||||
console.error(chalk.red('Error: --id parameter is required'));
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Usage example: task-master scope-down --id=1,2,3 --strength=regular'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse and validate task IDs
|
||||
const taskIds = options.id.split(',').map((id) => {
|
||||
const parsed = parseInt(id.trim(), 10);
|
||||
if (Number.isNaN(parsed) || parsed <= 0) {
|
||||
console.error(chalk.red(`Error: Invalid task ID: ${id.trim()}`));
|
||||
process.exit(1);
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
// Validate strength level
|
||||
if (!validateStrength(options.strength)) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Error: Invalid strength level: ${options.strength}. Must be one of: light, regular, heavy`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate tasks file exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
console.error(
|
||||
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Scoping down ${taskIds.length} task(s): ${taskIds.join(', ')}`
|
||||
)
|
||||
);
|
||||
console.log(chalk.blue(`Strength level: ${options.strength}`));
|
||||
if (options.prompt) {
|
||||
console.log(chalk.blue(`Custom instructions: ${options.prompt}`));
|
||||
}
|
||||
|
||||
const context = {
|
||||
projectRoot: taskMaster.getProjectRoot(),
|
||||
tag,
|
||||
commandName: 'scope-down',
|
||||
outputType: 'cli'
|
||||
};
|
||||
|
||||
const result = await scopeDownTask(
|
||||
tasksPath,
|
||||
taskIds,
|
||||
options.strength,
|
||||
options.prompt || null,
|
||||
context,
|
||||
'text'
|
||||
);
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✅ Successfully scoped down ${result.updatedTasks.length} task(s)`
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
|
||||
if (error.message.includes('not found')) {
|
||||
console.log(chalk.yellow('\nTo fix this issue:'));
|
||||
console.log(
|
||||
' 1. Run task-master list to see all available task IDs'
|
||||
);
|
||||
console.log(' 2. Use valid task IDs with the --id parameter');
|
||||
}
|
||||
|
||||
if (getDebugFlag()) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// generate command
|
||||
programInstance
|
||||
.command('generate')
|
||||
|
||||
@@ -28,6 +28,11 @@ import moveTask from './task-manager/move-task.js';
|
||||
import { migrateProject } from './task-manager/migrate.js';
|
||||
import { performResearch } from './task-manager/research.js';
|
||||
import { readComplexityReport } from './utils.js';
|
||||
import {
|
||||
scopeUpTask,
|
||||
scopeDownTask,
|
||||
validateStrength
|
||||
} from './task-manager/scope-adjustment.js';
|
||||
|
||||
// Export task manager functions
|
||||
export {
|
||||
@@ -55,5 +60,8 @@ export {
|
||||
moveTask,
|
||||
readComplexityReport,
|
||||
migrateProject,
|
||||
performResearch
|
||||
performResearch,
|
||||
scopeUpTask,
|
||||
scopeDownTask,
|
||||
validateStrength
|
||||
};
|
||||
|
||||
828
scripts/modules/task-manager/scope-adjustment.js
Normal file
828
scripts/modules/task-manager/scope-adjustment.js
Normal file
@@ -0,0 +1,828 @@
|
||||
/**
|
||||
* scope-adjustment.js
|
||||
* Core logic for dynamic task complexity adjustment (scope-up and scope-down)
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
log,
|
||||
readJSON,
|
||||
writeJSON,
|
||||
getCurrentTag,
|
||||
readComplexityReport,
|
||||
findTaskInComplexityReport
|
||||
} from '../utils.js';
|
||||
import {
|
||||
generateObjectService,
|
||||
generateTextService
|
||||
} from '../ai-services-unified.js';
|
||||
import { findTaskById, taskExists } from '../task-manager.js';
|
||||
import analyzeTaskComplexity from './analyze-task-complexity.js';
|
||||
import { findComplexityReportPath } from '../../../src/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Valid strength levels for scope adjustments
|
||||
*/
|
||||
const VALID_STRENGTHS = ['light', 'regular', 'heavy'];
|
||||
|
||||
/**
|
||||
* Statuses that should be preserved during subtask regeneration
|
||||
* These represent work that has been started or intentionally set by the user
|
||||
*/
|
||||
const PRESERVE_STATUSES = [
|
||||
'done',
|
||||
'in-progress',
|
||||
'review',
|
||||
'cancelled',
|
||||
'deferred',
|
||||
'blocked'
|
||||
];
|
||||
|
||||
/**
|
||||
* Statuses that should be regenerated during subtask regeneration
|
||||
* These represent work that hasn't been started yet
|
||||
*/
|
||||
const REGENERATE_STATUSES = ['pending'];
|
||||
|
||||
/**
|
||||
* Validates strength parameter
|
||||
* @param {string} strength - The strength level to validate
|
||||
* @returns {boolean} True if valid, false otherwise
|
||||
*/
|
||||
export function validateStrength(strength) {
|
||||
return VALID_STRENGTHS.includes(strength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-analyzes the complexity of a single task after scope adjustment
|
||||
* @param {Object} task - The task to analyze
|
||||
* @param {string} tasksPath - Path to tasks.json
|
||||
* @param {Object} context - Context containing projectRoot, tag, session
|
||||
* @returns {Promise<number|null>} New complexity score or null if analysis failed
|
||||
*/
|
||||
async function reanalyzeTaskComplexity(task, tasksPath, context) {
|
||||
const { projectRoot, tag, session } = context;
|
||||
|
||||
try {
|
||||
// Create a minimal tasks data structure for analysis
|
||||
const tasksForAnalysis = {
|
||||
tasks: [task],
|
||||
metadata: { analyzedAt: new Date().toISOString() }
|
||||
};
|
||||
|
||||
// Find the complexity report path for this tag
|
||||
const complexityReportPath = findComplexityReportPath(
|
||||
null,
|
||||
{ projectRoot, tag },
|
||||
null
|
||||
);
|
||||
|
||||
if (!complexityReportPath) {
|
||||
log('warn', 'No complexity report found - cannot re-analyze complexity');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use analyze-task-complexity to re-analyze just this task
|
||||
const analysisOptions = {
|
||||
file: tasksPath,
|
||||
output: complexityReportPath,
|
||||
id: task.id.toString(), // Analyze only this specific task
|
||||
projectRoot,
|
||||
tag,
|
||||
_filteredTasksData: tasksForAnalysis, // Pass pre-filtered data
|
||||
_originalTaskCount: 1
|
||||
};
|
||||
|
||||
// Run the analysis with proper context
|
||||
await analyzeTaskComplexity(analysisOptions, { session });
|
||||
|
||||
// Read the updated complexity report to get the new score
|
||||
const updatedReport = readComplexityReport(complexityReportPath);
|
||||
if (updatedReport) {
|
||||
const taskAnalysis = findTaskInComplexityReport(updatedReport, task.id);
|
||||
if (taskAnalysis) {
|
||||
log(
|
||||
'info',
|
||||
`Re-analyzed task ${task.id} complexity: ${taskAnalysis.complexityScore}/10`
|
||||
);
|
||||
return taskAnalysis.complexityScore;
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
'warn',
|
||||
`Could not find updated complexity analysis for task ${task.id}`
|
||||
);
|
||||
return null;
|
||||
} catch (error) {
|
||||
log('error', `Failed to re-analyze task complexity: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current complexity score for a task from the complexity report
|
||||
* @param {number} taskId - Task ID to look up
|
||||
* @param {Object} context - Context containing projectRoot, tag
|
||||
* @returns {number|null} Current complexity score or null if not found
|
||||
*/
|
||||
function getCurrentComplexityScore(taskId, context) {
|
||||
const { projectRoot, tag } = context;
|
||||
|
||||
try {
|
||||
// Find the complexity report path for this tag
|
||||
const complexityReportPath = findComplexityReportPath(
|
||||
null,
|
||||
{ projectRoot, tag },
|
||||
null
|
||||
);
|
||||
|
||||
if (!complexityReportPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the current complexity report
|
||||
const complexityReport = readComplexityReport(complexityReportPath);
|
||||
if (!complexityReport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find this task's current complexity
|
||||
const taskAnalysis = findTaskInComplexityReport(complexityReport, taskId);
|
||||
return taskAnalysis ? taskAnalysis.complexityScore : null;
|
||||
} catch (error) {
|
||||
log('debug', `Could not read current complexity score: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerates subtasks for a task based on new complexity while preserving completed work
|
||||
* @param {Object} task - The updated task object
|
||||
* @param {string} tasksPath - Path to tasks.json
|
||||
* @param {Object} context - Context containing projectRoot, tag, session
|
||||
* @param {string} direction - Direction of scope change (up/down) for logging
|
||||
* @param {string} strength - Strength level ('light', 'regular', 'heavy')
|
||||
* @param {number|null} originalComplexity - Original complexity score for smarter adjustments
|
||||
* @returns {Promise<Object>} Object with updated task and regeneration info
|
||||
*/
|
||||
async function regenerateSubtasksForComplexity(
|
||||
task,
|
||||
tasksPath,
|
||||
context,
|
||||
direction,
|
||||
strength = 'regular',
|
||||
originalComplexity = null
|
||||
) {
|
||||
const { projectRoot, tag, session } = context;
|
||||
|
||||
// Check if task has subtasks
|
||||
if (
|
||||
!task.subtasks ||
|
||||
!Array.isArray(task.subtasks) ||
|
||||
task.subtasks.length === 0
|
||||
) {
|
||||
return {
|
||||
updatedTask: task,
|
||||
regenerated: false,
|
||||
preserved: 0,
|
||||
generated: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Identify subtasks to preserve vs regenerate
|
||||
const preservedSubtasks = task.subtasks.filter((subtask) =>
|
||||
PRESERVE_STATUSES.includes(subtask.status)
|
||||
);
|
||||
const pendingSubtasks = task.subtasks.filter((subtask) =>
|
||||
REGENERATE_STATUSES.includes(subtask.status)
|
||||
);
|
||||
|
||||
// If no pending subtasks, nothing to regenerate
|
||||
if (pendingSubtasks.length === 0) {
|
||||
return {
|
||||
updatedTask: task,
|
||||
regenerated: false,
|
||||
preserved: preservedSubtasks.length,
|
||||
generated: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate appropriate number of total subtasks based on direction, complexity, strength, and original complexity
|
||||
let targetSubtaskCount;
|
||||
const preservedCount = preservedSubtasks.length;
|
||||
const currentPendingCount = pendingSubtasks.length;
|
||||
|
||||
// Use original complexity to inform decisions (if available)
|
||||
const complexityFactor = originalComplexity
|
||||
? Math.max(0.5, originalComplexity / 10)
|
||||
: 1.0;
|
||||
const complexityInfo = originalComplexity
|
||||
? ` (original complexity: ${originalComplexity}/10)`
|
||||
: '';
|
||||
|
||||
if (direction === 'up') {
|
||||
// Scope up: More subtasks for increased complexity
|
||||
if (strength === 'light') {
|
||||
const base = Math.max(
|
||||
5,
|
||||
preservedCount + Math.ceil(currentPendingCount * 1.1)
|
||||
);
|
||||
targetSubtaskCount = Math.ceil(base * (0.8 + 0.4 * complexityFactor));
|
||||
} else if (strength === 'regular') {
|
||||
const base = Math.max(
|
||||
6,
|
||||
preservedCount + Math.ceil(currentPendingCount * 1.3)
|
||||
);
|
||||
targetSubtaskCount = Math.ceil(base * (0.8 + 0.4 * complexityFactor));
|
||||
} else {
|
||||
// heavy
|
||||
const base = Math.max(
|
||||
8,
|
||||
preservedCount + Math.ceil(currentPendingCount * 1.6)
|
||||
);
|
||||
targetSubtaskCount = Math.ceil(base * (0.8 + 0.6 * complexityFactor));
|
||||
}
|
||||
} else {
|
||||
// Scope down: Fewer subtasks for decreased complexity
|
||||
// High complexity tasks get reduced more aggressively
|
||||
const aggressiveFactor =
|
||||
originalComplexity >= 8 ? 0.7 : originalComplexity >= 6 ? 0.85 : 1.0;
|
||||
|
||||
if (strength === 'light') {
|
||||
const base = Math.max(
|
||||
3,
|
||||
preservedCount + Math.ceil(currentPendingCount * 0.8)
|
||||
);
|
||||
targetSubtaskCount = Math.ceil(base * aggressiveFactor);
|
||||
} else if (strength === 'regular') {
|
||||
const base = Math.max(
|
||||
3,
|
||||
preservedCount + Math.ceil(currentPendingCount * 0.5)
|
||||
);
|
||||
targetSubtaskCount = Math.ceil(base * aggressiveFactor);
|
||||
} else {
|
||||
// heavy
|
||||
// Heavy scope-down should be much more aggressive - aim for only core functionality
|
||||
// Very high complexity tasks (9-10) get reduced to almost nothing
|
||||
const ultraAggressiveFactor =
|
||||
originalComplexity >= 9 ? 0.3 : originalComplexity >= 7 ? 0.5 : 0.7;
|
||||
const base = Math.max(
|
||||
2,
|
||||
preservedCount + Math.ceil(currentPendingCount * 0.25)
|
||||
);
|
||||
targetSubtaskCount = Math.max(1, Math.ceil(base * ultraAggressiveFactor));
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
'debug',
|
||||
`Complexity-aware subtask calculation${complexityInfo}: ${currentPendingCount} pending -> target ${targetSubtaskCount} total`
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Complexity-aware calculation${complexityInfo}: ${currentPendingCount} pending -> ${targetSubtaskCount} total subtasks (${strength} ${direction})`
|
||||
);
|
||||
|
||||
const newSubtasksNeeded = Math.max(1, targetSubtaskCount - preservedCount);
|
||||
|
||||
try {
|
||||
// Generate new subtasks using AI to match the new complexity level
|
||||
const systemPrompt = `You are an expert project manager who creates task breakdowns that match complexity levels.`;
|
||||
|
||||
const prompt = `Based on this updated task, generate ${newSubtasksNeeded} NEW subtasks that reflect the ${direction === 'up' ? 'increased' : 'decreased'} complexity level:
|
||||
|
||||
**Task Title**: ${task.title}
|
||||
**Task Description**: ${task.description}
|
||||
**Implementation Details**: ${task.details}
|
||||
**Test Strategy**: ${task.testStrategy}
|
||||
|
||||
**Complexity Direction**: This task was recently scoped ${direction} (${strength} strength) to ${direction === 'up' ? 'increase' : 'decrease'} complexity.
|
||||
${originalComplexity ? `**Original Complexity**: ${originalComplexity}/10 - consider this when determining appropriate scope level.` : ''}
|
||||
|
||||
${preservedCount > 0 ? `**Preserved Subtasks**: ${preservedCount} existing subtasks with work already done will be kept.` : ''}
|
||||
|
||||
Generate subtasks that:
|
||||
${
|
||||
direction === 'up'
|
||||
? strength === 'heavy'
|
||||
? `- Add comprehensive implementation steps with advanced features
|
||||
- Include extensive error handling, validation, and edge cases
|
||||
- Cover multiple integration scenarios and advanced testing
|
||||
- Provide thorough documentation and optimization approaches`
|
||||
: strength === 'regular'
|
||||
? `- Add more detailed implementation steps
|
||||
- Include additional error handling and validation
|
||||
- Cover more edge cases and advanced features
|
||||
- Provide more comprehensive testing approaches`
|
||||
: `- Add some additional implementation details
|
||||
- Include basic error handling considerations
|
||||
- Cover a few common edge cases
|
||||
- Enhance testing approaches slightly`
|
||||
: strength === 'heavy'
|
||||
? `- Focus ONLY on absolutely essential core functionality
|
||||
- Strip out ALL non-critical features (error handling, advanced testing, etc.)
|
||||
- Provide only the minimum viable implementation
|
||||
- Eliminate any complex integrations or advanced scenarios
|
||||
- Aim for the simplest possible working solution`
|
||||
: strength === 'regular'
|
||||
? `- Focus on core functionality only
|
||||
- Simplify implementation steps
|
||||
- Remove non-essential features
|
||||
- Streamline to basic requirements`
|
||||
: `- Focus mainly on core functionality
|
||||
- Slightly simplify implementation steps
|
||||
- Remove some non-essential features
|
||||
- Streamline most requirements`
|
||||
}
|
||||
|
||||
Return a JSON object with a "subtasks" array. Each subtask should have:
|
||||
- id: Sequential number starting from 1
|
||||
- title: Clear, specific title
|
||||
- description: Detailed description
|
||||
- dependencies: Array of dependency IDs as STRINGS (use format ["${task.id}.1", "${task.id}.2"] for siblings, or empty array [] for no dependencies)
|
||||
- details: Implementation guidance
|
||||
- status: "pending"
|
||||
- testStrategy: Testing approach
|
||||
|
||||
IMPORTANT: Dependencies must be strings, not numbers!
|
||||
|
||||
Ensure the JSON is valid and properly formatted.`;
|
||||
|
||||
// Define subtask schema
|
||||
const subtaskSchema = z.object({
|
||||
subtasks: z.array(
|
||||
z.object({
|
||||
id: z.number().int().positive(),
|
||||
title: z.string().min(5),
|
||||
description: z.string().min(10),
|
||||
dependencies: z.array(z.string()),
|
||||
details: z.string().min(20),
|
||||
status: z.string().default('pending'),
|
||||
testStrategy: z.string().nullable().default('')
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const aiResult = await generateObjectService({
|
||||
role: 'main',
|
||||
session: context.session,
|
||||
systemPrompt,
|
||||
prompt,
|
||||
schema: subtaskSchema,
|
||||
objectName: 'subtask_regeneration',
|
||||
commandName: context.commandName || `subtask-regen-${direction}`,
|
||||
outputType: context.outputType || 'cli'
|
||||
});
|
||||
|
||||
const generatedSubtasks = aiResult.mainResult.subtasks || [];
|
||||
|
||||
// Update task with preserved subtasks + newly generated ones
|
||||
task.subtasks = [...preservedSubtasks, ...generatedSubtasks];
|
||||
|
||||
return {
|
||||
updatedTask: task,
|
||||
regenerated: true,
|
||||
preserved: preservedSubtasks.length,
|
||||
generated: generatedSubtasks.length
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[WARN] Failed to regenerate subtasks for task ${task.id}: ${error.message}`
|
||||
);
|
||||
// Don't fail the whole operation if subtask regeneration fails
|
||||
return {
|
||||
updatedTask: task,
|
||||
regenerated: false,
|
||||
preserved: preservedSubtasks.length,
|
||||
generated: 0,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates AI prompt for scope adjustment
|
||||
* @param {Object} task - The task to adjust
|
||||
* @param {string} direction - 'up' or 'down'
|
||||
* @param {string} strength - 'light', 'regular', or 'heavy'
|
||||
* @param {string} customPrompt - Optional custom instructions
|
||||
* @returns {string} The generated prompt
|
||||
*/
|
||||
function generateScopePrompt(task, direction, strength, customPrompt) {
|
||||
const isUp = direction === 'up';
|
||||
const strengthDescriptions = {
|
||||
light: isUp ? 'minor enhancements' : 'slight simplifications',
|
||||
regular: isUp
|
||||
? 'moderate complexity increases'
|
||||
: 'moderate simplifications',
|
||||
heavy: isUp ? 'significant complexity additions' : 'major simplifications'
|
||||
};
|
||||
|
||||
let basePrompt = `You are tasked with adjusting the complexity of a task.
|
||||
|
||||
CURRENT TASK:
|
||||
Title: ${task.title}
|
||||
Description: ${task.description}
|
||||
Details: ${task.details}
|
||||
Test Strategy: ${task.testStrategy || 'Not specified'}
|
||||
|
||||
ADJUSTMENT REQUIREMENTS:
|
||||
- Direction: ${isUp ? 'INCREASE' : 'DECREASE'} complexity
|
||||
- Strength: ${strength} (${strengthDescriptions[strength]})
|
||||
- Preserve the core purpose and functionality of the task
|
||||
- Maintain consistency with the existing task structure`;
|
||||
|
||||
if (isUp) {
|
||||
basePrompt += `
|
||||
- Add more detailed requirements, edge cases, or advanced features
|
||||
- Include additional implementation considerations
|
||||
- Enhance error handling and validation requirements
|
||||
- Expand testing strategies with more comprehensive scenarios`;
|
||||
} else {
|
||||
basePrompt += `
|
||||
- Focus on core functionality and essential requirements
|
||||
- Remove or simplify non-essential features
|
||||
- Streamline implementation details
|
||||
- Simplify testing to focus on basic functionality`;
|
||||
}
|
||||
|
||||
if (customPrompt) {
|
||||
basePrompt += `\n\nCUSTOM INSTRUCTIONS:\n${customPrompt}`;
|
||||
}
|
||||
|
||||
basePrompt += `\n\nReturn a JSON object with the updated task containing these fields:
|
||||
- title: Updated task title
|
||||
- description: Updated task description
|
||||
- details: Updated implementation details
|
||||
- testStrategy: Updated test strategy
|
||||
|
||||
Ensure the JSON is valid and properly formatted.`;
|
||||
|
||||
return basePrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts task complexity using AI
|
||||
* @param {Object} task - The task to adjust
|
||||
* @param {string} direction - 'up' or 'down'
|
||||
* @param {string} strength - 'light', 'regular', or 'heavy'
|
||||
* @param {string} customPrompt - Optional custom instructions
|
||||
* @param {Object} context - Context object with projectRoot, tag, etc.
|
||||
* @returns {Promise<Object>} Updated task data and telemetry
|
||||
*/
|
||||
async function adjustTaskComplexity(
|
||||
task,
|
||||
direction,
|
||||
strength,
|
||||
customPrompt,
|
||||
context
|
||||
) {
|
||||
const systemPrompt = `You are an expert software project manager who helps adjust task complexity while maintaining clarity and actionability.`;
|
||||
|
||||
const prompt = generateScopePrompt(task, direction, strength, customPrompt);
|
||||
|
||||
// Define the task schema for structured response using Zod
|
||||
const taskSchema = z.object({
|
||||
title: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe('Updated task title reflecting scope adjustment'),
|
||||
description: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe('Updated task description with adjusted scope'),
|
||||
details: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe('Updated implementation details with adjusted complexity'),
|
||||
testStrategy: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe('Updated testing approach for the adjusted scope'),
|
||||
priority: z
|
||||
.enum(['low', 'medium', 'high'])
|
||||
.optional()
|
||||
.describe('Task priority level')
|
||||
});
|
||||
|
||||
const aiResult = await generateObjectService({
|
||||
role: 'main',
|
||||
session: context.session,
|
||||
systemPrompt,
|
||||
prompt,
|
||||
schema: taskSchema,
|
||||
objectName: 'updated_task',
|
||||
commandName: context.commandName || `scope-${direction}`,
|
||||
outputType: context.outputType || 'cli'
|
||||
});
|
||||
|
||||
const updatedTaskData = aiResult.mainResult;
|
||||
|
||||
return {
|
||||
updatedTask: {
|
||||
...task,
|
||||
...updatedTaskData
|
||||
},
|
||||
telemetryData: aiResult.telemetryData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases task complexity (scope-up)
|
||||
* @param {string} tasksPath - Path to tasks.json file
|
||||
* @param {Array<number>} taskIds - Array of task IDs to scope up
|
||||
* @param {string} strength - Strength level ('light', 'regular', 'heavy')
|
||||
* @param {string} customPrompt - Optional custom instructions
|
||||
* @param {Object} context - Context object with projectRoot, tag, etc.
|
||||
* @param {string} outputFormat - Output format ('text' or 'json')
|
||||
* @returns {Promise<Object>} Results of the scope-up operation
|
||||
*/
|
||||
export async function scopeUpTask(
|
||||
tasksPath,
|
||||
taskIds,
|
||||
strength = 'regular',
|
||||
customPrompt = null,
|
||||
context = {},
|
||||
outputFormat = 'text'
|
||||
) {
|
||||
// Validate inputs
|
||||
if (!validateStrength(strength)) {
|
||||
throw new Error(
|
||||
`Invalid strength level: ${strength}. Must be one of: ${VALID_STRENGTHS.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const { projectRoot = '.', tag = 'master' } = context;
|
||||
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
const tasks = data?.tasks || [];
|
||||
|
||||
// Validate all task IDs exist
|
||||
for (const taskId of taskIds) {
|
||||
if (!taskExists(tasks, taskId)) {
|
||||
throw new Error(`Task with ID ${taskId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedTasks = [];
|
||||
let combinedTelemetryData = null;
|
||||
|
||||
// Process each task
|
||||
for (const taskId of taskIds) {
|
||||
const taskResult = findTaskById(tasks, taskId);
|
||||
const task = taskResult.task;
|
||||
if (!task) {
|
||||
throw new Error(`Task with ID ${taskId} not found`);
|
||||
}
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
log('info', `Scoping up task ${taskId}: ${task.title}`);
|
||||
}
|
||||
|
||||
// Get original complexity score (if available)
|
||||
const originalComplexity = getCurrentComplexityScore(taskId, context);
|
||||
if (originalComplexity && outputFormat === 'text') {
|
||||
console.log(`[INFO] Original complexity: ${originalComplexity}/10`);
|
||||
}
|
||||
|
||||
const adjustResult = await adjustTaskComplexity(
|
||||
task,
|
||||
'up',
|
||||
strength,
|
||||
customPrompt,
|
||||
context
|
||||
);
|
||||
|
||||
// Regenerate subtasks based on new complexity while preserving completed work
|
||||
const subtaskResult = await regenerateSubtasksForComplexity(
|
||||
adjustResult.updatedTask,
|
||||
tasksPath,
|
||||
context,
|
||||
'up',
|
||||
strength,
|
||||
originalComplexity
|
||||
);
|
||||
|
||||
// Log subtask regeneration info if in text mode
|
||||
if (outputFormat === 'text' && subtaskResult.regenerated) {
|
||||
log(
|
||||
'info',
|
||||
`Regenerated ${subtaskResult.generated} pending subtasks (preserved ${subtaskResult.preserved} completed)`
|
||||
);
|
||||
}
|
||||
|
||||
// Update task in data
|
||||
const taskIndex = data.tasks.findIndex((t) => t.id === taskId);
|
||||
if (taskIndex !== -1) {
|
||||
data.tasks[taskIndex] = subtaskResult.updatedTask;
|
||||
updatedTasks.push(subtaskResult.updatedTask);
|
||||
}
|
||||
|
||||
// Re-analyze complexity after scoping (if we have a session for AI calls)
|
||||
if (context.session && originalComplexity) {
|
||||
try {
|
||||
// Write the updated task first so complexity analysis can read it
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
// Re-analyze complexity
|
||||
const newComplexity = await reanalyzeTaskComplexity(
|
||||
subtaskResult.updatedTask,
|
||||
tasksPath,
|
||||
context
|
||||
);
|
||||
if (newComplexity && outputFormat === 'text') {
|
||||
const complexityChange = newComplexity - originalComplexity;
|
||||
const arrow =
|
||||
complexityChange > 0 ? '↗️' : complexityChange < 0 ? '↘️' : '➡️';
|
||||
console.log(
|
||||
`[INFO] New complexity: ${originalComplexity}/10 ${arrow} ${newComplexity}/10 (${complexityChange > 0 ? '+' : ''}${complexityChange})`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'text') {
|
||||
log('warn', `Could not re-analyze complexity: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine telemetry data
|
||||
if (adjustResult.telemetryData) {
|
||||
if (!combinedTelemetryData) {
|
||||
combinedTelemetryData = { ...adjustResult.telemetryData };
|
||||
} else {
|
||||
// Sum up costs and tokens
|
||||
combinedTelemetryData.inputTokens +=
|
||||
adjustResult.telemetryData.inputTokens || 0;
|
||||
combinedTelemetryData.outputTokens +=
|
||||
adjustResult.telemetryData.outputTokens || 0;
|
||||
combinedTelemetryData.totalTokens +=
|
||||
adjustResult.telemetryData.totalTokens || 0;
|
||||
combinedTelemetryData.totalCost +=
|
||||
adjustResult.telemetryData.totalCost || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write updated data
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
log('info', `Successfully scoped up ${updatedTasks.length} task(s)`);
|
||||
}
|
||||
|
||||
return {
|
||||
updatedTasks,
|
||||
telemetryData: combinedTelemetryData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases task complexity (scope-down)
|
||||
* @param {string} tasksPath - Path to tasks.json file
|
||||
* @param {Array<number>} taskIds - Array of task IDs to scope down
|
||||
* @param {string} strength - Strength level ('light', 'regular', 'heavy')
|
||||
* @param {string} customPrompt - Optional custom instructions
|
||||
* @param {Object} context - Context object with projectRoot, tag, etc.
|
||||
* @param {string} outputFormat - Output format ('text' or 'json')
|
||||
* @returns {Promise<Object>} Results of the scope-down operation
|
||||
*/
|
||||
export async function scopeDownTask(
|
||||
tasksPath,
|
||||
taskIds,
|
||||
strength = 'regular',
|
||||
customPrompt = null,
|
||||
context = {},
|
||||
outputFormat = 'text'
|
||||
) {
|
||||
// Validate inputs
|
||||
if (!validateStrength(strength)) {
|
||||
throw new Error(
|
||||
`Invalid strength level: ${strength}. Must be one of: ${VALID_STRENGTHS.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const { projectRoot = '.', tag = 'master' } = context;
|
||||
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
const tasks = data?.tasks || [];
|
||||
|
||||
// Validate all task IDs exist
|
||||
for (const taskId of taskIds) {
|
||||
if (!taskExists(tasks, taskId)) {
|
||||
throw new Error(`Task with ID ${taskId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedTasks = [];
|
||||
let combinedTelemetryData = null;
|
||||
|
||||
// Process each task
|
||||
for (const taskId of taskIds) {
|
||||
const taskResult = findTaskById(tasks, taskId);
|
||||
const task = taskResult.task;
|
||||
if (!task) {
|
||||
throw new Error(`Task with ID ${taskId} not found`);
|
||||
}
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
log('info', `Scoping down task ${taskId}: ${task.title}`);
|
||||
}
|
||||
|
||||
// Get original complexity score (if available)
|
||||
const originalComplexity = getCurrentComplexityScore(taskId, context);
|
||||
if (originalComplexity && outputFormat === 'text') {
|
||||
console.log(`[INFO] Original complexity: ${originalComplexity}/10`);
|
||||
}
|
||||
|
||||
const adjustResult = await adjustTaskComplexity(
|
||||
task,
|
||||
'down',
|
||||
strength,
|
||||
customPrompt,
|
||||
context
|
||||
);
|
||||
|
||||
// Regenerate subtasks based on new complexity while preserving completed work
|
||||
const subtaskResult = await regenerateSubtasksForComplexity(
|
||||
adjustResult.updatedTask,
|
||||
tasksPath,
|
||||
context,
|
||||
'down',
|
||||
strength,
|
||||
originalComplexity
|
||||
);
|
||||
|
||||
// Log subtask regeneration info if in text mode
|
||||
if (outputFormat === 'text' && subtaskResult.regenerated) {
|
||||
log(
|
||||
'info',
|
||||
`Regenerated ${subtaskResult.generated} pending subtasks (preserved ${subtaskResult.preserved} completed)`
|
||||
);
|
||||
}
|
||||
|
||||
// Update task in data
|
||||
const taskIndex = data.tasks.findIndex((t) => t.id === taskId);
|
||||
if (taskIndex !== -1) {
|
||||
data.tasks[taskIndex] = subtaskResult.updatedTask;
|
||||
updatedTasks.push(subtaskResult.updatedTask);
|
||||
}
|
||||
|
||||
// Re-analyze complexity after scoping (if we have a session for AI calls)
|
||||
if (context.session && originalComplexity) {
|
||||
try {
|
||||
// Write the updated task first so complexity analysis can read it
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
// Re-analyze complexity
|
||||
const newComplexity = await reanalyzeTaskComplexity(
|
||||
subtaskResult.updatedTask,
|
||||
tasksPath,
|
||||
context
|
||||
);
|
||||
if (newComplexity && outputFormat === 'text') {
|
||||
const complexityChange = newComplexity - originalComplexity;
|
||||
const arrow =
|
||||
complexityChange > 0 ? '↗️' : complexityChange < 0 ? '↘️' : '➡️';
|
||||
console.log(
|
||||
`[INFO] New complexity: ${originalComplexity}/10 ${arrow} ${newComplexity}/10 (${complexityChange > 0 ? '+' : ''}${complexityChange})`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputFormat === 'text') {
|
||||
log('warn', `Could not re-analyze complexity: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine telemetry data
|
||||
if (adjustResult.telemetryData) {
|
||||
if (!combinedTelemetryData) {
|
||||
combinedTelemetryData = { ...adjustResult.telemetryData };
|
||||
} else {
|
||||
// Sum up costs and tokens
|
||||
combinedTelemetryData.inputTokens +=
|
||||
adjustResult.telemetryData.inputTokens || 0;
|
||||
combinedTelemetryData.outputTokens +=
|
||||
adjustResult.telemetryData.outputTokens || 0;
|
||||
combinedTelemetryData.totalTokens +=
|
||||
adjustResult.telemetryData.totalTokens || 0;
|
||||
combinedTelemetryData.totalCost +=
|
||||
adjustResult.telemetryData.totalCost || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write updated data
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
log('info', `Successfully scoped down ${updatedTasks.length} task(s)`);
|
||||
}
|
||||
|
||||
return {
|
||||
updatedTasks,
|
||||
telemetryData: combinedTelemetryData
|
||||
};
|
||||
}
|
||||
@@ -1640,23 +1640,80 @@ async function displayTaskById(
|
||||
}
|
||||
|
||||
// --- Suggested Actions ---
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Suggested Actions:') +
|
||||
'\n' +
|
||||
`${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` +
|
||||
`${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` +
|
||||
// Determine action 3 based on whether subtasks *exist* (use the source list for progress)
|
||||
(subtasksForProgress && subtasksForProgress.length > 0
|
||||
? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` // Example uses .1
|
||||
: `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`),
|
||||
{
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
const actions = [];
|
||||
let actionNumber = 1;
|
||||
|
||||
// Basic actions
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
|
||||
// Subtask-related action
|
||||
if (subtasksForProgress && subtasksForProgress.length > 0) {
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}`
|
||||
);
|
||||
} else {
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`
|
||||
);
|
||||
}
|
||||
actionNumber++;
|
||||
|
||||
// Complexity-based scope adjustment actions
|
||||
if (task.complexityScore) {
|
||||
const complexityScore = task.complexityScore;
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Re-analyze complexity: ${chalk.yellow(`task-master analyze-complexity --id=${task.id}`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
|
||||
// Add scope adjustment suggestions based on current complexity
|
||||
if (complexityScore >= 7) {
|
||||
// High complexity - suggest scoping down
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Scope down (simplify): ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=regular`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
if (complexityScore >= 9) {
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Heavy scope down: ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=heavy`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
}
|
||||
)
|
||||
} else if (complexityScore <= 4) {
|
||||
// Low complexity - suggest scoping up
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Scope up (add detail): ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=regular`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
if (complexityScore <= 2) {
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Heavy scope up: ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=heavy`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
}
|
||||
} else {
|
||||
// Medium complexity (5-6) - offer both options
|
||||
actions.push(
|
||||
`${chalk.cyan(`${actionNumber}.`)} Scope up/down: ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=light`)} or ${chalk.yellow(`scope-down --id=${task.id} --strength=light`)}`
|
||||
);
|
||||
actionNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Suggested Actions:') + '\n' + actions.join('\n'), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
})
|
||||
);
|
||||
|
||||
// Show FYI notice if migration occurred
|
||||
|
||||
Reference in New Issue
Block a user