scholarcheck 0.1.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.
- scholarcheck-0.1.0/LICENSE +21 -0
- scholarcheck-0.1.0/PKG-INFO +183 -0
- scholarcheck-0.1.0/README.md +163 -0
- scholarcheck-0.1.0/pyproject.toml +33 -0
- scholarcheck-0.1.0/scholarcheck/__init__.py +50 -0
- scholarcheck-0.1.0/scholarcheck/_nudge.py +58 -0
- scholarcheck-0.1.0/scholarcheck/cli.py +803 -0
- scholarcheck-0.1.0/scholarcheck.egg-info/PKG-INFO +183 -0
- scholarcheck-0.1.0/scholarcheck.egg-info/SOURCES.txt +12 -0
- scholarcheck-0.1.0/scholarcheck.egg-info/dependency_links.txt +1 -0
- scholarcheck-0.1.0/scholarcheck.egg-info/entry_points.txt +2 -0
- scholarcheck-0.1.0/scholarcheck.egg-info/top_level.txt +1 -0
- scholarcheck-0.1.0/setup.cfg +4 -0
- scholarcheck-0.1.0/tests/test_matching.py +168 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guo Cheng
|
|
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,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scholarcheck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verify citations against real metadata - stop hallucinated references. Zero dependencies.
|
|
5
|
+
Author: Guo Cheng
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/GuoCheng24/scholarcheck
|
|
8
|
+
Project-URL: Issues, https://github.com/GuoCheng24/scholarcheck/issues
|
|
9
|
+
Keywords: citations,bibtex,openalex,crossref,arxiv,literature-review,hallucination,research-tools,doi
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# scholarcheck
|
|
22
|
+
|
|
23
|
+
[](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml) [](https://www.python.org/) [](LICENSE)
|
|
24
|
+
|
|
25
|
+
**Stop hallucinated citations.** Verify any reference against real metadata — from the command line, with zero dependencies.
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="docs/three-states.png" width="100%">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<sub>The figure above is generated by <a href="docs/three-states_figure.py">docs/three-states_figure.py</a> — <code>pip install git+https://github.com/GuoCheng24/sciglyph</code> and run it to reproduce <code>docs/three-states.png</code> byte for byte.</sub>
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Language models invent plausible-looking papers: right-sounding title, plausible authors, a DOI that resolves to nothing. `scholarcheck` answers one question honestly — **does this paper actually exist?** — by querying OpenAlex, Semantic Scholar, Crossref and arXiv directly.
|
|
35
|
+
|
|
36
|
+
```console
|
|
37
|
+
$ scholarcheck verify "Deep Residual Learning for Image Recognition"
|
|
38
|
+
MATCH (high confidence) [query term coverage = 100%]
|
|
39
|
+
Deep Residual Learning for Image Recognition (2016, conference-paper; cited=226875) doi:10.1109/cvpr.2016.90
|
|
40
|
+
Kaiming He, Xiangyu Zhang, Shaoqing Ren et al.
|
|
41
|
+
|
|
42
|
+
$ scholarcheck verify "Quantum Topological Radiomics for Zebra Diagnosis in Martian Cohorts"
|
|
43
|
+
NOT FOUND in any of the four sources -> this citation is very likely hallucinated
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Why not just ask an AI assistant?
|
|
47
|
+
|
|
48
|
+
Because an assistant answers from memory, and memory is exactly what fails here. Three design choices make this different:
|
|
49
|
+
|
|
50
|
+
**1. It says "I could not check" instead of "it is fake."**
|
|
51
|
+
A verifier that reports a network outage as *hallucinated* is worse than no verifier. `scholarcheck` tracks every failed request and distinguishes the two:
|
|
52
|
+
|
|
53
|
+
```console
|
|
54
|
+
$ scholarcheck verify "Attention Is All You Need" # with the network down
|
|
55
|
+
INCONCLUSIVE - could not query the sources, so nothing can be said about: Attention Is All You Need
|
|
56
|
+
Could not reach: api.openalex.org: curl: (7) Connection refused
|
|
57
|
+
(no proxy set; if your network needs one, set SCHOLARCHECK_PROXY)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It also knows which sources matter: Semantic Scholar rate-limits aggressively without an API key, so its failure never turns a real answer into "inconclusive" — only the primary sources do.
|
|
61
|
+
|
|
62
|
+
**2. It refuses to guess.**
|
|
63
|
+
Ask for BibTeX from a slightly-wrong title and most tools hand back the nearest hit. Silently citing the *wrong* paper is worse than citing none, so a weak match returns the candidate and stops:
|
|
64
|
+
|
|
65
|
+
```console
|
|
66
|
+
$ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
|
|
67
|
+
No confident match (best term coverage only 62%). Refusing to emit a possibly wrong entry.
|
|
68
|
+
Closest candidate:
|
|
69
|
+
Deep Residual Learning for Image Recognition (2016, CVPR) doi:10.1109/CVPR.2016.90
|
|
70
|
+
-> If that is the paper, re-run with its DOI: scholarcheck bibtex "<DOI>".
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The same refusal applies when the sources themselves are unavailable, which is
|
|
74
|
+
when a wrong entry is most likely — the "best" match would then be whichever
|
|
75
|
+
paper happened to be reachable:
|
|
76
|
+
|
|
77
|
+
```console
|
|
78
|
+
$ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
|
|
79
|
+
INCONCLUSIVE - a primary source could not be reached, so no entry is emitted for: ...
|
|
80
|
+
Could not reach: api.openalex.org: HTTP 429
|
|
81
|
+
(the partial search's best candidate was 50% coverage - not enough to stand on
|
|
82
|
+
while sources are down)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**3. An identifier is resolved, not searched.**
|
|
86
|
+
`verify "arXiv:1906.08253"` looks the identifier up directly. Feeding it to a
|
|
87
|
+
title matcher would return whatever paper happens to share those digits and
|
|
88
|
+
then score it as a mismatch — which reads as *"this citation is fake"* when the
|
|
89
|
+
truth is that the query was never looked up properly.
|
|
90
|
+
|
|
91
|
+
**4. Recency is a separate command, on purpose.**
|
|
92
|
+
Relevance ranking systematically favours highly-cited older work, which is exactly wrong when you are checking whether someone *just* published your idea. `latest` filters by recency as well as relevance.
|
|
93
|
+
|
|
94
|
+
## Install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install git+https://github.com/GuoCheng24/scholarcheck
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or clone and `pip install -e .` if you would rather read the source first — it is
|
|
101
|
+
one file.
|
|
102
|
+
|
|
103
|
+
**No dependencies.** Standard library plus `curl`. Nothing to break, nothing to
|
|
104
|
+
audit, and nothing that needs an API key: every source it queries is open.
|
|
105
|
+
|
|
106
|
+
<sub>Not on PyPI yet, so the git URL above is the install line that works today.
|
|
107
|
+
When it lands, `pip install scholarcheck` will too.</sub>
|
|
108
|
+
|
|
109
|
+
## Commands
|
|
110
|
+
|
|
111
|
+
| | |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `verify "<title/DOI/arXiv id>"` | Is this citation real? An identifier resolves exactly; a title is matched by term coverage |
|
|
114
|
+
| `bibtex "<DOI/title>"` | A BibTeX entry — refuses to guess on a weak match |
|
|
115
|
+
| `search "<keywords>"` | Multi-source search, re-ranked by term overlap |
|
|
116
|
+
| `latest "<keywords>"` | Recent work only — relevance **and** recency |
|
|
117
|
+
| `priorart "<claim>"` | Nearest N real papers for a claim, plus a checklist for judging whether it is already taken |
|
|
118
|
+
| `citedby "<DOI/title>"` | What cited this paper — has someone already extended it? |
|
|
119
|
+
| `journal "<name>"` | Live journal metrics, instead of quoting an impact factor from memory |
|
|
120
|
+
| `injournal "<name>"` | Recent papers from one journal, to study its actual conventions |
|
|
121
|
+
| `fetch "<DOI/arXiv id>"` | Download the open-access PDF so a claim can be checked in full text |
|
|
122
|
+
|
|
123
|
+
Add `--json` to any command for structured output, `-n` for the number of results, `--since YYYY` to bound the year.
|
|
124
|
+
|
|
125
|
+
## Use as a library
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from scholarcheck import verify_citation, get_bibtex, NET_ERRORS
|
|
129
|
+
|
|
130
|
+
paper, confidence = verify_citation("Attention Is All You Need")
|
|
131
|
+
if paper is None and NET_ERRORS:
|
|
132
|
+
... # could not check — not evidence of anything
|
|
133
|
+
elif confidence >= 0.75:
|
|
134
|
+
print(get_bibtex(paper["doi"]))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
All optional:
|
|
140
|
+
|
|
141
|
+
| variable | effect |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `SCHOLARCHECK_MAILTO` | your email — joins OpenAlex's polite pool, giving better rate limits |
|
|
144
|
+
| `SCHOLARCHECK_S2KEY` | Semantic Scholar API key (free) — avoids the frequent 429s |
|
|
145
|
+
| `SCHOLARCHECK_PROXY` | e.g. `socks5h://127.0.0.1:1080`; default is a direct connection |
|
|
146
|
+
|
|
147
|
+
Proxy behaviour is decided **solely** by `SCHOLARCHECK_PROXY`. Inherited `http_proxy` / `all_proxy` variables are stripped before each request, so the tool behaves the same on every machine.
|
|
148
|
+
|
|
149
|
+
## What it can and cannot tell you
|
|
150
|
+
|
|
151
|
+
**A match confirms the paper exists — not that the metadata you have is right.**
|
|
152
|
+
Bibliographic databases often hold several records for one work: a preprint, a
|
|
153
|
+
conference version, a publisher deposit. `verify` returns whichever record
|
|
154
|
+
matched best, so the year and venue you see may belong to a different record
|
|
155
|
+
than the one you meant to cite. Check them; the DOI is the reliable part.
|
|
156
|
+
|
|
157
|
+
**"NOT FOUND" is strong evidence, not proof.** Very new work, non-English
|
|
158
|
+
venues and some book chapters are indexed poorly. When it matters, run
|
|
159
|
+
`search` with looser keywords before concluding a reference is invented.
|
|
160
|
+
|
|
161
|
+
## Notes from real use
|
|
162
|
+
|
|
163
|
+
- **Feed focused keywords, not whole sentences.** A long claim drags in off-topic papers; two or three precise terms work far better.
|
|
164
|
+
- **`search` favours highly-cited older work.** That is what relevance ranking does. Use `latest` when the question is "has this been done recently?"
|
|
165
|
+
- **A title-only judgement is not a prior-art check.** For the closest candidates, `fetch` the PDF and read it.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT © Guo Cheng
|
|
170
|
+
|
|
171
|
+
## 关于那行 star 提示
|
|
172
|
+
|
|
173
|
+
跑命令时,`scholarcheck` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
|
|
174
|
+
|
|
175
|
+
它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
|
|
176
|
+
|
|
177
|
+
永久关掉:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
export SCHOLARCHECK_NO_NUDGE=1
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
计数存在 `$XDG_STATE_HOME/scholarcheck/usage.json`(默认 `~/.local/state/scholarcheck/usage.json`),删掉即重置。
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# scholarcheck
|
|
2
|
+
|
|
3
|
+
[](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml) [](https://www.python.org/) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
**Stop hallucinated citations.** Verify any reference against real metadata — from the command line, with zero dependencies.
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/three-states.png" width="100%">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<sub>The figure above is generated by <a href="docs/three-states_figure.py">docs/three-states_figure.py</a> — <code>pip install git+https://github.com/GuoCheng24/sciglyph</code> and run it to reproduce <code>docs/three-states.png</code> byte for byte.</sub>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Language models invent plausible-looking papers: right-sounding title, plausible authors, a DOI that resolves to nothing. `scholarcheck` answers one question honestly — **does this paper actually exist?** — by querying OpenAlex, Semantic Scholar, Crossref and arXiv directly.
|
|
15
|
+
|
|
16
|
+
```console
|
|
17
|
+
$ scholarcheck verify "Deep Residual Learning for Image Recognition"
|
|
18
|
+
MATCH (high confidence) [query term coverage = 100%]
|
|
19
|
+
Deep Residual Learning for Image Recognition (2016, conference-paper; cited=226875) doi:10.1109/cvpr.2016.90
|
|
20
|
+
Kaiming He, Xiangyu Zhang, Shaoqing Ren et al.
|
|
21
|
+
|
|
22
|
+
$ scholarcheck verify "Quantum Topological Radiomics for Zebra Diagnosis in Martian Cohorts"
|
|
23
|
+
NOT FOUND in any of the four sources -> this citation is very likely hallucinated
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Why not just ask an AI assistant?
|
|
27
|
+
|
|
28
|
+
Because an assistant answers from memory, and memory is exactly what fails here. Three design choices make this different:
|
|
29
|
+
|
|
30
|
+
**1. It says "I could not check" instead of "it is fake."**
|
|
31
|
+
A verifier that reports a network outage as *hallucinated* is worse than no verifier. `scholarcheck` tracks every failed request and distinguishes the two:
|
|
32
|
+
|
|
33
|
+
```console
|
|
34
|
+
$ scholarcheck verify "Attention Is All You Need" # with the network down
|
|
35
|
+
INCONCLUSIVE - could not query the sources, so nothing can be said about: Attention Is All You Need
|
|
36
|
+
Could not reach: api.openalex.org: curl: (7) Connection refused
|
|
37
|
+
(no proxy set; if your network needs one, set SCHOLARCHECK_PROXY)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
It also knows which sources matter: Semantic Scholar rate-limits aggressively without an API key, so its failure never turns a real answer into "inconclusive" — only the primary sources do.
|
|
41
|
+
|
|
42
|
+
**2. It refuses to guess.**
|
|
43
|
+
Ask for BibTeX from a slightly-wrong title and most tools hand back the nearest hit. Silently citing the *wrong* paper is worse than citing none, so a weak match returns the candidate and stops:
|
|
44
|
+
|
|
45
|
+
```console
|
|
46
|
+
$ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
|
|
47
|
+
No confident match (best term coverage only 62%). Refusing to emit a possibly wrong entry.
|
|
48
|
+
Closest candidate:
|
|
49
|
+
Deep Residual Learning for Image Recognition (2016, CVPR) doi:10.1109/CVPR.2016.90
|
|
50
|
+
-> If that is the paper, re-run with its DOI: scholarcheck bibtex "<DOI>".
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The same refusal applies when the sources themselves are unavailable, which is
|
|
54
|
+
when a wrong entry is most likely — the "best" match would then be whichever
|
|
55
|
+
paper happened to be reachable:
|
|
56
|
+
|
|
57
|
+
```console
|
|
58
|
+
$ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
|
|
59
|
+
INCONCLUSIVE - a primary source could not be reached, so no entry is emitted for: ...
|
|
60
|
+
Could not reach: api.openalex.org: HTTP 429
|
|
61
|
+
(the partial search's best candidate was 50% coverage - not enough to stand on
|
|
62
|
+
while sources are down)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**3. An identifier is resolved, not searched.**
|
|
66
|
+
`verify "arXiv:1906.08253"` looks the identifier up directly. Feeding it to a
|
|
67
|
+
title matcher would return whatever paper happens to share those digits and
|
|
68
|
+
then score it as a mismatch — which reads as *"this citation is fake"* when the
|
|
69
|
+
truth is that the query was never looked up properly.
|
|
70
|
+
|
|
71
|
+
**4. Recency is a separate command, on purpose.**
|
|
72
|
+
Relevance ranking systematically favours highly-cited older work, which is exactly wrong when you are checking whether someone *just* published your idea. `latest` filters by recency as well as relevance.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install git+https://github.com/GuoCheng24/scholarcheck
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or clone and `pip install -e .` if you would rather read the source first — it is
|
|
81
|
+
one file.
|
|
82
|
+
|
|
83
|
+
**No dependencies.** Standard library plus `curl`. Nothing to break, nothing to
|
|
84
|
+
audit, and nothing that needs an API key: every source it queries is open.
|
|
85
|
+
|
|
86
|
+
<sub>Not on PyPI yet, so the git URL above is the install line that works today.
|
|
87
|
+
When it lands, `pip install scholarcheck` will too.</sub>
|
|
88
|
+
|
|
89
|
+
## Commands
|
|
90
|
+
|
|
91
|
+
| | |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `verify "<title/DOI/arXiv id>"` | Is this citation real? An identifier resolves exactly; a title is matched by term coverage |
|
|
94
|
+
| `bibtex "<DOI/title>"` | A BibTeX entry — refuses to guess on a weak match |
|
|
95
|
+
| `search "<keywords>"` | Multi-source search, re-ranked by term overlap |
|
|
96
|
+
| `latest "<keywords>"` | Recent work only — relevance **and** recency |
|
|
97
|
+
| `priorart "<claim>"` | Nearest N real papers for a claim, plus a checklist for judging whether it is already taken |
|
|
98
|
+
| `citedby "<DOI/title>"` | What cited this paper — has someone already extended it? |
|
|
99
|
+
| `journal "<name>"` | Live journal metrics, instead of quoting an impact factor from memory |
|
|
100
|
+
| `injournal "<name>"` | Recent papers from one journal, to study its actual conventions |
|
|
101
|
+
| `fetch "<DOI/arXiv id>"` | Download the open-access PDF so a claim can be checked in full text |
|
|
102
|
+
|
|
103
|
+
Add `--json` to any command for structured output, `-n` for the number of results, `--since YYYY` to bound the year.
|
|
104
|
+
|
|
105
|
+
## Use as a library
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from scholarcheck import verify_citation, get_bibtex, NET_ERRORS
|
|
109
|
+
|
|
110
|
+
paper, confidence = verify_citation("Attention Is All You Need")
|
|
111
|
+
if paper is None and NET_ERRORS:
|
|
112
|
+
... # could not check — not evidence of anything
|
|
113
|
+
elif confidence >= 0.75:
|
|
114
|
+
print(get_bibtex(paper["doi"]))
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
All optional:
|
|
120
|
+
|
|
121
|
+
| variable | effect |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `SCHOLARCHECK_MAILTO` | your email — joins OpenAlex's polite pool, giving better rate limits |
|
|
124
|
+
| `SCHOLARCHECK_S2KEY` | Semantic Scholar API key (free) — avoids the frequent 429s |
|
|
125
|
+
| `SCHOLARCHECK_PROXY` | e.g. `socks5h://127.0.0.1:1080`; default is a direct connection |
|
|
126
|
+
|
|
127
|
+
Proxy behaviour is decided **solely** by `SCHOLARCHECK_PROXY`. Inherited `http_proxy` / `all_proxy` variables are stripped before each request, so the tool behaves the same on every machine.
|
|
128
|
+
|
|
129
|
+
## What it can and cannot tell you
|
|
130
|
+
|
|
131
|
+
**A match confirms the paper exists — not that the metadata you have is right.**
|
|
132
|
+
Bibliographic databases often hold several records for one work: a preprint, a
|
|
133
|
+
conference version, a publisher deposit. `verify` returns whichever record
|
|
134
|
+
matched best, so the year and venue you see may belong to a different record
|
|
135
|
+
than the one you meant to cite. Check them; the DOI is the reliable part.
|
|
136
|
+
|
|
137
|
+
**"NOT FOUND" is strong evidence, not proof.** Very new work, non-English
|
|
138
|
+
venues and some book chapters are indexed poorly. When it matters, run
|
|
139
|
+
`search` with looser keywords before concluding a reference is invented.
|
|
140
|
+
|
|
141
|
+
## Notes from real use
|
|
142
|
+
|
|
143
|
+
- **Feed focused keywords, not whole sentences.** A long claim drags in off-topic papers; two or three precise terms work far better.
|
|
144
|
+
- **`search` favours highly-cited older work.** That is what relevance ranking does. Use `latest` when the question is "has this been done recently?"
|
|
145
|
+
- **A title-only judgement is not a prior-art check.** For the closest candidates, `fetch` the PDF and read it.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT © Guo Cheng
|
|
150
|
+
|
|
151
|
+
## 关于那行 star 提示
|
|
152
|
+
|
|
153
|
+
跑命令时,`scholarcheck` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
|
|
154
|
+
|
|
155
|
+
它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
|
|
156
|
+
|
|
157
|
+
永久关掉:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export SCHOLARCHECK_NO_NUDGE=1
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
计数存在 `$XDG_STATE_HOME/scholarcheck/usage.json`(默认 `~/.local/state/scholarcheck/usage.json`),删掉即重置。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scholarcheck"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Verify citations against real metadata - stop hallucinated references. Zero dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Guo Cheng" }]
|
|
13
|
+
keywords = ["citations", "bibtex", "openalex", "crossref", "arxiv",
|
|
14
|
+
"literature-review", "hallucination", "research-tools", "doi"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
scholarcheck = "scholarcheck.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/GuoCheng24/scholarcheck"
|
|
30
|
+
Issues = "https://github.com/GuoCheng24/scholarcheck/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
include = ["scholarcheck*"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""scholarcheck - verifiable literature grounding from the command line.
|
|
2
|
+
|
|
3
|
+
Never cite a paper that does not exist. Every answer is backed by live
|
|
4
|
+
metadata from OpenAlex, Semantic Scholar, Crossref and arXiv.
|
|
5
|
+
|
|
6
|
+
Command line::
|
|
7
|
+
|
|
8
|
+
scholarcheck verify "Attention Is All You Need"
|
|
9
|
+
scholarcheck bibtex "10.1038/s41586-025-10014-0"
|
|
10
|
+
scholarcheck priorart "conformal risk control" -n 6
|
|
11
|
+
|
|
12
|
+
As a library::
|
|
13
|
+
|
|
14
|
+
from scholarcheck import verify_citation, get_bibtex, search
|
|
15
|
+
paper, confidence = verify_citation("Attention Is All You Need")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .cli import (
|
|
19
|
+
multi_search as search,
|
|
20
|
+
latest,
|
|
21
|
+
bibtex as get_bibtex,
|
|
22
|
+
best_match,
|
|
23
|
+
citedby_openalex as cited_by,
|
|
24
|
+
journal_lookup as journal,
|
|
25
|
+
injournal,
|
|
26
|
+
resolve_pdf,
|
|
27
|
+
download_pdf,
|
|
28
|
+
NET_ERRORS,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def verify_citation(query, n=5):
|
|
35
|
+
"""Check whether a citation refers to a real paper.
|
|
36
|
+
|
|
37
|
+
Returns ``(paper, confidence)``, where confidence is the fraction of the
|
|
38
|
+
query's content words covered by the matched title: >=0.75 is a confident
|
|
39
|
+
match, <0.45 means the citation is very likely hallucinated. ``paper`` is
|
|
40
|
+
None when nothing matched.
|
|
41
|
+
|
|
42
|
+
An empty result together with a non-empty :data:`NET_ERRORS` means the
|
|
43
|
+
sources could not be reached - that is *not* evidence the paper is fake.
|
|
44
|
+
"""
|
|
45
|
+
return best_match(query, n)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = ["search", "latest", "get_bibtex", "best_match", "cited_by",
|
|
49
|
+
"journal", "injournal", "resolve_pdf", "download_pdf",
|
|
50
|
+
"verify_citation", "NET_ERRORS", "__version__"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Mention the repo once or twice, to people who are actually using this.
|
|
2
|
+
|
|
3
|
+
Deliberately quiet: never on the first run, never when stderr is not a
|
|
4
|
+
terminal (so piped and redirected output stays clean), never in CI, and
|
|
5
|
+
never more than twice in the lifetime of an install. `SCHOLARCHECK_NO_NUDGE=1`
|
|
6
|
+
turns it off for good.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
REPO = "GuoCheng24/scholarcheck"
|
|
14
|
+
_SHOW_AT = (5, 25) # run counts at which we say something
|
|
15
|
+
_ENV_OFF = "SCHOLARCHECK_NO_NUDGE"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _state_path():
|
|
19
|
+
base = os.environ.get("XDG_STATE_HOME") or (Path.home() / ".local" / "state")
|
|
20
|
+
return Path(base) / "scholarcheck" / "usage.json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _quiet():
|
|
24
|
+
if os.environ.get(_ENV_OFF):
|
|
25
|
+
return True
|
|
26
|
+
if os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"):
|
|
27
|
+
return True
|
|
28
|
+
# Not a terminal means someone is piping or redirecting us; stay out of it.
|
|
29
|
+
return not (hasattr(sys.stderr, "isatty") and sys.stderr.isatty())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def record_run():
|
|
33
|
+
"""Count this run and, at two points, print a single line to stderr.
|
|
34
|
+
|
|
35
|
+
Any failure here is swallowed: a nudge must never break the tool or
|
|
36
|
+
change its exit status.
|
|
37
|
+
"""
|
|
38
|
+
if _quiet():
|
|
39
|
+
return
|
|
40
|
+
try:
|
|
41
|
+
p = _state_path()
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(p.read_text())
|
|
44
|
+
except Exception:
|
|
45
|
+
data = {}
|
|
46
|
+
n = int(data.get("runs", 0)) + 1
|
|
47
|
+
data["runs"] = n
|
|
48
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
p.write_text(json.dumps(data))
|
|
50
|
+
if n in _SHOW_AT:
|
|
51
|
+
print(
|
|
52
|
+
"\n── scholarcheck has been useful " + str(n) + " times. If it saved you time,\n"
|
|
53
|
+
" a star helps other people find it: https://github.com/" + REPO + "\n"
|
|
54
|
+
" (silence this with SCHOLARCHECK_NO_NUDGE=1)",
|
|
55
|
+
file=sys.stderr,
|
|
56
|
+
)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|