any2md 1.0.6__tar.gz → 1.1.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 (142) hide show
  1. {any2md-1.0.6 → any2md-1.1.0}/CHANGELOG.md +108 -6
  2. {any2md-1.0.6 → any2md-1.1.0}/PKG-INFO +18 -8
  3. {any2md-1.0.6 → any2md-1.1.0}/README.md +16 -7
  4. any2md-1.1.0/any2md/__init__.py +11 -0
  5. any2md-1.1.0/any2md/_docling_cache.py +434 -0
  6. {any2md-1.0.6 → any2md-1.1.0}/any2md/cli.py +1 -1
  7. {any2md-1.0.6 → any2md-1.1.0}/any2md/converters/docx.py +15 -3
  8. {any2md-1.0.6 → any2md-1.1.0}/any2md/converters/pdf.py +16 -8
  9. {any2md-1.0.6 → any2md-1.1.0}/any2md/converters/txt.py +1 -1
  10. {any2md-1.0.6 → any2md-1.1.0}/any2md/frontmatter.py +6 -6
  11. {any2md-1.0.6 → any2md-1.1.0}/any2md/pipeline/cleanup.py +1 -1
  12. {any2md-1.0.6 → any2md-1.1.0}/docs/architecture.md +13 -10
  13. {any2md-1.0.6 → any2md-1.1.0}/docs/cli-reference.md +1 -1
  14. {any2md-1.0.6 → any2md-1.1.0}/docs/output-format.md +19 -19
  15. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md +19 -19
  16. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-any2md-v1-phase4-polish-config-docs.md +11 -11
  17. any2md-1.1.0/docs/superpowers/plans/2026-05-04-v1.1.0-persistent-converter.md +2243 -0
  18. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/specs/2026-04-26-any2md-v1-design.md +25 -25
  19. any2md-1.1.0/docs/superpowers/specs/2026-05-04-v1.1.0-persistent-converter-design.md +951 -0
  20. {any2md-1.0.6 → any2md-1.1.0}/docs/troubleshooting.md +76 -0
  21. {any2md-1.0.6 → any2md-1.1.0}/docs/upgrading-from-0.7.md +9 -9
  22. {any2md-1.0.6 → any2md-1.1.0}/pyproject.toml +1 -0
  23. any2md-1.1.0/scripts/bench_docling_cache.py +87 -0
  24. {any2md-1.0.6 → any2md-1.1.0}/scripts/validate-phase1.sh +1 -1
  25. any2md-1.1.0/tests/conftest.py +43 -0
  26. any2md-1.1.0/tests/integration/test_docling_persistence.py +105 -0
  27. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_txt.py +1 -1
  28. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_content_hash.py +1 -1
  29. any2md-1.1.0/tests/unit/test_docling_cache.py +703 -0
  30. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_frontmatter_compose.py +2 -2
  31. any2md-1.0.6/any2md/__init__.py +0 -3
  32. any2md-1.0.6/tests/conftest.py +0 -25
  33. {any2md-1.0.6 → any2md-1.1.0}/.gitignore +0 -0
  34. {any2md-1.0.6 → any2md-1.1.0}/CODE_OF_CONDUCT.md +0 -0
  35. {any2md-1.0.6 → any2md-1.1.0}/CONTRIBUTING.md +0 -0
  36. {any2md-1.0.6 → any2md-1.1.0}/LICENSE +0 -0
  37. {any2md-1.0.6 → any2md-1.1.0}/SECURITY.md +0 -0
  38. {any2md-1.0.6 → any2md-1.1.0}/any2md/__main__.py +0 -0
  39. {any2md-1.0.6 → any2md-1.1.0}/any2md/_docling.py +0 -0
  40. {any2md-1.0.6 → any2md-1.1.0}/any2md/_http.py +0 -0
  41. {any2md-1.0.6 → any2md-1.1.0}/any2md/config.py +0 -0
  42. {any2md-1.0.6 → any2md-1.1.0}/any2md/converters/__init__.py +0 -0
  43. {any2md-1.0.6 → any2md-1.1.0}/any2md/converters/html.py +0 -0
  44. {any2md-1.0.6 → any2md-1.1.0}/any2md/heuristics.py +0 -0
  45. {any2md-1.0.6 → any2md-1.1.0}/any2md/pipeline/__init__.py +0 -0
  46. {any2md-1.0.6 → any2md-1.1.0}/any2md/pipeline/structured.py +0 -0
  47. {any2md-1.0.6 → any2md-1.1.0}/any2md/pipeline/text.py +0 -0
  48. {any2md-1.0.6 → any2md-1.1.0}/any2md/utils.py +0 -0
  49. {any2md-1.0.6 → any2md-1.1.0}/any2md/validators.py +0 -0
  50. {any2md-1.0.6 → any2md-1.1.0}/assets/logo-original.jpeg +0 -0
  51. {any2md-1.0.6 → any2md-1.1.0}/assets/logo.png +0 -0
  52. {any2md-1.0.6 → any2md-1.1.0}/assets/social-preview.jpg +0 -0
  53. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-any2md-v1-phase2-docling-backend.md +0 -0
  54. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-any2md-v1-phase3-figures-ocr-text-lane.md +0 -0
  55. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-any2md-v1.0.2-implementation.md +0 -0
  56. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-26-issue-17-toc-followups.md +0 -0
  57. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/plans/2026-04-27-v1.0.6-security-hardening.md +0 -0
  58. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/specs/2026-04-26-any2md-v1.0.2-design.md +0 -0
  59. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/specs/2026-04-26-issue-17-toc-followups-design.md +0 -0
  60. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/specs/2026-04-26-v1.0.3-empty-title-orphan-punct-design.md +0 -0
  61. {any2md-1.0.6 → any2md-1.1.0}/docs/superpowers/specs/2026-04-27-v1.0.6-security-hardening-design.md +0 -0
  62. {any2md-1.0.6 → any2md-1.1.0}/requirements.txt +0 -0
  63. {any2md-1.0.6 → any2md-1.1.0}/scripts/audit-outputs.py +0 -0
  64. {any2md-1.0.6 → any2md-1.1.0}/tests/__init__.py +0 -0
  65. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/__init__.py +0 -0
  66. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_backend.py +0 -0
  67. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_existing_flags.py +0 -0
  68. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_exit_codes.py +0 -0
  69. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_high_fidelity.py +0 -0
  70. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_meta.py +0 -0
  71. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_no_arxiv_lookup.py +0 -0
  72. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_ocr_save_images.py +0 -0
  73. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_quiet_verbose.py +0 -0
  74. {any2md-1.0.6 → any2md-1.1.0}/tests/cli/test_cli_strict.py +0 -0
  75. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/__init__.py +0 -0
  76. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/docs/ligatures_and_softhyphens.txt +0 -0
  77. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/docs/multi_column.pdf +0 -0
  78. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/docs/table_heavy.docx +0 -0
  79. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/docs/web_page.html +0 -0
  80. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/make_fixtures.py +0 -0
  81. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/ligatures_and_softhyphens.md +0 -0
  82. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/multi_column.docling.md +0 -0
  83. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/multi_column.fallback.md +0 -0
  84. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/table_heavy.docling.md +0 -0
  85. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/table_heavy.fallback.md +0 -0
  86. {any2md-1.0.6 → any2md-1.1.0}/tests/fixtures/snapshots/web_page.md +0 -0
  87. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/__init__.py +0 -0
  88. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_docx_backend_override.py +0 -0
  89. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_docx_docling.py +0 -0
  90. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_docx_fallback_on_warn.py +0 -0
  91. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_docx_mammoth_fallback.py +0 -0
  92. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_html_trafilatura.py +0 -0
  93. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_pdf_backend_override.py +0 -0
  94. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_pdf_docling.py +0 -0
  95. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_pdf_pymupdf_fallback.py +0 -0
  96. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_save_images.py +0 -0
  97. {any2md-1.0.6 → any2md-1.1.0}/tests/integration/test_snapshots.py +0 -0
  98. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/__init__.py +0 -0
  99. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/__init__.py +0 -0
  100. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_footnote_markers.py +0 -0
  101. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_html_entities.py +0 -0
  102. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_html_entities_double_encoded.py +0 -0
  103. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_ligatures.py +0 -0
  104. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_nfc.py +0 -0
  105. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_quotes_dashes.py +0 -0
  106. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_soft_hyphens.py +0 -0
  107. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_validate.py +0 -0
  108. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_cleanup_whitespace.py +0 -0
  109. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_runner.py +0 -0
  110. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_strip_orphan_punctuation.py +0 -0
  111. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_structured_body_cleanup_stages.py +0 -0
  112. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_structured_compact_tables.py +0 -0
  113. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_structured_heading_hierarchy.py +0 -0
  114. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_structured_lift_figures.py +0 -0
  115. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_structured_normalize_cites.py +0 -0
  116. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_dedupe_paragraphs.py +0 -0
  117. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_dedupe_toc.py +0 -0
  118. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_dedupe_toc_table.py +0 -0
  119. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_dehyphenate.py +0 -0
  120. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_repair_line_wraps.py +0 -0
  121. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_restore_lists_code.py +0 -0
  122. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_strip_cover_artifacts.py +0 -0
  123. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_strip_headers_footers.py +0 -0
  124. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_strip_repeated_byline.py +0 -0
  125. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/pipeline/test_text_strip_web_fragments.py +0 -0
  126. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/scripts/__init__.py +0 -0
  127. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/scripts/test_audit_outputs.py +0 -0
  128. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_atomic_write.py +0 -0
  129. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_auto_id.py +0 -0
  130. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_config.py +0 -0
  131. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_config_walkup.py +0 -0
  132. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_docling_helper.py +0 -0
  133. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_frontmatter_helpers.py +0 -0
  134. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_heuristics.py +0 -0
  135. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_http_safe.py +0 -0
  136. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_log_sanitizer.py +0 -0
  137. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_meta_overrides.py +0 -0
  138. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_meta_reserved_keys.py +0 -0
  139. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_pdf_complexity.py +0 -0
  140. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_safe_dir_name.py +0 -0
  141. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_validators.py +0 -0
  142. {any2md-1.0.6 → any2md-1.1.0}/tests/unit/test_zipbomb_guard.py +0 -0
@@ -4,6 +4,108 @@ 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.1.0] — 2026-05-04
8
+
9
+ Performance and library-mode hygiene release.
10
+
11
+ ### Added
12
+
13
+ - `any2md.release_models()` — imperative escape hatch to free
14
+ cached Docling `DocumentConverter` instances. Stability:
15
+ experimental for v1.1.0.
16
+ - `any2md.docling_session()` — contextmanager (preferred shape
17
+ for library use). Stability: experimental for v1.1.0.
18
+ - `ANY2MD_DOCLING_CACHE=0` env-var to disable the persistent
19
+ converter cache (per-call construction, equivalent to v1.0.x
20
+ behavior).
21
+ - `__all__` declared on `any2md/__init__.py` (formalizes the
22
+ public surface).
23
+ - New benchmark `scripts/bench_docling_cache.py` for maintainer
24
+ pre-release verification.
25
+
26
+ ### Changed
27
+
28
+ - `DocumentConverter` is now constructed once per process and
29
+ reused across all files in a batch (was: once per file). New
30
+ one-time stderr line `Loading Docling models (one-time)...`
31
+ printed on first PDF/DOCX in non-quiet TTY runs.
32
+ - `.github/workflows/ci.yml` now runs `pytest tests/unit` on each
33
+ push and PR (was: lint + import smoke + pip-audit only).
34
+
35
+ ### Performance
36
+
37
+ - Measured: CPU first-call ~2.47s (model load), warm calls ~0.87s
38
+ (vs prior ~2.47s per file in v1.0.x). Mac MPS first-call ~2.77s,
39
+ warm ~0.14s. RSS floor +535 MB sustained for the process lifetime
40
+ (see `docs/troubleshooting.md` for `release_models()` mitigation).
41
+
42
+ ### Migration
43
+
44
+ - None required for CLI users. The `Loading Docling models...`
45
+ stderr line is the only user-visible default-output change.
46
+ - Library/embedder users holding any2md across long-running
47
+ processes should call `any2md.release_models()` or use
48
+ `with any2md.docling_session():` to free model state between
49
+ workloads.
50
+ - Test/instrumentation code that monkey-patches
51
+ `docling.document_converter.DocumentConverter` AFTER any2md's
52
+ first call will see no effect on subsequent calls (cache returns
53
+ the pre-patch instance). Workaround: call `release_models()`
54
+ after applying the patch, or set `ANY2MD_DOCLING_CACHE=0` in the
55
+ test environment.
56
+
57
+ ### Stability notice
58
+
59
+ The new public APIs (`release_models`, `docling_session`) are
60
+ marked experimental for v1.1.0; their shape may change in
61
+ subsequent v1.x releases without a major bump until promoted to
62
+ stable in a later release.
63
+
64
+ ### Known limitations
65
+
66
+ - HF Hub outages during cold-start now cause N retries instead of
67
+ one silent fallback (the v1.0.x failure-cache behavior was
68
+ rejected on quality grounds). Bounded retry-with-backoff
69
+ deferred to a future hardening PR.
70
+ - Forking a process (`multiprocessing` with `fork` start method)
71
+ after the first `convert()` call inherits torch/CUDA state
72
+ unsafely regardless of cache. Use
73
+ `multiprocessing.set_start_method("spawn")` or call
74
+ `release_models()` before fork.
75
+ - `os.register_at_fork` is POSIX-only; on Windows the fork-reset
76
+ path is dead code (no fork on Windows). Cache works normally
77
+ on Windows.
78
+
79
+ ## [1.0.7] — 2026-04-29
80
+
81
+ Branding/rebrand release. The frontmatter contract that any2md emits is
82
+ now described as "SAGE-compatible" (Security Analysis and Guidance
83
+ Exchange) — the upstream specification was renamed from SSRM (Structured
84
+ Security Reasoning Markdown) to SAGE. No functional or schema changes:
85
+ field names, ordering, controlled vocabularies, and `content_hash`
86
+ derivation are byte-identical to v1.0.6. Existing v1.0.6 outputs remain
87
+ valid SAGE-compatible documents without modification.
88
+
89
+ ### Changed
90
+
91
+ - README, CHANGELOG, and all of `docs/` updated to reference SAGE /
92
+ Security Analysis and Guidance Exchange.
93
+ - `--auto-id` CLI help text now reads "SAGE-conformant document_id"
94
+ (previously "SSRM-conformant"). Behavior unchanged.
95
+ - Module/function docstrings in `any2md/frontmatter.py`,
96
+ `any2md/converters/txt.py`, and `any2md/pipeline/cleanup.py` updated
97
+ to reference SAGE §3 / §5.1 (same section numbers as the upstream
98
+ SSRM v1.0-RC1 spec).
99
+ - Two test functions renamed for naming consistency
100
+ (`test_txt_end_to_end_writes_sage_compat_output`,
101
+ `test_compose_emits_required_sage_fields`). Pytest auto-discovery is
102
+ unaffected; assertions unchanged.
103
+
104
+ ### Migration
105
+
106
+ None required. Public API, CLI flags, frontmatter shape, and
107
+ `content_hash` outputs are all preserved bit-for-bit.
108
+
7
109
  ## [1.0.6] — 2026-04-27
8
110
 
9
111
  Security hardening release. Closes 8 actionable findings from the
@@ -318,7 +420,7 @@ Patch release. Adds an explicit backend-selection CLI flag.
318
420
  First stable release of any2md v1.0. Validated end-to-end against
319
421
  real-world documents (a 164-page PDF technical standard via Docling,
320
422
  a multi-page academic DOCX, and a Wikipedia article via trafilatura).
321
- The output contract — SSRM-compatible frontmatter, deterministic
423
+ The output contract — SAGE-compatible frontmatter, deterministic
322
424
  `content_hash`, NFC + LF body normalization — is now stable. Downstream
323
425
  consumers can rely on this shape.
324
426
 
@@ -340,7 +442,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
340
442
  - `--meta-file PATH` flag plus auto-discovery of `.any2md.toml` (walks up
341
443
  from cwd). The `[meta]` table supplies frontmatter overrides; the
342
444
  `[document_id]` table supplies `--auto-id` prefix and type code.
343
- - `--auto-id` flag — generates an SSRM-conformant `document_id` as
445
+ - `--auto-id` flag — generates an SAGE-conformant `document_id` as
344
446
  `{PREFIX}-{YYYY}-{TYPE}-{SHA8}`. Defaults to `LOCAL`/`DOC`; override via
345
447
  the `[document_id]` table in `.any2md.toml`.
346
448
  - `--strict` flag — promotes pipeline validation warnings (heading
@@ -359,7 +461,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
359
461
  - Comprehensive documentation set:
360
462
  - `README.md` rewritten with educational tone — every command preceded
361
463
  by why you'd run it and followed by what you'll see.
362
- - `docs/output-format.md` — SSRM-compatible field reference and the
464
+ - `docs/output-format.md` — SAGE-compatible field reference and the
363
465
  `content_hash` recomputation recipe.
364
466
  - `docs/cli-reference.md` — flag-by-flag reference with use cases and a
365
467
  worked-example matrix.
@@ -462,14 +564,14 @@ any2md transparently falls back to pymupdf4llm (PDF) and mammoth (DOCX).
462
564
 
463
565
  First prerelease of any2md v1.0. Phase 1 of 5: foundation only — no Docling
464
566
  backend yet, no new CLI flags. Output frontmatter has been rewritten to be
465
- SSRM-compatible (Structured Security Reasoning Markdown) — this is a
567
+ SAGE-compatible (Security Analysis and Guidance Exchange) — this is a
466
568
  breaking change for downstream consumers parsing v0.7 output. See
467
569
  `docs/superpowers/specs/2026-04-26-any2md-v1-design.md` for the full
468
570
  v1.0 design and `docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md`
469
571
  for this phase's task plan.
470
572
 
471
573
  ### Added
472
- - New `any2md/frontmatter.py` module — SSRM-compatible YAML emitter with the
574
+ - New `any2md/frontmatter.py` module — SAGE-compatible YAML emitter with the
473
575
  `SourceMeta` dataclass, deterministic `compute_content_hash`,
474
576
  `estimate_tokens`, `recommend_chunk_level`, `extract_abstract`, and
475
577
  `derive_title` helpers, plus the central `compose()` function (the only
@@ -495,7 +597,7 @@ for this phase's task plan.
495
597
  `reportlab`, `ruff`.
496
598
 
497
599
  ### Changed
498
- - **BREAKING:** Output frontmatter shape is now SSRM-compatible. New required
600
+ - **BREAKING:** Output frontmatter shape is now SAGE-compatible. New required
499
601
  fields: `document_id` (empty string by default — opt-in via future
500
602
  `--auto-id`), `version`, `date`, `status: "draft"`, `document_type` (empty
501
603
  for non-security documents), `content_domain` (empty array),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: any2md
3
- Version: 1.0.6
3
+ Version: 1.1.0
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
@@ -25,6 +25,7 @@ Requires-Dist: urllib3<3,>=2.6.3
25
25
  Provides-Extra: dev
26
26
  Requires-Dist: pytest-snapshot>=0.9.0; extra == 'dev'
27
27
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
28
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
28
29
  Requires-Dist: reportlab>=4.0.0; extra == 'dev'
29
30
  Requires-Dist: ruff>=0.6.0; extra == 'dev'
30
31
  Provides-Extra: high-fidelity
@@ -52,7 +53,7 @@ The full design rationale lives in [docs/superpowers/specs/2026-04-26-v1.0.3-emp
52
53
 
53
54
  ## What this is
54
55
 
55
- any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SSRM-compatible (Structured Security Reasoning Markdown — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
56
+ any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SAGE-compatible (Security Analysis and Guidance Exchange — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
56
57
 
57
58
  ## Quick start
58
59
 
@@ -144,8 +145,8 @@ document_id: "" # empty unless you pass --aut
144
145
  version: "1" # "1" for source files without an embedded version
145
146
  date: "2026-03-15" # docx core props "modified" → ISO-8601
146
147
  status: "draft" # always "draft" for converted documents
147
- document_type: "" # SSRM controlled vocab — empty for non-security docs
148
- content_domain: [] # SSRM controlled vocab — empty array when unknown
148
+ document_type: "" # SAGE controlled vocab — empty for non-security docs
149
+ content_domain: [] # SAGE controlled vocab — empty array when unknown
149
150
  authors: # extracted from docProps/core.xml dc:creator
150
151
  - "Rock Lambros"
151
152
  organization: "" # docProps/app.xml Company; empty when absent
@@ -166,7 +167,7 @@ produced_by: "Microsoft® Word for Microsoft 365" # software that produced the
166
167
  ---
167
168
  ```
168
169
 
169
- Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SSRM contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
170
+ Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SAGE contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
170
171
 
171
172
  ### Before / after
172
173
 
@@ -360,9 +361,18 @@ any2md -f -r ./corpus/
360
361
 
361
362
  You'll see the OK lines as if the conversion were fresh; previously-written outputs are overwritten.
362
363
 
364
+ > **v1.1.0+:** when processing PDFs or DOCX files via the Docling
365
+ > backend, the converter (and its loaded model weights) is constructed
366
+ > once on the first PDF/DOCX in an invocation and reused for all
367
+ > subsequent files; subsequent files extract in ~0.5–1s rather than
368
+ > re-paying the ~2.5s cold-load tax. Library users embedding any2md
369
+ > in long-running processes should call `any2md.release_models()` or
370
+ > use `with any2md.docling_session():` — see
371
+ > `docs/troubleshooting.md`.
372
+
363
373
  ## The output format
364
374
 
365
- Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SSRM-compatible** — it matches the field names, types, and ordering of the Structured Security Reasoning Markdown specification, but values aren't required to match SSRM's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
375
+ Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SAGE-compatible** — it matches the field names, types, and ordering of the Security Analysis and Guidance Exchange specification, but values aren't required to match SAGE's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
366
376
 
367
377
  ### Field auto-fill summary
368
378
 
@@ -407,7 +417,7 @@ You'll see the OK line as usual. The frontmatter of `Text/paper.md` will have `o
407
417
 
408
418
  ### `.any2md.toml`
409
419
 
410
- You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SSRM-conforming outputs:
420
+ You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SAGE-conforming outputs:
411
421
 
412
422
  ```toml
413
423
  [meta]
@@ -561,7 +571,7 @@ The two-lane design exists because Docling output is layout-correct (running agg
561
571
 
562
572
  ## Migrating from v0.7
563
573
 
564
- v1.0 emits SSRM-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
574
+ v1.0 emits SAGE-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
565
575
 
566
576
  ## Security
567
577
 
@@ -19,7 +19,7 @@ The full design rationale lives in [docs/superpowers/specs/2026-04-26-v1.0.3-emp
19
19
 
20
20
  ## What this is
21
21
 
22
- any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SSRM-compatible (Structured Security Reasoning Markdown — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
22
+ any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SAGE-compatible (Security Analysis and Guidance Exchange — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
23
23
 
24
24
  ## Quick start
25
25
 
@@ -111,8 +111,8 @@ document_id: "" # empty unless you pass --aut
111
111
  version: "1" # "1" for source files without an embedded version
112
112
  date: "2026-03-15" # docx core props "modified" → ISO-8601
113
113
  status: "draft" # always "draft" for converted documents
114
- document_type: "" # SSRM controlled vocab — empty for non-security docs
115
- content_domain: [] # SSRM controlled vocab — empty array when unknown
114
+ document_type: "" # SAGE controlled vocab — empty for non-security docs
115
+ content_domain: [] # SAGE controlled vocab — empty array when unknown
116
116
  authors: # extracted from docProps/core.xml dc:creator
117
117
  - "Rock Lambros"
118
118
  organization: "" # docProps/app.xml Company; empty when absent
@@ -133,7 +133,7 @@ produced_by: "Microsoft® Word for Microsoft 365" # software that produced the
133
133
  ---
134
134
  ```
135
135
 
136
- Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SSRM contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
136
+ Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SAGE contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
137
137
 
138
138
  ### Before / after
139
139
 
@@ -327,9 +327,18 @@ any2md -f -r ./corpus/
327
327
 
328
328
  You'll see the OK lines as if the conversion were fresh; previously-written outputs are overwritten.
329
329
 
330
+ > **v1.1.0+:** when processing PDFs or DOCX files via the Docling
331
+ > backend, the converter (and its loaded model weights) is constructed
332
+ > once on the first PDF/DOCX in an invocation and reused for all
333
+ > subsequent files; subsequent files extract in ~0.5–1s rather than
334
+ > re-paying the ~2.5s cold-load tax. Library users embedding any2md
335
+ > in long-running processes should call `any2md.release_models()` or
336
+ > use `with any2md.docling_session():` — see
337
+ > `docs/troubleshooting.md`.
338
+
330
339
  ## The output format
331
340
 
332
- Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SSRM-compatible** — it matches the field names, types, and ordering of the Structured Security Reasoning Markdown specification, but values aren't required to match SSRM's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
341
+ Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SAGE-compatible** — it matches the field names, types, and ordering of the Security Analysis and Guidance Exchange specification, but values aren't required to match SAGE's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
333
342
 
334
343
  ### Field auto-fill summary
335
344
 
@@ -374,7 +383,7 @@ You'll see the OK line as usual. The frontmatter of `Text/paper.md` will have `o
374
383
 
375
384
  ### `.any2md.toml`
376
385
 
377
- You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SSRM-conforming outputs:
386
+ You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SAGE-conforming outputs:
378
387
 
379
388
  ```toml
380
389
  [meta]
@@ -528,7 +537,7 @@ The two-lane design exists because Docling output is layout-correct (running agg
528
537
 
529
538
  ## Migrating from v0.7
530
539
 
531
- v1.0 emits SSRM-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
540
+ v1.0 emits SAGE-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
532
541
 
533
542
  ## Security
534
543
 
@@ -0,0 +1,11 @@
1
+ """any2md — convert documents to LLM-friendly Markdown."""
2
+
3
+ from any2md._docling_cache import docling_session, release_models
4
+
5
+ __version__ = "1.1.0"
6
+
7
+ __all__ = [
8
+ "__version__",
9
+ "docling_session",
10
+ "release_models",
11
+ ]