sciwrite-lint 0.2.2__tar.gz → 0.4.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.4.0}/PKG-INFO +74 -22
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/README.md +56 -18
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/pyproject.toml +23 -45
- sciwrite_lint-0.4.0/sciwrite_lint/__init__.py +28 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/__main__.py +68 -94
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/_network.py +17 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/api.py +74 -11
- sciwrite_lint-0.4.0/sciwrite_lint/api_keys.py +93 -0
- sciwrite_lint-0.4.0/sciwrite_lint/checks/_diagnostics.py +237 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/_section_utils.py +27 -9
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/cite_purpose.py +5 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/cross_section_consistency.py +45 -19
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/dangling_cite.py +1 -1
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/full_paper_consistency.py +88 -28
- sciwrite_lint-0.4.0/sciwrite_lint/checks/internal_consistency_pairs.py +473 -0
- sciwrite_lint-0.4.0/sciwrite_lint/checks/prose_quality.py +565 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/ref_internal_checks.py +120 -26
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/reference_exists.py +18 -2
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/reference_unreliable.py +9 -3
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/registry.py +2 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/structure_promises.py +23 -12
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/claims.py +13 -4
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/claude_cli.py +8 -21
- sciwrite_lint-0.4.0/sciwrite_lint/cli/_common.py +106 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/check.py +131 -103
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/config.py +11 -76
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/fetch.py +60 -26
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/__init__.py +62 -0
- sciwrite_lint-0.2.2/sciwrite_lint/cli/misc.py → sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/_monitor.py +524 -593
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/containers.py +217 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/dismiss_claim.py +62 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/grobid.py +53 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/init.py +14 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/override.py +66 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/parse.py +95 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/vision.py +60 -0
- sciwrite_lint-0.4.0/sciwrite_lint/cli/misc/vllm.py +47 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/rank.py +6 -2
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/verify.py +52 -26
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/config.py +332 -9
- sciwrite_lint-0.4.0/sciwrite_lint/cross_paper.py +59 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/eval_claims.py +752 -238
- sciwrite_lint-0.4.0/sciwrite_lint/exceptions.py +17 -0
- sciwrite_lint-0.4.0/sciwrite_lint/footnote_urls.py +584 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/__init__.py +193 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/_common.py +221 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/_download.py +125 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/_orchestrator.py +120 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/_search.py +219 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/_validation.py +461 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/arxiv.py +30 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/biorxiv.py +36 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/core.py +98 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/eric.py +159 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/europepmc.py +76 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/hal.py +171 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/ideas.py +224 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/nasa_ads.py +214 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/nber.py +157 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/osf.py +218 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/pmc.py +69 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/s2.py +28 -0
- sciwrite_lint-0.4.0/sciwrite_lint/fulltext/unpaywall.py +59 -0
- sciwrite_lint-0.4.0/sciwrite_lint/latex_to_markdown.py +136 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/__init__.py +5 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/__init__.py +60 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/compute_cap.py +84 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/controller.py +464 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/decide.py +462 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/host_metrics.py +113 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/metrics_probe.py +70 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/registry.py +199 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/telemetry.py +414 -0
- sciwrite_lint-0.2.2/sciwrite_lint/vllm/metrics.py → sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/vllm_metrics.py +34 -38
- sciwrite_lint-0.4.0/sciwrite_lint/llm/concurrency_optimizer/wiring.py +119 -0
- sciwrite_lint-0.4.0/sciwrite_lint/llm_utils.py +826 -0
- sciwrite_lint-0.4.0/sciwrite_lint/local_sources.py +422 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/manuscript_store.py +261 -17
- sciwrite_lint-0.4.0/sciwrite_lint/mhtml.py +209 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/models.py +2 -4
- sciwrite_lint-0.4.0/sciwrite_lint/oa/__init__.py +42 -0
- sciwrite_lint-0.4.0/sciwrite_lint/oa/_models.py +91 -0
- sciwrite_lint-0.4.0/sciwrite_lint/oa/_pdf.py +404 -0
- sciwrite_lint-0.4.0/sciwrite_lint/oa/_search.py +113 -0
- sciwrite_lint-0.4.0/sciwrite_lint/oa/_web.py +165 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/pdf/grobid.py +244 -33
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/pdf/pdf_download.py +31 -43
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/__init__.py +151 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/checks.py +195 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/claims.py +116 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/embeddings.py +350 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/fetch.py +290 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/orchestration.py +440 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/parse.py +153 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/pdf_context.py +87 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/ref_internal.py +26 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/runners.py +651 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/staged.py +761 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/swap.py +305 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/tracking.py +106 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/unreliable.py +47 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/verify.py +539 -0
- sciwrite_lint-0.4.0/sciwrite_lint/pipeline/vision.py +284 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/_embed_timing.py +63 -0
- {sciwrite_lint-0.2.2/sciwrite_lint/scoring → sciwrite_lint-0.4.0/sciwrite_lint/references}/chain.py +3 -1
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/citations.py +115 -59
- sciwrite_lint-0.4.0/sciwrite_lint/references/embedding_store.py +429 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/matching.py +8 -34
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/metadata.py +16 -17
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/reference_store.py +420 -102
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/retraction_watch.py +4 -3
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/__init__.py +149 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/_core.py +381 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/bib_checks.py +82 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/citation_metadata.py +178 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/claim_results.py +164 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/manuscript_citations.py +89 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/parse_cache.py +136 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/pipeline_stage.py +82 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/query_vectors.py +32 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/ref_internal.py +101 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/registry.py +259 -0
- sciwrite_lint-0.4.0/sciwrite_lint/references/workspace_db/vision_cache.py +143 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/report.py +14 -5
- sciwrite_lint-0.4.0/sciwrite_lint/schemas.py +505 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/scoring/contribution.py +2 -2
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/scoring/scilint_score.py +32 -10
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/tex_parser.py +67 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/usage.py +4 -1
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vision/cache.py +60 -24
- sciwrite_lint-0.4.0/sciwrite_lint/vision/describe.py +1095 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vision/pipeline.py +4 -3
- sciwrite_lint-0.4.0/sciwrite_lint/vllm/metrics.py +42 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vllm/vllm_server.py +290 -20
- sciwrite_lint-0.4.0/sciwrite_lint/web.py +604 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0/sciwrite_lint.egg-info}/PKG-INFO +74 -22
- sciwrite_lint-0.4.0/sciwrite_lint.egg-info/SOURCES.txt +159 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint.egg-info/requires.txt +17 -3
- sciwrite_lint-0.2.2/sciwrite_lint/__init__.py +0 -3
- sciwrite_lint-0.2.2/sciwrite_lint/cross_paper.py +0 -174
- 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/embedding_store.py +0 -389
- sciwrite_lint-0.2.2/sciwrite_lint/references/workspace_db.py +0 -1508
- sciwrite_lint-0.2.2/sciwrite_lint/schemas.py +0 -260
- sciwrite_lint-0.2.2/sciwrite_lint/vision/describe.py +0 -632
- 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.4.0}/LICENSE +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/claim_support.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/dangling_ref.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/reference_accuracy.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/retracted_cite.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/checks/unreferenced_figure.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/claude_backend.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/cli/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/pdf/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/prompt_safety.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/py.typed +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/rate_limiter.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/references/crossref.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/scoring/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vision/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vision/image_extraction.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint/vllm/__init__.py +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint.egg-info/dependency_links.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint.egg-info/entry_points.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.0}/sciwrite_lint.egg-info/top_level.txt +0 -0
- {sciwrite_lint-0.2.2 → sciwrite_lint-0.4.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.4.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,13 +41,27 @@ 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
|
|
51
|
+
Requires-Dist: psutil>=5.9
|
|
52
|
+
Requires-Dist: annotated-types>=0.6
|
|
48
53
|
Provides-Extra: dev
|
|
49
|
-
Requires-Dist: pytest>=
|
|
54
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
50
55
|
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
56
|
+
Requires-Dist: ruff>=0.15; extra == "dev"
|
|
57
|
+
Requires-Dist: mypy>=1.18; extra == "dev"
|
|
58
|
+
Requires-Dist: ast-grep-cli>=0.35; extra == "dev"
|
|
59
|
+
Requires-Dist: deptry>=0.25; extra == "dev"
|
|
60
|
+
Requires-Dist: bandit>=1.9; extra == "dev"
|
|
61
|
+
Requires-Dist: pip-audit>=2.10; extra == "dev"
|
|
62
|
+
Requires-Dist: pip-licenses>=5.5; extra == "dev"
|
|
63
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
64
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
51
65
|
Dynamic: license-file
|
|
52
66
|
|
|
53
67
|
# sciwrite-lint
|
|
@@ -66,7 +80,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
66
80
|
|
|
67
81
|
## Features
|
|
68
82
|
|
|
69
|
-
**
|
|
83
|
+
**23 automated checks:**
|
|
70
84
|
|
|
71
85
|
### Reference verification
|
|
72
86
|
- **Do your references exist?** — checked against CrossRef, OpenAlex, Semantic Scholar, Open Library, and Library of Congress
|
|
@@ -75,7 +89,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
75
89
|
- **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
90
|
|
|
77
91
|
### Claim verification (local LLM)
|
|
78
|
-
- **Do cited papers support your claims?** — downloads full text from
|
|
92
|
+
- **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
93
|
- **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
94
|
- **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
95
|
|
|
@@ -88,6 +102,7 @@ AI writing tools produce text that *looks* like good science — fluent prose, c
|
|
|
88
102
|
- **Abstract–body alignment** — abstract makes factual claims the body contradicts
|
|
89
103
|
- **Statistical reporting** — p-values vs. their verbal interpretation
|
|
90
104
|
- **Structure promises** — contributions promised in the introduction but never delivered
|
|
105
|
+
- **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
106
|
|
|
92
107
|
### Figure checks (vision model + LLM)
|
|
93
108
|
- **Caption vs. content** — does the caption match what the figure actually shows?
|
|
@@ -128,26 +143,26 @@ sciwrite-lint contributions paper.pdf --format json
|
|
|
128
143
|
### Two audiences
|
|
129
144
|
|
|
130
145
|
- **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.
|
|
146
|
+
- **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
147
|
|
|
133
148
|
### Optimizations
|
|
134
149
|
|
|
135
|
-
Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU.
|
|
150
|
+
Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
|
|
136
151
|
|
|
137
|
-
- **
|
|
152
|
+
- **Cost-aware verify-claim ladder** — sentence chunk → paragraph chunk → whole section. Each level fans out top-N candidates in parallel; stops on a conclusive verdict. Most claims resolve at the cheap sentence level (~200-token prompts) instead of paying for whole sections (~5K tokens), with full-section escalation reserved for hard cases
|
|
138
153
|
- **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
|
|
139
|
-
- **Per-call-site thinking budgets** — each LLM call site has
|
|
154
|
+
- **Per-call-site thinking budgets** — each LLM call site has its own (max_tokens, thinking_preset) pair tuned for that check's prompt and output shape
|
|
140
155
|
- **Adaptive embedding batches** — token-aware batch sizing gives ~50x speedup over CPU while staying within VRAM limits
|
|
141
156
|
- **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
157
|
- **Phased API resolution** — citations flow through OpenAlex → Semantic Scholar → CrossRef → Open Library/LoC, each phase only processing what previous phases didn't resolve
|
|
143
|
-
- **
|
|
158
|
+
- **14-source full-text cascade** — early exit on first successful download across arXiv, Semantic Scholar, OpenAlex, PubMed Central, Europe PMC, Unpaywall, bioRxiv, NBER, RePEc/IDEAS, HAL, ERIC, NASA ADS, OSF Preprints, CORE
|
|
144
159
|
- **Live monitoring** *(advanced)* — `sciwrite-lint containers monitor` shows service health, VRAM usage, and KV cache utilization in a terminal dashboard
|
|
145
160
|
|
|
146
|
-
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns.
|
|
161
|
+
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU) — same code path on WSL2 and native Linux.
|
|
147
162
|
|
|
148
163
|
## Install
|
|
149
164
|
|
|
150
|
-
**Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM).
|
|
165
|
+
**Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)). Not tested on macOS.
|
|
151
166
|
|
|
152
167
|
Requires [uv](https://docs.astral.sh/uv/getting-started/installation/), a container runtime (podman or docker), CUDA drivers, and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
|
|
153
168
|
|
|
@@ -186,30 +201,63 @@ Activate the venv in each new terminal with `source .venv/bin/activate`. For mos
|
|
|
186
201
|
## Setup
|
|
187
202
|
|
|
188
203
|
```bash
|
|
189
|
-
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/
|
|
204
|
+
sciwrite-lint init # scaffold .sciwrite-lint.toml + references/ + local_pdfs/ + local_web/
|
|
190
205
|
sciwrite-lint config set-email you@example.com # required for Unpaywall + Retraction Watch
|
|
191
206
|
sciwrite-lint containers start # start GROBID + vLLM (needs GPU for vLLM)
|
|
192
207
|
sciwrite-lint containers monitor # live dashboard: service health, VRAM, KV cache
|
|
193
208
|
```
|
|
194
209
|
|
|
210
|
+
### WSL2: disable CUDA Sysmem Fallback (recommended)
|
|
211
|
+
|
|
212
|
+
By default Windows silently spills GPU overflow into system RAM via PCIe (30–100× slower than VRAM); you'll see this as a sudden throughput collapse during the heaviest pipeline stages with no error in the logs. Tell the NVIDIA driver to fail loudly instead:
|
|
213
|
+
|
|
214
|
+
> NVIDIA Control Panel → 3D Settings → Manage 3D Settings → *CUDA - Sysmem Fallback Policy* → **Prefer No Sysmem Fallback** → Apply.
|
|
215
|
+
|
|
216
|
+
Driver 546.01+. After this, WSL2 behaves like native Linux on overflow (CUDA OOM error). The pipeline already sequences GPU consumers so OOM should not happen in normal use; the setting just prevents silent slowdowns when something does go wrong. No setting needed on native Linux — `cudaMalloc` is already loud there.
|
|
217
|
+
|
|
195
218
|

|
|
196
219
|
|
|
197
220
|
`init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
|
|
198
221
|
|
|
199
|
-
**Providing
|
|
222
|
+
**Providing references manually — two drop folders.** sciwrite-lint reads two local directories before going to the open-access waterfall, kept separate because the folder itself signals how much to trust the source:
|
|
200
223
|
|
|
224
|
+
- `local_pdfs/` — **academic sources**. Accepts `.pdf` (primary) and `.md` summaries you've written yourself. Use this for paywalled papers, for OA papers whose publisher requires a browser (Cloudflare/JS walls, Cell, Springer, some PMC pages), and for anything you'd cite as peer-reviewed evidence.
|
|
225
|
+
- `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.
|
|
226
|
+
|
|
227
|
+
Name files by the reference title, or prefix them with the citekey:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
local_pdfs/ local_web/
|
|
231
|
+
├── States of Curiosity … Learning.pdf ├── dewey1910_How_We_Think.mhtml
|
|
232
|
+
├── Mind in Society.pdf ├── earthwatch2025_About.md
|
|
233
|
+
├── vanfraassen1980_The_Scientific_Image.pdf └── bca2026_Research_Teachers.md
|
|
234
|
+
└── dewey2016_summary.md # reader-written
|
|
201
235
|
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
|
|
237
|
+
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.
|
|
238
|
+
|
|
239
|
+
**Per-paper curated archives.** Each paper can point both drop folders at its own directories in its `[[papers]]` block:
|
|
240
|
+
|
|
241
|
+
```toml
|
|
242
|
+
[[papers]]
|
|
243
|
+
name = "my-paper"
|
|
244
|
+
file_path = "papers/my-paper/my-paper.tex"
|
|
245
|
+
bib = "papers/my-paper/my-paper.bib"
|
|
246
|
+
local_pdfs_dir = "papers/my-paper/Sources/full_text" # academic archive
|
|
247
|
+
local_web_dir = "papers/my-paper/Sources/full_text_web" # web captures
|
|
206
248
|
```
|
|
207
249
|
|
|
208
|
-
|
|
250
|
+
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.
|
|
251
|
+
|
|
252
|
+
**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.
|
|
209
253
|
|
|
210
|
-
**
|
|
254
|
+
**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.
|
|
211
255
|
|
|
212
|
-
**
|
|
256
|
+
**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.
|
|
257
|
+
|
|
258
|
+
**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.
|
|
259
|
+
|
|
260
|
+
**API keys** increase rate limits for Semantic Scholar, NCBI, and CORE, and enable NASA ADS (required for that source):
|
|
213
261
|
|
|
214
262
|
```bash
|
|
215
263
|
sciwrite-lint config show # see what's configured
|
|
@@ -227,6 +275,7 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
227
275
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
228
276
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
229
277
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
278
|
+
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
230
279
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
231
280
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
232
281
|
```
|
|
@@ -235,6 +284,8 @@ sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
|
235
284
|
|
|
236
285
|
Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
|
|
237
286
|
|
|
287
|
+
Use `--checks ID[,ID...]` to run only the listed checks (comma-separated), disabling the rest for this invocation. Useful for fast iteration during editing (`--checks prose-quality` runs just the prose review), for debugging a single check, or for scripting a two-pass workflow. Unknown check IDs fail loudly — run `sciwrite-lint checks` to list available IDs.
|
|
288
|
+
|
|
238
289
|
### Contribution axes (`sciwrite-lint contributions`)
|
|
239
290
|
|
|
240
291
|
`contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
|
|
@@ -303,6 +354,7 @@ Output formats: terminal (default) or `--format json`.
|
|
|
303
354
|
- `sciwrite-lint checks` — list all checks
|
|
304
355
|
- `sciwrite-lint <command> --help` — detailed usage for any command
|
|
305
356
|
- [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md) — GROBID, vLLM, external APIs, configuration
|
|
357
|
+
- [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
358
|
|
|
307
359
|
For contributors and advanced users:
|
|
308
360
|
|
|
@@ -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,26 +77,26 @@ 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
|
|
|
83
|
-
Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU.
|
|
84
|
+
Three models — a vision model (Qwen3-VL-2B default, or 8B FP8 via `--vision-backend vllm` for +15% accuracy), an embedding model (Arctic Embed), and an 8B reasoning LLM (Qwen3 via vLLM) — share a single consumer GPU. The pipeline runs each in its own stage and explicitly stops one before starting the next, so only one ever owns GPU memory at a time (same code path on WSL2 and native Linux). FP8 weights and KV cache (Ada Lovelace+) and per-paper SQLite caching with hash-based invalidation are baseline. On top of that:
|
|
84
85
|
|
|
85
|
-
- **
|
|
86
|
+
- **Cost-aware verify-claim ladder** — sentence chunk → paragraph chunk → whole section. Each level fans out top-N candidates in parallel; stops on a conclusive verdict. Most claims resolve at the cheap sentence level (~200-token prompts) instead of paying for whole sections (~5K tokens), with full-section escalation reserved for hard cases
|
|
86
87
|
- **Prefix-first prompt structure** — shared context placed before variable content in all prompts, maximizing vLLM's automatic prefix caching
|
|
87
|
-
- **Per-call-site thinking budgets** — each LLM call site has
|
|
88
|
+
- **Per-call-site thinking budgets** — each LLM call site has its own (max_tokens, thinking_preset) pair tuned for that check's prompt and output shape
|
|
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
|
-
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns.
|
|
95
|
+
Full pipeline on a 50-reference paper: ~30 minutes initial (dominated by downloads and claim verification), minutes on cached reruns. The pipeline automatically swaps vLLM containers to free GPU for embedding and vision stages (~50× faster than CPU) — same code path on WSL2 and native Linux.
|
|
95
96
|
|
|
96
97
|
## Install
|
|
97
98
|
|
|
98
|
-
**Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM).
|
|
99
|
+
**Assumed setup:** A workstation with an NVIDIA GPU (16+ GB VRAM). Tested on WSL2 with NVIDIA driver 546.01+. Native Linux is likely to work with GPU memory allocation tuning (see [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md#embedding-device)). Not tested on macOS.
|
|
99
100
|
|
|
100
101
|
Requires [uv](https://docs.astral.sh/uv/getting-started/installation/), a container runtime (podman or docker), CUDA drivers, and [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
|
|
101
102
|
|
|
@@ -134,30 +135,63 @@ 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
|
|
141
142
|
```
|
|
142
143
|
|
|
144
|
+
### WSL2: disable CUDA Sysmem Fallback (recommended)
|
|
145
|
+
|
|
146
|
+
By default Windows silently spills GPU overflow into system RAM via PCIe (30–100× slower than VRAM); you'll see this as a sudden throughput collapse during the heaviest pipeline stages with no error in the logs. Tell the NVIDIA driver to fail loudly instead:
|
|
147
|
+
|
|
148
|
+
> NVIDIA Control Panel → 3D Settings → Manage 3D Settings → *CUDA - Sysmem Fallback Policy* → **Prefer No Sysmem Fallback** → Apply.
|
|
149
|
+
|
|
150
|
+
Driver 546.01+. After this, WSL2 behaves like native Linux on overflow (CUDA OOM error). The pipeline already sequences GPU consumers so OOM should not happen in normal use; the setting just prevents silent slowdowns when something does go wrong. No setting needed on native Linux — `cudaMalloc` is already loud there.
|
|
151
|
+
|
|
143
152
|

|
|
144
153
|
|
|
145
154
|
`init` detects `.tex` files and their `.bib` references and generates a `.sciwrite-lint.toml` config. Review to confirm the right files were detected.
|
|
146
155
|
|
|
147
|
-
**Providing
|
|
156
|
+
**Providing references manually — two drop folders.** sciwrite-lint reads two local directories before going to the open-access waterfall, kept separate because the folder itself signals how much to trust the source:
|
|
148
157
|
|
|
158
|
+
- `local_pdfs/` — **academic sources**. Accepts `.pdf` (primary) and `.md` summaries you've written yourself. Use this for paywalled papers, for OA papers whose publisher requires a browser (Cloudflare/JS walls, Cell, Springer, some PMC pages), and for anything you'd cite as peer-reviewed evidence.
|
|
159
|
+
- `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.
|
|
160
|
+
|
|
161
|
+
Name files by the reference title, or prefix them with the citekey:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
local_pdfs/ local_web/
|
|
165
|
+
├── States of Curiosity … Learning.pdf ├── dewey1910_How_We_Think.mhtml
|
|
166
|
+
├── Mind in Society.pdf ├── earthwatch2025_About.md
|
|
167
|
+
├── vanfraassen1980_The_Scientific_Image.pdf └── bca2026_Research_Teachers.md
|
|
168
|
+
└── dewey2016_summary.md # reader-written
|
|
149
169
|
```
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
**Per-paper curated archives.** Each paper can point both drop folders at its own directories in its `[[papers]]` block:
|
|
174
|
+
|
|
175
|
+
```toml
|
|
176
|
+
[[papers]]
|
|
177
|
+
name = "my-paper"
|
|
178
|
+
file_path = "papers/my-paper/my-paper.tex"
|
|
179
|
+
bib = "papers/my-paper/my-paper.bib"
|
|
180
|
+
local_pdfs_dir = "papers/my-paper/Sources/full_text" # academic archive
|
|
181
|
+
local_web_dir = "papers/my-paper/Sources/full_text_web" # web captures
|
|
154
182
|
```
|
|
155
183
|
|
|
156
|
-
|
|
184
|
+
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.
|
|
185
|
+
|
|
186
|
+
**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.
|
|
157
187
|
|
|
158
|
-
**
|
|
188
|
+
**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.
|
|
159
189
|
|
|
160
|
-
**
|
|
190
|
+
**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.
|
|
191
|
+
|
|
192
|
+
**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.
|
|
193
|
+
|
|
194
|
+
**API keys** increase rate limits for Semantic Scholar, NCBI, and CORE, and enable NASA ADS (required for that source):
|
|
161
195
|
|
|
162
196
|
```bash
|
|
163
197
|
sciwrite-lint config show # see what's configured
|
|
@@ -175,6 +209,7 @@ sciwrite-lint check # all papers (batch-staged when
|
|
|
175
209
|
sciwrite-lint check --concurrency 4 # batch parallelism (default 2, validated up to 4)
|
|
176
210
|
sciwrite-lint check paper.tex # text + LLM rules on a .tex file
|
|
177
211
|
sciwrite-lint check paper.pdf # check a PDF (GROBID required)
|
|
212
|
+
sciwrite-lint check --checks prose-quality paper.tex # run only the listed checks (comma-separated)
|
|
178
213
|
sciwrite-lint contributions --paper my_paper # add contribution axes to SciLint Score
|
|
179
214
|
sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
180
215
|
```
|
|
@@ -183,6 +218,8 @@ sciwrite-lint contributions paper.pdf # standalone file scoring
|
|
|
183
218
|
|
|
184
219
|
Use `--fresh` to start from scratch (backs up the existing workspace before recreating it).
|
|
185
220
|
|
|
221
|
+
Use `--checks ID[,ID...]` to run only the listed checks (comma-separated), disabling the rest for this invocation. Useful for fast iteration during editing (`--checks prose-quality` runs just the prose review), for debugging a single check, or for scripting a two-pass workflow. Unknown check IDs fail loudly — run `sciwrite-lint checks` to list available IDs.
|
|
222
|
+
|
|
186
223
|
### Contribution axes (`sciwrite-lint contributions`)
|
|
187
224
|
|
|
188
225
|
`contributions` computes 5 contribution axes from philosophy of science (Popper, Lakatos, Kitcher, Laudan, Mayo) and updates the SciLint Score. Requires vLLM.
|
|
@@ -251,6 +288,7 @@ Output formats: terminal (default) or `--format json`.
|
|
|
251
288
|
- `sciwrite-lint checks` — list all checks
|
|
252
289
|
- `sciwrite-lint <command> --help` — detailed usage for any command
|
|
253
290
|
- [docs/services.md](https://github.com/authentic-research-partners/sciwrite-lint/blob/main/docs/services.md) — GROBID, vLLM, external APIs, configuration
|
|
291
|
+
- [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
292
|
|
|
255
293
|
For contributors and advanced users:
|
|
256
294
|
|
|
@@ -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.4.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,15 @@ 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",
|
|
52
|
+
"psutil>=5.9",
|
|
53
|
+
"annotated-types>=0.6",
|
|
49
54
|
]
|
|
50
55
|
|
|
51
56
|
[project.urls]
|
|
@@ -56,9 +61,23 @@ Issues = "https://github.com/authentic-research-partners/sciwrite-lint/issues"
|
|
|
56
61
|
Paper = "https://arxiv.org/abs/2604.08501"
|
|
57
62
|
|
|
58
63
|
[project.optional-dependencies]
|
|
64
|
+
# `pip install -e ".[dev]"` brings in every tool used by the mandatory checks
|
|
65
|
+
# and the release pipeline. Exception: the `claude` CLI is a Node app —
|
|
66
|
+
# install separately per https://docs.anthropic.com/en/docs/claude-code.
|
|
59
67
|
dev = [
|
|
60
|
-
|
|
68
|
+
# Mandatory checks — run after every code change
|
|
69
|
+
"pytest>=9.0.3",
|
|
61
70
|
"pytest-xdist>=3.0",
|
|
71
|
+
"ruff>=0.15",
|
|
72
|
+
"mypy>=1.18",
|
|
73
|
+
"ast-grep-cli>=0.35",
|
|
74
|
+
# Release pipeline (scripts/release.sh phases 3–6)
|
|
75
|
+
"deptry>=0.25",
|
|
76
|
+
"bandit>=1.9",
|
|
77
|
+
"pip-audit>=2.10",
|
|
78
|
+
"pip-licenses>=5.5",
|
|
79
|
+
"twine>=6.0",
|
|
80
|
+
"build>=1.0",
|
|
62
81
|
]
|
|
63
82
|
|
|
64
83
|
[project.scripts]
|
|
@@ -67,44 +86,3 @@ sciwrite-lint = "sciwrite_lint.__main__:main"
|
|
|
67
86
|
[tool.setuptools.packages.find]
|
|
68
87
|
include = ["sciwrite_lint*"]
|
|
69
88
|
|
|
70
|
-
[tool.pytest.ini_options]
|
|
71
|
-
testpaths = ["tests"]
|
|
72
|
-
pythonpath = ["."]
|
|
73
|
-
markers = [
|
|
74
|
-
"integration: tests that read actual paper files",
|
|
75
|
-
"network: tests that make real network connections (slow)",
|
|
76
|
-
]
|
|
77
|
-
# PID-unique basetemp set in conftest.py — prevents FileExistsError
|
|
78
|
-
# when multiple pytest processes run concurrently.
|
|
79
|
-
addopts = "-n auto -p no:cacheprovider"
|
|
80
|
-
|
|
81
|
-
[tool.bandit]
|
|
82
|
-
skips = [
|
|
83
|
-
"B101", # assert — used for type narrowing and internal invariants
|
|
84
|
-
"B104", # bind 0.0.0.0 — container start commands for GROBID/vLLM
|
|
85
|
-
"B314", # xml.etree.fromstring — parsing trusted GROBID TEI output
|
|
86
|
-
"B404", # subprocess import — CLI tool, subprocess is core functionality
|
|
87
|
-
"B405", # xml.etree import — parsing trusted GROBID TEI output
|
|
88
|
-
"B603", # subprocess call — CLI invokes grobid/vllm/claude by design
|
|
89
|
-
"B607", # partial path — CLI tools resolved via PATH
|
|
90
|
-
"B608", # SQL string — internal usage.db, no user input in queries
|
|
91
|
-
"B615", # HF from_pretrained without revision pin — trusted publisher (Qwen), research tool
|
|
92
|
-
]
|
|
93
|
-
|
|
94
|
-
[tool.deptry]
|
|
95
|
-
optional_dependencies_dev_groups = ["dev"]
|
|
96
|
-
|
|
97
|
-
[tool.deptry.per_rule_ignores]
|
|
98
|
-
# Self-imports of shipped non-package directories
|
|
99
|
-
DEP001 = ["evals", "eval_real_world", "bench"]
|
|
100
|
-
# Lazy/conditional imports in optional groups — deptry can't detect these
|
|
101
|
-
DEP002 = ["bibtexparser", "pdfplumber", "sentence-transformers", "numpy", "sqlite-vec", "grobid-tei-xml", "torch", "transformers", "Pillow", "pypdf", "pdf2image"]
|
|
102
|
-
# Intra-package imports
|
|
103
|
-
DEP003 = ["sciwrite_lint", "evals", "eval_real_world"]
|
|
104
|
-
|
|
105
|
-
[tool.mypy]
|
|
106
|
-
plugins = ["pydantic.mypy"]
|
|
107
|
-
[[tool.mypy.overrides]]
|
|
108
|
-
# No py.typed marker or typeshed stubs available
|
|
109
|
-
module = ["bibtexparser", "sqlite_vec", "pypdf", "pypdfium2", "pypdfium2.raw", "transformers", "pdf2image"]
|
|
110
|
-
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.4.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
|
+
]
|