chore: linting ffs

This commit is contained in:
Eyal Toledano
2025-06-11 13:23:21 -04:00
parent efd14544f0
commit f1d593f887
4 changed files with 1622 additions and 1622 deletions

View File

@@ -3,14 +3,14 @@
* Tool for moving tasks or subtasks to a new position
*/
import { z } from "zod";
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot,
} from "./utils.js";
import { moveTaskDirect } from "../core/task-master-core.js";
import { findTasksPath } from "../core/utils/path-utils.js";
withNormalizedProjectRoot
} from './utils.js';
import { moveTaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
/**
* Register the moveTask tool with the MCP server
@@ -18,8 +18,8 @@ import { findTasksPath } from "../core/utils/path-utils.js";
*/
export function registerMoveTaskTool(server) {
server.addTool({
name: "move_task",
description: "Move a task or subtask to a new position",
name: 'move_task',
description: 'Move a task or subtask to a new position',
parameters: z.object({
from: z
.string()
@@ -31,12 +31,12 @@ export function registerMoveTaskTool(server) {
.describe(
'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
),
file: z.string().optional().describe("Custom path to tasks.json file"),
file: z.string().optional().describe('Custom path to tasks.json file'),
projectRoot: z
.string()
.describe(
"Root directory of the project (typically derived from session)"
),
'Root directory of the project (typically derived from session)'
)
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
@@ -48,14 +48,14 @@ export function registerMoveTaskTool(server) {
}
// Parse comma-separated IDs
const fromIds = args.from.split(",").map((id) => id.trim());
const toIds = args.to.split(",").map((id) => id.trim());
const fromIds = args.from.split(',').map((id) => id.trim());
const toIds = args.to.split(',').map((id) => id.trim());
// Validate matching IDs count
if (fromIds.length !== toIds.length) {
return createErrorResponse(
"The number of source and destination IDs must match",
"MISMATCHED_ID_COUNT"
'The number of source and destination IDs must match',
'MISMATCHED_ID_COUNT'
);
}
@@ -79,7 +79,7 @@ export function registerMoveTaskTool(server) {
sourceId: fromId,
destinationId: toId,
tasksJsonPath,
projectRoot: args.projectRoot,
projectRoot: args.projectRoot
},
log,
{ session }
@@ -99,8 +99,8 @@ export function registerMoveTaskTool(server) {
success: true,
data: {
moves: results,
message: `Successfully moved ${results.length} tasks`,
},
message: `Successfully moved ${results.length} tasks`
}
},
log
);
@@ -112,7 +112,7 @@ export function registerMoveTaskTool(server) {
sourceId: args.from,
destinationId: args.to,
tasksJsonPath,
projectRoot: args.projectRoot,
projectRoot: args.projectRoot
},
log,
{ session }
@@ -123,9 +123,9 @@ export function registerMoveTaskTool(server) {
} catch (error) {
return createErrorResponse(
`Failed to move task: ${error.message}`,
"MOVE_TASK_ERROR"
'MOVE_TASK_ERROR'
);
}
}),
})
});
}

View File

@@ -1,9 +1,9 @@
import path from "path";
import chalk from "chalk";
import boxen from "boxen";
import Table from "cli-table3";
import { z } from "zod";
import Fuse from "fuse.js"; // Import Fuse.js for advanced fuzzy search
import path from 'path';
import chalk from 'chalk';
import boxen from 'boxen';
import Table from 'cli-table3';
import { z } from 'zod';
import Fuse from 'fuse.js'; // Import Fuse.js for advanced fuzzy search
import {
displayBanner,
@@ -12,31 +12,31 @@ import {
stopLoadingIndicator,
succeedLoadingIndicator,
failLoadingIndicator,
displayAiUsageSummary,
} from "../ui.js";
import { readJSON, writeJSON, log as consoleLog, truncate } from "../utils.js";
import { generateObjectService } from "../ai-services-unified.js";
import { getDefaultPriority } from "../config-manager.js";
import generateTaskFiles from "./generate-task-files.js";
displayAiUsageSummary
} from '../ui.js';
import { readJSON, writeJSON, log as consoleLog, truncate } from '../utils.js';
import { generateObjectService } from '../ai-services-unified.js';
import { getDefaultPriority } from '../config-manager.js';
import generateTaskFiles from './generate-task-files.js';
// Define Zod schema for the expected AI output object
const AiTaskDataSchema = z.object({
title: z.string().describe("Clear, concise title for the task"),
title: z.string().describe('Clear, concise title for the task'),
description: z
.string()
.describe("A one or two sentence description of the task"),
.describe('A one or two sentence description of the task'),
details: z
.string()
.describe("In-depth implementation details, considerations, and guidance"),
.describe('In-depth implementation details, considerations, and guidance'),
testStrategy: z
.string()
.describe("Detailed approach for verifying task completion"),
.describe('Detailed approach for verifying task completion'),
dependencies: z
.array(z.number())
.optional()
.describe(
"Array of task IDs that this task depends on (must be completed before this task can start)"
),
'Array of task IDs that this task depends on (must be completed before this task can start)'
)
});
/**
@@ -64,7 +64,7 @@ async function addTask(
dependencies = [],
priority = null,
context = {},
outputFormat = "text", // Default to text for CLI
outputFormat = 'text', // Default to text for CLI
manualTaskData = null,
useResearch = false
) {
@@ -76,27 +76,27 @@ async function addTask(
? mcpLog // Use MCP logger if provided
: {
// Create a wrapper around consoleLog for CLI
info: (...args) => consoleLog("info", ...args),
warn: (...args) => consoleLog("warn", ...args),
error: (...args) => consoleLog("error", ...args),
debug: (...args) => consoleLog("debug", ...args),
success: (...args) => consoleLog("success", ...args),
info: (...args) => consoleLog('info', ...args),
warn: (...args) => consoleLog('warn', ...args),
error: (...args) => consoleLog('error', ...args),
debug: (...args) => consoleLog('debug', ...args),
success: (...args) => consoleLog('success', ...args)
};
const effectivePriority = priority || getDefaultPriority(projectRoot);
logFn.info(
`Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(", ") || "None"}, Research: ${useResearch}, ProjectRoot: ${projectRoot}`
`Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}`
);
let loadingIndicator = null;
let aiServiceResponse = null; // To store the full response from AI service
// Create custom reporter that checks for MCP log
const report = (message, level = "info") => {
const report = (message, level = 'info') => {
if (mcpLog) {
mcpLog[level](message);
} else if (outputFormat === "text") {
} else if (outputFormat === 'text') {
consoleLog(level, message);
}
};
@@ -158,7 +158,7 @@ async function addTask(
title: task.title,
description: task.description,
status: task.status,
dependencies: dependencyData,
dependencies: dependencyData
};
}
@@ -168,14 +168,14 @@ async function addTask(
// If tasks.json doesn't exist or is invalid, create a new one
if (!data || !data.tasks) {
report("tasks.json not found or invalid. Creating a new one.", "info");
report('tasks.json not found or invalid. Creating a new one.', 'info');
// Create default tasks data structure
data = {
tasks: [],
tasks: []
};
// Ensure the directory exists and write the new file
writeJSON(tasksPath, data);
report("Created new tasks.json file with empty tasks array.", "info");
report('Created new tasks.json file with empty tasks array.', 'info');
}
// Find the highest task ID to determine the next ID
@@ -184,13 +184,13 @@ async function addTask(
const newTaskId = highestId + 1;
// Only show UI box for CLI mode
if (outputFormat === "text") {
if (outputFormat === 'text') {
console.log(
boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), {
padding: 1,
borderColor: "blue",
borderStyle: "round",
margin: { top: 1, bottom: 1 },
borderColor: 'blue',
borderStyle: 'round',
margin: { top: 1, bottom: 1 }
})
);
}
@@ -204,10 +204,10 @@ async function addTask(
if (invalidDeps.length > 0) {
report(
`The following dependencies do not exist or are invalid: ${invalidDeps.join(", ")}`,
"warn"
`The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`,
'warn'
);
report("Removing invalid dependencies...", "info");
report('Removing invalid dependencies...', 'info');
dependencies = dependencies.filter(
(depId) => !invalidDeps.includes(depId)
);
@@ -242,28 +242,28 @@ async function addTask(
// Check if manual task data is provided
if (manualTaskData) {
report("Using manually provided task data", "info");
report('Using manually provided task data', 'info');
taskData = manualTaskData;
report("DEBUG: Taking MANUAL task data path.", "debug");
report('DEBUG: Taking MANUAL task data path.', 'debug');
// Basic validation for manual data
if (
!taskData.title ||
typeof taskData.title !== "string" ||
typeof taskData.title !== 'string' ||
!taskData.description ||
typeof taskData.description !== "string"
typeof taskData.description !== 'string'
) {
throw new Error(
"Manual task data must include at least a title and description."
'Manual task data must include at least a title and description.'
);
}
} else {
report("DEBUG: Taking AI task generation path.", "debug");
report('DEBUG: Taking AI task generation path.', 'debug');
// --- Refactored AI Interaction ---
report(`Generating task data with AI with prompt:\n${prompt}`, "info");
report(`Generating task data with AI with prompt:\n${prompt}`, 'info');
// Create context string for task creation prompt
let contextTasks = "";
let contextTasks = '';
// Create a dependency map for better understanding of the task relationships
const taskMap = {};
@@ -274,18 +274,18 @@ async function addTask(
title: t.title,
description: t.description,
dependencies: t.dependencies || [],
status: t.status,
status: t.status
};
});
// CLI-only feedback for the dependency analysis
if (outputFormat === "text") {
if (outputFormat === 'text') {
console.log(
boxen(chalk.cyan.bold("Task Context Analysis"), {
boxen(chalk.cyan.bold('Task Context Analysis'), {
padding: { top: 0, bottom: 0, left: 1, right: 1 },
margin: { top: 0, bottom: 0 },
borderColor: "cyan",
borderStyle: "round",
borderColor: 'cyan',
borderStyle: 'round'
})
);
}
@@ -316,7 +316,7 @@ async function addTask(
const directDeps = data.tasks.filter((t) =>
numericDependencies.includes(t.id)
);
contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join("\n")}`;
contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`;
// Add an overview of indirect dependencies if present
const indirectDeps = dependentTasks.filter(
@@ -327,7 +327,7 @@ async function addTask(
contextTasks += `\n${indirectDeps
.slice(0, 5)
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
.join("\n")}`;
.join('\n')}`;
if (indirectDeps.length > 5) {
contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`;
}
@@ -338,15 +338,15 @@ async function addTask(
for (const depTask of uniqueDetailedTasks) {
const depthInfo = depthMap.get(depTask.id)
? ` (depth: ${depthMap.get(depTask.id)})`
: "";
: '';
const isDirect = numericDependencies.includes(depTask.id)
? " [DIRECT DEPENDENCY]"
: "";
? ' [DIRECT DEPENDENCY]'
: '';
contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`;
contextTasks += `Description: ${depTask.description}\n`;
contextTasks += `Status: ${depTask.status || "pending"}\n`;
contextTasks += `Priority: ${depTask.priority || "medium"}\n`;
contextTasks += `Status: ${depTask.status || 'pending'}\n`;
contextTasks += `Priority: ${depTask.priority || 'medium'}\n`;
// List its dependencies
if (depTask.dependencies && depTask.dependencies.length > 0) {
@@ -356,7 +356,7 @@ async function addTask(
? `Task ${dId}: ${depDepTask.title}`
: `Task ${dId}`;
});
contextTasks += `Dependencies: ${depDeps.join(", ")}\n`;
contextTasks += `Dependencies: ${depDeps.join(', ')}\n`;
} else {
contextTasks += `Dependencies: None\n`;
}
@@ -365,7 +365,7 @@ async function addTask(
if (depTask.details) {
const truncatedDetails =
depTask.details.length > 400
? depTask.details.substring(0, 400) + "... (truncated)"
? depTask.details.substring(0, 400) + '... (truncated)'
: depTask.details;
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
}
@@ -373,19 +373,19 @@ async function addTask(
// Add dependency chain visualization
if (dependencyGraphs.length > 0) {
contextTasks += "\n\nDependency Chain Visualization:";
contextTasks += '\n\nDependency Chain Visualization:';
// Helper function to format dependency chain as text
function formatDependencyChain(
node,
prefix = "",
prefix = '',
isLast = true,
depth = 0
) {
if (depth > 3) return ""; // Limit depth to avoid excessive nesting
if (depth > 3) return ''; // Limit depth to avoid excessive nesting
const connector = isLast ? "└── " : "├── ";
const childPrefix = isLast ? " " : "";
const connector = isLast ? '└── ' : '├── ';
const childPrefix = isLast ? ' ' : '';
let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`;
@@ -411,7 +411,7 @@ async function addTask(
}
// Show dependency analysis in CLI mode
if (outputFormat === "text") {
if (outputFormat === 'text') {
if (directDeps.length > 0) {
console.log(chalk.gray(` Explicitly specified dependencies:`));
directDeps.forEach((t) => {
@@ -451,14 +451,14 @@ async function addTask(
// Convert dependency graph to ASCII art for terminal
function visualizeDependencyGraph(
node,
prefix = "",
prefix = '',
isLast = true,
depth = 0
) {
if (depth > 2) return; // Limit depth for display
const connector = isLast ? "└── " : "├── ";
const childPrefix = isLast ? " " : "";
const connector = isLast ? '└── ' : '├── ';
const childPrefix = isLast ? ' ' : '';
console.log(
chalk.blue(
@@ -494,18 +494,18 @@ async function addTask(
includeScore: true, // Return match scores
threshold: 0.4, // Lower threshold = stricter matching (range 0-1)
keys: [
{ name: "title", weight: 1.5 }, // Title is most important
{ name: "description", weight: 2 }, // Description is very important
{ name: "details", weight: 3 }, // Details is most important
{ name: 'title', weight: 1.5 }, // Title is most important
{ name: 'description', weight: 2 }, // Description is very important
{ name: 'details', weight: 3 }, // Details is most important
// Search dependencies to find tasks that depend on similar things
{ name: "dependencyTitles", weight: 0.5 },
{ name: 'dependencyTitles', weight: 0.5 }
],
// Sort matches by score (lower is better)
shouldSort: true,
// Allow searching in nested properties
useExtendedSearch: true,
// Return up to 50 matches
limit: 50,
limit: 50
};
// Prepare task data with dependencies expanded as titles for better semantic search
@@ -516,15 +516,15 @@ async function addTask(
? task.dependencies
.map((depId) => {
const depTask = data.tasks.find((t) => t.id === depId);
return depTask ? depTask.title : "";
return depTask ? depTask.title : '';
})
.filter((title) => title)
.join(" ")
: "";
.join(' ')
: '';
return {
...task,
dependencyTitles,
dependencyTitles
};
});
@@ -534,7 +534,7 @@ async function addTask(
// Extract significant words and phrases from the prompt
const promptWords = prompt
.toLowerCase()
.replace(/[^\w\s-]/g, " ") // Replace non-alphanumeric chars with spaces
.replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces
.split(/\s+/)
.filter((word) => word.length > 3); // Words at least 4 chars
@@ -602,21 +602,21 @@ async function addTask(
if (relatedTasks.length > 0) {
contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks
.map((t, i) => {
const relevanceMarker = i < highRelevance.length ? "" : "";
const relevanceMarker = i < highRelevance.length ? '' : '';
return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`;
})
.join("\n")}`;
.join('\n')}`;
}
if (
recentTasks.length > 0 &&
!contextTasks.includes("Recently created tasks")
!contextTasks.includes('Recently created tasks')
) {
contextTasks += `\n\nRecently created tasks:\n${recentTasks
.filter((t) => !relatedTasks.some((rt) => rt.id === t.id))
.slice(0, 3)
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
.join("\n")}`;
.join('\n')}`;
}
// Add detailed information about the most relevant tasks
@@ -630,8 +630,8 @@ async function addTask(
for (const task of uniqueDetailedTasks) {
contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`;
contextTasks += `Description: ${task.description}\n`;
contextTasks += `Status: ${task.status || "pending"}\n`;
contextTasks += `Priority: ${task.priority || "medium"}\n`;
contextTasks += `Status: ${task.status || 'pending'}\n`;
contextTasks += `Priority: ${task.priority || 'medium'}\n`;
if (task.dependencies && task.dependencies.length > 0) {
// Format dependency list with titles
const depList = task.dependencies.map((depId) => {
@@ -640,13 +640,13 @@ async function addTask(
? `Task ${depId} (${depTask.title})`
: `Task ${depId}`;
});
contextTasks += `Dependencies: ${depList.join(", ")}\n`;
contextTasks += `Dependencies: ${depList.join(', ')}\n`;
}
// Add implementation details but truncate if too long
if (task.details) {
const truncatedDetails =
task.details.length > 400
? task.details.substring(0, 400) + "... (truncated)"
? task.details.substring(0, 400) + '... (truncated)'
: task.details;
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
}
@@ -654,7 +654,7 @@ async function addTask(
}
// Add a concise view of the task dependency structure
contextTasks += "\n\nSummary of task dependencies in the project:";
contextTasks += '\n\nSummary of task dependencies in the project:';
// Get pending/in-progress tasks that might be most relevant based on fuzzy search
// Prioritize tasks from our similarity search
@@ -662,7 +662,7 @@ async function addTask(
const relevantPendingTasks = data.tasks
.filter(
(t) =>
(t.status === "pending" || t.status === "in-progress") &&
(t.status === 'pending' || t.status === 'in-progress') &&
// Either in our relevant set OR has relevant words in title/description
(relevantTaskIds.has(t.id) ||
promptWords.some(
@@ -676,8 +676,8 @@ async function addTask(
for (const task of relevantPendingTasks) {
const depsStr =
task.dependencies && task.dependencies.length > 0
? task.dependencies.join(", ")
: "None";
? task.dependencies.join(', ')
: 'None';
contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`;
}
@@ -709,7 +709,7 @@ async function addTask(
.slice(0, 10);
if (commonDeps.length > 0) {
contextTasks += "\nMost common dependencies for similar tasks:";
contextTasks += '\nMost common dependencies for similar tasks:';
commonDeps.forEach(([depId, count]) => {
const depTask = data.tasks.find((t) => t.id === parseInt(depId));
if (depTask) {
@@ -720,7 +720,7 @@ async function addTask(
}
// Show fuzzy search analysis in CLI mode
if (outputFormat === "text") {
if (outputFormat === 'text') {
console.log(
chalk.gray(
` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords`
@@ -777,7 +777,7 @@ async function addTask(
const isHighRelevance = highRelevance.some(
(ht) => ht.id === t.id
);
const relevanceIndicator = isHighRelevance ? "" : "";
const relevanceIndicator = isHighRelevance ? '' : '';
console.log(
chalk.cyan(
`${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}`
@@ -805,14 +805,14 @@ async function addTask(
}
// Add a visual transition to show we're moving to AI generation - only for CLI
if (outputFormat === "text") {
if (outputFormat === 'text') {
console.log(
boxen(
chalk.white.bold("AI Task Generation") +
`\n\n${chalk.gray("Analyzing context and generating task details using AI...")}` +
`\n${chalk.cyan("Context size: ")}${chalk.yellow(contextTasks.length.toLocaleString())} characters` +
`\n${chalk.cyan("Dependency detection: ")}${chalk.yellow(numericDependencies.length > 0 ? "Explicit dependencies" : "Auto-discovery mode")}` +
`\n${chalk.cyan("Detailed tasks: ")}${chalk.yellow(
chalk.white.bold('AI Task Generation') +
`\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` +
`\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` +
`\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` +
`\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow(
numericDependencies.length > 0
? dependentTasks.length // Use length of tasks from explicit dependency path
: uniqueDetailedTasks.length // Use length of tasks from fuzzy search path
@@ -820,8 +820,8 @@ async function addTask(
{
padding: { top: 0, bottom: 1, left: 1, right: 1 },
margin: { top: 1, bottom: 0 },
borderColor: "white",
borderStyle: "round",
borderColor: 'white',
borderStyle: 'round'
}
)
);
@@ -831,15 +831,15 @@ async function addTask(
// System Prompt - Enhanced for dependency awareness
const systemPrompt =
"You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" +
"When determining dependencies for a new task, follow these principles:\n" +
"1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n" +
"2. Prioritize task dependencies that are semantically related to the functionality being built.\n" +
"3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n" +
"4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n" +
"5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n" +
'When determining dependencies for a new task, follow these principles:\n' +
'1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' +
'2. Prioritize task dependencies that are semantically related to the functionality being built.\n' +
'3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n' +
'4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n' +
'5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n' +
"6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" +
"7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n" +
"The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n";
'7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n' +
'The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n';
// Task Structure Description (for user prompt)
const taskStructureDesc = `
@@ -853,7 +853,7 @@ async function addTask(
`;
// Add any manually provided details to the prompt for context
let contextFromArgs = "";
let contextFromArgs = '';
if (manualTaskData?.title)
contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`;
if (manualTaskData?.description)
@@ -867,7 +867,7 @@ async function addTask(
const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project.
${contextTasks}
${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ""}
${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''}
Based on the information about existing tasks provided above, include appropriate dependencies in the "dependencies" array. Only include task IDs that this new task directly depends on.
@@ -878,15 +878,15 @@ async function addTask(
`;
// Start the loading indicator - only for text mode
if (outputFormat === "text") {
if (outputFormat === 'text') {
loadingIndicator = startLoadingIndicator(
`Generating new task with ${useResearch ? "Research" : "Main"} AI... \n`
`Generating new task with ${useResearch ? 'Research' : 'Main'} AI... \n`
);
}
try {
const serviceRole = useResearch ? "research" : "main";
report("DEBUG: Calling generateObjectService...", "debug");
const serviceRole = useResearch ? 'research' : 'main';
report('DEBUG: Calling generateObjectService...', 'debug');
aiServiceResponse = await generateObjectService({
// Capture the full response
@@ -894,17 +894,17 @@ async function addTask(
session: session,
projectRoot: projectRoot,
schema: AiTaskDataSchema,
objectName: "newTaskData",
objectName: 'newTaskData',
systemPrompt: systemPrompt,
prompt: userPrompt,
commandName: commandName || "add-task", // Use passed commandName or default
outputType: outputType || (isMCP ? "mcp" : "cli"), // Use passed outputType or derive
commandName: commandName || 'add-task', // Use passed commandName or default
outputType: outputType || (isMCP ? 'mcp' : 'cli') // Use passed outputType or derive
});
report("DEBUG: generateObjectService returned successfully.", "debug");
report('DEBUG: generateObjectService returned successfully.', 'debug');
if (!aiServiceResponse || !aiServiceResponse.mainResult) {
throw new Error(
"AI service did not return the expected object structure."
'AI service did not return the expected object structure.'
);
}
@@ -921,33 +921,33 @@ async function addTask(
) {
taskData = aiServiceResponse.mainResult.object;
} else {
throw new Error("AI service did not return a valid task object.");
throw new Error('AI service did not return a valid task object.');
}
report("Successfully generated task data from AI.", "success");
report('Successfully generated task data from AI.', 'success');
// Success! Show checkmark
if (loadingIndicator) {
succeedLoadingIndicator(
loadingIndicator,
"Task generated successfully"
'Task generated successfully'
);
loadingIndicator = null; // Clear it
}
} catch (error) {
// Failure! Show X
if (loadingIndicator) {
failLoadingIndicator(loadingIndicator, "AI generation failed");
failLoadingIndicator(loadingIndicator, 'AI generation failed');
loadingIndicator = null;
}
report(
`DEBUG: generateObjectService caught error: ${error.message}`,
"debug"
'debug'
);
report(`Error generating task with AI: ${error.message}`, "error");
report(`Error generating task with AI: ${error.message}`, 'error');
throw error; // Re-throw error after logging
} finally {
report("DEBUG: generateObjectService finally block reached.", "debug");
report('DEBUG: generateObjectService finally block reached.', 'debug');
// Clean up if somehow still running
if (loadingIndicator) {
stopLoadingIndicator(loadingIndicator);
@@ -961,14 +961,14 @@ async function addTask(
id: newTaskId,
title: taskData.title,
description: taskData.description,
details: taskData.details || "",
testStrategy: taskData.testStrategy || "",
status: "pending",
details: taskData.details || '',
testStrategy: taskData.testStrategy || '',
status: 'pending',
dependencies: taskData.dependencies?.length
? taskData.dependencies
: numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified
priority: effectivePriority,
subtasks: [], // Initialize with empty subtasks array
subtasks: [] // Initialize with empty subtasks array
};
// Additional check: validate all dependencies in the AI response
@@ -980,8 +980,8 @@ async function addTask(
if (!allValidDeps) {
report(
"AI suggested invalid dependencies. Filtering them out...",
"warn"
'AI suggested invalid dependencies. Filtering them out...',
'warn'
);
newTask.dependencies = taskData.dependencies.filter((depId) => {
const numDepId = parseInt(depId, 10);
@@ -993,48 +993,48 @@ async function addTask(
// Add the task to the tasks array
data.tasks.push(newTask);
report("DEBUG: Writing tasks.json...", "debug");
report('DEBUG: Writing tasks.json...', 'debug');
// Write the updated tasks to the file
writeJSON(tasksPath, data);
report("DEBUG: tasks.json written.", "debug");
report('DEBUG: tasks.json written.', 'debug');
// Generate markdown task files
report("Generating task files...", "info");
report("DEBUG: Calling generateTaskFiles...", "debug");
report('Generating task files...', 'info');
report('DEBUG: Calling generateTaskFiles...', 'debug');
// Pass mcpLog if available to generateTaskFiles
await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog });
report("DEBUG: generateTaskFiles finished.", "debug");
report('DEBUG: generateTaskFiles finished.', 'debug');
// Show success message - only for text output (CLI)
if (outputFormat === "text") {
if (outputFormat === 'text') {
const table = new Table({
head: [
chalk.cyan.bold("ID"),
chalk.cyan.bold("Title"),
chalk.cyan.bold("Description"),
chalk.cyan.bold('ID'),
chalk.cyan.bold('Title'),
chalk.cyan.bold('Description')
],
colWidths: [5, 30, 50], // Adjust widths as needed
colWidths: [5, 30, 50] // Adjust widths as needed
});
table.push([
newTask.id,
truncate(newTask.title, 27),
truncate(newTask.description, 47),
truncate(newTask.description, 47)
]);
console.log(chalk.green("✓ New task created successfully:"));
console.log(chalk.green('✓ New task created successfully:'));
console.log(table.toString());
// Helper to get priority color
const getPriorityColor = (p) => {
switch (p?.toLowerCase()) {
case "high":
return "red";
case "low":
return "gray";
case "medium":
case 'high':
return 'red';
case 'low':
return 'gray';
case 'medium':
default:
return "yellow";
return 'yellow';
}
};
@@ -1058,49 +1058,49 @@ async function addTask(
});
// Prepare dependency display string
let dependencyDisplay = "";
let dependencyDisplay = '';
if (newTask.dependencies.length > 0) {
dependencyDisplay = chalk.white("Dependencies:") + "\n";
dependencyDisplay = chalk.white('Dependencies:') + '\n';
newTask.dependencies.forEach((dep) => {
const isAiAdded = aiAddedDeps.includes(dep);
const depType = isAiAdded ? chalk.yellow(" (AI suggested)") : "";
const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : '';
dependencyDisplay +=
chalk.white(
` - ${dep}: ${depTitles[dep] || "Unknown task"}${depType}`
) + "\n";
` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}`
) + '\n';
});
} else {
dependencyDisplay = chalk.white("Dependencies: None") + "\n";
dependencyDisplay = chalk.white('Dependencies: None') + '\n';
}
// Add info about removed dependencies if any
if (aiRemovedDeps.length > 0) {
dependencyDisplay +=
chalk.gray("\nUser-specified dependencies that were not used:") +
"\n";
chalk.gray('\nUser-specified dependencies that were not used:') +
'\n';
aiRemovedDeps.forEach((dep) => {
const depTask = data.tasks.find((t) => t.id === dep);
const title = depTask ? truncate(depTask.title, 30) : "Unknown task";
dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + "\n";
const title = depTask ? truncate(depTask.title, 30) : 'Unknown task';
dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n';
});
}
// Add dependency analysis summary
let dependencyAnalysis = "";
let dependencyAnalysis = '';
if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) {
dependencyAnalysis =
"\n" + chalk.white.bold("Dependency Analysis:") + "\n";
'\n' + chalk.white.bold('Dependency Analysis:') + '\n';
if (aiAddedDeps.length > 0) {
dependencyAnalysis +=
chalk.green(
`AI identified ${aiAddedDeps.length} additional dependencies`
) + "\n";
) + '\n';
}
if (aiRemovedDeps.length > 0) {
dependencyAnalysis +=
chalk.yellow(
`AI excluded ${aiRemovedDeps.length} user-provided dependencies`
) + "\n";
) + '\n';
}
}
@@ -1108,32 +1108,32 @@ async function addTask(
console.log(
boxen(
chalk.white.bold(`Task ${newTaskId} Created Successfully`) +
"\n\n" +
'\n\n' +
chalk.white(`Title: ${newTask.title}`) +
"\n" +
'\n' +
chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) +
"\n" +
'\n' +
chalk.white(
`Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}`
) +
"\n\n" +
'\n\n' +
dependencyDisplay +
dependencyAnalysis +
"\n" +
chalk.white.bold("Next Steps:") +
"\n" +
'\n' +
chalk.white.bold('Next Steps:') +
'\n' +
chalk.cyan(
`1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details`
) +
"\n" +
'\n' +
chalk.cyan(
`2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it`
) +
"\n" +
'\n' +
chalk.cyan(
`3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks`
),
{ padding: 1, borderColor: "green", borderStyle: "round" }
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
)
);
@@ -1141,19 +1141,19 @@ async function addTask(
if (
aiServiceResponse &&
aiServiceResponse.telemetryData &&
(outputType === "cli" || outputType === "text")
(outputType === 'cli' || outputType === 'text')
) {
displayAiUsageSummary(aiServiceResponse.telemetryData, "cli");
displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
}
}
report(
`DEBUG: Returning new task ID: ${newTaskId} and telemetry.`,
"debug"
'debug'
);
return {
newTaskId: newTaskId,
telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null,
telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null
};
} catch (error) {
// Stop any loading indicator on error
@@ -1161,8 +1161,8 @@ async function addTask(
stopLoadingIndicator(loadingIndicator);
}
report(`Error adding task: ${error.message}`, "error");
if (outputFormat === "text") {
report(`Error adding task: ${error.message}`, 'error');
if (outputFormat === 'text') {
console.error(chalk.red(`Error: ${error.message}`));
}
// In MCP mode, we let the direct function handler catch and format