refactor: enhance add-task fuzzy search and fix duplicate banner display
- **Remove hardcoded category system** in add-task that always matched 'Task management' - **Eliminate arbitrary limits** in fuzzy search results (5→25 high relevance, 3→10 medium relevance, 8→20 detailed tasks) - **Improve semantic weighting** in Fuse.js search (details=3, description=2, title=1.5) for better relevance - **Fix duplicate banner issue** by removing console.clear() and redundant displayBanner() calls from UI functions - **Enhance context generation** to rely on semantic similarity rather than rigid pattern matching - **Preserve terminal history** to address GitHub issue #553 about eating terminal lines - **Remove displayBanner() calls** from: displayHelp, displayNextTask, displayTaskById, displayComplexityReport, set-task-status, clear-subtasks, dependency-manager functions The add-task system now provides truly relevant task context based on semantic similarity rather than arbitrary categories and limits, while maintaining a cleaner terminal experience. Changes span: add-task.js, ui.js, set-task-status.js, clear-subtasks.js, list-tasks.js, dependency-manager.js Closes #553
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"models": {
|
||||
"main": {
|
||||
"provider": "openrouter",
|
||||
"modelId": "qwen/qwen3-235b-a22b:free",
|
||||
"provider": "anthropic",
|
||||
"modelId": "claude-sonnet-4-20250514",
|
||||
"maxTokens": 50000,
|
||||
"temperature": 0.2
|
||||
},
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task ID: 97
|
||||
# Title: Create Taskmaster Jingle Implementation
|
||||
# Status: pending
|
||||
# Dependencies: 95, 57, 3, 2
|
||||
# Priority: medium
|
||||
# Description: Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.
|
||||
# Details:
|
||||
This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:
|
||||
|
||||
1. Audio System Integration:
|
||||
- Research and select appropriate audio library compatible with Node.js CLI applications
|
||||
- Implement cross-platform audio playback (Windows, macOS, Linux)
|
||||
- Create sound configuration options in .taskmasterconfig
|
||||
|
||||
2. Jingle Design:
|
||||
- Define sound triggers for key events (task creation, completion, errors, etc.)
|
||||
- Create or source appropriate sound files (WAV/MP3 format)
|
||||
- Implement volume control and mute option in settings
|
||||
|
||||
3. CLI Integration:
|
||||
- Add sound playback to core CLI commands (init, create, update, delete)
|
||||
- Implement optional sound effects toggle via command line flags
|
||||
- Ensure audio playback doesn't interfere with CLI performance
|
||||
|
||||
4. Documentation:
|
||||
- Update user guide with sound configuration instructions
|
||||
- Add troubleshooting section for audio playback issues
|
||||
|
||||
# Test Strategy:
|
||||
1. Verify audio plays correctly during each supported CLI operation
|
||||
2. Test sound configuration options across different platforms
|
||||
3. Confirm volume control and mute functionality works as expected
|
||||
4. Validate that audio playback doesn't affect CLI performance
|
||||
5. Test edge cases (no audio hardware, invalid sound files, etc.)
|
||||
6. Ensure sound effects can be disabled via configuration and CLI flags
|
||||
@@ -5871,22 +5871,6 @@
|
||||
"parentTaskId": 96
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 97,
|
||||
"title": "Create Taskmaster Jingle Implementation",
|
||||
"description": "Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.",
|
||||
"details": "This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:\n\n1. Audio System Integration:\n - Research and select appropriate audio library compatible with Node.js CLI applications\n - Implement cross-platform audio playback (Windows, macOS, Linux)\n - Create sound configuration options in .taskmasterconfig\n\n2. Jingle Design:\n - Define sound triggers for key events (task creation, completion, errors, etc.)\n - Create or source appropriate sound files (WAV/MP3 format)\n - Implement volume control and mute option in settings\n\n3. CLI Integration:\n - Add sound playback to core CLI commands (init, create, update, delete)\n - Implement optional sound effects toggle via command line flags\n - Ensure audio playback doesn't interfere with CLI performance\n\n4. Documentation:\n - Update user guide with sound configuration instructions\n - Add troubleshooting section for audio playback issues",
|
||||
"testStrategy": "1. Verify audio plays correctly during each supported CLI operation\n2. Test sound configuration options across different platforms\n3. Confirm volume control and mute functionality works as expected\n4. Validate that audio playback doesn't affect CLI performance\n5. Test edge cases (no audio hardware, invalid sound files, etc.)\n6. Ensure sound effects can be disabled via configuration and CLI flags",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
95,
|
||||
57,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"priority": "medium",
|
||||
"subtasks": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
* Manages task dependencies and relationships
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
|
||||
import {
|
||||
log,
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
taskExists,
|
||||
formatTaskId,
|
||||
findCycles,
|
||||
isSilentMode
|
||||
} from './utils.js';
|
||||
isSilentMode,
|
||||
} from "./utils.js";
|
||||
|
||||
import { displayBanner } from './ui.js';
|
||||
import { displayBanner } from "./ui.js";
|
||||
|
||||
import { generateTaskFiles } from './task-manager.js';
|
||||
import { generateTaskFiles } from "./task-manager.js";
|
||||
|
||||
/**
|
||||
* Add a dependency to a task
|
||||
@@ -28,17 +28,17 @@ import { generateTaskFiles } from './task-manager.js';
|
||||
* @param {number|string} dependencyId - ID of the task to add as dependency
|
||||
*/
|
||||
async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);
|
||||
log("info", `Adding dependency ${dependencyId} to task ${taskId}...`);
|
||||
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found in tasks.json');
|
||||
log("error", "No valid tasks found in tasks.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Format the task and dependency IDs correctly
|
||||
const formattedTaskId =
|
||||
typeof taskId === 'string' && taskId.includes('.')
|
||||
typeof taskId === "string" && taskId.includes(".")
|
||||
? taskId
|
||||
: parseInt(taskId, 10);
|
||||
|
||||
@@ -47,7 +47,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
// Check if the dependency task or subtask actually exists
|
||||
if (!taskExists(data.tasks, formattedDependencyId)) {
|
||||
log(
|
||||
'error',
|
||||
"error",
|
||||
`Dependency target ${formattedDependencyId} does not exist in tasks.json`
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -57,20 +57,20 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
let targetTask = null;
|
||||
let isSubtask = false;
|
||||
|
||||
if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
|
||||
if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) {
|
||||
// Handle dot notation for subtasks (e.g., "1.2")
|
||||
const [parentId, subtaskId] = formattedTaskId
|
||||
.split('.')
|
||||
.split(".")
|
||||
.map((id) => parseInt(id, 10));
|
||||
const parentTask = data.tasks.find((t) => t.id === parentId);
|
||||
|
||||
if (!parentTask) {
|
||||
log('error', `Parent task ${parentId} not found.`);
|
||||
log("error", `Parent task ${parentId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!parentTask.subtasks) {
|
||||
log('error', `Parent task ${parentId} has no subtasks.`);
|
||||
log("error", `Parent task ${parentId} has no subtasks.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
isSubtask = true;
|
||||
|
||||
if (!targetTask) {
|
||||
log('error', `Subtask ${formattedTaskId} not found.`);
|
||||
log("error", `Subtask ${formattedTaskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
@@ -86,7 +86,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
targetTask = data.tasks.find((t) => t.id === formattedTaskId);
|
||||
|
||||
if (!targetTask) {
|
||||
log('error', `Task ${formattedTaskId} not found.`);
|
||||
log("error", `Task ${formattedTaskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
})
|
||||
) {
|
||||
log(
|
||||
'warn',
|
||||
"warn",
|
||||
`Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`
|
||||
);
|
||||
return;
|
||||
@@ -112,7 +112,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
|
||||
// Check if the task is trying to depend on itself - compare full IDs (including subtask parts)
|
||||
if (String(formattedTaskId) === String(formattedDependencyId)) {
|
||||
log('error', `Task ${formattedTaskId} cannot depend on itself.`);
|
||||
log("error", `Task ${formattedTaskId} cannot depend on itself.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -121,30 +121,30 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
let isSelfDependency = false;
|
||||
|
||||
if (
|
||||
typeof formattedTaskId === 'string' &&
|
||||
typeof formattedDependencyId === 'string' &&
|
||||
formattedTaskId.includes('.') &&
|
||||
formattedDependencyId.includes('.')
|
||||
typeof formattedTaskId === "string" &&
|
||||
typeof formattedDependencyId === "string" &&
|
||||
formattedTaskId.includes(".") &&
|
||||
formattedDependencyId.includes(".")
|
||||
) {
|
||||
const [taskParentId] = formattedTaskId.split('.');
|
||||
const [depParentId] = formattedDependencyId.split('.');
|
||||
const [taskParentId] = formattedTaskId.split(".");
|
||||
const [depParentId] = formattedDependencyId.split(".");
|
||||
|
||||
// Only treat it as a self-dependency if both the parent ID and subtask ID are identical
|
||||
isSelfDependency = formattedTaskId === formattedDependencyId;
|
||||
|
||||
// Log for debugging
|
||||
log(
|
||||
'debug',
|
||||
"debug",
|
||||
`Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}`
|
||||
);
|
||||
log(
|
||||
'debug',
|
||||
"debug",
|
||||
`Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}`
|
||||
);
|
||||
}
|
||||
|
||||
if (isSelfDependency) {
|
||||
log('error', `Subtask ${formattedTaskId} cannot depend on itself.`);
|
||||
log("error", `Subtask ${formattedTaskId} cannot depend on itself.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -158,13 +158,13 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
|
||||
// Sort dependencies numerically or by parent task ID first, then subtask ID
|
||||
targetTask.dependencies.sort((a, b) => {
|
||||
if (typeof a === 'number' && typeof b === 'number') {
|
||||
if (typeof a === "number" && typeof b === "number") {
|
||||
return a - b;
|
||||
} else if (typeof a === 'string' && typeof b === 'string') {
|
||||
const [aParent, aChild] = a.split('.').map(Number);
|
||||
const [bParent, bChild] = b.split('.').map(Number);
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
const [aParent, aChild] = a.split(".").map(Number);
|
||||
const [bParent, bChild] = b.split(".").map(Number);
|
||||
return aParent !== bParent ? aParent - bParent : aChild - bChild;
|
||||
} else if (typeof a === 'number') {
|
||||
} else if (typeof a === "number") {
|
||||
return -1; // Numbers come before strings
|
||||
} else {
|
||||
return 1; // Strings come after numbers
|
||||
@@ -174,7 +174,7 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
// Save changes
|
||||
writeJSON(tasksPath, data);
|
||||
log(
|
||||
'success',
|
||||
"success",
|
||||
`Added dependency ${formattedDependencyId} to task ${formattedTaskId}`
|
||||
);
|
||||
|
||||
@@ -186,9 +186,9 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -197,10 +197,10 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
// Generate updated task files
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
|
||||
log('info', 'Task files regenerated with updated dependencies.');
|
||||
log("info", "Task files regenerated with updated dependencies.");
|
||||
} else {
|
||||
log(
|
||||
'error',
|
||||
"error",
|
||||
`Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -214,18 +214,18 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
* @param {number|string} dependencyId - ID of the task to remove as dependency
|
||||
*/
|
||||
async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
log('info', `Removing dependency ${dependencyId} from task ${taskId}...`);
|
||||
log("info", `Removing dependency ${dependencyId} from task ${taskId}...`);
|
||||
|
||||
// Read tasks file
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found.');
|
||||
log("error", "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Format the task and dependency IDs correctly
|
||||
const formattedTaskId =
|
||||
typeof taskId === 'string' && taskId.includes('.')
|
||||
typeof taskId === "string" && taskId.includes(".")
|
||||
? taskId
|
||||
: parseInt(taskId, 10);
|
||||
|
||||
@@ -235,20 +235,20 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
let targetTask = null;
|
||||
let isSubtask = false;
|
||||
|
||||
if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
|
||||
if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) {
|
||||
// Handle dot notation for subtasks (e.g., "1.2")
|
||||
const [parentId, subtaskId] = formattedTaskId
|
||||
.split('.')
|
||||
.split(".")
|
||||
.map((id) => parseInt(id, 10));
|
||||
const parentTask = data.tasks.find((t) => t.id === parentId);
|
||||
|
||||
if (!parentTask) {
|
||||
log('error', `Parent task ${parentId} not found.`);
|
||||
log("error", `Parent task ${parentId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!parentTask.subtasks) {
|
||||
log('error', `Parent task ${parentId} has no subtasks.`);
|
||||
log("error", `Parent task ${parentId} has no subtasks.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
isSubtask = true;
|
||||
|
||||
if (!targetTask) {
|
||||
log('error', `Subtask ${formattedTaskId} not found.`);
|
||||
log("error", `Subtask ${formattedTaskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
@@ -264,7 +264,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
targetTask = data.tasks.find((t) => t.id === formattedTaskId);
|
||||
|
||||
if (!targetTask) {
|
||||
log('error', `Task ${formattedTaskId} not found.`);
|
||||
log("error", `Task ${formattedTaskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -272,7 +272,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
// Check if the task has any dependencies
|
||||
if (!targetTask.dependencies || targetTask.dependencies.length === 0) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Task ${formattedTaskId} has no dependencies, nothing to remove.`
|
||||
);
|
||||
return;
|
||||
@@ -287,10 +287,10 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
let depStr = String(dep);
|
||||
|
||||
// Special handling for numeric IDs that might be subtask references
|
||||
if (typeof dep === 'number' && dep < 100 && isSubtask) {
|
||||
if (typeof dep === "number" && dep < 100 && isSubtask) {
|
||||
// It's likely a reference to another subtask in the same parent task
|
||||
// Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1)
|
||||
const [parentId] = formattedTaskId.split('.');
|
||||
const [parentId] = formattedTaskId.split(".");
|
||||
depStr = `${parentId}.${dep}`;
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
|
||||
if (dependencyIndex === -1) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`
|
||||
);
|
||||
return;
|
||||
@@ -313,7 +313,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
|
||||
// Success message
|
||||
log(
|
||||
'success',
|
||||
"success",
|
||||
`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
|
||||
);
|
||||
|
||||
@@ -325,9 +325,9 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -358,8 +358,8 @@ function isCircularDependency(tasks, taskId, chain = []) {
|
||||
let parentIdForSubtask = null;
|
||||
|
||||
// Check if this is a subtask reference (e.g., "1.2")
|
||||
if (taskIdStr.includes('.')) {
|
||||
const [parentId, subtaskId] = taskIdStr.split('.').map(Number);
|
||||
if (taskIdStr.includes(".")) {
|
||||
const [parentId, subtaskId] = taskIdStr.split(".").map(Number);
|
||||
const parentTask = tasks.find((t) => t.id === parentId);
|
||||
parentIdForSubtask = parentId; // Store parent ID if it's a subtask
|
||||
|
||||
@@ -385,7 +385,7 @@ function isCircularDependency(tasks, taskId, chain = []) {
|
||||
return task.dependencies.some((depId) => {
|
||||
let normalizedDepId = String(depId);
|
||||
// Normalize relative subtask dependencies
|
||||
if (typeof depId === 'number' && parentIdForSubtask !== null) {
|
||||
if (typeof depId === "number" && parentIdForSubtask !== null) {
|
||||
// If the current task is a subtask AND the dependency is a number,
|
||||
// assume it refers to a sibling subtask.
|
||||
normalizedDepId = `${parentIdForSubtask}.${depId}`;
|
||||
@@ -413,9 +413,9 @@ function validateTaskDependencies(tasks) {
|
||||
// Check for self-dependencies
|
||||
if (String(depId) === String(task.id)) {
|
||||
issues.push({
|
||||
type: 'self',
|
||||
type: "self",
|
||||
taskId: task.id,
|
||||
message: `Task ${task.id} depends on itself`
|
||||
message: `Task ${task.id} depends on itself`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -423,10 +423,10 @@ function validateTaskDependencies(tasks) {
|
||||
// Check if dependency exists
|
||||
if (!taskExists(tasks, depId)) {
|
||||
issues.push({
|
||||
type: 'missing',
|
||||
type: "missing",
|
||||
taskId: task.id,
|
||||
dependencyId: depId,
|
||||
message: `Task ${task.id} depends on non-existent task ${depId}`
|
||||
message: `Task ${task.id} depends on non-existent task ${depId}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -434,9 +434,9 @@ function validateTaskDependencies(tasks) {
|
||||
// Check for circular dependencies
|
||||
if (isCircularDependency(tasks, task.id)) {
|
||||
issues.push({
|
||||
type: 'circular',
|
||||
type: "circular",
|
||||
taskId: task.id,
|
||||
message: `Task ${task.id} is part of a circular dependency chain`
|
||||
message: `Task ${task.id} is part of a circular dependency chain`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -454,12 +454,12 @@ function validateTaskDependencies(tasks) {
|
||||
// Check for self-dependencies in subtasks
|
||||
if (
|
||||
String(depId) === String(fullSubtaskId) ||
|
||||
(typeof depId === 'number' && depId === subtask.id)
|
||||
(typeof depId === "number" && depId === subtask.id)
|
||||
) {
|
||||
issues.push({
|
||||
type: 'self',
|
||||
type: "self",
|
||||
taskId: fullSubtaskId,
|
||||
message: `Subtask ${fullSubtaskId} depends on itself`
|
||||
message: `Subtask ${fullSubtaskId} depends on itself`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -467,10 +467,10 @@ function validateTaskDependencies(tasks) {
|
||||
// Check if dependency exists
|
||||
if (!taskExists(tasks, depId)) {
|
||||
issues.push({
|
||||
type: 'missing',
|
||||
type: "missing",
|
||||
taskId: fullSubtaskId,
|
||||
dependencyId: depId,
|
||||
message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`
|
||||
message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -478,9 +478,9 @@ function validateTaskDependencies(tasks) {
|
||||
// Check for circular dependencies in subtasks
|
||||
if (isCircularDependency(tasks, fullSubtaskId)) {
|
||||
issues.push({
|
||||
type: 'circular',
|
||||
type: "circular",
|
||||
taskId: fullSubtaskId,
|
||||
message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`
|
||||
message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -489,7 +489,7 @@ function validateTaskDependencies(tasks) {
|
||||
|
||||
return {
|
||||
valid: issues.length === 0,
|
||||
issues
|
||||
issues,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -508,13 +508,13 @@ function removeDuplicateDependencies(tasksData) {
|
||||
const uniqueDeps = [...new Set(task.dependencies)];
|
||||
return {
|
||||
...task,
|
||||
dependencies: uniqueDeps
|
||||
dependencies: uniqueDeps,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...tasksData,
|
||||
tasks
|
||||
tasks,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ function cleanupSubtaskDependencies(tasksData) {
|
||||
|
||||
return {
|
||||
...tasksData,
|
||||
tasks
|
||||
tasks,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -563,17 +563,12 @@ function cleanupSubtaskDependencies(tasksData) {
|
||||
* @param {string} tasksPath - Path to tasks.json
|
||||
*/
|
||||
async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
// Only display banner if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
log('info', 'Checking for invalid dependencies in task files...');
|
||||
log("info", "Checking for invalid dependencies in task files...");
|
||||
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found in tasks.json');
|
||||
log("error", "No valid tasks found in tasks.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -587,7 +582,7 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
});
|
||||
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...`
|
||||
);
|
||||
|
||||
@@ -597,7 +592,7 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
if (!validationResult.valid) {
|
||||
log(
|
||||
'error',
|
||||
"error",
|
||||
`Dependency validation failed. Found ${validationResult.issues.length} issue(s):`
|
||||
);
|
||||
validationResult.issues.forEach((issue) => {
|
||||
@@ -605,7 +600,7 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
if (issue.dependencyId) {
|
||||
errorMsg += ` (Dependency: ${issue.dependencyId})`;
|
||||
}
|
||||
log('error', errorMsg); // Log each issue as an error
|
||||
log("error", errorMsg); // Log each issue as an error
|
||||
});
|
||||
|
||||
// Optionally exit if validation fails, depending on desired behavior
|
||||
@@ -616,22 +611,22 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.red(`Dependency Validation FAILED\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.red('Issues found:')} ${validationResult.issues.length}`, // Display count from result
|
||||
`${chalk.cyan("Tasks checked:")} ${taskCount}\n` +
|
||||
`${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` +
|
||||
`${chalk.red("Issues found:")} ${validationResult.issues.length}`, // Display count from result
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'red',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "red",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log(
|
||||
'success',
|
||||
'No invalid dependencies found - all dependencies are valid'
|
||||
"success",
|
||||
"No invalid dependencies found - all dependencies are valid"
|
||||
);
|
||||
|
||||
// Show validation summary - only if not in silent mode
|
||||
@@ -639,21 +634,21 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
`${chalk.cyan("Tasks checked:")} ${taskCount}\n` +
|
||||
`${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` +
|
||||
`${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Error validating dependencies:', error);
|
||||
log("error", "Error validating dependencies:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -691,18 +686,13 @@ function countAllDependencies(tasks) {
|
||||
* @param {Object} options - Options object
|
||||
*/
|
||||
async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
// Only display banner if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
log('info', 'Checking for and fixing invalid dependencies in tasks.json...');
|
||||
log("info", "Checking for and fixing invalid dependencies in tasks.json...");
|
||||
|
||||
try {
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found in tasks.json');
|
||||
log("error", "No valid tasks found in tasks.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -716,7 +706,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
duplicateDependenciesRemoved: 0,
|
||||
circularDependenciesFixed: 0,
|
||||
tasksFixed: 0,
|
||||
subtasksFixed: 0
|
||||
subtasksFixed: 0,
|
||||
};
|
||||
|
||||
// First phase: Remove duplicate dependencies in tasks
|
||||
@@ -728,7 +718,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
const depIdStr = String(depId);
|
||||
if (uniqueDeps.has(depIdStr)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing duplicate dependency from task ${task.id}: ${depId}`
|
||||
);
|
||||
stats.duplicateDependenciesRemoved++;
|
||||
@@ -750,12 +740,12 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
const originalLength = subtask.dependencies.length;
|
||||
subtask.dependencies = subtask.dependencies.filter((depId) => {
|
||||
let depIdStr = String(depId);
|
||||
if (typeof depId === 'number' && depId < 100) {
|
||||
if (typeof depId === "number" && depId < 100) {
|
||||
depIdStr = `${task.id}.${depId}`;
|
||||
}
|
||||
if (uniqueDeps.has(depIdStr)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`
|
||||
);
|
||||
stats.duplicateDependenciesRemoved++;
|
||||
@@ -788,13 +778,13 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
if (task.dependencies && Array.isArray(task.dependencies)) {
|
||||
const originalLength = task.dependencies.length;
|
||||
task.dependencies = task.dependencies.filter((depId) => {
|
||||
const isSubtask = typeof depId === 'string' && depId.includes('.');
|
||||
const isSubtask = typeof depId === "string" && depId.includes(".");
|
||||
|
||||
if (isSubtask) {
|
||||
// Check if the subtask exists
|
||||
if (!validSubtaskIds.has(depId)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`
|
||||
);
|
||||
stats.nonExistentDependenciesRemoved++;
|
||||
@@ -804,10 +794,10 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
} else {
|
||||
// Check if the task exists
|
||||
const numericId =
|
||||
typeof depId === 'string' ? parseInt(depId, 10) : depId;
|
||||
typeof depId === "string" ? parseInt(depId, 10) : depId;
|
||||
if (!validTaskIds.has(numericId)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`
|
||||
);
|
||||
stats.nonExistentDependenciesRemoved++;
|
||||
@@ -831,9 +821,9 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
// First check for self-dependencies
|
||||
const hasSelfDependency = subtask.dependencies.some((depId) => {
|
||||
if (typeof depId === 'string' && depId.includes('.')) {
|
||||
if (typeof depId === "string" && depId.includes(".")) {
|
||||
return depId === subtaskId;
|
||||
} else if (typeof depId === 'number' && depId < 100) {
|
||||
} else if (typeof depId === "number" && depId < 100) {
|
||||
return depId === subtask.id;
|
||||
}
|
||||
return false;
|
||||
@@ -842,13 +832,13 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
if (hasSelfDependency) {
|
||||
subtask.dependencies = subtask.dependencies.filter((depId) => {
|
||||
const normalizedDepId =
|
||||
typeof depId === 'number' && depId < 100
|
||||
typeof depId === "number" && depId < 100
|
||||
? `${task.id}.${depId}`
|
||||
: String(depId);
|
||||
|
||||
if (normalizedDepId === subtaskId) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing self-dependency from subtask ${subtaskId}`
|
||||
);
|
||||
stats.selfDependenciesRemoved++;
|
||||
@@ -860,10 +850,10 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
// Then check for non-existent dependencies
|
||||
subtask.dependencies = subtask.dependencies.filter((depId) => {
|
||||
if (typeof depId === 'string' && depId.includes('.')) {
|
||||
if (typeof depId === "string" && depId.includes(".")) {
|
||||
if (!validSubtaskIds.has(depId)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)`
|
||||
);
|
||||
stats.nonExistentDependenciesRemoved++;
|
||||
@@ -874,7 +864,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
// Handle numeric dependencies
|
||||
const numericId =
|
||||
typeof depId === 'number' ? depId : parseInt(depId, 10);
|
||||
typeof depId === "number" ? depId : parseInt(depId, 10);
|
||||
|
||||
// Small numbers likely refer to subtasks in the same task
|
||||
if (numericId < 100) {
|
||||
@@ -882,7 +872,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
if (!validSubtaskIds.has(fullSubtaskId)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`
|
||||
);
|
||||
stats.nonExistentDependenciesRemoved++;
|
||||
@@ -895,7 +885,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
// Otherwise it's a task reference
|
||||
if (!validTaskIds.has(numericId)) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`
|
||||
);
|
||||
stats.nonExistentDependenciesRemoved++;
|
||||
@@ -914,7 +904,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
});
|
||||
|
||||
// Third phase: Check for circular dependencies
|
||||
log('info', 'Checking for circular dependencies...');
|
||||
log("info", "Checking for circular dependencies...");
|
||||
|
||||
// Build the dependency map for subtasks
|
||||
const subtaskDependencyMap = new Map();
|
||||
@@ -925,9 +915,9 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
if (subtask.dependencies && Array.isArray(subtask.dependencies)) {
|
||||
const normalizedDeps = subtask.dependencies.map((depId) => {
|
||||
if (typeof depId === 'string' && depId.includes('.')) {
|
||||
if (typeof depId === "string" && depId.includes(".")) {
|
||||
return depId;
|
||||
} else if (typeof depId === 'number' && depId < 100) {
|
||||
} else if (typeof depId === "number" && depId < 100) {
|
||||
return `${task.id}.${depId}`;
|
||||
}
|
||||
return String(depId);
|
||||
@@ -955,7 +945,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
if (cycleEdges.length > 0) {
|
||||
const [taskId, subtaskNum] = subtaskId
|
||||
.split('.')
|
||||
.split(".")
|
||||
.map((part) => Number(part));
|
||||
const task = data.tasks.find((t) => t.id === taskId);
|
||||
|
||||
@@ -966,9 +956,9 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
const originalLength = subtask.dependencies.length;
|
||||
|
||||
const edgesToRemove = cycleEdges.map((edge) => {
|
||||
if (edge.includes('.')) {
|
||||
if (edge.includes(".")) {
|
||||
const [depTaskId, depSubtaskId] = edge
|
||||
.split('.')
|
||||
.split(".")
|
||||
.map((part) => Number(part));
|
||||
|
||||
if (depTaskId === taskId) {
|
||||
@@ -983,7 +973,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
subtask.dependencies = subtask.dependencies.filter((depId) => {
|
||||
const normalizedDepId =
|
||||
typeof depId === 'number' && depId < 100
|
||||
typeof depId === "number" && depId < 100
|
||||
? `${taskId}.${depId}`
|
||||
: String(depId);
|
||||
|
||||
@@ -992,7 +982,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
edgesToRemove.includes(normalizedDepId)
|
||||
) {
|
||||
log(
|
||||
'info',
|
||||
"info",
|
||||
`Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}`
|
||||
);
|
||||
stats.circularDependenciesFixed++;
|
||||
@@ -1015,13 +1005,13 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
if (dataChanged) {
|
||||
// Save the changes
|
||||
writeJSON(tasksPath, data);
|
||||
log('success', 'Fixed dependency issues in tasks.json');
|
||||
log("success", "Fixed dependency issues in tasks.json");
|
||||
|
||||
// Regenerate task files
|
||||
log('info', 'Regenerating task files to reflect dependency changes...');
|
||||
log("info", "Regenerating task files to reflect dependency changes...");
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
} else {
|
||||
log('info', 'No changes needed to fix dependencies');
|
||||
log("info", "No changes needed to fix dependencies");
|
||||
}
|
||||
|
||||
// Show detailed statistics report
|
||||
@@ -1033,48 +1023,48 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
|
||||
if (!isSilentMode()) {
|
||||
if (totalFixedAll > 0) {
|
||||
log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
|
||||
log("success", `Fixed ${totalFixedAll} dependency issues in total!`);
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Dependency Fixes Summary:\n\n`) +
|
||||
`${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` +
|
||||
`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`,
|
||||
`${chalk.cyan("Invalid dependencies removed:")} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan("Self-dependencies removed:")} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan("Duplicate dependencies removed:")} ${stats.duplicateDependenciesRemoved}\n` +
|
||||
`${chalk.cyan("Circular dependencies fixed:")} ${stats.circularDependenciesFixed}\n\n` +
|
||||
`${chalk.cyan("Tasks fixed:")} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan("Subtasks fixed:")} ${stats.subtasksFixed}\n`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
log(
|
||||
'success',
|
||||
'No dependency issues found - all dependencies are valid'
|
||||
"success",
|
||||
"No dependency issues found - all dependencies are valid"
|
||||
);
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
`${chalk.cyan("Tasks checked:")} ${data.tasks.length}\n` +
|
||||
`${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Error in fix-dependencies command:', error);
|
||||
log("error", "Error in fix-dependencies command:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1113,7 +1103,7 @@ function ensureAtLeastOneIndependentSubtask(tasksData) {
|
||||
if (task.subtasks.length > 0) {
|
||||
const firstSubtask = task.subtasks[0];
|
||||
log(
|
||||
'debug',
|
||||
"debug",
|
||||
`Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}`
|
||||
);
|
||||
firstSubtask.dependencies = [];
|
||||
@@ -1134,11 +1124,11 @@ function ensureAtLeastOneIndependentSubtask(tasksData) {
|
||||
*/
|
||||
function validateAndFixDependencies(tasksData, tasksPath = null) {
|
||||
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
|
||||
log('error', 'Invalid tasks data');
|
||||
log("error", "Invalid tasks data");
|
||||
return false;
|
||||
}
|
||||
|
||||
log('debug', 'Validating and fixing dependencies...');
|
||||
log("debug", "Validating and fixing dependencies...");
|
||||
|
||||
// Create a deep copy for comparison
|
||||
const originalData = JSON.parse(JSON.stringify(tasksData));
|
||||
@@ -1184,7 +1174,7 @@ function validateAndFixDependencies(tasksData, tasksPath = null) {
|
||||
if (subtask.dependencies) {
|
||||
subtask.dependencies = subtask.dependencies.filter((depId) => {
|
||||
// Handle numeric subtask references
|
||||
if (typeof depId === 'number' && depId < 100) {
|
||||
if (typeof depId === "number" && depId < 100) {
|
||||
const fullSubtaskId = `${task.id}.${depId}`;
|
||||
return taskExists(tasksData.tasks, fullSubtaskId);
|
||||
}
|
||||
@@ -1220,9 +1210,9 @@ function validateAndFixDependencies(tasksData, tasksPath = null) {
|
||||
if (tasksPath && changesDetected) {
|
||||
try {
|
||||
writeJSON(tasksPath, tasksData);
|
||||
log('debug', 'Saved dependency fixes to tasks.json');
|
||||
log("debug", "Saved dependency fixes to tasks.json");
|
||||
} catch (error) {
|
||||
log('error', 'Failed to save dependency fixes to tasks.json', error);
|
||||
log("error", "Failed to save dependency fixes to tasks.json", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,5 +1229,5 @@ export {
|
||||
removeDuplicateDependencies,
|
||||
cleanupSubtaskDependencies,
|
||||
ensureAtLeastOneIndependentSubtask,
|
||||
validateAndFixDependencies
|
||||
validateAndFixDependencies,
|
||||
};
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import { z } from 'zod';
|
||||
import Fuse from 'fuse.js'; // Import Fuse.js for advanced fuzzy search
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
import Table from "cli-table3";
|
||||
import { z } from "zod";
|
||||
import Fuse from "fuse.js"; // Import Fuse.js for advanced fuzzy search
|
||||
|
||||
import {
|
||||
displayBanner,
|
||||
getStatusWithColor,
|
||||
startLoadingIndicator,
|
||||
stopLoadingIndicator,
|
||||
displayAiUsageSummary
|
||||
} from '../ui.js';
|
||||
import { readJSON, writeJSON, log as consoleLog, truncate } from '../utils.js';
|
||||
import { generateObjectService } from '../ai-services-unified.js';
|
||||
import { getDefaultPriority } from '../config-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
succeedLoadingIndicator,
|
||||
failLoadingIndicator,
|
||||
displayAiUsageSummary,
|
||||
} from "../ui.js";
|
||||
import { readJSON, writeJSON, log as consoleLog, truncate } from "../utils.js";
|
||||
import { generateObjectService } from "../ai-services-unified.js";
|
||||
import { getDefaultPriority } from "../config-manager.js";
|
||||
import generateTaskFiles from "./generate-task-files.js";
|
||||
|
||||
// Define Zod schema for the expected AI output object
|
||||
const AiTaskDataSchema = z.object({
|
||||
title: z.string().describe('Clear, concise title for the task'),
|
||||
title: z.string().describe("Clear, concise title for the task"),
|
||||
description: z
|
||||
.string()
|
||||
.describe('A one or two sentence description of the task'),
|
||||
.describe("A one or two sentence description of the task"),
|
||||
details: z
|
||||
.string()
|
||||
.describe('In-depth implementation details, considerations, and guidance'),
|
||||
.describe("In-depth implementation details, considerations, and guidance"),
|
||||
testStrategy: z
|
||||
.string()
|
||||
.describe('Detailed approach for verifying task completion'),
|
||||
.describe("Detailed approach for verifying task completion"),
|
||||
dependencies: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.describe(
|
||||
'Array of task IDs that this task depends on (must be completed before this task can start)'
|
||||
)
|
||||
"Array of task IDs that this task depends on (must be completed before this task can start)"
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -62,7 +64,7 @@ async function addTask(
|
||||
dependencies = [],
|
||||
priority = null,
|
||||
context = {},
|
||||
outputFormat = 'text', // Default to text for CLI
|
||||
outputFormat = "text", // Default to text for CLI
|
||||
manualTaskData = null,
|
||||
useResearch = false
|
||||
) {
|
||||
@@ -74,27 +76,27 @@ async function addTask(
|
||||
? mcpLog // Use MCP logger if provided
|
||||
: {
|
||||
// Create a wrapper around consoleLog for CLI
|
||||
info: (...args) => consoleLog('info', ...args),
|
||||
warn: (...args) => consoleLog('warn', ...args),
|
||||
error: (...args) => consoleLog('error', ...args),
|
||||
debug: (...args) => consoleLog('debug', ...args),
|
||||
success: (...args) => consoleLog('success', ...args)
|
||||
info: (...args) => consoleLog("info", ...args),
|
||||
warn: (...args) => consoleLog("warn", ...args),
|
||||
error: (...args) => consoleLog("error", ...args),
|
||||
debug: (...args) => consoleLog("debug", ...args),
|
||||
success: (...args) => consoleLog("success", ...args),
|
||||
};
|
||||
|
||||
const effectivePriority = priority || getDefaultPriority(projectRoot);
|
||||
|
||||
logFn.info(
|
||||
`Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}`
|
||||
`Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(", ") || "None"}, Research: ${useResearch}, ProjectRoot: ${projectRoot}`
|
||||
);
|
||||
|
||||
let loadingIndicator = null;
|
||||
let aiServiceResponse = null; // To store the full response from AI service
|
||||
|
||||
// Create custom reporter that checks for MCP log
|
||||
const report = (message, level = 'info') => {
|
||||
const report = (message, level = "info") => {
|
||||
if (mcpLog) {
|
||||
mcpLog[level](message);
|
||||
} else if (outputFormat === 'text') {
|
||||
} else if (outputFormat === "text") {
|
||||
consoleLog(level, message);
|
||||
}
|
||||
};
|
||||
@@ -156,7 +158,7 @@ async function addTask(
|
||||
title: task.title,
|
||||
description: task.description,
|
||||
status: task.status,
|
||||
dependencies: dependencyData
|
||||
dependencies: dependencyData,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -166,14 +168,14 @@ async function addTask(
|
||||
|
||||
// If tasks.json doesn't exist or is invalid, create a new one
|
||||
if (!data || !data.tasks) {
|
||||
report('tasks.json not found or invalid. Creating a new one.', 'info');
|
||||
report("tasks.json not found or invalid. Creating a new one.", "info");
|
||||
// Create default tasks data structure
|
||||
data = {
|
||||
tasks: []
|
||||
tasks: [],
|
||||
};
|
||||
// Ensure the directory exists and write the new file
|
||||
writeJSON(tasksPath, data);
|
||||
report('Created new tasks.json file with empty tasks array.', 'info');
|
||||
report("Created new tasks.json file with empty tasks array.", "info");
|
||||
}
|
||||
|
||||
// Find the highest task ID to determine the next ID
|
||||
@@ -182,13 +184,13 @@ async function addTask(
|
||||
const newTaskId = highestId + 1;
|
||||
|
||||
// Only show UI box for CLI mode
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -202,10 +204,10 @@ async function addTask(
|
||||
|
||||
if (invalidDeps.length > 0) {
|
||||
report(
|
||||
`The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`,
|
||||
'warn'
|
||||
`The following dependencies do not exist or are invalid: ${invalidDeps.join(", ")}`,
|
||||
"warn"
|
||||
);
|
||||
report('Removing invalid dependencies...', 'info');
|
||||
report("Removing invalid dependencies...", "info");
|
||||
dependencies = dependencies.filter(
|
||||
(depId) => !invalidDeps.includes(depId)
|
||||
);
|
||||
@@ -240,28 +242,28 @@ async function addTask(
|
||||
|
||||
// Check if manual task data is provided
|
||||
if (manualTaskData) {
|
||||
report('Using manually provided task data', 'info');
|
||||
report("Using manually provided task data", "info");
|
||||
taskData = manualTaskData;
|
||||
report('DEBUG: Taking MANUAL task data path.', 'debug');
|
||||
report("DEBUG: Taking MANUAL task data path.", "debug");
|
||||
|
||||
// Basic validation for manual data
|
||||
if (
|
||||
!taskData.title ||
|
||||
typeof taskData.title !== 'string' ||
|
||||
typeof taskData.title !== "string" ||
|
||||
!taskData.description ||
|
||||
typeof taskData.description !== 'string'
|
||||
typeof taskData.description !== "string"
|
||||
) {
|
||||
throw new Error(
|
||||
'Manual task data must include at least a title and description.'
|
||||
"Manual task data must include at least a title and description."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
report('DEBUG: Taking AI task generation path.', 'debug');
|
||||
report("DEBUG: Taking AI task generation path.", "debug");
|
||||
// --- Refactored AI Interaction ---
|
||||
report(`Generating task data with AI with prompt:\n${prompt}`, 'info');
|
||||
report(`Generating task data with AI with prompt:\n${prompt}`, "info");
|
||||
|
||||
// Create context string for task creation prompt
|
||||
let contextTasks = '';
|
||||
let contextTasks = "";
|
||||
|
||||
// Create a dependency map for better understanding of the task relationships
|
||||
const taskMap = {};
|
||||
@@ -272,18 +274,18 @@ async function addTask(
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
dependencies: t.dependencies || [],
|
||||
status: t.status
|
||||
status: t.status,
|
||||
};
|
||||
});
|
||||
|
||||
// CLI-only feedback for the dependency analysis
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
console.log(
|
||||
boxen(chalk.cyan.bold('Task Context Analysis') + '\n', {
|
||||
boxen(chalk.cyan.bold("Task Context Analysis"), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
margin: { top: 0, bottom: 0 },
|
||||
borderColor: 'cyan',
|
||||
borderStyle: 'round'
|
||||
borderColor: "cyan",
|
||||
borderStyle: "round",
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -314,7 +316,7 @@ async function addTask(
|
||||
const directDeps = data.tasks.filter((t) =>
|
||||
numericDependencies.includes(t.id)
|
||||
);
|
||||
contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`;
|
||||
contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join("\n")}`;
|
||||
|
||||
// Add an overview of indirect dependencies if present
|
||||
const indirectDeps = dependentTasks.filter(
|
||||
@@ -325,7 +327,7 @@ async function addTask(
|
||||
contextTasks += `\n${indirectDeps
|
||||
.slice(0, 5)
|
||||
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
|
||||
.join('\n')}`;
|
||||
.join("\n")}`;
|
||||
if (indirectDeps.length > 5) {
|
||||
contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`;
|
||||
}
|
||||
@@ -336,15 +338,15 @@ async function addTask(
|
||||
for (const depTask of uniqueDetailedTasks) {
|
||||
const depthInfo = depthMap.get(depTask.id)
|
||||
? ` (depth: ${depthMap.get(depTask.id)})`
|
||||
: '';
|
||||
: "";
|
||||
const isDirect = numericDependencies.includes(depTask.id)
|
||||
? ' [DIRECT DEPENDENCY]'
|
||||
: '';
|
||||
? " [DIRECT DEPENDENCY]"
|
||||
: "";
|
||||
|
||||
contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`;
|
||||
contextTasks += `Description: ${depTask.description}\n`;
|
||||
contextTasks += `Status: ${depTask.status || 'pending'}\n`;
|
||||
contextTasks += `Priority: ${depTask.priority || 'medium'}\n`;
|
||||
contextTasks += `Status: ${depTask.status || "pending"}\n`;
|
||||
contextTasks += `Priority: ${depTask.priority || "medium"}\n`;
|
||||
|
||||
// List its dependencies
|
||||
if (depTask.dependencies && depTask.dependencies.length > 0) {
|
||||
@@ -354,7 +356,7 @@ async function addTask(
|
||||
? `Task ${dId}: ${depDepTask.title}`
|
||||
: `Task ${dId}`;
|
||||
});
|
||||
contextTasks += `Dependencies: ${depDeps.join(', ')}\n`;
|
||||
contextTasks += `Dependencies: ${depDeps.join(", ")}\n`;
|
||||
} else {
|
||||
contextTasks += `Dependencies: None\n`;
|
||||
}
|
||||
@@ -363,7 +365,7 @@ async function addTask(
|
||||
if (depTask.details) {
|
||||
const truncatedDetails =
|
||||
depTask.details.length > 400
|
||||
? depTask.details.substring(0, 400) + '... (truncated)'
|
||||
? depTask.details.substring(0, 400) + "... (truncated)"
|
||||
: depTask.details;
|
||||
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
|
||||
}
|
||||
@@ -371,19 +373,19 @@ async function addTask(
|
||||
|
||||
// Add dependency chain visualization
|
||||
if (dependencyGraphs.length > 0) {
|
||||
contextTasks += '\n\nDependency Chain Visualization:';
|
||||
contextTasks += "\n\nDependency Chain Visualization:";
|
||||
|
||||
// Helper function to format dependency chain as text
|
||||
function formatDependencyChain(
|
||||
node,
|
||||
prefix = '',
|
||||
prefix = "",
|
||||
isLast = true,
|
||||
depth = 0
|
||||
) {
|
||||
if (depth > 3) return ''; // Limit depth to avoid excessive nesting
|
||||
if (depth > 3) return ""; // Limit depth to avoid excessive nesting
|
||||
|
||||
const connector = isLast ? '└── ' : '├── ';
|
||||
const childPrefix = isLast ? ' ' : '│ ';
|
||||
const connector = isLast ? "└── " : "├── ";
|
||||
const childPrefix = isLast ? " " : "│ ";
|
||||
|
||||
let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`;
|
||||
|
||||
@@ -409,7 +411,7 @@ async function addTask(
|
||||
}
|
||||
|
||||
// Show dependency analysis in CLI mode
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
if (directDeps.length > 0) {
|
||||
console.log(chalk.gray(` Explicitly specified dependencies:`));
|
||||
directDeps.forEach((t) => {
|
||||
@@ -449,14 +451,14 @@ async function addTask(
|
||||
// Convert dependency graph to ASCII art for terminal
|
||||
function visualizeDependencyGraph(
|
||||
node,
|
||||
prefix = '',
|
||||
prefix = "",
|
||||
isLast = true,
|
||||
depth = 0
|
||||
) {
|
||||
if (depth > 2) return; // Limit depth for display
|
||||
|
||||
const connector = isLast ? '└── ' : '├── ';
|
||||
const childPrefix = isLast ? ' ' : '│ ';
|
||||
const connector = isLast ? "└── " : "├── ";
|
||||
const childPrefix = isLast ? " " : "│ ";
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
@@ -492,18 +494,18 @@ async function addTask(
|
||||
includeScore: true, // Return match scores
|
||||
threshold: 0.4, // Lower threshold = stricter matching (range 0-1)
|
||||
keys: [
|
||||
{ name: 'title', weight: 2 }, // Title is most important
|
||||
{ name: 'description', weight: 1.5 }, // Description is next
|
||||
{ name: 'details', weight: 0.8 }, // Details is less important
|
||||
{ name: "title", weight: 1.5 }, // Title is most important
|
||||
{ name: "description", weight: 2 }, // Description is very important
|
||||
{ name: "details", weight: 3 }, // Details is most important
|
||||
// Search dependencies to find tasks that depend on similar things
|
||||
{ name: 'dependencyTitles', weight: 0.5 }
|
||||
{ name: "dependencyTitles", weight: 0.5 },
|
||||
],
|
||||
// Sort matches by score (lower is better)
|
||||
shouldSort: true,
|
||||
// Allow searching in nested properties
|
||||
useExtendedSearch: true,
|
||||
// Return up to 15 matches
|
||||
limit: 15
|
||||
// Return up to 50 matches
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
// Prepare task data with dependencies expanded as titles for better semantic search
|
||||
@@ -514,15 +516,15 @@ async function addTask(
|
||||
? task.dependencies
|
||||
.map((depId) => {
|
||||
const depTask = data.tasks.find((t) => t.id === depId);
|
||||
return depTask ? depTask.title : '';
|
||||
return depTask ? depTask.title : "";
|
||||
})
|
||||
.filter((title) => title)
|
||||
.join(' ')
|
||||
: '';
|
||||
.join(" ")
|
||||
: "";
|
||||
|
||||
return {
|
||||
...task,
|
||||
dependencyTitles
|
||||
dependencyTitles,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -532,7 +534,7 @@ async function addTask(
|
||||
// Extract significant words and phrases from the prompt
|
||||
const promptWords = prompt
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces
|
||||
.replace(/[^\w\s-]/g, " ") // Replace non-alphanumeric chars with spaces
|
||||
.split(/\s+/)
|
||||
.filter((word) => word.length > 3); // Words at least 4 chars
|
||||
|
||||
@@ -596,75 +598,40 @@ async function addTask(
|
||||
// Get top N results for context
|
||||
const relatedTasks = allRelevantTasks.slice(0, 8);
|
||||
|
||||
// Also look for tasks with similar purposes or categories
|
||||
const purposeCategories = [
|
||||
{ pattern: /(command|cli|flag)/i, label: 'CLI commands' },
|
||||
{ pattern: /(task|subtask|add)/i, label: 'Task management' },
|
||||
{ pattern: /(dependency|depend)/i, label: 'Dependency handling' },
|
||||
{ pattern: /(AI|model|prompt)/i, label: 'AI integration' },
|
||||
{ pattern: /(UI|display|show)/i, label: 'User interface' },
|
||||
{ pattern: /(schedule|time|cron)/i, label: 'Scheduling' }, // Added scheduling category
|
||||
{ pattern: /(config|setting|option)/i, label: 'Configuration' } // Added configuration category
|
||||
];
|
||||
|
||||
promptCategory = purposeCategories.find((cat) =>
|
||||
cat.pattern.test(prompt)
|
||||
);
|
||||
const categoryTasks = promptCategory
|
||||
? data.tasks
|
||||
.filter(
|
||||
(t) =>
|
||||
promptCategory.pattern.test(t.title) ||
|
||||
promptCategory.pattern.test(t.description) ||
|
||||
(t.details && promptCategory.pattern.test(t.details))
|
||||
)
|
||||
.filter((t) => !relatedTasks.some((rt) => rt.id === t.id))
|
||||
.slice(0, 3)
|
||||
: [];
|
||||
|
||||
// Format basic task overviews
|
||||
if (relatedTasks.length > 0) {
|
||||
contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks
|
||||
.map((t, i) => {
|
||||
const relevanceMarker = i < highRelevance.length ? '⭐ ' : '';
|
||||
const relevanceMarker = i < highRelevance.length ? "⭐ " : "";
|
||||
return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`;
|
||||
})
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
if (categoryTasks.length > 0) {
|
||||
contextTasks += `\n\nTasks related to ${promptCategory.label}:\n${categoryTasks
|
||||
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
|
||||
.join('\n')}`;
|
||||
.join("\n")}`;
|
||||
}
|
||||
|
||||
if (
|
||||
recentTasks.length > 0 &&
|
||||
!contextTasks.includes('Recently created tasks')
|
||||
!contextTasks.includes("Recently created tasks")
|
||||
) {
|
||||
contextTasks += `\n\nRecently created tasks:\n${recentTasks
|
||||
.filter((t) => !relatedTasks.some((rt) => rt.id === t.id))
|
||||
.slice(0, 3)
|
||||
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
|
||||
.join('\n')}`;
|
||||
.join("\n")}`;
|
||||
}
|
||||
|
||||
// Add detailed information about the most relevant tasks
|
||||
const allDetailedTasks = [
|
||||
...relatedTasks.slice(0, 5),
|
||||
...categoryTasks.slice(0, 2)
|
||||
];
|
||||
const allDetailedTasks = [...relatedTasks.slice(0, 25)];
|
||||
uniqueDetailedTasks = Array.from(
|
||||
new Map(allDetailedTasks.map((t) => [t.id, t])).values()
|
||||
).slice(0, 8);
|
||||
).slice(0, 20);
|
||||
|
||||
if (uniqueDetailedTasks.length > 0) {
|
||||
contextTasks += `\n\nDetailed information about relevant tasks:`;
|
||||
for (const task of uniqueDetailedTasks) {
|
||||
contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`;
|
||||
contextTasks += `Description: ${task.description}\n`;
|
||||
contextTasks += `Status: ${task.status || 'pending'}\n`;
|
||||
contextTasks += `Priority: ${task.priority || 'medium'}\n`;
|
||||
contextTasks += `Status: ${task.status || "pending"}\n`;
|
||||
contextTasks += `Priority: ${task.priority || "medium"}\n`;
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
// Format dependency list with titles
|
||||
const depList = task.dependencies.map((depId) => {
|
||||
@@ -673,13 +640,13 @@ async function addTask(
|
||||
? `Task ${depId} (${depTask.title})`
|
||||
: `Task ${depId}`;
|
||||
});
|
||||
contextTasks += `Dependencies: ${depList.join(', ')}\n`;
|
||||
contextTasks += `Dependencies: ${depList.join(", ")}\n`;
|
||||
}
|
||||
// Add implementation details but truncate if too long
|
||||
if (task.details) {
|
||||
const truncatedDetails =
|
||||
task.details.length > 400
|
||||
? task.details.substring(0, 400) + '... (truncated)'
|
||||
? task.details.substring(0, 400) + "... (truncated)"
|
||||
: task.details;
|
||||
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
|
||||
}
|
||||
@@ -687,7 +654,7 @@ async function addTask(
|
||||
}
|
||||
|
||||
// Add a concise view of the task dependency structure
|
||||
contextTasks += '\n\nSummary of task dependencies in the project:';
|
||||
contextTasks += "\n\nSummary of task dependencies in the project:";
|
||||
|
||||
// Get pending/in-progress tasks that might be most relevant based on fuzzy search
|
||||
// Prioritize tasks from our similarity search
|
||||
@@ -695,7 +662,7 @@ async function addTask(
|
||||
const relevantPendingTasks = data.tasks
|
||||
.filter(
|
||||
(t) =>
|
||||
(t.status === 'pending' || t.status === 'in-progress') &&
|
||||
(t.status === "pending" || t.status === "in-progress") &&
|
||||
// Either in our relevant set OR has relevant words in title/description
|
||||
(relevantTaskIds.has(t.id) ||
|
||||
promptWords.some(
|
||||
@@ -709,24 +676,20 @@ async function addTask(
|
||||
for (const task of relevantPendingTasks) {
|
||||
const depsStr =
|
||||
task.dependencies && task.dependencies.length > 0
|
||||
? task.dependencies.join(', ')
|
||||
: 'None';
|
||||
? task.dependencies.join(", ")
|
||||
: "None";
|
||||
contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`;
|
||||
}
|
||||
|
||||
// Additional analysis of common patterns
|
||||
const similarPurposeTasks = promptCategory
|
||||
? data.tasks.filter(
|
||||
(t) =>
|
||||
promptCategory.pattern.test(t.title) ||
|
||||
promptCategory.pattern.test(t.description)
|
||||
)
|
||||
: [];
|
||||
const similarPurposeTasks = data.tasks.filter((t) =>
|
||||
prompt.toLowerCase().includes(t.title.toLowerCase())
|
||||
);
|
||||
|
||||
let commonDeps = []; // Initialize commonDeps
|
||||
|
||||
if (similarPurposeTasks.length > 0) {
|
||||
contextTasks += `\n\nCommon patterns for ${promptCategory ? promptCategory.label : 'similar'} tasks:`;
|
||||
contextTasks += `\n\nCommon patterns for similar tasks:`;
|
||||
|
||||
// Collect dependencies from similar purpose tasks
|
||||
const similarDeps = similarPurposeTasks
|
||||
@@ -743,10 +706,10 @@ async function addTask(
|
||||
// Get most common dependencies for similar tasks
|
||||
commonDeps = Object.entries(depCounts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5);
|
||||
.slice(0, 10);
|
||||
|
||||
if (commonDeps.length > 0) {
|
||||
contextTasks += '\nMost common dependencies for similar tasks:';
|
||||
contextTasks += "\nMost common dependencies for similar tasks:";
|
||||
commonDeps.forEach(([depId, count]) => {
|
||||
const depTask = data.tasks.find((t) => t.id === parseInt(depId));
|
||||
if (depTask) {
|
||||
@@ -757,10 +720,10 @@ async function addTask(
|
||||
}
|
||||
|
||||
// Show fuzzy search analysis in CLI mode
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
` Fuzzy search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords`
|
||||
` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -768,7 +731,7 @@ async function addTask(
|
||||
console.log(
|
||||
chalk.gray(`\n High relevance matches (score < 0.25):`)
|
||||
);
|
||||
highRelevance.slice(0, 5).forEach((t) => {
|
||||
highRelevance.slice(0, 25).forEach((t) => {
|
||||
console.log(
|
||||
chalk.yellow(` • ⭐ Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
@@ -779,24 +742,13 @@ async function addTask(
|
||||
console.log(
|
||||
chalk.gray(`\n Medium relevance matches (score < 0.4):`)
|
||||
);
|
||||
mediumRelevance.slice(0, 3).forEach((t) => {
|
||||
mediumRelevance.slice(0, 10).forEach((t) => {
|
||||
console.log(
|
||||
chalk.green(` • Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (promptCategory && categoryTasks.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(`\n Tasks related to ${promptCategory.label}:`)
|
||||
);
|
||||
categoryTasks.forEach((t) => {
|
||||
console.log(
|
||||
chalk.magenta(` • Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Show dependency patterns
|
||||
if (commonDeps && commonDeps.length > 0) {
|
||||
console.log(
|
||||
@@ -825,7 +777,7 @@ async function addTask(
|
||||
const isHighRelevance = highRelevance.some(
|
||||
(ht) => ht.id === t.id
|
||||
);
|
||||
const relevanceIndicator = isHighRelevance ? '⭐ ' : '';
|
||||
const relevanceIndicator = isHighRelevance ? "⭐ " : "";
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
` • ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}`
|
||||
@@ -853,26 +805,23 @@ async function addTask(
|
||||
}
|
||||
|
||||
// Add a visual transition to show we're moving to AI generation - only for CLI
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('AI Task Generation') +
|
||||
`\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` +
|
||||
`\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` +
|
||||
`\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` +
|
||||
`\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow(
|
||||
chalk.white.bold("AI Task Generation") +
|
||||
`\n\n${chalk.gray("Analyzing context and generating task details using AI...")}` +
|
||||
`\n${chalk.cyan("Context size: ")}${chalk.yellow(contextTasks.length.toLocaleString())} characters` +
|
||||
`\n${chalk.cyan("Dependency detection: ")}${chalk.yellow(numericDependencies.length > 0 ? "Explicit dependencies" : "Auto-discovery mode")}` +
|
||||
`\n${chalk.cyan("Detailed tasks: ")}${chalk.yellow(
|
||||
numericDependencies.length > 0
|
||||
? dependentTasks.length // Use length of tasks from explicit dependency path
|
||||
: uniqueDetailedTasks.length // Use length of tasks from fuzzy search path
|
||||
)}` +
|
||||
(promptCategory
|
||||
? `\n${chalk.cyan('Category detected: ')}${chalk.yellow(promptCategory.label)}`
|
||||
: ''),
|
||||
)}`,
|
||||
{
|
||||
padding: { top: 0, bottom: 1, left: 1, right: 1 },
|
||||
margin: { top: 1, bottom: 0 },
|
||||
borderColor: 'white',
|
||||
borderStyle: 'round'
|
||||
borderColor: "white",
|
||||
borderStyle: "round",
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -882,15 +831,15 @@ async function addTask(
|
||||
// System Prompt - Enhanced for dependency awareness
|
||||
const systemPrompt =
|
||||
"You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" +
|
||||
'When determining dependencies for a new task, follow these principles:\n' +
|
||||
'1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' +
|
||||
'2. Prioritize task dependencies that are semantically related to the functionality being built.\n' +
|
||||
'3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n' +
|
||||
'4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n' +
|
||||
'5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n' +
|
||||
"When determining dependencies for a new task, follow these principles:\n" +
|
||||
"1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n" +
|
||||
"2. Prioritize task dependencies that are semantically related to the functionality being built.\n" +
|
||||
"3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n" +
|
||||
"4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n" +
|
||||
"5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n" +
|
||||
"6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" +
|
||||
'7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n' +
|
||||
'The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n';
|
||||
"7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n" +
|
||||
"The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n";
|
||||
|
||||
// Task Structure Description (for user prompt)
|
||||
const taskStructureDesc = `
|
||||
@@ -904,7 +853,7 @@ async function addTask(
|
||||
`;
|
||||
|
||||
// Add any manually provided details to the prompt for context
|
||||
let contextFromArgs = '';
|
||||
let contextFromArgs = "";
|
||||
if (manualTaskData?.title)
|
||||
contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`;
|
||||
if (manualTaskData?.description)
|
||||
@@ -918,7 +867,7 @@ async function addTask(
|
||||
const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project.
|
||||
|
||||
${contextTasks}
|
||||
${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''}
|
||||
${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ""}
|
||||
|
||||
Based on the information about existing tasks provided above, include appropriate dependencies in the "dependencies" array. Only include task IDs that this new task directly depends on.
|
||||
|
||||
@@ -929,15 +878,15 @@ async function addTask(
|
||||
`;
|
||||
|
||||
// Start the loading indicator - only for text mode
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
loadingIndicator = startLoadingIndicator(
|
||||
`Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n`
|
||||
`Generating new task with ${useResearch ? "Research" : "Main"} AI... \n`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const serviceRole = useResearch ? 'research' : 'main';
|
||||
report('DEBUG: Calling generateObjectService...', 'debug');
|
||||
const serviceRole = useResearch ? "research" : "main";
|
||||
report("DEBUG: Calling generateObjectService...", "debug");
|
||||
|
||||
aiServiceResponse = await generateObjectService({
|
||||
// Capture the full response
|
||||
@@ -945,17 +894,17 @@ async function addTask(
|
||||
session: session,
|
||||
projectRoot: projectRoot,
|
||||
schema: AiTaskDataSchema,
|
||||
objectName: 'newTaskData',
|
||||
objectName: "newTaskData",
|
||||
systemPrompt: systemPrompt,
|
||||
prompt: userPrompt,
|
||||
commandName: commandName || 'add-task', // Use passed commandName or default
|
||||
outputType: outputType || (isMCP ? 'mcp' : 'cli') // Use passed outputType or derive
|
||||
commandName: commandName || "add-task", // Use passed commandName or default
|
||||
outputType: outputType || (isMCP ? "mcp" : "cli"), // Use passed outputType or derive
|
||||
});
|
||||
report('DEBUG: generateObjectService returned successfully.', 'debug');
|
||||
report("DEBUG: generateObjectService returned successfully.", "debug");
|
||||
|
||||
if (!aiServiceResponse || !aiServiceResponse.mainResult) {
|
||||
throw new Error(
|
||||
'AI service did not return the expected object structure.'
|
||||
"AI service did not return the expected object structure."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -972,21 +921,37 @@ async function addTask(
|
||||
) {
|
||||
taskData = aiServiceResponse.mainResult.object;
|
||||
} else {
|
||||
throw new Error('AI service did not return a valid task object.');
|
||||
throw new Error("AI service did not return a valid task object.");
|
||||
}
|
||||
|
||||
report('Successfully generated task data from AI.', 'success');
|
||||
report("Successfully generated task data from AI.", "success");
|
||||
|
||||
// Success! Show checkmark
|
||||
if (loadingIndicator) {
|
||||
succeedLoadingIndicator(
|
||||
loadingIndicator,
|
||||
"Task generated successfully"
|
||||
);
|
||||
loadingIndicator = null; // Clear it
|
||||
}
|
||||
} catch (error) {
|
||||
// Failure! Show X
|
||||
if (loadingIndicator) {
|
||||
failLoadingIndicator(loadingIndicator, "AI generation failed");
|
||||
loadingIndicator = null;
|
||||
}
|
||||
report(
|
||||
`DEBUG: generateObjectService caught error: ${error.message}`,
|
||||
'debug'
|
||||
"debug"
|
||||
);
|
||||
report(`Error generating task with AI: ${error.message}`, 'error');
|
||||
if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
|
||||
report(`Error generating task with AI: ${error.message}`, "error");
|
||||
throw error; // Re-throw error after logging
|
||||
} finally {
|
||||
report('DEBUG: generateObjectService finally block reached.', 'debug');
|
||||
if (loadingIndicator) stopLoadingIndicator(loadingIndicator); // Ensure indicator stops
|
||||
report("DEBUG: generateObjectService finally block reached.", "debug");
|
||||
// Clean up if somehow still running
|
||||
if (loadingIndicator) {
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
}
|
||||
}
|
||||
// --- End Refactored AI Interaction ---
|
||||
}
|
||||
@@ -996,14 +961,14 @@ async function addTask(
|
||||
id: newTaskId,
|
||||
title: taskData.title,
|
||||
description: taskData.description,
|
||||
details: taskData.details || '',
|
||||
testStrategy: taskData.testStrategy || '',
|
||||
status: 'pending',
|
||||
details: taskData.details || "",
|
||||
testStrategy: taskData.testStrategy || "",
|
||||
status: "pending",
|
||||
dependencies: taskData.dependencies?.length
|
||||
? taskData.dependencies
|
||||
: numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified
|
||||
priority: effectivePriority,
|
||||
subtasks: [] // Initialize with empty subtasks array
|
||||
subtasks: [], // Initialize with empty subtasks array
|
||||
};
|
||||
|
||||
// Additional check: validate all dependencies in the AI response
|
||||
@@ -1015,8 +980,8 @@ async function addTask(
|
||||
|
||||
if (!allValidDeps) {
|
||||
report(
|
||||
'AI suggested invalid dependencies. Filtering them out...',
|
||||
'warn'
|
||||
"AI suggested invalid dependencies. Filtering them out...",
|
||||
"warn"
|
||||
);
|
||||
newTask.dependencies = taskData.dependencies.filter((depId) => {
|
||||
const numDepId = parseInt(depId, 10);
|
||||
@@ -1028,48 +993,48 @@ async function addTask(
|
||||
// Add the task to the tasks array
|
||||
data.tasks.push(newTask);
|
||||
|
||||
report('DEBUG: Writing tasks.json...', 'debug');
|
||||
report("DEBUG: Writing tasks.json...", "debug");
|
||||
// Write the updated tasks to the file
|
||||
writeJSON(tasksPath, data);
|
||||
report('DEBUG: tasks.json written.', 'debug');
|
||||
report("DEBUG: tasks.json written.", "debug");
|
||||
|
||||
// Generate markdown task files
|
||||
report('Generating task files...', 'info');
|
||||
report('DEBUG: Calling generateTaskFiles...', 'debug');
|
||||
report("Generating task files...", "info");
|
||||
report("DEBUG: Calling generateTaskFiles...", "debug");
|
||||
// Pass mcpLog if available to generateTaskFiles
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog });
|
||||
report('DEBUG: generateTaskFiles finished.', 'debug');
|
||||
report("DEBUG: generateTaskFiles finished.", "debug");
|
||||
|
||||
// Show success message - only for text output (CLI)
|
||||
if (outputFormat === 'text') {
|
||||
if (outputFormat === "text") {
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.cyan.bold('ID'),
|
||||
chalk.cyan.bold('Title'),
|
||||
chalk.cyan.bold('Description')
|
||||
chalk.cyan.bold("ID"),
|
||||
chalk.cyan.bold("Title"),
|
||||
chalk.cyan.bold("Description"),
|
||||
],
|
||||
colWidths: [5, 30, 50] // Adjust widths as needed
|
||||
colWidths: [5, 30, 50], // Adjust widths as needed
|
||||
});
|
||||
|
||||
table.push([
|
||||
newTask.id,
|
||||
truncate(newTask.title, 27),
|
||||
truncate(newTask.description, 47)
|
||||
truncate(newTask.description, 47),
|
||||
]);
|
||||
|
||||
console.log(chalk.green('✅ New task created successfully:'));
|
||||
console.log(chalk.green("✓ New task created successfully:"));
|
||||
console.log(table.toString());
|
||||
|
||||
// Helper to get priority color
|
||||
const getPriorityColor = (p) => {
|
||||
switch (p?.toLowerCase()) {
|
||||
case 'high':
|
||||
return 'red';
|
||||
case 'low':
|
||||
return 'gray';
|
||||
case 'medium':
|
||||
case "high":
|
||||
return "red";
|
||||
case "low":
|
||||
return "gray";
|
||||
case "medium":
|
||||
default:
|
||||
return 'yellow';
|
||||
return "yellow";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1093,49 +1058,49 @@ async function addTask(
|
||||
});
|
||||
|
||||
// Prepare dependency display string
|
||||
let dependencyDisplay = '';
|
||||
let dependencyDisplay = "";
|
||||
if (newTask.dependencies.length > 0) {
|
||||
dependencyDisplay = chalk.white('Dependencies:') + '\n';
|
||||
dependencyDisplay = chalk.white("Dependencies:") + "\n";
|
||||
newTask.dependencies.forEach((dep) => {
|
||||
const isAiAdded = aiAddedDeps.includes(dep);
|
||||
const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : '';
|
||||
const depType = isAiAdded ? chalk.yellow(" (AI suggested)") : "";
|
||||
dependencyDisplay +=
|
||||
chalk.white(
|
||||
` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}`
|
||||
) + '\n';
|
||||
` - ${dep}: ${depTitles[dep] || "Unknown task"}${depType}`
|
||||
) + "\n";
|
||||
});
|
||||
} else {
|
||||
dependencyDisplay = chalk.white('Dependencies: None') + '\n';
|
||||
dependencyDisplay = chalk.white("Dependencies: None") + "\n";
|
||||
}
|
||||
|
||||
// Add info about removed dependencies if any
|
||||
if (aiRemovedDeps.length > 0) {
|
||||
dependencyDisplay +=
|
||||
chalk.gray('\nUser-specified dependencies that were not used:') +
|
||||
'\n';
|
||||
chalk.gray("\nUser-specified dependencies that were not used:") +
|
||||
"\n";
|
||||
aiRemovedDeps.forEach((dep) => {
|
||||
const depTask = data.tasks.find((t) => t.id === dep);
|
||||
const title = depTask ? truncate(depTask.title, 30) : 'Unknown task';
|
||||
dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n';
|
||||
const title = depTask ? truncate(depTask.title, 30) : "Unknown task";
|
||||
dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + "\n";
|
||||
});
|
||||
}
|
||||
|
||||
// Add dependency analysis summary
|
||||
let dependencyAnalysis = '';
|
||||
let dependencyAnalysis = "";
|
||||
if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) {
|
||||
dependencyAnalysis =
|
||||
'\n' + chalk.white.bold('Dependency Analysis:') + '\n';
|
||||
"\n" + chalk.white.bold("Dependency Analysis:") + "\n";
|
||||
if (aiAddedDeps.length > 0) {
|
||||
dependencyAnalysis +=
|
||||
chalk.green(
|
||||
`AI identified ${aiAddedDeps.length} additional dependencies`
|
||||
) + '\n';
|
||||
) + "\n";
|
||||
}
|
||||
if (aiRemovedDeps.length > 0) {
|
||||
dependencyAnalysis +=
|
||||
chalk.yellow(
|
||||
`AI excluded ${aiRemovedDeps.length} user-provided dependencies`
|
||||
) + '\n';
|
||||
) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,32 +1108,32 @@ async function addTask(
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold(`Task ${newTaskId} Created Successfully`) +
|
||||
'\n\n' +
|
||||
"\n\n" +
|
||||
chalk.white(`Title: ${newTask.title}`) +
|
||||
'\n' +
|
||||
"\n" +
|
||||
chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) +
|
||||
'\n' +
|
||||
"\n" +
|
||||
chalk.white(
|
||||
`Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}`
|
||||
) +
|
||||
'\n\n' +
|
||||
"\n\n" +
|
||||
dependencyDisplay +
|
||||
dependencyAnalysis +
|
||||
'\n' +
|
||||
chalk.white.bold('Next Steps:') +
|
||||
'\n' +
|
||||
"\n" +
|
||||
chalk.white.bold("Next Steps:") +
|
||||
"\n" +
|
||||
chalk.cyan(
|
||||
`1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details`
|
||||
) +
|
||||
'\n' +
|
||||
"\n" +
|
||||
chalk.cyan(
|
||||
`2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it`
|
||||
) +
|
||||
'\n' +
|
||||
"\n" +
|
||||
chalk.cyan(
|
||||
`3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks`
|
||||
),
|
||||
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||
{ padding: 1, borderColor: "green", borderStyle: "round" }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1176,19 +1141,19 @@ async function addTask(
|
||||
if (
|
||||
aiServiceResponse &&
|
||||
aiServiceResponse.telemetryData &&
|
||||
(outputType === 'cli' || outputType === 'text')
|
||||
(outputType === "cli" || outputType === "text")
|
||||
) {
|
||||
displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
|
||||
displayAiUsageSummary(aiServiceResponse.telemetryData, "cli");
|
||||
}
|
||||
}
|
||||
|
||||
report(
|
||||
`DEBUG: Returning new task ID: ${newTaskId} and telemetry.`,
|
||||
'debug'
|
||||
"debug"
|
||||
);
|
||||
return {
|
||||
newTaskId: newTaskId,
|
||||
telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null
|
||||
telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null,
|
||||
};
|
||||
} catch (error) {
|
||||
// Stop any loading indicator on error
|
||||
@@ -1196,8 +1161,8 @@ async function addTask(
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
}
|
||||
|
||||
report(`Error adding task: ${error.message}`, 'error');
|
||||
if (outputFormat === 'text') {
|
||||
report(`Error adding task: ${error.message}`, "error");
|
||||
if (outputFormat === "text") {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
}
|
||||
// In MCP mode, we let the direct function handler catch and format
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
import Table from "cli-table3";
|
||||
|
||||
import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { log, readJSON, writeJSON, truncate, isSilentMode } from "../utils.js";
|
||||
import { displayBanner } from "../ui.js";
|
||||
import generateTaskFiles from "./generate-task-files.js";
|
||||
|
||||
/**
|
||||
* Clear subtasks from specified tasks
|
||||
@@ -13,60 +13,58 @@ import generateTaskFiles from './generate-task-files.js';
|
||||
* @param {string} taskIds - Task IDs to clear subtasks from
|
||||
*/
|
||||
function clearSubtasks(tasksPath, taskIds) {
|
||||
displayBanner();
|
||||
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
log("info", `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found.');
|
||||
log("error", "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Clearing Subtasks'), {
|
||||
boxen(chalk.white.bold("Clearing Subtasks"), {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIdArray = taskIds.split(',').map((id) => id.trim());
|
||||
const taskIdArray = taskIds.split(",").map((id) => id.trim());
|
||||
let clearedCount = 0;
|
||||
|
||||
// Create a summary table for the cleared subtasks
|
||||
const summaryTable = new Table({
|
||||
head: [
|
||||
chalk.cyan.bold('Task ID'),
|
||||
chalk.cyan.bold('Task Title'),
|
||||
chalk.cyan.bold('Subtasks Cleared')
|
||||
chalk.cyan.bold("Task ID"),
|
||||
chalk.cyan.bold("Task Title"),
|
||||
chalk.cyan.bold("Subtasks Cleared"),
|
||||
],
|
||||
colWidths: [10, 50, 20],
|
||||
style: { head: [], border: [] }
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
taskIdArray.forEach((taskId) => {
|
||||
const id = parseInt(taskId, 10);
|
||||
if (isNaN(id)) {
|
||||
log('error', `Invalid task ID: ${taskId}`);
|
||||
log("error", `Invalid task ID: ${taskId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const task = data.tasks.find((t) => t.id === id);
|
||||
if (!task) {
|
||||
log('error', `Task ${id} not found`);
|
||||
log("error", `Task ${id} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!task.subtasks || task.subtasks.length === 0) {
|
||||
log('info', `Task ${id} has no subtasks to clear`);
|
||||
log("info", `Task ${id} has no subtasks to clear`);
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.yellow('No subtasks')
|
||||
chalk.yellow("No subtasks"),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
@@ -74,12 +72,12 @@ function clearSubtasks(tasksPath, taskIds) {
|
||||
const subtaskCount = task.subtasks.length;
|
||||
task.subtasks = [];
|
||||
clearedCount++;
|
||||
log('info', `Cleared ${subtaskCount} subtasks from task ${id}`);
|
||||
log("info", `Cleared ${subtaskCount} subtasks from task ${id}`);
|
||||
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.green(`${subtaskCount} subtasks cleared`)
|
||||
chalk.green(`${subtaskCount} subtasks cleared`),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -89,18 +87,18 @@ function clearSubtasks(tasksPath, taskIds) {
|
||||
// Show summary table
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Subtask Clearing Summary:'), {
|
||||
boxen(chalk.white.bold("Subtask Clearing Summary:"), {
|
||||
padding: { left: 2, right: 2, top: 0, bottom: 0 },
|
||||
margin: { top: 1, bottom: 0 },
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round'
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
})
|
||||
);
|
||||
console.log(summaryTable.toString());
|
||||
}
|
||||
|
||||
// Regenerate task files to reflect changes
|
||||
log('info', 'Regenerating task files...');
|
||||
log("info", "Regenerating task files...");
|
||||
generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
|
||||
// Success message
|
||||
@@ -112,9 +110,9 @@ function clearSubtasks(tasksPath, taskIds) {
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -122,15 +120,15 @@ function clearSubtasks(tasksPath, taskIds) {
|
||||
// Next steps suggestion
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Next Steps:') +
|
||||
'\n\n' +
|
||||
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` +
|
||||
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`,
|
||||
chalk.white.bold("Next Steps:") +
|
||||
"\n\n" +
|
||||
`${chalk.cyan("1.")} Run ${chalk.yellow("task-master expand --id=<id>")} to generate new subtasks\n` +
|
||||
`${chalk.cyan("2.")} Run ${chalk.yellow("task-master list --with-subtasks")} to verify changes`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'cyan',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "cyan",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -138,11 +136,11 @@ function clearSubtasks(tasksPath, taskIds) {
|
||||
} else {
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.yellow('No subtasks were cleared'), {
|
||||
boxen(chalk.yellow("No subtasks were cleared"), {
|
||||
padding: 1,
|
||||
borderColor: 'yellow',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "yellow",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
import Table from "cli-table3";
|
||||
|
||||
import {
|
||||
log,
|
||||
readJSON,
|
||||
truncate,
|
||||
readComplexityReport,
|
||||
addComplexityToTask
|
||||
} from '../utils.js';
|
||||
import findNextTask from './find-next-task.js';
|
||||
addComplexityToTask,
|
||||
} from "../utils.js";
|
||||
import findNextTask from "./find-next-task.js";
|
||||
|
||||
import {
|
||||
displayBanner,
|
||||
getStatusWithColor,
|
||||
formatDependenciesWithStatus,
|
||||
getComplexityWithColor,
|
||||
createProgressBar
|
||||
} from '../ui.js';
|
||||
createProgressBar,
|
||||
} from "../ui.js";
|
||||
|
||||
/**
|
||||
* List all tasks
|
||||
@@ -33,14 +33,9 @@ function listTasks(
|
||||
statusFilter,
|
||||
reportPath = null,
|
||||
withSubtasks = false,
|
||||
outputFormat = 'text'
|
||||
outputFormat = "text"
|
||||
) {
|
||||
try {
|
||||
// Only display banner for text output
|
||||
if (outputFormat === 'text') {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
const data = readJSON(tasksPath); // Reads the whole tasks.json
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
@@ -55,7 +50,7 @@ function listTasks(
|
||||
|
||||
// Filter tasks by status if specified
|
||||
const filteredTasks =
|
||||
statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all'
|
||||
statusFilter && statusFilter.toLowerCase() !== "all" // <-- Added check for 'all'
|
||||
? data.tasks.filter(
|
||||
(task) =>
|
||||
task.status &&
|
||||
@@ -66,7 +61,7 @@ function listTasks(
|
||||
// Calculate completion statistics
|
||||
const totalTasks = data.tasks.length;
|
||||
const completedTasks = data.tasks.filter(
|
||||
(task) => task.status === 'done' || task.status === 'completed'
|
||||
(task) => task.status === "done" || task.status === "completed"
|
||||
).length;
|
||||
const completionPercentage =
|
||||
totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
|
||||
@@ -74,19 +69,19 @@ function listTasks(
|
||||
// Count statuses for tasks
|
||||
const doneCount = completedTasks;
|
||||
const inProgressCount = data.tasks.filter(
|
||||
(task) => task.status === 'in-progress'
|
||||
(task) => task.status === "in-progress"
|
||||
).length;
|
||||
const pendingCount = data.tasks.filter(
|
||||
(task) => task.status === 'pending'
|
||||
(task) => task.status === "pending"
|
||||
).length;
|
||||
const blockedCount = data.tasks.filter(
|
||||
(task) => task.status === 'blocked'
|
||||
(task) => task.status === "blocked"
|
||||
).length;
|
||||
const deferredCount = data.tasks.filter(
|
||||
(task) => task.status === 'deferred'
|
||||
(task) => task.status === "deferred"
|
||||
).length;
|
||||
const cancelledCount = data.tasks.filter(
|
||||
(task) => task.status === 'cancelled'
|
||||
(task) => task.status === "cancelled"
|
||||
).length;
|
||||
|
||||
// Count subtasks and their statuses
|
||||
@@ -102,22 +97,22 @@ function listTasks(
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
totalSubtasks += task.subtasks.length;
|
||||
completedSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'done' || st.status === 'completed'
|
||||
(st) => st.status === "done" || st.status === "completed"
|
||||
).length;
|
||||
inProgressSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'in-progress'
|
||||
(st) => st.status === "in-progress"
|
||||
).length;
|
||||
pendingSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'pending'
|
||||
(st) => st.status === "pending"
|
||||
).length;
|
||||
blockedSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'blocked'
|
||||
(st) => st.status === "blocked"
|
||||
).length;
|
||||
deferredSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'deferred'
|
||||
(st) => st.status === "deferred"
|
||||
).length;
|
||||
cancelledSubtasks += task.subtasks.filter(
|
||||
(st) => st.status === 'cancelled'
|
||||
(st) => st.status === "cancelled"
|
||||
).length;
|
||||
}
|
||||
});
|
||||
@@ -126,7 +121,7 @@ function listTasks(
|
||||
totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0;
|
||||
|
||||
// For JSON output, return structured data
|
||||
if (outputFormat === 'json') {
|
||||
if (outputFormat === "json") {
|
||||
// *** Modification: Remove 'details' field for JSON output ***
|
||||
const tasksWithoutDetails = filteredTasks.map((task) => {
|
||||
// <-- USES filteredTasks!
|
||||
@@ -146,7 +141,7 @@ function listTasks(
|
||||
|
||||
return {
|
||||
tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED
|
||||
filter: statusFilter || 'all', // Return the actual filter used
|
||||
filter: statusFilter || "all", // Return the actual filter used
|
||||
stats: {
|
||||
total: totalTasks,
|
||||
completed: doneCount,
|
||||
@@ -164,9 +159,9 @@ function listTasks(
|
||||
blocked: blockedSubtasks,
|
||||
deferred: deferredSubtasks,
|
||||
cancelled: cancelledSubtasks,
|
||||
completionPercentage: subtaskCompletionPercentage
|
||||
}
|
||||
}
|
||||
completionPercentage: subtaskCompletionPercentage,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,22 +169,22 @@ function listTasks(
|
||||
|
||||
// Calculate status breakdowns as percentages of total
|
||||
const taskStatusBreakdown = {
|
||||
'in-progress': totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0,
|
||||
"in-progress": totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0,
|
||||
pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0,
|
||||
blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0,
|
||||
deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0,
|
||||
cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0
|
||||
cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0,
|
||||
};
|
||||
|
||||
const subtaskStatusBreakdown = {
|
||||
'in-progress':
|
||||
"in-progress":
|
||||
totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0,
|
||||
pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0,
|
||||
blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0,
|
||||
deferred:
|
||||
totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0,
|
||||
cancelled:
|
||||
totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0
|
||||
totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0,
|
||||
};
|
||||
|
||||
// Create progress bars with status breakdowns
|
||||
@@ -207,21 +202,21 @@ function listTasks(
|
||||
// Calculate dependency statistics
|
||||
const completedTaskIds = new Set(
|
||||
data.tasks
|
||||
.filter((t) => t.status === 'done' || t.status === 'completed')
|
||||
.filter((t) => t.status === "done" || t.status === "completed")
|
||||
.map((t) => t.id)
|
||||
);
|
||||
|
||||
const tasksWithNoDeps = data.tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' &&
|
||||
t.status !== 'completed' &&
|
||||
t.status !== "done" &&
|
||||
t.status !== "completed" &&
|
||||
(!t.dependencies || t.dependencies.length === 0)
|
||||
).length;
|
||||
|
||||
const tasksWithAllDepsSatisfied = data.tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' &&
|
||||
t.status !== 'completed' &&
|
||||
t.status !== "done" &&
|
||||
t.status !== "completed" &&
|
||||
t.dependencies &&
|
||||
t.dependencies.length > 0 &&
|
||||
t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||
@@ -229,8 +224,8 @@ function listTasks(
|
||||
|
||||
const tasksWithUnsatisfiedDeps = data.tasks.filter(
|
||||
(t) =>
|
||||
t.status !== 'done' &&
|
||||
t.status !== 'completed' &&
|
||||
t.status !== "done" &&
|
||||
t.status !== "completed" &&
|
||||
t.dependencies &&
|
||||
t.dependencies.length > 0 &&
|
||||
!t.dependencies.every((depId) => completedTaskIds.has(depId))
|
||||
@@ -283,7 +278,7 @@ function listTasks(
|
||||
terminalWidth = process.stdout.columns;
|
||||
} catch (e) {
|
||||
// Fallback if columns cannot be determined
|
||||
log('debug', 'Could not determine terminal width, using default');
|
||||
log("debug", "Could not determine terminal width, using default");
|
||||
}
|
||||
// Ensure we have a reasonable default if detection fails
|
||||
terminalWidth = terminalWidth || 80;
|
||||
@@ -293,35 +288,35 @@ function listTasks(
|
||||
|
||||
// Create dashboard content
|
||||
const projectDashboardContent =
|
||||
chalk.white.bold('Project Dashboard') +
|
||||
'\n' +
|
||||
chalk.white.bold("Project Dashboard") +
|
||||
"\n" +
|
||||
`Tasks Progress: ${chalk.greenBright(taskProgressBar)} ${completionPercentage.toFixed(0)}%\n` +
|
||||
`Done: ${chalk.green(doneCount)} In Progress: ${chalk.blue(inProgressCount)} Pending: ${chalk.yellow(pendingCount)} Blocked: ${chalk.red(blockedCount)} Deferred: ${chalk.gray(deferredCount)} Cancelled: ${chalk.gray(cancelledCount)}\n\n` +
|
||||
`Subtasks Progress: ${chalk.cyan(subtaskProgressBar)} ${subtaskCompletionPercentage.toFixed(0)}%\n` +
|
||||
`Completed: ${chalk.green(completedSubtasks)}/${totalSubtasks} In Progress: ${chalk.blue(inProgressSubtasks)} Pending: ${chalk.yellow(pendingSubtasks)} Blocked: ${chalk.red(blockedSubtasks)} Deferred: ${chalk.gray(deferredSubtasks)} Cancelled: ${chalk.gray(cancelledSubtasks)}\n\n` +
|
||||
chalk.cyan.bold('Priority Breakdown:') +
|
||||
'\n' +
|
||||
`${chalk.red('•')} ${chalk.white('High priority:')} ${data.tasks.filter((t) => t.priority === 'high').length}\n` +
|
||||
`${chalk.yellow('•')} ${chalk.white('Medium priority:')} ${data.tasks.filter((t) => t.priority === 'medium').length}\n` +
|
||||
`${chalk.green('•')} ${chalk.white('Low priority:')} ${data.tasks.filter((t) => t.priority === 'low').length}`;
|
||||
chalk.cyan.bold("Priority Breakdown:") +
|
||||
"\n" +
|
||||
`${chalk.red("•")} ${chalk.white("High priority:")} ${data.tasks.filter((t) => t.priority === "high").length}\n` +
|
||||
`${chalk.yellow("•")} ${chalk.white("Medium priority:")} ${data.tasks.filter((t) => t.priority === "medium").length}\n` +
|
||||
`${chalk.green("•")} ${chalk.white("Low priority:")} ${data.tasks.filter((t) => t.priority === "low").length}`;
|
||||
|
||||
const dependencyDashboardContent =
|
||||
chalk.white.bold('Dependency Status & Next Task') +
|
||||
'\n' +
|
||||
chalk.cyan.bold('Dependency Metrics:') +
|
||||
'\n' +
|
||||
`${chalk.green('•')} ${chalk.white('Tasks with no dependencies:')} ${tasksWithNoDeps}\n` +
|
||||
`${chalk.green('•')} ${chalk.white('Tasks ready to work on:')} ${tasksReadyToWork}\n` +
|
||||
`${chalk.yellow('•')} ${chalk.white('Tasks blocked by dependencies:')} ${tasksWithUnsatisfiedDeps}\n` +
|
||||
`${chalk.magenta('•')} ${chalk.white('Most depended-on task:')} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray('None')}\n` +
|
||||
`${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${avgDependenciesPerTask.toFixed(1)}\n\n` +
|
||||
chalk.cyan.bold('Next Task to Work On:') +
|
||||
'\n' +
|
||||
`ID: ${chalk.cyan(nextItem ? nextItem.id : 'N/A')} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow('No task available')}
|
||||
chalk.white.bold("Dependency Status & Next Task") +
|
||||
"\n" +
|
||||
chalk.cyan.bold("Dependency Metrics:") +
|
||||
"\n" +
|
||||
`${chalk.green("•")} ${chalk.white("Tasks with no dependencies:")} ${tasksWithNoDeps}\n` +
|
||||
`${chalk.green("•")} ${chalk.white("Tasks ready to work on:")} ${tasksReadyToWork}\n` +
|
||||
`${chalk.yellow("•")} ${chalk.white("Tasks blocked by dependencies:")} ${tasksWithUnsatisfiedDeps}\n` +
|
||||
`${chalk.magenta("•")} ${chalk.white("Most depended-on task:")} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray("None")}\n` +
|
||||
`${chalk.blue("•")} ${chalk.white("Avg dependencies per task:")} ${avgDependenciesPerTask.toFixed(1)}\n\n` +
|
||||
chalk.cyan.bold("Next Task to Work On:") +
|
||||
"\n" +
|
||||
`ID: ${chalk.cyan(nextItem ? nextItem.id : "N/A")} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow("No task available")}
|
||||
` +
|
||||
`Priority: ${nextItem ? chalk.white(nextItem.priority || 'medium') : ''} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ''}
|
||||
`Priority: ${nextItem ? chalk.white(nextItem.priority || "medium") : ""} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ""}
|
||||
` +
|
||||
`Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray('N/A')}`;
|
||||
`Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray("N/A")}`;
|
||||
|
||||
// Calculate width for side-by-side display
|
||||
// Box borders, padding take approximately 4 chars on each side
|
||||
@@ -341,23 +336,23 @@ function listTasks(
|
||||
// Create boxen options with precise widths
|
||||
const dashboardBox = boxen(projectDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
width: boxContentWidth,
|
||||
dimBorder: false
|
||||
dimBorder: false,
|
||||
});
|
||||
|
||||
const dependencyBox = boxen(dependencyDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'magenta',
|
||||
borderStyle: 'round',
|
||||
borderColor: "magenta",
|
||||
borderStyle: "round",
|
||||
width: boxContentWidth,
|
||||
dimBorder: false
|
||||
dimBorder: false,
|
||||
});
|
||||
|
||||
// Create a better side-by-side layout with exact spacing
|
||||
const dashboardLines = dashboardBox.split('\n');
|
||||
const dependencyLines = dependencyBox.split('\n');
|
||||
const dashboardLines = dashboardBox.split("\n");
|
||||
const dependencyLines = dependencyBox.split("\n");
|
||||
|
||||
// Make sure both boxes have the same height
|
||||
const maxHeight = Math.max(dashboardLines.length, dependencyLines.length);
|
||||
@@ -367,35 +362,35 @@ function listTasks(
|
||||
const combinedLines = [];
|
||||
for (let i = 0; i < maxHeight; i++) {
|
||||
// Get the dashboard line (or empty string if we've run out of lines)
|
||||
const dashLine = i < dashboardLines.length ? dashboardLines[i] : '';
|
||||
const dashLine = i < dashboardLines.length ? dashboardLines[i] : "";
|
||||
// Get the dependency line (or empty string if we've run out of lines)
|
||||
const depLine = i < dependencyLines.length ? dependencyLines[i] : '';
|
||||
const depLine = i < dependencyLines.length ? dependencyLines[i] : "";
|
||||
|
||||
// Remove any trailing spaces from dashLine before padding to exact width
|
||||
const trimmedDashLine = dashLine.trimEnd();
|
||||
// Pad the dashboard line to exactly halfWidth chars with no extra spaces
|
||||
const paddedDashLine = trimmedDashLine.padEnd(halfWidth, ' ');
|
||||
const paddedDashLine = trimmedDashLine.padEnd(halfWidth, " ");
|
||||
|
||||
// Join the lines with no space in between
|
||||
combinedLines.push(paddedDashLine + depLine);
|
||||
}
|
||||
|
||||
// Join all lines and output
|
||||
console.log(combinedLines.join('\n'));
|
||||
console.log(combinedLines.join("\n"));
|
||||
} else {
|
||||
// Terminal too narrow, show boxes stacked vertically
|
||||
const dashboardBox = boxen(projectDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 0, bottom: 1 }
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
margin: { top: 0, bottom: 1 },
|
||||
});
|
||||
|
||||
const dependencyBox = boxen(dependencyDashboardContent, {
|
||||
padding: 1,
|
||||
borderColor: 'magenta',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 0, bottom: 1 }
|
||||
borderColor: "magenta",
|
||||
borderStyle: "round",
|
||||
margin: { top: 0, bottom: 1 },
|
||||
});
|
||||
|
||||
// Display stacked vertically
|
||||
@@ -408,8 +403,8 @@ function listTasks(
|
||||
boxen(
|
||||
statusFilter
|
||||
? chalk.yellow(`No tasks with status '${statusFilter}' found`)
|
||||
: chalk.yellow('No tasks found'),
|
||||
{ padding: 1, borderColor: 'yellow', borderStyle: 'round' }
|
||||
: chalk.yellow("No tasks found"),
|
||||
{ padding: 1, borderColor: "yellow", borderStyle: "round" }
|
||||
)
|
||||
);
|
||||
return;
|
||||
@@ -458,12 +453,12 @@ function listTasks(
|
||||
// Create a table with correct borders and spacing
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.cyan.bold('ID'),
|
||||
chalk.cyan.bold('Title'),
|
||||
chalk.cyan.bold('Status'),
|
||||
chalk.cyan.bold('Priority'),
|
||||
chalk.cyan.bold('Dependencies'),
|
||||
chalk.cyan.bold('Complexity')
|
||||
chalk.cyan.bold("ID"),
|
||||
chalk.cyan.bold("Title"),
|
||||
chalk.cyan.bold("Status"),
|
||||
chalk.cyan.bold("Priority"),
|
||||
chalk.cyan.bold("Dependencies"),
|
||||
chalk.cyan.bold("Complexity"),
|
||||
],
|
||||
colWidths: [
|
||||
idWidth,
|
||||
@@ -471,21 +466,21 @@ function listTasks(
|
||||
statusWidth,
|
||||
priorityWidth,
|
||||
depsWidth,
|
||||
complexityWidth // Added complexity column width
|
||||
complexityWidth, // Added complexity column width
|
||||
],
|
||||
style: {
|
||||
head: [], // No special styling for header
|
||||
border: [], // No special styling for border
|
||||
compact: false // Use default spacing
|
||||
compact: false, // Use default spacing
|
||||
},
|
||||
wordWrap: true,
|
||||
wrapOnWordBoundary: true
|
||||
wrapOnWordBoundary: true,
|
||||
});
|
||||
|
||||
// Process tasks for the table
|
||||
filteredTasks.forEach((task) => {
|
||||
// Format dependencies with status indicators (colored)
|
||||
let depText = 'None';
|
||||
let depText = "None";
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
// Use the proper formatDependenciesWithStatus function for colored status
|
||||
depText = formatDependenciesWithStatus(
|
||||
@@ -495,19 +490,19 @@ function listTasks(
|
||||
complexityReport
|
||||
);
|
||||
} else {
|
||||
depText = chalk.gray('None');
|
||||
depText = chalk.gray("None");
|
||||
}
|
||||
|
||||
// Clean up any ANSI codes or confusing characters
|
||||
const cleanTitle = task.title.replace(/\n/g, ' ');
|
||||
const cleanTitle = task.title.replace(/\n/g, " ");
|
||||
|
||||
// Get priority color
|
||||
const priorityColor =
|
||||
{
|
||||
high: chalk.red,
|
||||
medium: chalk.yellow,
|
||||
low: chalk.gray
|
||||
}[task.priority || 'medium'] || chalk.white;
|
||||
low: chalk.gray,
|
||||
}[task.priority || "medium"] || chalk.white;
|
||||
|
||||
// Format status
|
||||
const status = getStatusWithColor(task.status, true);
|
||||
@@ -517,38 +512,38 @@ function listTasks(
|
||||
task.id.toString(),
|
||||
truncate(cleanTitle, titleWidth - 3),
|
||||
status,
|
||||
priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)),
|
||||
priorityColor(truncate(task.priority || "medium", priorityWidth - 2)),
|
||||
depText,
|
||||
task.complexityScore
|
||||
? getComplexityWithColor(task.complexityScore)
|
||||
: chalk.gray('N/A')
|
||||
: chalk.gray("N/A"),
|
||||
]);
|
||||
|
||||
// Add subtasks if requested
|
||||
if (withSubtasks && task.subtasks && task.subtasks.length > 0) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
// Format subtask dependencies with status indicators
|
||||
let subtaskDepText = 'None';
|
||||
let subtaskDepText = "None";
|
||||
if (subtask.dependencies && subtask.dependencies.length > 0) {
|
||||
// Handle both subtask-to-subtask and subtask-to-task dependencies
|
||||
const formattedDeps = subtask.dependencies
|
||||
.map((depId) => {
|
||||
// Check if it's a dependency on another subtask
|
||||
if (typeof depId === 'number' && depId < 100) {
|
||||
if (typeof depId === "number" && depId < 100) {
|
||||
const foundSubtask = task.subtasks.find(
|
||||
(st) => st.id === depId
|
||||
);
|
||||
if (foundSubtask) {
|
||||
const isDone =
|
||||
foundSubtask.status === 'done' ||
|
||||
foundSubtask.status === 'completed';
|
||||
const isInProgress = foundSubtask.status === 'in-progress';
|
||||
foundSubtask.status === "done" ||
|
||||
foundSubtask.status === "completed";
|
||||
const isInProgress = foundSubtask.status === "in-progress";
|
||||
|
||||
// Use consistent color formatting instead of emojis
|
||||
if (isDone) {
|
||||
return chalk.green.bold(`${task.id}.${depId}`);
|
||||
} else if (isInProgress) {
|
||||
return chalk.hex('#FFA500').bold(`${task.id}.${depId}`);
|
||||
return chalk.hex("#FFA500").bold(`${task.id}.${depId}`);
|
||||
} else {
|
||||
return chalk.red.bold(`${task.id}.${depId}`);
|
||||
}
|
||||
@@ -560,22 +555,22 @@ function listTasks(
|
||||
// Add complexity to depTask before checking status
|
||||
addComplexityToTask(depTask, complexityReport);
|
||||
const isDone =
|
||||
depTask.status === 'done' || depTask.status === 'completed';
|
||||
const isInProgress = depTask.status === 'in-progress';
|
||||
depTask.status === "done" || depTask.status === "completed";
|
||||
const isInProgress = depTask.status === "in-progress";
|
||||
// Use the same color scheme as in formatDependenciesWithStatus
|
||||
if (isDone) {
|
||||
return chalk.green.bold(`${depId}`);
|
||||
} else if (isInProgress) {
|
||||
return chalk.hex('#FFA500').bold(`${depId}`);
|
||||
return chalk.hex("#FFA500").bold(`${depId}`);
|
||||
} else {
|
||||
return chalk.red.bold(`${depId}`);
|
||||
}
|
||||
}
|
||||
return chalk.cyan(depId.toString());
|
||||
})
|
||||
.join(', ');
|
||||
.join(", ");
|
||||
|
||||
subtaskDepText = formattedDeps || chalk.gray('None');
|
||||
subtaskDepText = formattedDeps || chalk.gray("None");
|
||||
}
|
||||
|
||||
// Add the subtask row without truncating dependencies
|
||||
@@ -583,11 +578,11 @@ function listTasks(
|
||||
`${task.id}.${subtask.id}`,
|
||||
chalk.dim(`└─ ${truncate(subtask.title, titleWidth - 5)}`),
|
||||
getStatusWithColor(subtask.status, true),
|
||||
chalk.dim('-'),
|
||||
chalk.dim("-"),
|
||||
subtaskDepText,
|
||||
subtask.complexityScore
|
||||
? chalk.gray(`${subtask.complexityScore}`)
|
||||
: chalk.gray('N/A')
|
||||
: chalk.gray("N/A"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
@@ -597,12 +592,12 @@ function listTasks(
|
||||
try {
|
||||
console.log(table.toString());
|
||||
} catch (err) {
|
||||
log('error', `Error rendering table: ${err.message}`);
|
||||
log("error", `Error rendering table: ${err.message}`);
|
||||
|
||||
// Fall back to simpler output
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nFalling back to simple task list due to terminal width constraints:'
|
||||
"\nFalling back to simple task list due to terminal width constraints:"
|
||||
)
|
||||
);
|
||||
filteredTasks.forEach((task) => {
|
||||
@@ -624,13 +619,13 @@ function listTasks(
|
||||
const priorityColors = {
|
||||
high: chalk.red.bold,
|
||||
medium: chalk.yellow,
|
||||
low: chalk.gray
|
||||
low: chalk.gray,
|
||||
};
|
||||
|
||||
// Show next task box in a prominent color
|
||||
if (nextItem) {
|
||||
// Prepare subtasks section if they exist (Only tasks have .subtasks property)
|
||||
let subtasksSection = '';
|
||||
let subtasksSection = "";
|
||||
// Check if the nextItem is a top-level task before looking for subtasks
|
||||
const parentTaskForSubtasks = data.tasks.find(
|
||||
(t) => String(t.id) === String(nextItem.id)
|
||||
@@ -640,75 +635,75 @@ function listTasks(
|
||||
parentTaskForSubtasks.subtasks &&
|
||||
parentTaskForSubtasks.subtasks.length > 0
|
||||
) {
|
||||
subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`;
|
||||
subtasksSection = `\n\n${chalk.white.bold("Subtasks:")}\n`;
|
||||
subtasksSection += parentTaskForSubtasks.subtasks
|
||||
.map((subtask) => {
|
||||
// Add complexity to subtask before display
|
||||
addComplexityToTask(subtask, complexityReport);
|
||||
// Using a more simplified format for subtask status display
|
||||
const status = subtask.status || 'pending';
|
||||
const status = subtask.status || "pending";
|
||||
const statusColors = {
|
||||
done: chalk.green,
|
||||
completed: chalk.green,
|
||||
pending: chalk.yellow,
|
||||
'in-progress': chalk.blue,
|
||||
"in-progress": chalk.blue,
|
||||
deferred: chalk.gray,
|
||||
blocked: chalk.red,
|
||||
cancelled: chalk.gray
|
||||
cancelled: chalk.gray,
|
||||
};
|
||||
const statusColor =
|
||||
statusColors[status.toLowerCase()] || chalk.white;
|
||||
// Ensure subtask ID is displayed correctly using parent ID from the original task object
|
||||
return `${chalk.cyan(`${parentTaskForSubtasks.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`;
|
||||
})
|
||||
.join('\n');
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.hex('#FF8800').bold(
|
||||
chalk.hex("#FF8800").bold(
|
||||
// Use nextItem.id and nextItem.title
|
||||
`🔥 Next Task to Work On: #${nextItem.id} - ${nextItem.title}`
|
||||
) +
|
||||
'\n\n' +
|
||||
"\n\n" +
|
||||
// Use nextItem.priority, nextItem.status, nextItem.dependencies
|
||||
`${chalk.white('Priority:')} ${priorityColors[nextItem.priority || 'medium'](nextItem.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextItem.status, true)}\n` +
|
||||
`${chalk.white('Dependencies:')} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray('None')}\n\n` +
|
||||
`${chalk.white("Priority:")} ${priorityColors[nextItem.priority || "medium"](nextItem.priority || "medium")} ${chalk.white("Status:")} ${getStatusWithColor(nextItem.status, true)}\n` +
|
||||
`${chalk.white("Dependencies:")} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray("None")}\n\n` +
|
||||
// Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this)
|
||||
// *** Fetching original item for description and details ***
|
||||
`${chalk.white('Description:')} ${getWorkItemDescription(nextItem, data.tasks)}` +
|
||||
`${chalk.white("Description:")} ${getWorkItemDescription(nextItem, data.tasks)}` +
|
||||
subtasksSection + // <-- Subtasks are handled above now
|
||||
'\n\n' +
|
||||
"\n\n" +
|
||||
// Use nextItem.id
|
||||
`${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` +
|
||||
`${chalk.cyan("Start working:")} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` +
|
||||
// Use nextItem.id
|
||||
`${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${nextItem.id}`)}`,
|
||||
`${chalk.cyan("View details:")} ${chalk.yellow(`task-master show ${nextItem.id}`)}`,
|
||||
{
|
||||
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
||||
borderColor: '#FF8800',
|
||||
borderStyle: 'round',
|
||||
borderColor: "#FF8800",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
title: '⚡ RECOMMENDED NEXT TASK ⚡',
|
||||
titleAlignment: 'center',
|
||||
title: "⚡ RECOMMENDED NEXT TASK ⚡",
|
||||
titleAlignment: "center",
|
||||
width: terminalWidth - 4,
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.hex('#FF8800').bold('No eligible next task found') +
|
||||
'\n\n' +
|
||||
'All pending tasks have dependencies that are not yet completed, or all tasks are done.',
|
||||
chalk.hex("#FF8800").bold("No eligible next task found") +
|
||||
"\n\n" +
|
||||
"All pending tasks have dependencies that are not yet completed, or all tasks are done.",
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: '#FF8800',
|
||||
borderStyle: 'round',
|
||||
borderColor: "#FF8800",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
title: '⚡ NEXT TASK ⚡',
|
||||
titleAlignment: 'center',
|
||||
width: terminalWidth - 4 // Use full terminal width minus a small margin
|
||||
title: "⚡ NEXT TASK ⚡",
|
||||
titleAlignment: "center",
|
||||
width: terminalWidth - 4, // Use full terminal width minus a small margin
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -717,28 +712,28 @@ function listTasks(
|
||||
// Show next steps
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Suggested Next Steps:') +
|
||||
'\n\n' +
|
||||
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master next')} to see what to work on next\n` +
|
||||
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down a task into subtasks\n` +
|
||||
`${chalk.cyan('3.')} Run ${chalk.yellow('task-master set-status --id=<id> --status=done')} to mark a task as complete`,
|
||||
chalk.white.bold("Suggested Next Steps:") +
|
||||
"\n\n" +
|
||||
`${chalk.cyan("1.")} Run ${chalk.yellow("task-master next")} to see what to work on next\n` +
|
||||
`${chalk.cyan("2.")} Run ${chalk.yellow("task-master expand --id=<id>")} to break down a task into subtasks\n` +
|
||||
`${chalk.cyan("3.")} Run ${chalk.yellow("task-master set-status --id=<id> --status=done")} to mark a task as complete`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'gray',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
borderColor: "gray",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
log('error', `Error listing tasks: ${error.message}`);
|
||||
log("error", `Error listing tasks: ${error.message}`);
|
||||
|
||||
if (outputFormat === 'json') {
|
||||
if (outputFormat === "json") {
|
||||
// Return structured error for JSON output
|
||||
throw {
|
||||
code: 'TASK_LIST_ERROR',
|
||||
code: "TASK_LIST_ERROR",
|
||||
message: error.message,
|
||||
details: error.stack
|
||||
details: error.stack,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -749,18 +744,18 @@ function listTasks(
|
||||
|
||||
// *** Helper function to get description for task or subtask ***
|
||||
function getWorkItemDescription(item, allTasks) {
|
||||
if (!item) return 'N/A';
|
||||
if (!item) return "N/A";
|
||||
if (item.parentId) {
|
||||
// It's a subtask
|
||||
const parent = allTasks.find((t) => t.id === item.parentId);
|
||||
const subtask = parent?.subtasks?.find(
|
||||
(st) => `${parent.id}.${st.id}` === item.id
|
||||
);
|
||||
return subtask?.description || 'No description available.';
|
||||
return subtask?.description || "No description available.";
|
||||
} else {
|
||||
// It's a top-level task
|
||||
const task = allTasks.find((t) => String(t.id) === String(item.id));
|
||||
return task?.description || 'No description available.';
|
||||
return task?.description || "No description available.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
|
||||
import { log, readJSON, writeJSON, findTaskById } from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
import { validateTaskDependencies } from '../dependency-manager.js';
|
||||
import { getDebugFlag } from '../config-manager.js';
|
||||
import updateSingleTaskStatus from './update-single-task-status.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { log, readJSON, writeJSON, findTaskById } from "../utils.js";
|
||||
import { displayBanner } from "../ui.js";
|
||||
import { validateTaskDependencies } from "../dependency-manager.js";
|
||||
import { getDebugFlag } from "../config-manager.js";
|
||||
import updateSingleTaskStatus from "./update-single-task-status.js";
|
||||
import generateTaskFiles from "./generate-task-files.js";
|
||||
import {
|
||||
isValidTaskStatus,
|
||||
TASK_STATUS_OPTIONS
|
||||
} from '../../../src/constants/task-status.js';
|
||||
TASK_STATUS_OPTIONS,
|
||||
} from "../../../src/constants/task-status.js";
|
||||
|
||||
/**
|
||||
* Set the status of a task
|
||||
@@ -25,7 +25,7 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
try {
|
||||
if (!isValidTaskStatus(newStatus)) {
|
||||
throw new Error(
|
||||
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
|
||||
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(", ")}`
|
||||
);
|
||||
}
|
||||
// Determine if we're in MCP mode by checking for mcpLog
|
||||
@@ -33,25 +33,23 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
|
||||
// Only display UI elements if not in MCP mode
|
||||
if (!isMcpMode) {
|
||||
displayBanner();
|
||||
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round'
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
log("info", `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
}
|
||||
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIds = taskIdInput.split(',').map((id) => id.trim());
|
||||
const taskIds = taskIdInput.split(",").map((id) => id.trim());
|
||||
const updatedTasks = [];
|
||||
|
||||
// Update each task
|
||||
@@ -64,13 +62,13 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
writeJSON(tasksPath, data);
|
||||
|
||||
// Validate dependencies after status update
|
||||
log('info', 'Validating dependencies after status update...');
|
||||
log("info", "Validating dependencies after status update...");
|
||||
validateTaskDependencies(data.tasks);
|
||||
|
||||
// Generate individual task files
|
||||
log('info', 'Regenerating task files...');
|
||||
log("info", "Regenerating task files...");
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
mcpLog: options.mcpLog
|
||||
mcpLog: options.mcpLog,
|
||||
});
|
||||
|
||||
// Display success message - only in CLI mode
|
||||
@@ -82,10 +80,10 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold(`Successfully updated task ${id} status:`) +
|
||||
'\n' +
|
||||
`From: ${chalk.yellow(task ? task.status : 'unknown')}\n` +
|
||||
"\n" +
|
||||
`From: ${chalk.yellow(task ? task.status : "unknown")}\n` +
|
||||
`To: ${chalk.green(newStatus)}`,
|
||||
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||
{ padding: 1, borderColor: "green", borderStyle: "round" }
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -96,11 +94,11 @@ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
success: true,
|
||||
updatedTasks: updatedTasks.map((id) => ({
|
||||
id,
|
||||
status: newStatus
|
||||
}))
|
||||
status: newStatus,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
log('error', `Error setting task status: ${error.message}`);
|
||||
log("error", `Error setting task status: ${error.message}`);
|
||||
|
||||
// Only show error UI in CLI mode
|
||||
if (!options?.mcpLog) {
|
||||
|
||||
@@ -40,7 +40,7 @@ const warmGradient = gradient(["#fb8b24", "#e36414", "#9a031e"]);
|
||||
function displayBanner() {
|
||||
if (isSilentMode()) return;
|
||||
|
||||
console.clear();
|
||||
// console.clear(); // Removing this to avoid clearing the terminal per command
|
||||
const bannerText = figlet.textSync("Task Master", {
|
||||
font: "Standard",
|
||||
horizontalLayout: "default",
|
||||
@@ -78,6 +78,8 @@ function displayBanner() {
|
||||
* @returns {Object} Spinner object
|
||||
*/
|
||||
function startLoadingIndicator(message) {
|
||||
if (isSilentMode()) return null;
|
||||
|
||||
const spinner = ora({
|
||||
text: message,
|
||||
color: "cyan",
|
||||
@@ -87,15 +89,75 @@ function startLoadingIndicator(message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a loading indicator
|
||||
* Stop a loading indicator (basic stop, no success/fail indicator)
|
||||
* @param {Object} spinner - Spinner object to stop
|
||||
*/
|
||||
function stopLoadingIndicator(spinner) {
|
||||
if (spinner && spinner.stop) {
|
||||
if (spinner && typeof spinner.stop === "function") {
|
||||
spinner.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a loading indicator with success (shows checkmark)
|
||||
* @param {Object} spinner - Spinner object to complete
|
||||
* @param {string} message - Optional success message (defaults to current text)
|
||||
*/
|
||||
function succeedLoadingIndicator(spinner, message = null) {
|
||||
if (spinner && typeof spinner.succeed === "function") {
|
||||
if (message) {
|
||||
spinner.succeed(message);
|
||||
} else {
|
||||
spinner.succeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a loading indicator with failure (shows X)
|
||||
* @param {Object} spinner - Spinner object to fail
|
||||
* @param {string} message - Optional failure message (defaults to current text)
|
||||
*/
|
||||
function failLoadingIndicator(spinner, message = null) {
|
||||
if (spinner && typeof spinner.fail === "function") {
|
||||
if (message) {
|
||||
spinner.fail(message);
|
||||
} else {
|
||||
spinner.fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a loading indicator with warning (shows warning symbol)
|
||||
* @param {Object} spinner - Spinner object to warn
|
||||
* @param {string} message - Optional warning message (defaults to current text)
|
||||
*/
|
||||
function warnLoadingIndicator(spinner, message = null) {
|
||||
if (spinner && typeof spinner.warn === "function") {
|
||||
if (message) {
|
||||
spinner.warn(message);
|
||||
} else {
|
||||
spinner.warn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a loading indicator with info (shows info symbol)
|
||||
* @param {Object} spinner - Spinner object to complete with info
|
||||
* @param {string} message - Optional info message (defaults to current text)
|
||||
*/
|
||||
function infoLoadingIndicator(spinner, message = null) {
|
||||
if (spinner && typeof spinner.info === "function") {
|
||||
if (message) {
|
||||
spinner.info(message);
|
||||
} else {
|
||||
spinner.info();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a colored progress bar
|
||||
* @param {number} percent - The completion percentage
|
||||
@@ -232,14 +294,14 @@ function getStatusWithColor(status, forTable = false) {
|
||||
}
|
||||
|
||||
const statusConfig = {
|
||||
done: { color: chalk.green, icon: "✅", tableIcon: "✓" },
|
||||
completed: { color: chalk.green, icon: "✅", tableIcon: "✓" },
|
||||
pending: { color: chalk.yellow, icon: "⏱️", tableIcon: "⏱" },
|
||||
done: { color: chalk.green, icon: "✓", tableIcon: "✓" },
|
||||
completed: { color: chalk.green, icon: "✓", tableIcon: "✓" },
|
||||
pending: { color: chalk.yellow, icon: "○", tableIcon: "⏱" },
|
||||
"in-progress": { color: chalk.hex("#FFA500"), icon: "🔄", tableIcon: "►" },
|
||||
deferred: { color: chalk.gray, icon: "⏱️", tableIcon: "⏱" },
|
||||
blocked: { color: chalk.red, icon: "❌", tableIcon: "✗" },
|
||||
review: { color: chalk.magenta, icon: "👀", tableIcon: "👁" },
|
||||
cancelled: { color: chalk.gray, icon: "❌", tableIcon: "✗" },
|
||||
deferred: { color: chalk.gray, icon: "x", tableIcon: "⏱" },
|
||||
blocked: { color: chalk.red, icon: "!", tableIcon: "✗" },
|
||||
review: { color: chalk.magenta, icon: "?", tableIcon: "?" },
|
||||
cancelled: { color: chalk.gray, icon: "❌", tableIcon: "x" },
|
||||
};
|
||||
|
||||
const config = statusConfig[status.toLowerCase()] || {
|
||||
@@ -383,8 +445,6 @@ function formatDependenciesWithStatus(
|
||||
* Display a comprehensive help guide
|
||||
*/
|
||||
function displayHelp() {
|
||||
displayBanner();
|
||||
|
||||
// Get terminal width - moved to top of function to make it available throughout
|
||||
const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect
|
||||
|
||||
@@ -767,8 +827,6 @@ function truncateString(str, maxLength) {
|
||||
* @param {string} tasksPath - Path to the tasks.json file
|
||||
*/
|
||||
async function displayNextTask(tasksPath, complexityReportPath = null) {
|
||||
displayBanner();
|
||||
|
||||
// Read the tasks file
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
@@ -1039,8 +1097,6 @@ async function displayTaskById(
|
||||
complexityReportPath = null,
|
||||
statusFilter = null
|
||||
) {
|
||||
displayBanner();
|
||||
|
||||
// Read the tasks file
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
@@ -1495,8 +1551,6 @@ async function displayTaskById(
|
||||
* @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(
|
||||
@@ -2094,4 +2148,8 @@ export {
|
||||
displayModelConfiguration,
|
||||
displayAvailableModels,
|
||||
displayAiUsageSummary,
|
||||
succeedLoadingIndicator,
|
||||
failLoadingIndicator,
|
||||
warnLoadingIndicator,
|
||||
infoLoadingIndicator,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user