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:
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
import Table from "cli-table3";
|
||||
|
||||
import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { log, readJSON, writeJSON, truncate, isSilentMode } from "../utils.js";
|
||||
import { displayBanner } from "../ui.js";
|
||||
import generateTaskFiles from "./generate-task-files.js";
|
||||
|
||||
/**
|
||||
* Clear subtasks from specified tasks
|
||||
@@ -13,140 +13,138 @@ import generateTaskFiles from './generate-task-files.js';
|
||||
* @param {string} taskIds - Task IDs to clear subtasks from
|
||||
*/
|
||||
function clearSubtasks(tasksPath, taskIds) {
|
||||
displayBanner();
|
||||
log("info", `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log("error", "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', 'No valid tasks found.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold("Clearing Subtasks"), {
|
||||
padding: 1,
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1, bottom: 1 },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Clearing Subtasks'), {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
})
|
||||
);
|
||||
}
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIdArray = taskIds.split(",").map((id) => id.trim());
|
||||
let clearedCount = 0;
|
||||
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIdArray = taskIds.split(',').map((id) => id.trim());
|
||||
let clearedCount = 0;
|
||||
// Create a summary table for the cleared subtasks
|
||||
const summaryTable = new Table({
|
||||
head: [
|
||||
chalk.cyan.bold("Task ID"),
|
||||
chalk.cyan.bold("Task Title"),
|
||||
chalk.cyan.bold("Subtasks Cleared"),
|
||||
],
|
||||
colWidths: [10, 50, 20],
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
// Create a summary table for the cleared subtasks
|
||||
const summaryTable = new Table({
|
||||
head: [
|
||||
chalk.cyan.bold('Task ID'),
|
||||
chalk.cyan.bold('Task Title'),
|
||||
chalk.cyan.bold('Subtasks Cleared')
|
||||
],
|
||||
colWidths: [10, 50, 20],
|
||||
style: { head: [], border: [] }
|
||||
});
|
||||
taskIdArray.forEach((taskId) => {
|
||||
const id = parseInt(taskId, 10);
|
||||
if (isNaN(id)) {
|
||||
log("error", `Invalid task ID: ${taskId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
taskIdArray.forEach((taskId) => {
|
||||
const id = parseInt(taskId, 10);
|
||||
if (isNaN(id)) {
|
||||
log('error', `Invalid task ID: ${taskId}`);
|
||||
return;
|
||||
}
|
||||
const task = data.tasks.find((t) => t.id === id);
|
||||
if (!task) {
|
||||
log("error", `Task ${id} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const task = data.tasks.find((t) => t.id === id);
|
||||
if (!task) {
|
||||
log('error', `Task ${id} not found`);
|
||||
return;
|
||||
}
|
||||
if (!task.subtasks || task.subtasks.length === 0) {
|
||||
log("info", `Task ${id} has no subtasks to clear`);
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.yellow("No subtasks"),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!task.subtasks || task.subtasks.length === 0) {
|
||||
log('info', `Task ${id} has no subtasks to clear`);
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.yellow('No subtasks')
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const subtaskCount = task.subtasks.length;
|
||||
task.subtasks = [];
|
||||
clearedCount++;
|
||||
log("info", `Cleared ${subtaskCount} subtasks from task ${id}`);
|
||||
|
||||
const subtaskCount = task.subtasks.length;
|
||||
task.subtasks = [];
|
||||
clearedCount++;
|
||||
log('info', `Cleared ${subtaskCount} subtasks from task ${id}`);
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.green(`${subtaskCount} subtasks cleared`),
|
||||
]);
|
||||
});
|
||||
|
||||
summaryTable.push([
|
||||
id.toString(),
|
||||
truncate(task.title, 47),
|
||||
chalk.green(`${subtaskCount} subtasks cleared`)
|
||||
]);
|
||||
});
|
||||
if (clearedCount > 0) {
|
||||
writeJSON(tasksPath, data);
|
||||
|
||||
if (clearedCount > 0) {
|
||||
writeJSON(tasksPath, data);
|
||||
// Show summary table
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold("Subtask Clearing Summary:"), {
|
||||
padding: { left: 2, right: 2, top: 0, bottom: 0 },
|
||||
margin: { top: 1, bottom: 0 },
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
})
|
||||
);
|
||||
console.log(summaryTable.toString());
|
||||
}
|
||||
|
||||
// Show summary table
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold('Subtask Clearing Summary:'), {
|
||||
padding: { left: 2, right: 2, top: 0, bottom: 0 },
|
||||
margin: { top: 1, bottom: 0 },
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
);
|
||||
console.log(summaryTable.toString());
|
||||
}
|
||||
// Regenerate task files to reflect changes
|
||||
log("info", "Regenerating task files...");
|
||||
generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
|
||||
// Regenerate task files to reflect changes
|
||||
log('info', 'Regenerating task files...');
|
||||
generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
// Success message
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(
|
||||
`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: "green",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Success message
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(
|
||||
`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Next steps suggestion
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('Next Steps:') +
|
||||
'\n\n' +
|
||||
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` +
|
||||
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'cyan',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.yellow('No subtasks were cleared'), {
|
||||
padding: 1,
|
||||
borderColor: 'yellow',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// Next steps suggestion
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold("Next Steps:") +
|
||||
"\n\n" +
|
||||
`${chalk.cyan("1.")} Run ${chalk.yellow("task-master expand --id=<id>")} to generate new subtasks\n` +
|
||||
`${chalk.cyan("2.")} Run ${chalk.yellow("task-master list --with-subtasks")} to verify changes`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: "cyan",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.yellow("No subtasks were cleared"), {
|
||||
padding: 1,
|
||||
borderColor: "yellow",
|
||||
borderStyle: "round",
|
||||
margin: { top: 1 },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default clearSubtasks;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import path from "path";
|
||||
import chalk from "chalk";
|
||||
import boxen from "boxen";
|
||||
|
||||
import { log, readJSON, writeJSON, findTaskById } from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
import { validateTaskDependencies } from '../dependency-manager.js';
|
||||
import { getDebugFlag } from '../config-manager.js';
|
||||
import updateSingleTaskStatus from './update-single-task-status.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { log, readJSON, writeJSON, findTaskById } from "../utils.js";
|
||||
import { displayBanner } from "../ui.js";
|
||||
import { validateTaskDependencies } from "../dependency-manager.js";
|
||||
import { getDebugFlag } from "../config-manager.js";
|
||||
import updateSingleTaskStatus from "./update-single-task-status.js";
|
||||
import generateTaskFiles from "./generate-task-files.js";
|
||||
import {
|
||||
isValidTaskStatus,
|
||||
TASK_STATUS_OPTIONS
|
||||
} from '../../../src/constants/task-status.js';
|
||||
isValidTaskStatus,
|
||||
TASK_STATUS_OPTIONS,
|
||||
} from "../../../src/constants/task-status.js";
|
||||
|
||||
/**
|
||||
* Set the status of a task
|
||||
@@ -22,102 +22,100 @@ import {
|
||||
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
|
||||
*/
|
||||
async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
try {
|
||||
if (!isValidTaskStatus(newStatus)) {
|
||||
throw new Error(
|
||||
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}`
|
||||
);
|
||||
}
|
||||
// Determine if we're in MCP mode by checking for mcpLog
|
||||
const isMcpMode = !!options?.mcpLog;
|
||||
try {
|
||||
if (!isValidTaskStatus(newStatus)) {
|
||||
throw new Error(
|
||||
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(", ")}`
|
||||
);
|
||||
}
|
||||
// Determine if we're in MCP mode by checking for mcpLog
|
||||
const isMcpMode = !!options?.mcpLog;
|
||||
|
||||
// Only display UI elements if not in MCP mode
|
||||
if (!isMcpMode) {
|
||||
displayBanner();
|
||||
// Only display UI elements if not in MCP mode
|
||||
if (!isMcpMode) {
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
|
||||
padding: 1,
|
||||
borderColor: "blue",
|
||||
borderStyle: "round",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
|
||||
padding: 1,
|
||||
borderColor: 'blue',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
);
|
||||
}
|
||||
log("info", `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
}
|
||||
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
}
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIds = taskIdInput.split(",").map((id) => id.trim());
|
||||
const updatedTasks = [];
|
||||
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
const taskIds = taskIdInput.split(',').map((id) => id.trim());
|
||||
const updatedTasks = [];
|
||||
// Update each task
|
||||
for (const id of taskIds) {
|
||||
await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
|
||||
updatedTasks.push(id);
|
||||
}
|
||||
|
||||
// Update each task
|
||||
for (const id of taskIds) {
|
||||
await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
|
||||
updatedTasks.push(id);
|
||||
}
|
||||
// Write the updated tasks to the file
|
||||
writeJSON(tasksPath, data);
|
||||
|
||||
// Write the updated tasks to the file
|
||||
writeJSON(tasksPath, data);
|
||||
// Validate dependencies after status update
|
||||
log("info", "Validating dependencies after status update...");
|
||||
validateTaskDependencies(data.tasks);
|
||||
|
||||
// Validate dependencies after status update
|
||||
log('info', 'Validating dependencies after status update...');
|
||||
validateTaskDependencies(data.tasks);
|
||||
// Generate individual task files
|
||||
log("info", "Regenerating task files...");
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
mcpLog: options.mcpLog,
|
||||
});
|
||||
|
||||
// Generate individual task files
|
||||
log('info', 'Regenerating task files...');
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
mcpLog: options.mcpLog
|
||||
});
|
||||
// Display success message - only in CLI mode
|
||||
if (!isMcpMode) {
|
||||
for (const id of updatedTasks) {
|
||||
const task = findTaskById(data.tasks, id);
|
||||
const taskName = task ? task.title : id;
|
||||
|
||||
// Display success message - only in CLI mode
|
||||
if (!isMcpMode) {
|
||||
for (const id of updatedTasks) {
|
||||
const task = findTaskById(data.tasks, id);
|
||||
const taskName = task ? task.title : id;
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold(`Successfully updated task ${id} status:`) +
|
||||
"\n" +
|
||||
`From: ${chalk.yellow(task ? task.status : "unknown")}\n` +
|
||||
`To: ${chalk.green(newStatus)}`,
|
||||
{ padding: 1, borderColor: "green", borderStyle: "round" }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold(`Successfully updated task ${id} status:`) +
|
||||
'\n' +
|
||||
`From: ${chalk.yellow(task ? task.status : 'unknown')}\n` +
|
||||
`To: ${chalk.green(newStatus)}`,
|
||||
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Return success value for programmatic use
|
||||
return {
|
||||
success: true,
|
||||
updatedTasks: updatedTasks.map((id) => ({
|
||||
id,
|
||||
status: newStatus,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
log("error", `Error setting task status: ${error.message}`);
|
||||
|
||||
// Return success value for programmatic use
|
||||
return {
|
||||
success: true,
|
||||
updatedTasks: updatedTasks.map((id) => ({
|
||||
id,
|
||||
status: newStatus
|
||||
}))
|
||||
};
|
||||
} catch (error) {
|
||||
log('error', `Error setting task status: ${error.message}`);
|
||||
// Only show error UI in CLI mode
|
||||
if (!options?.mcpLog) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
|
||||
// Only show error UI in CLI mode
|
||||
if (!options?.mcpLog) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
// Pass session to getDebugFlag
|
||||
if (getDebugFlag(options?.session)) {
|
||||
// Use getter
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// Pass session to getDebugFlag
|
||||
if (getDebugFlag(options?.session)) {
|
||||
// Use getter
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} else {
|
||||
// In MCP mode, throw the error for the caller to handle
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
process.exit(1);
|
||||
} else {
|
||||
// In MCP mode, throw the error for the caller to handle
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default setTaskStatus;
|
||||
|
||||
Reference in New Issue
Block a user