citations 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.
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "citations",
3
+ "owner": {
4
+ "name": "Elliot Tower"
5
+ },
6
+ "metadata": {
7
+ "description": "Check that quotations resolve in the sources they cite",
8
+ "version": "0.1.0"
9
+ },
10
+ "plugins": [
11
+ {
12
+ "name": "citations",
13
+ "source": "./plugin",
14
+ "strict": false
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,21 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ contents: read
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ - run: pip install hatchling
20
+ - run: python -m hatchling build
21
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ pdfs/
2
+ *.pyc
3
+ __pycache__/
4
+ .DS_Store
5
+ .venv/
6
+ papis_test/
7
+ pdfs/
8
+
9
+ # secrets
10
+ .env
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Elliot Tower
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ and associated documentation files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or
12
+ substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
15
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.5
2
+ Name: citations
3
+ Version: 0.1.0
4
+ Summary: A library of quotations checked against the sources they came from
5
+ Author-email: Elliot Tower <elliot@elliottower.ai>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: bibliography,bibtex,citations,provenance,reproducibility
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: platformdirs>=3.0
11
+ Requires-Dist: pyyaml>=6.0
12
+ Provides-Extra: lint
13
+ Requires-Dist: papis>=0.15; extra == 'lint'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # citations
17
+
18
+ Check that the passages you quote actually appear in the sources you cite, and keep a library of
19
+ the ones that do.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install citations
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ```bash
30
+ citations init
31
+ citations verify --claims claims/
32
+ ```
33
+
34
+ ```
35
+ 2,940 quotes
36
+
37
+ found 2,940
38
+ not found 0
39
+
40
+ warnings
41
+ 213 short — the source may qualify this in the next clause
42
+ 155 normalized — matched after ignoring punctuation and spacing
43
+
44
+ all found.
45
+ ```
46
+
47
+ ## Commands
48
+
49
+ | Command | What it does |
50
+ |---------|-------------|
51
+ | `citations init` | Create a library here |
52
+ | `citations verify` | Do the quotations resolve in their sources? |
53
+ | `citations resolve` | Backfill missing DOIs and arXiv ids |
54
+ | `citations build` | Rebuild records from bibliographies |
55
+ | `citations lint` | BibTeX correctness, via papis |
56
+ | `citations link` | Point pdfs/ at the papers' artifacts |
57
+
58
+ ## Verify output
59
+
60
+ Three results, and they are exhaustive:
61
+
62
+ | Result | Meaning |
63
+ |--------|---------|
64
+ | `found` | The passage is in the source |
65
+ | `not found` | The source was read and the passage is not in it |
66
+ | `unchecked` | The source could not be read, so no measurement was made |
67
+
68
+ Warnings are separate, because a passage can be found and still worth a second look. A quote
69
+ can be short enough that the next clause changes its meaning — `"We trained 50"` appears
70
+ verbatim in a paper whose sentence continues `"...and 5 refits each for 12 layered"`.
71
+
72
+ `unchecked` is neither a pass nor a failure. Only `not found` fails; `--strict` also fails on
73
+ unchecked, for CI.
74
+
75
+ `not found` means read the source. A mirror-reversed scan or a two-column extraction produces
76
+ the same signal as a passage that was never there.
77
+
78
+ ## Where the library lives
79
+
80
+ ```
81
+ $CITATIONS_HOME if set
82
+ ./.citations/ walking up this project's own, the way git finds .git
83
+ the shared library if you made one with citations init --user
84
+ none of those it tells you to run citations init
85
+ ```
86
+
87
+ Project-local by default, so running the tool inside a paper works on that paper and there is
88
+ no hidden global state.
89
+
90
+ ## What a claim file looks like
91
+
92
+ One file per source, in the paper's `claims/` directory. `citations verify --claims claims`
93
+ reads all of them.
94
+
95
+ ```yaml
96
+ source:
97
+ citation: schiffman2026 # the bibkey
98
+ local: reference/schiffman2026.pdf # what gets read
99
+ sha256: 3f9a… # which bytes were read
100
+ extract_cmd: pdftotext
101
+
102
+ claims:
103
+ orthogonal-cores:
104
+ statement: 'Cores meeting equivalent causal criteria sit at principal angles of 75-90 degrees.'
105
+ quotes:
106
+ - exact: 'and principal angles ranged'
107
+ section: 'body'
108
+ ```
109
+
110
+ `statement` is yours; `exact` is theirs. The tool checks the second only, so a `statement` that
111
+ overreaches its quote is for review to catch — the command cannot.
112
+
113
+ ## Records are YAML
114
+
115
+ So `git diff` shows what changed. A binary store cannot show you that a year moved from 2021 to
116
+ 2022 — a real discrepancy this found between two of one author's own papers.
117
+
118
+ ## Claude Code
119
+
120
+ `plugin/` is a Claude Code plugin that tells Claude when to reach for the CLI.
121
+
122
+ ```bash
123
+ /plugin marketplace add elliottower/citations
124
+ /plugin install citations@citations
125
+ ```
126
+
127
+ MIT licensed. `docs/` has the working practices this came out of.
@@ -0,0 +1,112 @@
1
+ # citations
2
+
3
+ Check that the passages you quote actually appear in the sources you cite, and keep a library of
4
+ the ones that do.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install citations
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ citations init
16
+ citations verify --claims claims/
17
+ ```
18
+
19
+ ```
20
+ 2,940 quotes
21
+
22
+ found 2,940
23
+ not found 0
24
+
25
+ warnings
26
+ 213 short — the source may qualify this in the next clause
27
+ 155 normalized — matched after ignoring punctuation and spacing
28
+
29
+ all found.
30
+ ```
31
+
32
+ ## Commands
33
+
34
+ | Command | What it does |
35
+ |---------|-------------|
36
+ | `citations init` | Create a library here |
37
+ | `citations verify` | Do the quotations resolve in their sources? |
38
+ | `citations resolve` | Backfill missing DOIs and arXiv ids |
39
+ | `citations build` | Rebuild records from bibliographies |
40
+ | `citations lint` | BibTeX correctness, via papis |
41
+ | `citations link` | Point pdfs/ at the papers' artifacts |
42
+
43
+ ## Verify output
44
+
45
+ Three results, and they are exhaustive:
46
+
47
+ | Result | Meaning |
48
+ |--------|---------|
49
+ | `found` | The passage is in the source |
50
+ | `not found` | The source was read and the passage is not in it |
51
+ | `unchecked` | The source could not be read, so no measurement was made |
52
+
53
+ Warnings are separate, because a passage can be found and still worth a second look. A quote
54
+ can be short enough that the next clause changes its meaning — `"We trained 50"` appears
55
+ verbatim in a paper whose sentence continues `"...and 5 refits each for 12 layered"`.
56
+
57
+ `unchecked` is neither a pass nor a failure. Only `not found` fails; `--strict` also fails on
58
+ unchecked, for CI.
59
+
60
+ `not found` means read the source. A mirror-reversed scan or a two-column extraction produces
61
+ the same signal as a passage that was never there.
62
+
63
+ ## Where the library lives
64
+
65
+ ```
66
+ $CITATIONS_HOME if set
67
+ ./.citations/ walking up this project's own, the way git finds .git
68
+ the shared library if you made one with citations init --user
69
+ none of those it tells you to run citations init
70
+ ```
71
+
72
+ Project-local by default, so running the tool inside a paper works on that paper and there is
73
+ no hidden global state.
74
+
75
+ ## What a claim file looks like
76
+
77
+ One file per source, in the paper's `claims/` directory. `citations verify --claims claims`
78
+ reads all of them.
79
+
80
+ ```yaml
81
+ source:
82
+ citation: schiffman2026 # the bibkey
83
+ local: reference/schiffman2026.pdf # what gets read
84
+ sha256: 3f9a… # which bytes were read
85
+ extract_cmd: pdftotext
86
+
87
+ claims:
88
+ orthogonal-cores:
89
+ statement: 'Cores meeting equivalent causal criteria sit at principal angles of 75-90 degrees.'
90
+ quotes:
91
+ - exact: 'and principal angles ranged'
92
+ section: 'body'
93
+ ```
94
+
95
+ `statement` is yours; `exact` is theirs. The tool checks the second only, so a `statement` that
96
+ overreaches its quote is for review to catch — the command cannot.
97
+
98
+ ## Records are YAML
99
+
100
+ So `git diff` shows what changed. A binary store cannot show you that a year moved from 2021 to
101
+ 2022 — a real discrepancy this found between two of one author's own papers.
102
+
103
+ ## Claude Code
104
+
105
+ `plugin/` is a Claude Code plugin that tells Claude when to reach for the CLI.
106
+
107
+ ```bash
108
+ /plugin marketplace add elliottower/citations
109
+ /plugin install citations@citations
110
+ ```
111
+
112
+ MIT licensed. `docs/` has the working practices this came out of.
@@ -0,0 +1,204 @@
1
+ # Why these practices, and what was rejected
2
+
3
+ Companion to `BEST_PRACTICES.md`. Every recommendation there was chosen over something, and
4
+ several were chosen over an earlier version of itself. This records the reasoning so a later
5
+ reader can overturn a decision on its merits rather than rediscovering the argument.
6
+
7
+ ---
8
+
9
+ ## One file per experiment, not five
10
+
11
+ **Rejected:** `PREREG.md` + `TIMELINE.md` + `AMENDMENTS/` + `DEVIATIONS.md` + `MANIFEST.sha256`,
12
+ with a vocabulary distinguishing amendments from deviations.
13
+
14
+ **Why.** Git already hashes every file, so the manifest restates what `git show <sha>:file`
15
+ proves. The timeline is the log. Amendments and deviations differ only in whether results had
16
+ been seen, and that is one column — so filing them in separate directories records the same fact
17
+ twice and adds a decision the author must get right each time.
18
+
19
+ The deeper problem is that a five-file system has five ways to fall out of sync, and the failure
20
+ is silent. A researcher who forgets to update `TIMELINE.md` has a repository that lies. One
21
+ append-only file has one failure mode: you forgot to append, which is visible as a gap.
22
+
23
+ **The strongest counter.** Separate files are easier to diff and easier for a script to parse.
24
+ True, and it would matter at scale — but a study has perhaps ten log lines, and a human reading
25
+ one file beats a script parsing five.
26
+
27
+ **What survives from the rejected design:** the results-access column. That idea was the good one.
28
+
29
+ ---
30
+
31
+ ## OSF headings, not a bespoke template
32
+
33
+ **Rejected:** keeping the existing house format, which already covers most of the same ground
34
+ under better-reading names.
35
+
36
+ **Why.** The existing format is genuinely good — the `msms-subspace-collapse` pre-registration
37
+ has an integrity protocol, a frozen threshold, a separated exploratory phase, and an appended
38
+ deviation recording that its own criterion failed. That last item is the behavior no template
39
+ produces and no tooling enforces.
40
+
41
+ But it is bespoke, which means each new document re-decides what to include. Two OSF headings
42
+ are missing from the house format and both map onto errors that actually occurred:
43
+ *Foreknowledge of data or evidence* would have forced a declaration on results that were reused
44
+ as if confirmatory, and *Inference criteria* turns a hoped-for outcome into a committed threshold.
45
+
46
+ Adopting the headings costs nothing and removes the per-document decision.
47
+
48
+ **The strongest counter.** Twenty-seven headings for a small study is heavy, and most will read
49
+ N/A. Fair — but an explicit N/A is information, and a heading you skipped is indistinguishable
50
+ from one you never considered.
51
+
52
+ **Not done:** retrofitting fifty-one existing documents. Converting a year of work to a template
53
+ is the theater this document exists to prevent.
54
+
55
+ ---
56
+
57
+ ## AsPredicted's eight questions
58
+
59
+ **Rejected as the default, kept as an option.**
60
+
61
+ **Why.** AsPredicted is lighter, widely used, and honest for a small study. It was not chosen as
62
+ the default because it lacks a foreknowledge field, and foreknowledge is the specific thing that
63
+ goes wrong here — exploratory results existing before a plan is written.
64
+
65
+ Use it when a study genuinely has eight questions' worth of content. Do not use the heavier
66
+ template as a way of looking rigorous.
67
+
68
+ ---
69
+
70
+ ## OSF registration for the plan, not a git tag
71
+
72
+ **Rejected:** signed git tags as the freeze mechanism.
73
+
74
+ **Why.** A signed tag is cryptographically sound and free, and it is the native git answer. It
75
+ fails on the only axis that matters: the author controls the repository. A reviewer discounting
76
+ "you could have rewritten this" is discounting exactly the property a tag cannot supply. The
77
+ value of registration is not cryptographic — it is that someone else holds a dated copy.
78
+
79
+ **Where tags still earn their place:** naming freeze commits so `git show prereg/I3:...` resolves
80
+ by name rather than by remembered hash. Do that, but do not mistake it for attestation.
81
+
82
+ ---
83
+
84
+ ## Not cryptographic timestamping
85
+
86
+ **Rejected:** OpenTimestamps, RFC 3161 timestamp authorities, a continuous stamping tool that
87
+ hashes every script and dataset at creation.
88
+
89
+ **Why.** A timestamp proves a lower bound — this existed by T. It cannot prove an upper bound —
90
+ that the work did not start earlier. The failure mode the field actually names is
91
+ pre-registering after results are known, and that is entirely compatible with a valid timestamp
92
+ on the pre-registration: you run the experiment, see the answer, write the plan, stamp it.
93
+
94
+ Continuous stamping of scripts and data is stronger, because it establishes an ordering — plan,
95
+ then code, then results. But it still cannot exclude an unstamped earlier run. You cannot prove
96
+ a negative about your own private activity, and no anchor changes that.
97
+
98
+ **Worth knowing:** the ML literature names this (*PARKing*, preregistering after results are
99
+ known) and reaches for pre-registration as the remedy rather than cryptography.
100
+
101
+ **The strongest counter, and it holds:** continuous stamping is nearly free and makes fraud more
102
+ expensive. Adopt it if you like. Just never describe it as proof of no peeking, which is the
103
+ claim it invites and cannot support.
104
+
105
+ ---
106
+
107
+ ## Not "because a reviewer will ask"
108
+
109
+ **Rejected reasoning**, not a rejected practice.
110
+
111
+ Pre-registration is close to dead as an ML norm. The workshop series collapsed — seventy-three
112
+ proposals in 2020 to twenty-two in 2021, and results-stage follow-through from twenty-three
113
+ papers to three. `preregister.science` has been untouched since 2022 and no ML venue offers
114
+ Registered Reports. TMLR is the pointed case: pre-registration was pitched to it directly and it
115
+ chose to change acceptance criteria instead.
116
+
117
+ So "a reviewer will ask" is false, and building a practice on it would be building on a norm that
118
+ does not exist.
119
+
120
+ **The reason that survives:** a paper arguing for validity standards that does not meet them has
121
+ a self-consistency problem, and would have one even if nobody ever checked. That reason does not
122
+ depend on anyone asking.
123
+
124
+ **Consequence:** do not advertise it. One sentence in methods. A paper that markets its own rigor
125
+ invites an audit of the rigor instead of the result.
126
+
127
+ ---
128
+
129
+ ## Three platforms, not one
130
+
131
+ **Rejected:** consolidating onto OSF alone, and separately, cutting Zenodo entirely.
132
+
133
+ **Why the plan and the code split.** They want opposite properties. A revisable prediction is not
134
+ a prediction, so the plan must freeze. Code legitimately iterates — bugs, dependency breaks,
135
+ tolerances derived from fixtures — so freezing it once would be absurd. One mechanism for both
136
+ forces a bad choice.
137
+
138
+ **Why not OSF alone.** OSF is file storage, not a git remote; mirroring loses the commit history
139
+ that makes freezes checkable. Its GitHub addon exists because they know this.
140
+
141
+ **Why Zenodo survived a cut.** It was nearly dropped on the grounds that OSF registrations
142
+ already snapshot code and mint a DOI. The argument that kept it: arXiv and bioRxiv are community
143
+ reading lists, and posting imposes a cost on every reader in the field. Zenodo is an archive and
144
+ imposes on nobody. For exploratory or AI-heavy work wanting a citable dated record without
145
+ claiming a field's attention, that distinction is real.
146
+
147
+ **Correction to a claim made in its favour:** a published Zenodo deposit cannot be deleted, and
148
+ files cannot be edited — only superseded, with the old version permanently resolvable. This was
149
+ offered as an advantage and is the opposite. It is also why the tool works: a timestamp you can
150
+ revise establishes nothing.
151
+
152
+ ---
153
+
154
+ ## The claims/audits split
155
+
156
+ **Rejected:** one record per claim holding both the extraction and the judgment.
157
+
158
+ **Why.** They have different standing. Verbatim quotations with a pinned sha256 are checkable by
159
+ anyone holding the PDF, and reusable by someone who rejects every verdict. Statuses and verdicts
160
+ are contestable and ours. Merged, a reader cannot tell which parts they are being asked to trust.
161
+
162
+ The split was verified lossless before adoption: every key landed on exactly one side, and all
163
+ 2,940 quotations still resolved afterwards.
164
+
165
+ **The strongest counter.** Two files per claim is more to maintain. Answered by having the loader
166
+ rejoin them, so every consumer still sees one object.
167
+
168
+ ---
169
+
170
+ ## The quote gate, and its known hole
171
+
172
+ **Adopted**, with a limitation that must be stated rather than hidden.
173
+
174
+ The gate catches fabrication: quotations that do not exist in the artifact they cite. It cannot
175
+ catch misinterpretation — a real, resolving quotation attached to a claim it does not support.
176
+
177
+ **And it is defeatable by truncation.** A pinned substring ending before a qualifying clause
178
+ verifies a claim its source contradicts. Observed: `"We trained 50"` resolves while the source
179
+ continues `"...each for 2, 4, and 8 layered variants and 5 refits each for 12 layered
180
+ (GPT2-small)"`. The record claimed fifty; the truth was five.
181
+
182
+ This is a measurement-validity failure in the verification apparatus itself, which is why it is
183
+ documented rather than quietly patched.
184
+
185
+ ---
186
+
187
+ ## Things deliberately not built
188
+
189
+ **A provenance timeline of AI contribution.** Conflates two separate questions — were decisions
190
+ fixed before outcomes, and how was AI used. The first is answered by the log; the second by one
191
+ methods sentence.
192
+
193
+ **A Claude Code plugin for timestamping.** Would have wrapped a mechanism that does not support
194
+ the claim it implies. See above.
195
+
196
+ **A central freeze registry across experiments.** Superseded by per-folder freezes plus a
197
+ generated index. A central file must be kept in sync with N folders, and that is a job which will
198
+ eventually not get done.
199
+
200
+ **Migrating research repositories to stripped-down clean copies.** Tried on three, reverted. The
201
+ paper repositories needed it because they were unshippable. Research repositories are different:
202
+ the experiments, data and pre-registrations are the substance, so a copy holding only the paper
203
+ is a different artifact rather than a tidier one — and the copies dropped pre-registrations,
204
+ which is the failure this whole document exists to prevent.