From 7660a29a1a1983a73d99cb7295f55488741eed3d Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:54:19 +0200 Subject: [PATCH] chore: implement requested changes --- mcp-server/src/custom-sdk/schema-converter.js | 6 +- scripts/modules/task-manager/expand-task.js | 2 - src/schemas/update-tasks.js | 4 +- tests/unit/prompts/prompt-migration.test.js | 15 +- .../task-manager/update-task-by-id.test.js | 201 ++++++++++++++++++ 5 files changed, 213 insertions(+), 15 deletions(-) diff --git a/mcp-server/src/custom-sdk/schema-converter.js b/mcp-server/src/custom-sdk/schema-converter.js index 4622941e..f6acce22 100644 --- a/mcp-server/src/custom-sdk/schema-converter.js +++ b/mcp-server/src/custom-sdk/schema-converter.js @@ -79,9 +79,7 @@ function generateExampleFromSchema(schema) { if (def.checks) { const minCheck = def.checks.find((c) => c.kind === 'min'); const maxCheck = def.checks.find((c) => c.kind === 'max'); - if (minCheck && minCheck.value >= 20) { - return ''; - } else if (minCheck && maxCheck) { + if (minCheck && maxCheck) { return ( '' ); + } else if (minCheck) { + return ''; } else if (maxCheck) { return ''; } diff --git a/scripts/modules/task-manager/expand-task.js b/scripts/modules/task-manager/expand-task.js index de6ba2c4..e1f82585 100644 --- a/scripts/modules/task-manager/expand-task.js +++ b/scripts/modules/task-manager/expand-task.js @@ -24,8 +24,6 @@ import { hasCodebaseAnalysis } from '../config-manager.js'; import { getPromptManager } from '../prompt-manager.js'; -import generateTaskFiles from './generate-task-files.js'; -import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js'; import { getDebugFlag, getDefaultSubtasks } from '../config-manager.js'; import { getPromptManager } from '../prompt-manager.js'; import { findProjectRoot, flattenTasksWithSubtasks } from '../utils.js'; diff --git a/src/schemas/update-tasks.js b/src/schemas/update-tasks.js index 0f2a5f88..85e9f461 100644 --- a/src/schemas/update-tasks.js +++ b/src/schemas/update-tasks.js @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { BaseTaskSchema } from './base-schemas.js'; +import { BaseTaskSchema, SubtaskSchema } from './base-schemas.js'; export const UpdatedTaskSchema = BaseTaskSchema.extend({ - subtasks: z.array(z.any()).nullable().default(null) + subtasks: z.array(SubtaskSchema).nullable().default(null) }); export const UpdateTasksResponseSchema = z.object({ diff --git a/tests/unit/prompts/prompt-migration.test.js b/tests/unit/prompts/prompt-migration.test.js index 23a4f48a..a513a107 100644 --- a/tests/unit/prompts/prompt-migration.test.js +++ b/tests/unit/prompts/prompt-migration.test.js @@ -38,17 +38,16 @@ describe('Prompt Migration Validation', () => { if (lowerContent.includes(lowerPhrase)) { // Check if this phrase is allowed in its context const allowedInContext = allowedContexts[lowerPhrase]; - if (allowedInContext) { - const isAllowed = allowedInContext.some((context) => + const isAllowed = + allowedInContext && + allowedInContext.some((context) => lowerContent.includes(context.toLowerCase()) ); - if (isAllowed) { - return; // Skip this phrase - it's allowed in this context - } - } - // If we get here, the phrase is not allowed - expect(lowerContent).not.toContain(lowerPhrase); + expect(isAllowed).toBe( + true, + `File ${file} contains banned phrase "${phrase}" without allowed context` + ); } }); }); diff --git a/tests/unit/scripts/modules/task-manager/update-task-by-id.test.js b/tests/unit/scripts/modules/task-manager/update-task-by-id.test.js index 08afb9d8..f2258c4d 100644 --- a/tests/unit/scripts/modules/task-manager/update-task-by-id.test.js +++ b/tests/unit/scripts/modules/task-manager/update-task-by-id.test.js @@ -136,3 +136,204 @@ describe('updateTaskById validation', () => { expect(log).toHaveBeenCalled(); }); }); + +describe('updateTaskById success path with generateObjectService', () => { + let fs; + let generateObjectService; + + beforeEach(async () => { + jest.clearAllMocks(); + jest.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('process.exit called'); + }); + fs = await import('fs'); + const aiServices = await import( + '../../../../../scripts/modules/ai-services-unified.js' + ); + generateObjectService = aiServices.generateObjectService; + }); + + test('successfully updates task with all fields from generateObjectService', async () => { + fs.existsSync.mockReturnValue(true); + readJSON.mockReturnValue({ + tag: 'master', + tasks: [ + { + id: 1, + title: 'Original Task', + description: 'Original description', + status: 'pending', + dependencies: [], + priority: 'low', + details: null, + testStrategy: null, + subtasks: [] + } + ] + }); + + const updatedTaskData = { + id: 1, + title: 'Updated Task', + description: 'Updated description', + status: 'pending', + dependencies: [2], + priority: 'high', + details: 'New implementation details', + testStrategy: 'Unit tests required', + subtasks: [ + { + id: 1, + title: 'Subtask 1', + description: 'First subtask', + status: 'pending', + dependencies: [] + } + ] + }; + + generateObjectService.mockResolvedValue({ + mainResult: { + task: updatedTaskData + }, + telemetryData: { + model: 'claude-3-5-sonnet-20241022', + inputTokens: 100, + outputTokens: 200 + } + }); + + const result = await updateTaskById( + 'tasks/tasks.json', + 1, + 'Update task with new requirements', + false, + { tag: 'master' }, + 'json' + ); + + // Verify generateObjectService was called (not generateTextService) + expect(generateObjectService).toHaveBeenCalled(); + const callArgs = generateObjectService.mock.calls[0][0]; + + // Verify correct arguments were passed + expect(callArgs).toMatchObject({ + role: 'main', + commandName: 'update-task', + objectName: 'task' + }); + expect(callArgs.schema).toBeDefined(); + expect(callArgs.systemPrompt).toContain('update a software development task'); + expect(callArgs.prompt).toContain('Update task with new requirements'); + + // Verify the returned task contains all expected fields + expect(result).toEqual({ + updatedTask: expect.objectContaining({ + id: 1, + title: 'Updated Task', + description: 'Updated description', + status: 'pending', + dependencies: [2], + priority: 'high', + details: 'New implementation details', + testStrategy: 'Unit tests required', + subtasks: expect.arrayContaining([ + expect.objectContaining({ + id: 1, + title: 'Subtask 1', + description: 'First subtask', + status: 'pending' + }) + ]) + }), + telemetryData: expect.objectContaining({ + model: 'claude-3-5-sonnet-20241022', + inputTokens: 100, + outputTokens: 200 + }), + tagInfo: undefined + }); + }); + + test('handles generateObjectService with malformed mainResult', async () => { + fs.existsSync.mockReturnValue(true); + readJSON.mockReturnValue({ + tag: 'master', + tasks: [ + { + id: 1, + title: 'Task', + description: 'Description', + status: 'pending', + dependencies: [], + priority: 'medium', + details: null, + testStrategy: null, + subtasks: [] + } + ] + }); + + generateObjectService.mockResolvedValue({ + mainResult: { + task: null // Malformed: task is null + }, + telemetryData: {} + }); + + await expect( + updateTaskById( + 'tasks/tasks.json', + 1, + 'Update task', + false, + { tag: 'master' }, + 'json' + ) + ).rejects.toThrow('Received invalid task object from AI'); + }); + + test('handles generateObjectService with missing required fields', async () => { + fs.existsSync.mockReturnValue(true); + readJSON.mockReturnValue({ + tag: 'master', + tasks: [ + { + id: 1, + title: 'Task', + description: 'Description', + status: 'pending', + dependencies: [], + priority: 'medium', + details: null, + testStrategy: null, + subtasks: [] + } + ] + }); + + generateObjectService.mockResolvedValue({ + mainResult: { + task: { + id: 1, + // Missing title and description + status: 'pending', + dependencies: [], + priority: 'medium' + } + }, + telemetryData: {} + }); + + await expect( + updateTaskById( + 'tasks/tasks.json', + 1, + 'Update task', + false, + { tag: 'master' }, + 'json' + ) + ).rejects.toThrow('Updated task missing required fields'); + }); +});