UNPKG

openai-compatible-task-master

Version:

使用MCP解析PRD文档并生成任务列表

247 lines 10.8 kB
import chalk from 'chalk'; import { resolveFullPath, ensureFileExists, readJsonFile } from '../utils/path_util.js'; /** * 从指定路径读取tasks.json文件并验证其格式 * @param projectDir 项目根目录路径 * @param tasksPath 任务文件相对路径 * @returns 验证通过的任务数据 * @throws 当文件不存在或格式不正确时抛出错误 */ export async function readTasksFile(projectDir, tasksPath) { console.debug(chalk.yellow(`读取任务文件 - projectDir: ${projectDir}, tasksPath: ${tasksPath}`)); // 构建完整路径 const fullTasksPath = resolveFullPath(projectDir, tasksPath); console.debug(chalk.yellow(`解析后路径 - fullTasksPath: ${fullTasksPath}`)); // 检查文件是否存在 if (!await ensureFileExists(fullTasksPath)) { throw new Error(`任务文件不存在: ${fullTasksPath}`); } try { // 读取并解析JSON文件 const tasksData = await readJsonFile(fullTasksPath); return tasksData; } catch (error) { const err = error; console.error(chalk.red(`解析任务文件失败: ${err}`)); throw new Error(`任务文件格式不正确: ${err}`); } } /** * 递归检查任务及其子任务是否都已完成 * @param task 要检查的任务 * @returns 如果任务及其所有子任务都已完成则返回true,否则返回false */ function isTaskAndSubTasksCompleted(task) { // 如果当前任务未完成,直接返回false if (task.status !== 'done') { return false; } // 如果没有子任务,则当前任务已完成 if (!task.subTasks || task.subTasks.length === 0) { return true; } // 检查所有子任务是否都已完成 return task.subTasks.every(subTask => isTaskAndSubTasksCompleted(subTask)); } /** * 查找下一个未完成的任务,考虑子任务状态 * @param tasks 任务列表 * @returns 下一个未完成的任务,如果所有任务都已完成则返回undefined */ export function findNextPendingTask(tasks) { // 首先查找状态不是'done'的主任务 const pendingMainTask = tasks.find(task => task.status !== 'done'); if (pendingMainTask) { return pendingMainTask; } // 如果所有主任务状态都是'done',检查是否有未完成的子任务 for (const task of tasks) { if (!isTaskAndSubTasksCompleted(task)) { return task; // 返回包含未完成子任务的父任务 } } // 所有任务和子任务都已完成 return undefined; } /** * 查找路径上的第一个未完成的子任务 * @param task 当前任务 * @param path 当前已累积的路径数组 * @returns 包含路径上所有任务的数组,最后一个是未完成的子任务(如果找到);如果没有未完成的子任务,返回空数组 */ export function findFirstPendingSubtask(task, path = []) { // 创建当前路径 const currentPath = [...path, task]; console.debug(chalk.cyan(`正在查找子任务 - 任务ID: ${task.id}, 标题: ${task.title}, 状态: ${task.status}`)); // 检查是否有子任务 if (task.subTasks && task.subTasks.length > 0) { console.debug(chalk.blue(`任务有子任务 - 父任务ID: ${task.id}, 子任务数量: ${task.subTasks.length}`)); // 按ID排序子任务 const sortedSubTasks = [...task.subTasks].sort((a, b) => { return String(a.id).localeCompare(String(b.id), undefined, { numeric: true }); }); console.debug(chalk.magenta(`子任务已排序 - 父任务ID: ${task.id}`)); // 遍历排序后的子任务 for (const subTask of sortedSubTasks) { console.debug(chalk.blue(`递归检查子任务 - 子任务ID: ${subTask.id}, 状态: ${subTask.status}`)); if (subTask.status !== 'done') { console.debug(chalk.green(`找到未完成子任务 - 子任务ID: ${subTask.id}, 状态: ${subTask.status}`)); return [...currentPath, subTask]; } // 递归查找子任务的子任务 const subPath = findFirstPendingSubtask(subTask, currentPath); // 如果找到未完成的子任务,返回路径 if (subPath.length > 0) { console.debug(chalk.green(`在子任务深层找到未完成任务 - 路径长度: ${subPath.length}`)); return subPath; } } console.debug(chalk.yellow(`所有子任务都已完成 - 任务ID: ${task.id}`)); } // 如果没有子任务或所有子任务已完成,检查当前任务状态 if (task.status !== 'done') { console.debug(chalk.green(`当前任务未完成且无未完成子任务 - 任务ID: ${task.id}, 状态: ${task.status}`)); return currentPath; } // 如果当前任务已完成且没有未完成的子任务,返回空数组 console.debug(chalk.yellow(`当前任务已完成且无未完成子任务 - 任务ID: ${task.id}`)); return []; } /** * 处理任务列表,根据任务状态处理子任务 * @param tasks 原始任务列表 * @returns 处理后的任务列表 */ function processTasksWithSubTasks(tasks) { // 深拷贝任务列表,避免修改原始数据 const processedTasks = JSON.parse(JSON.stringify(tasks)); // 处理每个根任务 for (const task of processedTasks) { if (task.subTasks && task.subTasks.length > 0) { // 按ID排序子任务 task.subTasks = [...task.subTasks].sort((a, b) => { return String(a.id).localeCompare(String(b.id), undefined, { numeric: true }); }); // 如果根任务是完成状态,清空子任务 if (task.status === 'done') { task.subTasks = []; } else { // 对于 pending 和 in-progress 状态的根任务,保留所有子任务 // 递归处理每个子任务 processSubTasksRecursively(task.subTasks); } } } return processedTasks; } /** * 递归处理子任务,保持子任务的完整层级结构 * @param subTasks 子任务数组 */ function processSubTasksRecursively(subTasks) { for (const task of subTasks) { if (task.subTasks && task.subTasks.length > 0) { // 按ID排序子任务 task.subTasks = [...task.subTasks].sort((a, b) => { return String(a.id).localeCompare(String(b.id), undefined, { numeric: true }); }); // 递归处理下一层子任务 processSubTasksRecursively(task.subTasks); } } } /** * 递归获取所有未完成的任务(包括子任务) * @param task 当前任务 * @param path 当前任务路径 * @returns 未完成任务的详细信息数组 */ function getUnfinishedTaskDetails(task, path = '') { const results = []; // 如果当前任务未完成,添加到结果中 if (task.status !== 'done') { results.push({ id: task.id, title: task.title, status: task.status, priority: task.priority, details: task.details, path: path ? `${path} > ${task.title}` : task.title }); } // 递归处理子任务 if (task.subTasks && task.subTasks.length > 0) { for (const subTask of task.subTasks) { const subTaskPath = path ? `${path} > ${task.title}` : task.title; results.push(...getUnfinishedTaskDetails(subTask, subTaskPath)); } } return results; } /** * 获取任务列表信息,支持子任务展示 * @param projectDir 项目根目录路径 * @param tasksPath 任务文件相对路径 * @param showDetails 是否显示未完成任务的详细信息 * @returns 格式化的任务列表信息 */ export async function listTasks(projectDir, tasksPath, showDetails = false) { try { // 读取任务文件 const tasksData = await readTasksFile(projectDir, tasksPath); // 处理任务列表,根据任务状态处理子任务 const processedTasks = processTasksWithSubTasks(tasksData.tasks); // 使用原始任务列表查找下一个未完成的任务 const nextPendingTask = findNextPendingTask(tasksData.tasks); // 查找未完成的子任务路径 let nextPendingSubtaskPath = []; console.debug(chalk.blue(`开始查找未完成子任务路径...`)); // 首先尝试从下一个未完成的主任务查找 if (nextPendingTask) { console.debug(chalk.blue(`从未完成主任务开始查找 - 任务ID: ${nextPendingTask.id}`)); nextPendingSubtaskPath = findFirstPendingSubtask(nextPendingTask); console.debug(chalk.blue(`查找结果 - 路径长度: ${nextPendingSubtaskPath.length}`)); } // 如果路径为空或没有未完成的主任务,遍历所有主任务寻找未完成的子任务 if (nextPendingSubtaskPath.length === 0) { console.debug(chalk.yellow(`未找到路径或无未完成主任务,开始检查所有主任务...`)); for (const task of tasksData.tasks) { console.debug(chalk.yellow(`检查主任务 - 任务ID: ${task.id}`)); const path = findFirstPendingSubtask(task); if (path.length > 0) { console.debug(chalk.green(`在主任务${task.id}中找到未完成子任务,路径长度: ${path.length}`)); nextPendingSubtaskPath = path; break; } } } console.debug(chalk.magenta(`最终未完成子任务路径长度: ${nextPendingSubtaskPath.length}`)); if (nextPendingSubtaskPath.length > 0) { console.debug(chalk.magenta(`路径中最后一个任务ID: ${nextPendingSubtaskPath[nextPendingSubtaskPath.length - 1].id}`)); } // 如果需要显示详细信息,获取所有未完成任务的详细信息 let unfinishedTaskDetails; if (showDetails) { unfinishedTaskDetails = tasksData.tasks.reduce((acc, task) => { return [...acc, ...getUnfinishedTaskDetails(task)]; }, []); } // 返回处理后的任务列表和下一个未完成的任务 return { tasksData: { ...tasksData, tasks: processedTasks }, nextPendingTask, nextPendingSubtaskPath, unfinishedTaskDetails }; } catch (error) { const err = error; throw new Error(`获取任务列表失败: ${err.message}`); } } //# sourceMappingURL=list_tasks.js.map