sciwrite-lint 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. sciwrite_lint-0.2.0/LICENSE +21 -0
  2. sciwrite_lint-0.2.0/PKG-INFO +268 -0
  3. sciwrite_lint-0.2.0/README.md +223 -0
  4. sciwrite_lint-0.2.0/pyproject.toml +103 -0
  5. sciwrite_lint-0.2.0/sciwrite_lint/__init__.py +3 -0
  6. sciwrite_lint-0.2.0/sciwrite_lint/__main__.py +527 -0
  7. sciwrite_lint-0.2.0/sciwrite_lint/_network.py +195 -0
  8. sciwrite_lint-0.2.0/sciwrite_lint/api.py +1484 -0
  9. sciwrite_lint-0.2.0/sciwrite_lint/checks/__init__.py +1 -0
  10. sciwrite_lint-0.2.0/sciwrite_lint/checks/_section_utils.py +111 -0
  11. sciwrite_lint-0.2.0/sciwrite_lint/checks/cite_purpose.py +122 -0
  12. sciwrite_lint-0.2.0/sciwrite_lint/checks/claim_support.py +96 -0
  13. sciwrite_lint-0.2.0/sciwrite_lint/checks/cross_section_consistency.py +185 -0
  14. sciwrite_lint-0.2.0/sciwrite_lint/checks/dangling_cite.py +93 -0
  15. sciwrite_lint-0.2.0/sciwrite_lint/checks/dangling_ref.py +116 -0
  16. sciwrite_lint-0.2.0/sciwrite_lint/checks/full_paper_consistency.py +604 -0
  17. sciwrite_lint-0.2.0/sciwrite_lint/checks/ref_internal_checks.py +919 -0
  18. sciwrite_lint-0.2.0/sciwrite_lint/checks/reference_accuracy.py +277 -0
  19. sciwrite_lint-0.2.0/sciwrite_lint/checks/reference_exists.py +119 -0
  20. sciwrite_lint-0.2.0/sciwrite_lint/checks/reference_unreliable.py +244 -0
  21. sciwrite_lint-0.2.0/sciwrite_lint/checks/registry.py +136 -0
  22. sciwrite_lint-0.2.0/sciwrite_lint/checks/retracted_cite.py +96 -0
  23. sciwrite_lint-0.2.0/sciwrite_lint/checks/structure_promises.py +115 -0
  24. sciwrite_lint-0.2.0/sciwrite_lint/checks/unreferenced_figure.py +70 -0
  25. sciwrite_lint-0.2.0/sciwrite_lint/claims.py +330 -0
  26. sciwrite_lint-0.2.0/sciwrite_lint/claude_backend.py +94 -0
  27. sciwrite_lint-0.2.0/sciwrite_lint/claude_cli.py +405 -0
  28. sciwrite_lint-0.2.0/sciwrite_lint/cli/__init__.py +1 -0
  29. sciwrite_lint-0.2.0/sciwrite_lint/cli/check.py +480 -0
  30. sciwrite_lint-0.2.0/sciwrite_lint/cli/config.py +229 -0
  31. sciwrite_lint-0.2.0/sciwrite_lint/cli/fetch.py +250 -0
  32. sciwrite_lint-0.2.0/sciwrite_lint/cli/misc.py +1202 -0
  33. sciwrite_lint-0.2.0/sciwrite_lint/cli/rank.py +470 -0
  34. sciwrite_lint-0.2.0/sciwrite_lint/cli/verify.py +437 -0
  35. sciwrite_lint-0.2.0/sciwrite_lint/config.py +646 -0
  36. sciwrite_lint-0.2.0/sciwrite_lint/cross_paper.py +174 -0
  37. sciwrite_lint-0.2.0/sciwrite_lint/eval_claims.py +1196 -0
  38. sciwrite_lint-0.2.0/sciwrite_lint/fulltext.py +851 -0
  39. sciwrite_lint-0.2.0/sciwrite_lint/llm_utils.py +386 -0
  40. sciwrite_lint-0.2.0/sciwrite_lint/local_pdfs.py +122 -0
  41. sciwrite_lint-0.2.0/sciwrite_lint/manuscript_store.py +674 -0
  42. sciwrite_lint-0.2.0/sciwrite_lint/models.py +139 -0
  43. sciwrite_lint-0.2.0/sciwrite_lint/pdf/__init__.py +1 -0
  44. sciwrite_lint-0.2.0/sciwrite_lint/pdf/grobid.py +785 -0
  45. sciwrite_lint-0.2.0/sciwrite_lint/pdf/pdf_download.py +258 -0
  46. sciwrite_lint-0.2.0/sciwrite_lint/pipeline.py +2694 -0
  47. sciwrite_lint-0.2.0/sciwrite_lint/prompt_safety.py +30 -0
  48. sciwrite_lint-0.2.0/sciwrite_lint/rate_limiter.py +227 -0
  49. sciwrite_lint-0.2.0/sciwrite_lint/references/__init__.py +1 -0
  50. sciwrite_lint-0.2.0/sciwrite_lint/references/citations.py +715 -0
  51. sciwrite_lint-0.2.0/sciwrite_lint/references/crossref.py +282 -0
  52. sciwrite_lint-0.2.0/sciwrite_lint/references/embedding_store.py +380 -0
  53. sciwrite_lint-0.2.0/sciwrite_lint/references/matching.py +273 -0
  54. sciwrite_lint-0.2.0/sciwrite_lint/references/metadata.py +273 -0
  55. sciwrite_lint-0.2.0/sciwrite_lint/references/reference_store.py +823 -0
  56. sciwrite_lint-0.2.0/sciwrite_lint/references/retraction_watch.py +178 -0
  57. sciwrite_lint-0.2.0/sciwrite_lint/references/workspace_db.py +1390 -0
  58. sciwrite_lint-0.2.0/sciwrite_lint/report.py +163 -0
  59. sciwrite_lint-0.2.0/sciwrite_lint/schemas.py +260 -0
  60. sciwrite_lint-0.2.0/sciwrite_lint/scoring/__init__.py +1 -0
  61. sciwrite_lint-0.2.0/sciwrite_lint/scoring/chain.py +716 -0
  62. sciwrite_lint-0.2.0/sciwrite_lint/scoring/contribution.py +322 -0
  63. sciwrite_lint-0.2.0/sciwrite_lint/scoring/scilint_score.py +611 -0
  64. sciwrite_lint-0.2.0/sciwrite_lint/tex_parser.py +248 -0
  65. sciwrite_lint-0.2.0/sciwrite_lint/usage.py +594 -0
  66. sciwrite_lint-0.2.0/sciwrite_lint/vision/__init__.py +1 -0
  67. sciwrite_lint-0.2.0/sciwrite_lint/vision/cache.py +140 -0
  68. sciwrite_lint-0.2.0/sciwrite_lint/vision/describe.py +311 -0
  69. sciwrite_lint-0.2.0/sciwrite_lint/vision/image_extraction.py +491 -0
  70. sciwrite_lint-0.2.0/sciwrite_lint/vision/pipeline.py +207 -0
  71. sciwrite_lint-0.2.0/sciwrite_lint/vllm/__init__.py +1 -0
  72. sciwrite_lint-0.2.0/sciwrite_lint/vllm/metrics.py +157 -0
  73. sciwrite_lint-0.2.0/sciwrite_lint/vllm/vllm_server.py +445 -0
  74. sciwrite_lint-0.2.0/sciwrite_lint/web.py +369 -0
  75. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/PKG-INFO +268 -0
  76. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/SOURCES.txt +79 -0
  77. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/dependency_links.txt +1 -0
  78. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/entry_points.txt +2 -0
  79. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/requires.txt +27 -0
  80. sciwrite_lint-0.2.0/sciwrite_lint.egg-info/top_level.txt +1 -0
  81. sciwrite_lint-0.2.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sergey Samsonau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.4
2
+ Name: sciwrite-lint
3
+ Version: 0.2.0
4
+ Summary: A linter for scientific manuscripts — reference verification, consistency checking, and structural validation.
5
+ Author: Sergey Samsonau
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/authentic-research-partners/sciwrite-lint
8
+ Project-URL: Repository, https://github.com/authentic-research-partners/sciwrite-lint
9
+ Project-URL: Issues, https://github.com/authentic-research-partners/sciwrite-lint/issues
10
+ Keywords: linter,scientific-writing,citation-verification,latex
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: <3.14,>=3.13
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: httpx>=0.27
19
+ Requires-Dist: tenacity>=8.0
20
+ Requires-Dist: trafilatura>=1.8
21
+ Requires-Dist: aiosqlite>=0.20
22
+ Requires-Dist: loguru>=0.7
23
+ Requires-Dist: anyascii>=0.3
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: defusedxml>=0.7
26
+ Requires-Dist: pydantic>=2.0
27
+ Requires-Dist: rapidfuzz>=3.0
28
+ Requires-Dist: bibtexparser>=1.4
29
+ Requires-Dist: pdfplumber>=0.10
30
+ Requires-Dist: openai>=1.0
31
+ Requires-Dist: grobid-tei-xml>=0.1
32
+ Requires-Dist: sentence-transformers>=3.0
33
+ Requires-Dist: numpy>=1.24
34
+ Requires-Dist: sqlite-vec>=0.1.6
35
+ Requires-Dist: torch>=2.0
36
+ Requires-Dist: transformers>=4.45
37
+ Requires-Dist: Pillow>=10.0
38
+ Requires-Dist: pypdf>=4.0
39
+ Requires-Dist: pypdfium2>=4.0
40
+ Requires-Dist: pdf2image>=1.16
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8.0; extra == "dev"
43
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # sciwrite-lint
47
+
48
+ A linter for scientific manuscripts. Checks that your references exist, your metadata is accurate, your citations support the claims you make, and your cited papers' own bibliographies are real. Works on LaTeX and PDF. Runs entirely on your machine. Produces a **SciLint Score**.
49
+
50
+ The only open-source tool that combines reference verification, claim checking, manuscript consistency, figure analysis, and bibliography verification in one pipeline — powered entirely by a local LLM on your own GPU.
51
+
52
+ ## Why
53
+
54
+ AI writing tools produce text that *looks* like good science — fluent prose, correct formatting, plausible-sounding citations. But they don't verify whether the references are real, whether the cited papers actually say what you claim, or whether the numbers in your abstract match your results.
55
+
56
+ **sciwrite-lint does.** It checks your references against academic databases, downloads the cited papers, verifies that they actually say what you claim, and follows one level deeper to check your references' own bibliographies. Fully local — no manuscripts leave your machine.
57
+
58
+ ## Features
59
+
60
+ **22 automated checks:**
61
+
62
+ ### Reference verification
63
+ - **Do your references exist?** — checked against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress
64
+ - **Is the metadata accurate?** — title, authors, year, venue compared against canonical records
65
+ - **Are any retracted?** — every DOI cross-referenced against 60,000+ entries in the Retraction Watch database
66
+ - **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)
67
+
68
+ ### Claim verification (local LLM)
69
+ - **Do cited papers support your claims?** — downloads full text from 8 open-access sources (arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv/medRxiv, CORE), parses via GROBID, embeds sections, and verifies each claim against the actual source text
70
+ - **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
71
+ - **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
72
+
73
+ ### Manuscript consistency (local LLM)
74
+ - **Cross-section contradictions** — numbers, claims, and framing that drift between sections
75
+ - **Numbers vs. tables** — text claims that contradict the corresponding table or figure
76
+ - **Arithmetic and percentages** — stated totals that don't match components; percentages that don't sum to 100%
77
+ - **Sample size tracking** — N values that change across sections without explanation
78
+ - **Causal language** — unhedged causal claims in correlational studies
79
+ - **Abstract–body alignment** — abstract makes factual claims the body contradicts
80
+ - **Statistical reporting** — p-values vs. their verbal interpretation
81
+ - **Structure promises** — contributions promised in the introduction but never delivered
82
+
83
+ ### Figure checks (vision model + LLM)
84
+ - **Caption vs. content** — does the caption match what the figure actually shows?
85
+ - **Text vs. figure** — does the text describe the figure accurately?
86
+ - **Axis labels** — units, labels, and scales consistent with the text
87
+ - **Figure–table agreement** — same data in a figure and table should agree
88
+
89
+ ### Text checks (deterministic, no services needed)
90
+ - **Dangling citations** — `\cite{key}` with no matching bib entry
91
+ - **Dangling cross-references** — `\ref{X}` with no matching `\label{X}`
92
+ - **Unreferenced figures** — figures included but never referenced in the text
93
+
94
+ ### Per-reference reliability score
95
+ All signals — metadata, retraction status, claim support, consistency, bibliography health — aggregate into a single reliability score per reference. When multiple independent checks flag the same reference, it is flagged as unreliable with specific reasons.
96
+
97
+ ### SciLint Score
98
+
99
+ ```
100
+ SciLint Score = Internal Consistency × Referencing Quality × Contribution
101
+ ```
102
+
103
+ A single number combining:
104
+ - **Internal Consistency** — fraction of checks passed within the manuscript
105
+ - **Referencing Quality** — are references real, accurate, and do they support your claims? Each reference weighted by its reliability score and citation purpose
106
+ - **Contribution** *(experimental)* — five axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo): empirical content, progressiveness, unification, problem-solving effectiveness, test severity. Defaults to 1.0 until `contributions` runs
107
+
108
+ ```bash
109
+ sciwrite-lint contributions paper.pdf --format json
110
+ ```
111
+
112
+ ### Privacy and security
113
+
114
+ - **Your manuscript never leaves your machine.** All parsing, LLM inference, and figure analysis run locally
115
+ - **Only citation metadata is sent externally** — DOIs, titles, author names for API verification. No paper content
116
+ - **Open-weights models pinned to specific versions** — results are reproducible forever, not dependent on a cloud provider's API updates
117
+ - **No API keys required** — all verification uses free public databases. Optional keys increase rate limits
118
+
119
+ ### Two audiences
120
+
121
+ - **Humans** — colored terminal output with severity levels, locations, and explanations. Decide in seconds whether each finding is real
122
+ - **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop. Configurable exit codes for CI integration
123
+
124
+ ### Optimizations
125
+
126
+ Three models — a vision model (Qwen3-VL-2B), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. Models run in separate pipeline stages; on WSL2, CUDA memory virtualization pages idle allocations to system RAM, letting all three share physical VRAM. FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
127
+
128
+ - **Semantic section filtering** — embedding-based KNN retrieval sends only the ~5 most relevant sections per claim to the LLM, reducing LLM calls ~4x
129
+ - **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
130
+ - **Per-call-site thinking budgets** — each LLM call site has empirically tuned (max_tokens, thinking_preset) pairs, measured via grid search to maximize detection quality
131
+ - **Adaptive embedding batches** — token-aware batch sizing gives ~50x speedup over CPU while staying within VRAM limits
132
+ - **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)
133
+ - **Phased API resolution** — citations flow through OpenAlex → Semantic Scholar → CrossRef → Open Library/LoC, each phase only processing what previous phases didn't resolve
134
+ - **8-source full-text cascade** — early exit on first successful download across arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv, CORE
135
+ - **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
136
+
137
+ Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. On native Linux, embedding and vision default to CPU (tens of times slower) due to limited VRAM on consumer GPUs and no transparent paging of inactive vLLM container allocations; force GPU via config if VRAM permits.
138
+
139
+ ## Install
140
+
141
+ **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Developed and tested on Windows (WSL2). Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](docs/services.md#embedding-device)). Not tested on macOS.
142
+
143
+ 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).
144
+
145
+ ```bash
146
+ uv tool install sciwrite-lint --python 3.13
147
+ ```
148
+
149
+ uv downloads Python 3.13 automatically (does not affect any Python you may already have) and installs sciwrite-lint as a globally available command.
150
+
151
+ ## Setup
152
+
153
+ ```bash
154
+ sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/
155
+ sciwrite-lint config set-email you@example.com # required for Unpaywall + Retraction Watch
156
+ sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM)
157
+ sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
158
+ ```
159
+
160
+ ![Monitor dashboard](docs/monitor.png)
161
+
162
+ `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
163
+
164
+ **Paywalled references:** drop PDFs into `local_pdfs/` with filenames matching the reference title. The tool fuzzy-matches filenames against your `.bib` titles and uses local copies instead of downloading.
165
+
166
+ **Optional API keys** increase rate limits for Semantic Scholar, NCBI, and CORE:
167
+
168
+ ```bash
169
+ sciwrite-lint config show # see what's configured
170
+ sciwrite-lint config set-key semantic-scholar YOUR_KEY # dedicated rate limit
171
+ ```
172
+
173
+ See [docs/services.md](docs/services.md) for GPU requirements, all external APIs, and API key details.
174
+
175
+ ## Usage
176
+
177
+ ```bash
178
+ sciwrite-lint check --paper my_paper # full pipeline + SciLint Score
179
+ sciwrite-lint check --paper my_paper --fresh # same, ignoring all caches
180
+ sciwrite-lint check # all papers (batch-staged when 2+, ~4-5x faster than sequential)
181
+ sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
182
+ sciwrite-lint check paper.tex # text + LLM rules on a .tex file
183
+ sciwrite-lint check paper.pdf # check a PDF (GROBID required)
184
+ sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
185
+ sciwrite-lint contributions paper.pdf # standalone file scoring
186
+ ```
187
+
188
+ `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.
189
+
190
+ Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
191
+
192
+ ### Contribution axes (`sciwrite-lint contributions`)
193
+
194
+ `contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
195
+
196
+ ```bash
197
+ sciwrite-lint check --paper my_paper # SciLint Score (contribution = 1.0)
198
+ sciwrite-lint contributions --paper my_paper # add 5 contribution axes, update score
199
+ ```
200
+
201
+ ### Individual stages
202
+
203
+ For debugging or advanced workflows, each pipeline stage is also available as a standalone command:
204
+
205
+ | Command | What it does |
206
+ |---------|-------------|
207
+ | `verify --paper NAME` | API verification only (CrossRef, OpenAlex, Semantic Scholar, Open Library, Library of Congress) |
208
+ | `fetch --paper NAME` | Download full-text PDFs for verified references |
209
+ | `parse --paper NAME` | Parse PDFs via GROBID and compute embeddings |
210
+ | `verify-claims --paper NAME` | LLM reads cited sources, checks claim support |
211
+ | `ref-health --paper NAME` | Fast reference health check: cite/bib mismatches, ID coverage, local PDF matches (no API calls) |
212
+ | `contributions --paper NAME` | Add 5 contribution axes to SciLint Score (requires vLLM) |
213
+
214
+ ## Output
215
+
216
+ Each finding has a severity level, a rule ID, and a message explaining the issue:
217
+
218
+ - **error** — a concrete manuscript problem (hallucinated reference, unsupported claim, retracted source)
219
+ - **warning** — needs human judgment (metadata mismatch, weak citation purpose, cross-section inconsistency)
220
+ - **info** — the tool could not complete a check (LLM error, API timeout, internal crash) or informational note
221
+
222
+ Findings also carry a `context` field with the reasoning behind the verdict: the LLM's explanation, which identifiers were searched, or which API provided the canonical data. This lets you distinguish "the tool found a problem" from "the tool couldn't check this."
223
+
224
+ Example terminal output:
225
+
226
+ ```
227
+ ERROR reference-exists johnson2024: Not found in any API
228
+ Searched with: title="Deep Learning for Climate", author="Johnson"
229
+
230
+ ERROR claim-support smith2023: Cited paper does not support this claim
231
+ Claim: "transformers outperform RNNs by 15% on BLEU"
232
+ Verdict: paper reports 8% improvement, not 15%
233
+
234
+ WARN reference-accuracy lee2022: Year mismatch (bib: 2022, canonical: 2021)
235
+ Source: OpenAlex (DOI: 10.1234/example)
236
+
237
+ WARN cross-section-consistency
238
+ Abstract claims "three novel contributions" but
239
+ Section 5 delivers two
240
+
241
+ WARN reference-unreliable chen2019: Low reliability (0.35)
242
+ Metadata mismatch, 2 unsupported claims,
243
+ 23% hallucinated bibliography entries
244
+
245
+ INFO caption-vs-content Figure 3: could not extract figure from PDF
246
+
247
+ SciLint Score: 0.41
248
+ Internal Consistency: 0.85
249
+ Referencing Quality: 0.48
250
+ (Run 'sciwrite-lint contributions' to add contribution axes)
251
+ ```
252
+
253
+ Output formats: terminal (default) or `--format json`.
254
+
255
+ ## Documentation
256
+
257
+ - `sciwrite-lint checks` — list all checks
258
+ - `sciwrite-lint <command> --help` — detailed usage for any command
259
+ - [docs/services.md](docs/services.md) — GROBID, vLLM, external APIs, configuration
260
+
261
+ For contributors and advanced users:
262
+
263
+ - [docs/evals.md](docs/evals.md) — detection evaluation framework, adding test cases
264
+ - [docs/calibration.md](docs/calibration.md) — SciLint Score calibration against ground-truth papers
265
+
266
+ ## License
267
+
268
+ MIT
@@ -0,0 +1,223 @@
1
+ # sciwrite-lint
2
+
3
+ A linter for scientific manuscripts. Checks that your references exist, your metadata is accurate, your citations support the claims you make, and your cited papers' own bibliographies are real. Works on LaTeX and PDF. Runs entirely on your machine. Produces a **SciLint Score**.
4
+
5
+ The only open-source tool that combines reference verification, claim checking, manuscript consistency, figure analysis, and bibliography verification in one pipeline — powered entirely by a local LLM on your own GPU.
6
+
7
+ ## Why
8
+
9
+ AI writing tools produce text that *looks* like good science — fluent prose, correct formatting, plausible-sounding citations. But they don't verify whether the references are real, whether the cited papers actually say what you claim, or whether the numbers in your abstract match your results.
10
+
11
+ **sciwrite-lint does.** It checks your references against academic databases, downloads the cited papers, verifies that they actually say what you claim, and follows one level deeper to check your references' own bibliographies. Fully local — no manuscripts leave your machine.
12
+
13
+ ## Features
14
+
15
+ **22 automated checks:**
16
+
17
+ ### Reference verification
18
+ - **Do your references exist?** — checked against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress
19
+ - **Is the metadata accurate?** — title, authors, year, venue compared against canonical records
20
+ - **Are any retracted?** — every DOI cross-referenced against 60,000+ entries in the Retraction Watch database
21
+ - **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)
22
+
23
+ ### Claim verification (local LLM)
24
+ - **Do cited papers support your claims?** — downloads full text from 8 open-access sources (arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv/medRxiv, CORE), parses via GROBID, embeds sections, and verifies each claim against the actual source text
25
+ - **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
26
+ - **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
27
+
28
+ ### Manuscript consistency (local LLM)
29
+ - **Cross-section contradictions** — numbers, claims, and framing that drift between sections
30
+ - **Numbers vs. tables** — text claims that contradict the corresponding table or figure
31
+ - **Arithmetic and percentages** — stated totals that don't match components; percentages that don't sum to 100%
32
+ - **Sample size tracking** — N values that change across sections without explanation
33
+ - **Causal language** — unhedged causal claims in correlational studies
34
+ - **Abstract–body alignment** — abstract makes factual claims the body contradicts
35
+ - **Statistical reporting** — p-values vs. their verbal interpretation
36
+ - **Structure promises** — contributions promised in the introduction but never delivered
37
+
38
+ ### Figure checks (vision model + LLM)
39
+ - **Caption vs. content** — does the caption match what the figure actually shows?
40
+ - **Text vs. figure** — does the text describe the figure accurately?
41
+ - **Axis labels** — units, labels, and scales consistent with the text
42
+ - **Figure–table agreement** — same data in a figure and table should agree
43
+
44
+ ### Text checks (deterministic, no services needed)
45
+ - **Dangling citations** — `\cite{key}` with no matching bib entry
46
+ - **Dangling cross-references** — `\ref{X}` with no matching `\label{X}`
47
+ - **Unreferenced figures** — figures included but never referenced in the text
48
+
49
+ ### Per-reference reliability score
50
+ All signals — metadata, retraction status, claim support, consistency, bibliography health — aggregate into a single reliability score per reference. When multiple independent checks flag the same reference, it is flagged as unreliable with specific reasons.
51
+
52
+ ### SciLint Score
53
+
54
+ ```
55
+ SciLint Score = Internal Consistency × Referencing Quality × Contribution
56
+ ```
57
+
58
+ A single number combining:
59
+ - **Internal Consistency** — fraction of checks passed within the manuscript
60
+ - **Referencing Quality** — are references real, accurate, and do they support your claims? Each reference weighted by its reliability score and citation purpose
61
+ - **Contribution** *(experimental)* — five axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo): empirical content, progressiveness, unification, problem-solving effectiveness, test severity. Defaults to 1.0 until `contributions` runs
62
+
63
+ ```bash
64
+ sciwrite-lint contributions paper.pdf --format json
65
+ ```
66
+
67
+ ### Privacy and security
68
+
69
+ - **Your manuscript never leaves your machine.** All parsing, LLM inference, and figure analysis run locally
70
+ - **Only citation metadata is sent externally** — DOIs, titles, author names for API verification. No paper content
71
+ - **Open-weights models pinned to specific versions** — results are reproducible forever, not dependent on a cloud provider's API updates
72
+ - **No API keys required** — all verification uses free public databases. Optional keys increase rate limits
73
+
74
+ ### Two audiences
75
+
76
+ - **Humans** — colored terminal output with severity levels, locations, and explanations. Decide in seconds whether each finding is real
77
+ - **AI writing agents** — `--format json` output with structured fields (`level`, `rule_id`, `message`, `context`). Run sciwrite-lint in a write → check → fix → recheck loop. Configurable exit codes for CI integration
78
+
79
+ ### Optimizations
80
+
81
+ Three models — a vision model (Qwen3-VL-2B), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. Models run in separate pipeline stages; on WSL2, CUDA memory virtualization pages idle allocations to system RAM, letting all three share physical VRAM. FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
82
+
83
+ - **Semantic section filtering** — embedding-based KNN retrieval sends only the ~5 most relevant sections per claim to the LLM, reducing LLM calls ~4x
84
+ - **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
85
+ - **Per-call-site thinking budgets** — each LLM call site has empirically tuned (max_tokens, thinking_preset) pairs, measured via grid search to maximize detection quality
86
+ - **Adaptive embedding batches** — token-aware batch sizing gives ~50x speedup over CPU while staying within VRAM limits
87
+ - **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)
88
+ - **Phased API resolution** — citations flow through OpenAlex → Semantic Scholar → CrossRef → Open Library/LoC, each phase only processing what previous phases didn't resolve
89
+ - **8-source full-text cascade** — early exit on first successful download across arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv, CORE
90
+ - **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
91
+
92
+ Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. On native Linux, embedding and vision default to CPU (tens of times slower) due to limited VRAM on consumer GPUs and no transparent paging of inactive vLLM container allocations; force GPU via config if VRAM permits.
93
+
94
+ ## Install
95
+
96
+ **Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Developed and tested on Windows (WSL2). Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](docs/services.md#embedding-device)). Not tested on macOS.
97
+
98
+ 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).
99
+
100
+ ```bash
101
+ uv tool install sciwrite-lint --python 3.13
102
+ ```
103
+
104
+ uv downloads Python 3.13 automatically (does not affect any Python you may already have) and installs sciwrite-lint as a globally available command.
105
+
106
+ ## Setup
107
+
108
+ ```bash
109
+ sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/
110
+ sciwrite-lint config set-email you@example.com # required for Unpaywall + Retraction Watch
111
+ sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM)
112
+ sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
113
+ ```
114
+
115
+ ![Monitor dashboard](docs/monitor.png)
116
+
117
+ `init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
118
+
119
+ **Paywalled references:** drop PDFs into `local_pdfs/` with filenames matching the reference title. The tool fuzzy-matches filenames against your `.bib` titles and uses local copies instead of downloading.
120
+
121
+ **Optional API keys** increase rate limits for Semantic Scholar, NCBI, and CORE:
122
+
123
+ ```bash
124
+ sciwrite-lint config show # see what's configured
125
+ sciwrite-lint config set-key semantic-scholar YOUR_KEY # dedicated rate limit
126
+ ```
127
+
128
+ See [docs/services.md](docs/services.md) for GPU requirements, all external APIs, and API key details.
129
+
130
+ ## Usage
131
+
132
+ ```bash
133
+ sciwrite-lint check --paper my_paper # full pipeline + SciLint Score
134
+ sciwrite-lint check --paper my_paper --fresh # same, ignoring all caches
135
+ sciwrite-lint check # all papers (batch-staged when 2+, ~4-5x faster than sequential)
136
+ sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
137
+ sciwrite-lint check paper.tex # text + LLM rules on a .tex file
138
+ sciwrite-lint check paper.pdf # check a PDF (GROBID required)
139
+ sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
140
+ sciwrite-lint contributions paper.pdf # standalone file scoring
141
+ ```
142
+
143
+ `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.
144
+
145
+ Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
146
+
147
+ ### Contribution axes (`sciwrite-lint contributions`)
148
+
149
+ `contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
150
+
151
+ ```bash
152
+ sciwrite-lint check --paper my_paper # SciLint Score (contribution = 1.0)
153
+ sciwrite-lint contributions --paper my_paper # add 5 contribution axes, update score
154
+ ```
155
+
156
+ ### Individual stages
157
+
158
+ For debugging or advanced workflows, each pipeline stage is also available as a standalone command:
159
+
160
+ | Command | What it does |
161
+ |---------|-------------|
162
+ | `verify --paper NAME` | API verification only (CrossRef, OpenAlex, Semantic Scholar, Open Library, Library of Congress) |
163
+ | `fetch --paper NAME` | Download full-text PDFs for verified references |
164
+ | `parse --paper NAME` | Parse PDFs via GROBID and compute embeddings |
165
+ | `verify-claims --paper NAME` | LLM reads cited sources, checks claim support |
166
+ | `ref-health --paper NAME` | Fast reference health check: cite/bib mismatches, ID coverage, local PDF matches (no API calls) |
167
+ | `contributions --paper NAME` | Add 5 contribution axes to SciLint Score (requires vLLM) |
168
+
169
+ ## Output
170
+
171
+ Each finding has a severity level, a rule ID, and a message explaining the issue:
172
+
173
+ - **error** — a concrete manuscript problem (hallucinated reference, unsupported claim, retracted source)
174
+ - **warning** — needs human judgment (metadata mismatch, weak citation purpose, cross-section inconsistency)
175
+ - **info** — the tool could not complete a check (LLM error, API timeout, internal crash) or informational note
176
+
177
+ Findings also carry a `context` field with the reasoning behind the verdict: the LLM's explanation, which identifiers were searched, or which API provided the canonical data. This lets you distinguish "the tool found a problem" from "the tool couldn't check this."
178
+
179
+ Example terminal output:
180
+
181
+ ```
182
+ ERROR reference-exists johnson2024: Not found in any API
183
+ Searched with: title="Deep Learning for Climate", author="Johnson"
184
+
185
+ ERROR claim-support smith2023: Cited paper does not support this claim
186
+ Claim: "transformers outperform RNNs by 15% on BLEU"
187
+ Verdict: paper reports 8% improvement, not 15%
188
+
189
+ WARN reference-accuracy lee2022: Year mismatch (bib: 2022, canonical: 2021)
190
+ Source: OpenAlex (DOI: 10.1234/example)
191
+
192
+ WARN cross-section-consistency
193
+ Abstract claims "three novel contributions" but
194
+ Section 5 delivers two
195
+
196
+ WARN reference-unreliable chen2019: Low reliability (0.35)
197
+ Metadata mismatch, 2 unsupported claims,
198
+ 23% hallucinated bibliography entries
199
+
200
+ INFO caption-vs-content Figure 3: could not extract figure from PDF
201
+
202
+ SciLint Score: 0.41
203
+ Internal Consistency: 0.85
204
+ Referencing Quality: 0.48
205
+ (Run 'sciwrite-lint contributions' to add contribution axes)
206
+ ```
207
+
208
+ Output formats: terminal (default) or `--format json`.
209
+
210
+ ## Documentation
211
+
212
+ - `sciwrite-lint checks` — list all checks
213
+ - `sciwrite-lint <command> --help` — detailed usage for any command
214
+ - [docs/services.md](docs/services.md) — GROBID, vLLM, external APIs, configuration
215
+
216
+ For contributors and advanced users:
217
+
218
+ - [docs/evals.md](docs/evals.md) — detection evaluation framework, adding test cases
219
+ - [docs/calibration.md](docs/calibration.md) — SciLint Score calibration against ground-truth papers
220
+
221
+ ## License
222
+
223
+ MIT
@@ -0,0 +1,103 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sciwrite-lint"
7
+ version = "0.2.0"
8
+ description = "A linter for scientific manuscripts — reference verification, consistency checking, and structural validation."
9
+ requires-python = ">=3.13,<3.14"
10
+ license = "MIT"
11
+ authors = [{ name = "Sergey Samsonau" }]
12
+ readme = "README.md"
13
+ keywords = ["linter", "scientific-writing", "citation-verification", "latex"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "Topic :: Text Processing :: Markup :: LaTeX",
18
+ "Programming Language :: Python :: 3.13",
19
+ ]
20
+ dependencies = [
21
+ "httpx>=0.27",
22
+ "tenacity>=8.0",
23
+ "trafilatura>=1.8",
24
+ "aiosqlite>=0.20",
25
+ "loguru>=0.7",
26
+ "anyascii>=0.3",
27
+ "rich>=13.0",
28
+ "defusedxml>=0.7",
29
+ "pydantic>=2.0",
30
+ "rapidfuzz>=3.0",
31
+ "bibtexparser>=1.4",
32
+ "pdfplumber>=0.10",
33
+ "openai>=1.0",
34
+ "grobid-tei-xml>=0.1",
35
+ "sentence-transformers>=3.0",
36
+ "numpy>=1.24",
37
+ "sqlite-vec>=0.1.6",
38
+ "torch>=2.0",
39
+ "transformers>=4.45",
40
+ "Pillow>=10.0",
41
+ "pypdf>=4.0",
42
+ "pypdfium2>=4.0",
43
+ "pdf2image>=1.16",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/authentic-research-partners/sciwrite-lint"
48
+ Repository = "https://github.com/authentic-research-partners/sciwrite-lint"
49
+ Issues = "https://github.com/authentic-research-partners/sciwrite-lint/issues"
50
+
51
+ [project.optional-dependencies]
52
+ dev = [
53
+ "pytest>=8.0",
54
+ "pytest-xdist>=3.0",
55
+ ]
56
+
57
+ [project.scripts]
58
+ sciwrite-lint = "sciwrite_lint.__main__:main"
59
+
60
+ [tool.setuptools.packages.find]
61
+ include = ["sciwrite_lint*"]
62
+
63
+ [tool.pytest.ini_options]
64
+ testpaths = ["tests"]
65
+ pythonpath = ["."]
66
+ markers = [
67
+ "integration: tests that read actual paper files",
68
+ "network: tests that make real network connections (slow)",
69
+ ]
70
+ # PID-unique basetemp set in conftest.py — prevents FileExistsError
71
+ # when multiple pytest processes run concurrently.
72
+ addopts = "-n auto -p no:cacheprovider"
73
+
74
+ [tool.bandit]
75
+ skips = [
76
+ "B101", # assert — used for type narrowing and internal invariants
77
+ "B104", # bind 0.0.0.0 — container start commands for GROBID/vLLM
78
+ "B314", # xml.etree.fromstring — parsing trusted GROBID TEI output
79
+ "B404", # subprocess import — CLI tool, subprocess is core functionality
80
+ "B405", # xml.etree import — parsing trusted GROBID TEI output
81
+ "B603", # subprocess call — CLI invokes grobid/vllm/claude by design
82
+ "B607", # partial path — CLI tools resolved via PATH
83
+ "B608", # SQL string — internal usage.db, no user input in queries
84
+ "B615", # HF from_pretrained without revision pin — trusted publisher (Qwen), research tool
85
+ ]
86
+
87
+ [tool.deptry]
88
+ optional_dependencies_dev_groups = ["dev"]
89
+
90
+ [tool.deptry.per_rule_ignores]
91
+ # Self-imports of shipped non-package directories
92
+ DEP001 = ["evals", "eval_real_world", "bench"]
93
+ # Lazy/conditional imports in optional groups — deptry can't detect these
94
+ DEP002 = ["bibtexparser", "pdfplumber", "sentence-transformers", "numpy", "sqlite-vec", "grobid-tei-xml", "torch", "transformers", "Pillow", "pypdf", "pdf2image"]
95
+ # Intra-package imports
96
+ DEP003 = ["sciwrite_lint", "evals", "eval_real_world"]
97
+
98
+ [tool.mypy]
99
+ plugins = ["pydantic.mypy"]
100
+ [[tool.mypy.overrides]]
101
+ # No py.typed marker or typeshed stubs available
102
+ module = ["bibtexparser", "sqlite_vec", "pypdf", "pypdfium2", "pypdfium2.raw", "transformers", "pdf2image"]
103
+ ignore_missing_imports = true
@@ -0,0 +1,3 @@
1
+ """sciwrite-lint: a linter for scientific manuscripts."""
2
+
3
+ __version__ = "0.2.0"