fix(update-task): pass projectRoot and adjust parsing
Modified update-task-by-id core, direct function, and tool to pass projectRoot. Reverted parsing logic in core function to prioritize `{...}` extraction, resolving parsing errors. Fixed ReferenceError by correctly destructuring projectRoot.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"models": {
|
"models": {
|
||||||
"main": {
|
"main": {
|
||||||
"provider": "anthropic",
|
"provider": "openai",
|
||||||
"modelId": "claude-3-7-sonnet-20250219",
|
"modelId": "gpt-4o",
|
||||||
"maxTokens": 100000,
|
"maxTokens": 100000,
|
||||||
"temperature": 0.2
|
"temperature": 0.2
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,30 +6,40 @@
|
|||||||
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
|
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
|
||||||
import {
|
import {
|
||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode,
|
||||||
|
isSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
import { createLogWrapper } from '../../tools/utils.js';
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for updateTaskById with error handling.
|
* Direct function wrapper for updateTaskById with error handling.
|
||||||
*
|
*
|
||||||
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
|
* @param {Object} args - Command arguments containing id, prompt, useResearch, tasksJsonPath, and projectRoot.
|
||||||
|
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||||
|
* @param {string} args.id - Task ID (or subtask ID like "1.2").
|
||||||
|
* @param {string} args.prompt - New information/context prompt.
|
||||||
|
* @param {boolean} [args.research] - Whether to use research role.
|
||||||
|
* @param {string} [args.projectRoot] - Project root path.
|
||||||
* @param {Object} log - Logger object.
|
* @param {Object} log - Logger object.
|
||||||
* @param {Object} context - Context object containing session data.
|
* @param {Object} context - Context object containing session data.
|
||||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||||
*/
|
*/
|
||||||
export async function updateTaskByIdDirect(args, log, context = {}) {
|
export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||||
const { session } = context; // Only extract session, not reportProgress
|
const { session } = context;
|
||||||
// Destructure expected args, including the resolved tasksJsonPath
|
// Destructure expected args, including projectRoot
|
||||||
const { tasksJsonPath, id, prompt, research } = args;
|
const { tasksJsonPath, id, prompt, research, projectRoot } = args;
|
||||||
|
|
||||||
|
const logWrapper = createLogWrapper(log);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info(`Updating task with args: ${JSON.stringify(args)}`);
|
logWrapper.info(
|
||||||
|
`Updating task by ID via direct function. ID: ${id}, ProjectRoot: ${projectRoot}`
|
||||||
|
);
|
||||||
|
|
||||||
// Check if tasksJsonPath was provided
|
// Check if tasksJsonPath was provided
|
||||||
if (!tasksJsonPath) {
|
if (!tasksJsonPath) {
|
||||||
const errorMessage = 'tasksJsonPath is required but was not provided.';
|
const errorMessage = 'tasksJsonPath is required but was not provided.';
|
||||||
log.error(errorMessage);
|
logWrapper.error(errorMessage);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||||
@@ -41,7 +51,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
if (!id) {
|
if (!id) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
'No task ID specified. Please provide a task ID to update.';
|
'No task ID specified. Please provide a task ID to update.';
|
||||||
log.error(errorMessage);
|
logWrapper.error(errorMessage);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: { code: 'MISSING_TASK_ID', message: errorMessage },
|
error: { code: 'MISSING_TASK_ID', message: errorMessage },
|
||||||
@@ -52,7 +62,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
'No prompt specified. Please provide a prompt with new information for the task update.';
|
'No prompt specified. Please provide a prompt with new information for the task update.';
|
||||||
log.error(errorMessage);
|
logWrapper.error(errorMessage);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: { code: 'MISSING_PROMPT', message: errorMessage },
|
error: { code: 'MISSING_PROMPT', message: errorMessage },
|
||||||
@@ -71,7 +81,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
taskId = parseInt(id, 10);
|
taskId = parseInt(id, 10);
|
||||||
if (isNaN(taskId)) {
|
if (isNaN(taskId)) {
|
||||||
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
||||||
log.error(errorMessage);
|
logWrapper.error(errorMessage);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: { code: 'INVALID_TASK_ID', message: errorMessage },
|
error: { code: 'INVALID_TASK_ID', message: errorMessage },
|
||||||
@@ -89,66 +99,80 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
// Get research flag
|
// Get research flag
|
||||||
const useResearch = research === true;
|
const useResearch = research === true;
|
||||||
|
|
||||||
log.info(
|
logWrapper.info(
|
||||||
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
|
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
const wasSilent = isSilentMode();
|
||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
if (!wasSilent) {
|
||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the logger wrapper using the utility function
|
try {
|
||||||
const mcpLog = createLogWrapper(log);
|
|
||||||
|
|
||||||
// Execute core updateTaskById function with proper parameters
|
// Execute core updateTaskById function with proper parameters
|
||||||
await updateTaskById(
|
const updatedTask = await updateTaskById(
|
||||||
tasksPath,
|
tasksPath,
|
||||||
taskId,
|
taskId,
|
||||||
prompt,
|
prompt,
|
||||||
useResearch,
|
useResearch,
|
||||||
{
|
{
|
||||||
mcpLog, // Pass the wrapped logger
|
mcpLog: logWrapper,
|
||||||
session
|
session,
|
||||||
|
projectRoot
|
||||||
},
|
},
|
||||||
'json'
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Since updateTaskById doesn't return a value but modifies the tasks file,
|
// Check if the core function indicated the task wasn't updated (e.g., status was 'done')
|
||||||
// we'll return a success message
|
if (updatedTask === null) {
|
||||||
|
// Core function logs the reason, just return success with info
|
||||||
|
const message = `Task ${taskId} was not updated (likely already completed).`;
|
||||||
|
logWrapper.info(message);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: { message: message, taskId: taskId, updated: false },
|
||||||
|
fromCache: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task was updated successfully
|
||||||
|
const successMessage = `Successfully updated task with ID ${taskId} based on the prompt`;
|
||||||
|
logWrapper.success(successMessage);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
message: `Successfully updated task with ID ${taskId} based on the prompt`,
|
message: successMessage,
|
||||||
taskId,
|
taskId: taskId,
|
||||||
tasksPath: tasksPath, // Return the used path
|
tasksPath: tasksPath,
|
||||||
useResearch
|
useResearch: useResearch,
|
||||||
|
updated: true,
|
||||||
|
updatedTask: updatedTask
|
||||||
},
|
},
|
||||||
fromCache: false // This operation always modifies state and should never be cached
|
fromCache: false
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error updating task by ID: ${error.message}`);
|
logWrapper.error(`Error updating task by ID: ${error.message}`);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
code: 'UPDATE_TASK_ERROR',
|
code: 'UPDATE_TASK_CORE_ERROR',
|
||||||
message: error.message || 'Unknown error updating task'
|
message: error.message || 'Unknown error updating task'
|
||||||
},
|
},
|
||||||
fromCache: false
|
fromCache: false
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
// Make sure to restore normal logging even if there's an error
|
if (!wasSilent && isSilentMode()) {
|
||||||
disableSilentMode();
|
disableSilentMode();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ensure silent mode is disabled
|
logWrapper.error(`Setup error in updateTaskByIdDirect: ${error.message}`);
|
||||||
disableSilentMode();
|
if (isSilentMode()) disableSilentMode();
|
||||||
|
|
||||||
log.error(`Error updating task by ID: ${error.message}`);
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
code: 'UPDATE_TASK_ERROR',
|
code: 'DIRECT_FUNCTION_SETUP_ERROR',
|
||||||
message: error.message || 'Unknown error updating task'
|
message: error.message || 'Unknown setup error'
|
||||||
},
|
},
|
||||||
fromCache: false
|
fromCache: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import path from 'path'; // Import path
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
@@ -23,7 +24,7 @@ export function registerUpdateTaskTool(server) {
|
|||||||
'Updates a single task by ID with new information or context provided in the prompt.',
|
'Updates a single task by ID with new information or context provided in the prompt.',
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
id: z
|
id: z
|
||||||
.string()
|
.string() // ID can be number or string like "1.2"
|
||||||
.describe(
|
.describe(
|
||||||
"ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool."
|
"ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool."
|
||||||
),
|
),
|
||||||
@@ -40,59 +41,65 @@ export function registerUpdateTaskTool(server) {
|
|||||||
.describe('The directory of the project. Must be an absolute path.')
|
.describe('The directory of the project. Must be an absolute path.')
|
||||||
}),
|
}),
|
||||||
execute: async (args, { log, session }) => {
|
execute: async (args, { log, session }) => {
|
||||||
|
const toolName = 'update_task';
|
||||||
try {
|
try {
|
||||||
log.info(`Updating task with args: ${JSON.stringify(args)}`);
|
log.info(
|
||||||
|
`Executing ${toolName} tool with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
// Get project root from args or session
|
// 1. Get Project Root
|
||||||
const rootFolder =
|
const rootFolder = args.projectRoot;
|
||||||
args.projectRoot || getProjectRootFromSession(session, log);
|
if (!rootFolder || !path.isAbsolute(rootFolder)) {
|
||||||
|
log.error(
|
||||||
// Ensure project root was determined
|
`${toolName}: projectRoot is required and must be absolute.`
|
||||||
if (!rootFolder) {
|
);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
|
'projectRoot is required and must be absolute.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
log.info(`${toolName}: Project root: ${rootFolder}`);
|
||||||
|
|
||||||
// Resolve the path to tasks.json
|
// 2. Resolve Tasks Path
|
||||||
let tasksJsonPath;
|
let tasksJsonPath;
|
||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksJsonPath(
|
tasksJsonPath = findTasksJsonPath(
|
||||||
{ projectRoot: rootFolder, file: args.file },
|
{ projectRoot: rootFolder, file: args.file }, // Pass root and optional relative file
|
||||||
log
|
log
|
||||||
);
|
);
|
||||||
|
log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
`Failed to find tasks.json: ${error.message}`
|
`Failed to find tasks.json within project root '${rootFolder}': ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Call Direct Function - Include projectRoot
|
||||||
const result = await updateTaskByIdDirect(
|
const result = await updateTaskByIdDirect(
|
||||||
{
|
{
|
||||||
// Pass the explicitly resolved path
|
tasksJsonPath: tasksJsonPath, // Pass resolved path
|
||||||
tasksJsonPath: tasksJsonPath,
|
|
||||||
// Pass other relevant args
|
|
||||||
id: args.id,
|
id: args.id,
|
||||||
prompt: args.prompt,
|
prompt: args.prompt,
|
||||||
research: args.research
|
research: args.research,
|
||||||
|
projectRoot: rootFolder // <<< Pass projectRoot HERE
|
||||||
},
|
},
|
||||||
log,
|
log,
|
||||||
{ session }
|
{ session } // Pass context with session
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
// 4. Handle Result
|
||||||
log.info(`Successfully updated task with ID ${args.id}`);
|
log.info(
|
||||||
} else {
|
`${toolName}: Direct function result: success=${result.success}`
|
||||||
log.error(
|
|
||||||
`Failed to update task: ${result.error?.message || 'Unknown error'}`
|
|
||||||
);
|
);
|
||||||
}
|
// Pass the actual data from the result (contains updated task or message)
|
||||||
|
|
||||||
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(
|
||||||
return createErrorResponse(error.message);
|
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||||
|
);
|
||||||
|
return createErrorResponse(
|
||||||
|
`Internal tool error (${toolName}): ${error.message}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,29 +70,80 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) {
|
|||||||
|
|
||||||
let cleanedResponse = text.trim();
|
let cleanedResponse = text.trim();
|
||||||
const originalResponseForDebug = cleanedResponse;
|
const originalResponseForDebug = cleanedResponse;
|
||||||
|
let parseMethodUsed = 'raw'; // Keep track of which method worked
|
||||||
|
|
||||||
// Extract from Markdown code block first
|
// --- NEW Step 1: Try extracting between {} first ---
|
||||||
|
const firstBraceIndex = cleanedResponse.indexOf('{');
|
||||||
|
const lastBraceIndex = cleanedResponse.lastIndexOf('}');
|
||||||
|
let potentialJsonFromBraces = null;
|
||||||
|
|
||||||
|
if (firstBraceIndex !== -1 && lastBraceIndex > firstBraceIndex) {
|
||||||
|
potentialJsonFromBraces = cleanedResponse.substring(
|
||||||
|
firstBraceIndex,
|
||||||
|
lastBraceIndex + 1
|
||||||
|
);
|
||||||
|
if (potentialJsonFromBraces.length <= 2) {
|
||||||
|
potentialJsonFromBraces = null; // Ignore empty braces {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If {} extraction yielded something, try parsing it immediately
|
||||||
|
if (potentialJsonFromBraces) {
|
||||||
|
try {
|
||||||
|
const testParse = JSON.parse(potentialJsonFromBraces);
|
||||||
|
// It worked! Use this as the primary cleaned response.
|
||||||
|
cleanedResponse = potentialJsonFromBraces;
|
||||||
|
parseMethodUsed = 'braces';
|
||||||
|
report(
|
||||||
|
'info',
|
||||||
|
'Successfully parsed JSON content extracted between first { and last }.'
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
report(
|
||||||
|
'info',
|
||||||
|
'Content between {} looked promising but failed initial parse. Proceeding to other methods.'
|
||||||
|
);
|
||||||
|
// Reset cleanedResponse to original if brace parsing failed
|
||||||
|
cleanedResponse = originalResponseForDebug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 2: If brace parsing didn't work or wasn't applicable, try code block extraction ---
|
||||||
|
if (parseMethodUsed === 'raw') {
|
||||||
const codeBlockMatch = cleanedResponse.match(
|
const codeBlockMatch = cleanedResponse.match(
|
||||||
/```(?:json)?\s*([\s\S]*?)\s*```/
|
/```(?:json|javascript)?\s*([\s\S]*?)\s*```/i
|
||||||
);
|
);
|
||||||
if (codeBlockMatch) {
|
if (codeBlockMatch) {
|
||||||
cleanedResponse = codeBlockMatch[1].trim();
|
cleanedResponse = codeBlockMatch[1].trim();
|
||||||
|
parseMethodUsed = 'codeblock';
|
||||||
report('info', 'Extracted JSON content from Markdown code block.');
|
report('info', 'Extracted JSON content from Markdown code block.');
|
||||||
} else {
|
} else {
|
||||||
// If no code block, find first '{' and last '}' for the object
|
// --- Step 3: If code block failed, try stripping prefixes ---
|
||||||
const firstBrace = cleanedResponse.indexOf('{');
|
const commonPrefixes = [
|
||||||
const lastBrace = cleanedResponse.lastIndexOf('}');
|
'json\n',
|
||||||
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
'javascript\n'
|
||||||
cleanedResponse = cleanedResponse.substring(firstBrace, lastBrace + 1);
|
// ... other prefixes ...
|
||||||
report('info', 'Extracted content between first { and last }.');
|
];
|
||||||
} else {
|
let prefixFound = false;
|
||||||
|
for (const prefix of commonPrefixes) {
|
||||||
|
if (cleanedResponse.toLowerCase().startsWith(prefix)) {
|
||||||
|
cleanedResponse = cleanedResponse.substring(prefix.length).trim();
|
||||||
|
parseMethodUsed = 'prefix';
|
||||||
|
report('info', `Stripped prefix: "${prefix.trim()}"`);
|
||||||
|
prefixFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!prefixFound) {
|
||||||
report(
|
report(
|
||||||
'warn',
|
'warn',
|
||||||
'Response does not appear to contain a JSON object structure. Parsing raw response.'
|
'Response does not appear to contain {}, code block, or known prefix. Attempting raw parse.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 4: Attempt final parse ---
|
||||||
let parsedTask;
|
let parsedTask;
|
||||||
try {
|
try {
|
||||||
parsedTask = JSON.parse(cleanedResponse);
|
parsedTask = JSON.parse(cleanedResponse);
|
||||||
@@ -168,7 +219,7 @@ async function updateTaskById(
|
|||||||
context = {},
|
context = {},
|
||||||
outputFormat = 'text'
|
outputFormat = 'text'
|
||||||
) {
|
) {
|
||||||
const { session, mcpLog } = context;
|
const { session, mcpLog, projectRoot } = context;
|
||||||
const logFn = mcpLog || consoleLog;
|
const logFn = mcpLog || consoleLog;
|
||||||
const isMCP = !!mcpLog;
|
const isMCP = !!mcpLog;
|
||||||
|
|
||||||
@@ -343,7 +394,8 @@ The changes described in the prompt should be thoughtfully applied to make the t
|
|||||||
prompt: userPrompt,
|
prompt: userPrompt,
|
||||||
systemPrompt: systemPrompt,
|
systemPrompt: systemPrompt,
|
||||||
role,
|
role,
|
||||||
session
|
session,
|
||||||
|
projectRoot
|
||||||
});
|
});
|
||||||
report('success', 'Successfully received text response from AI service');
|
report('success', 'Successfully received text response from AI service');
|
||||||
// --- End AI Service Call ---
|
// --- End AI Service Call ---
|
||||||
|
|||||||
@@ -43,13 +43,12 @@ const updatedTaskArraySchema = z.array(updatedTaskSchema);
|
|||||||
* Parses an array of task objects from AI's text response.
|
* Parses an array of task objects from AI's text response.
|
||||||
* @param {string} text - Response text from AI.
|
* @param {string} text - Response text from AI.
|
||||||
* @param {number} expectedCount - Expected number of tasks.
|
* @param {number} expectedCount - Expected number of tasks.
|
||||||
* @param {Function | Object} logFn - The logging function (consoleLog) or MCP log object.
|
* @param {Function | Object} logFn - The logging function or MCP log object.
|
||||||
* @param {boolean} isMCP - Flag indicating if logFn is MCP logger.
|
* @param {boolean} isMCP - Flag indicating if logFn is MCP logger.
|
||||||
* @returns {Array} Parsed and validated tasks array.
|
* @returns {Array} Parsed and validated tasks array.
|
||||||
* @throws {Error} If parsing or validation fails.
|
* @throws {Error} If parsing or validation fails.
|
||||||
*/
|
*/
|
||||||
function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
||||||
// Helper for consistent logging inside parser
|
|
||||||
const report = (level, ...args) => {
|
const report = (level, ...args) => {
|
||||||
if (isMCP) {
|
if (isMCP) {
|
||||||
if (typeof logFn[level] === 'function') logFn[level](...args);
|
if (typeof logFn[level] === 'function') logFn[level](...args);
|
||||||
@@ -70,32 +69,70 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
|||||||
let cleanedResponse = text.trim();
|
let cleanedResponse = text.trim();
|
||||||
const originalResponseForDebug = cleanedResponse;
|
const originalResponseForDebug = cleanedResponse;
|
||||||
|
|
||||||
// Extract from Markdown code block first
|
// Step 1: Attempt to extract from Markdown code block first
|
||||||
const codeBlockMatch = cleanedResponse.match(
|
const codeBlockMatch = cleanedResponse.match(
|
||||||
/```(?:json)?\s*([\s\S]*?)\s*```/
|
/```(?:json|javascript)?\s*([\s\S]*?)\s*```/i // Made case-insensitive, allow js
|
||||||
);
|
);
|
||||||
if (codeBlockMatch) {
|
if (codeBlockMatch) {
|
||||||
cleanedResponse = codeBlockMatch[1].trim();
|
cleanedResponse = codeBlockMatch[1].trim();
|
||||||
report('info', 'Extracted JSON content from Markdown code block.');
|
report('info', 'Extracted content from Markdown code block.');
|
||||||
} else {
|
} else {
|
||||||
// If no code block, find first '[' and last ']' for the array
|
// Step 2 (if no code block): Attempt to strip common language identifiers/intro text
|
||||||
|
// List common prefixes AI might add before JSON
|
||||||
|
const commonPrefixes = [
|
||||||
|
'json\n',
|
||||||
|
'javascript\n',
|
||||||
|
'python\n', // Language identifiers
|
||||||
|
'here are the updated tasks:',
|
||||||
|
'here is the updated json:', // Common intro phrases
|
||||||
|
'updated tasks:',
|
||||||
|
'updated json:',
|
||||||
|
'response:',
|
||||||
|
'output:'
|
||||||
|
];
|
||||||
|
let prefixFound = false;
|
||||||
|
for (const prefix of commonPrefixes) {
|
||||||
|
if (cleanedResponse.toLowerCase().startsWith(prefix)) {
|
||||||
|
cleanedResponse = cleanedResponse.substring(prefix.length).trim();
|
||||||
|
report('info', `Stripped prefix: "${prefix.trim()}"`);
|
||||||
|
prefixFound = true;
|
||||||
|
break; // Stop after finding the first matching prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3 (if no code block and no prefix stripped, or after stripping): Find first '[' and last ']'
|
||||||
|
// This helps if there's still leading/trailing text around the array
|
||||||
const firstBracket = cleanedResponse.indexOf('[');
|
const firstBracket = cleanedResponse.indexOf('[');
|
||||||
const lastBracket = cleanedResponse.lastIndexOf(']');
|
const lastBracket = cleanedResponse.lastIndexOf(']');
|
||||||
if (firstBracket !== -1 && lastBracket > firstBracket) {
|
if (firstBracket !== -1 && lastBracket > firstBracket) {
|
||||||
cleanedResponse = cleanedResponse.substring(
|
const extractedArray = cleanedResponse.substring(
|
||||||
firstBracket,
|
firstBracket,
|
||||||
lastBracket + 1
|
lastBracket + 1
|
||||||
);
|
);
|
||||||
|
// Basic check to see if the extraction looks like JSON
|
||||||
|
if (extractedArray.length > 2) {
|
||||||
|
// More than just '[]'
|
||||||
|
cleanedResponse = extractedArray; // Use the extracted array content
|
||||||
|
if (!codeBlockMatch && !prefixFound) {
|
||||||
|
// Only log if we didn't already log extraction/stripping
|
||||||
report('info', 'Extracted content between first [ and last ].');
|
report('info', 'Extracted content between first [ and last ].');
|
||||||
} else {
|
}
|
||||||
|
} else if (!codeBlockMatch && !prefixFound) {
|
||||||
report(
|
report(
|
||||||
'warn',
|
'warn',
|
||||||
'Response does not appear to contain a JSON array structure. Parsing raw response.'
|
'Found brackets "[]" but content seems empty or invalid. Proceeding with original cleaned response.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (!codeBlockMatch && !prefixFound) {
|
||||||
|
// Only warn if no other extraction method worked
|
||||||
|
report(
|
||||||
|
'warn',
|
||||||
|
'Response does not appear to contain a JSON code block, known prefix, or clear array structure ([...]). Attempting to parse raw response.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to parse the array
|
// Step 4: Attempt to parse the (hopefully) cleaned JSON array
|
||||||
let parsedTasks;
|
let parsedTasks;
|
||||||
try {
|
try {
|
||||||
parsedTasks = JSON.parse(cleanedResponse);
|
parsedTasks = JSON.parse(cleanedResponse);
|
||||||
@@ -114,7 +151,7 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Array structure
|
// Step 5: Validate Array structure
|
||||||
if (!Array.isArray(parsedTasks)) {
|
if (!Array.isArray(parsedTasks)) {
|
||||||
report(
|
report(
|
||||||
'error',
|
'error',
|
||||||
@@ -135,7 +172,7 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate each task object using Zod
|
// Step 6: Validate each task object using Zod
|
||||||
const validationResult = updatedTaskArraySchema.safeParse(parsedTasks);
|
const validationResult = updatedTaskArraySchema.safeParse(parsedTasks);
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
report('error', 'Parsed task array failed Zod validation.');
|
report('error', 'Parsed task array failed Zod validation.');
|
||||||
|
|||||||
@@ -510,8 +510,6 @@ function detectCamelCaseFlags(args) {
|
|||||||
|
|
||||||
// Export all utility functions and configuration
|
// Export all utility functions and configuration
|
||||||
export {
|
export {
|
||||||
// CONFIG, <-- Already Removed
|
|
||||||
// getConfig <-- Removing now
|
|
||||||
LOG_LEVELS,
|
LOG_LEVELS,
|
||||||
log,
|
log,
|
||||||
readJSON,
|
readJSON,
|
||||||
@@ -532,5 +530,4 @@ export {
|
|||||||
resolveEnvVariable,
|
resolveEnvVariable,
|
||||||
getTaskManager,
|
getTaskManager,
|
||||||
findProjectRoot
|
findProjectRoot
|
||||||
// getConfig <-- Removed
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user