Review of PR #103 found a real data-loss bug: parseSections only captured text between `## ` headings. Content before the first heading — an intro paragraph, or, if the page had no headings at all, the entire body — was dropped on the floor, not carried into any section. Merge then had nothing of `a` to keep there, so the fresh extraction's sections silently became the whole output wherever that content used to be. Exactly the failure this file exists to prevent, just re-triggered by a page shape the parser didn't expect instead of by every re-ingest. Fixed by treating pre-first-heading content as a section with an empty heading. Merge's switch never matches "", so it falls through to the same on-disk-wins default every other unlisted section already gets — no new merge path, just no longer discarding this one's input. Second, separate finding from the same review: mergeBullets/appendNew deduped on the raw trimmed line, so two re-extractions of the same logical bullet with different internal whitespace (a real possibility — the extractor is an LLM, not a fixed serializer) produced a duplicate instead of a no-op, undermining the idempotency this file's own doc comments claim. Fixed by comparing a whitespace-canonicalized key (strings.Fields + single-space join) while still emitting the original line's text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>