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