any2md 1.0.3__tar.gz → 1.0.4__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 (123) hide show
  1. {any2md-1.0.3 → any2md-1.0.4}/CHANGELOG.md +46 -0
  2. {any2md-1.0.3 → any2md-1.0.4}/PKG-INFO +1 -1
  3. {any2md-1.0.3 → any2md-1.0.4}/any2md/__init__.py +1 -1
  4. {any2md-1.0.3 → any2md-1.0.4}/any2md/pipeline/text.py +7 -2
  5. any2md-1.0.4/docs/superpowers/plans/2026-04-26-issue-17-toc-followups.md +981 -0
  6. any2md-1.0.4/docs/superpowers/specs/2026-04-26-issue-17-toc-followups-design.md +239 -0
  7. any2md-1.0.4/scripts/audit-outputs.py +202 -0
  8. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_dedupe_toc_table.py +41 -0
  9. any2md-1.0.4/tests/unit/scripts/__init__.py +0 -0
  10. any2md-1.0.4/tests/unit/scripts/test_audit_outputs.py +81 -0
  11. {any2md-1.0.3 → any2md-1.0.4}/.gitignore +0 -0
  12. {any2md-1.0.3 → any2md-1.0.4}/CODE_OF_CONDUCT.md +0 -0
  13. {any2md-1.0.3 → any2md-1.0.4}/CONTRIBUTING.md +0 -0
  14. {any2md-1.0.3 → any2md-1.0.4}/LICENSE +0 -0
  15. {any2md-1.0.3 → any2md-1.0.4}/README.md +0 -0
  16. {any2md-1.0.3 → any2md-1.0.4}/SECURITY.md +0 -0
  17. {any2md-1.0.3 → any2md-1.0.4}/any2md/__main__.py +0 -0
  18. {any2md-1.0.3 → any2md-1.0.4}/any2md/_docling.py +0 -0
  19. {any2md-1.0.3 → any2md-1.0.4}/any2md/cli.py +0 -0
  20. {any2md-1.0.3 → any2md-1.0.4}/any2md/config.py +0 -0
  21. {any2md-1.0.3 → any2md-1.0.4}/any2md/converters/__init__.py +0 -0
  22. {any2md-1.0.3 → any2md-1.0.4}/any2md/converters/docx.py +0 -0
  23. {any2md-1.0.3 → any2md-1.0.4}/any2md/converters/html.py +0 -0
  24. {any2md-1.0.3 → any2md-1.0.4}/any2md/converters/pdf.py +0 -0
  25. {any2md-1.0.3 → any2md-1.0.4}/any2md/converters/txt.py +0 -0
  26. {any2md-1.0.3 → any2md-1.0.4}/any2md/frontmatter.py +0 -0
  27. {any2md-1.0.3 → any2md-1.0.4}/any2md/heuristics.py +0 -0
  28. {any2md-1.0.3 → any2md-1.0.4}/any2md/pipeline/__init__.py +0 -0
  29. {any2md-1.0.3 → any2md-1.0.4}/any2md/pipeline/cleanup.py +0 -0
  30. {any2md-1.0.3 → any2md-1.0.4}/any2md/pipeline/structured.py +0 -0
  31. {any2md-1.0.3 → any2md-1.0.4}/any2md/utils.py +0 -0
  32. {any2md-1.0.3 → any2md-1.0.4}/any2md/validators.py +0 -0
  33. {any2md-1.0.3 → any2md-1.0.4}/assets/logo-original.jpeg +0 -0
  34. {any2md-1.0.3 → any2md-1.0.4}/assets/logo.png +0 -0
  35. {any2md-1.0.3 → any2md-1.0.4}/assets/social-preview.jpg +0 -0
  36. {any2md-1.0.3 → any2md-1.0.4}/docs/architecture.md +0 -0
  37. {any2md-1.0.3 → any2md-1.0.4}/docs/cli-reference.md +0 -0
  38. {any2md-1.0.3 → any2md-1.0.4}/docs/output-format.md +0 -0
  39. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md +0 -0
  40. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/plans/2026-04-26-any2md-v1-phase2-docling-backend.md +0 -0
  41. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/plans/2026-04-26-any2md-v1-phase3-figures-ocr-text-lane.md +0 -0
  42. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/plans/2026-04-26-any2md-v1-phase4-polish-config-docs.md +0 -0
  43. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/plans/2026-04-26-any2md-v1.0.2-implementation.md +0 -0
  44. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/specs/2026-04-26-any2md-v1-design.md +0 -0
  45. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/specs/2026-04-26-any2md-v1.0.2-design.md +0 -0
  46. {any2md-1.0.3 → any2md-1.0.4}/docs/superpowers/specs/2026-04-26-v1.0.3-empty-title-orphan-punct-design.md +0 -0
  47. {any2md-1.0.3 → any2md-1.0.4}/docs/troubleshooting.md +0 -0
  48. {any2md-1.0.3 → any2md-1.0.4}/docs/upgrading-from-0.7.md +0 -0
  49. {any2md-1.0.3 → any2md-1.0.4}/pyproject.toml +0 -0
  50. {any2md-1.0.3 → any2md-1.0.4}/requirements.txt +0 -0
  51. {any2md-1.0.3 → any2md-1.0.4}/scripts/validate-phase1.sh +0 -0
  52. {any2md-1.0.3 → any2md-1.0.4}/tests/__init__.py +0 -0
  53. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/__init__.py +0 -0
  54. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_backend.py +0 -0
  55. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_existing_flags.py +0 -0
  56. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_exit_codes.py +0 -0
  57. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_high_fidelity.py +0 -0
  58. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_meta.py +0 -0
  59. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_no_arxiv_lookup.py +0 -0
  60. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_ocr_save_images.py +0 -0
  61. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_quiet_verbose.py +0 -0
  62. {any2md-1.0.3 → any2md-1.0.4}/tests/cli/test_cli_strict.py +0 -0
  63. {any2md-1.0.3 → any2md-1.0.4}/tests/conftest.py +0 -0
  64. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/__init__.py +0 -0
  65. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/docs/ligatures_and_softhyphens.txt +0 -0
  66. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/docs/multi_column.pdf +0 -0
  67. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/docs/table_heavy.docx +0 -0
  68. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/docs/web_page.html +0 -0
  69. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/make_fixtures.py +0 -0
  70. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/ligatures_and_softhyphens.md +0 -0
  71. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/multi_column.docling.md +0 -0
  72. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/multi_column.fallback.md +0 -0
  73. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/table_heavy.docling.md +0 -0
  74. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/table_heavy.fallback.md +0 -0
  75. {any2md-1.0.3 → any2md-1.0.4}/tests/fixtures/snapshots/web_page.md +0 -0
  76. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/__init__.py +0 -0
  77. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_docx_backend_override.py +0 -0
  78. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_docx_docling.py +0 -0
  79. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_docx_mammoth_fallback.py +0 -0
  80. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_html_trafilatura.py +0 -0
  81. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_pdf_backend_override.py +0 -0
  82. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_pdf_docling.py +0 -0
  83. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_pdf_pymupdf_fallback.py +0 -0
  84. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_save_images.py +0 -0
  85. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_snapshots.py +0 -0
  86. {any2md-1.0.3 → any2md-1.0.4}/tests/integration/test_txt.py +0 -0
  87. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/__init__.py +0 -0
  88. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/__init__.py +0 -0
  89. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_footnote_markers.py +0 -0
  90. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_html_entities.py +0 -0
  91. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_html_entities_double_encoded.py +0 -0
  92. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_ligatures.py +0 -0
  93. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_nfc.py +0 -0
  94. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_quotes_dashes.py +0 -0
  95. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_soft_hyphens.py +0 -0
  96. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_validate.py +0 -0
  97. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_cleanup_whitespace.py +0 -0
  98. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_runner.py +0 -0
  99. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_strip_orphan_punctuation.py +0 -0
  100. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_structured_body_cleanup_stages.py +0 -0
  101. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_structured_compact_tables.py +0 -0
  102. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_structured_heading_hierarchy.py +0 -0
  103. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_structured_lift_figures.py +0 -0
  104. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_structured_normalize_cites.py +0 -0
  105. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_dedupe_paragraphs.py +0 -0
  106. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_dedupe_toc.py +0 -0
  107. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_dehyphenate.py +0 -0
  108. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_repair_line_wraps.py +0 -0
  109. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_restore_lists_code.py +0 -0
  110. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_strip_cover_artifacts.py +0 -0
  111. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_strip_headers_footers.py +0 -0
  112. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_strip_repeated_byline.py +0 -0
  113. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/pipeline/test_text_strip_web_fragments.py +0 -0
  114. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_auto_id.py +0 -0
  115. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_config.py +0 -0
  116. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_content_hash.py +0 -0
  117. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_docling_helper.py +0 -0
  118. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_frontmatter_compose.py +0 -0
  119. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_frontmatter_helpers.py +0 -0
  120. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_heuristics.py +0 -0
  121. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_meta_overrides.py +0 -0
  122. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_pdf_complexity.py +0 -0
  123. {any2md-1.0.3 → any2md-1.0.4}/tests/unit/test_validators.py +0 -0
@@ -4,6 +4,52 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.0.4] — 2026-04-26
8
+
9
+ Patch release. Two follow-ups deferred from v1.0.3 (#17): the T7
10
+ `dedupe_toc_table` now normalizes leader-dot padding from TOC
11
+ cells before matching against body headings, so Docling-rendered
12
+ TOC tables (e.g., `Purpose..........Page`) get properly stripped;
13
+ and the audit script's `leading-toc-table` check no longer
14
+ false-flags documents whose only GFM table sits at the end (skip
15
+ when the body contains no H2).
16
+
17
+ ### Changed
18
+ - `pipeline/text.py::dedupe_toc_table` strips leader-dot padding
19
+ (`re.sub(r'\.{3,}.*$', '', cell)`) from each TOC cell before
20
+ lower-casing and matching against body H2/H3 titles. (The
21
+ text-block TOC variant already handles this via `_TOC_LINE_RE`'s
22
+ capture group — only the table-variant site needed the fix.)
23
+ - `scripts/audit-outputs.py` `leading-toc-table` check is now
24
+ gated on `"\n## "` being present in the body — when no H2
25
+ exists, no "leading-TOC region" is defined, so the check is
26
+ skipped instead of matching the entire document.
27
+
28
+ ### Fixes
29
+ - TOC tables with dotted-page-number padding (typical Docling
30
+ output for SafeBreach-style PDFs — e.g., `Backup.pdf`,
31
+ `Password.pdf`) now get stripped by T7 in aggressive/maximum
32
+ profiles. Previously they survived because the cell content
33
+ `"1.1. Purpose............."` failed string-equality with the
34
+ body heading `"1.1. Purpose"`, dropping overlap below the 70%
35
+ threshold.
36
+ - Audit script no longer reports false-positive
37
+ `leading-toc-table` flags on documents whose only GFM table is
38
+ at the end and the body contains no `## ` headings.
39
+
40
+ ### Tests
41
+ - New `test_strips_toc_table_with_leader_dot_padding` in
42
+ `tests/unit/pipeline/test_text_dedupe_toc_table.py` — TOC
43
+ table whose cells carry leader-dot padding mirroring later H2
44
+ headings still triggers the strip.
45
+ - New `tests/unit/scripts/test_audit_outputs.py` covering the
46
+ audit-script `leading-toc-table` check: no-H2-with-trailing-
47
+ table is NOT flagged; leading-table-before-H2 IS still flagged.
48
+
49
+ ### Other
50
+ - `scripts/audit-outputs.py` is now tracked in version control
51
+ (was previously local-only).
52
+
7
53
  ## [1.0.3] — 2026-04-26
8
54
 
9
55
  Patch release. Audit on the v1.0.2-regenerated corpus surfaced two
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: any2md
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Convert PDF, DOCX, HTML, and TXT files — or web pages by URL — to clean, LLM-optimized Markdown with YAML frontmatter.
5
5
  Project-URL: Homepage, https://github.com/rocklambros/any2md
6
6
  Project-URL: Issues, https://github.com/rocklambros/any2md/issues
@@ -1,3 +1,3 @@
1
1
  """Convert PDF, DOCX, HTML, and TXT files to LLM-optimized Markdown."""
2
2
 
3
- __version__ = "1.0.3"
3
+ __version__ = "1.0.4"
@@ -199,6 +199,7 @@ def dedupe_toc_block(text: str, options: "PipelineOptions") -> str:
199
199
  return "\n".join(lines[end:]).lstrip("\n")
200
200
 
201
201
 
202
+ _LEADER_DOT_RE = re.compile(r"\.{3,}.*$")
202
203
  _TABLE_ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
203
204
  _TABLE_SEP_RE = re.compile(r"^\s*\|[\s\-:|]+\|\s*$")
204
205
 
@@ -260,12 +261,16 @@ def dedupe_toc_table(text: str, options: "PipelineOptions") -> str:
260
261
  continue
261
262
 
262
263
  # Extract title text from non-numeric cells.
264
+ # Strip leader-dot padding ('Purpose............') before matching —
265
+ # Docling renders TOC entries with dotted page-number padding and
266
+ # the body-heading equivalent does not contain the dots.
263
267
  toc_titles: set[str] = set()
264
268
  for row in entry_rows:
265
269
  cells = _split_table_cells(row)
266
270
  for cell in cells:
267
- if cell and not cell.replace(".", "").isdigit():
268
- toc_titles.add(cell.lower())
271
+ normalized = _LEADER_DOT_RE.sub("", cell).strip()
272
+ if normalized and not normalized.replace(".", "").isdigit():
273
+ toc_titles.add(normalized.lower())
269
274
  if not toc_titles:
270
275
  i = tbl_end if tbl_end > i else i + 1
271
276
  continue