Skip to main content
Glama
taskParser.ts6.83 kB
/** * Task Parser Module * * Parses tasks.md files into structured task objects. * Supports both legacy format (no metadata) and new format (with metadata). * * Metadata format: (phase: setup, type: backend, depends: T001, priority: high) */ import * as fs from 'fs'; export interface TaskMetadata { phase?: string; type?: string; depends?: string[]; priority?: 'high' | 'medium' | 'low'; effort?: number; tags?: string[]; } export interface Task { id: string; description: string; status: 'pending' | 'in_progress' | 'completed'; metadata: TaskMetadata; line: number; } export interface DependencyGraph { nodes: Map<string, Task>; edges: Map<string, string[]>; // taskId -> [dependencyIds] } /** * Parse tasks.md file into structured task objects */ export function parseTasksFile(filePath: string): Task[] { if (!fs.existsSync(filePath)) { throw new Error(`Tasks file not found: ${filePath}`); } const content = fs.readFileSync(filePath, 'utf-8'); return parseTasksContent(content); } /** * Parse tasks.md content into structured task objects */ export function parseTasksContent(content: string): Task[] { const tasks: Task[] = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const task = parseTaskLine(line, i + 1); if (task) { tasks.push(task); } } return tasks; } /** * Parse a single task line * Supports formats: * - [ ] T001: Description * - [x] T002: Description * - [~] T003: Description (in progress) * - [ ] T004: Description (phase: setup, type: backend, depends: T001) */ export function parseTaskLine(line: string, lineNumber: number): Task | null { // Match checkbox patterns: [ ], [x], [~], etc. const taskRegex = /^[\s-]*\[([x~\s])\]\s+([A-Z]\d+):\s+(.+)$/i; const match = line.match(taskRegex); if (!match) { return null; } const [, statusChar, id, rest] = match; // Determine status from checkbox character let status: Task['status']; if (statusChar === 'x' || statusChar === 'X') { status = 'completed'; } else if (statusChar === '~') { status = 'in_progress'; } else { status = 'pending'; } // Extract description and metadata const { description, metadata } = extractMetadata(rest); return { id, description, status, metadata, line: lineNumber, }; } /** * Extract metadata from task description * Format: Description (phase: setup, type: backend, depends: T001, T002) */ export function extractMetadata(text: string): { description: string; metadata: TaskMetadata } { const metadataRegex = /^(.+?)\s*\(([^)]+)\)\s*$/; const match = text.match(metadataRegex); if (!match) { // No metadata found return { description: text.trim(), metadata: {} }; } const [, description, metadataStr] = match; const metadata: TaskMetadata = {}; // Parse metadata key-value pairs const pairs = metadataStr.split(',').map(s => s.trim()); for (const pair of pairs) { const [key, value] = pair.split(':').map(s => s.trim()); switch (key.toLowerCase()) { case 'phase': metadata.phase = value; break; case 'type': metadata.type = value; break; case 'depends': // Support multiple dependencies: "T001, T002" or "T001" metadata.depends = value.split(/[\s,]+/).filter(Boolean); break; case 'priority': if (value === 'high' || value === 'medium' || value === 'low') { metadata.priority = value; } break; case 'effort': { const effort = parseInt(value, 10); if (!isNaN(effort)) { metadata.effort = effort; } break; } case 'tags': metadata.tags = value.split(/[\s,]+/).filter(Boolean); break; } } return { description: description.trim(), metadata }; } /** * Build dependency graph from tasks */ export function buildDependencyGraph(tasks: Task[]): DependencyGraph { const nodes = new Map<string, Task>(); const edges = new Map<string, string[]>(); // Build nodes map for (const task of tasks) { nodes.set(task.id, task); } // Build edges map (dependencies) for (const task of tasks) { if (task.metadata.depends && task.metadata.depends.length > 0) { edges.set(task.id, task.metadata.depends); } else { edges.set(task.id, []); } } return { nodes, edges }; } /** * Detect circular dependencies in the dependency graph * Returns array of task IDs involved in circular dependencies */ export function detectCircularDependencies(graph: DependencyGraph): string[] { const visited = new Set<string>(); const recursionStack = new Set<string>(); const circularTasks: string[] = []; function dfs(taskId: string): boolean { visited.add(taskId); recursionStack.add(taskId); const dependencies = graph.edges.get(taskId) || []; for (const depId of dependencies) { if (!visited.has(depId)) { if (dfs(depId)) { circularTasks.push(taskId); return true; } } else if (recursionStack.has(depId)) { // Found circular dependency circularTasks.push(taskId); return true; } } recursionStack.delete(taskId); return false; } // Check all nodes for (const taskId of graph.nodes.keys()) { if (!visited.has(taskId)) { dfs(taskId); } } return [...new Set(circularTasks)]; } /** * Get tasks that have no dependencies (can be started immediately) */ export function getUnblockedTasks(tasks: Task[]): Task[] { const graph = buildDependencyGraph(tasks); const completedTaskIds = new Set( tasks.filter(t => t.status === 'completed').map(t => t.id) ); return tasks.filter(task => { if (task.status === 'completed') { return false; // Already completed } const dependencies = graph.edges.get(task.id) || []; // All dependencies must be completed return dependencies.every(depId => completedTaskIds.has(depId)); }); } /** * Topological sort of tasks based on dependencies * Returns tasks in execution order (dependencies first) */ export function topologicalSort(tasks: Task[]): Task[] { const graph = buildDependencyGraph(tasks); const sorted: Task[] = []; const visited = new Set<string>(); function visit(taskId: string) { if (visited.has(taskId)) { return; } visited.add(taskId); const dependencies = graph.edges.get(taskId) || []; for (const depId of dependencies) { if (graph.nodes.has(depId)) { visit(depId); } } const task = graph.nodes.get(taskId); if (task) { sorted.push(task); } } for (const task of tasks) { visit(task.id); } return sorted; }

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/flight505/MCP_DinCoder'

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