flexdoc 0.2.0__tar.gz → 0.4.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 (204) hide show
  1. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/scripts/ensure-gh-cli.sh +1 -1
  2. {flexdoc-0.2.0 → flexdoc-0.4.0}/.codex/ensure-gh-cli.sh +1 -1
  3. flexdoc-0.4.0/.github/release-request/notes.md +32 -0
  4. flexdoc-0.4.0/.github/release-request/request.json +3 -0
  5. {flexdoc-0.2.0 → flexdoc-0.4.0}/.github/workflows/ci.yml +14 -14
  6. {flexdoc-0.2.0 → flexdoc-0.4.0}/.github/workflows/publish.yml +1 -0
  7. flexdoc-0.4.0/.github/workflows/release.yml +54 -0
  8. {flexdoc-0.2.0 → flexdoc-0.4.0}/.tbd/config.yml +25 -16
  9. {flexdoc-0.2.0 → flexdoc-0.4.0}/AGENTS.md +1 -1
  10. flexdoc-0.4.0/CHANGELOG.md +292 -0
  11. flexdoc-0.4.0/PKG-INFO +282 -0
  12. flexdoc-0.4.0/README.md +244 -0
  13. {flexdoc-0.2.0 → flexdoc-0.4.0}/SUPPLY-CHAIN-SECURITY.md +19 -45
  14. {flexdoc-0.2.0 → flexdoc-0.4.0}/TODO.md +17 -8
  15. {flexdoc-0.2.0 → flexdoc-0.4.0}/devtools/lint.py +5 -0
  16. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/development.md +7 -0
  17. flexdoc-0.4.0/docs/flexdoc-spec.md +1431 -0
  18. flexdoc-0.4.0/docs/project/drafts/draft-2026-07-flexdoc-intro-post.md +140 -0
  19. flexdoc-0.4.0/docs/project/research/research-2026-07-10-text-reference-microformat.md +3546 -0
  20. flexdoc-0.4.0/docs/project/review/senior-engineering-review-flexdoc-2026-07.md +366 -0
  21. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md +36 -36
  22. flexdoc-0.4.0/docs/project/review/senior-engineering-review-v0.4-release-2026-07.md +253 -0
  23. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/specs/active/plan-2026-05-29-unified-document-model.md +8 -8
  24. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md +97 -89
  25. flexdoc-0.4.0/docs/project/specs/active/plan-2026-07-09-flexdoc-stabilization-roadmap.md +267 -0
  26. {flexdoc-0.2.0/docs/project/specs/active → flexdoc-0.4.0/docs/project/specs/done}/plan-2026-06-11-structural-metadata.md +5 -5
  27. flexdoc-0.4.0/docs/project/specs/done/plan-2026-06-13-metrics-use-case.md +561 -0
  28. flexdoc-0.4.0/docs/project/specs/done/plan-2026-07-08-post-review-refinements.md +169 -0
  29. flexdoc-0.4.0/docs/project/specs/done/plan-2026-07-13-logical-word-metrics.md +260 -0
  30. flexdoc-0.4.0/docs/project/specs/done/plan-2026-07-14-native-textref-integration.md +344 -0
  31. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/publishing.md +56 -11
  32. flexdoc-0.4.0/docs/usage.md +245 -0
  33. {flexdoc-0.2.0 → flexdoc-0.4.0}/examples/backfill_timestamps.py +12 -1
  34. {flexdoc-0.2.0 → flexdoc-0.4.0}/examples/doc_structure.py +10 -5
  35. {flexdoc-0.2.0 → flexdoc-0.4.0}/examples/normalized_form.py +2 -1
  36. flexdoc-0.4.0/examples/textref_workflows.py +89 -0
  37. {flexdoc-0.2.0 → flexdoc-0.4.0}/pyproject.toml +20 -17
  38. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/__init__.py +25 -6
  39. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/__init__.py +60 -81
  40. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/base_blocks.py +1 -1
  41. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/block_info.py +5 -5
  42. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/block_tree.py +11 -9
  43. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/collect.py +13 -11
  44. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/debug.py +19 -10
  45. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/doc_graph.py +125 -50
  46. flexdoc-0.4.0/src/flexdoc/docs/doc_graph_schema.json +603 -0
  47. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/flex_doc.py +99 -77
  48. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/frontmatter.py +23 -6
  49. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/links.py +2 -2
  50. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/node_table.py +2 -1
  51. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/paragraphs.py +52 -2
  52. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/render.py +43 -1
  53. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/sections.py +32 -17
  54. flexdoc-0.4.0/src/flexdoc/docs/serialization.py +26 -0
  55. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/sizes.py +19 -4
  56. flexdoc-0.4.0/src/flexdoc/docs/span_ref.py +345 -0
  57. flexdoc-0.4.0/src/flexdoc/docs/text_annotations.py +157 -0
  58. flexdoc-0.4.0/src/flexdoc/docs/text_ref.py +855 -0
  59. flexdoc-0.4.0/src/flexdoc/docs/text_ref_context.py +730 -0
  60. flexdoc-0.4.0/src/flexdoc/docs/text_ref_schema.json +315 -0
  61. flexdoc-0.4.0/src/flexdoc/util/__init__.py +23 -0
  62. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/util/read_time.py +5 -7
  63. flexdoc-0.4.0/src/flexdoc/util/token_estimate.py +25 -0
  64. flexdoc-0.4.0/src/flexdoc/util/word_count.py +74 -0
  65. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_block_info.py +3 -2
  66. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_block_types.py +3 -3
  67. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_blocks.py +27 -7
  68. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_caching_threadsafe.py +21 -0
  69. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_collect.py +20 -5
  70. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_doc_graph.py +124 -35
  71. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_flex_doc.py +2 -3
  72. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_frontmatter.py +44 -5
  73. flexdoc-0.4.0/tests/docs/test_line_endings.py +86 -0
  74. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_links.py +8 -1
  75. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_sections.py +35 -8
  76. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_span_ref.py +126 -18
  77. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_spans.py +1 -1
  78. flexdoc-0.4.0/tests/docs/test_text_annotations.py +245 -0
  79. flexdoc-0.4.0/tests/docs/test_text_ref.py +399 -0
  80. flexdoc-0.4.0/tests/docs/test_text_ref_context.py +242 -0
  81. flexdoc-0.4.0/tests/docs/test_text_ref_render.py +136 -0
  82. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/README.md +13 -9
  83. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/footnotes_refs.md +11 -2
  84. flexdoc-0.4.0/tests/golden/documents/preamble_only.md +13 -0
  85. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/footnotes_refs/docgraph.yaml +19 -11
  86. flexdoc-0.4.0/tests/golden/expected/footnotes_refs/reassembled.md +15 -0
  87. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/footnotes_refs/report.yaml +120 -66
  88. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/heading_edges/docgraph.yaml +3 -2
  89. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/heading_edges/report.yaml +7 -12
  90. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_html/docgraph.yaml +3 -2
  91. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_html/report.yaml +1 -1
  92. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_pathology/docgraph.yaml +3 -2
  93. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_pathology/report.yaml +6 -11
  94. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/kitchen_sink/docgraph.yaml +3 -2
  95. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/kitchen_sink/report.yaml +3 -5
  96. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/link_taxonomy/docgraph.yaml +3 -2
  97. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/link_taxonomy/report.yaml +8 -14
  98. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/malformed/docgraph.yaml +3 -2
  99. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/malformed/report.yaml +1 -1
  100. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/nested_list/docgraph.yaml +3 -2
  101. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/nested_list/report.yaml +1 -1
  102. flexdoc-0.4.0/tests/golden/expected/preamble_only/docgraph.yaml +84 -0
  103. flexdoc-0.4.0/tests/golden/expected/preamble_only/reassembled.md +5 -0
  104. flexdoc-0.4.0/tests/golden/expected/preamble_only/report.yaml +141 -0
  105. flexdoc-0.4.0/tests/golden/expected/textref-annotations.md +59 -0
  106. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/tight_vs_loose/docgraph.yaml +3 -2
  107. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/tight_vs_loose/report.yaml +1 -1
  108. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/unicode/docgraph.yaml +3 -2
  109. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/unicode/report.yaml +4 -6
  110. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/test_golden_docs.py +10 -9
  111. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/test_examples.py +1 -0
  112. flexdoc-0.4.0/tests/test_root_api.py +157 -0
  113. flexdoc-0.4.0/tests/test_word_count.py +117 -0
  114. {flexdoc-0.2.0 → flexdoc-0.4.0}/uv.lock +170 -165
  115. flexdoc-0.2.0/CHANGELOG.md +0 -135
  116. flexdoc-0.2.0/PKG-INFO +0 -101
  117. flexdoc-0.2.0/README.md +0 -69
  118. flexdoc-0.2.0/docs/flexdoc-spec.md +0 -1018
  119. flexdoc-0.2.0/docs/project/specs/active/plan-2026-06-13-metrics-use-case.md +0 -531
  120. flexdoc-0.2.0/docs/usage.md +0 -136
  121. flexdoc-0.2.0/src/flexdoc/docs/doc_graph_schema.json +0 -219
  122. flexdoc-0.2.0/src/flexdoc/docs/span_ref.py +0 -192
  123. flexdoc-0.2.0/src/flexdoc/util/__init__.py +0 -11
  124. flexdoc-0.2.0/src/flexdoc/util/token_estimate.py +0 -34
  125. flexdoc-0.2.0/tests/golden/expected/footnotes_refs/reassembled.md +0 -9
  126. flexdoc-0.2.0/tests/test_root_api.py +0 -53
  127. {flexdoc-0.2.0 → flexdoc-0.4.0}/.agents/skills/simple-modern-uv/SKILL.md +0 -0
  128. {flexdoc-0.2.0 → flexdoc-0.4.0}/.agents/skills/simple-modern-uv/references/adopt-existing.md +0 -0
  129. {flexdoc-0.2.0 → flexdoc-0.4.0}/.agents/skills/simple-modern-uv/references/customize.md +0 -0
  130. {flexdoc-0.2.0 → flexdoc-0.4.0}/.agents/skills/simple-modern-uv/references/faq.md +0 -0
  131. {flexdoc-0.2.0 → flexdoc-0.4.0}/.agents/skills/tbd/SKILL.md +0 -0
  132. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/.gitignore +0 -0
  133. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/hooks/tbd-closing-reminder.sh +0 -0
  134. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/scripts/tbd-session.sh +0 -0
  135. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/settings.json +0 -0
  136. {flexdoc-0.2.0 → flexdoc-0.4.0}/.claude/skills/tbd/SKILL.md +0 -0
  137. {flexdoc-0.2.0 → flexdoc-0.4.0}/.codex/hooks.json +0 -0
  138. {flexdoc-0.2.0 → flexdoc-0.4.0}/.codex/tbd-closing-reminder.sh +0 -0
  139. {flexdoc-0.2.0 → flexdoc-0.4.0}/.codex/tbd-session.sh +0 -0
  140. {flexdoc-0.2.0 → flexdoc-0.4.0}/.copier-answers.yml +0 -0
  141. {flexdoc-0.2.0 → flexdoc-0.4.0}/.gitignore +0 -0
  142. {flexdoc-0.2.0 → flexdoc-0.4.0}/.tbd/.gitattributes +0 -0
  143. {flexdoc-0.2.0 → flexdoc-0.4.0}/.tbd/.gitignore +0 -0
  144. {flexdoc-0.2.0 → flexdoc-0.4.0}/CLAUDE.md +0 -0
  145. {flexdoc-0.2.0 → flexdoc-0.4.0}/LICENSE +0 -0
  146. {flexdoc-0.2.0 → flexdoc-0.4.0}/Makefile +0 -0
  147. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/installation.md +0 -0
  148. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/research/research-2026-05-29-document-model.md +0 -0
  149. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/research/research-2026-05-30-multilayer-parsing.md +0 -0
  150. {flexdoc-0.2.0 → flexdoc-0.4.0}/docs/project/research/research-2026-05-30-span-references.md +0 -0
  151. {flexdoc-0.2.0/docs/project/specs/active → flexdoc-0.4.0/docs/project/specs/done}/plan-2026-05-31-doc-model-refinements.md +0 -0
  152. {flexdoc-0.2.0/docs/project/specs/active → flexdoc-0.4.0/docs/project/specs/done}/plan-2026-05-31-golden-doc-testing.md +0 -0
  153. {flexdoc-0.2.0 → flexdoc-0.4.0}/skills-lock.json +0 -0
  154. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/block_types.py +0 -0
  155. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/interval_index.py +0 -0
  156. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/node.py +0 -0
  157. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/search_tokens.py +0 -0
  158. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/token_diffs.py +0 -0
  159. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/token_mapping.py +0 -0
  160. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/docs/wordtoks.py +0 -0
  161. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/__init__.py +0 -0
  162. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/extractor.py +0 -0
  163. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/html_in_md.py +0 -0
  164. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/html_plaintext.py +0 -0
  165. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/html_tags.py +0 -0
  166. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/html/timestamps.py +0 -0
  167. {flexdoc-0.2.0 → flexdoc-0.4.0}/src/flexdoc/py.typed +0 -0
  168. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/__init__.py +0 -0
  169. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/__init__.py +0 -0
  170. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_base_blocks.py +0 -0
  171. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_footnote_ref.py +0 -0
  172. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_interval_index.py +0 -0
  173. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_node.py +0 -0
  174. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_node_table.py +0 -0
  175. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_offsets.py +0 -0
  176. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_subdoc_and_empty.py +0 -0
  177. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_token_diffs.py +0 -0
  178. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_token_mapping.py +0 -0
  179. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_token_validation.py +0 -0
  180. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/docs/test_wordtoks.py +0 -0
  181. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/heading_edges.md +0 -0
  182. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/inline_html.md +0 -0
  183. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/inline_pathology.md +0 -0
  184. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/kitchen_sink.md +0 -0
  185. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/link_taxonomy.md +0 -0
  186. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/malformed.md +0 -0
  187. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/nested_list.md +0 -0
  188. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/tight_vs_loose.md +0 -0
  189. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/documents/unicode.md +0 -0
  190. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/heading_edges/reassembled.md +0 -0
  191. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_html/reassembled.md +0 -0
  192. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/inline_pathology/reassembled.md +0 -0
  193. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/kitchen_sink/reassembled.md +0 -0
  194. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/link_taxonomy/reassembled.md +0 -0
  195. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/malformed/reassembled.md +0 -0
  196. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/nested_list/reassembled.md +0 -0
  197. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/tight_vs_loose/reassembled.md +0 -0
  198. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/golden/expected/unicode/reassembled.md +0 -0
  199. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/html/__init__.py +0 -0
  200. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/html/test_html_tag_hardening.py +0 -0
  201. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/html/test_html_tags.py +0 -0
  202. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/html/test_html_validation_and_classes.py +0 -0
  203. {flexdoc-0.2.0 → flexdoc-0.4.0}/tests/html/test_timestamps.py +0 -0
  204. {flexdoc-0.2.0 → flexdoc-0.4.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,32 @@
1
+ ## What's Changed
2
+
3
+ This release adds the portable reference and annotation layer from the 2026-07 TextRef design cycle plus cross-language logical word metrics, finalized under an independent senior release review ([review and breaking-change catalog](https://github.com/jlevy/flexdoc/blob/main/docs/project/review/senior-engineering-review-v0.4-release-2026-07.md); PRs #18, #20, #21). 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`). If you depend on an unbounded `flexdoc>=0.3.0`, review the breaking changes below or pin `<0.4` before upgrading.
4
+
5
+ ### New Features
6
+
7
+ **Native TextRef integration**
8
+
9
+ Strict `DocRef`/`TextRef` values identify whole documents, spans, points, and semantic sections with canonical JSON and reversible `textref:0.1` URIs. `FlexDoc.references()` binds a document locator to one source snapshot: it maps every locatable public value (`for_target`, `for_span`, `for_point`, `for_section`), resolves references with typed outcomes on independent document/source-validation/selector axes (resolution never silently picks a duplicate), retrieves structured line-window context, and renders deterministic annotation views for humans and LLMs. Span quote evidence is configurable per context or per span; compact exact-less spans stay bound to one source hash.
10
+
11
+ **Consumer-owned annotations**
12
+
13
+ `TextAnnotation` and the one-document `AnnotationSet` sidecar round-trip strict JSON and safe YAML, and every sidecar entry is validated through the complete TextRef evidence contract at parse time. `DocGraph/v0.2` embeds a matching sidecar after verifying its document and source hash against the snapshot, and bounds-checks embedded positions. Committed JSON Schemas ship for both formats in the wheel.
14
+
15
+ **Cross-language logical word metrics**
16
+
17
+ `TextUnit.words` now measures normalized word-equivalent volume across natural language, CJK text, source code, URLs, and punctuation-dense content. `TextUnit.raw_words` and `raw_word_count()` keep literal whitespace counting, and `logical_word_count()` exposes the dependency-free primitive. See the [logical-word definition and validation](https://gist.github.com/jlevy/0d6d87885f6d85f31440e58b8cfce663).
18
+
19
+ ### Breaking Changes
20
+
21
+ Made cleanly with no compatibility aliases, given the pre-1.0 status. Full catalog with rationales and migrations: [review doc §4](https://github.com/jlevy/flexdoc/blob/main/docs/project/review/senior-engineering-review-v0.4-release-2026-07.md).
22
+
23
+ - **`FlexDoc.graph()` requires a document locator** — `doc.graph()` becomes `doc.graph(document="path/or/id.md")`; `build_doc_graph()` and the debug helpers `doc_graph_yaml()`/`dump_views()` take the same argument. Every serialized graph is now self-identifying.
24
+ - **DocGraph wire contract v0.1 → v0.2** — `schema` is `"DocGraph/v0.2"`; `source.document` is required; the unqualified `source.sha256` field is replaced by algorithm-qualified `source.source_hash` (shared with TextRef); models are strict (unknown fields rejected, frozen, strict types) and validate node references, span/text consistency, and annotation bounds.
25
+ - **`TextUnit.words` is logical** — it matches raw counts for ordinary non-wide prose but differs for wide/fullwidth scripts, URLs, code, and symbolic content; use `TextUnit.raw_words` for the previous whitespace-split behavior. Ripples through `size()`, `size_summary()`, `section_size_tree()`, and debug reports.
26
+ - **Token estimates scale logical words** — `estimate_tokens(text, tokens_per_logical_word=1.6)` replaces the `chars_per_token` parameter, and `TOKENS_PER_LOGICAL_WORD` replaces `CHARS_PER_TOKEN`; estimates change numerically even at defaults.
27
+ - **`format_read_time()` takes logical word counts** — same signature; the default rate corresponds to roughly 450 CJK characters per minute under the default wide-character weight.
28
+ - **Pinned outputs change** — regenerate any golden DocGraph/report snapshots (schema string, `source` block, and `words` fields).
29
+
30
+ ### Full Changelog
31
+
32
+ https://github.com/jlevy/flexdoc/compare/v0.3.0...v0.4.0
@@ -0,0 +1,3 @@
1
+ {
2
+ "tag": "v0.4.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,27 +14,25 @@ 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
33
34
  shortcuts/system/skill-minimal.md: internal:shortcuts/system/skill-minimal.md
35
+ shortcuts/standard/address-pr-review.md: internal:shortcuts/standard/address-pr-review.md
34
36
  shortcuts/standard/agent-handoff.md: internal:shortcuts/standard/agent-handoff.md
35
37
  shortcuts/standard/checkout-third-party-repo.md: internal:shortcuts/standard/checkout-third-party-repo.md
36
38
  shortcuts/standard/code-cleanup-all.md: internal:shortcuts/standard/code-cleanup-all.md
@@ -50,6 +52,7 @@ docs_cache:
50
52
  shortcuts/standard/new-shortcut.md: internal:shortcuts/standard/new-shortcut.md
51
53
  shortcuts/standard/new-validation-plan.md: internal:shortcuts/standard/new-validation-plan.md
52
54
  shortcuts/standard/plan-implementation-with-beads.md: internal:shortcuts/standard/plan-implementation-with-beads.md
55
+ shortcuts/standard/pr-review-workflows.md: internal:shortcuts/standard/pr-review-workflows.md
53
56
  shortcuts/standard/precommit-process.md: internal:shortcuts/standard/precommit-process.md
54
57
  shortcuts/standard/review-code-python.md: internal:shortcuts/standard/review-code-python.md
55
58
  shortcuts/standard/review-code-typescript.md: internal:shortcuts/standard/review-code-typescript.md
@@ -58,6 +61,7 @@ docs_cache:
58
61
  shortcuts/standard/revise-all-architecture-docs.md: internal:shortcuts/standard/revise-all-architecture-docs.md
59
62
  shortcuts/standard/revise-architecture-doc.md: internal:shortcuts/standard/revise-architecture-doc.md
60
63
  shortcuts/standard/setup-github-cli.md: internal:shortcuts/standard/setup-github-cli.md
64
+ shortcuts/standard/suggest-upstream-improvements.md: internal:shortcuts/standard/suggest-upstream-improvements.md
61
65
  shortcuts/standard/sync-failure-recovery.md: internal:shortcuts/standard/sync-failure-recovery.md
62
66
  shortcuts/standard/update-specs-status.md: internal:shortcuts/standard/update-specs-status.md
63
67
  shortcuts/standard/welcome-user.md: internal:shortcuts/standard/welcome-user.md
@@ -92,3 +96,8 @@ docs_cache:
92
96
  templates/plan-spec.md: internal:templates/plan-spec.md
93
97
  templates/qa-playbook.md: internal:templates/qa-playbook.md
94
98
  templates/research-brief.md: internal:templates/research-brief.md
99
+ references/docmap-format.md: internal:references/docmap-format.md
100
+ references/docref-format.md: internal:references/docref-format.md
101
+ lookup_path:
102
+ - .tbd/docs/shortcuts/system
103
+ - .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,292 @@
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.4.0 (2026-07-20)
8
+
9
+ This is an intentional pre-1.0 API break; per the pre-1.0 rule it is a minor release,
10
+ not a 0.3.x patch. The release-gate review, validation evidence, and full
11
+ breaking-change catalog with migrations are in
12
+ `docs/project/review/senior-engineering-review-v0.4-release-2026-07.md`.
13
+
14
+ ### Added
15
+
16
+ - **Native TextRef integration.** Strict `DocRef`/`TextRef` values now identify whole
17
+ documents, spans, points, and semantic sections with canonical JSON and reversible
18
+ `textref:0.1` URIs. `FlexDoc.references()` maps every locatable public document value,
19
+ resolves references with typed outcomes, retrieves structured source context, and
20
+ renders deterministic human/LLM-readable annotation views. Span quote evidence is
21
+ optional: callers can select a context-wide size policy or override individual spans,
22
+ while compact exact-less spans remain safely bound to one source hash.
23
+ - **Consumer-owned TextRef annotations.** `TextAnnotation` and the one-document
24
+ `AnnotationSet` sidecar round-trip strict JSON and safe YAML. `DocGraph/v0.2` carries
25
+ required document identity and source hash in every graph, with optional typed
26
+ annotations in the same schema. The protocol and renderer add no dependencies.
27
+ - **Cross-language logical word metrics.** `TextUnit.words` now measures normalized
28
+ word-equivalent volume across natural language, CJK text, source code, URLs, and other
29
+ punctuation-dense content. `TextUnit.raw_words` and `raw_word_count()` preserve
30
+ literal whitespace-delimited counting, while `logical_word_count()` exposes the
31
+ dependency-free normalized primitive.
32
+
33
+ ### Changed
34
+
35
+ - **DocGraph is v0.2 and self-identifying.** `FlexDoc.graph()` and `build_doc_graph()`
36
+ now require a consumer-owned `document` locator and always return `DocGraph/v0.2`;
37
+ annotations optionally populate that same model, and the debug helpers
38
+ `doc_graph_yaml()`/`dump_views()` take the same `document` argument. In the
39
+ serialized graph, `source.document` is required and the unqualified `source.sha256`
40
+ field is replaced by `source.source_hash`, whose algorithm-qualified value is shared
41
+ with TextRef. All DocGraph models are now strict (unknown fields rejected, strict
42
+ types, frozen instances) and validate node-reference integrity, span/text
43
+ consistency, and annotation bounds.
44
+
45
+ - **`TextUnit.words` changes from raw to logical semantics.** It matches the raw count
46
+ for ordinary non-wide prose averaging 3–6 characters per word, but differs for
47
+ wide/fullwidth scripts, longer or shorter average word lengths, URLs, code, and other
48
+ symbolic content. Callers requiring the exact previous whitespace-split behavior
49
+ must use `TextUnit.raw_words`; there is no separate `TextUnit.logical_words` member.
50
+ Size summaries, section-tree defaults, and debug-report `words` fields use the logical
51
+ measure, and aggregate counts are rounded only after the full text is measured.
52
+ - **Approximate token estimates now scale logical words.** `estimate_tokens()` uses
53
+ `TOKENS_PER_LOGICAL_WORD` (default `1.6`) instead of the former
54
+ `CHARS_PER_TOKEN`/`chars_per_token` API. This is still a model-family heuristic; use
55
+ the target provider's tokenizer for exact counts or hard context limits.
56
+ - **Reading-time guidance is language-robust.** Pass logical word counts to
57
+ `format_read_time()`; the default rate corresponds to roughly 450 CJK characters per
58
+ minute under the default wide-character weight.
59
+
60
+ ## 0.3.0 (2026-07-11)
61
+
62
+ Fixes from the 2026-07 pre-promotion design review
63
+ (`docs/project/review/senior-engineering-review-flexdoc-2026-07.md`). These changes
64
+ alter documented behavior, so per the pre-1.0 rule this is a minor release, not a
65
+ 0.2.x patch.
66
+
67
+ ### Fixed
68
+
69
+ - **CRLF input no longer corrupts the structural views.** marko computes block positions
70
+ against LF-only text, so `\r` in the input desynchronized every structural span
71
+ (`blocks()`, `sections()`, `base_blocks()`, `links()`, `prose_text()`, the node table)
72
+ from `source_text`, silently garbling content and violating the base-block cover
73
+ invariant. `from_text` now normalizes `\r\n` and lone `\r` to `\n` and retains the
74
+ normalized string as `source_text`, so all layers share one offset space.
75
+ Callers anchoring offsets to an external CRLF original must normalize it the same way
76
+ first.
77
+ - **Markdown constructs inside frontmatter can no longer swallow the body.** The shared
78
+ parse previously included the frontmatter region, so e.g. a YAML block scalar
79
+ containing a code fence opened a fenced block spanning the rest of the document,
80
+ leaving `blocks()` empty.
81
+ The frontmatter region is now blanked out of the shared parse (offsets preserved);
82
+ frontmatter remains a non-content region.
83
+ Link extraction now reuses that blanked parse instead of reparsing the body for
84
+ frontmatter documents.
85
+ - **`resolve()` no longer guesses on ambiguous quotes.** Per the spec’s error posture
86
+ (§11), a `SpanRef` quote that occurs multiple times with no disambiguating
87
+ prefix/suffix (or a tied context score) now resolves to `None` instead of silently
88
+ anchoring to the first occurrence.
89
+ A context-free position hint also cannot select a duplicate, because no context or
90
+ source identity proves which occurrence was intended.
91
+ A zero-width quote (`exact=""`) also resolves to `None` on both the fast and slow
92
+ paths.
93
+ - **`collect(overlaps=...)` treats empty intervals as empty.** A degenerate `[x, x)`
94
+ region (or node span) now overlaps nothing, matching half-open interval semantics;
95
+ point queries use `(x, x + 1)`.
96
+ - **Render helpers harden their HTML output.** `render_node_attrs` attribute-escapes
97
+ `node.id`, and `wrap_with_node_attrs` validates the tag name (raising `ValueError`),
98
+ matching the validation in the `flexdoc.html` tag helpers.
99
+
100
+ ### Changed
101
+
102
+ - **The dependency lock is refreshed under the 14-day cool-off.** The cutoff is
103
+ 2026-06-26, expired per-package exceptions are removed, and the audit group resolves
104
+ fixed `pip` and `msgpack` versions, so CI runs `pip-audit` without advisory ignores.
105
+ - **`graph()` accepts any set.** `FlexDoc.graph()` and `build_doc_graph()` annotate
106
+ `include`/`detail` as `collections.abc.Set`, so plain `set` literals type-check
107
+ (matching `collect()`); behavior is unchanged.
108
+ - **`TextUnit` is a `StrEnum`**, matching every other public enum, so
109
+ `TextUnit.words == "words"` now holds.
110
+ Source-compatible for enum-member access; only `str()`/equality-with-string behavior
111
+ changes.
112
+ - **Recursive `collect()` includes inline descendants by default.** The `inline`
113
+ parameter is now tri-state: omission follows recursive traversal or an explicit
114
+ inline-kind filter, `inline=False` excludes inline nodes, and `inline=True` includes
115
+ them for any query. Callers that need the previous block-only recursive result must
116
+ pass `inline=False`.
117
+ - **Cached structural views are mutation-safe.** `Block` is now frozen, `Block.children`
118
+ and `TableInfo.alignments` are tuples, and `sections()` returns recursively isolated
119
+ section/paragraph copies.
120
+ Code constructing blocks directly must pass child tuples; code comparing or building
121
+ table metadata must use alignment tuples.
122
+ Mutation of a returned section remains local to that view and does not persist to a
123
+ later call.
124
+ - **`SpanRef` owns its public resolution API.** Call `ref.resolve(source_text)` or
125
+ `ref.resolve_and_update(source_text)` on the root-exported type.
126
+ The generic `resolve` and `resolve_and_update` names are no longer promoted from
127
+ `flexdoc.docs`; update package-level imports and calls to use the methods.
128
+ - **Paragraph heading metadata is property-based.** `Paragraph.heading_level` and
129
+ `Paragraph.heading_title` now match `Paragraph.block_type` and `Block.heading_level`.
130
+ Remove `()` from calls to the two former methods.
131
+ - **The navigable-link constant is accurately named.** Import `NAVIGABLE_LINK_FORMS`
132
+ instead of `TRUE_LINK_FORMS`; no compatibility alias is retained.
133
+ - **`flexdoc.docs` now promotes the document model only.** Word-token/search and
134
+ diff/mapping names are no longer re-exported.
135
+ Import them from `flexdoc.docs.wordtoks`, `search_tokens`, `token_diffs`, or
136
+ `token_mapping` instead.
137
+ The current Chopdiff integration already uses these owning-module paths.
138
+ - **Frontmatter delimiters tolerate trailing horizontal whitespace.** Opening and
139
+ closing `---` lines may end in spaces or tabs while remaining verbatim in
140
+ `frontmatter`; leading whitespace still disqualifies a delimiter, and an unclosed
141
+ opening remains a thematic break.
142
+ - **The OS-independent classifier is backed by macOS CI.** Ubuntu still covers every
143
+ supported Python version, and Python 3.13 now runs the full lint/test gate on
144
+ `macos-latest` as a representative second platform.
145
+ - **Local release preparation is tag-aware.** The runbook fetches tags before building
146
+ and verifies candidate wheel metadata from an isolated local tag, preventing a tagless
147
+ clone from silently producing a `0.0.1.devN` release artifact.
148
+ - **Section sizing no longer constructs temporary documents.** `FlexDoc` and `Section`
149
+ now share private paragraph aggregation for every `TextUnit` and size summary; public
150
+ results and signatures are unchanged.
151
+
152
+ Remaining pre-1.0 design decisions and future mechanisms are collected in
153
+ `docs/project/specs/active/plan-2026-07-09-flexdoc-stabilization-roadmap.md`.
154
+
155
+ ## 0.2.0 (2026-06-14)
156
+
157
+ Correctness fixes and a completed inline/heading/link surface for the document-metrics
158
+ use case (`docs/project/specs/done/plan-2026-06-13-metrics-use-case.md`, issue #6). As
159
+ a preview-stage library this takes the cleanest shape with no compatibility shims; the
160
+ API additions below include breaking signature changes (see **Changed**).
161
+
162
+ ### Fixed
163
+
164
+ - **`node_table()` / `collect()` / `graph()` no longer raise on valid Markdown.** Inline
165
+ elements were discovered over the whole source and parented by start offset, so
166
+ backtick pairing across a block boundary (an empty fence next to inline backticks)
167
+ produced an inline span escaping its parent block and raised a layer-nesting error.
168
+ Inline discovery is now scoped per leaf content block, so an inline node can never
169
+ straddle a block boundary; links/images/definitions are parented by full containment.
170
+ - **`sections()` / `toc()` recover every heading `blocks()` finds, and own their content
171
+ correctly.** Headings were re-derived from the blank-line paragraph view, dropping
172
+ tight headings and headings preceded by a non-blank line (e.g. an HTML-comment
173
+ marker), and section content was bucketed from that same view, so a heading glued to
174
+ its body lost the body.
175
+ Sections now derive entirely from the structural block tree—the heading set *and* each
176
+ section’s own content (`own_paragraphs()` / `blocks()` / sizes) come from the
177
+ section’s source region—so tight and marker-preceded headings own exactly their
178
+ content.
179
+ - **Section spans nest correctly** even when a blank-line paragraph straddles a later
180
+ heading (e.g. an embedded `---` block marko reads as a setext heading): each section
181
+ spans from its heading to the next same-or-higher heading (trimmed), which nests by
182
+ construction. Byte-identical to the prior span for well-formed documents.
183
+ - **Reference-definition nodes attach to their block.** A `link_ref_def` span included
184
+ the line’s trailing newline and escaped the containing paragraph, leaving the node
185
+ unparented so a block-scoped `collect()` missed it; spans are now trimmed like every
186
+ structural block.
187
+
188
+ ### Added
189
+
190
+ - **Heading metadata on the structural block**: `Block.heading_info` (`HeadingInfo` with
191
+ parser-authoritative `level` and `title`) and the `Block.heading_level` convenience;
192
+ `HeadingInfo` is exported from `flexdoc.docs`. The node table reads heading level from
193
+ it.
194
+ - **Typed link forms**: `LinkForm` (`inline` / `reference` / `autolink` / `bare_url` /
195
+ `image` / `reference_definition`) and `Link.link_form`. `FlexDoc.links(link_forms=…)`
196
+ selects any forms (default: navigable links only), and `FlexDoc.images()` is a
197
+ convenience for image access.
198
+ Reference definitions (`[id]: url`) are surfaced as `NodeKind.link_ref_def` nodes and
199
+ via `links(link_forms={LinkForm.reference_definition})`.
200
+ - **`FlexDoc.prose_text()`**: prose-only text for editorial linting and prose metrics —
201
+ prose blocks (paragraphs/headings, and table cells when `include_tables=True`) with
202
+ inline code and footnote refs dropped, links/images replaced by their text/alt,
203
+ inline-HTML tags dropped (wrapped text kept), and heading/blockquote/list markers and
204
+ reference-definition lines stripped; from verbatim source slices (line wrapping
205
+ preserved, never reflowed) so spacing like a spaced em-dash is kept exactly.
206
+ - **`FlexDoc.block_at_offset()`**: the innermost structural `Block` containing an offset
207
+ (the structural counterpart of `paragraph_at_offset`; the name, freed in 0.1.0, now
208
+ correctly returns a `Block`).
209
+ - **Test-suite hardening**: adversarial corpus documents (`inline_pathology`,
210
+ `heading_edges`, `link_taxonomy`); cross-projection invariants tying `toc()` to the
211
+ heading blocks, inline nesting on the query surface, and link-form accounting; and a
212
+ dogfood test that parses every Markdown file in the repo and asserts the invariants.
213
+ See the spec’s “Why These Bugs Escaped the Tests” analysis.
214
+
215
+ ### Changed
216
+
217
+ These are breaking, made cleanly (no aliases) given the preview status:
218
+
219
+ - **`Link` gains a required `link_form: LinkForm` field.** Direct `Link(...)`
220
+ construction must pass it.
221
+ - **`block_links()` returns all link-like constructs** (navigable links, images, and
222
+ reference definitions), each with a `link_form`; previously it returned navigable
223
+ links only. `FlexDoc.links()` filters to navigable links by default, so its default
224
+ result is unchanged.
225
+ - **`collect()` returns inline-kind nodes without `recursive=True`.** An inline-kind
226
+ request (e.g. `collect(kinds={NodeKind.link})`) now widens the candidate set instead
227
+ of silently returning `[]`—matching the documented behavior.
228
+
229
+ ## 0.1.0 (2026-06-12)
230
+
231
+ First release.
232
+
233
+ ### Added
234
+
235
+ - **Initial flexdoc package**, extracted from
236
+ [chopdiff](https://github.com/jlevy/chopdiff) as its own standalone distribution.
237
+ This is the document/markdown layer—`FlexDoc`, paragraphs/sentences, the block tree
238
+ and block types, sections, the node table, `collect()`, `DocGraph`, `SpanRef`, token
239
+ diffs/mappings, word tokenization, html-in-md, and read-time/token estimation—with no
240
+ dependency on chopdiff’s diff and windowed-transform machinery.
241
+
242
+ The import roots are `flexdoc.docs`, `flexdoc.html`, and `flexdoc.util`. Parse
243
+ behavior is unchanged from the `flexdoc.*` modules that previously shipped inside the
244
+ chopdiff wheel; this release packages them independently.
245
+ See `docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md`.
246
+
247
+ - **A deliberate root API**: the working set is importable from the package root —
248
+ `FlexDoc`, `DocGraph`, `Detail`, `SpanRef`, `BlockType`, `NodeKind`, `Layer`,
249
+ `TextUnit`—designed against the known downstream users and pinned by contract tests.
250
+ The render helpers for source-linked HTML (`render_node_attrs`,
251
+ `wrap_with_node_attrs`, `parse_source_span_attr`) are public in `flexdoc.docs`.
252
+
253
+ - **DocGraph paragraph view**: `Views.paragraphs` joins `toc`/`blocks`/`links`/
254
+ `sentences` in the serialized projection.
255
+
256
+ ### Changed (Relative to the Modules as Shipped in Chopdiff)
257
+
258
+ The first standalone release also refines the API surface (the pre-publish design
259
+ review, `docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md`);
260
+ these are intentional hard cuts with no compatibility aliases:
261
+
262
+ - **`TextDoc` is renamed `FlexDoc`**—the package’s single entry point, named for the
263
+ model it carries (all layered projections hang off it).
264
+ It is importable from the package root: `from flexdoc import FlexDoc`. The module is
265
+ `flexdoc.docs.flex_doc` (was `chopdiff.docs.text_doc`), and the design of record is
266
+ now `docs/flexdoc-spec.md` (was `textdoc-spec.md`).
267
+ - **`collect()` is fully keyword-only and the deprecated aliases are gone**: use
268
+ `subtree_of=` (was `scope=`, previously also positional) and `within=` (was
269
+ `contains=`).
270
+ - **Editing-view methods are named in paragraph terms**, so “block” always means the
271
+ structural layer: `FlexDoc.paragraph_at_offset` (was `block_at_offset`),
272
+ `FlexDoc.iter_paragraphs` (was `iter_blocks`), `Section.own_paragraphs`/
273
+ `subtree_paragraphs` (were `own_blocks`/`subtree_blocks`).
274
+ - **The export surface is settled**: `flexdoc.docs` now exports
275
+ `CodeInfo`/`TableInfo`/`ListInfo`, `resolve`/`resolve_and_update`,
276
+ `parse_blocks`/`walk_blocks`/`block_type_for`, and `DEFAULT_INCLUDE`; `flexdoc.html`
277
+ exports `html_p`, `html_tag`, `escape_attribute`, `tag_wrapper`, and
278
+ `identity_wrapper`. Link extraction is public as `flexdoc.docs.links.block_links`.
279
+ - **`Node.attrs` values are JSON-typed** (`AttrValue`), validated at `DocGraph`
280
+ serialization, and node-id assignment order is pinned and tested for cross-language
281
+ ports; layer nesting guarantees (`LAYER_NESTING`) are enforced at node-table build.
282
+ - Internally, the former `text_doc.py` was split into `flex_doc.py`, `paragraphs.py`,
283
+ `links.py`, and `sections.py` (package imports from `flexdoc.docs` are unaffected),
284
+ and `sections()` is now cached like the other derived views.
285
+
286
+ Migration from chopdiff in one pass: `chopdiff.docs.TextDoc` → `flexdoc.FlexDoc` (or
287
+ `flexdoc.docs.FlexDoc`), `chopdiff.docs.*` → `flexdoc.docs.*`, plus the method renames
288
+ above.
289
+
290
+ <!-- This document follows common-doc-guidelines.md.
291
+ See github.com/jlevy/practical-prose and review guidelines before editing.
292
+ -->