sciwrite-lint 0.2.2__tar.gz → 0.3.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.2.2/sciwrite_lint.egg-info → sciwrite_lint-0.3.0}/PKG-INFO +50 -17
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/README.md +43 -13
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/pyproject.toml +8 -5
- sciwrite_lint-0.3.0/sciwrite_lint/__init__.py +28 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/__main__.py +11 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/_network.py +17 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/api.py +23 -5
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/full_paper_consistency.py +6 -2
- sciwrite_lint-0.3.0/sciwrite_lint/checks/prose_quality.py +541 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/ref_internal_checks.py +17 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/reference_exists.py +18 -2
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/registry.py +1 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/check.py +96 -27
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/config.py +5 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/fetch.py +75 -25
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/__init__.py +62 -0
- sciwrite_lint-0.2.2/sciwrite_lint/cli/misc.py → sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/_monitor.py +7 -584
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/containers.py +216 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/dismiss_claim.py +62 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/grobid.py +53 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/init.py +14 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/override.py +66 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/parse.py +100 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/vision.py +60 -0
- sciwrite_lint-0.3.0/sciwrite_lint/cli/misc/vllm.py +42 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/verify.py +48 -22
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/config.py +118 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/eval_claims.py +85 -6
- sciwrite_lint-0.3.0/sciwrite_lint/exceptions.py +17 -0
- sciwrite_lint-0.3.0/sciwrite_lint/footnote_urls.py +580 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/__init__.py +193 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/_common.py +221 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/_download.py +125 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/_orchestrator.py +127 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/_search.py +219 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/_validation.py +413 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/arxiv.py +30 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/biorxiv.py +36 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/core.py +98 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/eric.py +159 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/europepmc.py +76 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/hal.py +171 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/ideas.py +239 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/nasa_ads.py +214 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/nber.py +157 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/osf.py +218 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/pmc.py +69 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/s2.py +28 -0
- sciwrite_lint-0.3.0/sciwrite_lint/fulltext/unpaywall.py +59 -0
- sciwrite_lint-0.3.0/sciwrite_lint/latex_to_markdown.py +136 -0
- sciwrite_lint-0.3.0/sciwrite_lint/llm_utils.py +606 -0
- sciwrite_lint-0.3.0/sciwrite_lint/local_sources.py +420 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/manuscript_store.py +100 -12
- sciwrite_lint-0.3.0/sciwrite_lint/mhtml.py +197 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/models.py +2 -0
- sciwrite_lint-0.3.0/sciwrite_lint/oa/__init__.py +42 -0
- sciwrite_lint-0.3.0/sciwrite_lint/oa/_models.py +91 -0
- sciwrite_lint-0.3.0/sciwrite_lint/oa/_pdf.py +404 -0
- sciwrite_lint-0.3.0/sciwrite_lint/oa/_search.py +113 -0
- sciwrite_lint-0.3.0/sciwrite_lint/oa/_web.py +165 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/pdf/grobid.py +109 -1
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/pdf/pdf_download.py +29 -31
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/__init__.py +147 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/checks.py +64 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/claims.py +116 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/embeddings.py +234 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/fetch.py +276 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/orchestration.py +413 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/parse.py +57 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/pdf_context.py +87 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/ref_internal.py +26 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/runners.py +496 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/staged.py +755 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/swap.py +277 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/tracking.py +105 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/unreliable.py +47 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/verify.py +526 -0
- sciwrite_lint-0.3.0/sciwrite_lint/pipeline/vision.py +191 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/citations.py +58 -4
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/metadata.py +10 -2
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/__init__.py +134 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/_core.py +329 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/bib_checks.py +82 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/citation_metadata.py +178 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/claim_results.py +144 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/parse_cache.py +136 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/pipeline_stage.py +82 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/query_vectors.py +32 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/ref_internal.py +101 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/registry.py +259 -0
- sciwrite_lint-0.3.0/sciwrite_lint/references/workspace_db/vision_cache.py +143 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/schemas.py +32 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/tex_parser.py +67 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vision/pipeline.py +3 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vllm/vllm_server.py +1 -0
- sciwrite_lint-0.3.0/sciwrite_lint/web.py +595 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0/sciwrite_lint.egg-info}/PKG-INFO +50 -17
- sciwrite_lint-0.3.0/sciwrite_lint.egg-info/SOURCES.txt +142 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint.egg-info/requires.txt +6 -3
- sciwrite_lint-0.2.2/sciwrite_lint/__init__.py +0 -3
- sciwrite_lint-0.2.2/sciwrite_lint/fulltext.py +0 -863
- sciwrite_lint-0.2.2/sciwrite_lint/llm_utils.py +0 -415
- sciwrite_lint-0.2.2/sciwrite_lint/local_pdfs.py +0 -122
- sciwrite_lint-0.2.2/sciwrite_lint/pipeline.py +0 -3102
- sciwrite_lint-0.2.2/sciwrite_lint/references/workspace_db.py +0 -1508
- sciwrite_lint-0.2.2/sciwrite_lint/web.py +0 -369
- sciwrite_lint-0.2.2/sciwrite_lint.egg-info/SOURCES.txt +0 -80
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/LICENSE +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/_section_utils.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/cite_purpose.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/claim_support.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/cross_section_consistency.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/dangling_cite.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/dangling_ref.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/reference_accuracy.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/reference_unreliable.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/retracted_cite.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/structure_promises.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/checks/unreferenced_figure.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/claims.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/claude_backend.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/claude_cli.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cli/rank.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/cross_paper.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/pdf/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/prompt_safety.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/py.typed +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/rate_limiter.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/crossref.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/embedding_store.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/matching.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/reference_store.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/references/retraction_watch.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/report.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/scoring/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/scoring/chain.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/scoring/contribution.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/scoring/scilint_score.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/usage.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vision/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vision/cache.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vision/describe.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vision/image_extraction.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vllm/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint/vllm/metrics.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint.egg-info/dependency_links.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint.egg-info/entry_points.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.0}/sciwrite_lint.egg-info/top_level.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.3.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.3.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>
|
|
@@ -41,12 +41,15 @@ Requires-Dist: numpy>=2.0
|
|
|
41
41
|
Requires-Dist: sqlite-vec>=0.1.6
|
|
42
42
|
Requires-Dist: torch>=2.5
|
|
43
43
|
Requires-Dist: transformers>=4.50
|
|
44
|
-
Requires-Dist: Pillow>=12.
|
|
45
|
-
Requires-Dist: pypdf>=6.
|
|
44
|
+
Requires-Dist: Pillow>=12.2
|
|
45
|
+
Requires-Dist: pypdf>=6.10.2
|
|
46
46
|
Requires-Dist: pypdfium2>=5.0
|
|
47
47
|
Requires-Dist: pdf2image>=1.17
|
|
48
|
+
Requires-Dist: lxml>=6.1
|
|
49
|
+
Requires-Dist: pypandoc-binary>=1.15
|
|
50
|
+
Requires-Dist: rfc3986>=2.0
|
|
48
51
|
Provides-Extra: dev
|
|
49
|
-
Requires-Dist: pytest>=
|
|
52
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
50
53
|
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
51
54
|
Dynamic: license-file
|
|
52
55
|
|
|
@@ -66,7 +69,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
66
69
|
|
|
67
70
|
## Features
|
|
68
71
|
|
|
69
|
-
**
|
|
72
|
+
**23 automated checks:**
|
|
70
73
|
|
|
71
74
|
### Reference verification
|
|
72
75
|
- **Do your references exist?** — checked against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress
|
|
@@ -75,7 +78,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
75
78
|
- **Robust matching** — when references lack DOIs, a multi-signal matching engine scores candidates across title, author, year, and venue (handles the metadata errors that LLMs routinely introduce)
|
|
76
79
|
|
|
77
80
|
### Claim verification (local LLM)
|
|
78
|
-
- **Do cited papers support your claims?** — downloads full text from
|
|
81
|
+
- **Do cited papers support your claims?** — downloads full text from 14 open-access sources (arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv/medRxiv, NBER, RePEc/IDEAS, HAL, ERIC, NASA ADS, OSF Preprints, CORE), parses via GROBID, embeds sections, and verifies each claim against the actual source text
|
|
79
82
|
- **What role does each citation play?** — classifies citation purpose (evidence, contrast, method, attribution, context…) with graduated weights: an unsupported *evidence* citation is serious; an unsupported *context* citation barely matters
|
|
80
83
|
- **Are your references' own bibliographies real?** — batch-checks cited papers' reference lists for existence, metadata accuracy, and retraction. Papers built on fabricated evidence are flagged
|
|
81
84
|
|
|
@@ -88,6 +91,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
88
91
|
- **Abstract–body alignment** — abstract makes factual claims the body contradicts
|
|
89
92
|
- **Statistical reporting** — p-values vs. their verbal interpretation
|
|
90
93
|
- **Structure promises** — contributions promised in the introduction but never delivered
|
|
94
|
+
- **Prose quality** — syntactic grammar errors and semantic word-choice mistakes (e.g. "object" where "purpose" is intended). Per-sentence review with paragraph context; severity driven by the model's confidence
|
|
91
95
|
|
|
92
96
|
### Figure checks (vision model + LLM)
|
|
93
97
|
- **Caption vs. content** — does the caption match what the figure actually shows?
|
|
@@ -128,7 +132,7 @@ sciwrite-lint contributions paper.pdf --format json
|
|
|
128
132
|
### Two audiences
|
|
129
133
|
|
|
130
134
|
- **Humans** — colored terminal output with severity levels, locations, and explanations. Decide in seconds whether each finding is real
|
|
131
|
-
- **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop.
|
|
135
|
+
- **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop. Standard `ruff`/`mypy`-style exit codes: `0` clean, `1` findings at error level, `2` tool error. Python-API consumers can `except sciwrite_lint.LLMConnectionError` (typed exception hierarchy under `SciWriteLintError`) instead of matching on error strings
|
|
132
136
|
|
|
133
137
|
### Optimizations
|
|
134
138
|
|
|
@@ -140,7 +144,7 @@ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-ba
|
|
|
140
144
|
- **Adaptive embedding batches** — token-aware batch sizing gives ~50x speedup over CPU while staying within VRAM limits
|
|
141
145
|
- **Batch-staged multi-paper pipeline** — when checking 2+ papers, GPU models load once per batch (vision/embedding/cited-vision) and vLLM/network stages run concurrently, giving a meaningful speedup over sequential per-paper runs. Tune via `--concurrency` (default 2, validated up to 4 on a single consumer GPU)
|
|
142
146
|
- **Phased API resolution** — citations flow through OpenAlex → Semantic Scholar → CrossRef → Open Library/LoC, each phase only processing what previous phases didn't resolve
|
|
143
|
-
- **
|
|
147
|
+
- **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
|
|
144
148
|
- **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
|
|
145
149
|
|
|
146
150
|
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. On native Linux, the pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50x faster than CPU). *(Native Linux GPU swap is preliminary — tested on WSL2 only; expected to work, may need minor fixes.)*
|
|
@@ -186,7 +190,7 @@ Activate the venv in each new terminal with `source .venv/bin/activate`. For mos
|
|
|
186
190
|
## Setup
|
|
187
191
|
|
|
188
192
|
```bash
|
|
189
|
-
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/
|
|
193
|
+
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/ + local_web/
|
|
190
194
|
sciwrite-lint config set-email you@example.com # required for Unpaywall + Retraction Watch
|
|
191
195
|
sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM)
|
|
192
196
|
sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
|
|
@@ -196,20 +200,45 @@ sciwrite-lint containers monitor # live dashboard: service health, VRAM, K
|
|
|
196
200
|
|
|
197
201
|
`init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
|
|
198
202
|
|
|
199
|
-
**Providing
|
|
203
|
+
**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:
|
|
200
204
|
|
|
205
|
+
- `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.
|
|
206
|
+
- `local_web/` — **web captures**. Accepts `.md` (hand-written or pre-extracted) and `.mhtml` / `.mht` saved from a browser via **File → Save As → Webpage (Single File)**. Use this for JavaScript-heavy pages where a headless HTTP fetch would only see the pre-hydration shell; MHTML captures the rendered DOM post-JS. sciwrite-lint converts MHTML to markdown at ingest using the same trafilatura extractor it uses for live web fetches, so the output format is consistent either way. Anything landing here is classified `local_type=web_page` — softer evidence than a peer-reviewed PDF.
|
|
207
|
+
|
|
208
|
+
Name files by the reference title, or prefix them with the citekey:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
local_pdfs/ local_web/
|
|
212
|
+
├── States of Curiosity … Learning.pdf ├── dewey1910_How_We_Think.mhtml
|
|
213
|
+
├── Mind in Society.pdf ├── earthwatch2025_About.md
|
|
214
|
+
├── vanfraassen1980_The_Scientific_Image.pdf └── bca2026_Research_Teachers.md
|
|
215
|
+
└── dewey2016_summary.md # reader-written
|
|
201
216
|
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
|
|
218
|
+
Matching runs in two passes for both folders. **(1) Citekey prefix:** if the filename begins with a known bib citekey (case-insensitive, e.g. `vanfraassen1980_…`), the file is matched directly to that reference — no fuzzy scoring. This is the authoritative path and works reliably for archives that follow a `{citekey}_{Title}.<ext>` convention, including short titles where length differences would otherwise penalize the score. **(2) Fuzzy title:** filenames without a recognized citekey prefix fall through to fuzzy matching against `.bib` titles (threshold 0.80). Academic matches win over web matches when the same reference is present in both folders — PDF trumps web capture. Matched files are copied into the workspace; on the next run the reference upgrades from T2 to T1 and goes through GROBID parsing (for PDFs) or direct read (for `.md`) during claim verification.
|
|
219
|
+
|
|
220
|
+
**Per-paper curated archives.** Each paper can point both drop folders at its own directories in its `[[papers]]` block:
|
|
221
|
+
|
|
222
|
+
```toml
|
|
223
|
+
[[papers]]
|
|
224
|
+
name = "my-paper"
|
|
225
|
+
file_path = "papers/my-paper/my-paper.tex"
|
|
226
|
+
bib = "papers/my-paper/my-paper.bib"
|
|
227
|
+
local_pdfs_dir = "papers/my-paper/Sources/full_text" # academic archive
|
|
228
|
+
local_web_dir = "papers/my-paper/Sources/full_text_web" # web captures
|
|
206
229
|
```
|
|
207
230
|
|
|
208
|
-
|
|
231
|
+
If you don't set them explicitly, sciwrite-lint auto-detects sibling `Sources/full_text/` and `Sources/full_text_web/` directories next to `file_path`. Resolution order per paper: explicit override → auto-detected `Sources/full_text{,_web}/` → project-wide default.
|
|
232
|
+
|
|
233
|
+
**Refreshing a source file in place.** sciwrite-lint records the SHA-256 of each drop-folder file when it first ingests it. On every subsequent run the hash is re-checked: if you overwrite `dewey1910.pdf` with a corrected scan (or re-capture an MHTML, or edit a markdown summary) under the same filename, the hash changes and the tool automatically re-copies the file into the workspace — triggering a fresh GROBID parse and embedding for PDFs, a fresh MHTML-to-markdown conversion for web captures. You don't need `--fresh`, and you don't need to rename files. Unchanged files cost a single hash read per run and are otherwise skipped.
|
|
234
|
+
|
|
235
|
+
**Footnote URLs (`\footnote{\url{…}}`) get verified too.** Papers often cite informational web pages inline as a footnote URL rather than through a formal `.bib` entry — organisation pages, program descriptions, press releases, FAQs. sciwrite-lint matches every such URL to a `.md` file in `local_web_dir` by reading a `Source: https://…` (or `Source URL: …`) header from the first 20 lines of each file. The matched capture becomes a T1 source for claim verification — same as a cited reference. Browser-saved `.mhtml` gets this header written automatically at ingest; for hand-authored `.md` you add one line at the top. See [docs/local-sources.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/local-sources.md) for details and examples.
|
|
209
236
|
|
|
210
|
-
**Finding what to download:** `sciwrite-lint verify` lists T2 references that are confirmed open access with direct URLs — open in your browser and save to `local_pdfs
|
|
237
|
+
**Finding what to download:** `sciwrite-lint verify` lists T2 references that are confirmed open access with direct URLs — open each in your browser and save to `local_pdfs/` (or `local_web/` if the "paper" is actually a blog post or documentation page that the publisher serves as HTML). For paywalled papers, use your institution's library access.
|
|
211
238
|
|
|
212
|
-
**
|
|
239
|
+
**Fetched-PDF match validation.** Every downloaded PDF goes through a multi-signal match gate (title similarity, author surname, DOI, year, plus hard rejects for common bibliographic-record landing pages) before being cached. Wrong-paper downloads from API mismatches and template landing pages are rejected and deleted; the tool moves on to the next open-access source. You can still drop the correct PDF into `local_pdfs_dir` to skip the waterfall entirely.
|
|
240
|
+
|
|
241
|
+
**API keys** increase rate limits for Semantic Scholar, NCBI, and CORE, and enable NASA ADS (required for that source):
|
|
213
242
|
|
|
214
243
|
```bash
|
|
215
244
|
sciwrite-lint config show # see what's configured
|
|
@@ -227,6 +256,7 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
227
256
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
228
257
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
229
258
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
259
|
+
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
230
260
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
231
261
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
232
262
|
```
|
|
@@ -235,6 +265,8 @@ sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
|
235
265
|
|
|
236
266
|
Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
|
|
237
267
|
|
|
268
|
+
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.
|
|
269
|
+
|
|
238
270
|
### Contribution axes (`sciwrite-lint contributions`)
|
|
239
271
|
|
|
240
272
|
`contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
|
|
@@ -303,6 +335,7 @@ Output formats: terminal (default) or `--format json`.
|
|
|
303
335
|
- `sciwrite-lint checks` — list all checks
|
|
304
336
|
- `sciwrite-lint <command> --help` — detailed usage for any command
|
|
305
337
|
- [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md) — GROBID, vLLM, external APIs, configuration
|
|
338
|
+
- [docs/local-sources.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/local-sources.md) — drop directories, filename conventions, `Source:` header for footnote URLs
|
|
306
339
|
|
|
307
340
|
For contributors and advanced users:
|
|
308
341
|
|
|
@@ -14,7 +14,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**23 automated checks:**
|
|
18
18
|
|
|
19
19
|
### Reference verification
|
|
20
20
|
- **Do your references exist?** — checked against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress
|
|
@@ -23,7 +23,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
23
23
|
- **Robust matching** — when references lack DOIs, a multi-signal matching engine scores candidates across title, author, year, and venue (handles the metadata errors that LLMs routinely introduce)
|
|
24
24
|
|
|
25
25
|
### Claim verification (local LLM)
|
|
26
|
-
- **Do cited papers support your claims?** — downloads full text from
|
|
26
|
+
- **Do cited papers support your claims?** — downloads full text from 14 open-access sources (arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv/medRxiv, NBER, RePEc/IDEAS, HAL, ERIC, NASA ADS, OSF Preprints, CORE), parses via GROBID, embeds sections, and verifies each claim against the actual source text
|
|
27
27
|
- **What role does each citation play?** — classifies citation purpose (evidence, contrast, method, attribution, context…) with graduated weights: an unsupported *evidence* citation is serious; an unsupported *context* citation barely matters
|
|
28
28
|
- **Are your references' own bibliographies real?** — batch-checks cited papers' reference lists for existence, metadata accuracy, and retraction. Papers built on fabricated evidence are flagged
|
|
29
29
|
|
|
@@ -36,6 +36,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
36
36
|
- **Abstract–body alignment** — abstract makes factual claims the body contradicts
|
|
37
37
|
- **Statistical reporting** — p-values vs. their verbal interpretation
|
|
38
38
|
- **Structure promises** — contributions promised in the introduction but never delivered
|
|
39
|
+
- **Prose quality** — syntactic grammar errors and semantic word-choice mistakes (e.g. "object" where "purpose" is intended). Per-sentence review with paragraph context; severity driven by the model's confidence
|
|
39
40
|
|
|
40
41
|
### Figure checks (vision model + LLM)
|
|
41
42
|
- **Caption vs. content** — does the caption match what the figure actually shows?
|
|
@@ -76,7 +77,7 @@ sciwrite-lint contributions paper.pdf --format json
|
|
|
76
77
|
### Two audiences
|
|
77
78
|
|
|
78
79
|
- **Humans** — colored terminal output with severity levels, locations, and explanations. Decide in seconds whether each finding is real
|
|
79
|
-
- **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop.
|
|
80
|
+
- **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop. Standard `ruff`/`mypy`-style exit codes: `0` clean, `1` findings at error level, `2` tool error. Python-API consumers can `except sciwrite_lint.LLMConnectionError` (typed exception hierarchy under `SciWriteLintError`) instead of matching on error strings
|
|
80
81
|
|
|
81
82
|
### Optimizations
|
|
82
83
|
|
|
@@ -88,7 +89,7 @@ Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-ba
|
|
|
88
89
|
- **Adaptive embedding batches** — token-aware batch sizing gives ~50x speedup over CPU while staying within VRAM limits
|
|
89
90
|
- **Batch-staged multi-paper pipeline** — when checking 2+ papers, GPU models load once per batch (vision/embedding/cited-vision) and vLLM/network stages run concurrently, giving a meaningful speedup over sequential per-paper runs. Tune via `--concurrency` (default 2, validated up to 4 on a single consumer GPU)
|
|
90
91
|
- **Phased API resolution** — citations flow through OpenAlex → Semantic Scholar → CrossRef → Open Library/LoC, each phase only processing what previous phases didn't resolve
|
|
91
|
-
- **
|
|
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
|
|
92
93
|
- **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
|
|
93
94
|
|
|
94
95
|
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. On native Linux, the pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50x faster than CPU). *(Native Linux GPU swap is preliminary — tested on WSL2 only; expected to work, may need minor fixes.)*
|
|
@@ -134,7 +135,7 @@ Activate the venv in each new terminal with `source .venv/bin/activate`. For mos
|
|
|
134
135
|
## Setup
|
|
135
136
|
|
|
136
137
|
```bash
|
|
137
|
-
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/
|
|
138
|
+
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/ + local_web/
|
|
138
139
|
sciwrite-lint config set-email you@example.com # required for Unpaywall + Retraction Watch
|
|
139
140
|
sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM)
|
|
140
141
|
sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
|
|
@@ -144,20 +145,45 @@ sciwrite-lint containers monitor # live dashboard: service health, VRAM, K
|
|
|
144
145
|
|
|
145
146
|
`init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
|
|
146
147
|
|
|
147
|
-
**Providing
|
|
148
|
+
**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:
|
|
148
149
|
|
|
150
|
+
- `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.
|
|
151
|
+
- `local_web/` — **web captures**. Accepts `.md` (hand-written or pre-extracted) and `.mhtml` / `.mht` saved from a browser via **File → Save As → Webpage (Single File)**. Use this for JavaScript-heavy pages where a headless HTTP fetch would only see the pre-hydration shell; MHTML captures the rendered DOM post-JS. sciwrite-lint converts MHTML to markdown at ingest using the same trafilatura extractor it uses for live web fetches, so the output format is consistent either way. Anything landing here is classified `local_type=web_page` — softer evidence than a peer-reviewed PDF.
|
|
152
|
+
|
|
153
|
+
Name files by the reference title, or prefix them with the citekey:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
local_pdfs/ local_web/
|
|
157
|
+
├── States of Curiosity … Learning.pdf ├── dewey1910_How_We_Think.mhtml
|
|
158
|
+
├── Mind in Society.pdf ├── earthwatch2025_About.md
|
|
159
|
+
├── vanfraassen1980_The_Scientific_Image.pdf └── bca2026_Research_Teachers.md
|
|
160
|
+
└── dewey2016_summary.md # reader-written
|
|
149
161
|
```
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
|
|
163
|
+
Matching runs in two passes for both folders. **(1) Citekey prefix:** if the filename begins with a known bib citekey (case-insensitive, e.g. `vanfraassen1980_…`), the file is matched directly to that reference — no fuzzy scoring. This is the authoritative path and works reliably for archives that follow a `{citekey}_{Title}.<ext>` convention, including short titles where length differences would otherwise penalize the score. **(2) Fuzzy title:** filenames without a recognized citekey prefix fall through to fuzzy matching against `.bib` titles (threshold 0.80). Academic matches win over web matches when the same reference is present in both folders — PDF trumps web capture. Matched files are copied into the workspace; on the next run the reference upgrades from T2 to T1 and goes through GROBID parsing (for PDFs) or direct read (for `.md`) during claim verification.
|
|
164
|
+
|
|
165
|
+
**Per-paper curated archives.** Each paper can point both drop folders at its own directories in its `[[papers]]` block:
|
|
166
|
+
|
|
167
|
+
```toml
|
|
168
|
+
[[papers]]
|
|
169
|
+
name = "my-paper"
|
|
170
|
+
file_path = "papers/my-paper/my-paper.tex"
|
|
171
|
+
bib = "papers/my-paper/my-paper.bib"
|
|
172
|
+
local_pdfs_dir = "papers/my-paper/Sources/full_text" # academic archive
|
|
173
|
+
local_web_dir = "papers/my-paper/Sources/full_text_web" # web captures
|
|
154
174
|
```
|
|
155
175
|
|
|
156
|
-
|
|
176
|
+
If you don't set them explicitly, sciwrite-lint auto-detects sibling `Sources/full_text/` and `Sources/full_text_web/` directories next to `file_path`. Resolution order per paper: explicit override → auto-detected `Sources/full_text{,_web}/` → project-wide default.
|
|
177
|
+
|
|
178
|
+
**Refreshing a source file in place.** sciwrite-lint records the SHA-256 of each drop-folder file when it first ingests it. On every subsequent run the hash is re-checked: if you overwrite `dewey1910.pdf` with a corrected scan (or re-capture an MHTML, or edit a markdown summary) under the same filename, the hash changes and the tool automatically re-copies the file into the workspace — triggering a fresh GROBID parse and embedding for PDFs, a fresh MHTML-to-markdown conversion for web captures. You don't need `--fresh`, and you don't need to rename files. Unchanged files cost a single hash read per run and are otherwise skipped.
|
|
179
|
+
|
|
180
|
+
**Footnote URLs (`\footnote{\url{…}}`) get verified too.** Papers often cite informational web pages inline as a footnote URL rather than through a formal `.bib` entry — organisation pages, program descriptions, press releases, FAQs. sciwrite-lint matches every such URL to a `.md` file in `local_web_dir` by reading a `Source: https://…` (or `Source URL: …`) header from the first 20 lines of each file. The matched capture becomes a T1 source for claim verification — same as a cited reference. Browser-saved `.mhtml` gets this header written automatically at ingest; for hand-authored `.md` you add one line at the top. See [docs/local-sources.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/local-sources.md) for details and examples.
|
|
157
181
|
|
|
158
|
-
**Finding what to download:** `sciwrite-lint verify` lists T2 references that are confirmed open access with direct URLs — open in your browser and save to `local_pdfs
|
|
182
|
+
**Finding what to download:** `sciwrite-lint verify` lists T2 references that are confirmed open access with direct URLs — open each in your browser and save to `local_pdfs/` (or `local_web/` if the "paper" is actually a blog post or documentation page that the publisher serves as HTML). For paywalled papers, use your institution's library access.
|
|
159
183
|
|
|
160
|
-
**
|
|
184
|
+
**Fetched-PDF match validation.** Every downloaded PDF goes through a multi-signal match gate (title similarity, author surname, DOI, year, plus hard rejects for common bibliographic-record landing pages) before being cached. Wrong-paper downloads from API mismatches and template landing pages are rejected and deleted; the tool moves on to the next open-access source. You can still drop the correct PDF into `local_pdfs_dir` to skip the waterfall entirely.
|
|
185
|
+
|
|
186
|
+
**API keys** increase rate limits for Semantic Scholar, NCBI, and CORE, and enable NASA ADS (required for that source):
|
|
161
187
|
|
|
162
188
|
```bash
|
|
163
189
|
sciwrite-lint config show # see what's configured
|
|
@@ -175,6 +201,7 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
175
201
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
176
202
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
177
203
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
204
|
+
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
178
205
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
179
206
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
180
207
|
```
|
|
@@ -183,6 +210,8 @@ sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
|
183
210
|
|
|
184
211
|
Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
|
|
185
212
|
|
|
213
|
+
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.
|
|
214
|
+
|
|
186
215
|
### Contribution axes (`sciwrite-lint contributions`)
|
|
187
216
|
|
|
188
217
|
`contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
|
|
@@ -251,6 +280,7 @@ Output formats: terminal (default) or `--format json`.
|
|
|
251
280
|
- `sciwrite-lint checks` — list all checks
|
|
252
281
|
- `sciwrite-lint <command> --help` — detailed usage for any command
|
|
253
282
|
- [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md) — GROBID, vLLM, external APIs, configuration
|
|
283
|
+
- [docs/local-sources.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/local-sources.md) — drop directories, filename conventions, `Source:` header for footnote URLs
|
|
254
284
|
|
|
255
285
|
For contributors and advanced users:
|
|
256
286
|
|
|
@@ -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.3.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"
|
|
@@ -42,10 +42,13 @@ dependencies = [
|
|
|
42
42
|
"sqlite-vec>=0.1.6",
|
|
43
43
|
"torch>=2.5",
|
|
44
44
|
"transformers>=4.50",
|
|
45
|
-
"Pillow>=12.
|
|
46
|
-
"pypdf>=6.
|
|
45
|
+
"Pillow>=12.2",
|
|
46
|
+
"pypdf>=6.10.2",
|
|
47
47
|
"pypdfium2>=5.0",
|
|
48
48
|
"pdf2image>=1.17",
|
|
49
|
+
"lxml>=6.1",
|
|
50
|
+
"pypandoc-binary>=1.15",
|
|
51
|
+
"rfc3986>=2.0",
|
|
49
52
|
]
|
|
50
53
|
|
|
51
54
|
[project.urls]
|
|
@@ -57,7 +60,7 @@ Paper = "https://arxiv.org/abs/2604.08501"
|
|
|
57
60
|
|
|
58
61
|
[project.optional-dependencies]
|
|
59
62
|
dev = [
|
|
60
|
-
"pytest>=
|
|
63
|
+
"pytest>=9.0.3",
|
|
61
64
|
"pytest-xdist>=3.0",
|
|
62
65
|
]
|
|
63
66
|
|
|
@@ -106,5 +109,5 @@ DEP003 = ["sciwrite_lint", "evals", "eval_real_world"]
|
|
|
106
109
|
plugins = ["pydantic.mypy"]
|
|
107
110
|
[[tool.mypy.overrides]]
|
|
108
111
|
# No py.typed marker or typeshed stubs available
|
|
109
|
-
module = ["bibtexparser", "sqlite_vec", "pypdf", "pypdfium2", "pypdfium2.raw", "transformers", "pdf2image"]
|
|
112
|
+
module = ["bibtexparser", "sqlite_vec", "pypdf", "pypdfium2", "pypdfium2.raw", "transformers", "pdf2image", "lxml", "lxml.html", "lxml.html.etree", "rfc3986", "pypandoc"]
|
|
110
113
|
ignore_missing_imports = true
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""sciwrite-lint: a linter for scientific manuscripts."""
|
|
2
|
+
|
|
3
|
+
from sciwrite_lint.exceptions import LLMConnectionError, SciWriteLintError
|
|
4
|
+
|
|
5
|
+
__version__ = "0.3.0"
|
|
6
|
+
|
|
7
|
+
from sciwrite_lint.oa import (
|
|
8
|
+
DownloadResult,
|
|
9
|
+
FetchConfig,
|
|
10
|
+
SearchHit,
|
|
11
|
+
WebResult,
|
|
12
|
+
download_pdf,
|
|
13
|
+
fetch_web,
|
|
14
|
+
search_by_title,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DownloadResult",
|
|
19
|
+
"FetchConfig",
|
|
20
|
+
"LLMConnectionError",
|
|
21
|
+
"SciWriteLintError",
|
|
22
|
+
"SearchHit",
|
|
23
|
+
"WebResult",
|
|
24
|
+
"__version__",
|
|
25
|
+
"download_pdf",
|
|
26
|
+
"fetch_web",
|
|
27
|
+
"search_by_title",
|
|
28
|
+
]
|
|
@@ -189,6 +189,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
189
189
|
default=None,
|
|
190
190
|
help="Vision backend: transformers (2B, default) or vllm (8B FP8 on port 5002)",
|
|
191
191
|
)
|
|
192
|
+
p_check.add_argument(
|
|
193
|
+
"--checks",
|
|
194
|
+
default=None,
|
|
195
|
+
metavar="ID[,ID...]",
|
|
196
|
+
help=(
|
|
197
|
+
"Run only the listed check IDs (comma-separated). Other checks "
|
|
198
|
+
"are disabled for this invocation. Example: "
|
|
199
|
+
"--checks prose-quality,dangling-cite. Run `sciwrite-lint checks` "
|
|
200
|
+
"to list available IDs."
|
|
201
|
+
),
|
|
202
|
+
)
|
|
192
203
|
p_check.set_defaults(func=run_check)
|
|
193
204
|
|
|
194
205
|
# --- checks ---
|
|
@@ -147,8 +147,15 @@ async def stream_with_limit(
|
|
|
147
147
|
size we actually read. Always enforces *max_bytes* on the
|
|
148
148
|
decompressed data stream via ``aiter_bytes()``.
|
|
149
149
|
|
|
150
|
-
Returns a synthetic ``httpx.Response`` with the full body
|
|
151
|
-
Raises ``ResponseTooLarge`` if the limit is exceeded.
|
|
150
|
+
Returns a synthetic ``httpx.Response`` with the full decompressed body
|
|
151
|
+
populated. Raises ``ResponseTooLarge`` if the limit is exceeded.
|
|
152
|
+
|
|
153
|
+
Content-Encoding (and the now-stale Content-Length) is stripped from the
|
|
154
|
+
synthetic response's headers. ``aiter_bytes`` already decompressed the
|
|
155
|
+
body; leaving the header intact would cause httpx to try to decompress a
|
|
156
|
+
second time when the caller reads ``.content``, which fails with
|
|
157
|
+
``DecodingError: incorrect header check`` on servers that gzip real
|
|
158
|
+
binary content (observed on ``mpra.ub.uni-muenchen.de`` serving PDFs).
|
|
152
159
|
"""
|
|
153
160
|
async with client.stream("GET", url, **kwargs) as resp: # type: ignore[arg-type]
|
|
154
161
|
# Non-success responses are small — read eagerly for diagnostics
|
|
@@ -176,9 +183,16 @@ async def stream_with_limit(
|
|
|
176
183
|
chunks.append(chunk)
|
|
177
184
|
|
|
178
185
|
body = b"".join(chunks)
|
|
186
|
+
synthetic_headers = httpx.Headers(
|
|
187
|
+
[
|
|
188
|
+
(name, value)
|
|
189
|
+
for name, value in resp.headers.raw
|
|
190
|
+
if name.lower() not in (b"content-encoding", b"content-length")
|
|
191
|
+
]
|
|
192
|
+
)
|
|
179
193
|
return httpx.Response(
|
|
180
194
|
status_code=resp.status_code,
|
|
181
|
-
headers=
|
|
195
|
+
headers=synthetic_headers,
|
|
182
196
|
content=body,
|
|
183
197
|
request=resp.request,
|
|
184
198
|
)
|
|
@@ -1436,15 +1436,33 @@ async def _verify_web_resource(
|
|
|
1436
1436
|
c.issues.append(
|
|
1437
1437
|
f"URL alive but content extraction failed: {web_result.error or 'unknown'}"
|
|
1438
1438
|
)
|
|
1439
|
+
elif web_result.blocked:
|
|
1440
|
+
# Unverifiable: 4xx refusal, 5xx error, TLS/timeout/connection/
|
|
1441
|
+
# decoding/protocol/oversized. The URL may still be valid — we
|
|
1442
|
+
# just could not confirm. Downstream emits a WARN telling the
|
|
1443
|
+
# user to verify manually; distinct from web_dead (ERROR).
|
|
1444
|
+
c.api_match = "web_blocked"
|
|
1445
|
+
c.api_source = "web"
|
|
1446
|
+
c.api_data = {
|
|
1447
|
+
"source": "web",
|
|
1448
|
+
"url": resolved_url,
|
|
1449
|
+
"status_code": web_result.status_code,
|
|
1450
|
+
}
|
|
1451
|
+
reason = web_result.error or "unknown reason"
|
|
1452
|
+
c.issues.append(
|
|
1453
|
+
f"Blocked by {reason}: {resolved_url} — "
|
|
1454
|
+
f"unable to verify automatically, please check manually"
|
|
1455
|
+
)
|
|
1439
1456
|
else:
|
|
1457
|
+
# Genuinely dead: server explicitly returned 404 or 410 (or the
|
|
1458
|
+
# URL was structurally invalid). Safe to flag as ERROR — cited
|
|
1459
|
+
# resource cannot be retrieved at this URL and likely cannot be
|
|
1460
|
+
# retrieved anywhere at this URL.
|
|
1440
1461
|
c.api_match = "web_dead"
|
|
1441
1462
|
c.api_source = "web"
|
|
1442
1463
|
c.api_data = {"source": "web", "url": resolved_url}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
c.issues.append(f"Dead URL ({web_result.error}): {resolved_url}")
|
|
1446
|
-
else:
|
|
1447
|
-
c.issues.append(f"Dead URL (HTTP {web_result.status_code}): {resolved_url}")
|
|
1464
|
+
reason = web_result.error or f"HTTP {web_result.status_code}"
|
|
1465
|
+
c.issues.append(f"Dead URL ({reason}): {resolved_url}")
|
|
1448
1466
|
|
|
1449
1467
|
return c.api_data
|
|
1450
1468
|
|
|
@@ -96,11 +96,15 @@ Return {{"issues": []}} if no genuine issues found.\
|
|
|
96
96
|
_OVERHEAD_TOKENS = 2500
|
|
97
97
|
|
|
98
98
|
# Output budget reserved for the JSON response. Matches the vLLM model
|
|
99
|
-
# default (VLLM_MODELS["qwen3"]["max_tokens"])
|
|
100
|
-
#
|
|
99
|
+
# default (VLLM_MODELS["qwen3"]["max_tokens"]) — this is the response
|
|
100
|
+
# portion only. ``llm_query`` adds the active thinking budget on top
|
|
101
|
+
# when dispatching to vLLM. FullPaperIssueList caps the issues list at
|
|
102
|
+
# 5, which fits comfortably in this budget.
|
|
101
103
|
_OUTPUT_RESERVE_TOKENS = 2048
|
|
102
104
|
|
|
103
105
|
# Worst-case thinking budget (medium preset, see THINKING_PRESETS).
|
|
106
|
+
# Reserved in the context-window accounting alongside the output budget;
|
|
107
|
+
# the sum matches what ``llm_query`` sends as ``max_tokens``.
|
|
104
108
|
_THINKING_RESERVE_TOKENS = 1024
|
|
105
109
|
|
|
106
110
|
# Rough chars-to-tokens ratio (conservative: overestimates tokens).
|