sciwrite-lint 0.4.0__tar.gz → 0.5.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 (163) hide show
  1. {sciwrite_lint-0.4.0/sciwrite_lint.egg-info → sciwrite_lint-0.5.1}/PKG-INFO +34 -7
  2. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/README.md +33 -6
  3. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/pyproject.toml +3 -1
  4. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/__init__.py +1 -1
  5. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/__main__.py +52 -56
  6. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/parse.py +1 -1
  7. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/verify.py +12 -5
  8. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/eval_claims.py +107 -89
  9. sciwrite_lint-0.5.1/sciwrite_lint/llm/manuscript_cache.py +173 -0
  10. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/manuscript_store.py +6 -1
  11. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pdf/grobid.py +24 -8
  12. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/checks.py +85 -5
  13. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/claims.py +0 -1
  14. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/__init__.py +13 -0
  15. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/_core.py +43 -5
  16. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/claim_results.py +8 -3
  17. sciwrite_lint-0.5.1/sciwrite_lint/references/workspace_db/manuscript_check_cache.py +103 -0
  18. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/tex_parser.py +74 -0
  19. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1/sciwrite_lint.egg-info}/PKG-INFO +34 -7
  20. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint.egg-info/SOURCES.txt +2 -1
  21. sciwrite_lint-0.4.0/sciwrite_lint/claude_backend.py +0 -94
  22. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/LICENSE +0 -0
  23. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/_network.py +0 -0
  24. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/api.py +0 -0
  25. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/api_keys.py +0 -0
  26. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/__init__.py +0 -0
  27. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/_diagnostics.py +0 -0
  28. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/_section_utils.py +0 -0
  29. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/cite_purpose.py +0 -0
  30. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/claim_support.py +0 -0
  31. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/cross_section_consistency.py +0 -0
  32. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/dangling_cite.py +0 -0
  33. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/dangling_ref.py +0 -0
  34. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/full_paper_consistency.py +0 -0
  35. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/internal_consistency_pairs.py +0 -0
  36. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/prose_quality.py +0 -0
  37. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/ref_internal_checks.py +0 -0
  38. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/reference_accuracy.py +0 -0
  39. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/reference_exists.py +0 -0
  40. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/reference_unreliable.py +0 -0
  41. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/registry.py +0 -0
  42. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/retracted_cite.py +0 -0
  43. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/structure_promises.py +0 -0
  44. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/checks/unreferenced_figure.py +0 -0
  45. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/claims.py +0 -0
  46. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/claude_cli.py +0 -0
  47. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/__init__.py +0 -0
  48. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/_common.py +0 -0
  49. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/check.py +0 -0
  50. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/config.py +0 -0
  51. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/fetch.py +0 -0
  52. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/__init__.py +0 -0
  53. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/_monitor.py +0 -0
  54. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/containers.py +0 -0
  55. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/dismiss_claim.py +0 -0
  56. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/grobid.py +0 -0
  57. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/init.py +0 -0
  58. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/override.py +0 -0
  59. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/vision.py +0 -0
  60. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/misc/vllm.py +0 -0
  61. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cli/rank.py +0 -0
  62. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/config.py +0 -0
  63. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/cross_paper.py +0 -0
  64. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/exceptions.py +0 -0
  65. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/footnote_urls.py +0 -0
  66. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/__init__.py +0 -0
  67. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/_common.py +0 -0
  68. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/_download.py +0 -0
  69. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/_orchestrator.py +0 -0
  70. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/_search.py +0 -0
  71. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/_validation.py +0 -0
  72. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/arxiv.py +0 -0
  73. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/biorxiv.py +0 -0
  74. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/core.py +0 -0
  75. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/eric.py +0 -0
  76. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/europepmc.py +0 -0
  77. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/hal.py +0 -0
  78. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/ideas.py +0 -0
  79. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/nasa_ads.py +0 -0
  80. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/nber.py +0 -0
  81. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/osf.py +0 -0
  82. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/pmc.py +0 -0
  83. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/s2.py +0 -0
  84. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/fulltext/unpaywall.py +0 -0
  85. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/latex_to_markdown.py +0 -0
  86. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/__init__.py +0 -0
  87. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/__init__.py +0 -0
  88. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/compute_cap.py +0 -0
  89. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/controller.py +0 -0
  90. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/decide.py +0 -0
  91. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/host_metrics.py +0 -0
  92. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/metrics_probe.py +0 -0
  93. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/registry.py +0 -0
  94. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/telemetry.py +0 -0
  95. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/vllm_metrics.py +0 -0
  96. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm/concurrency_optimizer/wiring.py +0 -0
  97. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/llm_utils.py +0 -0
  98. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/local_sources.py +0 -0
  99. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/mhtml.py +0 -0
  100. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/models.py +0 -0
  101. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/oa/__init__.py +0 -0
  102. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/oa/_models.py +0 -0
  103. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/oa/_pdf.py +0 -0
  104. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/oa/_search.py +0 -0
  105. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/oa/_web.py +0 -0
  106. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pdf/__init__.py +0 -0
  107. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pdf/pdf_download.py +0 -0
  108. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/__init__.py +0 -0
  109. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/embeddings.py +0 -0
  110. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/fetch.py +0 -0
  111. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/orchestration.py +0 -0
  112. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/parse.py +0 -0
  113. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/pdf_context.py +0 -0
  114. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/ref_internal.py +0 -0
  115. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/runners.py +0 -0
  116. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/staged.py +0 -0
  117. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/swap.py +0 -0
  118. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/tracking.py +0 -0
  119. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/unreliable.py +0 -0
  120. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/verify.py +0 -0
  121. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/pipeline/vision.py +0 -0
  122. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/prompt_safety.py +0 -0
  123. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/py.typed +0 -0
  124. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/rate_limiter.py +0 -0
  125. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/__init__.py +0 -0
  126. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/_embed_timing.py +0 -0
  127. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/chain.py +0 -0
  128. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/citations.py +0 -0
  129. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/crossref.py +0 -0
  130. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/embedding_store.py +0 -0
  131. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/matching.py +0 -0
  132. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/metadata.py +0 -0
  133. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/reference_store.py +0 -0
  134. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/retraction_watch.py +0 -0
  135. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/bib_checks.py +0 -0
  136. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/citation_metadata.py +0 -0
  137. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/manuscript_citations.py +0 -0
  138. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/parse_cache.py +0 -0
  139. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/pipeline_stage.py +0 -0
  140. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/query_vectors.py +0 -0
  141. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/ref_internal.py +0 -0
  142. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/registry.py +0 -0
  143. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/references/workspace_db/vision_cache.py +0 -0
  144. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/report.py +0 -0
  145. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/schemas.py +0 -0
  146. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/scoring/__init__.py +0 -0
  147. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/scoring/contribution.py +0 -0
  148. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/scoring/scilint_score.py +0 -0
  149. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/usage.py +0 -0
  150. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vision/__init__.py +0 -0
  151. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vision/cache.py +0 -0
  152. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vision/describe.py +0 -0
  153. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vision/image_extraction.py +0 -0
  154. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vision/pipeline.py +0 -0
  155. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vllm/__init__.py +0 -0
  156. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vllm/metrics.py +0 -0
  157. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/vllm/vllm_server.py +0 -0
  158. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint/web.py +0 -0
  159. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint.egg-info/dependency_links.txt +0 -0
  160. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint.egg-info/entry_points.txt +0 -0
  161. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint.egg-info/requires.txt +0 -0
  162. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/sciwrite_lint.egg-info/top_level.txt +0 -0
  163. {sciwrite_lint-0.4.0 → sciwrite_lint-0.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sciwrite-lint
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: A linter for scientific manuscripts — reference verification, consistency checking, and structural validation.
5
5
  Author: Sergey Samsonau
6
6
  Maintainer-email: Authentic Research Partners LLC <info@arpconnect.com>
@@ -147,7 +147,7 @@ sciwrite-lint contributions paper.pdf --format json
147
147
 
148
148
  ### Optimizations
149
149
 
150
- Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
150
+ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching are baseline; every cache layer pins its inputs by hash so editing the paper, replacing a figure, or refreshing a reference recomputes only what's affected (see the *Iterative editing* subsection under Usage — `--fresh` is not part of the normal loop). On top of that:
151
151
 
152
152
  - **Cost-aware verify-claim ladder** — sentence chunk → paragraph chunk → whole section. Each level fans out top-N candidates in parallel; stops on a conclusive verdict. Most claims resolve at the cheap sentence level (~200-token prompts) instead of paying for whole sections (~5K tokens), with full-section escalation reserved for hard cases
153
153
  - **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
@@ -158,11 +158,11 @@ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-ba
158
158
  - **14-source full-text cascade** — early exit on first successful download across arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv, NBER, RePEc/IDEAS, HAL, ERIC, NASA ADS, OSF Preprints, CORE
159
159
  - **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
160
160
 
161
- Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU) — same code path on WSL2 and native Linux.
161
+ The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU).
162
162
 
163
163
  ## Install
164
164
 
165
- **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)). Not tested on macOS.
165
+ **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux uses the same GPU-sequencing code path with no extra configuration (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)), though it isn't actively tested. Not tested on macOS.
166
166
 
167
167
  Requires [uv](https://docs.astral.sh/uv/getting-started/installation/), a container runtime (podman or docker), CUDA drivers, and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
168
168
 
@@ -207,6 +207,8 @@ sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM
207
207
  sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
208
208
  ```
209
209
 
210
+ `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
211
+
210
212
  ### WSL2: disable CUDA Sysmem Fallback (recommended)
211
213
 
212
214
  By default Windows silently spills GPU overflow into system RAM via PCIe (30–100× slower than VRAM); you'll see this as a sudden throughput collapse during the heaviest pipeline stages with no error in the logs. Tell the NVIDIA driver to fail loudly instead:
@@ -217,8 +219,6 @@ Driver 546.01+. After this, WSL2 behaves like native Linux on overflow (CUDA OOM
217
219
 
218
220
  ![Monitor dashboard](https://github.com/authentic-research-partners/sciwrite-lint/raw/main/docs/monitor.png)
219
221
 
220
- `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
221
-
222
222
  **Providing references manually — two drop folders.** sciwrite-lint reads two local directories before going to the open-access waterfall, kept separate because the folder itself signals how much to trust the source:
223
223
 
224
224
  - `local_pdfs/` — **academic sources**. Accepts `.pdf` (primary) and `.md` summaries you've written yourself. Use this for paywalled papers, for OA papers whose publisher requires a browser (Cloudflare/JS walls, Cell, Springer, some PMC pages), and for anything you'd cite as peer-reviewed evidence.
@@ -280,9 +280,36 @@ sciwrite-lint contributions --paper my_paper # add contribution axes to SciLi
280
280
  sciwrite-lint contributions paper.pdf # standalone file scoring
281
281
  ```
282
282
 
283
+ **Supported inputs.**
284
+
285
+ *The manuscript you check:*
286
+
287
+ | Format | Notes |
288
+ |---|---|
289
+ | `.tex` | LaTeX — hand-written, or pandoc-generated (e.g. from Markdown); pandoc's heading anchors are unwrapped automatically. |
290
+ | `.pdf` | Parsed via GROBID (container required). |
291
+
292
+ *Cited sources you optionally supply (drop folders — see [Setup](#setup)):*
293
+
294
+ | Folder | Accepts | Trust level |
295
+ |---|---|---|
296
+ | `local_pdfs/` | `.pdf`, `.md` (your own summaries) | academic |
297
+ | `local_web/` | `.mhtml` / `.mht`, `.md` | web capture |
298
+
283
299
  `check` runs the full pipeline in one command: text checks → figure analysis → LLM consistency → reference verification via APIs → download and parse cited papers → claim verification → consistency checks on cited papers → bibliography verification → aggregate reliability scores → SciLint Score. An initial run on a 50-reference paper takes up to 30 minutes (dominated by downloads and claim verification); subsequent cached runs complete in minutes.
284
300
 
285
- Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
301
+ ### Iterative editing no `--fresh` needed
302
+
303
+ `sciwrite-lint check` invalidates cached results whenever the inputs they were derived from change, so rerunning after an edit just works. Concretely:
304
+
305
+ - **Edit a sentence or paragraph** → the manuscript-LLM checks (prose-quality, cross-section consistency, etc.) re-run only on the touched sentences and their paragraph siblings; untouched prose stays cached
306
+ - **Edit a paragraph around a citation** → the claim verifier reruns on the edited prose
307
+ - **Replace a figure** (overwrite the PNG/PDF in your tree) → the vision cache re-describes it
308
+ - **Swap the local LLM model** (e.g. `qwen3` ↔ `gemma3` via `--model`) → manuscript-check rows and claim verdicts no longer count as cache hits
309
+ - **Overwrite a reference PDF** in your drop folder, or **re-fetch** an open-access PDF that has new bytes → the parser, embeddings, and claim verifier all rerun against the new content
310
+ - **Upgrade a summary `.md` to a real PDF** in your drop folder → claims are re-verified against the richer evidence
311
+
312
+ Use `--fresh` only when you want to wipe the workspace deliberately (it backs up the existing workspace before recreating it). It is not the answer to "I edited the paper and a finding looks stale" — if you ever see that, file an issue; a cache invariant is wrong and we'll fix it.
286
313
 
287
314
  Use `--checks ID[,ID...]` to run only the listed checks (comma-separated), disabling the rest for this invocation. Useful for fast iteration during editing (`--checks prose-quality` runs just the prose review), for debugging a single check, or for scripting a two-pass workflow. Unknown check IDs fail loudly — run `sciwrite-lint checks` to list available IDs.
288
315
 
@@ -81,7 +81,7 @@ sciwrite-lint contributions paper.pdf --format json
81
81
 
82
82
  ### Optimizations
83
83
 
84
- Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
84
+ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching are baseline; every cache layer pins its inputs by hash so editing the paper, replacing a figure, or refreshing a reference recomputes only what's affected (see the *Iterative editing* subsection under Usage — `--fresh` is not part of the normal loop). On top of that:
85
85
 
86
86
  - **Cost-aware verify-claim ladder** — sentence chunk → paragraph chunk → whole section. Each level fans out top-N candidates in parallel; stops on a conclusive verdict. Most claims resolve at the cheap sentence level (~200-token prompts) instead of paying for whole sections (~5K tokens), with full-section escalation reserved for hard cases
87
87
  - **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
@@ -92,11 +92,11 @@ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-ba
92
92
  - **14-source full-text cascade** — early exit on first successful download across arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv, NBER, RePEc/IDEAS, HAL, ERIC, NASA ADS, OSF Preprints, CORE
93
93
  - **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
94
94
 
95
- Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU) — same code path on WSL2 and native Linux.
95
+ The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU).
96
96
 
97
97
  ## Install
98
98
 
99
- **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)). Not tested on macOS.
99
+ **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux uses the same GPU-sequencing code path with no extra configuration (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)), though it isn't actively tested. Not tested on macOS.
100
100
 
101
101
  Requires [uv](https://docs.astral.sh/uv/getting-started/installation/), a container runtime (podman or docker), CUDA drivers, and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
102
102
 
@@ -141,6 +141,8 @@ sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM
141
141
  sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
142
142
  ```
143
143
 
144
+ `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
145
+
144
146
  ### WSL2: disable CUDA Sysmem Fallback (recommended)
145
147
 
146
148
  By default Windows silently spills GPU overflow into system RAM via PCIe (30–100× slower than VRAM); you'll see this as a sudden throughput collapse during the heaviest pipeline stages with no error in the logs. Tell the NVIDIA driver to fail loudly instead:
@@ -151,8 +153,6 @@ Driver 546.01+. After this, WSL2 behaves like native Linux on overflow (CUDA OOM
151
153
 
152
154
  ![Monitor dashboard](https://github.com/authentic-research-partners/sciwrite-lint/raw/main/docs/monitor.png)
153
155
 
154
- `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
155
-
156
156
  **Providing references manually — two drop folders.** sciwrite-lint reads two local directories before going to the open-access waterfall, kept separate because the folder itself signals how much to trust the source:
157
157
 
158
158
  - `local_pdfs/` — **academic sources**. Accepts `.pdf` (primary) and `.md` summaries you've written yourself. Use this for paywalled papers, for OA papers whose publisher requires a browser (Cloudflare/JS walls, Cell, Springer, some PMC pages), and for anything you'd cite as peer-reviewed evidence.
@@ -214,9 +214,36 @@ sciwrite-lint contributions --paper my_paper # add contribution axes to SciLi
214
214
  sciwrite-lint contributions paper.pdf # standalone file scoring
215
215
  ```
216
216
 
217
+ **Supported inputs.**
218
+
219
+ *The manuscript you check:*
220
+
221
+ | Format | Notes |
222
+ |---|---|
223
+ | `.tex` | LaTeX — hand-written, or pandoc-generated (e.g. from Markdown); pandoc's heading anchors are unwrapped automatically. |
224
+ | `.pdf` | Parsed via GROBID (container required). |
225
+
226
+ *Cited sources you optionally supply (drop folders — see [Setup](#setup)):*
227
+
228
+ | Folder | Accepts | Trust level |
229
+ |---|---|---|
230
+ | `local_pdfs/` | `.pdf`, `.md` (your own summaries) | academic |
231
+ | `local_web/` | `.mhtml` / `.mht`, `.md` | web capture |
232
+
217
233
  `check` runs the full pipeline in one command: text checks → figure analysis → LLM consistency → reference verification via APIs → download and parse cited papers → claim verification → consistency checks on cited papers → bibliography verification → aggregate reliability scores → SciLint Score. An initial run on a 50-reference paper takes up to 30 minutes (dominated by downloads and claim verification); subsequent cached runs complete in minutes.
218
234
 
219
- Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
235
+ ### Iterative editing no `--fresh` needed
236
+
237
+ `sciwrite-lint check` invalidates cached results whenever the inputs they were derived from change, so rerunning after an edit just works. Concretely:
238
+
239
+ - **Edit a sentence or paragraph** → the manuscript-LLM checks (prose-quality, cross-section consistency, etc.) re-run only on the touched sentences and their paragraph siblings; untouched prose stays cached
240
+ - **Edit a paragraph around a citation** → the claim verifier reruns on the edited prose
241
+ - **Replace a figure** (overwrite the PNG/PDF in your tree) → the vision cache re-describes it
242
+ - **Swap the local LLM model** (e.g. `qwen3` ↔ `gemma3` via `--model`) → manuscript-check rows and claim verdicts no longer count as cache hits
243
+ - **Overwrite a reference PDF** in your drop folder, or **re-fetch** an open-access PDF that has new bytes → the parser, embeddings, and claim verifier all rerun against the new content
244
+ - **Upgrade a summary `.md` to a real PDF** in your drop folder → claims are re-verified against the richer evidence
245
+
246
+ Use `--fresh` only when you want to wipe the workspace deliberately (it backs up the existing workspace before recreating it). It is not the answer to "I edited the paper and a finding looks stale" — if you ever see that, file an issue; a cache invariant is wrong and we'll fix it.
220
247
 
221
248
  Use `--checks ID[,ID...]` to run only the listed checks (comma-separated), disabling the rest for this invocation. Useful for fast iteration during editing (`--checks prose-quality` runs just the prose review), for debugging a single check, or for scripting a two-pass workflow. Unknown check IDs fail loudly — run `sciwrite-lint checks` to list available IDs.
222
249
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sciwrite-lint"
7
- version = "0.4.0"
7
+ version = "0.5.1"
8
8
  description = "A linter for scientific manuscripts — reference verification, consistency checking, and structural validation."
9
9
  requires-python = ">=3.13,<3.14"
10
10
  license = "MIT"
@@ -86,3 +86,5 @@ sciwrite-lint = "sciwrite_lint.__main__:main"
86
86
  [tool.setuptools.packages.find]
87
87
  include = ["sciwrite_lint*"]
88
88
 
89
+ [tool.setuptools.package-data]
90
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  from sciwrite_lint.exceptions import LLMConnectionError, SciWriteLintError
4
4
 
5
- __version__ = "0.4.0"
5
+ __version__ = "0.5.1"
6
6
 
7
7
  from sciwrite_lint.oa import (
8
8
  DownloadResult,
@@ -14,8 +14,6 @@ import argparse
14
14
  import sys
15
15
  from pathlib import Path
16
16
 
17
- from loguru import logger
18
-
19
17
  from sciwrite_lint import __version__
20
18
  from sciwrite_lint.cli._common import (
21
19
  _classify_verify_issue as _classify_verify_issue,
@@ -42,6 +40,31 @@ _REPLACE_HELP = (
42
40
  # ---------------------------------------------------------------------------
43
41
 
44
42
 
43
+ def _register_optional_extensions(
44
+ sub: argparse._SubParsersAction, # type: ignore[type-arg]
45
+ ) -> None:
46
+ """Load CLI extensions discovered under ``sciwrite_lint``.
47
+
48
+ An underscore-prefixed subpackage is a CLI extension when its
49
+ ``__init__.py`` defines ``register_cli(sub)``. Discovery walks
50
+ ``sciwrite_lint.__path__`` via ``pkgutil``, so no extension package
51
+ names appear in this source — adding or removing one is a filesystem
52
+ change.
53
+ """
54
+ import importlib
55
+ import pkgutil
56
+
57
+ import sciwrite_lint as _pkg
58
+
59
+ for _finder, name, ispkg in pkgutil.iter_modules(_pkg.__path__):
60
+ if not ispkg or not name.startswith("_"):
61
+ continue
62
+ ext = importlib.import_module(f"{_pkg.__name__}.{name}")
63
+ register = getattr(ext, "register_cli", None)
64
+ if register is not None:
65
+ register(sub)
66
+
67
+
45
68
  def main(argv: list[str] | None = None) -> int:
46
69
  from sciwrite_lint.cli.check import run_check, run_checks_list
47
70
  from sciwrite_lint.cli.config import run_config
@@ -125,47 +148,8 @@ def main(argv: list[str] | None = None) -> int:
125
148
  p_checks = sub.add_parser("checks", help="List all registered checks")
126
149
  p_checks.set_defaults(func=run_checks_list)
127
150
 
128
- # --- aicode-lint (optional only registered when the WIP module is installed) ---
129
- # ``sciwrite_lint.aicode_lint`` is excluded from the public release rsync
130
- # while it stabilizes. Use ``importlib.util.find_spec`` to check whether
131
- # the module is present rather than try/except ImportError (separately
132
- # banned). Public installs (pip from PyPI) won't have it; dev installs
133
- # do. Check the parent ``sciwrite_lint.aicode_lint`` first because
134
- # ``find_spec`` raises ``ModuleNotFoundError`` on a missing intermediate
135
- # path; once the parent is confirmed present, the child lookup is safe.
136
- import importlib.util as _importlib_util
137
-
138
- if _importlib_util.find_spec("sciwrite_lint.aicode_lint") is not None:
139
- from sciwrite_lint.aicode_lint.cli import run_aicode_lint
140
-
141
- p_aicode = sub.add_parser(
142
- "aicode-lint",
143
- help="LLM code review on Python sources via local vLLM (semantic antipatterns)",
144
- )
145
- p_aicode.add_argument(
146
- "paths",
147
- nargs="*",
148
- help="Files, directories, or globs to scan (default: sciwrite_lint/**/*.py)",
149
- )
150
- p_aicode.add_argument(
151
- "--rules",
152
- default=None,
153
- metavar="ID[,ID...]",
154
- help="Run only the listed rule IDs (comma-separated). Default: all built-in rules.",
155
- )
156
- p_aicode.add_argument(
157
- "--list-rules",
158
- action="store_true",
159
- help="Print the rule registry and exit.",
160
- )
161
- p_aicode.add_argument(
162
- "--format",
163
- choices=["terminal", "json"],
164
- default=None,
165
- help="Output format (default: terminal).",
166
- )
167
- p_aicode.add_argument("--config", help="Path to .sciwrite-lint.toml")
168
- p_aicode.set_defaults(func=run_aicode_lint)
151
+ # Load optional underscore-prefixed CLI extension subpackages (if any).
152
+ _register_optional_extensions(sub)
169
153
 
170
154
  # --- init ---
171
155
  p_init = sub.add_parser(
@@ -270,12 +254,6 @@ def main(argv: list[str] | None = None) -> int:
270
254
  # --- verify-claims ---
271
255
  p_vc = sub.add_parser("verify-claims", help="Verify claims against cited sources")
272
256
  p_vc.add_argument("--paper", required=True, help="Paper to verify")
273
- p_vc.add_argument(
274
- "--backend",
275
- choices=["vllm", "claude"],
276
- default="vllm",
277
- help="vllm (local LLM, fast) or claude (Claude CLI, deep)",
278
- )
279
257
  p_vc.add_argument(
280
258
  "--model",
281
259
  choices=_TEXT_MODELS,
@@ -499,14 +477,32 @@ def main(argv: list[str] | None = None) -> int:
499
477
  p_vllm.print_help()
500
478
  return 0
501
479
 
502
- # Set up file logging from config (stderr sink stays at INFO by default)
503
- try:
504
- config = load_config(
505
- Path(args.config) if getattr(args, "config", None) else None
506
- )
507
- except Exception as e:
508
- logger.debug("Config load failed, using defaults: {}", e)
480
+ # Set up file logging from config (stderr sink stays at INFO by default).
481
+ # ``load_config`` already returns ``LintConfig()`` when no config file is
482
+ # discovered; reaching the exception path means a file WAS found (or
483
+ # explicitly passed via --config) but failed to parse or validate.
484
+ # Surface that instead of silently substituting hardcoded defaults — a
485
+ # broken TOML otherwise produces results computed against the wrong
486
+ # workspace, paper list, or API keys.
487
+ #
488
+ # The ``init`` command is the one exception: its job is to scaffold a
489
+ # config, so it must run against a missing, broken, or sentinel TOML
490
+ # without aborting. Use ``LintConfig()`` for logging setup only and let
491
+ # init proceed to (over)write the file.
492
+ if args.command == "init":
509
493
  config = LintConfig()
494
+ else:
495
+ try:
496
+ config = load_config(
497
+ Path(args.config) if getattr(args, "config", None) else None
498
+ )
499
+ except Exception as e:
500
+ cfg_arg = getattr(args, "config", None)
501
+ cfg_hint = f" ({cfg_arg})" if cfg_arg else " (.sciwrite-lint.toml)"
502
+ raise SystemExit(
503
+ f"ERROR: failed to load configuration{cfg_hint}: {type(e).__name__}: {e}\n"
504
+ " Fix the TOML or remove the file to use built-in defaults."
505
+ ) from e
510
506
  _setup_logging(config)
511
507
 
512
508
  return args.func(args)
@@ -84,7 +84,7 @@ def run_parse(args: argparse.Namespace) -> int:
84
84
  n = compute_and_store_embeddings(key, text, refs_dir)
85
85
  print(f" {key}: {n} chunks")
86
86
  except Exception as e:
87
- print(f" {key}: error {e}")
87
+ logger.warning("parse: embedding failed for {}: {}", key, e)
88
88
 
89
89
  print(f"\n Summary: {cached} cached, {parsed} parsed, {failed} failed")
90
90
  if failed:
@@ -335,6 +335,7 @@ def run_ref_health(args: argparse.Namespace) -> int:
335
335
  academic_unmatched: list[Path] = []
336
336
  web_matched: dict[str, Path] = {}
337
337
  web_unmatched: list[Path] = []
338
+ local_match_error: str | None = None
338
339
  try:
339
340
  if tex_file:
340
341
  from sciwrite_lint.config import load_config as _load_cfg
@@ -356,8 +357,13 @@ def run_ref_health(args: argparse.Namespace) -> int:
356
357
  _cfg.local_web_dir, titles, WEB_SUFFIXES
357
358
  )
358
359
  except Exception as e:
359
- logger.debug(
360
- f"ref-health: local source matching skipped ({type(e).__name__}: {e})"
360
+ # Record the failure so the report renders a visible notice instead
361
+ # of silently producing a ref-health output with the local-source
362
+ # section missing. Log at WARNING (not DEBUG) so the operator sees
363
+ # it even when running with the default log level.
364
+ local_match_error = f"{type(e).__name__}: {e}"
365
+ logger.warning(
366
+ "ref-health: local source matching skipped ({})", local_match_error
361
367
  )
362
368
 
363
369
  # --- Output ---
@@ -398,7 +404,9 @@ def run_ref_health(args: argparse.Namespace) -> int:
398
404
  if web_count:
399
405
  print(f" ({web_count} web resources)")
400
406
 
401
- # Local sources — academic (PDFs + MD summaries) + web (MD + MHTML)
407
+ # Local sources — academic (PDFs + MD summaries) + web (MD + MHTML).
408
+ # Matching failures are surfaced via logger.warning (above) so they
409
+ # appear in the operator's terminal regardless of report formatting.
402
410
  if academic_matched or academic_unmatched:
403
411
  print("\n LOCAL ACADEMIC SOURCES")
404
412
  if academic_matched:
@@ -425,7 +433,7 @@ def run_ref_health(args: argparse.Namespace) -> int:
425
433
 
426
434
 
427
435
  def run_verify_claims(args: argparse.Namespace) -> int:
428
- """Verify claims against cited sources using local LLM or Claude Opus."""
436
+ """Verify claims against cited sources using local vLLM."""
429
437
  from sciwrite_lint.cli._common import _load_config, _resolve_paper
430
438
 
431
439
  config = _load_config(args)
@@ -446,7 +454,6 @@ def run_verify_claims(args: argparse.Namespace) -> int:
446
454
  refs_dir,
447
455
  config=config,
448
456
  bib_path=pc.bib,
449
- backend=args.backend,
450
457
  model=args.model,
451
458
  key_filter=args.key,
452
459
  limit=args.limit,