Compare commits
11 Commits
fix/vscode
...
fix-task-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b594795238 | ||
|
|
8f97b6aead | ||
|
|
3f31c34f8d | ||
|
|
ef23beac0d | ||
|
|
077cbb99de | ||
|
|
ee2af1760f | ||
|
|
2421c205c4 | ||
|
|
33208388bd | ||
|
|
3fa0c7f7af | ||
|
|
0609b9ae28 | ||
|
|
b79eb4f7a2 |
5
.changeset/eleven-horses-shop.md
Normal file
5
.changeset/eleven-horses-shop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix for tasks not found when using string IDs
|
||||
@@ -262,6 +262,43 @@ function hasTaggedStructure(data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes task IDs to ensure they are numbers instead of strings
|
||||
* @param {Array} tasks - Array of tasks to normalize
|
||||
*/
|
||||
function normalizeTaskIds(tasks) {
|
||||
if (!Array.isArray(tasks)) return;
|
||||
|
||||
tasks.forEach((task) => {
|
||||
// Convert task ID to number with validation
|
||||
if (task.id !== undefined) {
|
||||
const parsedId = parseInt(task.id, 10);
|
||||
if (!isNaN(parsedId) && parsedId > 0) {
|
||||
task.id = parsedId;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert subtask IDs to numbers with validation
|
||||
if (Array.isArray(task.subtasks)) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
if (subtask.id !== undefined) {
|
||||
// Check for dot notation (which shouldn't exist in storage)
|
||||
if (typeof subtask.id === 'string' && subtask.id.includes('.')) {
|
||||
// Extract the subtask part after the dot
|
||||
const parts = subtask.id.split('.');
|
||||
subtask.id = parseInt(parts[parts.length - 1], 10);
|
||||
} else {
|
||||
const parsedSubtaskId = parseInt(subtask.id, 10);
|
||||
if (!isNaN(parsedSubtaskId) && parsedSubtaskId > 0) {
|
||||
subtask.id = parsedSubtaskId;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and parses a JSON file
|
||||
* @param {string} filepath - Path to the JSON file
|
||||
@@ -322,6 +359,8 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
console.log(`File is in legacy format, performing migration...`);
|
||||
}
|
||||
|
||||
normalizeTaskIds(data.tasks);
|
||||
|
||||
// This is legacy format - migrate it to tagged format
|
||||
const migratedData = {
|
||||
master: {
|
||||
@@ -401,6 +440,16 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
// Store reference to the raw tagged data for functions that need it
|
||||
const originalTaggedData = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Normalize IDs in all tags before storing as originalTaggedData
|
||||
for (const tagName in originalTaggedData) {
|
||||
if (
|
||||
originalTaggedData[tagName] &&
|
||||
Array.isArray(originalTaggedData[tagName].tasks)
|
||||
) {
|
||||
normalizeTaskIds(originalTaggedData[tagName].tasks);
|
||||
}
|
||||
}
|
||||
|
||||
// Check and auto-switch git tags if enabled (for existing tagged format)
|
||||
// This needs to run synchronously BEFORE tag resolution
|
||||
if (projectRoot) {
|
||||
@@ -448,6 +497,8 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
// Get the data for the resolved tag
|
||||
const tagData = data[resolvedTag];
|
||||
if (tagData && tagData.tasks) {
|
||||
normalizeTaskIds(tagData.tasks);
|
||||
|
||||
// Add the _rawTaggedData property and the resolved tag to the returned data
|
||||
const result = {
|
||||
...tagData,
|
||||
@@ -464,6 +515,8 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
// If the resolved tag doesn't exist, fall back to master
|
||||
const masterData = data.master;
|
||||
if (masterData && masterData.tasks) {
|
||||
normalizeTaskIds(masterData.tasks);
|
||||
|
||||
if (isDebug) {
|
||||
console.log(
|
||||
`Tag '${resolvedTag}' not found, falling back to master with ${masterData.tasks.length} tasks`
|
||||
@@ -493,6 +546,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
// If anything goes wrong, try to return master or empty
|
||||
const masterData = data.master;
|
||||
if (masterData && masterData.tasks) {
|
||||
normalizeTaskIds(masterData.tasks);
|
||||
return {
|
||||
...masterData,
|
||||
_rawTaggedData: originalTaggedData
|
||||
@@ -1412,5 +1466,6 @@ export {
|
||||
createStateJson,
|
||||
markMigrationForNotice,
|
||||
flattenTasksWithSubtasks,
|
||||
ensureTagMetadata
|
||||
ensureTagMetadata,
|
||||
normalizeTaskIds
|
||||
};
|
||||
|
||||
@@ -23,6 +23,82 @@ describe('Task Finder', () => {
|
||||
expect(result.originalSubtaskCount).toBeNull();
|
||||
});
|
||||
|
||||
test('should find tasks when JSON contains string IDs (normalized to numbers)', () => {
|
||||
// Simulate tasks loaded from JSON with string IDs and mixed subtask notations
|
||||
const tasksWithStringIds = [
|
||||
{ id: '1', title: 'First Task' },
|
||||
{
|
||||
id: '2',
|
||||
title: 'Second Task',
|
||||
subtasks: [
|
||||
{ id: '1', title: 'Subtask One' },
|
||||
{ id: '2.2', title: 'Subtask Two (with dotted notation)' } // Testing dotted notation
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Fifth Task',
|
||||
subtasks: [
|
||||
{ id: '5.1', title: 'Subtask with dotted ID' }, // Should normalize to 1
|
||||
{ id: '3', title: 'Subtask with simple ID' } // Should stay as 3
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// The readJSON function should normalize these IDs to numbers
|
||||
// For this test, we'll manually normalize them to simulate what happens
|
||||
tasksWithStringIds.forEach((task) => {
|
||||
task.id = parseInt(task.id, 10);
|
||||
if (task.subtasks) {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
// Handle dotted notation like "5.1" -> extract the subtask part
|
||||
if (typeof subtask.id === 'string' && subtask.id.includes('.')) {
|
||||
const parts = subtask.id.split('.');
|
||||
subtask.id = parseInt(parts[parts.length - 1], 10);
|
||||
} else {
|
||||
subtask.id = parseInt(subtask.id, 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Test finding tasks by numeric ID
|
||||
const result1 = findTaskById(tasksWithStringIds, 5);
|
||||
expect(result1.task).toBeDefined();
|
||||
expect(result1.task.id).toBe(5);
|
||||
expect(result1.task.title).toBe('Fifth Task');
|
||||
|
||||
// Test finding tasks by string ID
|
||||
const result2 = findTaskById(tasksWithStringIds, '5');
|
||||
expect(result2.task).toBeDefined();
|
||||
expect(result2.task.id).toBe(5);
|
||||
|
||||
// Test finding subtasks with normalized IDs
|
||||
const result3 = findTaskById(tasksWithStringIds, '2.1');
|
||||
expect(result3.task).toBeDefined();
|
||||
expect(result3.task.id).toBe(1);
|
||||
expect(result3.task.title).toBe('Subtask One');
|
||||
expect(result3.task.isSubtask).toBe(true);
|
||||
|
||||
// Test subtask that was originally "2.2" (should be normalized to 2)
|
||||
const result4 = findTaskById(tasksWithStringIds, '2.2');
|
||||
expect(result4.task).toBeDefined();
|
||||
expect(result4.task.id).toBe(2);
|
||||
expect(result4.task.title).toBe('Subtask Two (with dotted notation)');
|
||||
|
||||
// Test subtask that was originally "5.1" (should be normalized to 1)
|
||||
const result5 = findTaskById(tasksWithStringIds, '5.1');
|
||||
expect(result5.task).toBeDefined();
|
||||
expect(result5.task.id).toBe(1);
|
||||
expect(result5.task.title).toBe('Subtask with dotted ID');
|
||||
|
||||
// Test subtask that was originally "3" (should stay as 3)
|
||||
const result6 = findTaskById(tasksWithStringIds, '5.3');
|
||||
expect(result6.task).toBeDefined();
|
||||
expect(result6.task.id).toBe(3);
|
||||
expect(result6.task.title).toBe('Subtask with simple ID');
|
||||
});
|
||||
|
||||
test('should find a subtask using dot notation', () => {
|
||||
const result = findTaskById(sampleTasks.tasks, '3.1');
|
||||
expect(result.task).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user