Skip to main content
Glama
task-parser.ts5.55 kB
/** * 任务解析器 * 解析 tasks.md 文件中的任务列表 */ import * as fs from 'fs/promises'; import type { Task, Progress, TaskStatus } from '../types/openspec.js'; export class TaskParser { /** * 解析 tasks.md 文件 * * 支持格式: * - [ ] 待完成 * - [x] 已完成 * - [-] 进行中 * * 任务 ID 格式: * - **1.1** 任务描述 * - **2.3.1** 子任务描述 */ async parseTasks(tasksPath: string): Promise<Task[]> { const content = await fs.readFile(tasksPath, 'utf-8'); return this.parseTasksFromContent(content); } /** * 从内容解析任务 */ parseTasksFromContent(content: string): Task[] { const lines = content.split('\n'); const tasks: Task[] = []; let currentSection = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNum = i + 1; // 检测章节标题 (## 或 ###) const sectionMatch = line.match(/^#{2,3}\s+(.+)/); if (sectionMatch) { currentSection = sectionMatch[1].trim(); continue; } // 检测任务项 - 支持多种格式 // 格式1: - [x] **1.1** 任务描述 // 格式2: - [ ] **1.1** 任务描述 // 格式3: - [-] **1.1** 任务描述 const taskMatch = line.match( /^(\s*)-\s+\[([ x-])\]\s+\*\*(\d+(?:\.\d+)*)\*\*\s+(.+)/ ); if (taskMatch) { const [, indent, statusChar, id, title] = taskMatch; const depth = Math.floor(indent.length / 2); // 计算嵌套层级 tasks.push({ id, section: currentSection, title: title.trim(), status: this.parseStatus(statusChar), line: lineNum, }); continue; } // 检测简单任务项(无 ID) // 格式: - [ ] 任务描述 const simpleTaskMatch = line.match(/^(\s*)-\s+\[([ x-])\]\s+(.+)/); if (simpleTaskMatch && !simpleTaskMatch[3].startsWith('**')) { const [, indent, statusChar, title] = simpleTaskMatch; // 为简单任务生成一个基于行号的 ID tasks.push({ id: `line-${lineNum}`, section: currentSection, title: title.trim(), status: this.parseStatus(statusChar), line: lineNum, }); } } return tasks; } /** * 解析状态字符 */ private parseStatus(char: string): TaskStatus { switch (char) { case 'x': return 'done'; case '-': return 'in_progress'; default: return 'pending'; } } /** * 获取状态字符 */ private getStatusChar(status: TaskStatus): string { switch (status) { case 'done': return 'x'; case 'in_progress': return '-'; default: return ' '; } } /** * 计算进度 */ calculateProgress(tasks: Task[]): Progress { const total = tasks.length; const completed = tasks.filter((t) => t.status === 'done').length; const inProgress = tasks.filter((t) => t.status === 'in_progress').length; const pending = tasks.filter((t) => t.status === 'pending').length; return { total, completed, inProgress, pending, percentage: total > 0 ? Math.round((completed / total) * 100) : 0, }; } /** * 更新任务状态 */ async updateTaskStatus( tasksPath: string, taskId: string, newStatus: TaskStatus ): Promise<void> { const content = await fs.readFile(tasksPath, 'utf-8'); const lines = content.split('\n'); const statusChar = this.getStatusChar(newStatus); let found = false; for (let i = 0; i < lines.length; i++) { // 匹配带 ID 的任务 const taskMatch = lines[i].match( /^(\s*-\s+\[)([ x-])(\]\s+\*\*)(\d+(?:\.\d+)*)(\*\*.+)/ ); if (taskMatch && taskMatch[4] === taskId) { lines[i] = `${taskMatch[1]}${statusChar}${taskMatch[3]}${taskMatch[4]}${taskMatch[5]}`; found = true; break; } // 匹配基于行号的任务 ID if (taskId === `line-${i + 1}`) { const simpleMatch = lines[i].match(/^(\s*-\s+\[)([ x-])(\]\s+.+)/); if (simpleMatch) { lines[i] = `${simpleMatch[1]}${statusChar}${simpleMatch[3]}`; found = true; break; } } } if (!found) { throw new Error(`Task ${taskId} not found`); } await fs.writeFile(tasksPath, lines.join('\n'), 'utf-8'); } /** * 批量更新任务状态 */ async batchUpdateTaskStatus( tasksPath: string, updates: { taskId: string; status: TaskStatus }[] ): Promise<void> { const content = await fs.readFile(tasksPath, 'utf-8'); let lines = content.split('\n'); for (const { taskId, status } of updates) { const statusChar = this.getStatusChar(status); for (let i = 0; i < lines.length; i++) { const taskMatch = lines[i].match( /^(\s*-\s+\[)([ x-])(\]\s+\*\*)(\d+(?:\.\d+)*)(\*\*.+)/ ); if (taskMatch && taskMatch[4] === taskId) { lines[i] = `${taskMatch[1]}${statusChar}${taskMatch[3]}${taskMatch[4]}${taskMatch[5]}`; break; } if (taskId === `line-${i + 1}`) { const simpleMatch = lines[i].match(/^(\s*-\s+\[)([ x-])(\]\s+.+)/); if (simpleMatch) { lines[i] = `${simpleMatch[1]}${statusChar}${simpleMatch[3]}`; break; } } } } await fs.writeFile(tasksPath, lines.join('\n'), 'utf-8'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Lumiaqian/openspec-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server