export interface TodoItem { step: number; text: string; completed: boolean; } function cleanStepText(text: string): string { let cleaned = text .replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1") .replace(/`([^`]+)`/g, "$1") .replace( /^(Use|Run|Execute|Create|Write|Read|Check|Verify|Update|Modify|Add|Remove|Delete|Install|Analyze|Review|Test)\s+(the\s+)?/i, "", ) .replace(/\s+/g, " ") .trim(); if (cleaned.length > 0) { cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1); } if (cleaned.length > 50) { cleaned = `${cleaned.slice(0, 47)}...`; } return cleaned; } function isTemplatePlanStep(text: string): boolean { const normalized = text .toLowerCase() .replace(/[`:*_()[\]{}]/g, "") .replace(/\s+/g, " ") .trim(); return ( normalized === "target files and rationale" || normalized === "implementation steps" || normalized === "verification method" || normalized === "risks and edge cases" || normalized === "archive step" || normalized === "completion" || normalized.includes("실행/유지/수정 선택") || normalized.includes("선택 프롬프트") ); } export function extractTodoItems(message: string): TodoItem[] { const items: TodoItem[] = []; const headerMatch = message.match(/\*{0,2}Plan:\*{0,2}\s*\n/i); if (!headerMatch) return items; const planSection = message.slice(message.indexOf(headerMatch[0]) + headerMatch[0].length); const numberedPattern = /^\s*(\d+)[.)]\s+\*{0,2}([^*\n]+)/gm; for (const match of planSection.matchAll(numberedPattern)) { const rawText = match[2]; if (!rawText) continue; const text = rawText .trim() .replace(/\*{1,2}$/, "") .trim(); if (text.length > 5 && !text.startsWith("`") && !text.startsWith("/") && !text.startsWith("-")) { const cleaned = cleanStepText(text); if (cleaned.length > 3 && !isTemplatePlanStep(text) && !isTemplatePlanStep(cleaned)) { items.push({ step: items.length + 1, text: cleaned, completed: false }); } } } return items; } function extractDoneSteps(message: string): number[] { const steps: number[] = []; for (const match of message.matchAll(/\[DONE:(\d+)\]/gi)) { const step = Number(match[1]); if (Number.isFinite(step)) steps.push(step); } return steps; } export function markCompletedSteps(text: string, items: TodoItem[]): number { const doneSteps = extractDoneSteps(text); for (const step of doneSteps) { const item = items.find((t) => t.step === step); if (item) item.completed = true; } return doneSteps.length; }