fix: Improve MCP server robustness and debugging
- Refactor for more reliable project root detection, particularly when running within integrated environments like Cursor IDE. Includes deriving root from script path and avoiding fallback to '/'.
- Enhance error handling in :
- Add detailed debug information (paths searched, CWD, etc.) to the error message when is not found in the provided project root.
- Improve clarity of error messages and potential solutions.
- Add verbose logging in to trace session object content and the finally resolved project root path, aiding in debugging path-related issues.
- Add default values for and to the example environment configuration.
This commit is contained in:
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
- **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.
|
- **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 '/'.**
|
||||||
- **Simplify `findTasksJsonPath`**: The core path finding utility in `mcp-server/src/core/utils/path-utils.js` now prioritizes the `projectRoot` passed in `args` (originating from the session). Removed checks for `TASK_MASTER_PROJECT_ROOT` env var and package directory fallback.
|
- **Simplify `findTasksJsonPath`**: The core path finding utility in `mcp-server/src/core/utils/path-utils.js` now prioritizes the `projectRoot` passed in `args` (originating from the session). Removed checks for `TASK_MASTER_PROJECT_ROOT` env var (we do not use this anymore) and package directory fallback. **Enhanced error handling to include detailed debug information (paths searched, CWD, server dir, etc.) and clearer potential solutions when `tasks.json` is not found.**
|
||||||
- **Retain CLI Fallbacks**: Kept `lastFoundProjectRoot` cache check and CWD search in `findTasksJsonPath` for compatibility with direct CLI usage.
|
- **Retain CLI Fallbacks**: Kept `lastFoundProjectRoot` cache check and CWD search in `findTasksJsonPath` for compatibility with direct CLI usage.
|
||||||
|
|
||||||
- Updated all MCP tools to use the new project root handling:
|
- Updated all MCP tools to use the new project root handling:
|
||||||
@@ -22,7 +22,10 @@
|
|||||||
|
|
||||||
- Add comprehensive PROJECT_MARKERS array for detecting common project files (used in CLI fallback logic).
|
- Add comprehensive PROJECT_MARKERS array for detecting common project files (used in CLI fallback logic).
|
||||||
- Improved error messages with specific troubleshooting guidance.
|
- Improved error messages with specific troubleshooting guidance.
|
||||||
- Enhanced logging to indicate the source of project root selection.
|
- **Enhanced logging:**
|
||||||
|
- Indicate the source of project root selection more clearly.
|
||||||
|
- **Add verbose logging in `get-task.js` to trace session object content and resolved project root path, aiding debugging.**
|
||||||
|
|
||||||
- DRY refactoring by centralizing path utilities in `core/utils/path-utils.js` and session handling in `tools/utils.js`.
|
- DRY refactoring by centralizing path utilities in `core/utils/path-utils.js` and session handling in `tools/utils.js`.
|
||||||
- Keep caching of `lastFoundProjectRoot` for CLI performance.
|
- Keep caching of `lastFoundProjectRoot` for CLI performance.
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@
|
|||||||
- Update tool descriptions to better reflect their actual behavior and capabilities.
|
- Update tool descriptions to better reflect their actual behavior and capabilities.
|
||||||
- Add cross-references between related tools and commands.
|
- Add cross-references between related tools and commands.
|
||||||
- Include troubleshooting guidance in tool descriptions.
|
- Include troubleshooting guidance in tool descriptions.
|
||||||
|
- **Add default values for `DEFAULT_SUBTASKS` and `DEFAULT_PRIORITY` to the example `.cursor/mcp.json` configuration.**
|
||||||
|
|
||||||
- Document MCP server naming conventions in architecture.mdc and mcp.mdc files (file names use kebab-case, direct functions use camelCase with Direct suffix, tool registration functions use camelCase with Tool suffix, and MCP tool names use snake_case).
|
- Document MCP server naming conventions in architecture.mdc and mcp.mdc files (file names use kebab-case, direct functions use camelCase with Direct suffix, tool registration functions use camelCase with Tool suffix, and MCP tool names use snake_case).
|
||||||
- Update MCP tool naming to follow more intuitive conventions that better align with natural language requests in client chat applications.
|
- Update MCP tool naming to follow more intuitive conventions that better align with natural language requests in client chat applications.
|
||||||
|
|||||||
@@ -4,7 +4,17 @@
|
|||||||
"command": "node",
|
"command": "node",
|
||||||
"args": [
|
"args": [
|
||||||
"./mcp-server/server.js"
|
"./mcp-server/server.js"
|
||||||
]
|
],
|
||||||
|
"env": {
|
||||||
|
"ANTHROPIC_API_KEY": "%ANTHROPIC_API_KEY%",
|
||||||
|
"PERPLEXITY_API_KEY": "%PERPLEXITY_API_KEY%",
|
||||||
|
"MODEL": "%MODEL%",
|
||||||
|
"PERPLEXITY_MODEL": "%PERPLEXITY_MODEL%",
|
||||||
|
"MAX_TOKENS": "%MAX_TOKENS%",
|
||||||
|
"TEMPERATURE": "%TEMPERATURE%",
|
||||||
|
"DEFAULT_SUBTASKS": 5,
|
||||||
|
"DEFAULT_PRIORITY": "medium"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,8 +91,23 @@ export function findTasksJsonPath(args, log) {
|
|||||||
if (args.projectRoot) {
|
if (args.projectRoot) {
|
||||||
const projectRoot = args.projectRoot;
|
const projectRoot = args.projectRoot;
|
||||||
log.info(`Using explicitly provided project root: ${projectRoot}`);
|
log.info(`Using explicitly provided project root: ${projectRoot}`);
|
||||||
|
try {
|
||||||
// This will throw if tasks.json isn't found within this root
|
// This will throw if tasks.json isn't found within this root
|
||||||
return findTasksJsonInDirectory(projectRoot, args.file, log);
|
return findTasksJsonInDirectory(projectRoot, args.file, log);
|
||||||
|
} catch (error) {
|
||||||
|
// Include debug info in error
|
||||||
|
const debugInfo = {
|
||||||
|
projectRoot,
|
||||||
|
currentDir: process.cwd(),
|
||||||
|
serverDir: path.dirname(process.argv[1]),
|
||||||
|
possibleProjectRoot: path.resolve(path.dirname(process.argv[1]), '../..'),
|
||||||
|
lastFoundProjectRoot,
|
||||||
|
searchedPaths: error.message
|
||||||
|
};
|
||||||
|
|
||||||
|
error.message = `Tasks file not found in any of the expected locations relative to project root "${projectRoot}" (from session).\nDebug Info: ${JSON.stringify(debugInfo, null, 2)}`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Fallback logic primarily for CLI or when projectRoot isn't passed ---
|
// --- Fallback logic primarily for CLI or when projectRoot isn't passed ---
|
||||||
@@ -120,7 +135,7 @@ export function findTasksJsonPath(args, log) {
|
|||||||
return findTasksJsonWithParentSearch(startDir, args.file, log);
|
return findTasksJsonWithParentSearch(startDir, args.file, log);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If all attempts fail, augment and throw the original error from CWD search
|
// If all attempts fail, augment and throw the original error from CWD search
|
||||||
error.message = `${error.message}\n\nPossible solutions:\n1. Run the command from your project directory containing tasks.json\n2. Use --project-root=/path/to/project to specify the project location (if using CLI)\n3. Ensure the project root is correctly passed from the client (if using MCP)`;
|
error.message = `${error.message}\n\nPossible solutions:\n1. Run the command from your project directory containing tasks.json\n2. Use --project-root=/path/to/project to specify the project location (if using CLI)\n3. Ensure the project root is correctly passed from the client (if using MCP)\n\nCurrent working directory: ${startDir}\nLast known project root: ${lastFoundProjectRoot}\nProject root from args: ${args.projectRoot}`;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function registerAddDependencyTool(server) {
|
|||||||
const result = await addDependencyDirect({
|
const result = await addDependencyDirect({
|
||||||
projectRoot: rootFolder,
|
projectRoot: rootFolder,
|
||||||
...args
|
...args
|
||||||
}, log);
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
reportProgress({ progress: 100 });
|
reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addSubtaskDirect } from "../core/task-master-core.js";
|
import { addSubtaskDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -30,21 +31,28 @@ export function registerAddSubtaskTool(server) {
|
|||||||
skipGenerate: z.boolean().optional().describe("Skip regenerating task files"),
|
skipGenerate: z.boolean().optional().describe("Skip regenerating task files"),
|
||||||
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 }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
|
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await addSubtaskDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await addSubtaskDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Subtask added successfully: ${result.data.message}`);
|
log.info(`Subtask added successfully: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to add subtask: ${result.error.message}`);
|
log.error(`Failed to add subtask: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error adding subtask');
|
return handleApiResult(result, log, 'Error adding subtask');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in addSubtask tool: ${error.message}`);
|
log.error(`Error in addSubtask tool: ${error.message}`);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function registerAddTaskTool(server) {
|
|||||||
const result = await addTaskDirect({
|
const result = await addTaskDirect({
|
||||||
projectRoot: rootFolder, // Pass the resolved root
|
projectRoot: rootFolder, // Pass the resolved root
|
||||||
...args
|
...args
|
||||||
}, log);
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
return handleApiResult(result, log);
|
return handleApiResult(result, log);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { analyzeTaskComplexityDirect } from "../core/task-master-core.js";
|
import { analyzeTaskComplexityDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -26,14 +27,25 @@ export function registerAnalyzeTool(server) {
|
|||||||
research: z.boolean().optional().describe("Use Perplexity AI for research-backed complexity analysis"),
|
research: z.boolean().optional().describe("Use Perplexity AI for research-backed complexity analysis"),
|
||||||
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 }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await analyzeTaskComplexityDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await analyzeTaskComplexityDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Task complexity analysis complete: ${result.data.message}`);
|
log.info(`Task complexity analysis complete: ${result.data.message}`);
|
||||||
log.info(`Report summary: ${JSON.stringify(result.data.reportSummary)}`);
|
log.info(`Report summary: ${JSON.stringify(result.data.reportSummary)}`);
|
||||||
@@ -41,7 +53,6 @@ export function registerAnalyzeTool(server) {
|
|||||||
log.error(`Failed to analyze task complexity: ${result.error.message}`);
|
log.error(`Failed to analyze task complexity: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error analyzing task complexity');
|
return handleApiResult(result, log, 'Error analyzing task complexity');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in analyze tool: ${error.message}`);
|
log.error(`Error in analyze tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { clearSubtasksDirect } from "../core/task-master-core.js";
|
import { clearSubtasksDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -27,21 +28,31 @@ export function registerClearSubtasksTool(server) {
|
|||||||
message: "Either 'id' or 'all' parameter must be provided",
|
message: "Either 'id' or 'all' parameter must be provided",
|
||||||
path: ["id", "all"]
|
path: ["id", "all"]
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
|
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
|
||||||
|
await reportProgress({ progress: 0 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await clearSubtasksDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await clearSubtasksDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Subtasks cleared successfully: ${result.data.message}`);
|
log.info(`Subtasks cleared successfully: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to clear subtasks: ${result.error.message}`);
|
log.error(`Failed to clear subtasks: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error clearing subtasks');
|
return handleApiResult(result, log, 'Error clearing subtasks');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in clearSubtasks tool: ${error.message}`);
|
log.error(`Error in clearSubtasks tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { complexityReportDirect } from "../core/task-master-core.js";
|
import { complexityReportDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -22,21 +23,31 @@ export function registerComplexityReportTool(server) {
|
|||||||
file: z.string().optional().describe("Path to the report file (default: scripts/task-complexity-report.json)"),
|
file: z.string().optional().describe("Path to the report file (default: scripts/task-complexity-report.json)"),
|
||||||
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 }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await complexityReportDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await complexityReportDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
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)' : ''}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to retrieve complexity report: ${result.error.message}`);
|
log.error(`Failed to retrieve complexity report: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error retrieving complexity report');
|
return handleApiResult(result, log, 'Error retrieving complexity report');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in complexity-report tool: ${error.message}`);
|
log.error(`Error in complexity-report tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { expandAllTasksDirect } from "../core/task-master-core.js";
|
import { expandAllTasksDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -26,21 +27,31 @@ export function registerExpandAllTool(server) {
|
|||||||
file: z.string().optional().describe("Path to the tasks file (default: tasks/tasks.json)"),
|
file: z.string().optional().describe("Path to the tasks file (default: tasks/tasks.json)"),
|
||||||
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 }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await expandAllTasksDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await expandAllTasksDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`All tasks expanded successfully: ${result.data.message}`);
|
log.info(`All tasks expanded successfully: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to expand tasks: ${result.error.message}`);
|
log.error(`Failed to expand tasks: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error expanding tasks');
|
return handleApiResult(result, log, 'Error expanding tasks');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in expandAll tool: ${error.message}`);
|
log.error(`Error in expandAll tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { expandTaskDirect } from "../core/task-master-core.js";
|
import { expandTaskDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -27,25 +28,36 @@ export function registerExpandTaskTool(server) {
|
|||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await expandTaskDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await expandTaskDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
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 ID: ${args.id} with ${result.data.subtasksAdded} new subtasks${result.data.hasExistingSubtasks ? ' (appended to existing subtasks)' : ''}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to expand task: ${result.error.message}`);
|
log.error(`Failed to expand task: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
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}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { fixDependenciesDirect } from "../core/task-master-core.js";
|
import { fixDependenciesDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -22,21 +23,31 @@ export function registerFixDependenciesTool(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 }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
|
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
|
||||||
|
await reportProgress({ progress: 0 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await fixDependenciesDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await fixDependenciesDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully fixed dependencies: ${result.data.message}`);
|
log.info(`Successfully fixed dependencies: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to fix dependencies: ${result.error.message}`);
|
log.error(`Failed to fix dependencies: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error fixing dependencies');
|
return handleApiResult(result, log, 'Error fixing dependencies');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in fixDependencies tool: ${error.message}`);
|
log.error(`Error in fixDependencies tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { generateTaskFilesDirect } from "../core/task-master-core.js";
|
import { generateTaskFilesDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -23,21 +24,36 @@ export function registerGenerateTool(server) {
|
|||||||
output: z.string().optional().describe("Output directory (default: same directory as tasks file)"),
|
output: z.string().optional().describe("Output directory (default: same directory as tasks file)"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await generateTaskFilesDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? 'Successfully generated task files' : 'Failed to generate task files'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await generateTaskFilesDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully generated task files: ${result.data.message}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to generate task files: ${result.error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error generating task files');
|
return handleApiResult(result, log, 'Error generating task files');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in generate tool: ${error.message}`);
|
log.error(`Error in generate tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { showTaskDirect } from "../core/task-master-core.js";
|
import { showTaskDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -28,24 +29,43 @@ export function registerShowTaskTool(server) {
|
|||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
|
// Log the session right at the start of execute
|
||||||
|
log.info(`Session object received in execute: ${JSON.stringify(session)}`); // Use JSON.stringify for better visibility
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info(`Getting task details for ID: ${args.id}`);
|
log.info(`Getting task details for ID: ${args.id}`);
|
||||||
|
|
||||||
// Call the direct function wrapper
|
log.info(`Session object received in execute: ${JSON.stringify(session)}`); // Use JSON.stringify for better visibility
|
||||||
const result = await showTaskDirect(args, log);
|
|
||||||
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
} else if (!rootFolder) {
|
||||||
|
// Ensure we always have *some* root, even if session failed and args didn't provide one
|
||||||
|
rootFolder = process.cwd();
|
||||||
|
log.warn(`Session and args failed to provide root, using CWD: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Attempting to use project root: ${rootFolder}`); // Log the final resolved root
|
||||||
|
|
||||||
|
log.info(`Root folder: ${rootFolder}`); // Log the final resolved root
|
||||||
|
const result = await showTaskDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log);
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}`);
|
log.info(`Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to get task: ${result.error.message}`);
|
log.error(`Failed to get task: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error retrieving task details');
|
return handleApiResult(result, log, 'Error retrieving task details');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in get-task tool: ${error.message}`);
|
log.error(`Error in get-task tool: ${error.message}\n${error.stack}`); // Add stack trace
|
||||||
return createErrorResponse(`Failed to get task: ${error.message}`);
|
return createErrorResponse(`Failed to get task: ${error.message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult
|
handleApiResult,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { listTasksDirect } from "../core/task-master-core.js";
|
import { listTasksDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -16,31 +17,42 @@ import { listTasksDirect } from "../core/task-master-core.js";
|
|||||||
*/
|
*/
|
||||||
export function registerListTasksTool(server) {
|
export function registerListTasksTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "get-tasks",
|
name: "get_tasks",
|
||||||
description: "Get all tasks from Task Master",
|
description: "Get all tasks from Task Master, optionally filtering by status and including subtasks.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
status: z.string().optional().describe("Filter tasks by status"),
|
status: z.string().optional().describe("Filter tasks by status (e.g., 'pending', 'done')"),
|
||||||
withSubtasks: z
|
withSubtasks: z
|
||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Include subtasks in the response"),
|
.describe("Include subtasks nested within their parent tasks in the response"),
|
||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file (relative to project root or absolute)"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Root directory of the project (default: automatically detected)"
|
"Root directory of the project (default: automatically detected from session or CWD)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call core function - args contains projectRoot which is handled internally
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await listTasksDirect(args, log);
|
|
||||||
|
|
||||||
// Log result and use handleApiResult utility
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await listTasksDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log);
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
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');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error getting tasks: ${error.message}`);
|
log.error(`Error getting tasks: ${error.message}`);
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ import { registerAddDependencyTool } from "./add-dependency.js";
|
|||||||
* @param {Object} server - FastMCP server instance
|
* @param {Object} server - FastMCP server instance
|
||||||
*/
|
*/
|
||||||
export function registerTaskMasterTools(server) {
|
export function registerTaskMasterTools(server) {
|
||||||
logger.info("Registering Task Master tools with MCP server");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Register each tool
|
// Register each tool
|
||||||
registerListTasksTool(server);
|
registerListTasksTool(server);
|
||||||
@@ -56,8 +54,6 @@ export function registerTaskMasterTools(server) {
|
|||||||
registerFixDependenciesTool(server);
|
registerFixDependenciesTool(server);
|
||||||
registerComplexityReportTool(server);
|
registerComplexityReportTool(server);
|
||||||
registerAddDependencyTool(server);
|
registerAddDependencyTool(server);
|
||||||
|
|
||||||
logger.info("Successfully registered all Task Master tools");
|
|
||||||
} 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;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { nextTaskDirect } from "../core/task-master-core.js";
|
import { nextTaskDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -22,18 +23,30 @@ export function registerNextTaskTool(server) {
|
|||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await nextTaskDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await nextTaskDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (result.data.nextTask) {
|
if (result.data.nextTask) {
|
||||||
log.info(`Successfully found next task ID: ${result.data.nextTask.id}${result.fromCache ? ' (from cache)' : ''}`);
|
log.info(`Successfully found next task ID: ${result.data.nextTask.id}${result.fromCache ? ' (from cache)' : ''}`);
|
||||||
@@ -44,7 +57,6 @@ export function registerNextTaskTool(server) {
|
|||||||
log.error(`Failed to find next task: ${result.error.message}`);
|
log.error(`Failed to find next task: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
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 next-task tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { parsePRDDirect } from "../core/task-master-core.js";
|
import { parsePRDDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -16,32 +17,47 @@ import { parsePRDDirect } from "../core/task-master-core.js";
|
|||||||
*/
|
*/
|
||||||
export function registerParsePRDTool(server) {
|
export function registerParsePRDTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "parse_prd_document",
|
name: "parse_prd",
|
||||||
description: "Parse PRD document and generate tasks",
|
description: "Parse a Product Requirements Document (PRD) or text file to automatically generate initial tasks.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
input: z.string().describe("Path to the PRD document file"),
|
input: z.string().default("tasks/tasks.json").describe("Path to the PRD document file (relative to project root or absolute)"),
|
||||||
numTasks: z.union([z.number(), z.string()]).optional().describe("Number of tasks to generate (default: 10)"),
|
numTasks: z.union([z.number(), z.string()]).optional().describe("Approximate number of top-level tasks to generate (default: 10)"),
|
||||||
output: z.string().optional().describe("Output path for tasks.json file (default: tasks/tasks.json)"),
|
output: z.string().optional().describe("Output path for tasks.json file (relative to project root or absolute, default: tasks/tasks.json)"),
|
||||||
|
force: z.boolean().optional().describe("Allow overwriting an existing tasks.json file."),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: automatically detected from session or CWD)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`);
|
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`);
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await parsePRDDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? `Successfully generated ${result.data?.taskCount || 0} tasks` : 'Failed to parse PRD'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await parsePRDDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully generated ${result.data?.taskCount || 0} tasks from PRD at ${result.data?.outputPath}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to parse PRD: ${result.error?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error parsing PRD document');
|
return handleApiResult(result, log, 'Error parsing PRD document');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in parsePRD tool: ${error.message}`);
|
log.error(`Error in parse_prd tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { removeDependencyDirect } from "../core/task-master-core.js";
|
import { removeDependencyDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -24,26 +25,36 @@ export function registerRemoveDependencyTool(server) {
|
|||||||
file: z.string().optional().describe("Path to the tasks file (default: tasks/tasks.json)"),
|
file: z.string().optional().describe("Path to the tasks file (default: tasks/tasks.json)"),
|
||||||
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 }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await removeDependencyDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await removeDependencyDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully removed dependency: ${result.data.message}`);
|
log.info(`Successfully removed dependency: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to remove dependency: ${result.error.message}`);
|
log.error(`Failed to remove dependency: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error removing dependency');
|
return handleApiResult(result, log, 'Error removing dependency');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in removeDependency tool: ${error.message}`);
|
log.error(`Error in removeDependency tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { removeSubtaskDirect } from "../core/task-master-core.js";
|
import { removeSubtaskDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -25,21 +26,31 @@ export function registerRemoveSubtaskTool(server) {
|
|||||||
skipGenerate: z.boolean().optional().describe("Skip regenerating task files"),
|
skipGenerate: z.boolean().optional().describe("Skip regenerating task files"),
|
||||||
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 }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await removeSubtaskDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await removeSubtaskDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Subtask removed successfully: ${result.data.message}`);
|
log.info(`Subtask removed successfully: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to remove subtask: ${result.error.message}`);
|
log.error(`Failed to remove subtask: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error removing subtask');
|
return handleApiResult(result, log, 'Error removing subtask');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in removeSubtask tool: ${error.message}`);
|
log.error(`Error in removeSubtask tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { setTaskStatusDirect } from "../core/task-master-core.js";
|
import { setTaskStatusDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -17,14 +18,14 @@ import { setTaskStatusDirect } from "../core/task-master-core.js";
|
|||||||
export function registerSetTaskStatusTool(server) {
|
export function registerSetTaskStatusTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "set_task_status",
|
name: "set_task_status",
|
||||||
description: "Set the status of a task",
|
description: "Set the status of one or more tasks or subtasks.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
id: z
|
id: z
|
||||||
.string()
|
.string()
|
||||||
.describe("Task ID (can be comma-separated for multiple tasks)"),
|
.describe("Task ID or subtask ID (e.g., '15', '15.2'). Can be comma-separated for multiple updates."),
|
||||||
status: z
|
status: z
|
||||||
.string()
|
.string()
|
||||||
.describe("New status (todo, in-progress, review, done)"),
|
.describe("New status to set (e.g., 'pending', 'done', 'in-progress', 'review', 'deferred', 'cancelled'."),
|
||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
@@ -33,17 +34,31 @@ export function registerSetTaskStatusTool(server) {
|
|||||||
"Root directory of the project (default: automatically detected)"
|
"Root directory of the project (default: automatically detected)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await setTaskStatusDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? `Successfully updated task ${args.id} status to "${args.status}"` : 'Failed to update task status'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await setTaskStatusDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully updated status for task(s) ${args.id} to "${args.status}": ${result.data.message}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to update task status: ${result.error?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error setting task status');
|
return handleApiResult(result, log, 'Error setting task status');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in setTaskStatus tool: ${error.message}`);
|
log.error(`Error in setTaskStatus tool: ${error.message}`);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { updateSubtaskByIdDirect } from "../core/task-master-core.js";
|
import { updateSubtaskByIdDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ import { updateSubtaskByIdDirect } from "../core/task-master-core.js";
|
|||||||
*/
|
*/
|
||||||
export function registerUpdateSubtaskTool(server) {
|
export function registerUpdateSubtaskTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "update-subtask",
|
name: "update_subtask",
|
||||||
description: "Appends additional information to a specific subtask without replacing existing content",
|
description: "Appends additional information to a specific subtask without replacing existing content",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
id: z.string().describe("ID of the subtask to update in format \"parentId.subtaskId\" (e.g., \"5.2\")"),
|
id: z.string().describe("ID of the subtask to update in format \"parentId.subtaskId\" (e.g., \"5.2\")"),
|
||||||
@@ -30,20 +31,34 @@ export function registerUpdateSubtaskTool(server) {
|
|||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await updateSubtaskByIdDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? `Successfully updated subtask with ID ${args.id}` : 'Failed to update subtask'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateSubtaskByIdDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully updated subtask with ID ${args.id}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to update subtask: ${result.error?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error updating subtask');
|
return handleApiResult(result, log, 'Error updating subtask');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in update-subtask tool: ${error.message}`);
|
log.error(`Error in update_subtask tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { updateTaskByIdDirect } from "../core/task-master-core.js";
|
import { updateTaskByIdDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -16,11 +17,11 @@ import { updateTaskByIdDirect } from "../core/task-master-core.js";
|
|||||||
*/
|
*/
|
||||||
export function registerUpdateTaskTool(server) {
|
export function registerUpdateTaskTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "update-task",
|
name: "update_task",
|
||||||
description: "Updates a single task by ID with new information",
|
description: "Updates a single task by ID with new information or context provided in the prompt.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
id: z.union([z.number(), z.string()]).describe("ID of the task to update"),
|
id: z.union([z.number(), z.string()]).describe("ID of the task or subtask (e.g., '15', '15.2') to update"),
|
||||||
prompt: z.string().describe("New information or context to update the task"),
|
prompt: z.string().describe("New information or context to incorporate into the task"),
|
||||||
research: z.boolean().optional().describe("Use Perplexity AI for research-backed updates"),
|
research: z.boolean().optional().describe("Use Perplexity AI for research-backed updates"),
|
||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -30,20 +31,34 @@ export function registerUpdateTaskTool(server) {
|
|||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await updateTaskByIdDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? `Successfully updated task with ID ${args.id}` : 'Failed to update task'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateTaskByIdDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully updated task with ID ${args.id}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to update task: ${result.error?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error updating task');
|
return handleApiResult(result, log, 'Error updating task');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in update-task tool: ${error.message}`);
|
log.error(`Error in update_task tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { updateTasksDirect } from "../core/task-master-core.js";
|
import { updateTasksDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -17,10 +18,10 @@ import { updateTasksDirect } from "../core/task-master-core.js";
|
|||||||
export function registerUpdateTool(server) {
|
export function registerUpdateTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "update",
|
name: "update",
|
||||||
description: "Update tasks with ID >= specified ID based on the provided prompt",
|
description: "Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
from: z.union([z.number(), z.string()]).describe("Task ID from which to start updating"),
|
from: z.union([z.number(), z.string()]).describe("Task ID from which to start updating (inclusive)"),
|
||||||
prompt: z.string().describe("Explanation of changes or new context"),
|
prompt: z.string().describe("Explanation of changes or new context to apply"),
|
||||||
research: z.boolean().optional().describe("Use Perplexity AI for research-backed updates"),
|
research: z.boolean().optional().describe("Use Perplexity AI for research-backed updates"),
|
||||||
file: z.string().optional().describe("Path to the tasks file"),
|
file: z.string().optional().describe("Path to the tasks file"),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -30,17 +31,31 @@ export function registerUpdateTool(server) {
|
|||||||
"Root directory of the project (default: current working directory)"
|
"Root directory of the project (default: current working directory)"
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log }) => {
|
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 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await updateTasksDirect(args, log);
|
|
||||||
|
|
||||||
// Log result
|
if (!rootFolder && args.projectRoot) {
|
||||||
log.info(`${result.success ? `Successfully updated tasks from ID ${args.from}` : 'Failed to update tasks'}`);
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateTasksDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully updated tasks from ID ${args.from}: ${result.data.message}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to update tasks: ${result.error?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error updating tasks');
|
return handleApiResult(result, log, 'Error updating tasks');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in update tool: ${error.message}`);
|
log.error(`Error in update tool: ${error.message}`);
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { contextManager } from '../core/context-manager.js'; // Import the singleton
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { decodeURIComponent } from 'querystring'; // Added for URI decoding
|
import { contextManager } from '../core/context-manager.js'; // Import the singleton
|
||||||
|
|
||||||
// Import path utilities to ensure consistent path resolution
|
// Import path utilities to ensure consistent path resolution
|
||||||
import { lastFoundProjectRoot, getPackagePath, PROJECT_MARKERS } from '../core/utils/path-utils.js';
|
import { lastFoundProjectRoot, PROJECT_MARKERS } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get normalized project root path
|
* Get normalized project root path
|
||||||
@@ -18,7 +17,7 @@ import { lastFoundProjectRoot, getPackagePath, PROJECT_MARKERS } from '../core/u
|
|||||||
* @param {Object} log - Logger object
|
* @param {Object} log - Logger object
|
||||||
* @returns {string} - Normalized absolute path to project root
|
* @returns {string} - Normalized absolute path to project root
|
||||||
*/
|
*/
|
||||||
export function getProjectRoot(projectRootRaw, log) {
|
function getProjectRoot(projectRootRaw, log) {
|
||||||
// PRECEDENCE ORDER:
|
// PRECEDENCE ORDER:
|
||||||
// 1. Environment variable override
|
// 1. Environment variable override
|
||||||
// 2. Explicitly provided projectRoot in args
|
// 2. Explicitly provided projectRoot in args
|
||||||
@@ -74,28 +73,69 @@ export function getProjectRoot(projectRootRaw, log) {
|
|||||||
* @param {Object} log - Logger object.
|
* @param {Object} log - Logger object.
|
||||||
* @returns {string|null} - The absolute path to the project root, or null if not found.
|
* @returns {string|null} - The absolute path to the project root, or null if not found.
|
||||||
*/
|
*/
|
||||||
export function getProjectRootFromSession(session, log) {
|
function getProjectRootFromSession(session, log) {
|
||||||
if (session && session.roots && session.roots.length > 0) {
|
|
||||||
const firstRoot = session.roots[0];
|
|
||||||
if (firstRoot && firstRoot.uri) {
|
|
||||||
try {
|
try {
|
||||||
const rootUri = firstRoot.uri;
|
// If we have a session with roots array
|
||||||
|
if (session?.roots?.[0]?.uri) {
|
||||||
|
const rootUri = session.roots[0].uri;
|
||||||
const rootPath = rootUri.startsWith('file://')
|
const rootPath = rootUri.startsWith('file://')
|
||||||
? decodeURIComponent(rootUri.slice(7)) // Remove 'file://' and decode
|
? decodeURIComponent(rootUri.slice(7))
|
||||||
: rootUri; // Assume it's a path if no scheme
|
: rootUri;
|
||||||
log.info(`Extracted project root from session: ${rootPath}`);
|
|
||||||
return rootPath;
|
return rootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a session with roots.roots array (different structure)
|
||||||
|
if (session?.roots?.roots?.[0]?.uri) {
|
||||||
|
const rootUri = session.roots.roots[0].uri;
|
||||||
|
const rootPath = rootUri.startsWith('file://')
|
||||||
|
? decodeURIComponent(rootUri.slice(7))
|
||||||
|
: rootUri;
|
||||||
|
return rootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the server's location and try to find project root -- this is a fallback necessary in Cursor IDE
|
||||||
|
const serverPath = process.argv[1]; // This should be the path to server.js, which is in mcp-server/
|
||||||
|
if (serverPath && serverPath.includes('mcp-server')) {
|
||||||
|
// Find the mcp-server directory first
|
||||||
|
const mcpServerIndex = serverPath.indexOf('mcp-server');
|
||||||
|
if (mcpServerIndex !== -1) {
|
||||||
|
// Get the path up to mcp-server, which should be the project root
|
||||||
|
const projectRoot = serverPath.substring(0, mcpServerIndex - 1); // -1 to remove trailing slash
|
||||||
|
|
||||||
|
// Verify this looks like our project root by checking for key files/directories
|
||||||
|
if (fs.existsSync(path.join(projectRoot, '.cursor')) ||
|
||||||
|
fs.existsSync(path.join(projectRoot, 'mcp-server')) ||
|
||||||
|
fs.existsSync(path.join(projectRoot, 'package.json'))) {
|
||||||
|
return projectRoot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, we'll try process.cwd() but only if it's not "/"
|
||||||
|
const cwd = process.cwd();
|
||||||
|
if (cwd !== '/') {
|
||||||
|
return cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: try to derive from the server path we found earlier
|
||||||
|
if (serverPath) {
|
||||||
|
const mcpServerIndex = serverPath.indexOf('mcp-server');
|
||||||
|
return mcpServerIndex !== -1 ? serverPath.substring(0, mcpServerIndex - 1) : cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Could not determine project root');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(`Error decoding project root URI from session: ${firstRoot.uri}`, e);
|
// If we have a server path, use it as a basis for project root
|
||||||
return null;
|
const serverPath = process.argv[1];
|
||||||
|
if (serverPath && serverPath.includes('mcp-server')) {
|
||||||
|
const mcpServerIndex = serverPath.indexOf('mcp-server');
|
||||||
|
return mcpServerIndex !== -1 ? serverPath.substring(0, mcpServerIndex - 1) : process.cwd();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.info('Session exists, but first root or its URI is missing.');
|
// Only use cwd if it's not "/"
|
||||||
|
const cwd = process.cwd();
|
||||||
|
return cwd !== '/' ? cwd : '/';
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.info('No session or session roots found to extract project root.');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,7 +146,7 @@ export function getProjectRootFromSession(session, log) {
|
|||||||
* @param {Function} processFunction - Optional function to process successful result data
|
* @param {Function} processFunction - Optional function to process successful result data
|
||||||
* @returns {Object} - Standardized MCP response object
|
* @returns {Object} - Standardized MCP response object
|
||||||
*/
|
*/
|
||||||
export function handleApiResult(result, log, errorPrefix = 'API error', processFunction = processMCPResponseData) {
|
function handleApiResult(result, log, errorPrefix = 'API error', processFunction = processMCPResponseData) {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
|
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
|
||||||
// Include cache status in error logs
|
// Include cache status in error logs
|
||||||
@@ -138,7 +178,7 @@ export function handleApiResult(result, log, errorPrefix = 'API error', processF
|
|||||||
* @param {string|undefined} projectRootRaw - Optional raw project root path (will be normalized internally)
|
* @param {string|undefined} projectRootRaw - Optional raw project root path (will be normalized internally)
|
||||||
* @returns {Object} - The result of the command execution
|
* @returns {Object} - The result of the command execution
|
||||||
*/
|
*/
|
||||||
export function executeTaskMasterCommand(
|
function executeTaskMasterCommand(
|
||||||
command,
|
command,
|
||||||
log,
|
log,
|
||||||
args = [],
|
args = [],
|
||||||
@@ -215,7 +255,7 @@ export function executeTaskMasterCommand(
|
|||||||
* @returns {Promise<Object>} - An object containing the result, indicating if it was from cache.
|
* @returns {Promise<Object>} - An object containing the result, indicating if it was from cache.
|
||||||
* Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
|
* Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
|
||||||
*/
|
*/
|
||||||
export async function getCachedOrExecute({ cacheKey, actionFn, log }) {
|
async function getCachedOrExecute({ cacheKey, actionFn, log }) {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
const cachedResult = contextManager.getCachedData(cacheKey);
|
const cachedResult = contextManager.getCachedData(cacheKey);
|
||||||
|
|
||||||
@@ -259,7 +299,7 @@ export async function getCachedOrExecute({ cacheKey, actionFn, log }) {
|
|||||||
* @param {string[]} fieldsToRemove - An array of field names to remove.
|
* @param {string[]} fieldsToRemove - An array of field names to remove.
|
||||||
* @returns {Object|Array} - The processed data with specified fields removed.
|
* @returns {Object|Array} - The processed data with specified fields removed.
|
||||||
*/
|
*/
|
||||||
export function processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy']) {
|
function processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy']) {
|
||||||
if (!taskOrData) {
|
if (!taskOrData) {
|
||||||
return taskOrData;
|
return taskOrData;
|
||||||
}
|
}
|
||||||
@@ -316,7 +356,7 @@ export function processMCPResponseData(taskOrData, fieldsToRemove = ['details',
|
|||||||
* @param {string|Object} content - Content to include in response
|
* @param {string|Object} content - Content to include in response
|
||||||
* @returns {Object} - Content response object in FastMCP format
|
* @returns {Object} - Content response object in FastMCP format
|
||||||
*/
|
*/
|
||||||
export function createContentResponse(content) {
|
function createContentResponse(content) {
|
||||||
// FastMCP requires text type, so we format objects as JSON strings
|
// FastMCP requires text type, so we format objects as JSON strings
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
@@ -351,10 +391,11 @@ export function createErrorResponse(errorMessage) {
|
|||||||
|
|
||||||
// Ensure all functions are exported
|
// Ensure all functions are exported
|
||||||
export {
|
export {
|
||||||
|
getProjectRoot,
|
||||||
|
getProjectRootFromSession,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
executeTaskMasterCommand,
|
executeTaskMasterCommand,
|
||||||
getCachedOrExecute,
|
getCachedOrExecute,
|
||||||
processMCPResponseData,
|
processMCPResponseData,
|
||||||
createContentResponse,
|
createContentResponse,
|
||||||
createErrorResponse
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse
|
createErrorResponse,
|
||||||
|
getProjectRootFromSession
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { validateDependenciesDirect } from "../core/task-master-core.js";
|
import { validateDependenciesDirect } from "../core/task-master-core.js";
|
||||||
|
|
||||||
@@ -17,26 +18,36 @@ import { validateDependenciesDirect } from "../core/task-master-core.js";
|
|||||||
export function registerValidateDependenciesTool(server) {
|
export function registerValidateDependenciesTool(server) {
|
||||||
server.addTool({
|
server.addTool({
|
||||||
name: "validate_dependencies",
|
name: "validate_dependencies",
|
||||||
description: "Identify invalid dependencies in tasks without fixing them",
|
description: "Check tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
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 }) => {
|
execute: async (args, { log, session, reportProgress }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
|
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
|
||||||
|
await reportProgress({ progress: 0 });
|
||||||
|
|
||||||
// Call the direct function wrapper
|
let rootFolder = getProjectRootFromSession(session, log);
|
||||||
const result = await validateDependenciesDirect(args, log);
|
|
||||||
|
if (!rootFolder && args.projectRoot) {
|
||||||
|
rootFolder = args.projectRoot;
|
||||||
|
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await validateDependenciesDirect({
|
||||||
|
projectRoot: rootFolder,
|
||||||
|
...args
|
||||||
|
}, log, { reportProgress, mcpLog: log, session});
|
||||||
|
|
||||||
|
await reportProgress({ progress: 100 });
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully validated dependencies: ${result.data.message}`);
|
log.info(`Successfully validated dependencies: ${result.data.message}`);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to validate dependencies: ${result.error.message}`);
|
log.error(`Failed to validate dependencies: ${result.error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(result, log, 'Error validating dependencies');
|
return handleApiResult(result, log, 'Error validating dependencies');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in validateDependencies tool: ${error.message}`);
|
log.error(`Error in validateDependencies tool: ${error.message}`);
|
||||||
|
|||||||
@@ -136,9 +136,13 @@ function handleClaudeError(error) {
|
|||||||
* @param {string} prdPath - Path to the PRD file
|
* @param {string} prdPath - Path to the PRD file
|
||||||
* @param {number} numTasks - Number of tasks to generate
|
* @param {number} numTasks - Number of tasks to generate
|
||||||
* @param {number} retryCount - Retry count
|
* @param {number} retryCount - Retry count
|
||||||
|
* @param {Object} options - Options object containing:
|
||||||
|
* - reportProgress: Function to report progress to MCP server (optional)
|
||||||
|
* - mcpLog: MCP logger object (optional)
|
||||||
|
* - session: Session object from MCP server (optional)
|
||||||
* @returns {Object} Claude's response
|
* @returns {Object} Claude's response
|
||||||
*/
|
*/
|
||||||
async function callClaude(prdContent, prdPath, numTasks, retryCount = 0) {
|
async function callClaude(prdContent, prdPath, numTasks, retryCount = 0, { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
log('info', 'Calling Claude...');
|
log('info', 'Calling Claude...');
|
||||||
|
|
||||||
@@ -190,7 +194,7 @@ Expected output format:
|
|||||||
Important: Your response must be valid JSON only, with no additional explanation or comments.`;
|
Important: Your response must be valid JSON only, with no additional explanation or comments.`;
|
||||||
|
|
||||||
// Use streaming request to handle large responses and show progress
|
// Use streaming request to handle large responses and show progress
|
||||||
return await handleStreamingRequest(prdContent, prdPath, numTasks, CONFIG.maxTokens, systemPrompt);
|
return await handleStreamingRequest(prdContent, prdPath, numTasks, CONFIG.maxTokens, systemPrompt, { reportProgress, mcpLog, session } = {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Get user-friendly error message
|
// Get user-friendly error message
|
||||||
const userMessage = handleClaudeError(error);
|
const userMessage = handleClaudeError(error);
|
||||||
@@ -224,19 +228,24 @@ Important: Your response must be valid JSON only, with no additional explanation
|
|||||||
* @param {number} numTasks - Number of tasks to generate
|
* @param {number} numTasks - Number of tasks to generate
|
||||||
* @param {number} maxTokens - Maximum tokens
|
* @param {number} maxTokens - Maximum tokens
|
||||||
* @param {string} systemPrompt - System prompt
|
* @param {string} systemPrompt - System prompt
|
||||||
|
* @param {Object} options - Options object containing:
|
||||||
|
* - reportProgress: Function to report progress to MCP server (optional)
|
||||||
|
* - mcpLog: MCP logger object (optional)
|
||||||
|
* - session: Session object from MCP server (optional)
|
||||||
* @returns {Object} Claude's response
|
* @returns {Object} Claude's response
|
||||||
*/
|
*/
|
||||||
async function handleStreamingRequest(prdContent, prdPath, numTasks, maxTokens, systemPrompt) {
|
async function handleStreamingRequest(prdContent, prdPath, numTasks, maxTokens, systemPrompt, { reportProgress, mcpLog, session } = {}) {
|
||||||
const loadingIndicator = startLoadingIndicator('Generating tasks from PRD...');
|
const loadingIndicator = startLoadingIndicator('Generating tasks from PRD...');
|
||||||
|
if (reportProgress) { await reportProgress({ progress: 0 }); }
|
||||||
let responseText = '';
|
let responseText = '';
|
||||||
let streamingInterval = null;
|
let streamingInterval = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use streaming for handling large responses
|
// Use streaming for handling large responses
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
max_tokens: maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || maxTokens,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -261,6 +270,12 @@ async function handleStreamingRequest(prdContent, prdPath, numTasks, maxTokens,
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
@@ -355,9 +370,13 @@ function processClaudeResponse(textContent, numTasks, retryCount, prdContent, pr
|
|||||||
* @param {number} numSubtasks - Number of subtasks to generate
|
* @param {number} numSubtasks - Number of subtasks to generate
|
||||||
* @param {number} nextSubtaskId - Next subtask ID
|
* @param {number} nextSubtaskId - Next subtask ID
|
||||||
* @param {string} additionalContext - Additional context
|
* @param {string} additionalContext - Additional context
|
||||||
|
* @param {Object} options - Options object containing:
|
||||||
|
* - reportProgress: Function to report progress to MCP server (optional)
|
||||||
|
* - mcpLog: MCP logger object (optional)
|
||||||
|
* - session: Session object from MCP server (optional)
|
||||||
* @returns {Array} Generated subtasks
|
* @returns {Array} Generated subtasks
|
||||||
*/
|
*/
|
||||||
async function generateSubtasks(task, numSubtasks, nextSubtaskId, additionalContext = '') {
|
async function generateSubtasks(task, numSubtasks, nextSubtaskId, additionalContext = '', { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
log('info', `Generating ${numSubtasks} subtasks for task ${task.id}: ${task.title}`);
|
log('info', `Generating ${numSubtasks} subtasks for task ${task.id}: ${task.title}`);
|
||||||
|
|
||||||
@@ -419,11 +438,13 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
|
|||||||
dotCount = (dotCount + 1) % 4;
|
dotCount = (dotCount + 1) % 4;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
// TODO: MOVE THIS TO THE STREAM REQUEST FUNCTION (DRY)
|
||||||
|
|
||||||
// Use streaming API call
|
// Use streaming API call
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -439,6 +460,12 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
@@ -464,15 +491,19 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
|
|||||||
* @param {number} numSubtasks - Number of subtasks to generate
|
* @param {number} numSubtasks - Number of subtasks to generate
|
||||||
* @param {number} nextSubtaskId - Next subtask ID
|
* @param {number} nextSubtaskId - Next subtask ID
|
||||||
* @param {string} additionalContext - Additional context
|
* @param {string} additionalContext - Additional context
|
||||||
|
* @param {Object} options - Options object containing:
|
||||||
|
* - reportProgress: Function to report progress to MCP server (optional)
|
||||||
|
* - mcpLog: MCP logger object (optional)
|
||||||
|
* - session: Session object from MCP server (optional)
|
||||||
* @returns {Array} Generated subtasks
|
* @returns {Array} Generated subtasks
|
||||||
*/
|
*/
|
||||||
async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtaskId = 1, additionalContext = '') {
|
async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtaskId = 1, additionalContext = '', { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
// First, perform research to get context
|
// First, perform research to get context
|
||||||
log('info', `Researching context for task ${task.id}: ${task.title}`);
|
log('info', `Researching context for task ${task.id}: ${task.title}`);
|
||||||
const perplexityClient = getPerplexityClient();
|
const perplexityClient = getPerplexityClient();
|
||||||
|
|
||||||
const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-pro';
|
const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro';
|
||||||
const researchLoadingIndicator = startLoadingIndicator('Researching best practices with Perplexity AI...');
|
const researchLoadingIndicator = startLoadingIndicator('Researching best practices with Perplexity AI...');
|
||||||
|
|
||||||
// Formulate research query based on task
|
// Formulate research query based on task
|
||||||
@@ -566,9 +597,9 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
|
|||||||
|
|
||||||
// Use streaming API call
|
// Use streaming API call
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -584,6 +615,12 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
|
|||||||
@@ -49,19 +49,19 @@ import {
|
|||||||
|
|
||||||
// Initialize Anthropic client
|
// Initialize Anthropic client
|
||||||
const anthropic = new Anthropic({
|
const anthropic = new Anthropic({
|
||||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
apiKey: process.env.ANTHROPIC_API_KEY || session?.env?.ANTHROPIC_API_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Import perplexity if available
|
// Import perplexity if available
|
||||||
let perplexity;
|
let perplexity;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (process.env.PERPLEXITY_API_KEY) {
|
if (process.env.PERPLEXITY_API_KEY || session?.env?.PERPLEXITY_API_KEY) {
|
||||||
// Using the existing approach from ai-services.js
|
// Using the existing approach from ai-services.js
|
||||||
const OpenAI = (await import('openai')).default;
|
const OpenAI = (await import('openai')).default;
|
||||||
|
|
||||||
perplexity = new OpenAI({
|
perplexity = new OpenAI({
|
||||||
apiKey: process.env.PERPLEXITY_API_KEY,
|
apiKey: process.env.PERPLEXITY_API_KEY || session?.env?.PERPLEXITY_API_KEY,
|
||||||
baseURL: 'https://api.perplexity.ai',
|
baseURL: 'https://api.perplexity.ai',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,8 +77,11 @@ try {
|
|||||||
* @param {string} prdPath - Path to the PRD file
|
* @param {string} prdPath - Path to the PRD file
|
||||||
* @param {string} tasksPath - Path to the tasks.json file
|
* @param {string} tasksPath - Path to the tasks.json file
|
||||||
* @param {number} numTasks - Number of tasks to generate
|
* @param {number} numTasks - Number of tasks to generate
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (optional)
|
||||||
|
* @param {Object} session - Session object from MCP server (optional)
|
||||||
*/
|
*/
|
||||||
async function parsePRD(prdPath, tasksPath, numTasks) {
|
async function parsePRD(prdPath, tasksPath, numTasks, { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
log('info', `Parsing PRD file: ${prdPath}`);
|
log('info', `Parsing PRD file: ${prdPath}`);
|
||||||
|
|
||||||
@@ -86,22 +89,20 @@ async function parsePRD(prdPath, tasksPath, numTasks) {
|
|||||||
const prdContent = fs.readFileSync(prdPath, 'utf8');
|
const prdContent = fs.readFileSync(prdPath, 'utf8');
|
||||||
|
|
||||||
// Call Claude to generate tasks
|
// Call Claude to generate tasks
|
||||||
const tasksData = await callClaude(prdContent, prdPath, numTasks);
|
const tasksData = await callClaude(prdContent, prdPath, numTasks, { reportProgress, mcpLog, session } = {});
|
||||||
|
|
||||||
// Create the directory if it doesn't exist
|
// Create the directory if it doesn't exist
|
||||||
const tasksDir = path.dirname(tasksPath);
|
const tasksDir = path.dirname(tasksPath);
|
||||||
if (!fs.existsSync(tasksDir)) {
|
if (!fs.existsSync(tasksDir)) {
|
||||||
fs.mkdirSync(tasksDir, { recursive: true });
|
fs.mkdirSync(tasksDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the tasks to the file
|
// Write the tasks to the file
|
||||||
writeJSON(tasksPath, tasksData);
|
writeJSON(tasksPath, tasksData);
|
||||||
|
|
||||||
log('success', `Successfully generated ${tasksData.tasks.length} tasks from PRD`);
|
log('success', `Successfully generated ${tasksData.tasks.length} tasks from PRD`);
|
||||||
log('info', `Tasks saved to: ${tasksPath}`);
|
log('info', `Tasks saved to: ${tasksPath}`);
|
||||||
|
|
||||||
// Generate individual task files
|
// Generate individual task files
|
||||||
await generateTaskFiles(tasksPath, tasksDir);
|
await generateTaskFiles(tasksPath, tasksDir, { reportProgress, mcpLog, session } = {});
|
||||||
|
|
||||||
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`),
|
||||||
@@ -132,13 +133,16 @@ async function parsePRD(prdPath, tasksPath, numTasks) {
|
|||||||
* @param {number} fromId - Task ID to start updating from
|
* @param {number} fromId - Task ID to start updating from
|
||||||
* @param {string} prompt - Prompt with new context
|
* @param {string} prompt - Prompt with new context
|
||||||
* @param {boolean} useResearch - Whether to use Perplexity AI for research
|
* @param {boolean} useResearch - Whether to use Perplexity AI for research
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (optional)
|
||||||
|
* @param {Object} session - Session object from MCP server (optional)
|
||||||
*/
|
*/
|
||||||
async function updateTasks(tasksPath, fromId, prompt, useResearch = false) {
|
async function updateTasks(tasksPath, fromId, prompt, useResearch = false, { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
log('info', `Updating tasks from ID ${fromId} with prompt: "${prompt}"`);
|
log('info', `Updating tasks from ID ${fromId} with prompt: "${prompt}"`);
|
||||||
|
|
||||||
// Validate research flag
|
// Validate research flag
|
||||||
if (useResearch && (!perplexity || !process.env.PERPLEXITY_API_KEY)) {
|
if (useResearch && (!perplexity || !process.env.PERPLEXITY_API_KEY || session?.env?.PERPLEXITY_API_KEY)) {
|
||||||
log('warn', 'Perplexity AI is not available. Falling back to Claude AI.');
|
log('warn', 'Perplexity AI is not available. Falling back to Claude AI.');
|
||||||
console.log(chalk.yellow('Perplexity AI is not available (API key may be missing). Falling back to Claude AI.'));
|
console.log(chalk.yellow('Perplexity AI is not available (API key may be missing). Falling back to Claude AI.'));
|
||||||
useResearch = false;
|
useResearch = false;
|
||||||
@@ -224,7 +228,7 @@ The changes described in the prompt should be applied to ALL tasks in the list.`
|
|||||||
log('info', 'Using Perplexity AI for research-backed task updates');
|
log('info', 'Using Perplexity AI for research-backed task updates');
|
||||||
|
|
||||||
// Call Perplexity AI using format consistent with ai-services.js
|
// Call Perplexity AI using format consistent with ai-services.js
|
||||||
const perplexityModel = process.env.PERPLEXITY_MODEL || 'sonar-pro';
|
const perplexityModel = process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro';
|
||||||
const result = await perplexity.chat.completions.create({
|
const result = await perplexity.chat.completions.create({
|
||||||
model: perplexityModel,
|
model: perplexityModel,
|
||||||
messages: [
|
messages: [
|
||||||
@@ -245,8 +249,8 @@ IMPORTANT: In the tasks JSON above, any subtasks with "status": "done" or "statu
|
|||||||
Return only the updated tasks as a valid JSON array.`
|
Return only the updated tasks as a valid JSON array.`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
temperature: parseFloat(process.env.TEMPERATURE || CONFIG.temperature),
|
temperature: parseFloat(process.env.TEMPERATURE || session?.env?.TEMPERATURE || CONFIG.temperature),
|
||||||
max_tokens: parseInt(process.env.MAX_TOKENS || CONFIG.maxTokens),
|
max_tokens: parseInt(process.env.MAX_TOKENS || session?.env?.MAX_TOKENS || CONFIG.maxTokens),
|
||||||
});
|
});
|
||||||
|
|
||||||
const responseText = result.choices[0].message.content;
|
const responseText = result.choices[0].message.content;
|
||||||
@@ -278,9 +282,9 @@ Return only the updated tasks as a valid JSON array.`
|
|||||||
|
|
||||||
// Use streaming API call
|
// Use streaming API call
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -304,6 +308,13 @@ Return only the updated tasks as a valid JSON array.`
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
@@ -366,9 +377,12 @@ Return only the updated tasks as a valid JSON array.`
|
|||||||
* @param {number} taskId - Task ID to update
|
* @param {number} taskId - Task ID to update
|
||||||
* @param {string} prompt - Prompt with new context
|
* @param {string} prompt - Prompt with new context
|
||||||
* @param {boolean} useResearch - Whether to use Perplexity AI for research
|
* @param {boolean} useResearch - Whether to use Perplexity AI for research
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (optional)
|
||||||
|
* @param {Object} session - Session object from MCP server (optional)
|
||||||
* @returns {Object} - Updated task data or null if task wasn't updated
|
* @returns {Object} - Updated task data or null if task wasn't updated
|
||||||
*/
|
*/
|
||||||
async function updateTaskById(tasksPath, taskId, prompt, useResearch = false) {
|
async function updateTaskById(tasksPath, taskId, prompt, useResearch = false, { reportProgress, mcpLog, session } = {}) {
|
||||||
try {
|
try {
|
||||||
log('info', `Updating single task ${taskId} with prompt: "${prompt}"`);
|
log('info', `Updating single task ${taskId} with prompt: "${prompt}"`);
|
||||||
|
|
||||||
@@ -383,7 +397,7 @@ async function updateTaskById(tasksPath, taskId, prompt, useResearch = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate research flag
|
// Validate research flag
|
||||||
if (useResearch && (!perplexity || !process.env.PERPLEXITY_API_KEY)) {
|
if (useResearch && (!perplexity || !process.env.PERPLEXITY_API_KEY || session?.env?.PERPLEXITY_API_KEY)) {
|
||||||
log('warn', 'Perplexity AI is not available. Falling back to Claude AI.');
|
log('warn', 'Perplexity AI is not available. Falling back to Claude AI.');
|
||||||
console.log(chalk.yellow('Perplexity AI is not available (API key may be missing). Falling back to Claude AI.'));
|
console.log(chalk.yellow('Perplexity AI is not available (API key may be missing). Falling back to Claude AI.'));
|
||||||
useResearch = false;
|
useResearch = false;
|
||||||
@@ -484,13 +498,13 @@ The changes described in the prompt should be thoughtfully applied to make the t
|
|||||||
log('info', 'Using Perplexity AI for research-backed task update');
|
log('info', 'Using Perplexity AI for research-backed task update');
|
||||||
|
|
||||||
// Verify Perplexity API key exists
|
// Verify Perplexity API key exists
|
||||||
if (!process.env.PERPLEXITY_API_KEY) {
|
if (!process.env.PERPLEXITY_API_KEY || session?.env?.PERPLEXITY_API_KEY) {
|
||||||
throw new Error('PERPLEXITY_API_KEY environment variable is missing but --research flag was used.');
|
throw new Error('PERPLEXITY_API_KEY environment variable is missing but --research flag was used.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call Perplexity AI
|
// Call Perplexity AI
|
||||||
const perplexityModel = process.env.PERPLEXITY_MODEL || 'sonar-pro';
|
const perplexityModel = process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro';
|
||||||
const result = await perplexity.chat.completions.create({
|
const result = await perplexity.chat.completions.create({
|
||||||
model: perplexityModel,
|
model: perplexityModel,
|
||||||
messages: [
|
messages: [
|
||||||
@@ -511,8 +525,8 @@ IMPORTANT: In the task JSON above, any subtasks with "status": "done" or "status
|
|||||||
Return only the updated task as a valid JSON object.`
|
Return only the updated task as a valid JSON object.`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
temperature: parseFloat(process.env.TEMPERATURE || CONFIG.temperature),
|
temperature: parseFloat(process.env.TEMPERATURE || session?.env?.TEMPERATURE || CONFIG.temperature),
|
||||||
max_tokens: parseInt(process.env.MAX_TOKENS || CONFIG.maxTokens),
|
max_tokens: parseInt(process.env.MAX_TOKENS || session?.env?.MAX_TOKENS || CONFIG.maxTokens),
|
||||||
});
|
});
|
||||||
|
|
||||||
const responseText = result.choices[0].message.content;
|
const responseText = result.choices[0].message.content;
|
||||||
@@ -542,7 +556,7 @@ Return only the updated task as a valid JSON object.`
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify Anthropic API key exists
|
// Verify Anthropic API key exists
|
||||||
if (!process.env.ANTHROPIC_API_KEY) {
|
if (!process.env.ANTHROPIC_API_KEY || session?.env?.ANTHROPIC_API_KEY) {
|
||||||
throw new Error('ANTHROPIC_API_KEY environment variable is missing. Required for task updates.');
|
throw new Error('ANTHROPIC_API_KEY environment variable is missing. Required for task updates.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,9 +571,9 @@ Return only the updated task as a valid JSON object.`
|
|||||||
|
|
||||||
// Use streaming API call
|
// Use streaming API call
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -583,6 +597,12 @@ Return only the updated task as a valid JSON object.`
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
@@ -2034,9 +2054,12 @@ function clearSubtasks(tasksPath, taskIds) {
|
|||||||
* @param {string} prompt - Description of the task to add
|
* @param {string} prompt - Description of the task to add
|
||||||
* @param {Array} dependencies - Task dependencies
|
* @param {Array} dependencies - Task dependencies
|
||||||
* @param {string} priority - Task priority
|
* @param {string} priority - Task priority
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (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') {
|
async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium', { reportProgress, mcpLog, session } = {}) {
|
||||||
displayBanner();
|
displayBanner();
|
||||||
|
|
||||||
// Read the existing tasks
|
// Read the existing tasks
|
||||||
@@ -2112,9 +2135,9 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
|
|||||||
try {
|
try {
|
||||||
// Call Claude with streaming enabled
|
// Call Claude with streaming enabled
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
model: CONFIG.model,
|
model: session?.env?.ANTHROPIC_MODEL || CONFIG.model,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
messages: [{ role: "user", content: userPrompt }],
|
messages: [{ role: "user", content: userPrompt }],
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
stream: true
|
stream: true
|
||||||
@@ -2133,6 +2156,13 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
fullResponse += chunk.delta.text;
|
fullResponse += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (fullResponse.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${fullResponse.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
@@ -2213,8 +2243,11 @@ async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium'
|
|||||||
/**
|
/**
|
||||||
* Analyzes task complexity and generates expansion recommendations
|
* Analyzes task complexity and generates expansion recommendations
|
||||||
* @param {Object} options Command options
|
* @param {Object} options Command options
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (optional)
|
||||||
|
* @param {Object} session - Session object from MCP server (optional)
|
||||||
*/
|
*/
|
||||||
async function analyzeTaskComplexity(options) {
|
async function analyzeTaskComplexity(options, { reportProgress, mcpLog, session } = {}) {
|
||||||
const tasksPath = options.file || 'tasks/tasks.json';
|
const tasksPath = options.file || 'tasks/tasks.json';
|
||||||
const outputPath = options.output || 'scripts/task-complexity-report.json';
|
const outputPath = options.output || 'scripts/task-complexity-report.json';
|
||||||
const modelOverride = options.model;
|
const modelOverride = options.model;
|
||||||
@@ -2274,7 +2307,7 @@ Your response must be a clean JSON array only, following exactly this format:
|
|||||||
DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`;
|
DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`;
|
||||||
|
|
||||||
const result = await perplexity.chat.completions.create({
|
const result = await perplexity.chat.completions.create({
|
||||||
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
|
model: process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@@ -2285,8 +2318,8 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
content: researchPrompt
|
content: researchPrompt
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract the response text
|
// Extract the response text
|
||||||
@@ -2315,9 +2348,9 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
async function useClaudeForComplexityAnalysis() {
|
async function useClaudeForComplexityAnalysis() {
|
||||||
// Call the LLM API with streaming
|
// Call the LLM API with streaming
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
model: modelOverride || CONFIG.model,
|
model: modelOverride || CONFIG.model || session?.env?.ANTHROPIC_MODEL,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
messages: [{ role: "user", content: prompt }],
|
messages: [{ role: "user", content: prompt }],
|
||||||
system: "You are an expert software architect and project manager analyzing task complexity. Respond only with valid JSON.",
|
system: "You are an expert software architect and project manager analyzing task complexity. Respond only with valid JSON.",
|
||||||
stream: true
|
stream: true
|
||||||
@@ -2336,6 +2369,12 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
fullResponse += chunk.delta.text;
|
fullResponse += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (fullResponse.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${fullResponse.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearInterval(streamingInterval);
|
clearInterval(streamingInterval);
|
||||||
@@ -2530,7 +2569,7 @@ Your response must be a clean JSON array only, following exactly this format:
|
|||||||
DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`;
|
DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`;
|
||||||
|
|
||||||
const result = await perplexity.chat.completions.create({
|
const result = await perplexity.chat.completions.create({
|
||||||
model: process.env.PERPLEXITY_MODEL || 'sonar-pro',
|
model: process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@@ -2541,8 +2580,8 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
content: missingTasksResearchPrompt
|
content: missingTasksResearchPrompt
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract the response
|
// Extract the response
|
||||||
@@ -2550,9 +2589,9 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
} else {
|
} else {
|
||||||
// Use Claude
|
// Use Claude
|
||||||
const stream = await anthropic.messages.create({
|
const stream = await anthropic.messages.create({
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: session?.env?.MAX_TOKENS || CONFIG.maxTokens,
|
||||||
model: modelOverride || CONFIG.model,
|
model: modelOverride || CONFIG.model || session?.env?.ANTHROPIC_MODEL,
|
||||||
temperature: CONFIG.temperature,
|
temperature: session?.env?.TEMPERATURE || CONFIG.temperature,
|
||||||
messages: [{ role: "user", content: missingTasksPrompt }],
|
messages: [{ role: "user", content: missingTasksPrompt }],
|
||||||
system: "You are an expert software architect and project manager analyzing task complexity. Respond only with valid JSON.",
|
system: "You are an expert software architect and project manager analyzing task complexity. Respond only with valid JSON.",
|
||||||
stream: true
|
stream: true
|
||||||
@@ -2563,6 +2602,12 @@ DO NOT include any text before or after the JSON array. No explanations, no mark
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
missingAnalysisResponse += chunk.delta.text;
|
missingAnalysisResponse += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (missingAnalysisResponse.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${missingAnalysisResponse.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3063,9 +3108,12 @@ async function removeSubtask(tasksPath, subtaskId, convertToTask = false, genera
|
|||||||
* @param {string} subtaskId - ID of the subtask to update in format "parentId.subtaskId"
|
* @param {string} subtaskId - ID of the subtask to update in format "parentId.subtaskId"
|
||||||
* @param {string} prompt - Prompt for generating additional information
|
* @param {string} prompt - Prompt for generating additional information
|
||||||
* @param {boolean} useResearch - Whether to use Perplexity AI for research-backed updates
|
* @param {boolean} useResearch - Whether to use Perplexity AI for research-backed updates
|
||||||
|
* @param {function} reportProgress - Function to report progress to MCP server (optional)
|
||||||
|
* @param {Object} mcpLog - MCP logger object (optional)
|
||||||
|
* @param {Object} session - Session object from MCP server (optional)
|
||||||
* @returns {Object|null} - The updated subtask or null if update failed
|
* @returns {Object|null} - The updated subtask or null if update failed
|
||||||
*/
|
*/
|
||||||
async function updateSubtaskById(tasksPath, subtaskId, prompt, useResearch = false) {
|
async function updateSubtaskById(tasksPath, subtaskId, prompt, useResearch = false, { reportProgress, mcpLog, session } = {} ) {
|
||||||
let loadingIndicator = null;
|
let loadingIndicator = null;
|
||||||
try {
|
try {
|
||||||
log('info', `Updating subtask ${subtaskId} with prompt: "${prompt}"`);
|
log('info', `Updating subtask ${subtaskId} with prompt: "${prompt}"`);
|
||||||
@@ -3194,15 +3242,15 @@ Provide concrete examples, code snippets, or implementation details when relevan
|
|||||||
|
|
||||||
if (modelType === 'perplexity') {
|
if (modelType === 'perplexity') {
|
||||||
// Construct Perplexity payload
|
// Construct Perplexity payload
|
||||||
const perplexityModel = process.env.PERPLEXITY_MODEL || 'sonar-pro';
|
const perplexityModel = process.env.PERPLEXITY_MODEL || session?.env?.PERPLEXITY_MODEL || 'sonar-pro';
|
||||||
const response = await client.chat.completions.create({
|
const response = await client.chat.completions.create({
|
||||||
model: perplexityModel,
|
model: perplexityModel,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: 'system', content: systemPrompt },
|
{ role: 'system', content: systemPrompt },
|
||||||
{ role: 'user', content: userMessageContent }
|
{ role: 'user', content: userMessageContent }
|
||||||
],
|
],
|
||||||
temperature: parseFloat(process.env.TEMPERATURE || CONFIG.temperature),
|
temperature: parseFloat(process.env.TEMPERATURE || session?.env?.TEMPERATURE || CONFIG.temperature),
|
||||||
max_tokens: parseInt(process.env.MAX_TOKENS || CONFIG.maxTokens),
|
max_tokens: parseInt(process.env.MAX_TOKENS || session?.env?.MAX_TOKENS || CONFIG.maxTokens),
|
||||||
});
|
});
|
||||||
additionalInformation = response.choices[0].message.content.trim();
|
additionalInformation = response.choices[0].message.content.trim();
|
||||||
} else { // Claude
|
} else { // Claude
|
||||||
@@ -3234,6 +3282,12 @@ Provide concrete examples, code snippets, or implementation details when relevan
|
|||||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||||
responseText += chunk.delta.text;
|
responseText += chunk.delta.text;
|
||||||
}
|
}
|
||||||
|
if (reportProgress) {
|
||||||
|
await reportProgress({ progress: (responseText.length / CONFIG.maxTokens) * 100 });
|
||||||
|
}
|
||||||
|
if (mcpLog) {
|
||||||
|
mcpLog.info(`Progress: ${responseText.length / CONFIG.maxTokens * 100}%`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (streamingInterval) clearInterval(streamingInterval);
|
if (streamingInterval) clearInterval(streamingInterval);
|
||||||
|
|||||||
Reference in New Issue
Block a user