v3.7.0 — coverage guard
Coverage guard for page-builder one-pagers (closes #46)
Readability commits to a single best candidate container. On the one-pagers page builders emit, the article is a flat list of sibling blocks whose prose sits several levels deep — the candidate score dilutes with nesting depth, so a shallow paragraph-rich block near the top outscores the far larger content below it, and the rest is dropped. The output is well-formed markdown, so the loss is silent. Measured on one such page: 10,503 of 336,670 characters, 3.1% coverage.
The guard re-converts the container that holds the body instead — effectively an auto-written select.content, using the same conversion path a recipe takes.
Fires only when all of these hold:
- no recipe
select.content, no forced static extractor, not the comments path - cleaned body ≥ 20,000 characters
- coverage < 10%
- a single container dominates the body text
- the recovered candidate is ≥ 3× larger and has ≥ 5 paragraphs
Condition 4 is the discriminator: it separates one article split across siblings from many separate teasers, and it is what keeps listing pages out.
Calibration. 85 live pages — forum threads, comment-heavy blogs, wikis, long single-document pages (GPL-3.0, RFC 9110, MDN, Python docs), shops, listing homepages, landing pages. It fires on 1 of 85, and on 0 of 12 forum threads. The first false positive appears at roughly twice the shipped thresholds.
Safety. The guard can only grow a result, never shrink it. Any error degrades to previous behavior. Opt out with the new PULLMD_COVERAGE_GUARD=off. Every intervention carries source: coverage-guard and records coverage, body size, gain and the share of the body the recovered container actually holds in metadata.extractorReason, so an operator can judge afterwards whether it was warranted. A select.content recipe still wins outright.
1038 tests passing.
Full changelog: https://github.com/AeternaLabsHQ/pullmd/blob/main/CHANGELOG.md