chore: changeset + update rules.

This commit is contained in:
Eyal Toledano
2025-04-03 04:09:27 -04:00
parent 1582fe32c1
commit bad16b200f
54 changed files with 1462 additions and 316 deletions

View File

@@ -29,6 +29,33 @@
- Include new sections about handling dependencies during task removal operations - Include new sections about handling dependencies during task removal operations
- Document naming conventions and implementation patterns for destructive operations - Document naming conventions and implementation patterns for destructive operations
- **Implement silent mode across all direct functions:**
- Add `enableSilentMode` and `disableSilentMode` utility imports to all direct function files
- Wrap all core function calls with silent mode to prevent console logs from interfering with JSON responses
- Add comprehensive error handling to ensure silent mode is disabled even when errors occur
- Fix "Unexpected token 'I', "[INFO] Gene"... is not valid JSON" errors by suppressing log output
- Apply consistent silent mode pattern across all MCP direct functions
- Maintain clean JSON responses for better integration with client tools
- **Implement AsyncOperationManager for background task processing:**
- Add new `async-manager.js` module to handle long-running operations asynchronously
- Support background execution of computationally intensive tasks like expansion and analysis
- Implement unique operation IDs with UUID generation for reliable tracking
- Add operation status tracking (pending, running, completed, failed)
- Create `get_operation_status` MCP tool to check on background task progress
- Forward progress reporting from background tasks to the client
- Implement operation history with automatic cleanup of completed operations
- Support proper error handling in background tasks with detailed status reporting
- Maintain context (log, session) for background operations ensuring consistent behavior
- **Implement initialize_project command:**
- Add new MCP tool to allow project setup via integrated MCP clients
- Create `initialize_project` direct function with proper parameter handling
- Improve onboarding experience by adding to mcp.json configuration
- Support project-specific metadata like name, description, and version
- Handle shell alias creation with proper confirmation
- Improve first-time user experience in AI environments
- **Refactor project root handling for MCP Server:** - **Refactor project root handling for MCP Server:**
- **Prioritize Session Roots**: MCP tools now extract the project root path directly from `session.roots[0].uri` provided by the client (e.g., Cursor). - **Prioritize Session Roots**: MCP tools now extract the project root path directly from `session.roots[0].uri` provided by the client (e.g., Cursor).
- **New Utility `getProjectRootFromSession`**: Added to `mcp-server/src/tools/utils.js` to encapsulate session root extraction and decoding. **Further refined for more reliable detection, especially in integrated environments, including deriving root from script path and avoiding fallback to '/'.** - **New Utility `getProjectRootFromSession`**: Added to `mcp-server/src/tools/utils.js` to encapsulate session root extraction and decoding. **Further refined for more reliable detection, especially in integrated environments, including deriving root from script path and avoiding fallback to '/'.**

View File

@@ -93,6 +93,7 @@ alwaysApply: false
- Includes string manipulation utilities (e.g., `truncate`, `sanitizePrompt`). - Includes string manipulation utilities (e.g., `truncate`, `sanitizePrompt`).
- Offers task-specific utility functions (e.g., `formatTaskId`, `findTaskById`, `taskExists`). - Offers task-specific utility functions (e.g., `formatTaskId`, `findTaskById`, `taskExists`).
- Implements graph algorithms like cycle detection for dependency management. - Implements graph algorithms like cycle detection for dependency management.
- **Silent Mode Control**: Provides `enableSilentMode` and `disableSilentMode` functions to control log output.
- **Key Components**: - **Key Components**:
- `CONFIG`: Global configuration object. - `CONFIG`: Global configuration object.
- `log(level, ...args)`: Logging function. - `log(level, ...args)`: Logging function.
@@ -100,6 +101,7 @@ alwaysApply: false
- `truncate(text, maxLength)`: String truncation utility. - `truncate(text, maxLength)`: String truncation utility.
- `formatTaskId(id)` / `findTaskById(tasks, taskId)`: Task ID and search utilities. - `formatTaskId(id)` / `findTaskById(tasks, taskId)`: Task ID and search utilities.
- `findCycles(subtaskId, dependencyMap)`: Cycle detection algorithm. - `findCycles(subtaskId, dependencyMap)`: Cycle detection algorithm.
- `enableSilentMode()` / `disableSilentMode()`: Control console logging output.
- **[`mcp-server/`](mdc:mcp-server/): MCP Server Integration** - **[`mcp-server/`](mdc:mcp-server/): MCP Server Integration**
- **Purpose**: Provides an MCP (Model Context Protocol) interface for Task Master, allowing integration with external tools like Cursor. Uses FastMCP framework. - **Purpose**: Provides an MCP (Model Context Protocol) interface for Task Master, allowing integration with external tools like Cursor. Uses FastMCP framework.
@@ -110,6 +112,9 @@ alwaysApply: false
- Tool `execute` methods use `getProjectRootFromSession` (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) to determine the project root from the client session and pass it to the direct function. - Tool `execute` methods use `getProjectRootFromSession` (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) to determine the project root from the client session and pass it to the direct function.
- **Direct function wrappers (`*Direct` functions in `mcp-server/src/core/direct-functions/*.js`) contain the main logic for handling MCP requests**, including path resolution, argument validation, caching, and calling core Task Master functions. - **Direct function wrappers (`*Direct` functions in `mcp-server/src/core/direct-functions/*.js`) contain the main logic for handling MCP requests**, including path resolution, argument validation, caching, and calling core Task Master functions.
- Direct functions use `findTasksJsonPath` (from [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js)) to locate `tasks.json` based on the provided `projectRoot`. - Direct functions use `findTasksJsonPath` (from [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js)) to locate `tasks.json` based on the provided `projectRoot`.
- **Silent Mode Implementation**: Direct functions use `enableSilentMode` and `disableSilentMode` to prevent logs from interfering with JSON responses.
- **Async Operations**: Uses `AsyncOperationManager` to handle long-running operations in the background.
- **Project Initialization**: Provides `initialize_project` command for setting up new projects from within integrated clients.
- Tool `execute` methods use `handleApiResult` from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) to process the result from the direct function and format the final MCP response. - Tool `execute` methods use `handleApiResult` from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) to process the result from the direct function and format the final MCP response.
- Uses CLI execution via `executeTaskMasterCommand` as a fallback only when necessary. - Uses CLI execution via `executeTaskMasterCommand` as a fallback only when necessary.
- **Implements Robust Path Finding**: The utility [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) (specifically `getProjectRootFromSession`) and [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js) (specifically `findTasksJsonPath`) work together. The tool gets the root via session, passes it to the direct function, which uses `findTasksJsonPath` to locate the specific `tasks.json` file within that root. - **Implements Robust Path Finding**: The utility [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) (specifically `getProjectRootFromSession`) and [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js) (specifically `findTasksJsonPath`) work together. The tool gets the root via session, passes it to the direct function, which uses `findTasksJsonPath` to locate the specific `tasks.json` file within that root.
@@ -121,7 +126,7 @@ alwaysApply: false
- `mcp-server/src/server.js`: Main server setup and initialization. - `mcp-server/src/server.js`: Main server setup and initialization.
- `mcp-server/src/tools/`: Directory containing individual tool definitions. Each tool's `execute` method orchestrates the call to core logic and handles the response. - `mcp-server/src/tools/`: Directory containing individual tool definitions. Each tool's `execute` method orchestrates the call to core logic and handles the response.
- `mcp-server/src/tools/utils.js`: Provides MCP-specific utilities like `handleApiResult`, `processMCPResponseData`, `getCachedOrExecute`, and **`getProjectRootFromSession`**. - `mcp-server/src/tools/utils.js`: Provides MCP-specific utilities like `handleApiResult`, `processMCPResponseData`, `getCachedOrExecute`, and **`getProjectRootFromSession`**.
- `mcp-server/src/core/utils/`: Directory containing utility functions specific to the MCP server, like **`path-utils.js` for resolving `tasks.json` within a given root**. - `mcp-server/src/core/utils/`: Directory containing utility functions specific to the MCP server, like **`path-utils.js` for resolving `tasks.json` within a given root** and **`async-manager.js` for handling background operations**.
- `mcp-server/src/core/direct-functions/`: Directory containing individual files for each **direct function wrapper (`*Direct`)**. These files contain the primary logic for MCP tool execution. - `mcp-server/src/core/direct-functions/`: Directory containing individual files for each **direct function wrapper (`*Direct`)**. These files contain the primary logic for MCP tool execution.
- `mcp-server/src/core/resources/`: Directory containing resource handlers for task templates, workflow definitions, and other static/dynamic data exposed to LLM clients. - `mcp-server/src/core/resources/`: Directory containing resource handlers for task templates, workflow definitions, and other static/dynamic data exposed to LLM clients.
- [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js): Acts as an import/export hub, collecting and exporting direct functions from the `direct-functions` directory and MCP utility functions. - [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js): Acts as an import/export hub, collecting and exporting direct functions from the `direct-functions` directory and MCP utility functions.
@@ -131,6 +136,17 @@ alwaysApply: false
- **Tool Registration Functions** use **camelCase** with `Tool` suffix: `registerListTasksTool`, `registerSetTaskStatusTool` - **Tool Registration Functions** use **camelCase** with `Tool` suffix: `registerListTasksTool`, `registerSetTaskStatusTool`
- **MCP Tool Names** use **snake_case**: `list_tasks`, `set_task_status`, `parse_prd_document` - **MCP Tool Names** use **snake_case**: `list_tasks`, `set_task_status`, `parse_prd_document`
- **Resource Handlers** use **camelCase** with pattern URI: `@mcp.resource("tasks://templates/{template_id}")` - **Resource Handlers** use **camelCase** with pattern URI: `@mcp.resource("tasks://templates/{template_id}")`
- **AsyncOperationManager**:
- **Purpose**: Manages background execution of long-running operations.
- **Location**: `mcp-server/src/core/utils/async-manager.js`
- **Key Features**:
- Operation tracking with unique IDs using UUID
- Status management (pending, running, completed, failed)
- Progress reporting forwarded from background tasks
- Operation history with automatic cleanup of completed operations
- Context preservation (log, session, reportProgress)
- Robust error handling for background tasks
- **Usage**: Used for CPU-intensive operations like task expansion and PRD parsing
- **Data Flow and Module Dependencies**: - **Data Flow and Module Dependencies**:
@@ -191,10 +207,11 @@ Follow these steps to add MCP support for an existing Task Master command (see [
2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**: 2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**:
- Create a new file (e.g., `your-command.js`) using **kebab-case** naming. - Create a new file (e.g., `your-command.js`) using **kebab-case** naming.
- Import necessary core functions and **`findTasksJsonPath` from `../utils/path-utils.js`**. - Import necessary core functions, **`findTasksJsonPath` from `../utils/path-utils.js`**, and **silent mode utilities**.
- Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix: - Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix:
- **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This relies on `args.projectRoot` being provided. - **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This relies on `args.projectRoot` being provided.
- Parse other `args` and perform necessary validation. - Parse other `args` and perform necessary validation.
- **Implement Silent Mode**: Wrap core function calls with `enableSilentMode()` and `disableSilentMode()`.
- Implement caching with `getCachedOrExecute` if applicable. - Implement caching with `getCachedOrExecute` if applicable.
- Call core logic. - Call core logic.
- Return `{ success: true/false, data/error, fromCache: boolean }`. - Return `{ success: true/false, data/error, fromCache: boolean }`.
@@ -207,11 +224,41 @@ Follow these steps to add MCP support for an existing Task Master command (see [
- Import `zod`, `handleApiResult`, **`getProjectRootFromSession`**, and your `yourCommandDirect` function. - Import `zod`, `handleApiResult`, **`getProjectRootFromSession`**, and your `yourCommandDirect` function.
- Implement `registerYourCommandTool(server)`. - Implement `registerYourCommandTool(server)`.
- **Define parameters, making `projectRoot` optional**: `projectRoot: z.string().optional().describe(...)`. - **Define parameters, making `projectRoot` optional**: `projectRoot: z.string().optional().describe(...)`.
- Consider if this operation should run in the background using `AsyncOperationManager`.
- Implement the standard `execute` method: - Implement the standard `execute` method:
- Get `rootFolder` using `getProjectRootFromSession` (with fallback to `args.projectRoot`). - Get `rootFolder` using `getProjectRootFromSession` (with fallback to `args.projectRoot`).
- Call `yourCommandDirect({ ...args, projectRoot: rootFolder }, log)`. - Call `yourCommandDirect({ ...args, projectRoot: rootFolder }, log)` or use `asyncOperationManager.addOperation`.
- Pass the result to `handleApiResult`. - Pass the result to `handleApiResult`.
5. **Register Tool**: Import and call `registerYourCommandTool` in `mcp-server/src/tools/index.js`. 5. **Register Tool**: Import and call `registerYourCommandTool` in `mcp-server/src/tools/index.js`.
6. **Update `mcp.json`**: Add the new tool definition. 6. **Update `mcp.json`**: Add the new tool definition.
## Project Initialization
The `initialize_project` command provides a way to set up a new Task Master project:
- **CLI Command**: `task-master init`
- **MCP Tool**: `initialize_project`
- **Functionality**:
- Creates necessary directories and files for a new project
- Sets up `tasks.json` and initial task files
- Configures project metadata (name, description, version)
- Handles shell alias creation if requested
- Works in both interactive and non-interactive modes
## Async Operation Management
The AsyncOperationManager provides background task execution capabilities:
- **Location**: `mcp-server/src/core/utils/async-manager.js`
- **Key Components**:
- `asyncOperationManager` singleton instance
- `addOperation(operationFn, args, context)` method
- `getStatus(operationId)` method
- **Usage Flow**:
1. Client calls an MCP tool that may take time to complete
2. Tool uses AsyncOperationManager to run the operation in background
3. Tool returns immediate response with operation ID
4. Client polls `get_operation_status` tool with the ID
5. Once completed, client can access operation results

View File

@@ -89,6 +89,44 @@ When implementing a new direct function in `mcp-server/src/core/direct-functions
} }
``` ```
5. **Silent Mode Implementation**:
- ✅ **DO**: Import silent mode utilities at the top of your file
```javascript
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
```
- ✅ **DO**: Wrap core function calls with silent mode control
```javascript
// Enable silent mode before the core function call
enableSilentMode();
// Execute core function
const result = await coreFunction(param1, param2);
// Restore normal logging
disableSilentMode();
```
- ✅ **DO**: Add proper error handling to ensure silent mode is disabled
```javascript
try {
enableSilentMode();
// Core function execution
const result = await coreFunction(param1, param2);
disableSilentMode();
return { success: true, data: result };
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in function: ${error.message}`);
return {
success: false,
error: { code: 'ERROR_CODE', message: error.message }
};
}
```
- ❌ **DON'T**: Forget to disable silent mode when errors occur
- ❌ **DON'T**: Leave silent mode enabled outside a direct function's scope
- ❌ **DON'T**: Skip silent mode for core function calls that generate logs
## Tool Definition and Execution ## Tool Definition and Execution
### Tool Structure ### Tool Structure
@@ -174,6 +212,114 @@ execute: async (args, { log, reportProgress, session }) => {
} }
``` ```
### Using AsyncOperationManager for Background Tasks
For tools that execute long-running operations, use the AsyncOperationManager to run them in the background:
```javascript
import { asyncOperationManager } from '../core/utils/async-manager.js';
import { getProjectRootFromSession, createContentResponse, createErrorResponse } from './utils.js';
import { someIntensiveDirect } from '../core/task-master-core.js';
// ... inside server.addTool({...})
execute: async (args, { log, reportProgress, session }) => {
try {
log.info(`Starting background operation with args: ${JSON.stringify(args)}`);
// 1. Get Project Root
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// 2. Add operation to the async manager
const operationId = asyncOperationManager.addOperation(
someIntensiveDirect, // The direct function to execute
{ ...args, projectRoot: rootFolder }, // Args to pass
{ log, reportProgress, session } // Context to preserve
);
// 3. Return immediate response with operation ID
return createContentResponse({
message: "Operation started successfully",
operationId,
status: "pending"
});
} catch (error) {
log.error(`Error starting background operation: ${error.message}`);
return createErrorResponse(error.message);
}
}
```
Clients should then use the `get_operation_status` tool to check on operation progress:
```javascript
// In get-operation-status.js
import { asyncOperationManager } from '../core/utils/async-manager.js';
import { createContentResponse, createErrorResponse } from './utils.js';
// ... inside server.addTool({...})
execute: async (args, { log }) => {
try {
const { operationId } = args;
log.info(`Checking status of operation: ${operationId}`);
const status = asyncOperationManager.getStatus(operationId);
if (status.status === 'not_found') {
return createErrorResponse(status.error.message);
}
return createContentResponse({
...status,
message: `Operation status: ${status.status}`
});
} catch (error) {
log.error(`Error checking operation status: ${error.message}`);
return createErrorResponse(error.message);
}
}
```
### Project Initialization Tool
The `initialize_project` tool allows integrated clients like Cursor to set up a new Task Master project:
```javascript
// In initialize-project.js
import { z } from "zod";
import { initializeProjectDirect } from "../core/task-master-core.js";
import { handleApiResult, createErrorResponse } from "./utils.js";
export function registerInitializeProjectTool(server) {
server.addTool({
name: "initialize_project",
description: "Initialize a new Task Master project",
parameters: z.object({
projectName: z.string().optional().describe("The name for the new project"),
projectDescription: z.string().optional().describe("A brief description"),
projectVersion: z.string().optional().describe("Initial version (e.g., '0.1.0')"),
authorName: z.string().optional().describe("The author's name"),
skipInstall: z.boolean().optional().describe("Skip installing dependencies"),
addAliases: z.boolean().optional().describe("Add shell aliases"),
yes: z.boolean().optional().describe("Skip prompts and use defaults")
}),
execute: async (args, { log, reportProgress }) => {
try {
// Since we're initializing, we don't need project root
const result = await initializeProjectDirect(args, log);
return handleApiResult(result, log, 'Error initializing project');
} catch (error) {
log.error(`Error in initialize_project: ${error.message}`);
return createErrorResponse(`Failed to initialize project: ${error.message}`);
}
}
});
}
```
### Logging Convention ### Logging Convention
The `log` object (destructured from `context`) provides standardized logging methods. Use it within both the `execute` method and the `*Direct` functions. The `log` object (destructured from `context`) provides standardized logging methods. Use it within both the `execute` method and the `*Direct` functions.
@@ -215,6 +361,7 @@ These functions, located in `mcp-server/src/core/direct-functions/`, form the co
- Receive `args` (including the `projectRoot` determined by the tool) and `log` object. - Receive `args` (including the `projectRoot` determined by the tool) and `log` object.
- **Find `tasks.json`**: Use `findTasksJsonPath(args, log)` from [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js). This function prioritizes the provided `args.projectRoot`. - **Find `tasks.json`**: Use `findTasksJsonPath(args, log)` from [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js). This function prioritizes the provided `args.projectRoot`.
- Validate arguments specific to the core logic. - Validate arguments specific to the core logic.
- **Implement Silent Mode**: Import and use `enableSilentMode` and `disableSilentMode` around core function calls.
- **Implement Caching**: Use `getCachedOrExecute` from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) for read operations. - **Implement Caching**: Use `getCachedOrExecute` from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) for read operations.
- Call the underlying function from the core Task Master modules. - Call the underlying function from the core Task Master modules.
- Handle errors gracefully. - Handle errors gracefully.
@@ -225,6 +372,9 @@ These functions, located in `mcp-server/src/core/direct-functions/`, form the co
- **Prefer Direct Function Calls**: MCP tools should always call `*Direct` wrappers instead of `executeTaskMasterCommand`. - **Prefer Direct Function Calls**: MCP tools should always call `*Direct` wrappers instead of `executeTaskMasterCommand`.
- **Standardized Execution Flow**: Follow the pattern: MCP Tool -> `getProjectRootFromSession` -> `*Direct` Function -> Core Logic. - **Standardized Execution Flow**: Follow the pattern: MCP Tool -> `getProjectRootFromSession` -> `*Direct` Function -> Core Logic.
- **Path Resolution via Direct Functions**: The `*Direct` function is responsible for finding the exact `tasks.json` path using `findTasksJsonPath`, relying on the `projectRoot` passed in `args`. - **Path Resolution via Direct Functions**: The `*Direct` function is responsible for finding the exact `tasks.json` path using `findTasksJsonPath`, relying on the `projectRoot` passed in `args`.
- **Silent Mode in Direct Functions**: Wrap all core function calls with `enableSilentMode()` and `disableSilentMode()` to prevent logs from interfering with JSON responses.
- **Async Processing for Intensive Operations**: Use AsyncOperationManager for CPU-intensive or long-running operations.
- **Project Initialization**: Use the initialize_project tool for setting up new projects in integrated environments.
- **Centralized Utilities**: Use helpers from `mcp-server/src/tools/utils.js` (like `handleApiResult`, `getProjectRootFromSession`, `getCachedOrExecute`) and `mcp-server/src/core/utils/path-utils.js` (`findTasksJsonPath`). See [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc). - **Centralized Utilities**: Use helpers from `mcp-server/src/tools/utils.js` (like `handleApiResult`, `getProjectRootFromSession`, `getCachedOrExecute`) and `mcp-server/src/core/utils/path-utils.js` (`findTasksJsonPath`). See [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc).
- **Caching in Direct Functions**: Caching logic resides *within* the `*Direct` functions using `getCachedOrExecute`. - **Caching in Direct Functions**: Caching logic resides *within* the `*Direct` functions using `getCachedOrExecute`.
@@ -246,10 +396,11 @@ Follow these steps to add MCP support for an existing Task Master command (see [
2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**: 2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**:
- Create a new file (e.g., `your-command.js`) using **kebab-case** naming. - Create a new file (e.g., `your-command.js`) using **kebab-case** naming.
- Import necessary core functions and **`findTasksJsonPath` from `../utils/path-utils.js`**. - Import necessary core functions, **`findTasksJsonPath` from `../utils/path-utils.js`**, and **silent mode utilities**.
- Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix: - Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix:
- **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This handles project root detection automatically based on `args.projectRoot`. - **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This handles project root detection automatically based on `args.projectRoot`.
- Parse other `args` and perform necessary validation. - Parse other `args` and perform necessary validation.
- **Implement Silent Mode**: Wrap core function calls with enableSilentMode/disableSilentMode.
- **If Caching**: Implement caching using `getCachedOrExecute` from `../../tools/utils.js`. - **If Caching**: Implement caching using `getCachedOrExecute` from `../../tools/utils.js`.
- **If Not Caching**: Directly call the core logic function within a try/catch block. - **If Not Caching**: Directly call the core logic function within a try/catch block.
- Format the return as `{ success: true/false, data/error, fromCache: boolean }`. - Format the return as `{ success: true/false, data/error, fromCache: boolean }`.
@@ -312,16 +463,18 @@ Follow these steps to add MCP support for an existing Task Master command (see [
- [ ] Identify all required parameters and their types - [ ] Identify all required parameters and their types
2. **Direct Function Wrapper**: 2. **Direct Function Wrapper**:
- [ ] Create the `*Direct` function in `task-master-core.js` - [ ] Create the `*Direct` function in the appropriate file in `mcp-server/src/core/direct-functions/`
- [ ] Import silent mode utilities and implement them around core function calls
- [ ] Handle all parameter validations and type conversions - [ ] Handle all parameter validations and type conversions
- [ ] Implement path resolving for relative paths - [ ] Implement path resolving for relative paths
- [ ] Add appropriate error handling with standardized error codes - [ ] Add appropriate error handling with standardized error codes
- [ ] Add to `directFunctions` map - [ ] Add to imports/exports in `task-master-core.js`
3. **MCP Tool Implementation**: 3. **MCP Tool Implementation**:
- [ ] Create new file in `mcp-server/src/tools/` with kebab-case naming - [ ] Create new file in `mcp-server/src/tools/` with kebab-case naming
- [ ] Define zod schema for all parameters - [ ] Define zod schema for all parameters
- [ ] Implement the `execute` method following the standard pattern - [ ] Implement the `execute` method following the standard pattern
- [ ] Consider using AsyncOperationManager for long-running operations
- [ ] Register tool in `mcp-server/src/tools/index.js` - [ ] Register tool in `mcp-server/src/tools/index.js`
4. **Testing**: 4. **Testing**:

View File

@@ -97,6 +97,35 @@ The standard pattern for adding a feature follows this workflow:
} }
``` ```
- **Silent Mode Implementation**:
- ✅ **DO**: Import silent mode utilities in direct functions: `import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';`
- ✅ **DO**: Wrap core function calls with silent mode:
```javascript
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
const result = await coreFunction(...);
// Restore normal logging
disableSilentMode();
```
- ✅ **DO**: Ensure silent mode is disabled in error handling:
```javascript
try {
enableSilentMode();
// Core function call
disableSilentMode();
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
```
- ✅ **DO**: Add silent mode handling in all direct functions that call core functions
- ❌ **DON'T**: Forget to disable silent mode, which would suppress all future logs
- ❌ **DON'T**: Enable silent mode outside of direct functions in the MCP server
```javascript ```javascript
// 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js) // 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js)
/** /**
@@ -388,69 +417,112 @@ Integrating Task Master commands with the MCP server (for use by tools like Curs
1. **Core Logic**: Ensure the command's core logic exists and is exported from the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)). 1. **Core Logic**: Ensure the command's core logic exists and is exported from the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)).
2. **Direct Function Wrapper (`mcp-server/src/core/direct-functions/`)**: 2. **Direct Function Wrapper (`mcp-server/src/core/direct-functions/`)**:
- Create a new file (e.g., `your-command.js`) in `mcp-server/src/core/direct-functions/` using **kebab-case** naming. - Create a new file (e.g., `your-command.js`) in `mcp-server/src/core/direct-functions/` using **kebab-case** naming.
- Import the core logic function and necessary MCP utilities like **`findTasksJsonPath` from `../utils/path-utils.js`** and potentially `getCachedOrExecute` from `../../tools/utils.js`. - Import the core logic function, necessary MCP utilities like **`findTasksJsonPath` from `../utils/path-utils.js`**, and **silent mode utilities**: `import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';`
- Implement an `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix. - Implement an `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix.
- **Path Finding**: Inside this function, obtain the `tasksPath` by calling `const tasksPath = findTasksJsonPath(args, log);`. This relies on `args.projectRoot` (derived from the session) being passed correctly. - **Path Finding**: Inside this function, obtain the `tasksPath` by calling `const tasksPath = findTasksJsonPath(args, log);`. This relies on `args.projectRoot` (derived from the session) being passed correctly.
- Perform validation on other arguments received in `args`. - Perform validation on other arguments received in `args`.
- **Implement Caching (if applicable)**: - **Implement Silent Mode**: Wrap core function calls with `enableSilentMode()` and `disableSilentMode()` to prevent logs from interfering with JSON responses.
- **Use Case**: Apply caching primarily for read-only operations that benefit from repeated calls (e.g., `get_tasks`, `get_task`, `next_task`). Avoid caching for operations that modify state (`set_task_status`, `add_task`, `parse_prd`, `update_task`, `add_dependency`, etc.). - **If Caching**: Implement caching using `getCachedOrExecute` from `../../tools/utils.js`.
- **Implementation**: Use the `getCachedOrExecute` utility (imported from `../../tools/utils.js`).
- Generate a unique `cacheKey` based on relevant arguments (e.g., file path, filters).
- Define an `async` function `coreActionFn` that wraps the actual call to the core logic and returns `{ success: true/false, data/error }`.
- Call `await getCachedOrExecute({ cacheKey, actionFn: coreActionFn, log });`.
- **If Not Caching**: Directly call the core logic function within a try/catch block. - **If Not Caching**: Directly call the core logic function within a try/catch block.
- Handle errors and return a standard object: `{ success: true/false, data/error, fromCache: boolean }`. (For non-cached operations, `fromCache` is `false`). - Format the return as `{ success: true/false, data/error, fromCache: boolean }`.
- Export the function. - Export the wrapper function.
3. **Export from `task-master-core.js`**: Import your new `*Direct` function into [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js), re-export it, and add it to the `directFunctions` map.
4. **MCP Tool File (`mcp-server/src/tools/`)**:
- Create a new file (e.g., `your-command.js`) using **kebab-case** naming.
- Import `zod` (for schema), `handleApiResult`, `createErrorResponse`, **`getProjectRootFromSession`** from `./utils.js`, and your `yourCommandDirect` function from `../core/task-master-core.js`.
- Implement `registerYourCommandTool(server)` using **camelCase** with `Tool` suffix, which calls `server.addTool`.
- Define the tool's `name` using **snake_case** (e.g., `your_command`), `description`, and `parameters` using `zod`. **Crucially, define `projectRoot` as optional**: `projectRoot: z.string().optional().describe(...)`. Include `file: z.string().optional().describe(...)` if applicable.
- Define the standard `async execute(args, { log, reportProgress, session })` method:
```javascript
// In mcp-server/src/tools/your-command.js
import { z } from "zod";
import { handleApiResult, createErrorResponse, getProjectRootFromSession } from "./utils.js";
import { yourCommandDirect } from "../core/task-master-core.js"; // Adjust path as needed
export function registerYourCommandTool(server) {
server.addTool({
name: "your_command", // snake_case
description: "Description of your command.",
parameters: z.object({
/* zod schema */
projectRoot: z.string().optional().describe("Optional project root path"),
file: z.string().optional().describe("Optional tasks file path relative to project root"),
/* other parameters */
}),
execute: async (args, { log, reportProgress, session }) => { // Destructure context
try {
log.info(`Executing your_command with args: ${JSON.stringify(args)}`);
// 1. Get project root from session (or args fallback)
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// 2. Call the direct function wrapper, passing the resolved root 3. **Update `task-master-core.js` with Import/Export**: Import and re-export your `*Direct` function and add it to the `directFunctions` map.
const result = await yourCommandDirect({
...args, 4. **Create MCP Tool (`mcp-server/src/tools/`)**:
projectRoot: rootFolder // Pass the resolved root - Create a new file (e.g., `your-command.js`) using **kebab-case**.
}, log); - Import `zod`, `handleApiResult`, `createErrorResponse`, **`getProjectRootFromSession`**, and your `yourCommandDirect` function.
- Implement `registerYourCommandTool(server)`.
// 3. Pass the result to handleApiResult for formatting - Define the tool `name` using **snake_case** (e.g., `your_command`).
return handleApiResult(result, log, 'Error executing your_command'); // Provide default error - Define the `parameters` using `zod`. **Crucially, define `projectRoot` as optional**: `projectRoot: z.string().optional().describe(...)`. Include `file` if applicable.
} catch (error) { - Implement the standard `async execute(args, { log, reportProgress, session })` method:
// Catch unexpected errors from the direct call or handleApiResult itself - Get `rootFolder` using `getProjectRootFromSession` (with fallback to `args.projectRoot`).
log.error(`Unexpected error in your_command tool execute: ${error.message}`); - Call `yourCommandDirect({ ...args, projectRoot: rootFolder }, log)`.
return createErrorResponse(`Tool execution failed: ${error.message}`); - Pass the result to `handleApiResult(result, log, 'Error Message')`.
}
} 5. **Register Tool**: Import and call `registerYourCommandTool` in `mcp-server/src/tools/index.js`.
});
} 6. **Update `mcp.json`**: Add the new tool definition to the `tools` array in `.cursor/mcp.json`.
```
5. **Register in Tool Index**: Import and call `registerYourCommandTool` in [`mcp-server/src/tools/index.js`](mdc:mcp-server/src/tools/index.js). ## Implementing Background Operations
6. **Update `mcp.json`**: Add the tool definition (name, description, parameters) to `.cursor/mcp.json`.
For long-running operations that should not block the client, use the AsyncOperationManager:
1. **Identify Background-Appropriate Operations**:
- ✅ **DO**: Use async operations for CPU-intensive tasks like task expansion or PRD parsing
- ✅ **DO**: Consider async operations for tasks that may take more than 1-2 seconds
- ❌ **DON'T**: Use async operations for quick read/status operations
- ❌ **DON'T**: Use async operations when immediate feedback is critical
2. **Use AsyncOperationManager in MCP Tools**:
```javascript
import { asyncOperationManager } from '../core/utils/async-manager.js';
// In execute method:
const operationId = asyncOperationManager.addOperation(
expandTaskDirect, // The direct function to run in background
{ ...args, projectRoot: rootFolder }, // Args to pass to the function
{ log, reportProgress, session } // Context to preserve for the operation
);
// Return immediate response with operation ID
return createContentResponse({
message: "Operation started successfully",
operationId,
status: "pending"
});
```
3. **Implement Progress Reporting**:
- ✅ **DO**: Use the reportProgress function in direct functions:
```javascript
// In your direct function:
if (reportProgress) {
await reportProgress({ progress: 50 }); // 50% complete
}
```
- AsyncOperationManager will forward progress updates to the client
4. **Check Operation Status**:
- Implement a way for clients to check status using the `get_operation_status` MCP tool
- Return appropriate status codes and messages
## Project Initialization
When implementing project initialization commands:
1. **Support Programmatic Initialization**:
- ✅ **DO**: Design initialization to work with both CLI and MCP
- ✅ **DO**: Support non-interactive modes with sensible defaults
- ✅ **DO**: Handle project metadata like name, description, version
- ✅ **DO**: Create necessary files and directories
2. **In MCP Tool Implementation**:
```javascript
// In initialize-project.js MCP tool:
import { z } from "zod";
import { initializeProjectDirect } from "../core/task-master-core.js";
export function registerInitializeProjectTool(server) {
server.addTool({
name: "initialize_project",
description: "Initialize a new Task Master project",
parameters: z.object({
projectName: z.string().optional().describe("The name for the new project"),
projectDescription: z.string().optional().describe("A brief description"),
projectVersion: z.string().optional().describe("Initial version (e.g., '0.1.0')"),
// Add other parameters as needed
}),
execute: async (args, { log, reportProgress, session }) => {
try {
// No need for project root since we're creating a new project
const result = await initializeProjectDirect(args, log);
return handleApiResult(result, log, 'Error initializing project');
} catch (error) {
log.error(`Error in initialize_project: ${error.message}`);
return createErrorResponse(`Failed to initialize project: ${error.message}`);
}
}
});
}
```

View File

@@ -5,6 +5,7 @@
import { addDependency } from '../../../../scripts/modules/dependency-manager.js'; import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for addDependency with error handling. * Direct function wrapper for addDependency with error handling.
@@ -51,9 +52,15 @@ export async function addDependencyDirect(args, log) {
log.info(`Adding dependency: task ${taskId} will depend on ${dependencyId}`); log.info(`Adding dependency: task ${taskId} will depend on ${dependencyId}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function // Call the core function
await addDependency(tasksPath, taskId, dependencyId); await addDependency(tasksPath, taskId, dependencyId);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -63,6 +70,9 @@ export async function addDependencyDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addDependencyDirect: ${error.message}`); log.error(`Error in addDependencyDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { addSubtask } from '../../../../scripts/modules/task-manager.js'; import { addSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Add a subtask to an existing task * Add a subtask to an existing task
@@ -67,10 +68,17 @@ export async function addSubtaskDirect(args, log) {
// Determine if we should generate files // Determine if we should generate files
const generateFiles = !args.skipGenerate; const generateFiles = !args.skipGenerate;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Case 1: Convert existing task to subtask // Case 1: Convert existing task to subtask
if (existingTaskId) { if (existingTaskId) {
log.info(`Converting task ${existingTaskId} to a subtask of ${parentId}`); log.info(`Converting task ${existingTaskId} to a subtask of ${parentId}`);
const result = await addSubtask(tasksPath, parentId, existingTaskId, null, generateFiles); const result = await addSubtask(tasksPath, parentId, existingTaskId, null, generateFiles);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -92,6 +100,10 @@ export async function addSubtaskDirect(args, log) {
}; };
const result = await addSubtask(tasksPath, parentId, null, newSubtaskData, generateFiles); const result = await addSubtask(tasksPath, parentId, null, newSubtaskData, generateFiles);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -101,6 +113,9 @@ export async function addSubtaskDirect(args, log) {
}; };
} }
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addSubtaskDirect: ${error.message}`); log.error(`Error in addSubtaskDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -5,6 +5,7 @@
import { addTask } from '../../../../scripts/modules/task-manager.js'; import { addTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for adding a new task with error handling. * Direct function wrapper for adding a new task with error handling.
@@ -20,6 +21,9 @@ import { findTasksJsonPath } from '../utils/path-utils.js';
*/ */
export async function addTaskDirect(args, log) { export async function addTaskDirect(args, log) {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Find the tasks.json path // Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log); const tasksPath = findTasksJsonPath(args, log);
@@ -44,8 +48,18 @@ export async function addTaskDirect(args, log) {
log.info(`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`); log.info(`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`);
// Call the addTask function // Call the addTask function with 'json' outputFormat to prevent console output when called via MCP
const newTaskId = await addTask(tasksPath, prompt, dependencies, priority); const newTaskId = await addTask(
tasksPath,
prompt,
dependencies,
priority,
{ mcpLog: log },
'json'
);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
@@ -55,6 +69,9 @@ export async function addTaskDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addTaskDirect: ${error.message}`); log.error(`Error in addTaskDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js'; import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@@ -48,9 +49,15 @@ export async function analyzeTaskComplexityDirect(args, log) {
log.info('Using Perplexity AI for research-backed complexity analysis'); log.info('Using Perplexity AI for research-backed complexity analysis');
} }
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function // Call the core function
await analyzeTaskComplexity(options); await analyzeTaskComplexity(options);
// Restore normal logging
disableSilentMode();
// Verify the report file was created // Verify the report file was created
if (!fs.existsSync(outputPath)) { if (!fs.existsSync(outputPath)) {
return { return {
@@ -79,6 +86,9 @@ export async function analyzeTaskComplexityDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`); log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js'; import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs'; import fs from 'fs';
/** /**
@@ -68,9 +69,15 @@ export async function clearSubtasksDirect(args, log) {
log.info(`Clearing subtasks from tasks: ${taskIds}`); log.info(`Clearing subtasks from tasks: ${taskIds}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function // Call the core function
clearSubtasks(tasksPath, taskIds); clearSubtasks(tasksPath, taskIds);
// Restore normal logging
disableSilentMode();
// Read the updated data to provide a summary // Read the updated data to provide a summary
const updatedData = JSON.parse(fs.readFileSync(tasksPath, 'utf8')); const updatedData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
const taskIdArray = taskIds.split(',').map(id => parseInt(id.trim(), 10)); const taskIdArray = taskIds.split(',').map(id => parseInt(id.trim(), 10));
@@ -90,6 +97,9 @@ export async function clearSubtasksDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in clearSubtasksDirect: ${error.message}`); log.error(`Error in clearSubtasksDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -3,7 +3,7 @@
* Direct function implementation for displaying complexity analysis report * Direct function implementation for displaying complexity analysis report
*/ */
import { readComplexityReport } from '../../../../scripts/modules/utils.js'; import { readComplexityReport, enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { getCachedOrExecute } from '../../tools/utils.js'; import { getCachedOrExecute } from '../../tools/utils.js';
import path from 'path'; import path from 'path';
@@ -39,8 +39,14 @@ export async function complexityReportDirect(args, log) {
// Define the core action function to read the report // Define the core action function to read the report
const coreActionFn = async () => { const coreActionFn = async () => {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
const report = readComplexityReport(reportPath); const report = readComplexityReport(reportPath);
// Restore normal logging
disableSilentMode();
if (!report) { if (!report) {
log.warn(`No complexity report found at ${reportPath}`); log.warn(`No complexity report found at ${reportPath}`);
return { return {
@@ -60,6 +66,9 @@ export async function complexityReportDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error reading complexity report: ${error.message}`); log.error(`Error reading complexity report: ${error.message}`);
return { return {
success: false, success: false,
@@ -82,6 +91,9 @@ export async function complexityReportDirect(args, log) {
return result; // Returns { success, data/error, fromCache } return result; // Returns { success, data/error, fromCache }
} catch (error) { } catch (error) {
// Catch unexpected errors from getCachedOrExecute itself // Catch unexpected errors from getCachedOrExecute itself
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Unexpected error during getCachedOrExecute for complexityReport: ${error.message}`); log.error(`Unexpected error during getCachedOrExecute for complexityReport: ${error.message}`);
return { return {
success: false, success: false,
@@ -93,6 +105,9 @@ export async function complexityReportDirect(args, log) {
}; };
} }
} catch (error) { } catch (error) {
// Ensure silent mode is disabled if an outer error occurs
disableSilentMode();
log.error(`Error in complexityReportDirect: ${error.message}`); log.error(`Error in complexityReportDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -3,6 +3,7 @@
*/ */
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js'; import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**
@@ -41,23 +42,38 @@ export async function expandAllTasksDirect(args, log) {
log.info('Force regeneration of subtasks is enabled'); log.info('Force regeneration of subtasks is enabled');
} }
// Call the core function try {
await expandAllTasks(numSubtasks, useResearch, additionalContext, forceFlag); // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// The expandAllTasks function doesn't have a return value, so we'll create our own success response
return { // Call the core function
success: true, await expandAllTasks(numSubtasks, useResearch, additionalContext, forceFlag);
data: {
message: "Successfully expanded all pending tasks with subtasks", // Restore normal logging
details: { disableSilentMode();
numSubtasks: numSubtasks,
research: useResearch, // The expandAllTasks function doesn't have a return value, so we'll create our own success response
prompt: additionalContext, return {
force: forceFlag success: true,
data: {
message: "Successfully expanded all pending tasks with subtasks",
details: {
numSubtasks: numSubtasks,
research: useResearch,
prompt: additionalContext,
force: forceFlag
}
} }
} };
}; } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
} catch (error) { } catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error in expandAllTasksDirect: ${error.message}`); log.error(`Error in expandAllTasksDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,7 +4,7 @@
*/ */
import { expandTask } from '../../../../scripts/modules/task-manager.js'; import { expandTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON, writeJSON } from '../../../../scripts/modules/utils.js'; import { readJSON, writeJSON, enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
@@ -116,28 +116,50 @@ export async function expandTaskDirect(args, log) {
// Save tasks.json with potentially empty subtasks array // Save tasks.json with potentially empty subtasks array
writeJSON(tasksPath, data); writeJSON(tasksPath, data);
// Call the core expandTask function // Process the request
await expandTask(taskId, numSubtasks, useResearch, additionalContext); try {
// Enable silent mode to prevent console logs from interfering with JSON response
// Read the updated data enableSilentMode();
const updatedData = readJSON(tasksPath);
const updatedTask = updatedData.tasks.find(t => t.id === taskId); // Call expandTask
const result = await expandTask(taskId, numSubtasks, useResearch, additionalContext);
// Calculate how many subtasks were added
const subtasksAdded = updatedTask.subtasks ? // Restore normal logging
updatedTask.subtasks.length - subtasksCountBefore : 0; disableSilentMode();
// Return the result // Read the updated data
log.info(`Successfully expanded task ${taskId} with ${subtasksAdded} new subtasks`); const updatedData = readJSON(tasksPath);
return { const updatedTask = updatedData.tasks.find(t => t.id === taskId);
success: true,
data: { // Calculate how many subtasks were added
task: updatedTask, const subtasksAdded = updatedTask.subtasks ?
subtasksAdded, updatedTask.subtasks.length - subtasksCountBefore : 0;
hasExistingSubtasks
}, // Return the result
fromCache: false log.info(`Successfully expanded task ${taskId} with ${subtasksAdded} new subtasks`);
}; return {
success: true,
data: {
task: updatedTask,
subtasksAdded,
hasExistingSubtasks
},
fromCache: false
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error expanding task: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task'
},
fromCache: false
};
}
} catch (error) { } catch (error) {
log.error(`Error expanding task: ${error.message}`); log.error(`Error expanding task: ${error.message}`);
return { return {

View File

@@ -4,6 +4,7 @@
import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js'; import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs'; import fs from 'fs';
/** /**
@@ -32,9 +33,15 @@ export async function fixDependenciesDirect(args, log) {
}; };
} }
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function // Call the original command function
await fixDependenciesCommand(tasksPath); await fixDependenciesCommand(tasksPath);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -43,6 +50,9 @@ export async function fixDependenciesDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error fixing dependencies: ${error.message}`); log.error(`Error fixing dependencies: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
*/ */
import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js'; import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path'; import path from 'path';
@@ -39,8 +40,27 @@ export async function generateTaskFilesDirect(args, log) {
log.info(`Generating task files from ${tasksPath} to ${outputDir}`); log.info(`Generating task files from ${tasksPath} to ${outputDir}`);
// Execute core generateTaskFiles function // Execute core generateTaskFiles function in a separate try/catch
generateTaskFiles(tasksPath, outputDir); try {
// Enable silent mode to prevent logs from being written to stdout
enableSilentMode();
// The function is synchronous despite being awaited elsewhere
generateTaskFiles(tasksPath, outputDir);
// Restore normal logging after task generation
disableSilentMode();
} catch (genError) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in generateTaskFiles: ${genError.message}`);
return {
success: false,
error: { code: 'GENERATE_FILES_ERROR', message: genError.message },
fromCache: false
};
}
// Return success with file paths // Return success with file paths
return { return {
@@ -54,6 +74,9 @@ export async function generateTaskFilesDirect(args, log) {
fromCache: false // This operation always modifies state and should never be cached fromCache: false // This operation always modifies state and should never be cached
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging if an outer error occurs
disableSilentMode();
log.error(`Error generating task files: ${error.message}`); log.error(`Error generating task files: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -6,6 +6,7 @@
import { listTasks } from '../../../../scripts/modules/task-manager.js'; import { listTasks } from '../../../../scripts/modules/task-manager.js';
import { getCachedOrExecute } from '../../tools/utils.js'; import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for listTasks with error handling and caching. * Direct function wrapper for listTasks with error handling and caching.
@@ -38,6 +39,9 @@ export async function listTasksDirect(args, log) {
// Define the action function to be executed on cache miss // Define the action function to be executed on cache miss
const coreListTasksAction = async () => { const coreListTasksAction = async () => {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`); log.info(`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`);
const resultData = listTasks(tasksPath, statusFilter, withSubtasks, 'json'); const resultData = listTasks(tasksPath, statusFilter, withSubtasks, 'json');
@@ -46,9 +50,16 @@ export async function listTasksDirect(args, log) {
return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Invalid or empty response from listTasks core function' } }; return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Invalid or empty response from listTasks core function' } };
} }
log.info(`Core listTasks function retrieved ${resultData.tasks.length} tasks`); log.info(`Core listTasks function retrieved ${resultData.tasks.length} tasks`);
// Restore normal logging
disableSilentMode();
return { success: true, data: resultData }; return { success: true, data: resultData };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Core listTasks function failed: ${error.message}`); log.error(`Core listTasks function failed: ${error.message}`);
return { success: false, error: { code: 'LIST_TASKS_CORE_ERROR', message: error.message || 'Failed to list tasks' } }; return { success: false, error: { code: 'LIST_TASKS_CORE_ERROR', message: error.message || 'Failed to list tasks' } };
} }

View File

@@ -7,6 +7,7 @@ import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js'; import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js'; import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for finding the next task to work on with error handling and caching. * Direct function wrapper for finding the next task to work on with error handling and caching.
@@ -38,6 +39,9 @@ export async function nextTaskDirect(args, log) {
// Define the action function to be executed on cache miss // Define the action function to be executed on cache miss
const coreNextTaskAction = async () => { const coreNextTaskAction = async () => {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Finding next task from ${tasksPath}`); log.info(`Finding next task from ${tasksPath}`);
// Read tasks data // Read tasks data
@@ -67,6 +71,9 @@ export async function nextTaskDirect(args, log) {
}; };
} }
// Restore normal logging
disableSilentMode();
// Return the next task data with the full tasks array for reference // Return the next task data with the full tasks array for reference
log.info(`Successfully found next task ${nextTask.id}: ${nextTask.title}`); log.info(`Successfully found next task ${nextTask.id}: ${nextTask.title}`);
return { return {
@@ -77,6 +84,9 @@ export async function nextTaskDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error finding next task: ${error.message}`); log.error(`Error finding next task: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -7,6 +7,7 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { parsePRD } from '../../../../scripts/modules/task-manager.js'; import { parsePRD } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for parsing PRD documents and generating tasks. * Direct function wrapper for parsing PRD documents and generating tasks.
@@ -66,9 +67,15 @@ export async function parsePRDDirect(args, log) {
log.info(`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`); log.info(`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core parsePRD function (which is not async but we'll await it to maintain consistency) // Execute core parsePRD function (which is not async but we'll await it to maintain consistency)
await parsePRD(inputPath, outputPath, numTasks); await parsePRD(inputPath, outputPath, numTasks);
// Restore normal logging
disableSilentMode();
// Since parsePRD doesn't return a value but writes to a file, we'll read the result // Since parsePRD doesn't return a value but writes to a file, we'll read the result
// to return it to the caller // to return it to the caller
if (fs.existsSync(outputPath)) { if (fs.existsSync(outputPath)) {
@@ -94,6 +101,9 @@ export async function parsePRDDirect(args, log) {
}; };
} }
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error parsing PRD: ${error.message}`); log.error(`Error parsing PRD: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js'; import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Remove a dependency from a task * Remove a dependency from a task
@@ -49,9 +50,15 @@ export async function removeDependencyDirect(args, log) {
log.info(`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`); log.info(`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function // Call the core function
await removeDependency(tasksPath, taskId, dependencyId); await removeDependency(tasksPath, taskId, dependencyId);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -61,6 +68,9 @@ export async function removeDependencyDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in removeDependencyDirect: ${error.message}`); log.error(`Error in removeDependencyDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { removeSubtask } from '../../../../scripts/modules/task-manager.js'; import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Remove a subtask from its parent task * Remove a subtask from its parent task
@@ -18,6 +19,9 @@ import { findTasksJsonPath } from '../utils/path-utils.js';
*/ */
export async function removeSubtaskDirect(args, log) { export async function removeSubtaskDirect(args, log) {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Removing subtask with args: ${JSON.stringify(args)}`); log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
if (!args.id) { if (!args.id) {
@@ -54,6 +58,9 @@ export async function removeSubtaskDirect(args, log) {
const result = await removeSubtask(tasksPath, args.id, convertToTask, generateFiles); const result = await removeSubtask(tasksPath, args.id, convertToTask, generateFiles);
// Restore normal logging
disableSilentMode();
if (convertToTask && result) { if (convertToTask && result) {
// Return info about the converted task // Return info about the converted task
return { return {
@@ -73,6 +80,9 @@ export async function removeSubtaskDirect(args, log) {
}; };
} }
} catch (error) { } catch (error) {
// Ensure silent mode is disabled even if an outer error occurs
disableSilentMode();
log.error(`Error in removeSubtaskDirect: ${error.message}`); log.error(`Error in removeSubtaskDirect: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
*/ */
import { removeTask } from '../../../../scripts/modules/task-manager.js'; import { removeTask } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**
@@ -49,9 +50,15 @@ export async function removeTaskDirect(args, log) {
log.info(`Removing task with ID: ${taskId} from ${tasksPath}`); log.info(`Removing task with ID: ${taskId} from ${tasksPath}`);
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core removeTask function // Call the core removeTask function
const result = await removeTask(tasksPath, taskId); const result = await removeTask(tasksPath, taskId);
// Restore normal logging
disableSilentMode();
log.info(`Successfully removed task: ${taskId}`); log.info(`Successfully removed task: ${taskId}`);
// Return the result // Return the result
@@ -66,6 +73,9 @@ export async function removeTaskDirect(args, log) {
fromCache: false fromCache: false
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error removing task: ${error.message}`); log.error(`Error removing task: ${error.message}`);
return { return {
success: false, success: false,
@@ -77,6 +87,9 @@ export async function removeTaskDirect(args, log) {
}; };
} }
} catch (error) { } catch (error) {
// Ensure silent mode is disabled even if an outer error occurs
disableSilentMode();
// Catch any unexpected errors // Catch any unexpected errors
log.error(`Unexpected error in removeTaskDirect: ${error.message}`); log.error(`Unexpected error in removeTaskDirect: ${error.message}`);
return { return {

View File

@@ -5,6 +5,7 @@
import { setTaskStatus } from '../../../../scripts/modules/task-manager.js'; import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for setTaskStatus with error handling. * Direct function wrapper for setTaskStatus with error handling.
@@ -63,20 +64,40 @@ export async function setTaskStatusDirect(args, log) {
log.info(`Setting task ${taskId} status to "${newStatus}"`); log.info(`Setting task ${taskId} status to "${newStatus}"`);
// Execute the setTaskStatus function with source=mcp to avoid console output // Call the core function
await setTaskStatus(tasksPath, taskId, newStatus); try {
// Enable silent mode to prevent console logs from interfering with JSON response
// Return success data enableSilentMode();
return {
success: true, await setTaskStatus(tasksPath, taskId, newStatus);
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`, // Restore normal logging
taskId, disableSilentMode();
status: newStatus,
tasksPath log.info(`Successfully set task ${taskId} status to ${newStatus}`);
},
fromCache: false // This operation always modifies state and should never be cached // Return success data
}; return {
success: true,
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
taskId,
status: newStatus,
tasksPath
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error setting task status: ${error.message}`);
return {
success: false,
error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' },
fromCache: false
};
}
} catch (error) { } catch (error) {
log.error(`Error setting task status: ${error.message}`); log.error(`Error setting task status: ${error.message}`);
return { return {

View File

@@ -7,6 +7,7 @@ import { findTaskById } from '../../../../scripts/modules/utils.js';
import { readJSON } from '../../../../scripts/modules/utils.js'; import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js'; import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for showing task details with error handling and caching. * Direct function wrapper for showing task details with error handling and caching.
@@ -52,6 +53,9 @@ export async function showTaskDirect(args, log) {
// Define the action function to be executed on cache miss // Define the action function to be executed on cache miss
const coreShowTaskAction = async () => { const coreShowTaskAction = async () => {
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Retrieving task details for ID: ${taskId} from ${tasksPath}`); log.info(`Retrieving task details for ID: ${taskId} from ${tasksPath}`);
// Read tasks data // Read tasks data
@@ -79,6 +83,9 @@ export async function showTaskDirect(args, log) {
}; };
} }
// Restore normal logging
disableSilentMode();
// Return the task data with the full tasks array for reference // Return the task data with the full tasks array for reference
// (needed for formatDependenciesWithStatus function in UI) // (needed for formatDependenciesWithStatus function in UI)
log.info(`Successfully found task ${taskId}`); log.info(`Successfully found task ${taskId}`);
@@ -90,6 +97,9 @@ export async function showTaskDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error showing task: ${error.message}`); log.error(`Error showing task: ${error.message}`);
return { return {
success: false, success: false,
@@ -112,6 +122,7 @@ export async function showTaskDirect(args, log) {
return result; // Returns { success, data/error, fromCache } return result; // Returns { success, data/error, fromCache }
} catch (error) { } catch (error) {
// Catch unexpected errors from getCachedOrExecute itself // Catch unexpected errors from getCachedOrExecute itself
disableSilentMode();
log.error(`Unexpected error during getCachedOrExecute for showTask: ${error.message}`); log.error(`Unexpected error during getCachedOrExecute for showTask: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
*/ */
import { updateSubtaskById } from '../../../../scripts/modules/task-manager.js'; import { updateSubtaskById } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**
@@ -68,35 +69,50 @@ export async function updateSubtaskByIdDirect(args, log) {
log.info(`Updating subtask with ID ${subtaskId} with prompt "${args.prompt}" and research: ${useResearch}`); log.info(`Updating subtask with ID ${subtaskId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Execute core updateSubtaskById function try {
const updatedSubtask = await updateSubtaskById(tasksPath, subtaskId, args.prompt, useResearch); // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Handle the case where the subtask couldn't be updated (e.g., already marked as done)
if (!updatedSubtask) { // Execute core updateSubtaskById function
const updatedSubtask = await updateSubtaskById(tasksPath, subtaskId, args.prompt, useResearch);
// Restore normal logging
disableSilentMode();
// Handle the case where the subtask couldn't be updated (e.g., already marked as done)
if (!updatedSubtask) {
return {
success: false,
error: {
code: 'SUBTASK_UPDATE_FAILED',
message: 'Failed to update subtask. It may be marked as completed, or another error occurred.'
},
fromCache: false
};
}
// Return the updated subtask information
return { return {
success: false, success: true,
error: { data: {
code: 'SUBTASK_UPDATE_FAILED', message: `Successfully updated subtask with ID ${subtaskId}`,
message: 'Failed to update subtask. It may be marked as completed, or another error occurred.' subtaskId,
parentId: subtaskId.split('.')[0],
subtask: updatedSubtask,
tasksPath,
useResearch
}, },
fromCache: false fromCache: false // This operation always modifies state and should never be cached
}; };
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
} }
// Return the updated subtask information
return {
success: true,
data: {
message: `Successfully updated subtask with ID ${subtaskId}`,
subtaskId,
parentId: subtaskId.split('.')[0],
subtask: updatedSubtask,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) { } catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error updating subtask by ID: ${error.message}`); log.error(`Error updating subtask by ID: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -5,6 +5,7 @@
import { updateTaskById } from '../../../../scripts/modules/task-manager.js'; import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for updateTaskById with error handling. * Direct function wrapper for updateTaskById with error handling.
@@ -79,9 +80,15 @@ export async function updateTaskByIdDirect(args, log) {
log.info(`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`); log.info(`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateTaskById function // Execute core updateTaskById function
await updateTaskById(tasksPath, taskId, args.prompt, useResearch); await updateTaskById(tasksPath, taskId, args.prompt, useResearch);
// Restore normal logging
disableSilentMode();
// Since updateTaskById doesn't return a value but modifies the tasks file, // Since updateTaskById doesn't return a value but modifies the tasks file,
// we'll return a success message // we'll return a success message
return { return {
@@ -95,6 +102,9 @@ export async function updateTaskByIdDirect(args, log) {
fromCache: false // This operation always modifies state and should never be cached fromCache: false // This operation always modifies state and should never be cached
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error updating task by ID: ${error.message}`); log.error(`Error updating task by ID: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
*/ */
import { updateTasks } from '../../../../scripts/modules/task-manager.js'; import { updateTasks } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**
@@ -73,22 +74,37 @@ export async function updateTasksDirect(args, log) {
log.info(`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`); log.info(`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Execute core updateTasks function try {
await updateTasks(tasksPath, fromId, args.prompt, useResearch); // Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Since updateTasks doesn't return a value but modifies the tasks file,
// we'll return a success message // Execute core updateTasks function
return { await updateTasks(tasksPath, fromId, args.prompt, useResearch);
success: true,
data: { // Restore normal logging
message: `Successfully updated tasks from ID ${fromId} based on the prompt`, disableSilentMode();
fromId,
tasksPath, // Since updateTasks doesn't return a value but modifies the tasks file,
useResearch // we'll return a success message
}, return {
fromCache: false // This operation always modifies state and should never be cached success: true,
}; data: {
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
fromId,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
} catch (error) { } catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error updating tasks: ${error.message}`); log.error(`Error updating tasks: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -4,6 +4,7 @@
import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js'; import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs'; import fs from 'fs';
/** /**
@@ -32,9 +33,15 @@ export async function validateDependenciesDirect(args, log) {
}; };
} }
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function // Call the original command function
await validateDependenciesCommand(tasksPath); await validateDependenciesCommand(tasksPath);
// Restore normal logging
disableSilentMode();
return { return {
success: true, success: true,
data: { data: {
@@ -43,6 +50,9 @@ export async function validateDependenciesDirect(args, log) {
} }
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error validating dependencies: ${error.message}`); log.error(`Error validating dependencies: ${error.message}`);
return { return {
success: false, success: false,

View File

@@ -0,0 +1,217 @@
import { v4 as uuidv4 } from 'uuid';
class AsyncOperationManager {
constructor() {
this.operations = new Map(); // Stores active operation state
this.completedOperations = new Map(); // Stores completed operations
this.maxCompletedOperations = 100; // Maximum number of completed operations to store
this.listeners = new Map(); // For potential future notifications
}
/**
* Adds an operation to be executed asynchronously.
* @param {Function} operationFn - The async function to execute (e.g., a Direct function).
* @param {Object} args - Arguments to pass to the operationFn.
* @param {Object} context - The MCP tool context { log, reportProgress, session }.
* @returns {string} The unique ID assigned to this operation.
*/
addOperation(operationFn, args, context) {
const operationId = `op-${uuidv4()}`;
const operation = {
id: operationId,
status: 'pending',
startTime: Date.now(),
endTime: null,
result: null,
error: null,
// Store necessary parts of context, especially log for background execution
log: context.log,
reportProgress: context.reportProgress, // Pass reportProgress through
session: context.session // Pass session through if needed by the operationFn
};
this.operations.set(operationId, operation);
this.log(operationId, 'info', `Operation added.`);
// Start execution in the background (don't await here)
this._runOperation(operationId, operationFn, args, context).catch(err => {
// Catch unexpected errors during the async execution setup itself
this.log(operationId, 'error', `Critical error starting operation: ${err.message}`, { stack: err.stack });
operation.status = 'failed';
operation.error = { code: 'MANAGER_EXECUTION_ERROR', message: err.message };
operation.endTime = Date.now();
// Move to completed operations
this._moveToCompleted(operationId);
});
return operationId;
}
/**
* Internal function to execute the operation.
* @param {string} operationId - The ID of the operation.
* @param {Function} operationFn - The async function to execute.
* @param {Object} args - Arguments for the function.
* @param {Object} context - The original MCP tool context.
*/
async _runOperation(operationId, operationFn, args, context) {
const operation = this.operations.get(operationId);
if (!operation) return; // Should not happen
operation.status = 'running';
this.log(operationId, 'info', `Operation running.`);
this.emit('statusChanged', { operationId, status: 'running' });
try {
// Pass the necessary context parts to the direct function
// The direct function needs to be adapted if it needs reportProgress
// We pass the original context's log, plus our wrapped reportProgress
const result = await operationFn(args, operation.log, {
reportProgress: (progress) => this._handleProgress(operationId, progress),
mcpLog: operation.log, // Pass log as mcpLog if direct fn expects it
session: operation.session
});
operation.status = result.success ? 'completed' : 'failed';
operation.result = result.success ? result.data : null;
operation.error = result.success ? null : result.error;
this.log(operationId, 'info', `Operation finished with status: ${operation.status}`);
} catch (error) {
this.log(operationId, 'error', `Operation failed with error: ${error.message}`, { stack: error.stack });
operation.status = 'failed';
operation.error = { code: 'OPERATION_EXECUTION_ERROR', message: error.message };
} finally {
operation.endTime = Date.now();
this.emit('statusChanged', { operationId, status: operation.status, result: operation.result, error: operation.error });
// Move to completed operations if done or failed
if (operation.status === 'completed' || operation.status === 'failed') {
this._moveToCompleted(operationId);
}
}
}
/**
* Move an operation from active operations to completed operations history.
* @param {string} operationId - The ID of the operation to move.
* @private
*/
_moveToCompleted(operationId) {
const operation = this.operations.get(operationId);
if (!operation) return;
// Store only the necessary data in completed operations
const completedData = {
id: operation.id,
status: operation.status,
startTime: operation.startTime,
endTime: operation.endTime,
result: operation.result,
error: operation.error,
};
this.completedOperations.set(operationId, completedData);
this.operations.delete(operationId);
// Trim completed operations if exceeding maximum
if (this.completedOperations.size > this.maxCompletedOperations) {
// Get the oldest operation (sorted by endTime)
const oldest = [...this.completedOperations.entries()]
.sort((a, b) => a[1].endTime - b[1].endTime)[0];
if (oldest) {
this.completedOperations.delete(oldest[0]);
}
}
}
/**
* Handles progress updates from the running operation and forwards them.
* @param {string} operationId - The ID of the operation reporting progress.
* @param {Object} progress - The progress object { progress, total? }.
*/
_handleProgress(operationId, progress) {
const operation = this.operations.get(operationId);
if (operation && operation.reportProgress) {
try {
// Use the reportProgress function captured from the original context
operation.reportProgress(progress);
this.log(operationId, 'debug', `Reported progress: ${JSON.stringify(progress)}`);
} catch(err) {
this.log(operationId, 'warn', `Failed to report progress: ${err.message}`);
// Don't stop the operation, just log the reporting failure
}
}
}
/**
* Retrieves the status and result/error of an operation.
* @param {string} operationId - The ID of the operation.
* @returns {Object | null} The operation details or null if not found.
*/
getStatus(operationId) {
// First check active operations
const operation = this.operations.get(operationId);
if (operation) {
return {
id: operation.id,
status: operation.status,
startTime: operation.startTime,
endTime: operation.endTime,
result: operation.result,
error: operation.error,
};
}
// Then check completed operations
const completedOperation = this.completedOperations.get(operationId);
if (completedOperation) {
return completedOperation;
}
// Operation not found in either active or completed
return {
error: {
code: 'OPERATION_NOT_FOUND',
message: `Operation ID ${operationId} not found. It may have been completed and removed from history, or the ID may be invalid.`
},
status: 'not_found'
};
}
/**
* Internal logging helper to prefix logs with the operation ID.
* @param {string} operationId - The ID of the operation.
* @param {'info'|'warn'|'error'|'debug'} level - Log level.
* @param {string} message - Log message.
* @param {Object} [meta] - Additional metadata.
*/
log(operationId, level, message, meta = {}) {
const operation = this.operations.get(operationId);
// Use the logger instance associated with the operation if available, otherwise console
const logger = operation?.log || console;
const logFn = logger[level] || logger.log || console.log; // Fallback
logFn(`[AsyncOp ${operationId}] ${message}`, meta);
}
// --- Basic Event Emitter ---
on(eventName, listener) {
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, []);
}
this.listeners.get(eventName).push(listener);
}
emit(eventName, data) {
if (this.listeners.has(eventName)) {
this.listeners.get(eventName).forEach(listener => listener(data));
}
}
}
// Export a singleton instance
const asyncOperationManager = new AsyncOperationManager();
// Export the manager and potentially the class if needed elsewhere
export { asyncOperationManager, AsyncOperationManager };

View File

@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
import fs from "fs"; import fs from "fs";
import logger from "./logger.js"; import logger from "./logger.js";
import { registerTaskMasterTools } from "./tools/index.js"; import { registerTaskMasterTools } from "./tools/index.js";
import { asyncOperationManager } from './core/utils/async-manager.js';
// Load environment variables // Load environment variables
dotenv.config(); dotenv.config();
@@ -34,6 +35,9 @@ class TaskMasterMCPServer {
this.server.addResourceTemplate({}); this.server.addResourceTemplate({});
// Make the manager accessible (e.g., pass it to tool registration)
this.asyncManager = asyncOperationManager;
// Bind methods // Bind methods
this.init = this.init.bind(this); this.init = this.init.bind(this);
this.start = this.start.bind(this); this.start = this.start.bind(this);
@@ -49,8 +53,8 @@ class TaskMasterMCPServer {
async init() { async init() {
if (this.initialized) return; if (this.initialized) return;
// Register Task Master tools // Pass the manager instance to the tool registration function
registerTaskMasterTools(this.server); registerTaskMasterTools(this.server, this.asyncManager);
this.initialized = true; this.initialized = true;
@@ -83,4 +87,7 @@ class TaskMasterMCPServer {
} }
} }
// Export the manager from here as well, if needed elsewhere
export { asyncOperationManager };
export default TaskMasterMCPServer; export default TaskMasterMCPServer;

View File

@@ -11,7 +11,7 @@ const LOG_LEVELS = {
// Get log level from environment or default to info // Get log level from environment or default to info
const LOG_LEVEL = process.env.LOG_LEVEL const LOG_LEVEL = process.env.LOG_LEVEL
? LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()] ? LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()] ?? LOG_LEVELS.info
: LOG_LEVELS.info; : LOG_LEVELS.info;
/** /**
@@ -20,43 +20,66 @@ const LOG_LEVEL = process.env.LOG_LEVEL
* @param {...any} args - Arguments to log * @param {...any} args - Arguments to log
*/ */
function log(level, ...args) { function log(level, ...args) {
const icons = { // Use text prefixes instead of emojis
debug: chalk.gray("🔍"), const prefixes = {
info: chalk.blue(""), debug: chalk.gray("[DEBUG]"),
warn: chalk.yellow("⚠️"), info: chalk.blue("[INFO]"),
error: chalk.red("❌"), warn: chalk.yellow("[WARN]"),
success: chalk.green(""), error: chalk.red("[ERROR]"),
success: chalk.green("[SUCCESS]"),
}; };
if (LOG_LEVELS[level] >= LOG_LEVEL) { if (LOG_LEVELS[level] !== undefined && LOG_LEVELS[level] >= LOG_LEVEL) {
const icon = icons[level] || ""; const prefix = prefixes[level] || "";
let coloredArgs = args;
if (level === "error") { try {
console.error(icon, chalk.red(...args)); switch(level) {
} else if (level === "warn") { case "error":
console.warn(icon, chalk.yellow(...args)); coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.red(arg) : arg);
} else if (level === "success") { break;
console.log(icon, chalk.green(...args)); case "warn":
} else if (level === "info") { coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.yellow(arg) : arg);
console.log(icon, chalk.blue(...args)); break;
} else { case "success":
console.log(icon, ...args); coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.green(arg) : arg);
break;
case "info":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.blue(arg) : arg);
break;
case "debug":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.gray(arg) : arg);
break;
// default: use original args (no color)
}
} catch (colorError) {
// Fallback if chalk fails on an argument
// Use console.error here for internal logger errors, separate from normal logging
console.error("Internal Logger Error applying chalk color:", colorError);
coloredArgs = args;
} }
// Revert to console.log - FastMCP's context logger (context.log)
// is responsible for directing logs correctly (e.g., to stderr)
// during tool execution without upsetting the client connection.
// Logs outside of tool execution (like startup) will go to stdout.
console.log(prefix, ...coloredArgs);
} }
} }
/** /**
* Create a logger object with methods for different log levels * Create a logger object with methods for different log levels
* Can be used as a drop-in replacement for existing logger initialization
* @returns {Object} Logger object with info, error, debug, warn, and success methods * @returns {Object} Logger object with info, error, debug, warn, and success methods
*/ */
export function createLogger() { export function createLogger() {
const createLogMethod = (level) => (...args) => log(level, ...args);
return { return {
debug: (message) => log("debug", message), debug: createLogMethod("debug"),
info: (message) => log("info", message), info: createLogMethod("info"),
warn: (message) => log("warn", message), warn: createLogMethod("warn"),
error: (message) => log("error", message), error: createLogMethod("error"),
success: (message) => log("success", message), success: createLogMethod("success"),
log: log, // Also expose the raw log function log: log, // Also expose the raw log function
}; };
} }

View File

@@ -7,6 +7,7 @@ import { z } from "zod";
import { import {
handleApiResult, handleApiResult,
createErrorResponse, createErrorResponse,
createContentResponse,
getProjectRootFromSession getProjectRootFromSession
} from "./utils.js"; } from "./utils.js";
import { addTaskDirect } from "../core/task-master-core.js"; import { addTaskDirect } from "../core/task-master-core.js";
@@ -14,11 +15,12 @@ import { addTaskDirect } from "../core/task-master-core.js";
/** /**
* Register the add-task tool with the MCP server * Register the add-task tool with the MCP server
* @param {Object} server - FastMCP server instance * @param {Object} server - FastMCP server instance
* @param {AsyncOperationManager} asyncManager - The async operation manager instance.
*/ */
export function registerAddTaskTool(server) { export function registerAddTaskTool(server, asyncManager) {
server.addTool({ server.addTool({
name: "add_task", name: "add_task",
description: "Add a new task using AI", description: "Starts adding a new task using AI in the background.",
parameters: z.object({ parameters: z.object({
prompt: z.string().describe("Description of the task to add"), prompt: z.string().describe("Description of the task to add"),
dependencies: z.string().optional().describe("Comma-separated list of task IDs this task depends on"), dependencies: z.string().optional().describe("Comma-separated list of task IDs this task depends on"),
@@ -26,29 +28,38 @@ export function registerAddTaskTool(server) {
file: z.string().optional().describe("Path to the tasks file"), file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)") projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)")
}), }),
execute: async (args, { log, reportProgress, session }) => { execute: async (args, context) => {
const { log, reportProgress, session } = context;
try { try {
log.info(`MCP add_task called with prompt: "${args.prompt}"`); log.info(`MCP add_task request received with prompt: \"${args.prompt}\"`);
// Get project root using the utility function if (!args.prompt) {
return createErrorResponse("Prompt is required for add_task.", "VALIDATION_ERROR");
}
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) { if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot; rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`); log.info(`Using project root from args as fallback: ${rootFolder}`);
} }
// Call the direct function with the resolved rootFolder const directArgs = {
const result = await addTaskDirect({ projectRoot: rootFolder,
projectRoot: rootFolder, // Pass the resolved root
...args ...args
}, log, { reportProgress, mcpLog: log, session}); };
const operationId = asyncManager.addOperation(addTaskDirect, directArgs, context);
return handleApiResult(result, log); log.info(`Started background operation for add_task. Operation ID: ${operationId}`);
return createContentResponse({
message: "Add task operation started successfully.",
operationId: operationId
});
} catch (error) { } catch (error) {
log.error(`Error in add_task MCP tool: ${error.message}`); log.error(`Error initiating add_task operation: ${error.message}`, { stack: error.stack });
return createErrorResponse(error.message, "ADD_TASK_ERROR"); return createErrorResponse(`Failed to start add task operation: ${error.message}`, "ADD_TASK_INIT_ERROR");
} }
} }
}); });

View File

@@ -30,7 +30,7 @@ export function registerAnalyzeTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`); log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -42,9 +42,9 @@ export function registerAnalyzeTool(server) {
const result = await analyzeTaskComplexityDirect({ const result = await analyzeTaskComplexityDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Task complexity analysis complete: ${result.data.message}`); log.info(`Task complexity analysis complete: ${result.data.message}`);

View File

@@ -26,7 +26,7 @@ export function registerComplexityReportTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`); log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -38,9 +38,9 @@ export function registerComplexityReportTool(server) {
const result = await complexityReportDirect({ const result = await complexityReportDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`); log.info(`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`);

View File

@@ -30,7 +30,7 @@ export function registerExpandAllTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`); log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -42,19 +42,19 @@ export function registerExpandAllTool(server) {
const result = await expandAllTasksDirect({ const result = await expandAllTasksDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`All tasks expanded successfully: ${result.data.message}`); log.info(`Successfully expanded all tasks: ${result.data.message}`);
} else { } else {
log.error(`Failed to expand tasks: ${result.error.message}`); log.error(`Failed to expand all tasks: ${result.error?.message || 'Unknown error'}`);
} }
return handleApiResult(result, log, 'Error expanding tasks'); return handleApiResult(result, log, 'Error expanding all tasks');
} catch (error) { } catch (error) {
log.error(`Error in expandAll tool: ${error.message}`); log.error(`Error in expand-all tool: ${error.message}`);
return createErrorResponse(error.message); return createErrorResponse(error.message);
} }
}, },

View File

@@ -36,7 +36,7 @@ export function registerExpandTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Expanding task with args: ${JSON.stringify(args)}`); log.info(`Expanding task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -48,20 +48,20 @@ export function registerExpandTaskTool(server) {
const result = await expandTaskDirect({ const result = await expandTaskDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully expanded task ID: ${args.id} with ${result.data.subtasksAdded} new subtasks${result.data.hasExistingSubtasks ? ' (appended to existing subtasks)' : ''}`); log.info(`Successfully expanded task with ID ${args.id}`);
} else { } else {
log.error(`Failed to expand task: ${result.error.message}`); log.error(`Failed to expand task: ${result.error?.message || 'Unknown error'}`);
} }
return handleApiResult(result, log, 'Error expanding task'); return handleApiResult(result, log, 'Error expanding task');
} catch (error) { } catch (error) {
log.error(`Error in expand-task tool: ${error.message}`); log.error(`Error in expand task tool: ${error.message}`);
return createErrorResponse(`Failed to expand task: ${error.message}`); return createErrorResponse(error.message);
} }
}, },
}); });

View File

@@ -32,7 +32,7 @@ export function registerGenerateTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`); log.info(`Generating task files with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -44,14 +44,14 @@ export function registerGenerateTool(server) {
const result = await generateTaskFilesDirect({ const result = await generateTaskFilesDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully generated task files: ${result.data.message}`); log.info(`Successfully generated task files: ${result.data.message}`);
} else { } else {
log.error(`Failed to generate task files: ${result.error.message}`); log.error(`Failed to generate task files: ${result.error?.message || 'Unknown error'}`);
} }
return handleApiResult(result, log, 'Error generating task files'); return handleApiResult(result, log, 'Error generating task files');

View File

@@ -0,0 +1,42 @@
// mcp-server/src/tools/get-operation-status.js
import { z } from 'zod';
import { createErrorResponse, createContentResponse } from './utils.js'; // Assuming these utils exist
/**
* Register the get_operation_status tool.
* @param {FastMCP} server - FastMCP server instance.
* @param {AsyncOperationManager} asyncManager - The async operation manager.
*/
export function registerGetOperationStatusTool(server, asyncManager) {
server.addTool({
name: 'get_operation_status',
description: 'Retrieves the status and result/error of a background operation.',
parameters: z.object({
operationId: z.string().describe('The ID of the operation to check.'),
}),
execute: async (args, { log }) => {
try {
const { operationId } = args;
log.info(`Checking status for operation ID: ${operationId}`);
const status = asyncManager.getStatus(operationId);
// Status will now always return an object, but it might have status='not_found'
if (status.status === 'not_found') {
log.warn(`Operation ID not found: ${operationId}`);
return createErrorResponse(
status.error?.message || `Operation ID not found: ${operationId}`,
status.error?.code || 'OPERATION_NOT_FOUND'
);
}
log.info(`Status for ${operationId}: ${status.status}`);
return createContentResponse(status);
} catch (error) {
log.error(`Error in get_operation_status tool: ${error.message}`, { stack: error.stack });
return createErrorResponse(`Failed to get operation status: ${error.message}`, 'GET_STATUS_ERROR');
}
},
});
}

View File

@@ -36,7 +36,7 @@ export function registerListTasksTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`); log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -48,9 +48,9 @@ export function registerListTasksTool(server) {
const result = await listTasksDirect({ const result = await listTasksDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks${result.fromCache ? ' (from cache)' : ''}`); log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks${result.fromCache ? ' (from cache)' : ''}`);
return handleApiResult(result, log, 'Error getting tasks'); return handleApiResult(result, log, 'Error getting tasks');

View File

@@ -27,12 +27,15 @@ import { registerComplexityReportTool } from "./complexity-report.js";
import { registerAddDependencyTool } from "./add-dependency.js"; import { registerAddDependencyTool } from "./add-dependency.js";
import { registerRemoveTaskTool } from './remove-task.js'; import { registerRemoveTaskTool } from './remove-task.js';
import { registerInitializeProjectTool } from './initialize-project.js'; import { registerInitializeProjectTool } from './initialize-project.js';
import { asyncOperationManager } from '../core/utils/async-manager.js';
import { registerGetOperationStatusTool } from './get-operation-status.js';
/** /**
* Register all Task Master tools with the MCP server * Register all Task Master tools with the MCP server
* @param {Object} server - FastMCP server instance * @param {Object} server - FastMCP server instance
* @param {asyncOperationManager} asyncManager - The async operation manager instance
*/ */
export function registerTaskMasterTools(server) { export function registerTaskMasterTools(server, asyncManager) {
try { try {
// Register each tool // Register each tool
registerListTasksTool(server); registerListTasksTool(server);
@@ -45,7 +48,7 @@ export function registerTaskMasterTools(server) {
registerShowTaskTool(server); registerShowTaskTool(server);
registerNextTaskTool(server); registerNextTaskTool(server);
registerExpandTaskTool(server); registerExpandTaskTool(server);
registerAddTaskTool(server); registerAddTaskTool(server, asyncManager);
registerAddSubtaskTool(server); registerAddSubtaskTool(server);
registerRemoveSubtaskTool(server); registerRemoveSubtaskTool(server);
registerAnalyzeTool(server); registerAnalyzeTool(server);
@@ -58,10 +61,13 @@ export function registerTaskMasterTools(server) {
registerAddDependencyTool(server); registerAddDependencyTool(server);
registerRemoveTaskTool(server); registerRemoveTaskTool(server);
registerInitializeProjectTool(server); registerInitializeProjectTool(server);
registerGetOperationStatusTool(server, asyncManager);
} catch (error) { } catch (error) {
logger.error(`Error registering Task Master tools: ${error.message}`); logger.error(`Error registering Task Master tools: ${error.message}`);
throw error; throw error;
} }
logger.info('Registered Task Master MCP tools');
} }
export default { export default {

View File

@@ -31,7 +31,7 @@ export function registerNextTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`); log.info(`Finding next task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -43,24 +43,20 @@ export function registerNextTaskTool(server) {
const result = await nextTaskDirect({ const result = await nextTaskDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
if (result.data.nextTask) { log.info(`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`);
log.info(`Successfully found next task ID: ${result.data.nextTask.id}${result.fromCache ? ' (from cache)' : ''}`);
} else {
log.info(`No eligible next task found${result.fromCache ? ' (from cache)' : ''}`);
}
} else { } else {
log.error(`Failed to find next task: ${result.error.message}`); log.error(`Failed to find next task: ${result.error?.message || 'Unknown error'}`);
} }
return handleApiResult(result, log, 'Error finding next task'); return handleApiResult(result, log, 'Error finding next task');
} catch (error) { } catch (error) {
log.error(`Error in next-task tool: ${error.message}`); log.error(`Error in nextTask tool: ${error.message}`);
return createErrorResponse(`Failed to find next task: ${error.message}`); return createErrorResponse(error.message);
} }
}, },
}); });

View File

@@ -33,7 +33,7 @@ export function registerParsePRDTool(server) {
}), }),
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`); log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -45,19 +45,19 @@ export function registerParsePRDTool(server) {
const result = await parsePRDDirect({ const result = await parsePRDDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully generated ${result.data?.taskCount || 0} tasks from PRD at ${result.data?.outputPath}`); log.info(`Successfully parsed PRD: ${result.data.message}`);
} else { } else {
log.error(`Failed to parse PRD: ${result.error?.message || 'Unknown error'}`); log.error(`Failed to parse PRD: ${result.error?.message || 'Unknown error'}`);
} }
return handleApiResult(result, log, 'Error parsing PRD document'); return handleApiResult(result, log, 'Error parsing PRD');
} catch (error) { } catch (error) {
log.error(`Error in parse_prd tool: ${error.message}`); log.error(`Error in parse-prd tool: ${error.message}`);
return createErrorResponse(error.message); return createErrorResponse(error.message);
} }
}, },

View File

@@ -28,7 +28,7 @@ export function registerRemoveDependencyTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`); log.info(`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -40,9 +40,9 @@ export function registerRemoveDependencyTool(server) {
const result = await removeDependencyDirect({ const result = await removeDependencyDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully removed dependency: ${result.data.message}`); log.info(`Successfully removed dependency: ${result.data.message}`);

View File

@@ -29,7 +29,7 @@ export function registerRemoveSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Removing subtask with args: ${JSON.stringify(args)}`); log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -41,9 +41,9 @@ export function registerRemoveSubtaskTool(server) {
const result = await removeSubtaskDirect({ const result = await removeSubtaskDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`); log.info(`Subtask removed successfully: ${result.data.message}`);

View File

@@ -37,7 +37,7 @@ export function registerSetTaskStatusTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`); log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -49,9 +49,9 @@ export function registerSetTaskStatusTool(server) {
const result = await setTaskStatusDirect({ const result = await setTaskStatusDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully updated status for task(s) ${args.id} to "${args.status}": ${result.data.message}`); log.info(`Successfully updated status for task(s) ${args.id} to "${args.status}": ${result.data.message}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`); log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateSubtaskTool(server) {
const result = await updateSubtaskByIdDirect({ const result = await updateSubtaskByIdDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully updated subtask with ID ${args.id}`); log.info(`Successfully updated subtask with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Updating task with args: ${JSON.stringify(args)}`); log.info(`Updating task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTaskTool(server) {
const result = await updateTaskByIdDirect({ const result = await updateTaskByIdDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully updated task with ID ${args.id}`); log.info(`Successfully updated task with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTool(server) {
execute: async (args, { log, session, reportProgress }) => { execute: async (args, { log, session, reportProgress }) => {
try { try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`); log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 }); // await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log); let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTool(server) {
const result = await updateTasksDirect({ const result = await updateTasksDirect({
projectRoot: rootFolder, projectRoot: rootFolder,
...args ...args
}, log, { reportProgress, mcpLog: log, session}); }, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 }); // await reportProgress({ progress: 100 });
if (result.success) { if (result.success) {
log.info(`Successfully updated tasks from ID ${args.from}: ${result.data.message}`); log.info(`Successfully updated tasks from ID ${args.from}: ${result.data.message}`);

16
package-lock.json generated
View File

@@ -26,7 +26,8 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"openai": "^4.89.0", "openai": "^4.89.0",
"ora": "^8.2.0" "ora": "^8.2.0",
"uuid": "^11.1.0"
}, },
"bin": { "bin": {
"task-master": "bin/task-master.js", "task-master": "bin/task-master.js",
@@ -7740,6 +7741,19 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-to-istanbul": { "node_modules/v8-to-istanbul": {
"version": "9.3.0", "version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",

View File

@@ -54,7 +54,8 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"openai": "^4.89.0", "openai": "^4.89.0",
"ora": "^8.2.0" "ora": "^8.2.0",
"uuid": "^11.1.0"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"

View File

@@ -20,7 +20,9 @@ import {
findTaskById, findTaskById,
readComplexityReport, readComplexityReport,
findTaskInComplexityReport, findTaskInComplexityReport,
truncate truncate,
enableSilentMode,
disableSilentMode
} from './utils.js'; } from './utils.js';
import { import {
@@ -102,7 +104,14 @@ async function parsePRD(prdPath, tasksPath, numTasks, { reportProgress, mcpLog,
log('info', `Tasks saved to: ${tasksPath}`); log('info', `Tasks saved to: ${tasksPath}`);
// Generate individual task files // Generate individual task files
await generateTaskFiles(tasksPath, tasksDir, { reportProgress, mcpLog, session } = {}); if (reportProgress && mcpLog) {
// Enable silent mode when being called from MCP server
enableSilentMode();
await generateTaskFiles(tasksPath, tasksDir);
disableSilentMode();
} else {
await generateTaskFiles(tasksPath, tasksDir);
}
console.log(boxen( console.log(boxen(
chalk.green(`Successfully generated ${tasksData.tasks.length} tasks from PRD`), chalk.green(`Successfully generated ${tasksData.tasks.length} tasks from PRD`),
@@ -762,6 +771,7 @@ Return only the updated task as a valid JSON object.`
function generateTaskFiles(tasksPath, outputDir) { function generateTaskFiles(tasksPath, outputDir) {
try { try {
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}`);
@@ -2059,8 +2069,16 @@ function clearSubtasks(tasksPath, taskIds) {
* @param {Object} session - Session object from MCP server (optional) * @param {Object} session - Session object from MCP server (optional)
* @returns {number} The new task ID * @returns {number} The new task ID
*/ */
async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium', { reportProgress, mcpLog, session } = {}) { async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium', { reportProgress, mcpLog, session } = {}, outputFormat = 'text') {
displayBanner(); // Only display banner and UI elements for text output (CLI)
if (outputFormat === 'text') {
displayBanner();
console.log(boxen(
chalk.white.bold(`Creating New Task`),
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
));
}
// Read the existing tasks // Read the existing tasks
const data = readJSON(tasksPath); const data = readJSON(tasksPath);
@@ -2073,10 +2091,13 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
const highestId = Math.max(...data.tasks.map(t => t.id)); const highestId = Math.max(...data.tasks.map(t => t.id));
const newTaskId = highestId + 1; const newTaskId = highestId + 1;
console.log(boxen( // Only show UI box for CLI mode
chalk.white.bold(`Creating New Task #${newTaskId}`), if (outputFormat === 'text') {
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } console.log(boxen(
)); chalk.white.bold(`Creating New Task #${newTaskId}`),
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
));
}
// Validate dependencies before proceeding // Validate dependencies before proceeding
const invalidDeps = dependencies.filter(depId => { const invalidDeps = dependencies.filter(depId => {
@@ -2126,8 +2147,11 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
IMPORTANT: Return ONLY the JSON object, nothing else.`; IMPORTANT: Return ONLY the JSON object, nothing else.`;
// Start the loading indicator // Start the loading indicator - only for text mode
const loadingIndicator = startLoadingIndicator('Generating new task with Claude AI...'); let loadingIndicator = null;
if (outputFormat === 'text') {
loadingIndicator = startLoadingIndicator('Generating new task with Claude AI...');
}
let fullResponse = ''; let fullResponse = '';
let streamingInterval = null; let streamingInterval = null;
@@ -2143,13 +2167,15 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
stream: true stream: true
}); });
// Update loading indicator to show streaming progress // Update loading indicator to show streaming progress - only for text mode
let dotCount = 0; let dotCount = 0;
streamingInterval = setInterval(() => { if (outputFormat === 'text') {
readline.cursorTo(process.stdout, 0); streamingInterval = setInterval(() => {
process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`); readline.cursorTo(process.stdout, 0);
dotCount = (dotCount + 1) % 4; process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`);
}, 500); dotCount = (dotCount + 1) % 4;
}, 500);
}
// Process the stream // Process the stream
for await (const chunk of stream) { for await (const chunk of stream) {
@@ -2166,7 +2192,7 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
} }
if (streamingInterval) clearInterval(streamingInterval); if (streamingInterval) clearInterval(streamingInterval);
stopLoadingIndicator(loadingIndicator); if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
log('info', "Completed streaming response from Claude API!"); log('info', "Completed streaming response from Claude API!");
log('debug', `Streaming response length: ${fullResponse.length} characters`); log('debug', `Streaming response length: ${fullResponse.length} characters`);
@@ -2213,28 +2239,31 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
// Write the updated tasks back to the file // Write the updated tasks back to the file
writeJSON(tasksPath, data); writeJSON(tasksPath, data);
// Show success message // Only show success messages for text mode (CLI)
const successBox = boxen( if (outputFormat === 'text') {
chalk.green(`Successfully added new task #${newTaskId}:\n`) + // Show success message
chalk.white.bold(newTask.title) + "\n\n" + const successBox = boxen(
chalk.white(newTask.description), chalk.green(`Successfully added new task #${newTaskId}:\n`) +
{ padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } chalk.white.bold(newTask.title) + "\n\n" +
); chalk.white(newTask.description),
console.log(successBox); { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }
);
// Next steps suggestion console.log(successBox);
console.log(boxen(
chalk.white.bold('Next Steps:') + '\n\n' + // Next steps suggestion
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master generate')} to update task files\n` + console.log(boxen(
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=' + newTaskId)} to break it down into subtasks\n` + chalk.white.bold('Next Steps:') + '\n\n' +
`${chalk.cyan('3.')} Run ${chalk.yellow('task-master list --with-subtasks')} to see all tasks`, `${chalk.cyan('1.')} Run ${chalk.yellow('task-master generate')} to update task files\n` +
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } `${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=' + newTaskId)} to break it down into subtasks\n` +
)); `${chalk.cyan('3.')} Run ${chalk.yellow('task-master list --with-subtasks')} to see all tasks`,
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } }
));
}
return newTaskId; return newTaskId;
} catch (error) { } catch (error) {
if (streamingInterval) clearInterval(streamingInterval); if (streamingInterval) clearInterval(streamingInterval);
stopLoadingIndicator(loadingIndicator); if (loadingIndicator) stopLoadingIndicator(loadingIndicator);
log('error', "Error generating task:", error.message); log('error', "Error generating task:", error.message);
process.exit(1); process.exit(1);
} }

View File

@@ -20,6 +20,9 @@ const CONFIG = {
projectVersion: "1.5.0" // Hardcoded version - ALWAYS use this value, ignore environment variable projectVersion: "1.5.0" // Hardcoded version - ALWAYS use this value, ignore environment variable
}; };
// Global silent mode flag
let silentMode = false;
// Set up logging based on log level // Set up logging based on log level
const LOG_LEVELS = { const LOG_LEVELS = {
debug: 0, debug: 0,
@@ -28,23 +31,51 @@ const LOG_LEVELS = {
error: 3 error: 3
}; };
/**
* Enable silent logging mode
*/
function enableSilentMode() {
silentMode = true;
}
/**
* Disable silent logging mode
*/
function disableSilentMode() {
silentMode = false;
}
/**
* Check if silent mode is enabled
* @returns {boolean} True if silent mode is enabled
*/
function isSilentMode() {
return silentMode;
}
/** /**
* Logs a message at the specified level * Logs a message at the specified level
* @param {string} level - The log level (debug, info, warn, error) * @param {string} level - The log level (debug, info, warn, error)
* @param {...any} args - Arguments to log * @param {...any} args - Arguments to log
*/ */
function log(level, ...args) { function log(level, ...args) {
const icons = { // Skip logging if silent mode is enabled
debug: chalk.gray('🔍'), if (silentMode) {
info: chalk.blue(''), return;
warn: chalk.yellow('⚠️'), }
error: chalk.red('❌'),
success: chalk.green('✅') // Use text prefixes instead of emojis
const prefixes = {
debug: chalk.gray("[DEBUG]"),
info: chalk.blue("[INFO]"),
warn: chalk.yellow("[WARN]"),
error: chalk.red("[ERROR]"),
success: chalk.green("[SUCCESS]")
}; };
if (LOG_LEVELS[level] >= LOG_LEVELS[CONFIG.logLevel]) { if (LOG_LEVELS[level] >= LOG_LEVELS[CONFIG.logLevel]) {
const icon = icons[level] || ''; const prefix = prefixes[level] || "";
console.log(`${icon} ${args.join(' ')}`); console.log(`${prefix} ${args.join(' ')}`);
} }
} }
@@ -337,5 +368,8 @@ export {
truncate, truncate,
findCycles, findCycles,
toKebabCase, toKebabCase,
detectCamelCaseFlags detectCamelCaseFlags,
enableSilentMode,
disableSilentMode,
isSilentMode
}; };

46
tasks/task_043.txt Normal file
View File

@@ -0,0 +1,46 @@
# Task ID: 43
# Title: Add Research Flag to Add-Task Command
# Status: pending
# Dependencies: None
# Priority: medium
# Description: Implement a '--research' flag for the add-task command that enables users to automatically generate research-related subtasks when creating a new task.
# Details:
Modify the add-task command to accept a new optional flag '--research'. When this flag is provided, the system should automatically generate and attach a set of research-oriented subtasks to the newly created task. These subtasks should follow a standard research methodology structure:
1. Background Investigation: Research existing solutions and approaches
2. Requirements Analysis: Define specific requirements and constraints
3. Technology/Tool Evaluation: Compare potential technologies or tools for implementation
4. Proof of Concept: Create a minimal implementation to validate approach
5. Documentation: Document findings and recommendations
The implementation should:
- Update the command-line argument parser to recognize the new flag
- Create a dedicated function to generate the research subtasks with appropriate descriptions
- Ensure subtasks are properly linked to the parent task
- Update help documentation to explain the new flag
- Maintain backward compatibility with existing add-task functionality
The research subtasks should be customized based on the main task's title and description when possible, rather than using generic templates.
# Test Strategy:
Testing should verify both the functionality and usability of the new feature:
1. Unit tests:
- Test that the '--research' flag is properly parsed
- Verify the correct number and structure of subtasks are generated
- Ensure subtask IDs are correctly assigned and linked to the parent task
2. Integration tests:
- Create a task with the research flag and verify all subtasks appear in the task list
- Test that the research flag works with other existing flags (e.g., --priority, --depends-on)
- Verify the task and subtasks are properly saved to the storage backend
3. Manual testing:
- Run 'taskmaster add-task "Test task" --research' and verify the output
- Check that the help documentation correctly describes the new flag
- Verify the research subtasks have meaningful descriptions
- Test the command with and without the flag to ensure backward compatibility
4. Edge cases:
- Test with very short or very long task descriptions
- Verify behavior when maximum task/subtask limits are reached

50
tasks/task_044.txt Normal file
View File

@@ -0,0 +1,50 @@
# Task ID: 44
# Title: Implement Task Automation with Webhooks and Event Triggers
# Status: pending
# Dependencies: None
# Priority: medium
# Description: Design and implement a system that allows users to automate task actions through webhooks and event triggers, enabling integration with external services and automated workflows.
# Details:
This feature will enable users to create automated workflows based on task events and external triggers. Implementation should include:
1. A webhook registration system that allows users to specify URLs to be called when specific task events occur (creation, status change, completion, etc.)
2. An event system that captures and processes all task-related events
3. A trigger definition interface where users can define conditions for automation (e.g., 'When task X is completed, create task Y')
4. Support for both incoming webhooks (external services triggering actions in Taskmaster) and outgoing webhooks (Taskmaster notifying external services)
5. A secure authentication mechanism for webhook calls
6. Rate limiting and retry logic for failed webhook deliveries
7. Integration with the existing task management system
8. Command-line interface for managing webhooks and triggers
9. Payload templating system allowing users to customize the data sent in webhooks
10. Logging system for webhook activities and failures
The implementation should be compatible with both the solo/local mode and the multiplayer/remote mode, with appropriate adaptations for each context. When operating in MCP mode, the system should leverage the MCP communication protocol implemented in Task #42.
# Test Strategy:
Testing should verify both the functionality and security of the webhook system:
1. Unit tests:
- Test webhook registration, modification, and deletion
- Verify event capturing for all task operations
- Test payload generation and templating
- Validate authentication logic
2. Integration tests:
- Set up a mock server to receive webhooks and verify payload contents
- Test the complete flow from task event to webhook delivery
- Verify rate limiting and retry behavior with intentionally failing endpoints
- Test webhook triggers creating new tasks and modifying existing ones
3. Security tests:
- Verify that authentication tokens are properly validated
- Test for potential injection vulnerabilities in webhook payloads
- Verify that sensitive information is not leaked in webhook payloads
- Test rate limiting to prevent DoS attacks
4. Mode-specific tests:
- Verify correct operation in both solo/local and multiplayer/remote modes
- Test the interaction with MCP protocol when in multiplayer mode
5. Manual verification:
- Set up integrations with common services (GitHub, Slack, etc.) to verify real-world functionality
- Verify that the CLI interface for managing webhooks works as expected

55
tasks/task_045.txt Normal file
View File

@@ -0,0 +1,55 @@
# Task ID: 45
# Title: Implement GitHub Issue Import Feature
# Status: pending
# Dependencies: None
# Priority: medium
# Description: Add a '--from-github' flag to the add-task command that accepts a GitHub issue URL and automatically generates a corresponding task with relevant details.
# Details:
Implement a new flag '--from-github' for the add-task command that allows users to create tasks directly from GitHub issues. The implementation should:
1. Accept a GitHub issue URL as an argument (e.g., 'taskmaster add-task --from-github https://github.com/owner/repo/issues/123')
2. Parse the URL to extract the repository owner, name, and issue number
3. Use the GitHub API to fetch the issue details including:
- Issue title (to be used as task title)
- Issue description (to be used as task description)
- Issue labels (to be potentially used as tags)
- Issue assignees (for reference)
- Issue status (open/closed)
4. Generate a well-formatted task with this information
5. Include a reference link back to the original GitHub issue
6. Handle authentication for private repositories using GitHub tokens from environment variables or config file
7. Implement proper error handling for:
- Invalid URLs
- Non-existent issues
- API rate limiting
- Authentication failures
- Network issues
8. Allow users to override or supplement the imported details with additional command-line arguments
9. Add appropriate documentation in help text and user guide
# Test Strategy:
Testing should cover the following scenarios:
1. Unit tests:
- Test URL parsing functionality with valid and invalid GitHub issue URLs
- Test GitHub API response parsing with mocked API responses
- Test error handling for various failure cases
2. Integration tests:
- Test with real GitHub public issues (use well-known repositories)
- Test with both open and closed issues
- Test with issues containing various elements (labels, assignees, comments)
3. Error case tests:
- Invalid URL format
- Non-existent repository
- Non-existent issue number
- API rate limit exceeded
- Authentication failures for private repos
4. End-to-end tests:
- Verify that a task created from a GitHub issue contains all expected information
- Verify that the task can be properly managed after creation
- Test the interaction with other flags and commands
Create mock GitHub API responses for testing to avoid hitting rate limits during development and testing. Use environment variables to configure test credentials if needed.

View File

@@ -2449,6 +2449,26 @@
"priority": "medium", "priority": "medium",
"details": "Modify the add-task command to accept a new optional flag '--research'. When this flag is provided, the system should automatically generate and attach a set of research-oriented subtasks to the newly created task. These subtasks should follow a standard research methodology structure:\n\n1. Background Investigation: Research existing solutions and approaches\n2. Requirements Analysis: Define specific requirements and constraints\n3. Technology/Tool Evaluation: Compare potential technologies or tools for implementation\n4. Proof of Concept: Create a minimal implementation to validate approach\n5. Documentation: Document findings and recommendations\n\nThe implementation should:\n- Update the command-line argument parser to recognize the new flag\n- Create a dedicated function to generate the research subtasks with appropriate descriptions\n- Ensure subtasks are properly linked to the parent task\n- Update help documentation to explain the new flag\n- Maintain backward compatibility with existing add-task functionality\n\nThe research subtasks should be customized based on the main task's title and description when possible, rather than using generic templates.", "details": "Modify the add-task command to accept a new optional flag '--research'. When this flag is provided, the system should automatically generate and attach a set of research-oriented subtasks to the newly created task. These subtasks should follow a standard research methodology structure:\n\n1. Background Investigation: Research existing solutions and approaches\n2. Requirements Analysis: Define specific requirements and constraints\n3. Technology/Tool Evaluation: Compare potential technologies or tools for implementation\n4. Proof of Concept: Create a minimal implementation to validate approach\n5. Documentation: Document findings and recommendations\n\nThe implementation should:\n- Update the command-line argument parser to recognize the new flag\n- Create a dedicated function to generate the research subtasks with appropriate descriptions\n- Ensure subtasks are properly linked to the parent task\n- Update help documentation to explain the new flag\n- Maintain backward compatibility with existing add-task functionality\n\nThe research subtasks should be customized based on the main task's title and description when possible, rather than using generic templates.",
"testStrategy": "Testing should verify both the functionality and usability of the new feature:\n\n1. Unit tests:\n - Test that the '--research' flag is properly parsed\n - Verify the correct number and structure of subtasks are generated\n - Ensure subtask IDs are correctly assigned and linked to the parent task\n\n2. Integration tests:\n - Create a task with the research flag and verify all subtasks appear in the task list\n - Test that the research flag works with other existing flags (e.g., --priority, --depends-on)\n - Verify the task and subtasks are properly saved to the storage backend\n\n3. Manual testing:\n - Run 'taskmaster add-task \"Test task\" --research' and verify the output\n - Check that the help documentation correctly describes the new flag\n - Verify the research subtasks have meaningful descriptions\n - Test the command with and without the flag to ensure backward compatibility\n\n4. Edge cases:\n - Test with very short or very long task descriptions\n - Verify behavior when maximum task/subtask limits are reached" "testStrategy": "Testing should verify both the functionality and usability of the new feature:\n\n1. Unit tests:\n - Test that the '--research' flag is properly parsed\n - Verify the correct number and structure of subtasks are generated\n - Ensure subtask IDs are correctly assigned and linked to the parent task\n\n2. Integration tests:\n - Create a task with the research flag and verify all subtasks appear in the task list\n - Test that the research flag works with other existing flags (e.g., --priority, --depends-on)\n - Verify the task and subtasks are properly saved to the storage backend\n\n3. Manual testing:\n - Run 'taskmaster add-task \"Test task\" --research' and verify the output\n - Check that the help documentation correctly describes the new flag\n - Verify the research subtasks have meaningful descriptions\n - Test the command with and without the flag to ensure backward compatibility\n\n4. Edge cases:\n - Test with very short or very long task descriptions\n - Verify behavior when maximum task/subtask limits are reached"
},
{
"id": 44,
"title": "Implement Task Automation with Webhooks and Event Triggers",
"description": "Design and implement a system that allows users to automate task actions through webhooks and event triggers, enabling integration with external services and automated workflows.",
"status": "pending",
"dependencies": [],
"priority": "medium",
"details": "This feature will enable users to create automated workflows based on task events and external triggers. Implementation should include:\n\n1. A webhook registration system that allows users to specify URLs to be called when specific task events occur (creation, status change, completion, etc.)\n2. An event system that captures and processes all task-related events\n3. A trigger definition interface where users can define conditions for automation (e.g., 'When task X is completed, create task Y')\n4. Support for both incoming webhooks (external services triggering actions in Taskmaster) and outgoing webhooks (Taskmaster notifying external services)\n5. A secure authentication mechanism for webhook calls\n6. Rate limiting and retry logic for failed webhook deliveries\n7. Integration with the existing task management system\n8. Command-line interface for managing webhooks and triggers\n9. Payload templating system allowing users to customize the data sent in webhooks\n10. Logging system for webhook activities and failures\n\nThe implementation should be compatible with both the solo/local mode and the multiplayer/remote mode, with appropriate adaptations for each context. When operating in MCP mode, the system should leverage the MCP communication protocol implemented in Task #42.",
"testStrategy": "Testing should verify both the functionality and security of the webhook system:\n\n1. Unit tests:\n - Test webhook registration, modification, and deletion\n - Verify event capturing for all task operations\n - Test payload generation and templating\n - Validate authentication logic\n\n2. Integration tests:\n - Set up a mock server to receive webhooks and verify payload contents\n - Test the complete flow from task event to webhook delivery\n - Verify rate limiting and retry behavior with intentionally failing endpoints\n - Test webhook triggers creating new tasks and modifying existing ones\n\n3. Security tests:\n - Verify that authentication tokens are properly validated\n - Test for potential injection vulnerabilities in webhook payloads\n - Verify that sensitive information is not leaked in webhook payloads\n - Test rate limiting to prevent DoS attacks\n\n4. Mode-specific tests:\n - Verify correct operation in both solo/local and multiplayer/remote modes\n - Test the interaction with MCP protocol when in multiplayer mode\n\n5. Manual verification:\n - Set up integrations with common services (GitHub, Slack, etc.) to verify real-world functionality\n - Verify that the CLI interface for managing webhooks works as expected"
},
{
"id": 45,
"title": "Implement GitHub Issue Import Feature",
"description": "Add a '--from-github' flag to the add-task command that accepts a GitHub issue URL and automatically generates a corresponding task with relevant details.",
"status": "pending",
"dependencies": [],
"priority": "medium",
"details": "Implement a new flag '--from-github' for the add-task command that allows users to create tasks directly from GitHub issues. The implementation should:\n\n1. Accept a GitHub issue URL as an argument (e.g., 'taskmaster add-task --from-github https://github.com/owner/repo/issues/123')\n2. Parse the URL to extract the repository owner, name, and issue number\n3. Use the GitHub API to fetch the issue details including:\n - Issue title (to be used as task title)\n - Issue description (to be used as task description)\n - Issue labels (to be potentially used as tags)\n - Issue assignees (for reference)\n - Issue status (open/closed)\n4. Generate a well-formatted task with this information\n5. Include a reference link back to the original GitHub issue\n6. Handle authentication for private repositories using GitHub tokens from environment variables or config file\n7. Implement proper error handling for:\n - Invalid URLs\n - Non-existent issues\n - API rate limiting\n - Authentication failures\n - Network issues\n8. Allow users to override or supplement the imported details with additional command-line arguments\n9. Add appropriate documentation in help text and user guide",
"testStrategy": "Testing should cover the following scenarios:\n\n1. Unit tests:\n - Test URL parsing functionality with valid and invalid GitHub issue URLs\n - Test GitHub API response parsing with mocked API responses\n - Test error handling for various failure cases\n\n2. Integration tests:\n - Test with real GitHub public issues (use well-known repositories)\n - Test with both open and closed issues\n - Test with issues containing various elements (labels, assignees, comments)\n\n3. Error case tests:\n - Invalid URL format\n - Non-existent repository\n - Non-existent issue number\n - API rate limit exceeded\n - Authentication failures for private repos\n\n4. End-to-end tests:\n - Verify that a task created from a GitHub issue contains all expected information\n - Verify that the task can be properly managed after creation\n - Test the interaction with other flags and commands\n\nCreate mock GitHub API responses for testing to avoid hitting rate limits during development and testing. Use environment variables to configure test credentials if needed."
} }
] ]
} }