Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Add --renumber-sections with automatic section reference renaming#25

Open
jlevy wants to merge 31 commits intomainfrom
claude/setup-get-tbd-sheiG
Open

feat: Add --renumber-sections with automatic section reference renaming#25
jlevy wants to merge 31 commits intomainfrom
claude/setup-get-tbd-sheiG

Conversation

@jlevy
Copy link
Owner

@jlevy jlevy commented Jan 29, 2026

Summary

This PR implements the --renumber-sections CLI option for automatic section heading renumbering, with intelligent detection of numbering conventions (decimal, Roman numerals, alphabetic) and automatic updating of internal section references to match new heading slugs.

The feature handles complex numbering schemes across multiple heading levels (e.g., H1 decimal + H2 alphabetic + H3 Roman) while preserving document structure and ensuring internal links remain valid after renumbering.

Changes

Core Section Numbering (Phase 1-9)

  • Add --renumber-sections CLI flag and renumber_sections API parameter
  • Implement convention detection for decimal (1, 2, 3), Roman (i, ii, iii, I, II, III), and alphabetic (a, b, c, A, B, C) numbering
  • Support multi-level numbering with per-level style detection (e.g., H1 uses 1., H2 uses a.)
  • Handle hierarchical prefix detection and format strings (e.g., "1.a.", "2.b.")
  • Add single-H1 exception: when there's only one H1 (acting as document title), H2+ can be numbered independently
  • Implement comprehensive test suite with 250+ test cases

Section Reference Renaming (Phase 10)

  • Implement GitHub-compatible slugging algorithm (heading_to_slug(), GithubSlugger)
  • Add section reference detection (find_section_references())
  • Implement atomic rename primitive with support for slug swaps (rename_section_references())
  • Return clean RenameResult data structure with warnings for unknown references
  • Cross-file references (./other.md#section) and external URLs are preserved
  • Reference renaming always occurs with section renumbering (no separate flag needed)

API and Integration

  • Update fill_markdown(), reformat_text(), reformat_file(), reformat_files()
  • Integrate reference renaming with renumbering in apply_section_renumbering()

Documentation

  • Add comprehensive plan spec (docs/project/specs/active/plan-2026-01-29-renumber-sections.md)
  • Document all phases, design decisions, and edge cases

Test Plan

  • Unit tests pass (355 tests, all passing)
  • Linters pass (ruff, basedpyright)
  • CI passes on Python 3.10-3.14

Manual Testing Scenarios

  • Basic renumbering: flowmark --renumber-sections file.md
    • Verify sections like 1, 3, 5 become 1, 2, 3
  • Reference renaming: Internal #3-design links update to #2-design after renumbering
  • Cross-file preservation: Links like ./other.md#section remain unchanged
  • Multi-level styles: Document with H1 decimal + H2 alphabetic + H3 Roman
  • Single H1 title: Unnumbered H1 with numbered H2+ works correctly

Edge Cases Covered

  • Single H1 document (title exception - H2+ can be numbered independently)
  • Mixed numbered/unnumbered sections (preserve unnumbered)
  • Duplicate slugs handled correctly (GitHub-style -1, -2 suffix)
  • Unicode headings (proper slugification)
  • Empty sections and deeply nested structures
  • Contiguity enforced at H2+ (H3 without H2 is rejected)

Related Issues

All implementation issues have been closed.

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC

claude added 13 commits January 29, 2026 15:59
- Initialize tbd with fm- prefix for issue IDs
- Add Claude Code skill file and hooks integration
- Configure docs cache with shortcuts and guidelines

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Design document for automatic section heading renumbering:
- Detection logic for documents with numbered sections
- SectionNumberingConvention data structure to infer conventions
- Regex pattern for numeric prefixes (1, 1.2, 7.18.3, etc.)
- Implementation phases and acceptance criteria

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- SectionNumStyle enum: none, arabic_integer, arabic_decimal
- SectionLevelConfig: per-level style + trailing_char
- SectionNumConvention: tuple of 6 level configs
- Require 2+ headers at a level to activate (low false positives)
- Include in --auto flag (reliable like smart quotes)
- Always normalize trailing punctuation to period

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Clarifies that when only some H2s have numeric prefixes:
- H2 level is `none` (doesn't qualify)
- Numbered H2s keep their ORIGINAL numbers (no renumbering)
- Only levels where ALL headings are numbered get renumbered

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- Qualification rule: first-two + two-thirds threshold
  - First two headings at level must have matching prefix
  - At least 2/3 of all headings at level must have prefix
- Hierarchical constraint: conventions must be contiguous from H1
- Self-contained module: src/flowmark/transforms/section_numbering.py
- Detailed implementation plan with unit test requirements
- Documentation plan: README, CLI help, API docstrings
- Updated test cases for new qualification rules
- Feature included in --auto flag

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- Explicitly document that ONLY arabic_integer and arabic_decimal are
  recognized (Roman numerals, alphabetic are future extensions)
- Patterns: integer with optional . or ), decimal with optional . or )
- Normalization: trailing ) or missing separator → period
- Clear examples of what is/isn't recognized
- Document that unrecognized patterns pass through unchanged

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- Replace SectionNumStyle enum with explicit format string representation
- Format strings: "{h1:arabic}.", "{h1:arabic}.{h2:arabic}", etc.
- New data structures:
  - NumberStyle enum (arabic, future: roman, alpha)
  - FormatComponent (level + style)
  - SectionNumFormat (components + trailing, with format_string/format_number methods)
  - SectionNumConvention (tuple of 6 SectionNumFormat | None)
- extract_section_prefix() now returns (number_parts, trailing, title)
  - number_parts: [1] or [1, 2] or [1, 2, 3]
  - trailing: "." or ")" or ""
- More self-documenting and extensible design

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Update the --renumber-sections spec to implement all numbering styles
from the start, using a general regex approach:

- Add NumberStyle enum: arabic, roman_upper, roman_lower, alpha_upper, alpha_lower
- Add general regex pattern to recognize any combination of styles
- Add style inference logic to determine style from parsed components
- Add conversion functions: int_to_roman, roman_to_int, int_to_alpha, alpha_to_int
- Update Implementation Plan phases with all-style examples
- Add comprehensive test cases for Roman and alphabetic numbering
- Update Common Patterns table with mixed style examples
- Update normalization examples to include all styles
- Remove Roman/alphabetic from Future Extensions (now implemented)

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Review fixes:
- Replace legacy arabic_integer/arabic_decimal with style-based terminology
- Update Pattern Consistency examples to show style matching
- Fix Module Structure to show all supported styles
- Update Key Functions table with correct return types (strings, not ints)
- Fix FormatComponent docstring to show multi-style examples
- Fix extract_section_prefix signature to return ParsedPrefix
- Update format_number to use to_number() for style conversion
- Add note about Roman/Alpha ambiguity for single letters (I, V, X, etc.)
- Update format string description to reflect all styles are supported

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
When headings at a level contain both Roman-only letters (I, V, X, C, D, M)
and non-Roman letters (A, B, E, F, etc.), treat the entire level as
alphabetic. This prevents "C" and "D" from being misinterpreted as Roman
numerals in sequences like "A, B, C, D".

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Implement NumberStyle enum, FormatComponent, SectionNumFormat, and
SectionNumConvention dataclasses with comprehensive unit tests.

Key components:
- NumberStyle: arabic, roman_upper/lower, alpha_upper/lower
- FormatComponent: level + style for one component
- SectionNumFormat: format_string() and format_number() methods
- SectionNumConvention: max_depth, is_active properties
- Conversion functions: int_to_roman, roman_to_int, int_to_alpha, alpha_to_int

Closes fm-w9ti

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Implement ParsedPrefix dataclass, infer_style() function, and
extract_section_prefix() function with comprehensive unit tests.

Features:
- ParsedPrefix: holds parsed components, styles, trailing, title
- infer_style(): infers NumberStyle from component string
- extract_section_prefix(): parses heading text into ParsedPrefix
- Regex-based parsing supporting all number styles
- Handles mixed styles like "1.a.i" (arabic.alpha.roman)

29 new tests for Phase 2, total 49 tests passing.

Closes fm-e7bo

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
…ering

Implements automatic section heading renumbering for Markdown documents.

Key features:
- Detects numbered section conventions (arabic, roman, alphabetic)
- First-Two + Two-Thirds qualification rules for robustness
- Hierarchical constraint ensures contiguous numbering from H1
- Normalizes trailing separators to periods
- Supports mixed styles (e.g., Roman H1 + Alpha H2)

CLI:
- New --renumber-sections flag
- Included in --auto convenience flag

Implementation (9 phases):
1. Core data structures (NumberStyle, FormatComponent, SectionNumFormat)
2. Prefix extraction with regex parsing
3. Convention inference with disambiguation
4. Hierarchical constraint enforcement
5. Trailing character normalization
6. SectionRenumberer class for stateful renumbering
7. Integration with fill_markdown and reformat_api
8. CLI integration
9. End-to-end testing (122 tests)

All 294 project tests pass.

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
@jlevy jlevy changed the title Add plan spec for --renumber-sections feature feat: Add --renumber-sections CLI option for automatic section renumbering Jan 29, 2026
claude added 13 commits January 29, 2026 20:24
- Use getattr() instead of direct attribute access on object types
- Use __new__() to create RawText instances without constructor args
- Add explicit 'is not None' assertions in tests before accessing optional members

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- Add type annotations to local lists and class attributes
- Fix unreachable code by removing redundant else clauses
- Add pyright ignore comments for dynamic typing with marko elements
- Add @OverRide ignore comment for __str__ method

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Based on senior engineering review:
- Extract magic numbers to named constants (MAX_HEADING_LEVELS,
  TWO_THIRDS_THRESHOLD, MIN_HEADINGS_FOR_INFERENCE, ALPHABET_SIZE)
- Remove decorative section comment headers per comment rules
- Remove trivial tests that just check object instantiation per testing rules

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
When a document has only one H1 heading (common for title-only documents),
H1 is excluded from the hierarchical constraint check, allowing H2+ to be
numbered independently.

Changes:
- Update apply_hierarchical_constraint to accept headings parameter
- Skip H1 from hierarchy check when there's only one H1
- Calculate component levels based on heading level (fixes format inference)
- Pre-initialize H1 counter when single-H1 with deep level references

This enables renumbering for documents like:
  # My Document Title
  ## 1. Introduction
  ## 3. Background  <- renumbered to 2.

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Adds comprehensive documentation for automatic section reference renaming
when section headings are renumbered:

- GitHub slugging algorithm (heading_to_slug, GithubSlugger)
- Section reference detection (find_section_references)
- Atomic rename primitive (rename_section_references)
- Integration with renumbering pipeline
- Test cases for reference renaming
- Updated acceptance criteria (9 new requirements)
- Resolved questions about strict vs best-effort mode
- Open questions about validation and HTML anchors

Key design decisions:
- Best-effort by default (log warnings, don't fail)
- Only modify internal #slug references
- Leave external URLs and cross-file references unchanged
- Atomic replacement to handle swaps correctly

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
- Mark HTML anchor tags as explicitly OUT OF SCOPE (not future work)
- Emphasize RenameResult as clean data structure for all warnings
- Clarify that logging is separate from core logic (no embedded logging)
- Note that duplicates are rare for numbered sections (numbers make slugs unique)
- Add Phase 10.6 for end-to-end document testing
- Update Open Questions to reflect formatter vs linter distinction
- Fix duplicate heading example to show numbered slugs correctly

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Add section_references.py module with:
- heading_to_slug(): Convert heading text to GitHub-compatible anchor slug
  - Lowercase conversion
  - Remove special chars (keep alphanumeric, hyphens, unicode letters)
  - Replace spaces with hyphens
  - Collapse multiple hyphens
  - Remove leading/trailing hyphens
- GithubSlugger class: Stateful slugger tracking duplicates within document
  - Appends -1, -2, etc. for duplicate headings
  - reset() method to clear tracking

Includes 25 comprehensive tests covering:
- Basic slug generation
- Special character handling
- Unicode letter preservation
- Duplicate heading tracking
- Case insensitivity

Closes fm-5pyf

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Add to section_references.py module:
- SectionRef dataclass: Holds link element, slug, and is_internal flag
- find_section_references(): Traverse Marko document to find internal links
  - Returns all links starting with # (internal anchors)
  - Excludes external URLs and cross-file references
  - Works with inline and reference-style links
  - Handles nested structures (lists, blockquotes)

Includes 11 additional tests for reference detection covering:
- Inline and reference-style links
- External URL exclusion
- Cross-file reference exclusion
- Nested document structures

Closes fm-y3dr

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Add to section_references.py module:
- SectionRename dataclass: Holds old_slug and new_slug pair
- RenameResult dataclass: Clean data structure with links_modified count
  and warnings list (no embedded logging, separation of concerns)
- rename_section_references(): Atomically rename section references
  - Builds complete old→new mapping before any changes
  - Case-insensitive slug matching
  - Handles swaps (A→B, B→A) correctly via atomic mapping
  - Collects warnings for unmatched references (no dynamic logging)
  - Modifies document in place

Includes 12 additional tests for rename functionality covering:
- Single and multiple renames
- Atomic swap handling
- Case-insensitive matching
- Partial match rejection (no false positives)
- Warning collection for unmatched refs
- Clean data structure verification

Closes fm-u8zt

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Update apply_section_renumbering() to automatically rename internal
section references when headings are renumbered:

- Collect old/new heading text for each renumbered heading
- Calculate old/new slugs using heading_to_slug()
- Only add renames when slug actually changes
- Call rename_section_references() after heading updates
- Return RenameResult with links_modified count and warnings
- Return None when document doesn't qualify for renumbering

Integration ensures:
- References automatically follow renumbered headings
- External links are never modified
- Warnings collected for unknown references (no dynamic logging)

Includes 7 integration tests covering:
- Reference updates on renumbering
- Multiple references to same section
- No-op when numbers don't change
- External link preservation
- Warning collection for unknown refs
- Non-numbered document handling

Closes fm-lb2v

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Thread rename_references parameter through the entire API:
- apply_section_renumbering(doc, rename_references=True)
- fill_markdown(..., rename_references=True)
- reformat_text(..., rename_references=True)
- reformat_file(..., rename_references=True)
- reformat_files(..., rename_references=True)

Add --no-rename-references CLI flag:
- Disables automatic section reference updating when renumbering
- By default (without flag), references are updated
- Useful when users want manual control over link updates

When rename_references=False:
- Headings are still renumbered
- Links are NOT updated
- Returns RenameResult with links_modified=0

Includes 1 new test for the flag behavior.

Closes fm-5g8u

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Create comprehensive document-based tests for section renumbering:
- tests/testdocs/section_numbering.orig.md: Input with misnumbered sections
- tests/testdocs/section_numbering.expected.md: Golden output
- tests/test_section_numbering_docs.py: Test suite

Tests cover:
- Full document renumbering (3->2, 5->3, 7->4)
- Internal reference updates (#3-design -> #2-design)
- Cross-file references preserved
- External URLs preserved
- Unknown reference warnings collected
- rename_references=False disables link updates
- RenameResult is clean data structure verification

Closes fm-jesh

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
@jlevy jlevy changed the title feat: Add --renumber-sections CLI option for automatic section renumbering feat: Add --renumber-sections with automatic section reference renaming Feb 2, 2026
- Add type annotation to `cleaned` list to fix reportUnknownArgumentType
- Implement `strict` parameter in rename_section_references() to fix
  reportUnusedParameter (raises ValueError instead of warning if True)

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Section reference renaming is now always performed when renumbering sections,
since there's no sensible use case for renumbering headings without updating
internal links (which would break them).

Changes:
- Remove --no-rename-references CLI flag
- Remove rename_references parameter from API chain
- Remove related tests for the now-removed feature
- Simplify apply_section_renumbering() to always rename references

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Updated docstrings to better explain:
- Numbered levels must be contiguous (H2+H3 valid, H2+H4 gap invalid)
- Single-H1 exception: when only one H1 exists (title), H2+ can be
  numbered independently without requiring H1 to be numbered

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Documents the complete behavior of the --renumber-sections transform:
- Activation criteria (2/3 threshold, minimum 2 headings)
- Recognized prefix styles (decimal, Roman, alphabetic, hierarchical)
- Hierarchical constraint (contiguous levels required)
- Single-H1 exception (title excluded from hierarchy)
- Reference renaming behavior (GitHub slugs, cross-file preservation)
- Pass-through behavior for non-qualifying headings

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Apply flowmark formatting to the specification docstring for cleaner
Markdown with proper line wrapping and semantic breaks.

https://claude.ai/code/session_01U4WwmYorLGxdNd6FdgEjFC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants