Compare commits

..

1 Commits

Author SHA1 Message Date
Ralph Khreish
74a1abc3b2 chore: pimp my readme 2025-08-11 14:23:14 +02:00
12 changed files with 199 additions and 362 deletions

View File

@@ -1,12 +0,0 @@
---
"task-master-ai": minor
---
Add compact mode --compact / -c flag to the `tm list` CLI command
- outputs tasks in a minimal, git-style one-line format. This reduces verbose output from ~30+ lines of dashboards and tables to just 1 line per task, making it much easier to quickly scan available tasks.
- Git-style format: ID STATUS TITLE (PRIORITY) → DEPS
- Color-coded status, priority, and dependencies
- Smart title truncation and dependency abbreviation
- Subtask support with indentation
- Full backward compatibility with existing list options

View File

@@ -1,5 +0,0 @@
---
"extension": minor
---
Display current task ID on task details page

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Remove `clear` Taskmaster claude code commands since they were too close to the claude-code clear command

View File

@@ -0,0 +1,93 @@
Clear all subtasks from all tasks globally.
## Global Subtask Clearing
Remove all subtasks across the entire project. Use with extreme caution.
## Execution
```bash
task-master clear-subtasks --all
```
## Pre-Clear Analysis
1. **Project-Wide Summary**
```
Global Subtask Summary
━━━━━━━━━━━━━━━━━━━━
Total parent tasks: 12
Total subtasks: 47
- Completed: 15
- In-progress: 8
- Pending: 24
Work at risk: ~120 hours
```
2. **Critical Warnings**
- In-progress subtasks that will lose work
- Completed subtasks with valuable history
- Complex dependency chains
- Integration test results
## Double Confirmation
```
⚠️ DESTRUCTIVE OPERATION WARNING ⚠️
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This will remove ALL 47 subtasks from your project
Including 8 in-progress and 15 completed subtasks
This action CANNOT be undone
Type 'CLEAR ALL SUBTASKS' to confirm:
```
## Smart Safeguards
- Require explicit confirmation phrase
- Create automatic backup
- Log all removed data
- Option to export first
## Use Cases
Valid reasons for global clear:
- Project restructuring
- Major pivot in approach
- Starting fresh breakdown
- Switching to different task organization
## Process
1. Full project analysis
2. Create backup file
3. Show detailed impact
4. Require confirmation
5. Execute removal
6. Generate summary report
## Alternative Suggestions
Before clearing all:
- Export subtasks to file
- Clear only pending subtasks
- Clear by task category
- Archive instead of delete
## Post-Clear Report
```
Global Subtask Clear Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Removed: 47 subtasks from 12 tasks
Backup saved: .taskmaster/backup/subtasks-20240115.json
Parent tasks updated: 12
Time estimates adjusted: Yes
Next steps:
- Review updated task list
- Re-expand complex tasks as needed
- Check project timeline
```

View File

@@ -0,0 +1,86 @@
Clear all subtasks from a specific task.
Arguments: $ARGUMENTS (task ID)
Remove all subtasks from a parent task at once.
## Clearing Subtasks
Bulk removal of all subtasks from a parent task.
## Execution
```bash
task-master clear-subtasks --id=<task-id>
```
## Pre-Clear Analysis
1. **Subtask Summary**
- Number of subtasks
- Completion status of each
- Work already done
- Dependencies affected
2. **Impact Assessment**
- Data that will be lost
- Dependencies to be removed
- Effect on project timeline
- Parent task implications
## Confirmation Required
```
Clear Subtasks Confirmation
━━━━━━━━━━━━━━━━━━━━━━━━━
Parent Task: #5 "Implement user authentication"
Subtasks to remove: 4
- #5.1 "Setup auth framework" (done)
- #5.2 "Create login form" (in-progress)
- #5.3 "Add validation" (pending)
- #5.4 "Write tests" (pending)
⚠️ This will permanently delete all subtask data
Continue? (y/n)
```
## Smart Features
- Option to convert to standalone tasks
- Backup task data before clearing
- Preserve completed work history
- Update parent task appropriately
## Process
1. List all subtasks for confirmation
2. Check for in-progress work
3. Remove all subtasks
4. Update parent task
5. Clean up dependencies
## Alternative Options
Suggest alternatives:
- Convert important subtasks to tasks
- Keep completed subtasks
- Archive instead of delete
- Export subtask data first
## Post-Clear
- Show updated parent task
- Recalculate time estimates
- Update task complexity
- Suggest next steps
## Example
```
/project:tm/clear-subtasks 5
→ Found 4 subtasks to remove
→ Warning: Subtask #5.2 is in-progress
→ Cleared all subtasks from task #5
→ Updated parent task estimates
→ Suggestion: Consider re-expanding with better breakdown
```

View File

@@ -53,11 +53,6 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
refreshComplexityAfterAI
} = useTaskDetails({ taskId, sendMessage, tasks: allTasks });
const displayId =
isSubtask && parentTask
? `${parentTask.id}.${currentTask?.id}`
: currentTask?.id;
const handleStatusChange = async (newStatus: TaskMasterTask['status']) => {
if (!currentTask) return;
@@ -65,7 +60,10 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
await sendMessage({
type: 'updateTaskStatus',
data: {
taskId: displayId,
taskId:
isSubtask && parentTask
? `${parentTask.id}.${currentTask.id}`
: currentTask.id,
newStatus: newStatus
}
});
@@ -137,7 +135,7 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
<BreadcrumbSeparator />
<BreadcrumbItem>
<span className="text-vscode-foreground">
#{displayId} {currentTask.title}
{currentTask.title}
</span>
</BreadcrumbItem>
</BreadcrumbList>
@@ -154,9 +152,9 @@ export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
</button>
</div>
{/* Task ID and title */}
{/* Task title */}
<h1 className="text-2xl font-bold tracking-tight text-vscode-foreground">
#{displayId} {currentTask.title}
{currentTask.title}
</h1>
{/* Description */}

View File

@@ -1,4 +1,4 @@
# Available Models as of August 11, 2025
# Available Models as of August 8, 2025
## Main Models

View File

@@ -1753,7 +1753,6 @@ function registerCommands(programInstance) {
)
.option('-s, --status <status>', 'Filter by status')
.option('--with-subtasks', 'Show subtasks for each task')
.option('-c, --compact', 'Display tasks in compact one-line format')
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
// Initialize TaskMaster
@@ -1771,12 +1770,10 @@ function registerCommands(programInstance) {
const statusFilter = options.status;
const withSubtasks = options.withSubtasks || false;
const compact = options.compact || false;
const tag = taskMaster.getCurrentTag();
// Show current tag context
displayCurrentTagIndicator(tag);
if (!compact) {
console.log(
chalk.blue(`Listing tasks from: ${taskMaster.getTasksPath()}`)
);
@@ -1786,14 +1783,13 @@ function registerCommands(programInstance) {
if (withSubtasks) {
console.log(chalk.blue('Including subtasks in listing'));
}
}
await listTasks(
taskMaster.getTasksPath(),
statusFilter,
taskMaster.getComplexityReportPath(),
withSubtasks,
compact ? 'compact' : 'text',
'text',
{ projectRoot: taskMaster.getProjectRoot(), tag }
);
});

View File

@@ -294,11 +294,6 @@ function listTasks(
});
}
// For compact output, return minimal one-line format
if (outputFormat === 'compact') {
return renderCompactOutput(filteredTasks, withSubtasks);
}
// ... existing code for text output ...
// Calculate status breakdowns as percentages of total
@@ -967,98 +962,4 @@ function generateMarkdownOutput(data, filteredTasks, stats) {
return markdown;
}
/**
* Format dependencies for compact output with truncation and coloring
* @param {Array} dependencies - Array of dependency IDs
* @returns {string} - Formatted dependency string with arrow prefix
*/
function formatCompactDependencies(dependencies) {
if (!dependencies || dependencies.length === 0) {
return '';
}
if (dependencies.length > 5) {
const visible = dependencies.slice(0, 5).join(',');
const remaining = dependencies.length - 5;
return `${chalk.cyan(visible)}${chalk.gray('... (+' + remaining + ' more)')}`;
} else {
return `${chalk.cyan(dependencies.join(','))}`;
}
}
/**
* Format a single task in compact one-line format
* @param {Object} task - Task object
* @param {number} maxTitleLength - Maximum title length before truncation
* @returns {string} - Formatted task line
*/
function formatCompactTask(task, maxTitleLength = 50) {
const status = task.status || 'pending';
const priority = task.priority || 'medium';
const title = truncate(task.title || 'Untitled', maxTitleLength);
// Use colored status from existing function
const coloredStatus = getStatusWithColor(status, true);
// Color priority based on level
const priorityColors = {
high: chalk.red,
medium: chalk.yellow,
low: chalk.gray
};
const priorityColor = priorityColors[priority] || chalk.white;
// Format dependencies using shared helper
const depsText = formatCompactDependencies(task.dependencies);
return `${chalk.cyan(task.id)} ${coloredStatus} ${chalk.white(title)} ${priorityColor('(' + priority + ')')}${depsText}`;
}
/**
* Format a subtask in compact format with indentation
* @param {Object} subtask - Subtask object
* @param {string|number} parentId - Parent task ID
* @param {number} maxTitleLength - Maximum title length before truncation
* @returns {string} - Formatted subtask line
*/
function formatCompactSubtask(subtask, parentId, maxTitleLength = 47) {
const status = subtask.status || 'pending';
const title = truncate(subtask.title || 'Untitled', maxTitleLength);
// Use colored status from existing function
const coloredStatus = getStatusWithColor(status, true);
// Format dependencies using shared helper
const depsText = formatCompactDependencies(subtask.dependencies);
return ` ${chalk.cyan(parentId + '.' + subtask.id)} ${coloredStatus} ${chalk.dim(title)}${depsText}`;
}
/**
* Render complete compact output
* @param {Array} filteredTasks - Tasks to display
* @param {boolean} withSubtasks - Whether to include subtasks
* @returns {void} - Outputs directly to console
*/
function renderCompactOutput(filteredTasks, withSubtasks) {
if (filteredTasks.length === 0) {
console.log('No tasks found');
return;
}
const output = [];
filteredTasks.forEach((task) => {
output.push(formatCompactTask(task));
if (withSubtasks && task.subtasks && task.subtasks.length > 0) {
task.subtasks.forEach((subtask) => {
output.push(formatCompactSubtask(subtask, task.id));
});
}
});
console.log(output.join('\n'));
}
export default listTasks;

View File

@@ -1430,20 +1430,6 @@ function ensureTagMetadata(tagObj, opts = {}) {
return tagObj;
}
/**
* Strip ANSI color codes from a string
* Useful for testing, logging to files, or when clean text output is needed
* @param {string} text - The text that may contain ANSI color codes
* @returns {string} - The text with ANSI color codes removed
*/
function stripAnsiCodes(text) {
if (typeof text !== 'string') {
return text;
}
// Remove ANSI escape sequences (color codes, cursor movements, etc.)
return text.replace(/\x1b\[[0-9;]*m/g, '');
}
// Export all utility functions and configuration
export {
LOG_LEVELS,
@@ -1481,6 +1467,5 @@ export {
markMigrationForNotice,
flattenTasksWithSubtasks,
ensureTagMetadata,
stripAnsiCodes,
normalizeTaskIds
};

View File

@@ -22,10 +22,7 @@ jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
),
addComplexityToTask: jest.fn(),
readComplexityReport: jest.fn(() => null),
getTagAwareFilePath: jest.fn((tag, path) => '/mock/tagged/report.json'),
stripAnsiCodes: jest.fn((text) =>
text ? text.replace(/\x1b\[[0-9;]*m/g, '') : text
)
getTagAwareFilePath: jest.fn((tag, path) => '/mock/tagged/report.json')
}));
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
@@ -48,13 +45,8 @@ jest.unstable_mockModule(
);
// Import the mocked modules
const {
readJSON,
log,
readComplexityReport,
addComplexityToTask,
stripAnsiCodes
} = await import('../../../../../scripts/modules/utils.js');
const { readJSON, log, readComplexityReport, addComplexityToTask } =
await import('../../../../../scripts/modules/utils.js');
const { displayTaskList } = await import(
'../../../../../scripts/modules/ui.js'
);
@@ -592,140 +584,4 @@ describe('listTasks', () => {
expect(taskIds).toContain(5); // review task
});
});
describe('Compact output format', () => {
test('should output compact format when outputFormat is compact', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should contain compact format elements: ID status title (priority) [→ dependencies]
expect(cleanOutput).toContain('1 done Setup Project (high)');
expect(cleanOutput).toContain(
'2 pending Implement Core Features (high) → 1'
);
consoleSpy.mockRestore();
});
test('should format single task compactly', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Should be compact (no verbose headers)
expect(output).not.toContain('Project Dashboard');
expect(output).not.toContain('Progress:');
consoleSpy.mockRestore();
});
test('should handle compact format with subtasks', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(
tasksPath,
null,
null,
true, // withSubtasks = true
'compact',
{ tag: 'master' }
);
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should handle both tasks and subtasks
expect(cleanOutput).toContain('1 done Setup Project (high)');
expect(cleanOutput).toContain('3.1 done Create Header Component');
consoleSpy.mockRestore();
});
test('should handle empty task list in compact format', async () => {
readJSON.mockReturnValue({ tasks: [] });
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalledWith('No tasks found');
consoleSpy.mockRestore();
});
test('should format dependencies correctly with shared helper', async () => {
// Create mock tasks with various dependency scenarios
const tasksWithDeps = {
tasks: [
{
id: 1,
title: 'Task with no dependencies',
status: 'pending',
priority: 'medium',
dependencies: []
},
{
id: 2,
title: 'Task with few dependencies',
status: 'pending',
priority: 'high',
dependencies: [1, 3]
},
{
id: 3,
title: 'Task with many dependencies',
status: 'pending',
priority: 'low',
dependencies: [1, 2, 4, 5, 6, 7, 8, 9]
}
]
};
readJSON.mockReturnValue(tasksWithDeps);
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const tasksPath = 'tasks/tasks.json';
await listTasks(tasksPath, null, null, false, 'compact', {
tag: 'master'
});
expect(consoleSpy).toHaveBeenCalled();
const output = consoleSpy.mock.calls.map((call) => call[0]).join('\n');
// Strip ANSI color codes for testing
const cleanOutput = stripAnsiCodes(output);
// Should format tasks correctly with compact output including priority
expect(cleanOutput).toContain(
'1 pending Task with no dependencies (medium)'
);
expect(cleanOutput).toContain('Task with few dependencies');
expect(cleanOutput).toContain('Task with many dependencies');
// Should show dependencies with arrow when they exist
expect(cleanOutput).toMatch(/2.*→.*1,3/);
// Should truncate many dependencies with "+X more" format
expect(cleanOutput).toMatch(/3.*→.*1,2,4,5,6.*\(\+\d+ more\)/);
consoleSpy.mockRestore();
});
});
});

View File

@@ -1,56 +0,0 @@
/**
* Tests for the stripAnsiCodes utility function
*/
import { jest } from '@jest/globals';
// Import the module under test
const { stripAnsiCodes } = await import('../../scripts/modules/utils.js');
describe('stripAnsiCodes', () => {
test('should remove ANSI color codes from text', () => {
const textWithColors = '\x1b[31mRed text\x1b[0m \x1b[32mGreen text\x1b[0m';
const result = stripAnsiCodes(textWithColors);
expect(result).toBe('Red text Green text');
});
test('should handle text without ANSI codes', () => {
const plainText = 'This is plain text';
const result = stripAnsiCodes(plainText);
expect(result).toBe('This is plain text');
});
test('should handle empty string', () => {
const result = stripAnsiCodes('');
expect(result).toBe('');
});
test('should handle complex ANSI sequences', () => {
// Test with various ANSI escape sequences
const complexText =
'\x1b[1;31mBold red\x1b[0m \x1b[4;32mUnderlined green\x1b[0m \x1b[33;46mYellow on cyan\x1b[0m';
const result = stripAnsiCodes(complexText);
expect(result).toBe('Bold red Underlined green Yellow on cyan');
});
test('should handle non-string input gracefully', () => {
expect(stripAnsiCodes(null)).toBe(null);
expect(stripAnsiCodes(undefined)).toBe(undefined);
expect(stripAnsiCodes(123)).toBe(123);
expect(stripAnsiCodes({})).toEqual({});
});
test('should handle real chalk output patterns', () => {
// Test patterns similar to what chalk produces
const chalkLikeText =
'1 \x1b[32m✓ done\x1b[39m Setup Project \x1b[31m(high)\x1b[39m';
const result = stripAnsiCodes(chalkLikeText);
expect(result).toBe('1 ✓ done Setup Project (high)');
});
test('should handle multiline text with ANSI codes', () => {
const multilineText =
'\x1b[31mLine 1\x1b[0m\n\x1b[32mLine 2\x1b[0m\n\x1b[33mLine 3\x1b[0m';
const result = stripAnsiCodes(multilineText);
expect(result).toBe('Line 1\nLine 2\nLine 3');
});
});