chore: changeset + update rules.
This commit is contained in:
@@ -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 '/'.**
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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**:
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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' } };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
217
mcp-server/src/core/utils/async-manager.js
Normal file
217
mcp-server/src/core/utils/async-manager.js
Normal 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 };
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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)' : ''}`);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
42
mcp-server/src/tools/get-operation-status.js
Normal file
42
mcp-server/src/tools/get-operation-status.js
Normal 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');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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
16
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
46
tasks/task_043.txt
Normal 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
50
tasks/task_044.txt
Normal 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
55
tasks/task_045.txt
Normal 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.
|
||||||
@@ -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."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user