sciwrite-lint 0.5.0__tar.gz → 0.6.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.
- {sciwrite_lint-0.5.0/sciwrite_lint.egg-info → sciwrite_lint-0.6.0}/PKG-INFO +46 -5
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/README.md +45 -4
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/pyproject.toml +1 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/__init__.py +1 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/__main__.py +14 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/api.py +40 -18
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/_diagnostics.py +67 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/dangling_cite.py +13 -7
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/dangling_ref.py +31 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/full_paper_consistency.py +23 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/ref_internal_checks.py +65 -26
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/registry.py +22 -0
- sciwrite_lint-0.6.0/sciwrite_lint/checks/unreferenced_figure.py +157 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/_common.py +18 -3
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/check.py +52 -8
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/config.py +11 -1
- sciwrite_lint-0.6.0/sciwrite_lint/cli/misc/init.py +37 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/rank.py +17 -6
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/verify.py +113 -11
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/config.py +155 -37
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/eval_claims.py +170 -27
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/footnote_urls.py +143 -6
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/_common.py +4 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/_download.py +13 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/manuscript_store.py +55 -3
- sciwrite_lint-0.6.0/sciwrite_lint/markdown_cites.py +337 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/models.py +7 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/oa/_pdf.py +27 -2
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/__init__.py +3 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/checks.py +23 -2
- sciwrite_lint-0.6.0/sciwrite_lint/pipeline/citation_setup.py +157 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/claims.py +10 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/embeddings.py +53 -21
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/fetch.py +53 -27
- sciwrite_lint-0.6.0/sciwrite_lint/pipeline/markdown_context.py +111 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/orchestration.py +56 -42
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/pdf_context.py +16 -8
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/staged.py +16 -53
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/verify.py +15 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/citations.py +10 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/reference_store.py +4 -4
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/tex_parser.py +74 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vllm/vllm_server.py +21 -1
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0/sciwrite_lint.egg-info}/PKG-INFO +46 -5
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint.egg-info/SOURCES.txt +3 -0
- sciwrite_lint-0.5.0/sciwrite_lint/checks/unreferenced_figure.py +0 -70
- sciwrite_lint-0.5.0/sciwrite_lint/cli/misc/init.py +0 -14
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/LICENSE +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/_network.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/api_keys.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/_section_utils.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/cite_purpose.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/claim_support.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/cross_section_consistency.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/internal_consistency_pairs.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/prose_quality.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/reference_accuracy.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/reference_exists.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/reference_unreliable.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/retracted_cite.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/checks/structure_promises.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/claims.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/claude_cli.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/fetch.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/_monitor.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/containers.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/dismiss_claim.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/grobid.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/override.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/parse.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/vision.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cli/misc/vllm.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/cross_paper.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/exceptions.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/_orchestrator.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/_search.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/_validation.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/arxiv.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/biorxiv.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/core.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/eric.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/europepmc.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/hal.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/ideas.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/nasa_ads.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/nber.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/osf.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/pmc.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/s2.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/fulltext/unpaywall.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/latex_to_markdown.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/compute_cap.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/controller.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/decide.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/host_metrics.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/metrics_probe.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/registry.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/telemetry.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/vllm_metrics.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/concurrency_optimizer/wiring.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm/manuscript_cache.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/llm_utils.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/local_sources.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/mhtml.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/oa/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/oa/_models.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/oa/_search.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/oa/_web.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pdf/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pdf/grobid.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pdf/pdf_download.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/parse.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/ref_internal.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/runners.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/swap.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/tracking.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/unreliable.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/pipeline/vision.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/prompt_safety.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/py.typed +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/rate_limiter.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/_embed_timing.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/chain.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/crossref.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/embedding_store.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/matching.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/metadata.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/retraction_watch.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/_core.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/bib_checks.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/citation_metadata.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/claim_results.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/manuscript_check_cache.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/manuscript_citations.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/parse_cache.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/pipeline_stage.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/query_vectors.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/ref_internal.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/registry.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/references/workspace_db/vision_cache.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/report.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/schemas.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/scoring/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/scoring/contribution.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/scoring/scilint_score.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/usage.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vision/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vision/cache.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vision/describe.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vision/image_extraction.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vision/pipeline.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vllm/__init__.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/vllm/metrics.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint/web.py +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint.egg-info/dependency_links.txt +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint.egg-info/entry_points.txt +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint.egg-info/requires.txt +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/sciwrite_lint.egg-info/top_level.txt +0 -0
- {sciwrite_lint-0.5.0 → sciwrite_lint-0.6.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sciwrite-lint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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>
|
|
@@ -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
|
-
|
|
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
|
|
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` generates a `.sciwrite-lint.toml` config. With no argument it auto-detects the manuscript — a `.tex` file (bibliography read from `\bibliography{}`) or a markdown file with a sibling `{name}.bib` (so ordinary `.md` files aren't mistaken for papers); if it finds **several**, it lists them and asks you to pick one. Name a manuscript explicitly — including a **PDF** — with `sciwrite-lint init <file>`. Review to confirm the right file was registered.
|
|
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
|

|
|
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.
|
|
@@ -275,11 +275,52 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
275
275
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
276
276
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
277
277
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
278
|
+
sciwrite-lint check paper.md # markdown manuscript (prose, structure, [@key] citations, @fig:/@sec: cross-refs)
|
|
278
279
|
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
279
280
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
280
281
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
281
282
|
```
|
|
282
283
|
|
|
284
|
+
**Supported inputs.**
|
|
285
|
+
|
|
286
|
+
*The manuscript you check:*
|
|
287
|
+
|
|
288
|
+
| Format | Notes |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `.tex` | LaTeX — hand-written, or pandoc-generated (e.g. from Markdown); pandoc's heading anchors are unwrapped automatically. Full pipeline. |
|
|
291
|
+
| `.pdf` | Parsed via GROBID (container required). Full pipeline. |
|
|
292
|
+
| `.md` | Markdown (pandoc). Prose and structure checks run; pandoc `[@key]` citations are checked against a sibling `{name}.bib` and verified against external databases in the full pipeline; pandoc-crossref `@fig:`/`@sec:`/`@tbl:` references and `{#fig:…}` figures are checked for broken/unreferenced targets. Sections split by heading. |
|
|
293
|
+
|
|
294
|
+
*What runs on each format:*
|
|
295
|
+
|
|
296
|
+
| Capability | `.tex` | `.pdf` | `.md` |
|
|
297
|
+
|---|:---:|:---:|:---:|
|
|
298
|
+
| Prose & structure checks | ✓ | ✓ | ✓ |
|
|
299
|
+
| Citation ↔ bibliography (`dangling-cite`) | ✓ | ✓ | ✓ |
|
|
300
|
+
| Cross-references (`dangling-ref`) | ✓ | ✓ <sup>1</sup> | ✓ |
|
|
301
|
+
| Unreferenced figures | ✓ | ✓ <sup>2</sup> | ✓ |
|
|
302
|
+
| Reference verification (external APIs) | ✓ | ✓ | ✓ |
|
|
303
|
+
| Claim support / citation purpose | ✓ | ✓ | ✓ |
|
|
304
|
+
| Footnote-URL web sources | ✓ | — <sup>3</sup> | ✓ |
|
|
305
|
+
| `ref-health` (no external APIs) | ✓ | ✓ <sup>4</sup> | ✓ |
|
|
306
|
+
| `init` auto-detection | ✓ | — <sup>5</sup> | ✓ <sup>6</sup> |
|
|
307
|
+
|
|
308
|
+
<sup>1</sup> PDF detects broken references rendered as `??`; `.tex`/`.md` match each reference to its label.
|
|
309
|
+
<sup>2</sup> A PDF has no `\label`s, so figures are matched by the number GROBID reads from each figure's caption against the "Figure N" mentions in the prose (needs GROBID). Only cleanly-labelled figures are checked, so it stays conservative.
|
|
310
|
+
<sup>3</sup> Footnote-URL web sources are matched for LaTeX and markdown; not yet for PDF.
|
|
311
|
+
<sup>4</sup> A PDF's references come from GROBID (a local parser — still no external/network APIs), so PDF `ref-health` needs the GROBID container running.
|
|
312
|
+
<sup>5</sup> *Auto-scan* skips PDFs — no signal distinguishes a manuscript PDF from a project's reference/figure PDFs, the way a `.tex` or a `.md` with a sibling `.bib` is recognizable. Register one explicitly with `sciwrite-lint init paper.pdf` (the explicit form takes any type). When auto-scan finds several `.tex`/`.md`, it asks you to name one rather than registering all.
|
|
313
|
+
<sup>6</sup> Auto-detected when a sibling `{name}.bib` is present.
|
|
314
|
+
|
|
315
|
+
Markdown citation support targets pandoc `[@key]` citations with a bibliography. Numeric `[1]` citations are not supported by design — they carry no stable key (renumbering on every edit) and their reference list is free text rather than structured entries; a document using them is reported, not partially parsed.
|
|
316
|
+
|
|
317
|
+
*Cited sources you optionally supply (drop folders — see [Setup](#setup)):*
|
|
318
|
+
|
|
319
|
+
| Folder | Accepts | Trust level |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `local_pdfs/` | `.pdf`, `.md` (your own summaries) | academic |
|
|
322
|
+
| `local_web/` | `.mhtml` / `.mht`, `.md` | web capture |
|
|
323
|
+
|
|
283
324
|
`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
325
|
|
|
285
326
|
### Iterative editing — no `--fresh` needed
|
|
@@ -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
|
-
|
|
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
|
|
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` generates a `.sciwrite-lint.toml` config. With no argument it auto-detects the manuscript — a `.tex` file (bibliography read from `\bibliography{}`) or a markdown file with a sibling `{name}.bib` (so ordinary `.md` files aren't mistaken for papers); if it finds **several**, it lists them and asks you to pick one. Name a manuscript explicitly — including a **PDF** — with `sciwrite-lint init <file>`. Review to confirm the right file was registered.
|
|
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
|

|
|
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.
|
|
@@ -209,11 +209,52 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
209
209
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
210
210
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
211
211
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
212
|
+
sciwrite-lint check paper.md # markdown manuscript (prose, structure, [@key] citations, @fig:/@sec: cross-refs)
|
|
212
213
|
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
213
214
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
214
215
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
215
216
|
```
|
|
216
217
|
|
|
218
|
+
**Supported inputs.**
|
|
219
|
+
|
|
220
|
+
*The manuscript you check:*
|
|
221
|
+
|
|
222
|
+
| Format | Notes |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `.tex` | LaTeX — hand-written, or pandoc-generated (e.g. from Markdown); pandoc's heading anchors are unwrapped automatically. Full pipeline. |
|
|
225
|
+
| `.pdf` | Parsed via GROBID (container required). Full pipeline. |
|
|
226
|
+
| `.md` | Markdown (pandoc). Prose and structure checks run; pandoc `[@key]` citations are checked against a sibling `{name}.bib` and verified against external databases in the full pipeline; pandoc-crossref `@fig:`/`@sec:`/`@tbl:` references and `{#fig:…}` figures are checked for broken/unreferenced targets. Sections split by heading. |
|
|
227
|
+
|
|
228
|
+
*What runs on each format:*
|
|
229
|
+
|
|
230
|
+
| Capability | `.tex` | `.pdf` | `.md` |
|
|
231
|
+
|---|:---:|:---:|:---:|
|
|
232
|
+
| Prose & structure checks | ✓ | ✓ | ✓ |
|
|
233
|
+
| Citation ↔ bibliography (`dangling-cite`) | ✓ | ✓ | ✓ |
|
|
234
|
+
| Cross-references (`dangling-ref`) | ✓ | ✓ <sup>1</sup> | ✓ |
|
|
235
|
+
| Unreferenced figures | ✓ | ✓ <sup>2</sup> | ✓ |
|
|
236
|
+
| Reference verification (external APIs) | ✓ | ✓ | ✓ |
|
|
237
|
+
| Claim support / citation purpose | ✓ | ✓ | ✓ |
|
|
238
|
+
| Footnote-URL web sources | ✓ | — <sup>3</sup> | ✓ |
|
|
239
|
+
| `ref-health` (no external APIs) | ✓ | ✓ <sup>4</sup> | ✓ |
|
|
240
|
+
| `init` auto-detection | ✓ | — <sup>5</sup> | ✓ <sup>6</sup> |
|
|
241
|
+
|
|
242
|
+
<sup>1</sup> PDF detects broken references rendered as `??`; `.tex`/`.md` match each reference to its label.
|
|
243
|
+
<sup>2</sup> A PDF has no `\label`s, so figures are matched by the number GROBID reads from each figure's caption against the "Figure N" mentions in the prose (needs GROBID). Only cleanly-labelled figures are checked, so it stays conservative.
|
|
244
|
+
<sup>3</sup> Footnote-URL web sources are matched for LaTeX and markdown; not yet for PDF.
|
|
245
|
+
<sup>4</sup> A PDF's references come from GROBID (a local parser — still no external/network APIs), so PDF `ref-health` needs the GROBID container running.
|
|
246
|
+
<sup>5</sup> *Auto-scan* skips PDFs — no signal distinguishes a manuscript PDF from a project's reference/figure PDFs, the way a `.tex` or a `.md` with a sibling `.bib` is recognizable. Register one explicitly with `sciwrite-lint init paper.pdf` (the explicit form takes any type). When auto-scan finds several `.tex`/`.md`, it asks you to name one rather than registering all.
|
|
247
|
+
<sup>6</sup> Auto-detected when a sibling `{name}.bib` is present.
|
|
248
|
+
|
|
249
|
+
Markdown citation support targets pandoc `[@key]` citations with a bibliography. Numeric `[1]` citations are not supported by design — they carry no stable key (renumbering on every edit) and their reference list is free text rather than structured entries; a document using them is reported, not partially parsed.
|
|
250
|
+
|
|
251
|
+
*Cited sources you optionally supply (drop folders — see [Setup](#setup)):*
|
|
252
|
+
|
|
253
|
+
| Folder | Accepts | Trust level |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `local_pdfs/` | `.pdf`, `.md` (your own summaries) | academic |
|
|
256
|
+
| `local_web/` | `.mhtml` / `.mht`, `.md` | web capture |
|
|
257
|
+
|
|
217
258
|
`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
259
|
|
|
219
260
|
### Iterative editing — no `--fresh` needed
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sciwrite-lint"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
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"
|
|
@@ -155,6 +155,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
155
155
|
p_init = sub.add_parser(
|
|
156
156
|
"init", help="Initialize project: config + references directory"
|
|
157
157
|
)
|
|
158
|
+
p_init.add_argument(
|
|
159
|
+
"file",
|
|
160
|
+
nargs="?",
|
|
161
|
+
help="Manuscript to register (.tex/.pdf/.md). Omit to auto-detect "
|
|
162
|
+
"(a single .tex / .md+.bib); name one explicitly when there are "
|
|
163
|
+
"several or for a PDF.",
|
|
164
|
+
)
|
|
158
165
|
p_init.add_argument(
|
|
159
166
|
"--force", action="store_true", help="Overwrite existing config"
|
|
160
167
|
)
|
|
@@ -175,6 +182,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
175
182
|
)
|
|
176
183
|
p_config_email.add_argument("email", help="Your email address")
|
|
177
184
|
p_config_email.add_argument("--config", help="Path to .sciwrite-lint.toml")
|
|
185
|
+
p_config_email.add_argument(
|
|
186
|
+
"--global",
|
|
187
|
+
dest="set_global",
|
|
188
|
+
action="store_true",
|
|
189
|
+
help="Store as the machine-wide default (~/.sciwrite-lint/) instead of "
|
|
190
|
+
"this project's .sciwrite-lint.toml",
|
|
191
|
+
)
|
|
178
192
|
p_config_email.set_defaults(func=run_config)
|
|
179
193
|
|
|
180
194
|
p_config_set_key = config_sub.add_parser(
|
|
@@ -41,6 +41,20 @@ _openlibrary_limiter = MonotonicRateLimiter(5, 1.0) # ~5 req/s (be polite)
|
|
|
41
41
|
_loc_limiter = MonotonicRateLimiter(5, 1.0) # ~5 req/s (be polite)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _openalex_params(polite_email: str, **params: Any) -> dict[str, Any]:
|
|
45
|
+
"""OpenAlex query params with the polite-pool ``mailto`` merged in.
|
|
46
|
+
|
|
47
|
+
Single source of truth for every OpenAlex call site: requests carrying
|
|
48
|
+
a ``mailto`` are routed into OpenAlex's polite pool, which has a higher
|
|
49
|
+
and more stable shared rate limit than the anonymous common pool. This
|
|
50
|
+
keeps reference verification off the common pool, where sustained use
|
|
51
|
+
draws HTTP 429s. No-op when no contact email is configured.
|
|
52
|
+
"""
|
|
53
|
+
if polite_email:
|
|
54
|
+
params["mailto"] = polite_email
|
|
55
|
+
return params
|
|
56
|
+
|
|
57
|
+
|
|
44
58
|
class CitationAPI:
|
|
45
59
|
"""Verify citations against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress."""
|
|
46
60
|
|
|
@@ -129,6 +143,15 @@ class CitationAPI:
|
|
|
129
143
|
# OpenAlex
|
|
130
144
|
# ------------------------------------------------------------------
|
|
131
145
|
|
|
146
|
+
def _oa_params(self, **params: Any) -> dict[str, Any]:
|
|
147
|
+
"""OpenAlex query params for this client's configured contact email.
|
|
148
|
+
|
|
149
|
+
Thin instance-scoped wrapper over :func:`_openalex_params` so the
|
|
150
|
+
four ``_openalex_lookup`` call sites don't each repeat
|
|
151
|
+
``self._config.polite_email``.
|
|
152
|
+
"""
|
|
153
|
+
return _openalex_params(self._config.polite_email, **params)
|
|
154
|
+
|
|
132
155
|
async def _openalex_lookup(self, citation: Citation) -> dict[str, Any] | None:
|
|
133
156
|
try:
|
|
134
157
|
# Try DOI lookup
|
|
@@ -137,7 +160,7 @@ class CitationAPI:
|
|
|
137
160
|
resp = await rate_limited_get(
|
|
138
161
|
_openalex_limiter,
|
|
139
162
|
f"https://api.openalex.org/works/doi:{clean_doi}",
|
|
140
|
-
params=
|
|
163
|
+
params=self._oa_params(select=_OA_FIELDS),
|
|
141
164
|
label="OpenAlex DOI",
|
|
142
165
|
client=self._client,
|
|
143
166
|
service="openalex",
|
|
@@ -155,7 +178,7 @@ class CitationAPI:
|
|
|
155
178
|
resp = await rate_limited_get(
|
|
156
179
|
_openalex_limiter,
|
|
157
180
|
f"https://api.openalex.org/works/doi:{arxiv_doi}",
|
|
158
|
-
params=
|
|
181
|
+
params=self._oa_params(select=_OA_FIELDS),
|
|
159
182
|
label="OpenAlex arXiv DOI",
|
|
160
183
|
client=self._client,
|
|
161
184
|
service="openalex",
|
|
@@ -173,10 +196,10 @@ class CitationAPI:
|
|
|
173
196
|
resp = await rate_limited_get(
|
|
174
197
|
_openalex_limiter,
|
|
175
198
|
"https://api.openalex.org/works",
|
|
176
|
-
params=
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
199
|
+
params=self._oa_params(
|
|
200
|
+
filter=f"ids.pmid:https://pubmed.ncbi.nlm.nih.gov/{citation.pmid}",
|
|
201
|
+
select=_OA_FIELDS,
|
|
202
|
+
),
|
|
180
203
|
label="OpenAlex PMID",
|
|
181
204
|
client=self._client,
|
|
182
205
|
service="openalex",
|
|
@@ -192,11 +215,11 @@ class CitationAPI:
|
|
|
192
215
|
resp = await rate_limited_get(
|
|
193
216
|
_openalex_limiter,
|
|
194
217
|
"https://api.openalex.org/works",
|
|
195
|
-
params=
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
218
|
+
params=self._oa_params(
|
|
219
|
+
filter=f"title.search:{query}",
|
|
220
|
+
per_page=10,
|
|
221
|
+
select=_OA_FIELDS,
|
|
222
|
+
),
|
|
200
223
|
label="OpenAlex search",
|
|
201
224
|
client=self._client,
|
|
202
225
|
service="openalex",
|
|
@@ -481,13 +504,12 @@ async def batch_openalex(
|
|
|
481
504
|
doi_filter = "|".join(f"https://doi.org/{doi}" for _, doi in all_dois)
|
|
482
505
|
doi_by_key = {doi.lower(): key for key, doi in all_dois}
|
|
483
506
|
|
|
484
|
-
params
|
|
485
|
-
|
|
486
|
-
"
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
params["mailto"] = config.polite_email
|
|
507
|
+
params = _openalex_params(
|
|
508
|
+
config.polite_email,
|
|
509
|
+
filter=f"doi:{doi_filter}",
|
|
510
|
+
per_page=200,
|
|
511
|
+
select=_OA_FIELDS,
|
|
512
|
+
)
|
|
491
513
|
|
|
492
514
|
# Secondary index by arXiv ID: OpenAlex may return a different
|
|
493
515
|
# canonical DOI than the query DOI (e.g. arXiv DOI → publisher DOI).
|
|
@@ -38,7 +38,7 @@ Two orthogonal axes describe a Finding:
|
|
|
38
38
|
issue". Same level vocabulary, different semantics — the panel /
|
|
39
39
|
JSON-field disambiguates.
|
|
40
40
|
|
|
41
|
-
The
|
|
41
|
+
The diagnostic rule_ids:
|
|
42
42
|
|
|
43
43
|
- ``llm-unavailable`` — ``llm_query`` exhausted its retry ladder
|
|
44
44
|
(medium → low → off) and returned ``None``. One per skipped check
|
|
@@ -59,6 +59,17 @@ The three diagnostic rule_ids:
|
|
|
59
59
|
during ``build_queries`` or ``process_results`` / a text check's
|
|
60
60
|
body raised mid-run. One per failing check. Emitted by the LLM
|
|
61
61
|
batch runner and the text-check runner.
|
|
62
|
+
- ``source-unsupported`` — one or more checks declare
|
|
63
|
+
``supports_markdown=False`` (they parse LaTeX citation/cross-reference/
|
|
64
|
+
figure syntax) and the manuscript is markdown. The text-check runner
|
|
65
|
+
skips those checks and emits one summary finding listing them, so the
|
|
66
|
+
coverage gap is explicit rather than a silent zero-result.
|
|
67
|
+
- ``no-references`` — citation extraction found zero references in the
|
|
68
|
+
manuscript, so the citation / reference / claim stages had nothing to
|
|
69
|
+
verify. Emitted once per run by the pipeline setup. A genuinely
|
|
70
|
+
citation-free note is harmless, but a zero usually means the
|
|
71
|
+
bibliography was not detected — surfacing it stops a clean-looking
|
|
72
|
+
report from hiding that the reference checks never ran.
|
|
62
73
|
|
|
63
74
|
Adding a new system rule_id:
|
|
64
75
|
|
|
@@ -92,6 +103,8 @@ SYSTEM_RULE_IDS: frozenset[str] = frozenset(
|
|
|
92
103
|
"vision-incomplete",
|
|
93
104
|
"internal-error",
|
|
94
105
|
"parse-failed",
|
|
106
|
+
"source-unsupported",
|
|
107
|
+
"no-references",
|
|
95
108
|
}
|
|
96
109
|
)
|
|
97
110
|
|
|
@@ -235,3 +248,56 @@ def internal_error_finding(check_id: str, error: BaseException) -> Finding:
|
|
|
235
248
|
message=f"{check_id}: check raised an unexpected exception; check did not run",
|
|
236
249
|
context=f"{type(error).__name__}: {error!s}"[:200],
|
|
237
250
|
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def source_unsupported_finding(
|
|
254
|
+
source_type: str, skipped_check_ids: list[str]
|
|
255
|
+
) -> Finding:
|
|
256
|
+
"""Build a finding for checks skipped because the source type lacks the structure they need.
|
|
257
|
+
|
|
258
|
+
Emitted once per run by the text-check runner when the manuscript is
|
|
259
|
+
markdown and one or more registered checks declare
|
|
260
|
+
``supports_markdown=False`` (they parse LaTeX citation/cross-reference/
|
|
261
|
+
figure syntax or need a populated bibliography). Listing the skipped
|
|
262
|
+
checks makes the coverage gap explicit — a markdown manuscript still
|
|
263
|
+
gets prose and structure checks, but these did not run.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
source_type:
|
|
268
|
+
The manuscript source type (e.g. ``"markdown"``).
|
|
269
|
+
skipped_check_ids:
|
|
270
|
+
Registered check IDs that were skipped for this source.
|
|
271
|
+
"""
|
|
272
|
+
checks = ", ".join(sorted(skipped_check_ids))
|
|
273
|
+
return Finding(
|
|
274
|
+
level="warning",
|
|
275
|
+
rule_id="source-unsupported",
|
|
276
|
+
message=(
|
|
277
|
+
f"{len(skipped_check_ids)} check(s) need citation/reference structure "
|
|
278
|
+
f"not extracted from {source_type} manuscripts and did not run: {checks}. "
|
|
279
|
+
"Prose and structure checks ran normally."
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def no_references_finding() -> Finding:
|
|
285
|
+
"""Build a finding for a run that extracted zero references.
|
|
286
|
+
|
|
287
|
+
Emitted once by the pipeline setup when citation extraction yields an
|
|
288
|
+
empty list. The reference, citation, and claim stages then have
|
|
289
|
+
nothing to verify — surfacing this keeps a clean-looking report from
|
|
290
|
+
hiding that those checks never ran (usually a bibliography that was
|
|
291
|
+
not detected, not a genuinely citation-free manuscript).
|
|
292
|
+
"""
|
|
293
|
+
return Finding(
|
|
294
|
+
level="warning",
|
|
295
|
+
rule_id="no-references",
|
|
296
|
+
message=(
|
|
297
|
+
"No references found in the manuscript — citation, reference, and "
|
|
298
|
+
"claim checks had nothing to verify. If the manuscript cites sources, "
|
|
299
|
+
"check that its bibliography is detected (LaTeX \\bibliography{} / "
|
|
300
|
+
".bib, or for markdown the YAML bibliography: field or a sibling "
|
|
301
|
+
"{name}.bib)."
|
|
302
|
+
),
|
|
303
|
+
)
|
|
@@ -48,12 +48,13 @@ def _check_latex(tex_path: Path, config: LintConfig) -> list[Finding]:
|
|
|
48
48
|
return findings
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def
|
|
51
|
+
def _check_context(ctx: object) -> list[Finding]:
|
|
52
52
|
"""Check that inline citations link to a reference in the bibliography.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
Serves any ManuscriptContext that populates ``inline_citations`` and
|
|
55
|
+
``parsed_references`` — PDF (GROBID TEI <ref> tags) and markdown
|
|
56
|
+
(pandoc ``[@key]`` + sibling ``.bib``) both do. Citations whose key is
|
|
57
|
+
not in the reference list are dangling.
|
|
57
58
|
"""
|
|
58
59
|
from sciwrite_lint.manuscript_store import ManuscriptContext
|
|
59
60
|
|
|
@@ -86,8 +87,13 @@ def _check_pdf(ctx: object) -> list[Finding]:
|
|
|
86
87
|
description="Citation has no matching bibliography entry.",
|
|
87
88
|
)
|
|
88
89
|
def check_dangling_cite(tex_path: Path, config: LintConfig) -> list[Finding]:
|
|
89
|
-
"""Check dangling citations. Supports
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
"""Check dangling citations. Supports LaTeX, PDF, and markdown input.
|
|
91
|
+
|
|
92
|
+
PDF and markdown both populate ``inline_citations`` +
|
|
93
|
+
``parsed_references`` on the context, so the same key-membership
|
|
94
|
+
check (``_check_context``) serves both; LaTeX is parsed from source.
|
|
95
|
+
"""
|
|
96
|
+
if config.is_pdf or config.is_markdown:
|
|
97
|
+
return _check_context(config.manuscript_context)
|
|
92
98
|
|
|
93
99
|
return _check_latex(tex_path, config)
|
|
@@ -2,6 +2,8 @@ r"""Check: dangling-ref — cross-reference has no matching target.
|
|
|
2
2
|
|
|
3
3
|
For LaTeX: \ref{X} has no matching \label{X}.
|
|
4
4
|
For PDF: rendered "??" patterns in section text (broken references).
|
|
5
|
+
For markdown: pandoc-crossref ``@fig:`` / ``@sec:`` / ``@tbl:`` reference
|
|
6
|
+
with no matching labelled element.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
9
|
from __future__ import annotations
|
|
@@ -102,6 +104,32 @@ def _check_pdf(ctx: object) -> list[Finding]:
|
|
|
102
104
|
return findings
|
|
103
105
|
|
|
104
106
|
|
|
107
|
+
def _check_markdown(md_path: Path) -> list[Finding]:
|
|
108
|
+
"""Flag pandoc-crossref references with no matching labelled element.
|
|
109
|
+
|
|
110
|
+
``@fig:x`` / ``@sec:x`` / ``@tbl:x`` whose target is not defined by any
|
|
111
|
+
block id in the document is dangling.
|
|
112
|
+
"""
|
|
113
|
+
from sciwrite_lint.markdown_cites import analyze_markdown
|
|
114
|
+
|
|
115
|
+
analysis = analyze_markdown(md_path.read_text(encoding="utf-8"))
|
|
116
|
+
findings: list[Finding] = []
|
|
117
|
+
seen: set[str] = set()
|
|
118
|
+
for xref in analysis.crossrefs:
|
|
119
|
+
if xref.target not in analysis.labels and xref.target not in seen:
|
|
120
|
+
seen.add(xref.target)
|
|
121
|
+
findings.append(
|
|
122
|
+
Finding(
|
|
123
|
+
level="error",
|
|
124
|
+
rule_id="dangling-ref",
|
|
125
|
+
message=f"Cross-reference '@{xref.target}' has no matching label",
|
|
126
|
+
file=md_path.name,
|
|
127
|
+
context=xref.target,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
return findings
|
|
131
|
+
|
|
132
|
+
|
|
105
133
|
@check(
|
|
106
134
|
id="dangling-ref",
|
|
107
135
|
category="manuscript",
|
|
@@ -109,7 +137,9 @@ def _check_pdf(ctx: object) -> list[Finding]:
|
|
|
109
137
|
description="Cross-reference has no matching target.",
|
|
110
138
|
)
|
|
111
139
|
def check_dangling_ref(tex_path: Path, config: LintConfig) -> list[Finding]:
|
|
112
|
-
"""Check dangling references. Supports
|
|
140
|
+
"""Check dangling references. Supports LaTeX, PDF, and markdown input."""
|
|
141
|
+
if config.is_markdown:
|
|
142
|
+
return _check_markdown(tex_path)
|
|
113
143
|
if config.is_pdf:
|
|
114
144
|
return _check_pdf(config.manuscript_context)
|
|
115
145
|
|
|
@@ -570,6 +570,29 @@ If FIGURE DESCRIPTIONS says "Not available.", return {{"issues": []}}.\
|
|
|
570
570
|
]
|
|
571
571
|
|
|
572
572
|
|
|
573
|
+
def selected_check_defs(
|
|
574
|
+
config: "LintConfig", *, has_figures: bool
|
|
575
|
+
) -> list[tuple[str, str, str, str, bool]]:
|
|
576
|
+
"""Full-paper check defs that should actually run.
|
|
577
|
+
|
|
578
|
+
Single source of truth for two filters applied together:
|
|
579
|
+
|
|
580
|
+
1. Figure availability — figure checks are pointless without figure
|
|
581
|
+
descriptions.
|
|
582
|
+
2. The enabled-check gate (``config.is_check_enabled`` — honors both
|
|
583
|
+
``--checks`` and ``[lint] disabled_rules``).
|
|
584
|
+
|
|
585
|
+
Query-building and result-processing both call this so they select the
|
|
586
|
+
same checks in the same order. If they diverge, batched LLM results get
|
|
587
|
+
zipped against the wrong ``check_id``.
|
|
588
|
+
"""
|
|
589
|
+
return [
|
|
590
|
+
d
|
|
591
|
+
for d in _CHECK_DEFS
|
|
592
|
+
if (has_figures or not d[4]) and config.is_check_enabled(d[0])
|
|
593
|
+
]
|
|
594
|
+
|
|
595
|
+
|
|
573
596
|
# ---------------------------------------------------------------------------
|
|
574
597
|
# Factory: build_queries / process_results for each check
|
|
575
598
|
# ---------------------------------------------------------------------------
|