Files
claude-task-master/apps/extension/src/utils/taskFileReader.ts
Ralph Khreish 722b6c5836 feat: add universal logger instead of console.log.
- fixed esbuild issues
- converted to npm instead of pnpm since root project is npm (might switch whole project to pnpm later)
2025-07-28 17:03:35 +03:00

216 lines
5.6 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { logger } from './logger';
export interface TaskFileData {
details?: string;
testStrategy?: string;
}
export interface TasksJsonStructure {
[tagName: string]: {
tasks: TaskWithDetails[];
metadata: {
createdAt: string;
description?: string;
};
};
}
export interface TaskWithDetails {
id: string | number;
title: string;
description: string;
status: string;
priority: string;
dependencies?: (string | number)[];
details?: string;
testStrategy?: string;
subtasks?: TaskWithDetails[];
}
/**
* Reads tasks.json file directly and extracts implementation details and test strategy
* @param taskId - The ID of the task to read (e.g., "1" or "1.2" for subtasks)
* @param tagName - The tag/context name (defaults to "master")
* @returns TaskFileData with details and testStrategy fields
*/
export async function readTaskFileData(
taskId: string,
tagName: string = 'master'
): Promise<TaskFileData> {
try {
// Check if we're in a VS Code webview context
if (typeof window !== 'undefined' && (window as any).vscode) {
// Use VS Code API to read the file
const vscode = (window as any).vscode;
// Request file content from the extension
return new Promise((resolve, reject) => {
const messageId = Date.now().toString();
// Listen for response
const messageHandler = (event: MessageEvent) => {
const message = event.data;
if (
message.type === 'taskFileData' &&
message.messageId === messageId
) {
window.removeEventListener('message', messageHandler);
if (message.error) {
reject(new Error(message.error));
} else {
resolve(message.data);
}
}
};
window.addEventListener('message', messageHandler);
// Send request to extension
vscode.postMessage({
type: 'readTaskFileData',
messageId,
taskId,
tagName
});
// Timeout after 5 seconds
setTimeout(() => {
window.removeEventListener('message', messageHandler);
reject(new Error('Timeout reading task file data'));
}, 5000);
});
} else {
// Fallback for non-VS Code environments
return { details: undefined, testStrategy: undefined };
}
} catch (error) {
logger.error('Error reading task file data:', error);
return { details: undefined, testStrategy: undefined };
}
}
/**
* Finds a task by ID within a tasks array, supporting subtask notation (e.g., "1.2")
* @param tasks - Array of tasks to search
* @param taskId - ID to search for
* @returns The task object if found, undefined otherwise
*/
export function findTaskById(
tasks: TaskWithDetails[],
taskId: string
): TaskWithDetails | undefined {
// Check if this is a subtask ID with dotted notation (e.g., "1.2")
if (taskId.includes('.')) {
const [parentId, subtaskId] = taskId.split('.');
logger.log('🔍 Looking for subtask:', { parentId, subtaskId, taskId });
// Find the parent task first
const parentTask = tasks.find((task) => String(task.id) === parentId);
if (!parentTask || !parentTask.subtasks) {
logger.log('❌ Parent task not found or has no subtasks:', parentId);
return undefined;
}
logger.log(
'📋 Parent task found with',
parentTask.subtasks.length,
'subtasks'
);
logger.log(
'🔍 Subtask IDs in parent:',
parentTask.subtasks.map((st) => st.id)
);
// Find the subtask within the parent
const subtask = parentTask.subtasks.find(
(st) => String(st.id) === subtaskId
);
if (subtask) {
logger.log('✅ Subtask found:', subtask.id);
} else {
logger.log('❌ Subtask not found:', subtaskId);
}
return subtask;
}
// For regular task IDs (not dotted notation)
for (const task of tasks) {
// Convert both to strings for comparison to handle string vs number IDs
if (String(task.id) === String(taskId)) {
return task;
}
}
return undefined;
}
/**
* Parses tasks.json content and extracts task file data (details and testStrategy only)
* @param content - Raw tasks.json content
* @param taskId - Task ID to find
* @param tagName - Tag name to use
* @param workspacePath - Path to workspace root (not used anymore but kept for compatibility)
* @returns TaskFileData with details and testStrategy only
*/
export function parseTaskFileData(
content: string,
taskId: string,
tagName: string,
workspacePath?: string
): TaskFileData {
logger.log('🔍 parseTaskFileData called with:', {
taskId,
tagName,
contentLength: content.length
});
try {
const tasksJson: TasksJsonStructure = JSON.parse(content);
logger.log('📊 Available tags:', Object.keys(tasksJson));
// Get the tag data
const tagData = tasksJson[tagName];
if (!tagData || !tagData.tasks) {
logger.log('❌ Tag not found or no tasks in tag:', tagName);
return { details: undefined, testStrategy: undefined };
}
logger.log('📋 Tag found with', tagData.tasks.length, 'tasks');
logger.log(
'🔍 Available task IDs:',
tagData.tasks.map((t) => t.id)
);
// Find the task
const task = findTaskById(tagData.tasks, taskId);
if (!task) {
logger.log('❌ Task not found:', taskId);
return { details: undefined, testStrategy: undefined };
}
logger.log('✅ Task found:', task.id);
logger.log(
'📝 Task has details:',
!!task.details,
'length:',
task.details?.length
);
logger.log(
'🧪 Task has testStrategy:',
!!task.testStrategy,
'length:',
task.testStrategy?.length
);
return {
details: task.details,
testStrategy: task.testStrategy
};
} catch (error) {
logger.error('❌ Error parsing tasks.json:', error);
return { details: undefined, testStrategy: undefined };
}
}