From 4639eee09701caa330c0ca4595a9d1a974b25caf Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:34:23 +0300 Subject: [PATCH] fix(ai-validation): comprehensive fixes for AI response validation issues (#1000) * fix(ai-validation): comprehensive fixes for AI response validation issues - Fix update command validation when AI omits subtasks/status/dependencies - Fix add-task command when AI returns non-string details field - Fix update-task command when AI subtasks miss required fields - Add preprocessing to ensure proper field types before validation - Prevent split() errors on non-string fields - Set proper defaults for missing required fields * chore: run format * chore: implement coderabbit suggestions --- .../modules/task-manager/update-task-by-id.js | 39 ++++++++++++++++++- scripts/modules/task-manager/update-tasks.js | 25 +++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/scripts/modules/task-manager/update-task-by-id.js b/scripts/modules/task-manager/update-task-by-id.js index b77044fc..19603897 100644 --- a/scripts/modules/task-manager/update-task-by-id.js +++ b/scripts/modules/task-manager/update-task-by-id.js @@ -190,8 +190,45 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) { throw new Error('Parsed AI response is not a valid JSON object.'); } + // Preprocess the task to ensure subtasks have proper structure + const preprocessedTask = { + ...parsedTask, + status: parsedTask.status || 'pending', + dependencies: Array.isArray(parsedTask.dependencies) + ? parsedTask.dependencies + : [], + details: + typeof parsedTask.details === 'string' + ? parsedTask.details + : String(parsedTask.details || ''), + testStrategy: + typeof parsedTask.testStrategy === 'string' + ? parsedTask.testStrategy + : String(parsedTask.testStrategy || ''), + // Ensure subtasks is an array and each subtask has required fields + subtasks: Array.isArray(parsedTask.subtasks) + ? parsedTask.subtasks.map((subtask) => ({ + ...subtask, + title: subtask.title || '', + description: subtask.description || '', + status: subtask.status || 'pending', + dependencies: Array.isArray(subtask.dependencies) + ? subtask.dependencies + : [], + details: + typeof subtask.details === 'string' + ? subtask.details + : String(subtask.details || ''), + testStrategy: + typeof subtask.testStrategy === 'string' + ? subtask.testStrategy + : String(subtask.testStrategy || '') + })) + : [] + }; + // Validate the parsed task object using Zod - const validationResult = updatedTaskSchema.safeParse(parsedTask); + const validationResult = updatedTaskSchema.safeParse(preprocessedTask); if (!validationResult.success) { report('error', 'Parsed task object failed Zod validation.'); validationResult.error.errors.forEach((err) => { diff --git a/scripts/modules/task-manager/update-tasks.js b/scripts/modules/task-manager/update-tasks.js index fa048037..43b854b2 100644 --- a/scripts/modules/task-manager/update-tasks.js +++ b/scripts/modules/task-manager/update-tasks.js @@ -196,7 +196,18 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) { ); } - const validationResult = updatedTaskArraySchema.safeParse(parsedTasks); + // Preprocess tasks to ensure required fields have proper defaults + const preprocessedTasks = parsedTasks.map((task) => ({ + ...task, + // Ensure subtasks is always an array (not null or undefined) + subtasks: Array.isArray(task.subtasks) ? task.subtasks : [], + // Ensure status has a default value if missing + status: task.status || 'pending', + // Ensure dependencies is always an array + dependencies: Array.isArray(task.dependencies) ? task.dependencies : [] + })); + + const validationResult = updatedTaskArraySchema.safeParse(preprocessedTasks); if (!validationResult.success) { report('error', 'Parsed task array failed Zod validation.'); validationResult.error.errors.forEach((err) => { @@ -442,7 +453,17 @@ async function updateTasks( data.tasks.forEach((task, index) => { if (updatedTasksMap.has(task.id)) { // Only update if the task was part of the set sent to AI - data.tasks[index] = updatedTasksMap.get(task.id); + const updatedTask = updatedTasksMap.get(task.id); + // Merge the updated task with the existing one to preserve fields like subtasks + data.tasks[index] = { + ...task, // Keep all existing fields + ...updatedTask, // Override with updated fields + // Ensure subtasks field is preserved if not provided by AI + subtasks: + updatedTask.subtasks !== undefined + ? updatedTask.subtasks + : task.subtasks + }; actualUpdateCount++; } });