openai-compatible-task-master
Version:
使用MCP解析PRD文档并生成任务列表
247 lines • 10.8 kB
JavaScript
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