export interface Section { label: string; text: string; } interface Heading { line: number; level: number; title: string; } /** Find markdown ATX sections, ignoring headings inside fenced code blocks. */ export function findSections(markdown: string): Section[] { const lines = markdown.split("\n"); const headings: Heading[] = []; let fence: string | undefined; for (let line = 0; line < lines.length; line++) { const value = lines[line] ?? ""; const fenceMatch = value.match(/^\s*(`{3,}|~{3,})/); if (fenceMatch) { const marker = fenceMatch[1]!; if (!fence) fence = marker; else if (marker[0] === fence[0] && marker.length >= fence.length) fence = undefined; continue; } if (fence) continue; const heading = value.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/); if (heading) { headings.push({ line, level: heading[1]!.length, title: heading[2]!.trim() }); } } return headings.map((heading, index) => { let end = lines.length; for (let next = index + 1; next < headings.length; next++) { if (headings[next]!.level <= heading.level) { end = headings[next]!.line; break; } } return { label: `${"#".repeat(heading.level)} ${heading.title}`, text: lines.slice(heading.line, end).join("\n").trimEnd(), }; }); }