fix(mcp): Prevent tag deletion on subtask update

Adds a safety net to the writeJSON utility to prevent data loss when updating subtasks via the MCP server.

The MCP process was inadvertently causing the _rawTaggedData property, which holds the complete multi-tag structure, to be lost. When writeJSON received the data for only a single tag, it would overwrite the entire tasks.json file, deleting all other tags.

This fix makes writeJSON more robust. If it receives data that looks like a single, resolved tag without the complete structure, it re-reads the full tasks.json file from disk. It then carefully merges the updated data back into the correct tag within the full structure, preserving all other tags.
This commit is contained in:
Eyal Toledano
2025-06-13 20:48:27 -04:00
parent d3edd24b5a
commit 2e2d290c63
4 changed files with 38 additions and 17 deletions

View File

@@ -331,7 +331,7 @@ Output Requirements:
if (outputFormat === 'text' && getDebugFlag(session)) {
console.log('>>> DEBUG: About to call writeJSON with updated data...');
}
writeJSON(tasksPath, data);
writeJSON(tasksPath, data, projectRoot);
if (outputFormat === 'text' && getDebugFlag(session)) {
console.log('>>> DEBUG: writeJSON call completed.');
}

View File

@@ -615,9 +615,38 @@ function writeJSON(filepath, data, projectRoot = null, tag = null) {
try {
let finalData = data;
// If data represents resolved tag data but lost _rawTaggedData (edge-case observed in MCP path)
if (
!data._rawTaggedData &&
projectRoot &&
Array.isArray(data.tasks) &&
!hasTaggedStructure(data)
) {
const resolvedTag = tag || getCurrentTag(projectRoot);
if (isDebug) {
console.log(
`writeJSON: Detected resolved tag data missing _rawTaggedData. Re-reading raw data to prevent data loss for tag '${resolvedTag}'.`
);
}
// Re-read the full file to get the complete tagged structure
const rawFullData = JSON.parse(fs.readFileSync(filepath, 'utf8'));
// Merge the updated data into the full structure
finalData = {
...rawFullData,
[resolvedTag]: {
// Preserve existing tag metadata if it exists, otherwise use what's passed
...(rawFullData[resolvedTag]?.metadata || {}),
...(data.metadata ? { metadata: data.metadata } : {}),
tasks: data.tasks // The updated tasks array is the source of truth here
}
};
}
// If we have _rawTaggedData, this means we're working with resolved tag data
// and need to merge it back into the full tagged structure
if (data && data._rawTaggedData && projectRoot) {
else if (data && data._rawTaggedData && projectRoot) {
const resolvedTag = tag || getCurrentTag(projectRoot);
// Get the original tagged data