// mime-todo/lib/issue.ts // Valid status transitions const VALID_TRANSITIONS = { "open": ["in-progress", "hold", "cancelled"], "in-progress": ["done", "hold", "open", "cancelled"], "hold": ["open", "in-progress", "cancelled"], "done": ["open"], "cancelled": ["open"], }; export function validateStatusTransition(from, to) { if (from === to) return null; // no-op is fine const allowed = VALID_TRANSITIONS[from]; if (!allowed?.includes(to)) { return `Invalid status transition: ${from} → ${to}. Allowed from ${from}: ${allowed?.join(", ") ?? "none"}`; } return null; } export function parseRelationships(text) { const relationships = {}; // Handle empty relationships if (!text || text.trim() === "") { return relationships; } // Simple parsing for format like "dependsOn:3" const parts = text.split(/,\s*/).filter(p => p.trim() !== ""); for (const part of parts) { const [key, value] = part.split(":").map(s => s.trim()); if (key && value) { relationships[key] = value.split(/[\s]+/).map(Number).filter(n => !isNaN(n)); } } return relationships; } export function parseIssue(text) { const lines = text.split(/\r?\n/); const issue = {}; let inDescription = false; const descLines = []; const bodyLines = []; for (const line of lines) { if (!inDescription) { if (line.startsWith("ID:")) issue.id = Number(line.slice(3).trim()); else if (line.startsWith("Type:")) issue.type = line.slice(5).trim(); else if (line.startsWith("Title:")) issue.title = line.slice(6).trim(); else if (line.startsWith("Status:")) issue.status = line.slice(7).trim(); else if (line.startsWith("Priority:")) issue.priority = line.slice(9).trim(); else if (line.startsWith("Created:")) issue.created = line.slice(8).trim(); else if (line.startsWith("DueStart:")) issue.dueStart = line.slice(9).trim(); else if (line.startsWith("DueEnd:")) issue.dueEnd = line.slice(7).trim(); else if (line.startsWith("Module:")) issue.module = line.slice("Module:".length).trim(); else if (line.startsWith("Relationships:")) { const rel = line.slice("Relationships:".length).trim(); issue.relationships = parseRelationships(rel); } else if (line.startsWith("Description:")) { inDescription = true; descLines.push(line.slice("Description:".length).trim()); } } else { // Description continuation if (line.trim() === "") { // blank line ends description inDescription = false; continue; } if (/^\s+/.test(line)) { descLines.push(line.trim()); continue; } // first non-indented line ends description inDescription = false; bodyLines.push(line); } } issue.description = descLines.join("\n"); issue.body = bodyLines.join("\n"); return issue; }