any2md 1.0.6__tar.gz → 1.1.1__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 (160) hide show
  1. {any2md-1.0.6 → any2md-1.1.1}/.gitignore +0 -1
  2. {any2md-1.0.6 → any2md-1.1.1}/CHANGELOG.md +172 -6
  3. {any2md-1.0.6 → any2md-1.1.1}/CONTRIBUTING.md +18 -0
  4. {any2md-1.0.6 → any2md-1.1.1}/PKG-INFO +19 -8
  5. {any2md-1.0.6 → any2md-1.1.1}/README.md +16 -7
  6. {any2md-1.0.6 → any2md-1.1.1}/SECURITY.md +22 -0
  7. any2md-1.1.1/any2md/__init__.py +11 -0
  8. {any2md-1.0.6 → any2md-1.1.1}/any2md/_docling.py +1 -1
  9. any2md-1.1.1/any2md/_docling_cache.py +436 -0
  10. any2md-1.1.1/any2md/_http.py +199 -0
  11. any2md-1.1.1/any2md/_logging.py +61 -0
  12. {any2md-1.0.6 → any2md-1.1.1}/any2md/cli.py +12 -7
  13. {any2md-1.0.6 → any2md-1.1.1}/any2md/config.py +6 -1
  14. {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/__init__.py +2 -2
  15. {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/docx.py +18 -19
  16. {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/html.py +17 -5
  17. {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/pdf.py +43 -21
  18. {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/txt.py +4 -5
  19. {any2md-1.0.6 → any2md-1.1.1}/any2md/frontmatter.py +24 -6
  20. {any2md-1.0.6 → any2md-1.1.1}/any2md/heuristics.py +9 -6
  21. {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/cleanup.py +1 -1
  22. {any2md-1.0.6 → any2md-1.1.1}/any2md/utils.py +57 -10
  23. {any2md-1.0.6 → any2md-1.1.1}/docs/architecture.md +13 -10
  24. {any2md-1.0.6 → any2md-1.1.1}/docs/cli-reference.md +1 -1
  25. {any2md-1.0.6 → any2md-1.1.1}/docs/output-format.md +19 -19
  26. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md +19 -19
  27. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase4-polish-config-docs.md +11 -11
  28. any2md-1.1.1/docs/superpowers/plans/2026-05-04-v1.1.0-persistent-converter.md +2243 -0
  29. any2md-1.1.1/docs/superpowers/plans/2026-05-06-v1.1.1-security-hardening.md +2543 -0
  30. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-any2md-v1-design.md +25 -25
  31. any2md-1.1.1/docs/superpowers/specs/2026-05-04-v1.1.0-persistent-converter-design.md +951 -0
  32. any2md-1.1.1/docs/superpowers/specs/2026-05-06-v1.1.1-security-design.md +553 -0
  33. {any2md-1.0.6 → any2md-1.1.1}/docs/troubleshooting.md +76 -0
  34. {any2md-1.0.6 → any2md-1.1.1}/docs/upgrading-from-0.7.md +9 -9
  35. {any2md-1.0.6 → any2md-1.1.1}/pyproject.toml +2 -0
  36. any2md-1.1.1/requirements.txt +81 -0
  37. any2md-1.1.1/scripts/bench_docling_cache.py +87 -0
  38. {any2md-1.0.6 → any2md-1.1.1}/scripts/validate-phase1.sh +1 -1
  39. any2md-1.1.1/tests/conftest.py +43 -0
  40. any2md-1.1.1/tests/integration/test_docling_persistence.py +105 -0
  41. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_txt.py +1 -1
  42. any2md-1.1.1/tests/unit/test_atomic_write_symlink_race.py +39 -0
  43. any2md-1.1.1/tests/unit/test_config_toml_warn.py +36 -0
  44. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_content_hash.py +1 -1
  45. any2md-1.1.1/tests/unit/test_docling_cache.py +703 -0
  46. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_frontmatter_compose.py +2 -2
  47. any2md-1.1.1/tests/unit/test_frontmatter_key_injection.py +59 -0
  48. any2md-1.1.1/tests/unit/test_heuristics_wikipedia_host.py +22 -0
  49. any2md-1.1.1/tests/unit/test_http_ip_allowlist.py +68 -0
  50. any2md-1.1.1/tests/unit/test_http_ssrf_pin.py +63 -0
  51. any2md-1.1.1/tests/unit/test_http_user_agent.py +10 -0
  52. any2md-1.1.1/tests/unit/test_lockfile_freshness.py +41 -0
  53. any2md-1.1.1/tests/unit/test_logging_module.py +85 -0
  54. any2md-1.1.1/tests/unit/test_pdf_image_symlink.py +54 -0
  55. any2md-1.1.1/tests/unit/test_security_error_strings.py +86 -0
  56. any2md-1.1.1/tests/unit/test_url_creds_scrubbed.py +76 -0
  57. any2md-1.1.1/tests/unit/test_workflow_permissions.py +44 -0
  58. any2md-1.0.6/any2md/__init__.py +0 -3
  59. any2md-1.0.6/any2md/_http.py +0 -106
  60. any2md-1.0.6/requirements.txt +0 -10
  61. any2md-1.0.6/tests/conftest.py +0 -25
  62. {any2md-1.0.6 → any2md-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  63. {any2md-1.0.6 → any2md-1.1.1}/LICENSE +0 -0
  64. {any2md-1.0.6 → any2md-1.1.1}/any2md/__main__.py +0 -0
  65. {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/__init__.py +0 -0
  66. {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/structured.py +0 -0
  67. {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/text.py +0 -0
  68. {any2md-1.0.6 → any2md-1.1.1}/any2md/validators.py +0 -0
  69. {any2md-1.0.6 → any2md-1.1.1}/assets/logo-original.jpeg +0 -0
  70. {any2md-1.0.6 → any2md-1.1.1}/assets/logo.png +0 -0
  71. {any2md-1.0.6 → any2md-1.1.1}/assets/social-preview.jpg +0 -0
  72. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase2-docling-backend.md +0 -0
  73. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase3-figures-ocr-text-lane.md +0 -0
  74. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1.0.2-implementation.md +0 -0
  75. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-issue-17-toc-followups.md +0 -0
  76. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-27-v1.0.6-security-hardening.md +0 -0
  77. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-any2md-v1.0.2-design.md +0 -0
  78. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-issue-17-toc-followups-design.md +0 -0
  79. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-v1.0.3-empty-title-orphan-punct-design.md +0 -0
  80. {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-27-v1.0.6-security-hardening-design.md +0 -0
  81. {any2md-1.0.6 → any2md-1.1.1}/scripts/audit-outputs.py +0 -0
  82. {any2md-1.0.6 → any2md-1.1.1}/tests/__init__.py +0 -0
  83. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/__init__.py +0 -0
  84. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_backend.py +0 -0
  85. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_existing_flags.py +0 -0
  86. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_exit_codes.py +0 -0
  87. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_high_fidelity.py +0 -0
  88. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_meta.py +0 -0
  89. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_no_arxiv_lookup.py +0 -0
  90. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_ocr_save_images.py +0 -0
  91. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_quiet_verbose.py +0 -0
  92. {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_strict.py +0 -0
  93. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/__init__.py +0 -0
  94. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/ligatures_and_softhyphens.txt +0 -0
  95. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/multi_column.pdf +0 -0
  96. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/table_heavy.docx +0 -0
  97. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/web_page.html +0 -0
  98. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/make_fixtures.py +0 -0
  99. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/ligatures_and_softhyphens.md +0 -0
  100. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/multi_column.docling.md +0 -0
  101. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/multi_column.fallback.md +0 -0
  102. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/table_heavy.docling.md +0 -0
  103. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/table_heavy.fallback.md +0 -0
  104. {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/web_page.md +0 -0
  105. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/__init__.py +0 -0
  106. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_backend_override.py +0 -0
  107. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_docling.py +0 -0
  108. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_fallback_on_warn.py +0 -0
  109. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_mammoth_fallback.py +0 -0
  110. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_html_trafilatura.py +0 -0
  111. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_backend_override.py +0 -0
  112. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_docling.py +0 -0
  113. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_pymupdf_fallback.py +0 -0
  114. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_save_images.py +0 -0
  115. {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_snapshots.py +0 -0
  116. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/__init__.py +0 -0
  117. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/__init__.py +0 -0
  118. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_footnote_markers.py +0 -0
  119. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_html_entities.py +0 -0
  120. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_html_entities_double_encoded.py +0 -0
  121. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_ligatures.py +0 -0
  122. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_nfc.py +0 -0
  123. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_quotes_dashes.py +0 -0
  124. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_soft_hyphens.py +0 -0
  125. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_validate.py +0 -0
  126. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_whitespace.py +0 -0
  127. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_runner.py +0 -0
  128. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_strip_orphan_punctuation.py +0 -0
  129. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_body_cleanup_stages.py +0 -0
  130. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_compact_tables.py +0 -0
  131. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_heading_hierarchy.py +0 -0
  132. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_lift_figures.py +0 -0
  133. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_normalize_cites.py +0 -0
  134. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_paragraphs.py +0 -0
  135. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_toc.py +0 -0
  136. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_toc_table.py +0 -0
  137. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dehyphenate.py +0 -0
  138. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_repair_line_wraps.py +0 -0
  139. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_restore_lists_code.py +0 -0
  140. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_cover_artifacts.py +0 -0
  141. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_headers_footers.py +0 -0
  142. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_repeated_byline.py +0 -0
  143. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_web_fragments.py +0 -0
  144. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/scripts/__init__.py +0 -0
  145. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/scripts/test_audit_outputs.py +0 -0
  146. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_atomic_write.py +0 -0
  147. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_auto_id.py +0 -0
  148. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_config.py +0 -0
  149. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_config_walkup.py +0 -0
  150. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_docling_helper.py +0 -0
  151. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_frontmatter_helpers.py +0 -0
  152. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_heuristics.py +0 -0
  153. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_http_safe.py +0 -0
  154. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_log_sanitizer.py +0 -0
  155. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_meta_overrides.py +0 -0
  156. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_meta_reserved_keys.py +0 -0
  157. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_pdf_complexity.py +0 -0
  158. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_safe_dir_name.py +0 -0
  159. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_validators.py +0 -0
  160. {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_zipbomb_guard.py +0 -0
@@ -43,4 +43,3 @@ logo/
43
43
  # ZERG / dev tooling
44
44
  .gsd/
45
45
  .zerg/
46
- .devcontainer/
@@ -4,6 +4,172 @@ 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.1] — 2026-05-DD
8
+
9
+ Security patch release. Closes 10 findings from a comprehensive scan
10
+ (bandit, snyk-code, gitleaks, pip-audit, trivy, manual review) plus
11
+ a 5-round adversarial review of the implementation plan.
12
+
13
+ ### Security
14
+
15
+ - **SSRF intra-hop DNS-rebind**: `safe_fetch` now resolves the
16
+ hostname once, validates the IP against an allowlist, and passes
17
+ the validated IP through to `socket.create_connection` via a
18
+ custom `HTTPSConnection`. Defeats attacks that return a public IP
19
+ to a validator and a private IP (e.g., `169.254.169.254`) to the
20
+ connector. `ProxyHandler({})` explicitly suppresses
21
+ `HTTP_PROXY`/`HTTPS_PROXY` env vars that would otherwise route
22
+ traffic past the validator.
23
+ - **IP allowlist**: switched from a four-flag denylist
24
+ (`is_private | is_reserved | is_loopback | is_link_local`) to an
25
+ allowlist (`is_global and not is_multicast and not is_unspecified`).
26
+ **Breaking** for users on multicast / CGNAT / IETF-reserved
27
+ networks fetching same-network resources; pre-fetch with `curl`
28
+ and pass `--from-file` as a workaround.
29
+ - **Frontmatter override-key validation**: keys must now match
30
+ `^[A-Za-z_][A-Za-z0-9_-]*$`. Defends against newline-injection in
31
+ TOML basic-string keys that produced split YAML lines.
32
+ **Breaking** for TOML configs with quoted keys containing
33
+ whitespace or punctuation.
34
+ - **URL credentials and sensitive query parameters** in `userinfo`
35
+ and matching `(?i)(token|api[_-]?key|access[_-]?token|bearer|signature|sig|password|secret|aws[_-]?session[_-]?token)$`
36
+ are scrubbed before any logging, persistence, or filename
37
+ derivation. IPv6 host brackets preserved across the scrub.
38
+ - **Docling image saves** use `O_EXCL|O_NOFOLLOW` to refuse symlink
39
+ TOCTOU.
40
+ - **`atomic_write_text`** tightens the symlink-check window via
41
+ `os.lstat` immediately before `os.replace`. Parent-dir hardening
42
+ deferred to v1.2 per the operator-trusted threat model.
43
+ - **Stderr/stdout sanitizer** extended with Unicode bidi
44
+ (U+202A–E, U+2066–9), zero-width chars (U+200B–F, U+FEFF), and
45
+ the BOM. New `any2md._logging` module; `_sanitize_log_text`
46
+ re-exported from `any2md.converters.docx` for one release.
47
+ - **`publish.yml` workflow**: top-level `permissions: contents: read`;
48
+ both publish jobs flip to `attestations: true` for PEP 740 sigstore
49
+ provenance.
50
+
51
+ ### Fixed
52
+
53
+ - `wikipedia.org` heuristic host check no longer matches
54
+ `evilwikipedia.org`. Pure correctness fix; impact was incorrect
55
+ text formatting only, not a security boundary.
56
+ - `_USER_AGENT` no longer hardcoded to `any2md/1.0.6`; derives from
57
+ `__version__`.
58
+
59
+ ### Changed
60
+
61
+ - `requirements.txt` is now a generated artifact; canonical source
62
+ is `.devcontainer/requirements.lock` with hashes
63
+ (`--require-hashes` enforced in `.devcontainer/devcontainer.json`).
64
+ See `CONTRIBUTING.md` for the dep-update workflow.
65
+ - `load_toml()` now warns to stderr on parse error rather than
66
+ silently returning `{}`.
67
+ - `.devcontainer/` is now tracked in git (was previously ignored)
68
+ so the security-critical Codespace bootstrap config is
69
+ version-controlled.
70
+
71
+ ## [1.1.0] — 2026-05-04
72
+
73
+ Performance and library-mode hygiene release.
74
+
75
+ ### Added
76
+
77
+ - `any2md.release_models()` — imperative escape hatch to free
78
+ cached Docling `DocumentConverter` instances. Stability:
79
+ experimental for v1.1.0.
80
+ - `any2md.docling_session()` — contextmanager (preferred shape
81
+ for library use). Stability: experimental for v1.1.0.
82
+ - `ANY2MD_DOCLING_CACHE=0` env-var to disable the persistent
83
+ converter cache (per-call construction, equivalent to v1.0.x
84
+ behavior).
85
+ - `__all__` declared on `any2md/__init__.py` (formalizes the
86
+ public surface).
87
+ - New benchmark `scripts/bench_docling_cache.py` for maintainer
88
+ pre-release verification.
89
+
90
+ ### Changed
91
+
92
+ - `DocumentConverter` is now constructed once per process and
93
+ reused across all files in a batch (was: once per file). New
94
+ one-time stderr line `Loading Docling models (one-time)...`
95
+ printed on first PDF/DOCX in non-quiet TTY runs.
96
+ - `.github/workflows/ci.yml` now runs `pytest tests/unit` on each
97
+ push and PR (was: lint + import smoke + pip-audit only).
98
+
99
+ ### Performance
100
+
101
+ - Measured: CPU first-call ~2.47s (model load), warm calls ~0.87s
102
+ (vs prior ~2.47s per file in v1.0.x). Mac MPS first-call ~2.77s,
103
+ warm ~0.14s. RSS floor +535 MB sustained for the process lifetime
104
+ (see `docs/troubleshooting.md` for `release_models()` mitigation).
105
+
106
+ ### Migration
107
+
108
+ - None required for CLI users. The `Loading Docling models...`
109
+ stderr line is the only user-visible default-output change.
110
+ - Library/embedder users holding any2md across long-running
111
+ processes should call `any2md.release_models()` or use
112
+ `with any2md.docling_session():` to free model state between
113
+ workloads.
114
+ - Test/instrumentation code that monkey-patches
115
+ `docling.document_converter.DocumentConverter` AFTER any2md's
116
+ first call will see no effect on subsequent calls (cache returns
117
+ the pre-patch instance). Workaround: call `release_models()`
118
+ after applying the patch, or set `ANY2MD_DOCLING_CACHE=0` in the
119
+ test environment.
120
+
121
+ ### Stability notice
122
+
123
+ The new public APIs (`release_models`, `docling_session`) are
124
+ marked experimental for v1.1.0; their shape may change in
125
+ subsequent v1.x releases without a major bump until promoted to
126
+ stable in a later release.
127
+
128
+ ### Known limitations
129
+
130
+ - HF Hub outages during cold-start now cause N retries instead of
131
+ one silent fallback (the v1.0.x failure-cache behavior was
132
+ rejected on quality grounds). Bounded retry-with-backoff
133
+ deferred to a future hardening PR.
134
+ - Forking a process (`multiprocessing` with `fork` start method)
135
+ after the first `convert()` call inherits torch/CUDA state
136
+ unsafely regardless of cache. Use
137
+ `multiprocessing.set_start_method("spawn")` or call
138
+ `release_models()` before fork.
139
+ - `os.register_at_fork` is POSIX-only; on Windows the fork-reset
140
+ path is dead code (no fork on Windows). Cache works normally
141
+ on Windows.
142
+
143
+ ## [1.0.7] — 2026-04-29
144
+
145
+ Branding/rebrand release. The frontmatter contract that any2md emits is
146
+ now described as "SAGE-compatible" (Security Analysis and Guidance
147
+ Exchange) — the upstream specification was renamed from SSRM (Structured
148
+ Security Reasoning Markdown) to SAGE. No functional or schema changes:
149
+ field names, ordering, controlled vocabularies, and `content_hash`
150
+ derivation are byte-identical to v1.0.6. Existing v1.0.6 outputs remain
151
+ valid SAGE-compatible documents without modification.
152
+
153
+ ### Changed
154
+
155
+ - README, CHANGELOG, and all of `docs/` updated to reference SAGE /
156
+ Security Analysis and Guidance Exchange.
157
+ - `--auto-id` CLI help text now reads "SAGE-conformant document_id"
158
+ (previously "SSRM-conformant"). Behavior unchanged.
159
+ - Module/function docstrings in `any2md/frontmatter.py`,
160
+ `any2md/converters/txt.py`, and `any2md/pipeline/cleanup.py` updated
161
+ to reference SAGE §3 / §5.1 (same section numbers as the upstream
162
+ SSRM v1.0-RC1 spec).
163
+ - Two test functions renamed for naming consistency
164
+ (`test_txt_end_to_end_writes_sage_compat_output`,
165
+ `test_compose_emits_required_sage_fields`). Pytest auto-discovery is
166
+ unaffected; assertions unchanged.
167
+
168
+ ### Migration
169
+
170
+ None required. Public API, CLI flags, frontmatter shape, and
171
+ `content_hash` outputs are all preserved bit-for-bit.
172
+
7
173
  ## [1.0.6] — 2026-04-27
8
174
 
9
175
  Security hardening release. Closes 8 actionable findings from the
@@ -318,7 +484,7 @@ Patch release. Adds an explicit backend-selection CLI flag.
318
484
  First stable release of any2md v1.0. Validated end-to-end against
319
485
  real-world documents (a 164-page PDF technical standard via Docling,
320
486
  a multi-page academic DOCX, and a Wikipedia article via trafilatura).
321
- The output contract — SSRM-compatible frontmatter, deterministic
487
+ The output contract — SAGE-compatible frontmatter, deterministic
322
488
  `content_hash`, NFC + LF body normalization — is now stable. Downstream
323
489
  consumers can rely on this shape.
324
490
 
@@ -340,7 +506,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
340
506
  - `--meta-file PATH` flag plus auto-discovery of `.any2md.toml` (walks up
341
507
  from cwd). The `[meta]` table supplies frontmatter overrides; the
342
508
  `[document_id]` table supplies `--auto-id` prefix and type code.
343
- - `--auto-id` flag — generates an SSRM-conformant `document_id` as
509
+ - `--auto-id` flag — generates an SAGE-conformant `document_id` as
344
510
  `{PREFIX}-{YYYY}-{TYPE}-{SHA8}`. Defaults to `LOCAL`/`DOC`; override via
345
511
  the `[document_id]` table in `.any2md.toml`.
346
512
  - `--strict` flag — promotes pipeline validation warnings (heading
@@ -359,7 +525,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
359
525
  - Comprehensive documentation set:
360
526
  - `README.md` rewritten with educational tone — every command preceded
361
527
  by why you'd run it and followed by what you'll see.
362
- - `docs/output-format.md` — SSRM-compatible field reference and the
528
+ - `docs/output-format.md` — SAGE-compatible field reference and the
363
529
  `content_hash` recomputation recipe.
364
530
  - `docs/cli-reference.md` — flag-by-flag reference with use cases and a
365
531
  worked-example matrix.
@@ -462,14 +628,14 @@ any2md transparently falls back to pymupdf4llm (PDF) and mammoth (DOCX).
462
628
 
463
629
  First prerelease of any2md v1.0. Phase 1 of 5: foundation only — no Docling
464
630
  backend yet, no new CLI flags. Output frontmatter has been rewritten to be
465
- SSRM-compatible (Structured Security Reasoning Markdown) — this is a
631
+ SAGE-compatible (Security Analysis and Guidance Exchange) — this is a
466
632
  breaking change for downstream consumers parsing v0.7 output. See
467
633
  `docs/superpowers/specs/2026-04-26-any2md-v1-design.md` for the full
468
634
  v1.0 design and `docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md`
469
635
  for this phase's task plan.
470
636
 
471
637
  ### Added
472
- - New `any2md/frontmatter.py` module — SSRM-compatible YAML emitter with the
638
+ - New `any2md/frontmatter.py` module — SAGE-compatible YAML emitter with the
473
639
  `SourceMeta` dataclass, deterministic `compute_content_hash`,
474
640
  `estimate_tokens`, `recommend_chunk_level`, `extract_abstract`, and
475
641
  `derive_title` helpers, plus the central `compose()` function (the only
@@ -495,7 +661,7 @@ for this phase's task plan.
495
661
  `reportlab`, `ruff`.
496
662
 
497
663
  ### Changed
498
- - **BREAKING:** Output frontmatter shape is now SSRM-compatible. New required
664
+ - **BREAKING:** Output frontmatter shape is now SAGE-compatible. New required
499
665
  fields: `document_id` (empty string by default — opt-in via future
500
666
  `--auto-id`), `version`, `date`, `status: "draft"`, `document_type` (empty
501
667
  for non-security documents), `content_domain` (empty array),
@@ -131,6 +131,24 @@ The flow:
131
131
  Don't tag and release directly without the PR — CI gates exist for a
132
132
  reason.
133
133
 
134
+ ## Updating dependencies
135
+
136
+ Dependencies are declared in `pyproject.toml` and locked into both:
137
+
138
+ - `.devcontainer/requirements.lock` — hash-locked, used by Codespace `postCreateCommand` (`pip install --require-hashes`).
139
+ - `requirements.txt` — non-hash form, kept for backwards compatibility with `pip install -r requirements.txt` consumers.
140
+
141
+ To add or update a dep:
142
+
143
+ 1. Edit `pyproject.toml` `[project] dependencies` (or the relevant `[project.optional-dependencies]` group).
144
+ 2. Regenerate both lockfiles in a venv with `pip-tools` installed:
145
+
146
+ pip-compile pyproject.toml --generate-hashes --resolver=backtracking \
147
+ -o .devcontainer/requirements.lock
148
+ pip-compile pyproject.toml --resolver=backtracking -o requirements.txt
149
+
150
+ 3. Commit all three files together.
151
+
134
152
  ## Pull request process
135
153
 
136
154
  - Branch off `main` (or off a phase branch like `v1.0` if one is open for
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: any2md
3
- Version: 1.0.6
3
+ Version: 1.1.1
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
@@ -20,11 +20,13 @@ Requires-Dist: markdownify<2,>=0.13.0
20
20
  Requires-Dist: pillow<13,>=12.2.0
21
21
  Requires-Dist: pymupdf4llm<1,>=0.0.17
22
22
  Requires-Dist: pymupdf<2,>=1.24.0
23
+ Requires-Dist: tomli>=2.0.0; python_version < '3.11'
23
24
  Requires-Dist: trafilatura<3,>=1.12.0
24
25
  Requires-Dist: urllib3<3,>=2.6.3
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: pytest-snapshot>=0.9.0; extra == 'dev'
27
28
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
29
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
28
30
  Requires-Dist: reportlab>=4.0.0; extra == 'dev'
29
31
  Requires-Dist: ruff>=0.6.0; extra == 'dev'
30
32
  Provides-Extra: high-fidelity
@@ -52,7 +54,7 @@ The full design rationale lives in [docs/superpowers/specs/2026-04-26-v1.0.3-emp
52
54
 
53
55
  ## What this is
54
56
 
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.
57
+ 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
58
 
57
59
  ## Quick start
58
60
 
@@ -144,8 +146,8 @@ document_id: "" # empty unless you pass --aut
144
146
  version: "1" # "1" for source files without an embedded version
145
147
  date: "2026-03-15" # docx core props "modified" → ISO-8601
146
148
  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
149
+ document_type: "" # SAGE controlled vocab — empty for non-security docs
150
+ content_domain: [] # SAGE controlled vocab — empty array when unknown
149
151
  authors: # extracted from docProps/core.xml dc:creator
150
152
  - "Rock Lambros"
151
153
  organization: "" # docProps/app.xml Company; empty when absent
@@ -166,7 +168,7 @@ produced_by: "Microsoft® Word for Microsoft 365" # software that produced the
166
168
  ---
167
169
  ```
168
170
 
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.
171
+ 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
172
 
171
173
  ### Before / after
172
174
 
@@ -360,9 +362,18 @@ any2md -f -r ./corpus/
360
362
 
361
363
  You'll see the OK lines as if the conversion were fresh; previously-written outputs are overwritten.
362
364
 
365
+ > **v1.1.0+:** when processing PDFs or DOCX files via the Docling
366
+ > backend, the converter (and its loaded model weights) is constructed
367
+ > once on the first PDF/DOCX in an invocation and reused for all
368
+ > subsequent files; subsequent files extract in ~0.5–1s rather than
369
+ > re-paying the ~2.5s cold-load tax. Library users embedding any2md
370
+ > in long-running processes should call `any2md.release_models()` or
371
+ > use `with any2md.docling_session():` — see
372
+ > `docs/troubleshooting.md`.
373
+
363
374
  ## The output format
364
375
 
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`.
376
+ 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
377
 
367
378
  ### Field auto-fill summary
368
379
 
@@ -407,7 +418,7 @@ You'll see the OK line as usual. The frontmatter of `Text/paper.md` will have `o
407
418
 
408
419
  ### `.any2md.toml`
409
420
 
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:
421
+ 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
422
 
412
423
  ```toml
413
424
  [meta]
@@ -561,7 +572,7 @@ The two-lane design exists because Docling output is layout-correct (running agg
561
572
 
562
573
  ## Migrating from v0.7
563
574
 
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).
575
+ 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
576
 
566
577
  ## Security
567
578
 
@@ -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
 
@@ -81,3 +81,25 @@ If you operate any2md in a hardened environment:
81
81
 
82
82
  No publicly disclosed vulnerabilities to date. Future advisories will
83
83
  appear at https://github.com/rocklambros/any2md/security/advisories.
84
+
85
+ ## Trust boundaries
86
+
87
+ `any2md` is an operator-trusted CLI distributed via PyPI for desktop and CI use.
88
+
89
+ **Trusted:** the operator, their filesystem, their `--output-dir`, and the Python interpreter they invoke. CLI flags and `--meta key=val` arguments are operator-controlled.
90
+
91
+ **Untrusted:**
92
+ - URLs passed to `any2md` (may resolve to internal IPs, redirect, or carry credentials in userinfo/query).
93
+ - Documents (PDF/DOCX/HTML) passed for conversion (may embed crafted images, control characters, malicious metadata).
94
+ - `.any2md.toml` config files discovered via cwd-walking (an attacker who can drop a file in any ancestor directory can inject frontmatter overrides).
95
+ - HTTP responses from URLs (may carry hostile redirects, oversized bodies, control characters in headers).
96
+
97
+ **Out of scope:** multi-tenant service hosting; running `any2md` against attacker-supplied output directories; supply-chain compromise of pinned dependencies; protection against an operator who has chosen `--output-dir` pointing at a shared/symlinked directory.
98
+
99
+ ## Dependency-drift policy
100
+
101
+ If `pip-audit` reports a NEW HIGH/CRITICAL vulnerability between baseline and release-cut, the policy is:
102
+
103
+ 1. Bump the dep within its compatible range (regenerate `.devcontainer/requirements.lock` and `requirements.txt`) and re-cut the rc.
104
+ 2. If no compatible fix exists, document in CHANGELOG `## Known issues` and ship only if the codepath is mitigated by other code (e.g., XXE blocked by `defusedxml`).
105
+ 3. If neither is possible, hold the release until upstream lands a fix.
@@ -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.1"
6
+
7
+ __all__ = [
8
+ "__version__",
9
+ "docling_session",
10
+ "release_models",
11
+ ]
@@ -33,5 +33,5 @@ def install_hint() -> None:
33
33
  global _hint_emitted
34
34
  if _hint_emitted:
35
35
  return
36
- print(f" WARN: {INSTALL_HINT_MSG}", file=sys.stderr)
36
+ print(f" WARN: {INSTALL_HINT_MSG}", file=sys.stderr) # log-safe: literal
37
37
  _hint_emitted = True