flexdoc 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. flexdoc-0.2.0/CHANGELOG.md +135 -0
  2. {flexdoc-0.1.0 → flexdoc-0.2.0}/PKG-INFO +1 -1
  3. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/flexdoc-spec.md +23 -8
  4. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md +6 -2
  5. flexdoc-0.2.0/docs/project/specs/active/plan-2026-06-13-metrics-use-case.md +531 -0
  6. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/__init__.py +5 -2
  7. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/block_info.py +39 -1
  8. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/block_tree.py +14 -4
  9. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/collect.py +9 -6
  10. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/flex_doc.py +263 -48
  11. flexdoc-0.2.0/src/flexdoc/docs/links.py +234 -0
  12. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/node.py +1 -0
  13. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/node_table.py +131 -152
  14. flexdoc-0.2.0/src/flexdoc/docs/sections.py +176 -0
  15. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_collect.py +11 -0
  16. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_flex_doc.py +54 -0
  17. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_links.py +69 -0
  18. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_node.py +2 -0
  19. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_node_table.py +25 -0
  20. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_sections.py +55 -0
  21. flexdoc-0.2.0/tests/golden/documents/heading_edges.md +36 -0
  22. flexdoc-0.2.0/tests/golden/documents/inline_pathology.md +19 -0
  23. flexdoc-0.2.0/tests/golden/documents/link_taxonomy.md +20 -0
  24. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/footnotes_refs/report.yaml +25 -0
  25. flexdoc-0.2.0/tests/golden/expected/heading_edges/docgraph.yaml +274 -0
  26. flexdoc-0.2.0/tests/golden/expected/heading_edges/reassembled.md +21 -0
  27. flexdoc-0.2.0/tests/golden/expected/heading_edges/report.yaml +405 -0
  28. flexdoc-0.2.0/tests/golden/expected/inline_pathology/docgraph.yaml +91 -0
  29. flexdoc-0.2.0/tests/golden/expected/inline_pathology/reassembled.md +11 -0
  30. flexdoc-0.2.0/tests/golden/expected/inline_pathology/report.yaml +283 -0
  31. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/kitchen_sink/report.yaml +14 -13
  32. flexdoc-0.2.0/tests/golden/expected/link_taxonomy/docgraph.yaml +77 -0
  33. flexdoc-0.2.0/tests/golden/expected/link_taxonomy/reassembled.md +9 -0
  34. flexdoc-0.2.0/tests/golden/expected/link_taxonomy/report.yaml +332 -0
  35. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/unicode/report.yaml +1 -0
  36. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/test_golden_docs.py +105 -0
  37. flexdoc-0.1.0/CHANGELOG.md +0 -66
  38. flexdoc-0.1.0/src/flexdoc/docs/links.py +0 -109
  39. flexdoc-0.1.0/src/flexdoc/docs/sections.py +0 -130
  40. {flexdoc-0.1.0 → flexdoc-0.2.0}/.agents/skills/simple-modern-uv/SKILL.md +0 -0
  41. {flexdoc-0.1.0 → flexdoc-0.2.0}/.agents/skills/simple-modern-uv/references/adopt-existing.md +0 -0
  42. {flexdoc-0.1.0 → flexdoc-0.2.0}/.agents/skills/simple-modern-uv/references/customize.md +0 -0
  43. {flexdoc-0.1.0 → flexdoc-0.2.0}/.agents/skills/simple-modern-uv/references/faq.md +0 -0
  44. {flexdoc-0.1.0 → flexdoc-0.2.0}/.agents/skills/tbd/SKILL.md +0 -0
  45. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/.gitignore +0 -0
  46. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/hooks/tbd-closing-reminder.sh +0 -0
  47. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/scripts/ensure-gh-cli.sh +0 -0
  48. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/scripts/tbd-session.sh +0 -0
  49. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/settings.json +0 -0
  50. {flexdoc-0.1.0 → flexdoc-0.2.0}/.claude/skills/tbd/SKILL.md +0 -0
  51. {flexdoc-0.1.0 → flexdoc-0.2.0}/.codex/ensure-gh-cli.sh +0 -0
  52. {flexdoc-0.1.0 → flexdoc-0.2.0}/.codex/hooks.json +0 -0
  53. {flexdoc-0.1.0 → flexdoc-0.2.0}/.codex/tbd-closing-reminder.sh +0 -0
  54. {flexdoc-0.1.0 → flexdoc-0.2.0}/.codex/tbd-session.sh +0 -0
  55. {flexdoc-0.1.0 → flexdoc-0.2.0}/.copier-answers.yml +0 -0
  56. {flexdoc-0.1.0 → flexdoc-0.2.0}/.github/workflows/ci.yml +0 -0
  57. {flexdoc-0.1.0 → flexdoc-0.2.0}/.github/workflows/publish.yml +0 -0
  58. {flexdoc-0.1.0 → flexdoc-0.2.0}/.gitignore +0 -0
  59. {flexdoc-0.1.0 → flexdoc-0.2.0}/.tbd/.gitattributes +0 -0
  60. {flexdoc-0.1.0 → flexdoc-0.2.0}/.tbd/.gitignore +0 -0
  61. {flexdoc-0.1.0 → flexdoc-0.2.0}/.tbd/config.yml +0 -0
  62. {flexdoc-0.1.0 → flexdoc-0.2.0}/AGENTS.md +0 -0
  63. {flexdoc-0.1.0 → flexdoc-0.2.0}/CLAUDE.md +0 -0
  64. {flexdoc-0.1.0 → flexdoc-0.2.0}/LICENSE +0 -0
  65. {flexdoc-0.1.0 → flexdoc-0.2.0}/Makefile +0 -0
  66. {flexdoc-0.1.0 → flexdoc-0.2.0}/README.md +0 -0
  67. {flexdoc-0.1.0 → flexdoc-0.2.0}/SUPPLY-CHAIN-SECURITY.md +0 -0
  68. {flexdoc-0.1.0 → flexdoc-0.2.0}/TODO.md +0 -0
  69. {flexdoc-0.1.0 → flexdoc-0.2.0}/devtools/lint.py +0 -0
  70. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/development.md +0 -0
  71. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/installation.md +0 -0
  72. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/research/research-2026-05-29-document-model.md +0 -0
  73. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/research/research-2026-05-30-multilayer-parsing.md +0 -0
  74. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/research/research-2026-05-30-span-references.md +0 -0
  75. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md +0 -0
  76. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/specs/active/plan-2026-05-29-unified-document-model.md +0 -0
  77. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/specs/active/plan-2026-05-31-doc-model-refinements.md +0 -0
  78. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/specs/active/plan-2026-05-31-golden-doc-testing.md +0 -0
  79. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/project/specs/active/plan-2026-06-11-structural-metadata.md +0 -0
  80. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/publishing.md +0 -0
  81. {flexdoc-0.1.0 → flexdoc-0.2.0}/docs/usage.md +0 -0
  82. {flexdoc-0.1.0 → flexdoc-0.2.0}/examples/backfill_timestamps.py +0 -0
  83. {flexdoc-0.1.0 → flexdoc-0.2.0}/examples/doc_structure.py +0 -0
  84. {flexdoc-0.1.0 → flexdoc-0.2.0}/examples/normalized_form.py +0 -0
  85. {flexdoc-0.1.0 → flexdoc-0.2.0}/pyproject.toml +0 -0
  86. {flexdoc-0.1.0 → flexdoc-0.2.0}/skills-lock.json +0 -0
  87. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/__init__.py +0 -0
  88. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/base_blocks.py +0 -0
  89. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/block_types.py +0 -0
  90. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/debug.py +0 -0
  91. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/doc_graph.py +0 -0
  92. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/doc_graph_schema.json +0 -0
  93. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/frontmatter.py +0 -0
  94. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/interval_index.py +0 -0
  95. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/paragraphs.py +0 -0
  96. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/render.py +0 -0
  97. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/search_tokens.py +0 -0
  98. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/sizes.py +0 -0
  99. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/span_ref.py +0 -0
  100. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/token_diffs.py +0 -0
  101. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/token_mapping.py +0 -0
  102. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/docs/wordtoks.py +0 -0
  103. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/__init__.py +0 -0
  104. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/extractor.py +0 -0
  105. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/html_in_md.py +0 -0
  106. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/html_plaintext.py +0 -0
  107. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/html_tags.py +0 -0
  108. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/html/timestamps.py +0 -0
  109. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/py.typed +0 -0
  110. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/util/__init__.py +0 -0
  111. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/util/read_time.py +0 -0
  112. {flexdoc-0.1.0 → flexdoc-0.2.0}/src/flexdoc/util/token_estimate.py +0 -0
  113. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/__init__.py +0 -0
  114. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/__init__.py +0 -0
  115. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_base_blocks.py +0 -0
  116. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_block_info.py +0 -0
  117. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_block_types.py +0 -0
  118. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_blocks.py +0 -0
  119. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_caching_threadsafe.py +0 -0
  120. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_doc_graph.py +0 -0
  121. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_footnote_ref.py +0 -0
  122. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_frontmatter.py +0 -0
  123. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_interval_index.py +0 -0
  124. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_offsets.py +0 -0
  125. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_span_ref.py +0 -0
  126. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_spans.py +0 -0
  127. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_subdoc_and_empty.py +0 -0
  128. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_token_diffs.py +0 -0
  129. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_token_mapping.py +0 -0
  130. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_token_validation.py +0 -0
  131. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/docs/test_wordtoks.py +0 -0
  132. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/README.md +0 -0
  133. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/footnotes_refs.md +0 -0
  134. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/inline_html.md +0 -0
  135. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/kitchen_sink.md +0 -0
  136. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/malformed.md +0 -0
  137. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/nested_list.md +0 -0
  138. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/tight_vs_loose.md +0 -0
  139. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/documents/unicode.md +0 -0
  140. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/footnotes_refs/docgraph.yaml +0 -0
  141. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/footnotes_refs/reassembled.md +0 -0
  142. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/inline_html/docgraph.yaml +0 -0
  143. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/inline_html/reassembled.md +0 -0
  144. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/inline_html/report.yaml +0 -0
  145. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/kitchen_sink/docgraph.yaml +0 -0
  146. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/kitchen_sink/reassembled.md +0 -0
  147. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/malformed/docgraph.yaml +0 -0
  148. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/malformed/reassembled.md +0 -0
  149. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/malformed/report.yaml +0 -0
  150. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/nested_list/docgraph.yaml +0 -0
  151. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/nested_list/reassembled.md +0 -0
  152. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/nested_list/report.yaml +0 -0
  153. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/tight_vs_loose/docgraph.yaml +0 -0
  154. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/tight_vs_loose/reassembled.md +0 -0
  155. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/tight_vs_loose/report.yaml +0 -0
  156. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/unicode/docgraph.yaml +0 -0
  157. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/golden/expected/unicode/reassembled.md +0 -0
  158. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/html/__init__.py +0 -0
  159. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/html/test_html_tag_hardening.py +0 -0
  160. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/html/test_html_tags.py +0 -0
  161. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/html/test_html_validation_and_classes.py +0 -0
  162. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/html/test_timestamps.py +0 -0
  163. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/test_examples.py +0 -0
  164. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/test_root_api.py +0 -0
  165. {flexdoc-0.1.0 → flexdoc-0.2.0}/tests/test_supply_chain.py +0 -0
  166. {flexdoc-0.1.0 → flexdoc-0.2.0}/uv.lock +0 -0
@@ -0,0 +1,135 @@
1
+ # Changelog
2
+
3
+ All notable changes to flexdoc are documented here.
4
+ This project uses [semantic versioning](https://semver.org/); while pre-1.0, breaking
5
+ changes bump the **minor** version (see `docs/publishing.md`).
6
+
7
+ ## 0.2.0 (2026-06-14)
8
+
9
+ Correctness fixes and a completed inline/heading/link surface for the document-metrics
10
+ use case (`docs/project/specs/active/plan-2026-06-13-metrics-use-case.md`, issue #6).
11
+ As a preview-stage library this takes the cleanest shape with no compatibility shims;
12
+ the API additions below include breaking signature changes (see **Changed**).
13
+
14
+ ### Fixed
15
+
16
+ - **`node_table()` / `collect()` / `graph()` no longer raise on valid Markdown.** Inline
17
+ elements were discovered over the whole source and parented by start offset, so backtick
18
+ pairing across a block boundary (an empty fence next to inline backticks) produced an
19
+ inline span escaping its parent block and raised a layer-nesting error. Inline discovery
20
+ is now scoped per leaf content block, so an inline node can never straddle a block
21
+ boundary; links/images/definitions are parented by full containment.
22
+ - **`sections()` / `toc()` recover every heading `blocks()` finds, and own their content
23
+ correctly.** Headings were re-derived from the blank-line paragraph view, dropping tight
24
+ headings and headings preceded by a non-blank line (e.g. an HTML-comment marker), and
25
+ section content was bucketed from that same view, so a heading glued to its body lost the
26
+ body. Sections now derive entirely from the structural block tree — the heading set *and*
27
+ each section's own content (`own_paragraphs()` / `blocks()` / sizes) come from the section's
28
+ source region — so tight and marker-preceded headings own exactly their content.
29
+ - **Section spans nest correctly** even when a blank-line paragraph straddles a later
30
+ heading (e.g. an embedded `---` block marko reads as a setext heading): each section
31
+ spans from its heading to the next same-or-higher heading (trimmed), which nests by
32
+ construction. Byte-identical to the prior span for well-formed documents.
33
+ - **Reference-definition nodes attach to their block.** A `link_ref_def` span included the
34
+ line's trailing newline and escaped the containing paragraph, leaving the node unparented
35
+ so a block-scoped `collect()` missed it; spans are now trimmed like every structural block.
36
+
37
+ ### Added
38
+
39
+ - **Heading metadata on the structural block**: `Block.heading_info` (`HeadingInfo` with
40
+ parser-authoritative `level` and `title`) and the `Block.heading_level` convenience;
41
+ `HeadingInfo` is exported from `flexdoc.docs`. The node table reads heading level from it.
42
+ - **Typed link forms**: `LinkForm` (`inline` / `reference` / `autolink` / `bare_url` /
43
+ `image` / `reference_definition`) and `Link.link_form`. `FlexDoc.links(link_forms=…)` selects any
44
+ forms (default: navigable links only), and `FlexDoc.images()` is a convenience for image
45
+ access. Reference definitions (`[id]: url`) are surfaced as `NodeKind.link_ref_def` nodes
46
+ and via `links(link_forms={LinkForm.reference_definition})`.
47
+ - **`FlexDoc.prose_text()`**: prose-only text for editorial linting and prose metrics —
48
+ prose blocks (paragraphs/headings, and table cells when `include_tables=True`) with inline
49
+ code and footnote refs dropped, links/images replaced by their text/alt, inline-HTML tags
50
+ dropped (wrapped text kept), and heading/blockquote/list markers and reference-definition
51
+ lines stripped; from verbatim source slices (line wrapping preserved, never reflowed) so
52
+ spacing like a spaced em-dash is kept exactly.
53
+ - **`FlexDoc.block_at_offset()`**: the innermost structural `Block` containing an offset
54
+ (the structural counterpart of `paragraph_at_offset`; the name, freed in 0.1.0, now
55
+ correctly returns a `Block`).
56
+ - **Test-suite hardening**: adversarial corpus documents (`inline_pathology`,
57
+ `heading_edges`, `link_taxonomy`); cross-projection invariants tying `toc()` to the
58
+ heading blocks, inline nesting on the query surface, and link-form accounting; and a
59
+ dogfood test that parses every Markdown file in the repo and asserts the invariants. See
60
+ the spec's "Why These Bugs Escaped the Tests" analysis.
61
+
62
+ ### Changed
63
+
64
+ These are breaking, made cleanly (no aliases) given the preview status:
65
+
66
+ - **`Link` gains a required `link_form: LinkForm` field.** Direct `Link(...)` construction must
67
+ pass it.
68
+ - **`block_links()` returns all link-like constructs** (navigable links, images, and
69
+ reference definitions), each with a `link_form`; previously it returned navigable links only.
70
+ `FlexDoc.links()` filters to navigable links by default, so its default result is
71
+ unchanged.
72
+ - **`collect()` returns inline-kind nodes without `recursive=True`.** An inline-kind
73
+ request (e.g. `collect(kinds={NodeKind.link})`) now widens the candidate set instead of
74
+ silently returning `[]` — matching the documented behavior.
75
+
76
+ ## 0.1.0 (2026-06-12)
77
+
78
+ First release.
79
+
80
+ ### Added
81
+
82
+ - **Initial flexdoc package**, extracted from
83
+ [chopdiff](https://github.com/jlevy/chopdiff) as its own standalone distribution.
84
+ This is the document/markdown layer — `FlexDoc`, paragraphs/sentences, the block tree
85
+ and block types, sections, the node table, `collect()`, `DocGraph`, `SpanRef`, token
86
+ diffs/mappings, word tokenization, html-in-md, and read-time/token estimation — with
87
+ no dependency on chopdiff’s diff and windowed-transform machinery.
88
+
89
+ The import roots are `flexdoc.docs`, `flexdoc.html`, and `flexdoc.util`. Parse
90
+ behavior is unchanged from the `flexdoc.*` modules that previously shipped inside the
91
+ chopdiff wheel; this release packages them independently.
92
+ See `docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md`.
93
+
94
+ - **A deliberate root API**: the working set is importable from the package root —
95
+ `FlexDoc`, `DocGraph`, `Detail`, `SpanRef`, `BlockType`, `NodeKind`, `Layer`,
96
+ `TextUnit` — designed against the known downstream users and pinned by contract
97
+ tests. The render helpers for source-linked HTML (`render_node_attrs`,
98
+ `wrap_with_node_attrs`, `parse_source_span_attr`) are public in `flexdoc.docs`.
99
+
100
+ - **DocGraph paragraph view**: `Views.paragraphs` joins `toc`/`blocks`/`links`/
101
+ `sentences` in the serialized projection.
102
+
103
+ ### Changed (relative to the modules as shipped in chopdiff)
104
+
105
+ The first standalone release also refines the API surface (the pre-publish design
106
+ review, `docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md`);
107
+ these are intentional hard cuts with no compatibility aliases:
108
+
109
+ - **`TextDoc` is renamed `FlexDoc`** — the package’s single entry point, named for the
110
+ model it carries (all layered projections hang off it).
111
+ It is importable from the package root: `from flexdoc import FlexDoc`. The module is
112
+ `flexdoc.docs.flex_doc` (was `chopdiff.docs.text_doc`), and the design of record is
113
+ now `docs/flexdoc-spec.md` (was `textdoc-spec.md`).
114
+ - **`collect()` is fully keyword-only and the deprecated aliases are gone**: use
115
+ `subtree_of=` (was `scope=`, previously also positional) and `within=` (was
116
+ `contains=`).
117
+ - **Editing-view methods are named in paragraph terms**, so “block” always means the
118
+ structural layer: `FlexDoc.paragraph_at_offset` (was `block_at_offset`),
119
+ `FlexDoc.iter_paragraphs` (was `iter_blocks`), `Section.own_paragraphs`/
120
+ `subtree_paragraphs` (were `own_blocks`/`subtree_blocks`).
121
+ - **The export surface is settled**: `flexdoc.docs` now exports
122
+ `CodeInfo`/`TableInfo`/`ListInfo`, `resolve`/`resolve_and_update`,
123
+ `parse_blocks`/`walk_blocks`/`block_type_for`, and `DEFAULT_INCLUDE`; `flexdoc.html`
124
+ exports `html_p`, `html_tag`, `escape_attribute`, `tag_wrapper`, and
125
+ `identity_wrapper`. Link extraction is public as `flexdoc.docs.links.block_links`.
126
+ - **`Node.attrs` values are JSON-typed** (`AttrValue`), validated at `DocGraph`
127
+ serialization, and node-id assignment order is pinned and tested for cross-language
128
+ ports; layer nesting guarantees (`LAYER_NESTING`) are enforced at node-table build.
129
+ - Internally, the former `text_doc.py` was split into `flex_doc.py`, `paragraphs.py`,
130
+ `links.py`, and `sections.py` (package imports from `flexdoc.docs` are unaffected),
131
+ and `sections()` is now cached like the other derived views.
132
+
133
+ Migration from chopdiff in one pass: `chopdiff.docs.TextDoc` → `flexdoc.FlexDoc` (or
134
+ `flexdoc.docs.FlexDoc`), `chopdiff.docs.*` → `flexdoc.docs.*`, plus the method renames
135
+ above.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexdoc
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Document model for powerful text parsing and processing in Markdown
5
5
  Project-URL: Repository, https://github.com/jlevy/flexdoc
6
6
  Author-email: Joshua Levy <joshua@cal.berkeley.edu>
@@ -365,13 +365,20 @@ is the reassembled working text, and the invariant holds against that.
365
365
  lazily-cached projections — `blocks()` (§6), `base_blocks()` (§6), `sections()` (§7),
366
366
  `links()` (§8), `node_table()` (§4.3), `collect()` (§9), and `graph()` (§10).
367
367
  - **Offset inversion:** `paragraph_at_offset(o)` and `sentence_at_offset(o)` map an
368
- absolute offset back to the editing-view unit containing it (or `None` for offsets in
369
- inter-unit whitespace or outside the document).
370
- Structural blocks are addressed by their own spans or via `collect(overlaps=...)`.
368
+ absolute offset back to the editing-view unit containing it, and `block_at_offset(o)` to
369
+ the innermost structural `Block` (each `None` for offsets in inter-unit whitespace or
370
+ outside the document). Structural blocks are otherwise addressed by their own spans or via
371
+ `collect(overlaps=...)`.
371
372
  - **Frontmatter:** `FlexDoc.frontmatter` is the verbatim leading YAML block or `None`
372
373
  (§3).
373
374
  - **Sizing:** `size(unit)` and `size_summary()` measure the document in any `TextUnit`
374
375
  (see Terminology), including the approximate LLM `tokens` estimate.
376
+ - **Prose projection:** `prose_text(include_tables=False)` returns prose-only text for
377
+ editorial linting and metrics — prose-bearing blocks (paragraphs/headings, and table cells
378
+ when `include_tables=True`) with inline code/footnote refs dropped, links/images reduced to
379
+ their text/alt, inline-HTML tags removed (wrapped text kept), and heading/blockquote/list
380
+ markers and reference-definition lines stripped. Slices are verbatim (line wrapping
381
+ preserved, never reflowed), so editorial spacing (e.g. a spaced em-dash) survives.
375
382
 
376
383
  ### 4.3 Nodes, kinds, layers, and the node table
377
384
 
@@ -696,12 +703,20 @@ Inline elements (links, code spans, images, inline HTML, footnote references,
696
703
  `section`/`sentence` associations, so block↔inline relationships are node edges, and
697
704
  “links in section 3” is a scoped `collect(kinds={link})`.
698
705
 
699
- - `Link(text, url, title, span)`: identity from `flowmark.markdown_ast.extract_links`
700
- (reference links resolved, escapes honored, autolinks/images handled), which carries
701
- no span by design. flexdoc recovers each exact `[start, end)` by reconciling the
706
+ - `Link(text, url, title, span, link_form)`: identity from `flowmark.markdown_ast.extract_links`
707
+ (reference links resolved, escapes honored), an AST walk for images, and marko's
708
+ `LinkRefDef` elements for definitions. Each carries a `LinkForm` discriminator `inline`,
709
+ `reference`, `autolink`, `bare_url`, `image`, or `reference_definition` — so consumers count
710
+ by form without heuristics. flexdoc recovers each exact `[start, end)` by reconciling the
702
711
  ordered identities with the name-tagged atomic spans from
703
- `flowmark.atomic_spans.iter_atomic_spans` (`markdown_link` / `autolink` / `bare_url`);
704
- reference links keep identity but no exact span.
712
+ `flowmark.atomic_spans.iter_atomic_spans` (and a trimmed `block_span` for definitions); an
713
+ identity that cannot be located keeps its identity with `span=None`.
714
+ - **`links()` returns navigable links only by default** (`TRUE_LINK_FORMS`: `inline`,
715
+ `reference`, `autolink`, `bare_url`); `links(link_forms=…)` selects any forms and `images()` is
716
+ the convenience for `LinkForm.image`. **Reference definitions** (`[id]: url`) are surfaced
717
+ as `NodeKind.link_ref_def` nodes — parented to their containing block, so a block-scoped
718
+ `collect()` finds them — and via `links(link_forms={LinkForm.reference_definition})`, never the
719
+ default `links()` (a definition is not a link occurrence).
705
720
  - `link → sentence` via `sentence_at_offset(link.span[0])`.
706
721
  - **`footnote_ref`**: a footnote reference `[^label]` is a first-class inline node
707
722
  (`NodeKind.footnote_ref`) carrying its `label` in `attrs` and an exact span, collected
@@ -422,8 +422,12 @@ mechanical) in the same already-breaking release; the migration note is one pass
422
422
  2026-06-12 (pypi.org returns 404 for `flexdoc`); re-check at publish time.
423
423
  Resolve the flexdoc-spec §13 name collision (Stage 2.5 / Open Questions).
424
424
  Configure the PyPI Trusted Publisher for `jlevy/flexdoc` (`docs/publishing.md`).
425
- - [ ] Tag and publish `flexdoc 0.1.0` (its own version line) via `publish.yml`.
426
- Publishing is irreversible; it is the maintainer’s call to trigger.
425
+ - [x] Tagged and published `flexdoc 0.1.0` (2026-06-12): release-notes PR merged, GitHub
426
+ release v0.1.0 created, `publish.yml` succeeded on its first trusted-publishing run,
427
+ and the package was verified live on PyPI (wheel + sdist) with a clean-venv install
428
+ exercising the root API. Note: the install check must run outside the repo, since the
429
+ project's own cool-off excludes the just-published version.
430
+
427
431
 
428
432
  ### Step 5 — rewire chopdiff to the external flexdoc (pending; the breaking release)
429
433