diff --git a/.cursor/rules/general.mdc b/.cursor/rules/general.mdc index b272aba..d86ab3b 100644 --- a/.cursor/rules/general.mdc +++ b/.cursor/rules/general.mdc @@ -3,4 +3,6 @@ description: globs: alwaysApply: true --- -Work step-by-step. If presented with an implementation plan, implement the plan exactly. If the plan presents more than one implementation option, consult with the human user to decide between options. If you are tempted to embellish or imporve upon the plan, consult with the human user. Always complete the current task and wait for human review before proceeding to the next task. \ No newline at end of file +Work step-by-step. If presented with an implementation plan, implement the plan exactly. If the plan presents more than one implementation option, consult with the human user to decide between options. If you are tempted to embellish or imporve upon the plan, consult with the human user. Always complete the current task and wait for human review before proceeding to the next task. + +In developing this codebase, we are doing test-driven development with an integration testing (as opposed to a unit testing) verification strategy. Before writing any code (except perhaps for empty function or class signatures), we will write tests and run them to make sure they fail. The red phase is not complete until the tests are failing correctly. \ No newline at end of file diff --git a/.cursor/rules/tests.mdc b/.cursor/rules/tests.mdc index 8f39314..f895071 100644 --- a/.cursor/rules/tests.mdc +++ b/.cursor/rules/tests.mdc @@ -3,4 +3,4 @@ description: Writing unit tests with `jest` globs: tests/**/* alwaysApply: false --- -Make use of the helpers in tests/mcp/test-helpers.ts. +Make use of the helpers in tests/mcp/test-helpers.ts for writing tests. diff --git a/README.md b/README.md index 9477129..220ed59 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Usually you will set the tool configuration in Claude Desktop, Cursor, or anothe } ``` -To use the CLI utility, you can use the following command: +To use the CLI utility, you can install the package globally and then use the following command: ```bash npx taskqueue --help @@ -145,6 +145,8 @@ npx --package=taskqueue-mcp taskqueue --help #### Task Approval +By default, all tasks and projects will be auto-approved when marked "done" by the AI agent. To require manual human task approval, set `autoApprove` to `false` when creating a project. + Task approval is controlled exclusively by the human user through the CLI: ```bash @@ -154,7 +156,7 @@ npx taskqueue approve-task -- Options: - `-f, --force`: Force approval even if the task is not marked as done -Note: Tasks must be marked as "done" with completed details before they can be approved (unless using --force). +Note: Tasks must be marked as "done" with completed details by the AI agent before they can be approved (unless using --force). #### Listing Tasks and Projects @@ -221,6 +223,7 @@ TaskManagerFile ├── initialPrompt: string # Original user request text ├── projectPlan: string # Additional project details ├── completed: boolean # Project completion status + ├── autoApprove: boolean # Set `false` to require manual user approval └── tasks: Task[] # Array of tasks ├── id: string # Format: "task-{number}" ├── title: string # Short task title diff --git a/package-lock.json b/package-lock.json index ad37864..d9ad553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "taskqueue-mcp", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "taskqueue-mcp", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT", "dependencies": { "@ai-sdk/deepseek": "^0.2.4", diff --git a/package.json b/package.json index 63925f0..7b58a4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "taskqueue-mcp", - "version": "1.4.0", + "version": "1.4.1", "description": "Task Queue MCP Server", "author": "Christopher C. Smith (christopher.smith@promptlytechnologies.com)", "main": "dist/src/server/index.js", diff --git a/src/client/cli.ts b/src/client/cli.ts index af3ade2..f99fea6 100644 --- a/src/client/cli.ts +++ b/src/client/cli.ts @@ -14,7 +14,7 @@ const program = new Command(); program .name("taskqueue") .description("CLI for the Task Manager MCP Server") - .version("1.4.0") + .version("1.4.1") .option( '-f, --file-path ', 'Specify the path to the tasks JSON file. Overrides TASK_MANAGER_FILE_PATH env var.' diff --git a/src/server/TaskManager.ts b/src/server/TaskManager.ts index 662b4ce..1360e9b 100644 --- a/src/server/TaskManager.ts +++ b/src/server/TaskManager.ts @@ -15,6 +15,7 @@ import { AddTasksSuccessData, DeleteTaskSuccessData, ReadProjectSuccessData, + UpdateTaskSuccessData } from "../types/response.js"; import { AppError, AppErrorCode } from "../types/errors.js"; import { FileSystemService } from "./FileSystemService.js"; @@ -134,7 +135,7 @@ export class TaskManager { projectPlan: projectPlan || initialPrompt, tasks: newTasks, completed: false, - autoApprove: autoApprove === true ? true : false, + autoApprove: autoApprove === false ? false : true, }; this.data.projects.push(newProject); @@ -519,7 +520,7 @@ export class TaskManager { status?: "not started" | "in progress" | "done"; completedDetails?: string; } - ): Promise { + ): Promise { await this.ensureInitialized(); await this.reloadFromDisk(); @@ -544,8 +545,14 @@ export class TaskManager { // Apply updates Object.assign(task, updates); + // Generate message if needed + let message: string | undefined = undefined; + if (updates.status === 'done' && proj.autoApprove === false) { + message = `Task marked as done but requires human approval.\nTo approve, user should run: npx taskqueue approve-task -- ${projectId} ${taskId}`; + } + await this.saveTasks(); - return task; + return { task, message }; } public async deleteTask(projectId: string, taskId: string): Promise { diff --git a/src/server/index.ts b/src/server/index.ts index 6c96d56..10dea69 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -10,7 +10,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot const server = new Server( { name: "task-manager-server", - version: "1.4.0" + version: "1.4.1" }, { capabilities: { diff --git a/src/server/toolExecutors.ts b/src/server/toolExecutors.ts index 9fddbb8..4642141 100644 --- a/src/server/toolExecutors.ts +++ b/src/server/toolExecutors.ts @@ -152,7 +152,7 @@ const createProjectToolExecutor: ToolExecutor = { const initialPrompt = validateRequiredStringParam(args.initialPrompt, "initialPrompt"); const validatedTasks = validateTaskObjects(args.tasks); const projectPlan = args.projectPlan !== undefined ? String(args.projectPlan) : undefined; - const autoApprove = args.autoApprove === true; + const autoApprove = args.autoApprove as boolean | undefined; if (args.projectPlan !== undefined && typeof args.projectPlan !== 'string') { throw new AppError( diff --git a/src/server/tools.ts b/src/server/tools.ts index 5b9aee5..9ea380a 100644 --- a/src/server/tools.ts +++ b/src/server/tools.ts @@ -3,6 +3,7 @@ import { TaskManager } from "./TaskManager.js"; import { toolExecutorMap } from "./toolExecutors.js"; import { AppError, AppErrorCode } from "../types/errors.js"; import { McpError, CallToolResult, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; +import { UpdateTaskSuccessData } from '../types/response.js'; // ---------------------- PROJECT TOOLS ---------------------- @@ -468,9 +469,11 @@ export async function executeToolAndHandleErrors( // 2. Execute the tool - Validation errors (protocol) or TaskManager errors (execution) might be thrown const resultData = await executor.execute(taskManager, args); - // 3. Format successful execution result + // 3. Format successful execution result using standard stringify + const responseText = JSON.stringify(resultData, null, 2); + return { - content: [{ type: "text", text: JSON.stringify(resultData, null, 2) }] + content: [{ type: "text", text: responseText }] }; } catch (error: AppError | unknown) { diff --git a/src/types/response.ts b/src/types/response.ts index 24087f0..86a0139 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -55,7 +55,7 @@ export interface ProjectCreationSuccessData { export interface DeleteTaskSuccessData { message: string; } - + export interface ReadProjectSuccessData { projectId: string; initialPrompt: string; @@ -64,4 +64,10 @@ export interface ProjectCreationSuccessData { autoApprove?: boolean; tasks: Task[]; } + + // Add the new interface for update_task success + export interface UpdateTaskSuccessData { + task: Task; // The updated task object + message?: string; // Optional message (e.g., approval reminder) + } \ No newline at end of file diff --git a/tests/mcp/tools/create-project.test.ts b/tests/mcp/tools/create-project.test.ts index 9bca3e5..91a2b10 100644 --- a/tests/mcp/tools/create-project.test.ts +++ b/tests/mcp/tools/create-project.test.ts @@ -111,6 +111,7 @@ describe('create_project Tool', () => { "Task 2", "Task 3" ]); + expect(project).toHaveProperty('autoApprove', true); }); it('should create a project with auto-approve enabled', async () => { diff --git a/tests/mcp/tools/update-task.test.ts b/tests/mcp/tools/update-task.test.ts index ddd42d0..9158e2b 100644 --- a/tests/mcp/tools/update-task.test.ts +++ b/tests/mcp/tools/update-task.test.ts @@ -82,6 +82,54 @@ describe('update_task Tool', () => { }); }); + it('should return reminder message when marking task done in a project requiring approval', async () => { + // Create a project that requires approval + const project = await createTestProjectInFile(context.testFilePath, { + initialPrompt: "Project Requiring Approval", + autoApprove: false // Explicitly set for clarity + }); + const task = await createTestTaskInFile(context.testFilePath, project.projectId, { + title: "Task to be Approved", + status: "in progress" + }); + + // Mark the task as done + const result = await context.client.callTool({ + name: "update_task", + arguments: { + projectId: project.projectId, + taskId: task.id, + status: "done", + completedDetails: "Task finished, awaiting approval." + } + }) as CallToolResult; + + // Verify the response includes the approval reminder within the JSON structure + verifyCallToolResult(result); // Basic verification + expect(result.isError).toBeFalsy(); + const responseText = (result.content[0] as { text: string }).text; + // Parse the JSON response + const responseData = JSON.parse(responseText); + + // Check the message property + expect(responseData).toHaveProperty('message'); + const expectedMessage = `Task marked as done but requires human approval.\nTo approve, user should run: npx taskqueue approve-task -- ${project.projectId} ${task.id}`; + expect(responseData.message).toBe(expectedMessage); + + // Check that the core task data is present under the 'task' key + expect(responseData).toHaveProperty('task'); + expect(responseData.task.id).toBe(task.id); + expect(responseData.task.status).toBe('done'); + expect(responseData.task.completedDetails).toBe("Task finished, awaiting approval."); + + // Also verify the task state in the file + await verifyTaskInFile(context.testFilePath, project.projectId, task.id, { + status: "done", + completedDetails: "Task finished, awaiting approval.", + approved: false // Should not be approved yet + }); + }); + it('should update task title and description', async () => { const project = await createTestProjectInFile(context.testFilePath, { initialPrompt: "Test Project"