flexdoc 0.2.0__tar.gz → 0.3.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 (182) hide show
  1. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/scripts/ensure-gh-cli.sh +1 -1
  2. {flexdoc-0.2.0 → flexdoc-0.3.0}/.codex/ensure-gh-cli.sh +1 -1
  3. flexdoc-0.3.0/.github/release-request/notes.md +49 -0
  4. flexdoc-0.3.0/.github/release-request/request.json +3 -0
  5. {flexdoc-0.2.0 → flexdoc-0.3.0}/.github/workflows/ci.yml +14 -14
  6. {flexdoc-0.2.0 → flexdoc-0.3.0}/.github/workflows/publish.yml +1 -0
  7. flexdoc-0.3.0/.github/workflows/release.yml +54 -0
  8. {flexdoc-0.2.0 → flexdoc-0.3.0}/.tbd/config.yml +23 -16
  9. {flexdoc-0.2.0 → flexdoc-0.3.0}/AGENTS.md +1 -1
  10. flexdoc-0.3.0/CHANGELOG.md +239 -0
  11. flexdoc-0.3.0/PKG-INFO +170 -0
  12. flexdoc-0.3.0/README.md +132 -0
  13. {flexdoc-0.2.0 → flexdoc-0.3.0}/SUPPLY-CHAIN-SECURITY.md +19 -45
  14. {flexdoc-0.2.0 → flexdoc-0.3.0}/TODO.md +16 -9
  15. {flexdoc-0.2.0 → flexdoc-0.3.0}/devtools/lint.py +5 -0
  16. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/development.md +7 -0
  17. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/flexdoc-spec.md +277 -224
  18. flexdoc-0.3.0/docs/project/drafts/draft-2026-07-flexdoc-intro-post.md +140 -0
  19. flexdoc-0.3.0/docs/project/research/research-2026-07-10-text-reference-microformat.md +845 -0
  20. flexdoc-0.3.0/docs/project/review/senior-engineering-review-flexdoc-2026-07.md +366 -0
  21. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md +36 -36
  22. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/specs/active/plan-2026-05-29-unified-document-model.md +8 -8
  23. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md +97 -89
  24. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/specs/active/plan-2026-06-11-structural-metadata.md +3 -3
  25. flexdoc-0.3.0/docs/project/specs/active/plan-2026-06-13-metrics-use-case.md +561 -0
  26. flexdoc-0.3.0/docs/project/specs/active/plan-2026-07-08-post-review-refinements.md +169 -0
  27. flexdoc-0.3.0/docs/project/specs/active/plan-2026-07-09-flexdoc-stabilization-roadmap.md +255 -0
  28. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/publishing.md +45 -11
  29. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/usage.md +48 -15
  30. {flexdoc-0.2.0 → flexdoc-0.3.0}/examples/backfill_timestamps.py +12 -1
  31. {flexdoc-0.2.0 → flexdoc-0.3.0}/examples/doc_structure.py +6 -2
  32. {flexdoc-0.2.0 → flexdoc-0.3.0}/examples/normalized_form.py +2 -1
  33. {flexdoc-0.2.0 → flexdoc-0.3.0}/pyproject.toml +20 -17
  34. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/__init__.py +8 -5
  35. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/__init__.py +3 -83
  36. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/base_blocks.py +1 -1
  37. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/block_info.py +5 -5
  38. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/block_tree.py +11 -9
  39. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/collect.py +13 -11
  40. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/debug.py +2 -2
  41. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/doc_graph.py +7 -2
  42. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/doc_graph_schema.json +1 -1
  43. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/flex_doc.py +62 -69
  44. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/frontmatter.py +23 -6
  45. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/links.py +2 -2
  46. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/node_table.py +2 -1
  47. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/paragraphs.py +47 -1
  48. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/render.py +43 -1
  49. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/sections.py +32 -17
  50. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/sizes.py +2 -2
  51. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/span_ref.py +101 -30
  52. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_block_info.py +3 -2
  53. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_blocks.py +27 -7
  54. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_caching_threadsafe.py +21 -0
  55. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_collect.py +20 -5
  56. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_frontmatter.py +43 -4
  57. flexdoc-0.3.0/tests/docs/test_line_endings.py +86 -0
  58. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_links.py +8 -1
  59. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_sections.py +27 -0
  60. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_span_ref.py +126 -18
  61. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_spans.py +1 -1
  62. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/README.md +13 -9
  63. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/footnotes_refs.md +11 -2
  64. flexdoc-0.3.0/tests/golden/documents/preamble_only.md +13 -0
  65. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/footnotes_refs/docgraph.yaml +17 -10
  66. flexdoc-0.3.0/tests/golden/expected/footnotes_refs/reassembled.md +15 -0
  67. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/footnotes_refs/report.yaml +120 -64
  68. flexdoc-0.3.0/tests/golden/expected/preamble_only/docgraph.yaml +83 -0
  69. flexdoc-0.3.0/tests/golden/expected/preamble_only/reassembled.md +5 -0
  70. flexdoc-0.3.0/tests/golden/expected/preamble_only/report.yaml +141 -0
  71. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/test_golden_docs.py +3 -3
  72. flexdoc-0.3.0/tests/test_root_api.py +115 -0
  73. {flexdoc-0.2.0 → flexdoc-0.3.0}/uv.lock +170 -165
  74. flexdoc-0.2.0/CHANGELOG.md +0 -135
  75. flexdoc-0.2.0/PKG-INFO +0 -101
  76. flexdoc-0.2.0/README.md +0 -69
  77. flexdoc-0.2.0/docs/project/specs/active/plan-2026-06-13-metrics-use-case.md +0 -531
  78. flexdoc-0.2.0/tests/golden/expected/footnotes_refs/reassembled.md +0 -9
  79. flexdoc-0.2.0/tests/test_root_api.py +0 -53
  80. {flexdoc-0.2.0 → flexdoc-0.3.0}/.agents/skills/simple-modern-uv/SKILL.md +0 -0
  81. {flexdoc-0.2.0 → flexdoc-0.3.0}/.agents/skills/simple-modern-uv/references/adopt-existing.md +0 -0
  82. {flexdoc-0.2.0 → flexdoc-0.3.0}/.agents/skills/simple-modern-uv/references/customize.md +0 -0
  83. {flexdoc-0.2.0 → flexdoc-0.3.0}/.agents/skills/simple-modern-uv/references/faq.md +0 -0
  84. {flexdoc-0.2.0 → flexdoc-0.3.0}/.agents/skills/tbd/SKILL.md +0 -0
  85. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/.gitignore +0 -0
  86. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/hooks/tbd-closing-reminder.sh +0 -0
  87. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/scripts/tbd-session.sh +0 -0
  88. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/settings.json +0 -0
  89. {flexdoc-0.2.0 → flexdoc-0.3.0}/.claude/skills/tbd/SKILL.md +0 -0
  90. {flexdoc-0.2.0 → flexdoc-0.3.0}/.codex/hooks.json +0 -0
  91. {flexdoc-0.2.0 → flexdoc-0.3.0}/.codex/tbd-closing-reminder.sh +0 -0
  92. {flexdoc-0.2.0 → flexdoc-0.3.0}/.codex/tbd-session.sh +0 -0
  93. {flexdoc-0.2.0 → flexdoc-0.3.0}/.copier-answers.yml +0 -0
  94. {flexdoc-0.2.0 → flexdoc-0.3.0}/.gitignore +0 -0
  95. {flexdoc-0.2.0 → flexdoc-0.3.0}/.tbd/.gitattributes +0 -0
  96. {flexdoc-0.2.0 → flexdoc-0.3.0}/.tbd/.gitignore +0 -0
  97. {flexdoc-0.2.0 → flexdoc-0.3.0}/CLAUDE.md +0 -0
  98. {flexdoc-0.2.0 → flexdoc-0.3.0}/LICENSE +0 -0
  99. {flexdoc-0.2.0 → flexdoc-0.3.0}/Makefile +0 -0
  100. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/installation.md +0 -0
  101. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/research/research-2026-05-29-document-model.md +0 -0
  102. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/research/research-2026-05-30-multilayer-parsing.md +0 -0
  103. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/research/research-2026-05-30-span-references.md +0 -0
  104. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/specs/active/plan-2026-05-31-doc-model-refinements.md +0 -0
  105. {flexdoc-0.2.0 → flexdoc-0.3.0}/docs/project/specs/active/plan-2026-05-31-golden-doc-testing.md +0 -0
  106. {flexdoc-0.2.0 → flexdoc-0.3.0}/skills-lock.json +0 -0
  107. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/block_types.py +0 -0
  108. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/interval_index.py +0 -0
  109. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/node.py +0 -0
  110. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/search_tokens.py +0 -0
  111. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/token_diffs.py +0 -0
  112. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/token_mapping.py +0 -0
  113. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/docs/wordtoks.py +0 -0
  114. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/__init__.py +0 -0
  115. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/extractor.py +0 -0
  116. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/html_in_md.py +0 -0
  117. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/html_plaintext.py +0 -0
  118. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/html_tags.py +0 -0
  119. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/html/timestamps.py +0 -0
  120. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/py.typed +0 -0
  121. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/util/__init__.py +0 -0
  122. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/util/read_time.py +0 -0
  123. {flexdoc-0.2.0 → flexdoc-0.3.0}/src/flexdoc/util/token_estimate.py +0 -0
  124. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/__init__.py +0 -0
  125. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/__init__.py +0 -0
  126. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_base_blocks.py +0 -0
  127. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_block_types.py +0 -0
  128. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_doc_graph.py +0 -0
  129. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_flex_doc.py +0 -0
  130. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_footnote_ref.py +0 -0
  131. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_interval_index.py +0 -0
  132. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_node.py +0 -0
  133. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_node_table.py +0 -0
  134. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_offsets.py +0 -0
  135. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_subdoc_and_empty.py +0 -0
  136. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_token_diffs.py +0 -0
  137. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_token_mapping.py +0 -0
  138. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_token_validation.py +0 -0
  139. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/docs/test_wordtoks.py +0 -0
  140. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/heading_edges.md +0 -0
  141. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/inline_html.md +0 -0
  142. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/inline_pathology.md +0 -0
  143. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/kitchen_sink.md +0 -0
  144. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/link_taxonomy.md +0 -0
  145. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/malformed.md +0 -0
  146. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/nested_list.md +0 -0
  147. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/tight_vs_loose.md +0 -0
  148. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/documents/unicode.md +0 -0
  149. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/heading_edges/docgraph.yaml +0 -0
  150. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/heading_edges/reassembled.md +0 -0
  151. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/heading_edges/report.yaml +0 -0
  152. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_html/docgraph.yaml +0 -0
  153. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_html/reassembled.md +0 -0
  154. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_html/report.yaml +0 -0
  155. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_pathology/docgraph.yaml +0 -0
  156. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_pathology/reassembled.md +0 -0
  157. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/inline_pathology/report.yaml +0 -0
  158. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/kitchen_sink/docgraph.yaml +0 -0
  159. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/kitchen_sink/reassembled.md +0 -0
  160. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/kitchen_sink/report.yaml +0 -0
  161. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/link_taxonomy/docgraph.yaml +0 -0
  162. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/link_taxonomy/reassembled.md +0 -0
  163. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/link_taxonomy/report.yaml +0 -0
  164. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/malformed/docgraph.yaml +0 -0
  165. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/malformed/reassembled.md +0 -0
  166. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/malformed/report.yaml +0 -0
  167. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/nested_list/docgraph.yaml +0 -0
  168. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/nested_list/reassembled.md +0 -0
  169. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/nested_list/report.yaml +0 -0
  170. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/tight_vs_loose/docgraph.yaml +0 -0
  171. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/tight_vs_loose/reassembled.md +0 -0
  172. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/tight_vs_loose/report.yaml +0 -0
  173. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/unicode/docgraph.yaml +0 -0
  174. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/unicode/reassembled.md +0 -0
  175. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/golden/expected/unicode/report.yaml +0 -0
  176. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/html/__init__.py +0 -0
  177. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/html/test_html_tag_hardening.py +0 -0
  178. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/html/test_html_tags.py +0 -0
  179. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/html/test_html_validation_and_classes.py +0 -0
  180. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/html/test_timestamps.py +0 -0
  181. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/test_examples.py +0 -0
  182. {flexdoc-0.2.0 → flexdoc-0.3.0}/tests/test_supply_chain.py +0 -0
@@ -117,7 +117,7 @@ if [ -n "${GH_TOKEN:-}" ]; then
117
117
  fi
118
118
  else
119
119
  echo "[gh] NOTE: GH_TOKEN not set - some operations may require authentication"
120
- echo "[gh] See: docs/general/agent-setup/github-cli-setup.md"
120
+ echo "[gh] See: docs/development.md"
121
121
  fi
122
122
 
123
123
  exit 0
@@ -117,7 +117,7 @@ if [ -n "${GH_TOKEN:-}" ]; then
117
117
  fi
118
118
  else
119
119
  echo "[gh] NOTE: GH_TOKEN not set - some operations may require authentication"
120
- echo "[gh] See: docs/general/agent-setup/github-cli-setup.md"
120
+ echo "[gh] See: docs/development.md"
121
121
  fi
122
122
 
123
123
  exit 0
@@ -0,0 +1,49 @@
1
+ ## What's Changed
2
+
3
+ This release stabilizes the pre-1.0 API boundary from the 2026-07 pre-promotion design review ([senior engineering review](https://github.com/jlevy/flexdoc/blob/main/docs/project/review/senior-engineering-review-flexdoc-2026-07.md), PRs #9 and #10). It fixes correctness bugs in source-coordinate handling and anchoring, and settles the remaining pre-1.0 API decisions so later releases can add annotation and synthetic-layer mechanisms without reopening the foundation. Because it changes documented behavior on a pre-1.0 library, it bumps the minor version (per the pre-1.0 rule in `docs/publishing.md`).
4
+
5
+ ### Bug Fixes
6
+
7
+ **CRLF input no longer corrupts the structural views**
8
+
9
+ marko computes block positions against LF-only text, so `\r` in the input desynchronized every structural span (`blocks()`, `sections()`, `base_blocks()`, `links()`, `prose_text()`, the node table) from `source_text`, silently garbling content. `from_text` now normalizes `\r\n` and lone `\r` to `\n` and retains the normalized string as `source_text`, so all layers share one offset space. Callers anchoring offsets to an external CRLF original must normalize it the same way first.
10
+
11
+ **Markdown constructs inside frontmatter can no longer swallow the body**
12
+
13
+ The shared parse previously included the frontmatter region, so e.g. a YAML block scalar containing a code fence opened a fenced block spanning the rest of the document, leaving `blocks()` empty. The frontmatter region is now blanked out of the shared parse (offsets preserved); frontmatter remains a non-content region.
14
+
15
+ **`resolve()` no longer guesses on ambiguous quotes**
16
+
17
+ A `SpanRef` quote that occurs multiple times with no disambiguating prefix/suffix (or a tied context score) now resolves to `None` instead of silently anchoring to the first occurrence. A context-free position hint also cannot select a duplicate, and a zero-width quote (`exact=""`) resolves to `None`.
18
+
19
+ **`collect(overlaps=...)` treats empty intervals as empty**
20
+
21
+ A degenerate `[x, x)` region now overlaps nothing, matching half-open interval semantics; point queries use `(x, x + 1)`.
22
+
23
+ **Render helpers harden their HTML output**
24
+
25
+ `render_node_attrs` attribute-escapes `node.id`, and `wrap_with_node_attrs` validates the tag name (raising `ValueError`), matching the `flexdoc.html` tag helpers.
26
+
27
+ ### Breaking Changes
28
+
29
+ Made cleanly with no compatibility aliases, given the preview (pre-1.0) status:
30
+
31
+ - **`SpanRef` owns its public resolution API** — call `ref.resolve(source_text)` / `ref.resolve_and_update(source_text)`; the generic `resolve` / `resolve_and_update` names are no longer promoted from `flexdoc.docs`.
32
+ - **`flexdoc.docs` now promotes the document model only** — word-token/search and diff/mapping names moved to their owning modules (`flexdoc.docs.wordtoks`, `search_tokens`, `token_diffs`, `token_mapping`).
33
+ - **Recursive `collect()` includes inline descendants by default** — `inline` is now tri-state; pass `inline=False` for the previous block-only recursive result.
34
+ - **`Paragraph.heading_level` / `Paragraph.heading_title` are properties** — remove the `()` from calls.
35
+ - **`NAVIGABLE_LINK_FORMS` replaces `TRUE_LINK_FORMS`** — no alias retained.
36
+ - **`TextUnit` is a `StrEnum`** — `TextUnit.words == "words"` now holds; only `str()`/equality behavior changes.
37
+ - **Cached structural views are mutation-safe** — `Block` is frozen, `Block.children` and `TableInfo.alignments` are tuples, and `sections()` returns isolated copies; direct `Block` construction must pass child tuples.
38
+
39
+ ### Other Changes
40
+
41
+ - `graph()` accepts any `Set` for `include`/`detail`, so plain `set` literals type-check.
42
+ - Frontmatter delimiters tolerate trailing horizontal whitespace.
43
+ - The dependency lock is refreshed under the 14-day cool-off (cutoff 2026-06-26) with no per-package exceptions or audit ignores; CI runs `pip-audit` clean.
44
+ - The OS-independent classifier is backed by a macOS CI job (Python 3.13 full lint/test gate on `macos-latest`).
45
+ - Local release preparation is tag-aware, preventing a tagless clone from producing a `0.0.1.devN` artifact.
46
+
47
+ ### Full Changelog
48
+
49
+ https://github.com/jlevy/flexdoc/compare/v0.2.0...v0.3.0
@@ -0,0 +1,3 @@
1
+ {
2
+ "tag": "v0.3.0"
3
+ }
@@ -1,4 +1,5 @@
1
- # This workflow will install Python dependencies, run tests and lint with a single version of Python
1
+ # Install locked dependencies, lint, and test across every supported Python version on
2
+ # Linux plus one representative macOS job for the OS-independent package contract.
2
3
  # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3
4
 
4
5
  name: CI
@@ -18,12 +19,18 @@ jobs:
18
19
  build:
19
20
  strategy:
20
21
  matrix:
21
- # Update this as needed:
22
- # Common platforms: ["ubuntu-latest", "macos-latest", "windows-latest"]
23
- os: ["ubuntu-latest"]
24
- python-version: ["3.11", "3.12", "3.13", "3.14"]
22
+ include:
23
+ - os: ubuntu-latest
24
+ python-version: "3.11"
25
+ - os: ubuntu-latest
26
+ python-version: "3.12"
27
+ - os: ubuntu-latest
28
+ python-version: "3.13"
29
+ - os: ubuntu-latest
30
+ python-version: "3.14"
31
+ - os: macos-latest
32
+ python-version: "3.13"
25
33
 
26
- # Linux only by default. Use ${{ matrix.os }} for other OSes.
27
34
  runs-on: ${{ matrix.os }}
28
35
 
29
36
  steps:
@@ -91,15 +98,8 @@ jobs:
91
98
  version: "0.11.12"
92
99
  enable-cache: true
93
100
 
94
- # --ignore-vuln PYSEC-2026-196: a CVE in `pip` itself, which is only present as a
95
- # transitive dependency of the pip-audit tool (audit group), not a flexdoc
96
- # runtime/dev dependency, and is never shipped in the flexdoc wheel. Its fix
97
- # (pip 26.1.2) is newer than the [tool.uv] exclude-newer cool-off cutoff, so there
98
- # is no within-policy bump; this ignore does not touch dependency resolution or the
99
- # cool-off. Remove it once the cutoff advances past pip 26.1.2's release. See
100
- # SUPPLY-CHAIN-SECURITY.md (Audit-Gate Ignores).
101
101
  - name: Audit dependencies for known vulnerabilities
102
- run: uv run --frozen --group audit pip-audit --ignore-vuln PYSEC-2026-196
102
+ run: uv run --frozen --group audit pip-audit
103
103
 
104
104
  # Packaging check: the flexdoc wheel must import cleanly from an isolated install, not
105
105
  # just from the source tree. Guards against missing package data or accidental reliance
@@ -4,6 +4,7 @@ on:
4
4
  release:
5
5
  types: [published]
6
6
  workflow_dispatch: # Enable manual trigger.
7
+ workflow_call: # Callable from release.yml, which cannot fire the release event.
7
8
 
8
9
  jobs:
9
10
  build-and-publish:
@@ -0,0 +1,54 @@
1
+ name: Create Release
2
+
3
+ # Release automation for operators that cannot push tags or dispatch workflows
4
+ # directly (e.g. remote agent sessions, which can only push their own working
5
+ # branch and merge PRs). To cut a release: update
6
+ # .github/release-request/request.json and notes.md, land the change on main
7
+ # via PR after the docs/publishing.md pre-release checklist passes, and the
8
+ # merge triggers this workflow. It creates the tag and GitHub Release at the
9
+ # merged commit, then calls publish.yml to build, test, and publish to PyPI.
10
+ #
11
+ # The release is created with GITHUB_TOKEN, whose events do not trigger other
12
+ # workflows, so publish.yml is invoked here as a reusable workflow instead of
13
+ # through its release-published trigger. PyPI trusted publishing validates the
14
+ # job_workflow_ref claim, which points at publish.yml for the called job, so
15
+ # the registered publisher still matches. Releases cut by a maintainer with
16
+ # gh release create (the runbook path) fire publish.yml directly and never
17
+ # run this workflow.
18
+
19
+ on:
20
+ push:
21
+ branches:
22
+ - main
23
+ paths:
24
+ - ".github/release-request/**"
25
+
26
+ jobs:
27
+ create-release:
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: write
31
+ steps:
32
+ - name: Checkout (official GitHub action)
33
+ # actions/checkout@v6
34
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
35
+
36
+ - name: Create tag and release
37
+ env:
38
+ GH_TOKEN: ${{ github.token }}
39
+ run: |
40
+ TAG="$(jq -r .tag .github/release-request/request.json)"
41
+ case "$TAG" in
42
+ v[0-9]*) ;;
43
+ *) echo "Invalid tag in release request: $TAG" >&2; exit 1 ;;
44
+ esac
45
+ gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
46
+ --target "$GITHUB_SHA" --title "$TAG" \
47
+ --notes-file .github/release-request/notes.md
48
+
49
+ publish:
50
+ needs: create-release
51
+ permissions:
52
+ id-token: write
53
+ contents: read
54
+ uses: ./.github/workflows/publish.yml
@@ -1,5 +1,9 @@
1
- tbd_format: f04
1
+ tbd_format: f06
2
2
  tbd_version: 0.2.3
3
+ # tbd_upgrades: tbd versions that have run `tbd setup` in this repo (oldest first);
4
+ # tbd_version above is the most recent. Informational; updated automatically by setup.
5
+ tbd_upgrades:
6
+ - version: 0.2.3
3
7
  display:
4
8
  id_prefix: flexdoc
5
9
  sync:
@@ -10,23 +14,20 @@ settings:
10
14
  auto_sync: false
11
15
  doc_auto_sync_hours: 24
12
16
  use_gh_cli: true
13
- # Documentation cache configuration.
14
- # files: Maps destination paths (relative to .tbd/docs/) to source locations.
15
- # Sources can be:
16
- # - internal: prefix for bundled docs (e.g., "internal:shortcuts/standard/code-review-and-commit.md")
17
- # - Full URL for external docs (e.g., "https://raw.githubusercontent.com/org/repo/main/file.md")
18
- # lookup_path: Search paths for doc lookup (like shell $PATH). Earlier paths take precedence.
19
- #
20
- # To sync docs: tbd sync --docs
21
- # To check status: tbd sync --status
22
- #
23
- # Auto-sync: Docs are automatically synced when stale (default: every 24 hours).
24
- # Configure with settings.doc_auto_sync_hours (0 = disabled).
17
+ # Documentation cache: the docs tbd serves (guidelines, shortcuts, templates), synced
18
+ # into the gitignored .tbd/docs/ cache. Managed with `tbd docs` (run `tbd docs --help`).
19
+ # files: destination path (under .tbd/docs/) -> source docref. Common source forms:
20
+ # - internal:<path> bundled with tbd (e.g. internal:guidelines/typescript-rules.md)
21
+ # - github:owner/repo@ref//path/to/file.md a file in a git repo (gitlab: too; @ref optional)
22
+ # - https://host/path/file.md a plain URL, kept verbatim
23
+ # Full docref grammar: `tbd docs show docref-format`.
24
+ # lookup_path: doc lookup search order, like shell $PATH (earlier paths win).
25
+ # Refresh the cache with `tbd docs sync`; it also auto-syncs when stale
26
+ # (settings.doc_auto_sync_hours, default 24; 0 disables).
25
27
  docs_cache:
26
- lookup_path:
27
- - .tbd/docs/shortcuts/system
28
- - .tbd/docs/shortcuts/standard
29
28
  files:
29
+ references/tbd-docs.md: internal:tbd-docs.md
30
+ references/tbd-design.md: internal:tbd-design.md
30
31
  shortcuts/system/shortcut-explanation.md: internal:shortcuts/system/shortcut-explanation.md
31
32
  shortcuts/system/skill-baseline.md: internal:shortcuts/system/skill-baseline.md
32
33
  shortcuts/system/skill-brief.md: internal:shortcuts/system/skill-brief.md
@@ -58,6 +59,7 @@ docs_cache:
58
59
  shortcuts/standard/revise-all-architecture-docs.md: internal:shortcuts/standard/revise-all-architecture-docs.md
59
60
  shortcuts/standard/revise-architecture-doc.md: internal:shortcuts/standard/revise-architecture-doc.md
60
61
  shortcuts/standard/setup-github-cli.md: internal:shortcuts/standard/setup-github-cli.md
62
+ shortcuts/standard/suggest-upstream-improvements.md: internal:shortcuts/standard/suggest-upstream-improvements.md
61
63
  shortcuts/standard/sync-failure-recovery.md: internal:shortcuts/standard/sync-failure-recovery.md
62
64
  shortcuts/standard/update-specs-status.md: internal:shortcuts/standard/update-specs-status.md
63
65
  shortcuts/standard/welcome-user.md: internal:shortcuts/standard/welcome-user.md
@@ -92,3 +94,8 @@ docs_cache:
92
94
  templates/plan-spec.md: internal:templates/plan-spec.md
93
95
  templates/qa-playbook.md: internal:templates/qa-playbook.md
94
96
  templates/research-brief.md: internal:templates/research-brief.md
97
+ references/docmap-format.md: internal:references/docmap-format.md
98
+ references/docref-format.md: internal:references/docref-format.md
99
+ lookup_path:
100
+ - .tbd/docs/shortcuts/system
101
+ - .tbd/docs/shortcuts/standard
@@ -364,7 +364,7 @@ Always use full type annotations, generics, and other modern practices.
364
364
  - When changing code in a library or general function, if a change to an API or library
365
365
  will break backward compatibility, MENTION THIS to the user.
366
366
 
367
- - DO NOT implement additional code for backward compatiblity (such as extra methods or
367
+ - DO NOT implement additional code for backward compatibility (such as extra methods or
368
368
  variable aliases or comments about backward compatibility) UNLESS the user has
369
369
  confirmed that it is necessary.
370
370
 
@@ -0,0 +1,239 @@
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.3.0 (2026-07-11)
8
+
9
+ Fixes from the 2026-07 pre-promotion design review
10
+ (`docs/project/review/senior-engineering-review-flexdoc-2026-07.md`). These changes
11
+ alter documented behavior, so per the pre-1.0 rule this is a minor release, not a
12
+ 0.2.x patch.
13
+
14
+ ### Fixed
15
+
16
+ - **CRLF input no longer corrupts the structural views.** marko computes block positions
17
+ against LF-only text, so `\r` in the input desynchronized every structural span
18
+ (`blocks()`, `sections()`, `base_blocks()`, `links()`, `prose_text()`, the node table)
19
+ from `source_text`, silently garbling content and violating the base-block cover
20
+ invariant. `from_text` now normalizes `\r\n` and lone `\r` to `\n` and retains the
21
+ normalized string as `source_text`, so all layers share one offset space.
22
+ Callers anchoring offsets to an external CRLF original must normalize it the same way
23
+ first.
24
+ - **Markdown constructs inside frontmatter can no longer swallow the body.** The shared
25
+ parse previously included the frontmatter region, so e.g. a YAML block scalar
26
+ containing a code fence opened a fenced block spanning the rest of the document,
27
+ leaving `blocks()` empty.
28
+ The frontmatter region is now blanked out of the shared parse (offsets preserved);
29
+ frontmatter remains a non-content region.
30
+ Link extraction now reuses that blanked parse instead of reparsing the body for
31
+ frontmatter documents.
32
+ - **`resolve()` no longer guesses on ambiguous quotes.** Per the spec’s error posture
33
+ (§11), a `SpanRef` quote that occurs multiple times with no disambiguating
34
+ prefix/suffix (or a tied context score) now resolves to `None` instead of silently
35
+ anchoring to the first occurrence.
36
+ A context-free position hint also cannot select a duplicate, because no context or
37
+ source identity proves which occurrence was intended.
38
+ A zero-width quote (`exact=""`) also resolves to `None` on both the fast and slow
39
+ paths.
40
+ - **`collect(overlaps=...)` treats empty intervals as empty.** A degenerate `[x, x)`
41
+ region (or node span) now overlaps nothing, matching half-open interval semantics;
42
+ point queries use `(x, x + 1)`.
43
+ - **Render helpers harden their HTML output.** `render_node_attrs` attribute-escapes
44
+ `node.id`, and `wrap_with_node_attrs` validates the tag name (raising `ValueError`),
45
+ matching the validation in the `flexdoc.html` tag helpers.
46
+
47
+ ### Changed
48
+
49
+ - **The dependency lock is refreshed under the 14-day cool-off.** The cutoff is
50
+ 2026-06-26, expired per-package exceptions are removed, and the audit group resolves
51
+ fixed `pip` and `msgpack` versions, so CI runs `pip-audit` without advisory ignores.
52
+ - **`graph()` accepts any set.** `FlexDoc.graph()` and `build_doc_graph()` annotate
53
+ `include`/`detail` as `collections.abc.Set`, so plain `set` literals type-check
54
+ (matching `collect()`); behavior is unchanged.
55
+ - **`TextUnit` is a `StrEnum`**, matching every other public enum, so
56
+ `TextUnit.words == "words"` now holds.
57
+ Source-compatible for enum-member access; only `str()`/equality-with-string behavior
58
+ changes.
59
+ - **Recursive `collect()` includes inline descendants by default.** The `inline`
60
+ parameter is now tri-state: omission follows recursive traversal or an explicit
61
+ inline-kind filter, `inline=False` excludes inline nodes, and `inline=True` includes
62
+ them for any query. Callers that need the previous block-only recursive result must
63
+ pass `inline=False`.
64
+ - **Cached structural views are mutation-safe.** `Block` is now frozen, `Block.children`
65
+ and `TableInfo.alignments` are tuples, and `sections()` returns recursively isolated
66
+ section/paragraph copies.
67
+ Code constructing blocks directly must pass child tuples; code comparing or building
68
+ table metadata must use alignment tuples.
69
+ Mutation of a returned section remains local to that view and does not persist to a
70
+ later call.
71
+ - **`SpanRef` owns its public resolution API.** Call `ref.resolve(source_text)` or
72
+ `ref.resolve_and_update(source_text)` on the root-exported type.
73
+ The generic `resolve` and `resolve_and_update` names are no longer promoted from
74
+ `flexdoc.docs`; update package-level imports and calls to use the methods.
75
+ - **Paragraph heading metadata is property-based.** `Paragraph.heading_level` and
76
+ `Paragraph.heading_title` now match `Paragraph.block_type` and `Block.heading_level`.
77
+ Remove `()` from calls to the two former methods.
78
+ - **The navigable-link constant is accurately named.** Import `NAVIGABLE_LINK_FORMS`
79
+ instead of `TRUE_LINK_FORMS`; no compatibility alias is retained.
80
+ - **`flexdoc.docs` now promotes the document model only.** Word-token/search and
81
+ diff/mapping names are no longer re-exported.
82
+ Import them from `flexdoc.docs.wordtoks`, `search_tokens`, `token_diffs`, or
83
+ `token_mapping` instead.
84
+ The current Chopdiff integration already uses these owning-module paths.
85
+ - **Frontmatter delimiters tolerate trailing horizontal whitespace.** Opening and
86
+ closing `---` lines may end in spaces or tabs while remaining verbatim in
87
+ `frontmatter`; leading whitespace still disqualifies a delimiter, and an unclosed
88
+ opening remains a thematic break.
89
+ - **The OS-independent classifier is backed by macOS CI.** Ubuntu still covers every
90
+ supported Python version, and Python 3.13 now runs the full lint/test gate on
91
+ `macos-latest` as a representative second platform.
92
+ - **Local release preparation is tag-aware.** The runbook fetches tags before building
93
+ and verifies candidate wheel metadata from an isolated local tag, preventing a tagless
94
+ clone from silently producing a `0.0.1.devN` release artifact.
95
+ - **Section sizing no longer constructs temporary documents.** `FlexDoc` and `Section`
96
+ now share private paragraph aggregation for every `TextUnit` and size summary; public
97
+ results and signatures are unchanged.
98
+
99
+ Remaining pre-1.0 design decisions and future mechanisms are collected in
100
+ `docs/project/specs/active/plan-2026-07-09-flexdoc-stabilization-roadmap.md`.
101
+
102
+ ## 0.2.0 (2026-06-14)
103
+
104
+ Correctness fixes and a completed inline/heading/link surface for the document-metrics
105
+ use case (`docs/project/specs/active/plan-2026-06-13-metrics-use-case.md`, issue #6). As
106
+ a preview-stage library this takes the cleanest shape with no compatibility shims; the
107
+ API additions below include breaking signature changes (see **Changed**).
108
+
109
+ ### Fixed
110
+
111
+ - **`node_table()` / `collect()` / `graph()` no longer raise on valid Markdown.** Inline
112
+ elements were discovered over the whole source and parented by start offset, so
113
+ backtick pairing across a block boundary (an empty fence next to inline backticks)
114
+ produced an inline span escaping its parent block and raised a layer-nesting error.
115
+ Inline discovery is now scoped per leaf content block, so an inline node can never
116
+ straddle a block boundary; links/images/definitions are parented by full containment.
117
+ - **`sections()` / `toc()` recover every heading `blocks()` finds, and own their content
118
+ correctly.** Headings were re-derived from the blank-line paragraph view, dropping
119
+ tight headings and headings preceded by a non-blank line (e.g. an HTML-comment
120
+ marker), and section content was bucketed from that same view, so a heading glued to
121
+ its body lost the body.
122
+ Sections now derive entirely from the structural block tree—the heading set *and* each
123
+ section’s own content (`own_paragraphs()` / `blocks()` / sizes) come from the
124
+ section’s source region—so tight and marker-preceded headings own exactly their
125
+ content.
126
+ - **Section spans nest correctly** even when a blank-line paragraph straddles a later
127
+ heading (e.g. an embedded `---` block marko reads as a setext heading): each section
128
+ spans from its heading to the next same-or-higher heading (trimmed), which nests by
129
+ construction. Byte-identical to the prior span for well-formed documents.
130
+ - **Reference-definition nodes attach to their block.** A `link_ref_def` span included
131
+ the line’s trailing newline and escaped the containing paragraph, leaving the node
132
+ unparented so a block-scoped `collect()` missed it; spans are now trimmed like every
133
+ structural block.
134
+
135
+ ### Added
136
+
137
+ - **Heading metadata on the structural block**: `Block.heading_info` (`HeadingInfo` with
138
+ parser-authoritative `level` and `title`) and the `Block.heading_level` convenience;
139
+ `HeadingInfo` is exported from `flexdoc.docs`. The node table reads heading level from
140
+ it.
141
+ - **Typed link forms**: `LinkForm` (`inline` / `reference` / `autolink` / `bare_url` /
142
+ `image` / `reference_definition`) and `Link.link_form`. `FlexDoc.links(link_forms=…)`
143
+ selects any forms (default: navigable links only), and `FlexDoc.images()` is a
144
+ convenience for image access.
145
+ Reference definitions (`[id]: url`) are surfaced as `NodeKind.link_ref_def` nodes and
146
+ via `links(link_forms={LinkForm.reference_definition})`.
147
+ - **`FlexDoc.prose_text()`**: prose-only text for editorial linting and prose metrics —
148
+ prose blocks (paragraphs/headings, and table cells when `include_tables=True`) with
149
+ inline code and footnote refs dropped, links/images replaced by their text/alt,
150
+ inline-HTML tags dropped (wrapped text kept), and heading/blockquote/list markers and
151
+ reference-definition lines stripped; from verbatim source slices (line wrapping
152
+ preserved, never reflowed) so spacing like a spaced em-dash is kept exactly.
153
+ - **`FlexDoc.block_at_offset()`**: the innermost structural `Block` containing an offset
154
+ (the structural counterpart of `paragraph_at_offset`; the name, freed in 0.1.0, now
155
+ correctly returns a `Block`).
156
+ - **Test-suite hardening**: adversarial corpus documents (`inline_pathology`,
157
+ `heading_edges`, `link_taxonomy`); cross-projection invariants tying `toc()` to the
158
+ heading blocks, inline nesting on the query surface, and link-form accounting; and a
159
+ dogfood test that parses every Markdown file in the repo and asserts the invariants.
160
+ See the spec’s “Why These Bugs Escaped the Tests” analysis.
161
+
162
+ ### Changed
163
+
164
+ These are breaking, made cleanly (no aliases) given the preview status:
165
+
166
+ - **`Link` gains a required `link_form: LinkForm` field.** Direct `Link(...)`
167
+ construction must pass it.
168
+ - **`block_links()` returns all link-like constructs** (navigable links, images, and
169
+ reference definitions), each with a `link_form`; previously it returned navigable
170
+ links only. `FlexDoc.links()` filters to navigable links by default, so its default
171
+ result is unchanged.
172
+ - **`collect()` returns inline-kind nodes without `recursive=True`.** An inline-kind
173
+ request (e.g. `collect(kinds={NodeKind.link})`) now widens the candidate set instead
174
+ of silently returning `[]`—matching the documented behavior.
175
+
176
+ ## 0.1.0 (2026-06-12)
177
+
178
+ First release.
179
+
180
+ ### Added
181
+
182
+ - **Initial flexdoc package**, extracted from
183
+ [chopdiff](https://github.com/jlevy/chopdiff) as its own standalone distribution.
184
+ This is the document/markdown layer—`FlexDoc`, paragraphs/sentences, the block tree
185
+ and block types, sections, the node table, `collect()`, `DocGraph`, `SpanRef`, token
186
+ diffs/mappings, word tokenization, html-in-md, and read-time/token estimation—with no
187
+ dependency on chopdiff’s diff and windowed-transform machinery.
188
+
189
+ The import roots are `flexdoc.docs`, `flexdoc.html`, and `flexdoc.util`. Parse
190
+ behavior is unchanged from the `flexdoc.*` modules that previously shipped inside the
191
+ chopdiff wheel; this release packages them independently.
192
+ See `docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md`.
193
+
194
+ - **A deliberate root API**: the working set is importable from the package root —
195
+ `FlexDoc`, `DocGraph`, `Detail`, `SpanRef`, `BlockType`, `NodeKind`, `Layer`,
196
+ `TextUnit`—designed against the known downstream users and pinned by contract tests.
197
+ The render helpers for source-linked HTML (`render_node_attrs`,
198
+ `wrap_with_node_attrs`, `parse_source_span_attr`) are public in `flexdoc.docs`.
199
+
200
+ - **DocGraph paragraph view**: `Views.paragraphs` joins `toc`/`blocks`/`links`/
201
+ `sentences` in the serialized projection.
202
+
203
+ ### Changed (Relative to the Modules as Shipped in Chopdiff)
204
+
205
+ The first standalone release also refines the API surface (the pre-publish design
206
+ review, `docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md`);
207
+ these are intentional hard cuts with no compatibility aliases:
208
+
209
+ - **`TextDoc` is renamed `FlexDoc`**—the package’s single entry point, named for the
210
+ model it carries (all layered projections hang off it).
211
+ It is importable from the package root: `from flexdoc import FlexDoc`. The module is
212
+ `flexdoc.docs.flex_doc` (was `chopdiff.docs.text_doc`), and the design of record is
213
+ now `docs/flexdoc-spec.md` (was `textdoc-spec.md`).
214
+ - **`collect()` is fully keyword-only and the deprecated aliases are gone**: use
215
+ `subtree_of=` (was `scope=`, previously also positional) and `within=` (was
216
+ `contains=`).
217
+ - **Editing-view methods are named in paragraph terms**, so “block” always means the
218
+ structural layer: `FlexDoc.paragraph_at_offset` (was `block_at_offset`),
219
+ `FlexDoc.iter_paragraphs` (was `iter_blocks`), `Section.own_paragraphs`/
220
+ `subtree_paragraphs` (were `own_blocks`/`subtree_blocks`).
221
+ - **The export surface is settled**: `flexdoc.docs` now exports
222
+ `CodeInfo`/`TableInfo`/`ListInfo`, `resolve`/`resolve_and_update`,
223
+ `parse_blocks`/`walk_blocks`/`block_type_for`, and `DEFAULT_INCLUDE`; `flexdoc.html`
224
+ exports `html_p`, `html_tag`, `escape_attribute`, `tag_wrapper`, and
225
+ `identity_wrapper`. Link extraction is public as `flexdoc.docs.links.block_links`.
226
+ - **`Node.attrs` values are JSON-typed** (`AttrValue`), validated at `DocGraph`
227
+ serialization, and node-id assignment order is pinned and tested for cross-language
228
+ ports; layer nesting guarantees (`LAYER_NESTING`) are enforced at node-table build.
229
+ - Internally, the former `text_doc.py` was split into `flex_doc.py`, `paragraphs.py`,
230
+ `links.py`, and `sections.py` (package imports from `flexdoc.docs` are unaffected),
231
+ and `sections()` is now cached like the other derived views.
232
+
233
+ Migration from chopdiff in one pass: `chopdiff.docs.TextDoc` → `flexdoc.FlexDoc` (or
234
+ `flexdoc.docs.FlexDoc`), `chopdiff.docs.*` → `flexdoc.docs.*`, plus the method renames
235
+ above.
236
+
237
+ <!-- This document follows common-doc-guidelines.md.
238
+ See github.com/jlevy/practical-prose and review guidelines before editing.
239
+ -->