How book-to-skill Distinguishes Between Chapter Headings and Numbered List Items

book-to-skill distinguishes chapter headings from numbered list items by applying a three-rule heuristic in book_to_skill/utils.py that requires at least three systematically ordered digit-led titles followed by substantial prose content.

When parsing technical documentation, the book-to-skill library must differentiate between legitimate chapter headings like "1 Introduction" and ordinary numbered list items like "1. Install the package". This distinction prevents inflated chapter counts in documents that use numeric lists for procedures or steps rather than structural organization.

The Three-Rule Validation Heuristic

The decision logic resides in the _numbered_titles_are_structural helper function, which evaluates digit-led titles against three specific criteria to separate structural chapters from incidental list items.

Minimum Title Threshold

The parser requires at least _MIN_NUMBERED_TITLES = 3 digit-led titles at a given heading depth. Short procedural lists (e.g., "1-2-3 steps") fail this check immediately, while genuine books and comprehensive guides typically contain many numbered chapters. This threshold filters out isolated or brief numeric sequences that commonly appear in installation instructions or quick-start tutorials.

Systematic Ordering Requirement

Titles must appear in systematic order based on their line numbers in the source document. The function sorts heading_lines and verifies that the numeric titles form a continuous series rather than scattered or "jumping" numbers. This prevents fragmented numeric references—such as version numbers or arbitrary callouts—from being misinterpreted as chapter boundaries.

Median Body Length Check

Each candidate title must be followed by substantial prose content. The function calculates the character count of text following each title (up to the next heading) and requires _MIN_NUMBERED_BODY_CHARS = 200. Using statistics.median(bodies) rather than a simple average protects against outliers—such as a single long list item—while ensuring that real chapters with consistent narrative depth pass validation.

Core Implementation in book_to_skill/utils.py

The _numbered_titles_are_structural function implements the validation logic directly in the utilities module:


# book_to_skill/utils.py

_MIN_NUMBERED_TITLES = 3
_MIN_NUMBERED_BODY_CHARS = 200

def _numbered_titles_are_structural(
    entries: list[tuple[str, int]], heading_lines: list[int], lines: list[str]
) -> bool:
    """
    Decide whether digit-led titles at one depth are chapters or list items.
    """
    # 1️⃣ Require a systematic series of titles (≥ 3 titles)

    if len(entries) < _MIN_NUMBERED_TITLES:
        return False

    # 2️⃣ Compute the amount of body text that follows each title

    ordered = sorted(heading_lines)
    bodies = []
    for _, index in entries:
        after = [ln for ln in ordered if ln > index]
        end = after[0] if after else len(lines)
        bodies.append(sum(len(ln) for ln in lines[index + 1:end]))

    # 3️⃣ Accept the series only if the median body length is "chapter-like"

    return statistics.median(bodies) >= _MIN_NUMBERED_BODY_CHARS

The function returns True only when all three conditions are satisfied, causing the caller to treat the digit-led titles as structural chapter headings rather than ordinary list content.

Integration with _structural_chapter_count

The validation helper is invoked from _structural_chapter_count, which orchestrates chapter detection across Markdown, AsciiDoc, and RST sources:


# book_to_skill/utils.py

def _structural_chapter_count(text: str) -> int:
    """Count chapter-like structural headings in Markdown/AsciiDoc/RST sources."""
    # ... parsing ATX and setext headings ...

    for depth, entries in numbered.items():
        if _numbered_titles_are_structural(entries, heading_lines, lines):
            # this depth contributes to the chapter count

            ...

This architecture allows the parser to evaluate each heading depth independently, ensuring that mixed documents—such as those combining numbered chapters with numbered procedure lists—maintain accurate chapter counts.

Practical Code Examples

Detecting Chapters in a Markdown Snippet

from book_to_skill.utils import _structural_chapter_count

markdown = """

# 1 Introduction

Some introductory text that explains the purpose of this guide...

1. Install the package
2. Run the script
3. Enjoy

# 2 Methods

Detailed method discussion with extensive explanation of approaches...
"""

print(_structural_chapter_count(markdown))

# → 2   (only the two numbered headings are counted as chapters)

The parser correctly identifies "1 Introduction" and "2 Methods" as chapters while ignoring the procedural list items.

Validating Title Structure Internally

from book_to_skill.utils import _numbered_titles_are_structural

lines = markdown.splitlines()
heading_lines = [i for i, l in enumerate(lines) if l.lstrip().startswith('#')]
entries = [(l, i) for i, l in enumerate(lines) if l.lstrip().startswith('#')]

print(_numbered_titles_are_structural(entries, heading_lines, lines))

# → True  (the numbered headings satisfy the systematic + body-size rule)

Test Coverage and Validation

The library includes comprehensive tests to ensure reliable distinction between chapters and lists:

  • tests/test_numbered_headings.py validates that isolated numbered headings are counted as chapters while pure numbered lists are excluded.

  • tests/test_book_to_skill.py::test_numbered_list_items_are_not_chapters serves as a regression test confirming that plain numbered lists do not inflate chapter counts.

These test suites verify the heuristic against real-world documentation patterns, preventing false positives in technical books that frequently mix narrative chapters with installation procedures.

Summary

  • book-to-skill uses a multi-rule heuristic in _numbered_titles_are_structural to distinguish chapter headings from numbered list items.
  • The validation requires at least three digit-led titles (_MIN_NUMBERED_TITLES = 3) that follow systematic ordering.
  • Each title must be followed by substantial prose, with a median body length of at least 200 characters (_MIN_NUMBERED_BODY_CHARS).
  • The logic is implemented in book_to_skill/utils.py and integrated into the main _structural_chapter_count function.
  • Comprehensive tests in tests/test_numbered_headings.py and tests/test_book_to_skill.py ensure accuracy across diverse document formats.

Frequently Asked Questions

What is the minimum number of digit-led titles required to treat them as chapters?

The parser requires at least three digit-led titles at the same heading depth to consider them structural chapters. This threshold, defined by _MIN_NUMBERED_TITLES = 3 in book_to_skill/utils.py, prevents short procedural lists from being misclassified as chapter hierarchies.

Why does book-to-skill use median body length instead of average?

The implementation uses statistics.median(bodies) to guard against outliers. A single unusually long list item could skew an average calculation, causing false positives. The median provides a robust measure of central tendency that accurately reflects whether the majority of titles are followed by chapter-length content.

How does the parser handle isolated numbered headings versus systematic series?

The function sorts all heading line numbers and verifies that digit-led titles form a continuous series. Isolated or scattered numeric headings—such as version numbers or figure captions—fail the systematic ordering check and are rejected as non-structural content.

Where can I find the test cases for chapter detection logic?

The primary unit tests reside in tests/test_numbered_headings.py, which verifies the distinction between numbered headings and list items. Integration tests in tests/test_book_to_skill.py include specific regression tests like test_numbered_list_items_are_not_chapters to ensure end-to-end accuracy.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →