UNPKG

mcp-ai-agent-guidelines

Version:

A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices

158 lines 5.54 kB
/** * Tasks parser - extracts task data from tasks.md markdown * * @module strategies/speckit/tasks-parser */ /** * Parse tasks.md markdown content into structured Tasks * * Extracts task items from markdown format with task ID patterns like * TASK-001, P4-019, etc. * * @param markdown - The tasks.md markdown content to parse * @returns Structured Tasks object with items array * * @example * ```typescript * const markdown = ` * # Tasks * * ## Phase 1 * * ### TASK-001: Setup database * - **Priority**: high * - **Estimate**: 2 days * - **Description**: Initialize PostgreSQL database * - **Acceptance Criteria**: * - Database is running * - Schema is created * `; * * const tasks = parseTasksFromMarkdown(markdown); * // Returns: { items: [{ id: "TASK-001", title: "Setup database", ... }] } * ``` */ export function parseTasksFromMarkdown(markdown) { const items = []; // Pattern to match task headings like "### TASK-001: Title" or "### P4-019: Title" const taskHeadingPattern = /^###\s+([A-Z0-9]+-\d+):\s*(.+)$/gm; let match; // biome-ignore lint/suspicious/noAssignInExpressions: Required for regex iteration while ((match = taskHeadingPattern.exec(markdown)) !== null) { const taskId = match[1]; const title = match[2].trim(); // Extract task details from the section following this heading const sectionStart = match.index + match[0].length; const nextHeadingMatch = markdown.slice(sectionStart).match(/^###?\s+/m); const sectionEnd = nextHeadingMatch ? sectionStart + (nextHeadingMatch.index ?? 0) : markdown.length; const taskSection = markdown.slice(sectionStart, sectionEnd); // Extract task properties from bullet points const task = { id: taskId, title, description: extractDescription(taskSection), priority: extractPriority(taskSection), estimate: extractEstimate(taskSection), phase: extractPhase(taskSection), acceptanceCriteria: extractAcceptanceCriteria(taskSection), dependencies: extractDependencies(taskSection), }; items.push(task); } return { items, metadata: { createdAt: new Date().toISOString(), lastUpdated: new Date().toISOString(), }, }; } /** * Extract description from task section */ function extractDescription(section) { // Look for "- **Description**: ..." or "**Description**: ..." const descMatch = section.match(/^\s*[-*]?\s*\*\*Description\*\*:\s*(.+)$/im); if (descMatch) { return descMatch[1].trim(); } // Fallback: use first paragraph after heading const lines = section.trim().split("\n"); for (const line of lines) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith("-") && !trimmed.startsWith("*")) { return trimmed; } } return ""; } /** * Extract priority from task section */ function extractPriority(section) { const priorityMatch = section.match(/^\s*[-*]?\s*\*\*Priority\*\*:\s*(\w+)/im); if (priorityMatch) { const priority = priorityMatch[1].toLowerCase(); if (priority === "high" || priority === "medium" || priority === "low") { return priority; } } return "medium"; // default } /** * Extract estimate from task section */ function extractEstimate(section) { const estimateMatch = section.match(/^\s*[-*]?\s*\*\*Estimate\*\*:\s*(.+)$/im); return estimateMatch ? estimateMatch[1].trim() : ""; } /** * Extract phase from task section */ function extractPhase(section) { const phaseMatch = section.match(/^\s*[-*]?\s*\*\*Phase\*\*:\s*(.+)$/im); return phaseMatch ? phaseMatch[1].trim() : undefined; } /** * Extract acceptance criteria from task section */ function extractAcceptanceCriteria(section) { const criteria = []; // Look for "- **Acceptance Criteria**:" followed by nested bullets const criteriaMatch = section.match(/^\s*[-*]?\s*\*\*Acceptance Criteria\*\*:\s*$/im); if (criteriaMatch) { const startIdx = (criteriaMatch.index !== undefined ? criteriaMatch.index : 0) + criteriaMatch[0].length; const remainingText = section.slice(startIdx); // Extract nested bullet points (indented with 2+ spaces) const nestedBulletPattern = /^\s{2,}[-*]\s+(.+)$/gm; let bulletMatch; // biome-ignore lint/suspicious/noAssignInExpressions: Required for regex iteration while ((bulletMatch = nestedBulletPattern.exec(remainingText)) !== null) { criteria.push(bulletMatch[1].trim()); } } return criteria; } /** * Extract dependencies from task section */ function extractDependencies(section) { const dependencies = []; // Look for "- **Dependencies**: TASK-001, TASK-002" or similar const depsMatch = section.match(/^\s*[-*]?\s*\*\*Dependencies\*\*:\s*(.+)$/im); if (depsMatch) { const depsText = depsMatch[1]; // Extract task IDs from the text const taskIdPattern = /\b([A-Z0-9]+-\d+)\b/g; let idMatch; // biome-ignore lint/suspicious/noAssignInExpressions: Required for regex iteration while ((idMatch = taskIdPattern.exec(depsText)) !== null) { dependencies.push(idMatch[1]); } } return dependencies.length > 0 ? dependencies : undefined; } //# sourceMappingURL=tasks-parser.js.map