proofpath 0.0.1__tar.gz → 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.
Files changed (141) hide show
  1. proofpath-0.1.0/.env.example +4 -0
  2. proofpath-0.1.0/CHANGELOG.md +162 -0
  3. proofpath-0.1.0/PKG-INFO +224 -0
  4. proofpath-0.1.0/README.md +170 -0
  5. proofpath-0.1.0/docs/eval/2026-09-11-coverage.md +110 -0
  6. proofpath-0.1.0/docs/eval/2026-09-11-ghosts.md +77 -0
  7. proofpath-0.1.0/docs/eval/2026-09-11-pairing.md +279 -0
  8. proofpath-0.1.0/docs/eval/2026-09-11-scifact-dev.md +90 -0
  9. proofpath-0.1.0/docs/eval/2026-09-12-ghosts.md +121 -0
  10. proofpath-0.1.0/docs/eval/2026-09-12-scifact-dev.md +59 -0
  11. proofpath-0.1.0/docs/eval/2026-09-12-tiers.md +95 -0
  12. proofpath-0.1.0/docs/eval/2026-09-12-v0.1-live.md +719 -0
  13. proofpath-0.1.0/docs/research/2026-09-11-free-llm-api-tiers.md +66 -0
  14. proofpath-0.1.0/docs/superpowers/OPEN-ITEMS.md +345 -0
  15. {proofpath-0.0.1 → proofpath-0.1.0}/docs/superpowers/plans/2026-09-10-proofpath-implementation-plan.md +79 -11
  16. proofpath-0.1.0/docs/superpowers/specs/2026-09-10-proofpath-design.md +924 -0
  17. {proofpath-0.0.1 → proofpath-0.1.0}/pyproject.toml +26 -5
  18. proofpath-0.1.0/scripts/eval_coverage.py +443 -0
  19. proofpath-0.1.0/scripts/eval_ghosts.py +157 -0
  20. proofpath-0.1.0/scripts/eval_pairing.py +598 -0
  21. proofpath-0.1.0/scripts/eval_scifact.py +403 -0
  22. proofpath-0.1.0/scripts/zero_network_check.py +77 -0
  23. {proofpath-0.0.1 → proofpath-0.1.0}/src/proofpath/__init__.py +1 -1
  24. proofpath-0.1.0/src/proofpath/browser.py +260 -0
  25. proofpath-0.1.0/src/proofpath/cache.py +521 -0
  26. proofpath-0.1.0/src/proofpath/claims.py +327 -0
  27. proofpath-0.1.0/src/proofpath/cli.py +708 -0
  28. proofpath-0.1.0/src/proofpath/config.py +216 -0
  29. proofpath-0.1.0/src/proofpath/device.py +60 -0
  30. proofpath-0.1.0/src/proofpath/document.py +155 -0
  31. proofpath-0.1.0/src/proofpath/entailment.py +167 -0
  32. proofpath-0.1.0/src/proofpath/eval/__init__.py +1 -0
  33. proofpath-0.1.0/src/proofpath/eval/metrics.py +111 -0
  34. proofpath-0.1.0/src/proofpath/eval/scifact.py +130 -0
  35. proofpath-0.1.0/src/proofpath/events.py +91 -0
  36. proofpath-0.1.0/src/proofpath/fetch.py +670 -0
  37. proofpath-0.1.0/src/proofpath/ingest.py +711 -0
  38. proofpath-0.1.0/src/proofpath/judge.py +149 -0
  39. proofpath-0.1.0/src/proofpath/models.py +49 -0
  40. proofpath-0.1.0/src/proofpath/numerics.py +309 -0
  41. proofpath-0.1.0/src/proofpath/oa.py +492 -0
  42. proofpath-0.1.0/src/proofpath/paths.py +40 -0
  43. proofpath-0.1.0/src/proofpath/pipeline.py +153 -0
  44. proofpath-0.1.0/src/proofpath/polite.py +140 -0
  45. proofpath-0.1.0/src/proofpath/report.py +707 -0
  46. proofpath-0.1.0/src/proofpath/resolve.py +825 -0
  47. proofpath-0.1.0/src/proofpath/retrieval.py +186 -0
  48. proofpath-0.1.0/src/proofpath/ui.py +264 -0
  49. proofpath-0.1.0/src/proofpath/verify.py +1372 -0
  50. proofpath-0.1.0/tests/__init__.py +0 -0
  51. proofpath-0.1.0/tests/data/draft-live.md +41 -0
  52. proofpath-0.1.0/tests/data/ghost_set.jsonl +258 -0
  53. proofpath-0.1.0/tests/data/pairing_set.jsonl +61 -0
  54. proofpath-0.1.0/tests/fakes.py +96 -0
  55. proofpath-0.1.0/tests/fixtures/oa/crossref_work.json +47 -0
  56. proofpath-0.1.0/tests/fixtures/oa/crossref_work_no_abstract.json +16 -0
  57. proofpath-0.1.0/tests/fixtures/oa/europepmc_fulltext.xml +22 -0
  58. proofpath-0.1.0/tests/fixtures/oa/europepmc_search.json +19 -0
  59. proofpath-0.1.0/tests/fixtures/oa/europepmc_search_empty.json +1 -0
  60. proofpath-0.1.0/tests/fixtures/oa/europepmc_search_not_in_epmc.json +18 -0
  61. proofpath-0.1.0/tests/fixtures/oa/openalex_work.json +19 -0
  62. proofpath-0.1.0/tests/fixtures/oa/s2_paper.json +15 -0
  63. proofpath-0.1.0/tests/fixtures/oa/s2_paper_no_abstract.json +6 -0
  64. proofpath-0.1.0/tests/fixtures/oa/unpaywall.json +12 -0
  65. proofpath-0.1.0/tests/fixtures/oa/unpaywall_closed.json +1 -0
  66. proofpath-0.1.0/tests/fixtures/oa/unpaywall_landing_only.json +12 -0
  67. proofpath-0.1.0/tests/fixtures/resolve/arxiv_id_roberta.xml +51 -0
  68. proofpath-0.1.0/tests/fixtures/resolve/arxiv_title_fabricated.xml +10 -0
  69. proofpath-0.1.0/tests/fixtures/resolve/arxiv_title_roberta.xml +51 -0
  70. proofpath-0.1.0/tests/fixtures/resolve/crossref_alphafold.json +1 -0
  71. proofpath-0.1.0/tests/fixtures/resolve/crossref_fabricated.json +1 -0
  72. proofpath-0.1.0/tests/fixtures/resolve/crossref_numpy.json +37 -0
  73. proofpath-0.1.0/tests/fixtures/resolve/crossref_rag.json +1 -0
  74. proofpath-0.1.0/tests/fixtures/resolve/crossref_retracted_wakefield.json +1 -0
  75. proofpath-0.1.0/tests/fixtures/resolve/crossref_roberta.json +1 -0
  76. proofpath-0.1.0/tests/fixtures/resolve/crossref_work_alphafold.json +1 -0
  77. proofpath-0.1.0/tests/fixtures/resolve/crossref_work_numpy.json +34 -0
  78. proofpath-0.1.0/tests/fixtures/resolve/openalex_alphafold.json +1 -0
  79. proofpath-0.1.0/tests/fixtures/resolve/openalex_fabricated.json +1 -0
  80. proofpath-0.1.0/tests/fixtures/resolve/openalex_rag.json +1 -0
  81. proofpath-0.1.0/tests/fixtures/resolve/openalex_retracted_wakefield.json +1 -0
  82. proofpath-0.1.0/tests/fixtures/resolve/openalex_roberta.json +1 -0
  83. proofpath-0.1.0/tests/fixtures/resolve/openlibrary_fabricated.json +1 -0
  84. proofpath-0.1.0/tests/fixtures/resolve/openlibrary_using_language.json +1 -0
  85. proofpath-0.1.0/tests/fixtures/resolve/s2_alphafold.json +1 -0
  86. proofpath-0.1.0/tests/fixtures/resolve/s2_fabricated.json +1 -0
  87. proofpath-0.1.0/tests/fixtures/resolve/s2_rag.json +1 -0
  88. proofpath-0.1.0/tests/fixtures/resolve/s2_roberta.json +1 -0
  89. proofpath-0.1.0/tests/test_browser.py +381 -0
  90. proofpath-0.1.0/tests/test_cache.py +519 -0
  91. proofpath-0.1.0/tests/test_cache_cli.py +88 -0
  92. proofpath-0.1.0/tests/test_check_cli.py +704 -0
  93. proofpath-0.1.0/tests/test_claims.py +436 -0
  94. {proofpath-0.0.1 → proofpath-0.1.0}/tests/test_cli.py +3 -6
  95. proofpath-0.1.0/tests/test_config.py +112 -0
  96. proofpath-0.1.0/tests/test_config_cli.py +143 -0
  97. proofpath-0.1.0/tests/test_device.py +39 -0
  98. proofpath-0.1.0/tests/test_document.py +179 -0
  99. proofpath-0.1.0/tests/test_entailment.py +95 -0
  100. proofpath-0.1.0/tests/test_eval_coverage.py +292 -0
  101. proofpath-0.1.0/tests/test_eval_pairing.py +497 -0
  102. proofpath-0.1.0/tests/test_eval_scifact.py +183 -0
  103. proofpath-0.1.0/tests/test_fetch.py +923 -0
  104. proofpath-0.1.0/tests/test_fetch_cli.py +400 -0
  105. proofpath-0.1.0/tests/test_ingest.py +998 -0
  106. proofpath-0.1.0/tests/test_judge.py +106 -0
  107. proofpath-0.1.0/tests/test_metrics.py +46 -0
  108. proofpath-0.1.0/tests/test_models.py +35 -0
  109. proofpath-0.1.0/tests/test_numerics.py +190 -0
  110. proofpath-0.1.0/tests/test_oa.py +652 -0
  111. proofpath-0.1.0/tests/test_pipeline.py +165 -0
  112. proofpath-0.1.0/tests/test_polite.py +167 -0
  113. proofpath-0.1.0/tests/test_report.py +1179 -0
  114. proofpath-0.1.0/tests/test_resolve.py +903 -0
  115. proofpath-0.1.0/tests/test_resolve_cli.py +209 -0
  116. proofpath-0.1.0/tests/test_retrieval.py +172 -0
  117. proofpath-0.1.0/tests/test_scifact.py +77 -0
  118. proofpath-0.1.0/tests/test_ui.py +551 -0
  119. proofpath-0.1.0/tests/test_verify.py +1333 -0
  120. {proofpath-0.0.1 → proofpath-0.1.0}/uv.lock +17 -14
  121. proofpath-0.0.1/CHANGELOG.md +0 -23
  122. proofpath-0.0.1/PKG-INFO +0 -112
  123. proofpath-0.0.1/README.md +0 -43
  124. proofpath-0.0.1/docs/superpowers/OPEN-ITEMS.md +0 -120
  125. proofpath-0.0.1/docs/superpowers/specs/2026-09-10-proofpath-design.md +0 -587
  126. proofpath-0.0.1/src/proofpath/cli.py +0 -69
  127. {proofpath-0.0.1 → proofpath-0.1.0}/.editorconfig +0 -0
  128. {proofpath-0.0.1 → proofpath-0.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  129. {proofpath-0.0.1 → proofpath-0.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  130. {proofpath-0.0.1 → proofpath-0.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  131. {proofpath-0.0.1 → proofpath-0.1.0}/.github/ISSUE_TEMPLATE/wrong_verdict.yml +0 -0
  132. {proofpath-0.0.1 → proofpath-0.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  133. {proofpath-0.0.1 → proofpath-0.1.0}/.github/workflows/ci.yml +0 -0
  134. {proofpath-0.0.1 → proofpath-0.1.0}/.github/workflows/release.yml +0 -0
  135. {proofpath-0.0.1 → proofpath-0.1.0}/.gitignore +0 -0
  136. {proofpath-0.0.1 → proofpath-0.1.0}/CLAUDE.md +0 -0
  137. {proofpath-0.0.1 → proofpath-0.1.0}/CONTRIBUTING.md +0 -0
  138. {proofpath-0.0.1 → proofpath-0.1.0}/LICENSE +0 -0
  139. {proofpath-0.0.1 → proofpath-0.1.0}/SECURITY.md +0 -0
  140. {proofpath-0.0.1 → proofpath-0.1.0}/src/proofpath/py.typed +0 -0
  141. {proofpath-0.0.1 → proofpath-0.1.0}/tests/test_version.py +0 -0
@@ -0,0 +1,4 @@
1
+ # Copy to .env (gitignored) and fill in. Only the optional LLM judge needs a key;
2
+ # everything else runs offline. See `proofpath judge` for where this file is read.
3
+ GROQ_API_KEY=
4
+ # GEMINI_API_KEY=
@@ -0,0 +1,162 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-09-12
10
+
11
+ First working release: `proofpath check` verifies a document's citations end to end
12
+ and writes a report that states its own coverage.
13
+
14
+ ### Added
15
+ - `proofpath check TARGET` as the v0.1 surface: compiler-style diagnostics, a
16
+ markdown `report.md`, `--format json`, `-q`, and exit codes `0` / `1` / `2`.
17
+ - Confidence tiers calibrated on SciFact dev rather than chosen by hand
18
+ (`decide=0.45`, `medium=0.457948`, `high=0.99933`;
19
+ `docs/eval/2026-09-12-tiers.md`), shipped as `pipeline.DEFAULT_THRESHOLDS`.
20
+ - Nine live user-like runs recorded in `docs/eval/2026-09-12-v0.1-live.md`, and
21
+ `scripts/zero_network_check.py`, which guards both HTTP clients and re-verifies a
22
+ document: with the network gone the fetch ladder makes no attempt at all, because
23
+ resolution produces no identifiers to fetch with.
24
+ - Config and permissions module: `config.toml` under the platform config dir,
25
+ `proofpath config` / `proofpath config set permissions.<key>`, and the rule that an
26
+ `ask` permission without a TTY resolves to `deny` and is reported (spec §7.1).
27
+ - Core types (`Verdict` cannot be `SUPPORTED`/`REFUTED` without a passage), device
28
+ selection (CUDA → CoreML → CPU), sentence retrieval over `fastembed` embeddings
29
+ scanned with numpy, ONNX NLI entailment on `cross-encoder/nli-deberta-v3-base`, and the aggregation
30
+ pipeline.
31
+ - Reference resolution (spec §8): Crossref + Semantic Scholar first, then arXiv,
32
+ Open Library and OpenAlex before any ghost call; identity decided only by
33
+ field agreement against the raw string; DOI / arXiv id resolved directly;
34
+ retraction check via Crossref's Retraction Watch data and OpenAlex; new state
35
+ `UNVERIFIED (not in bibliographic indexes)`; per-host throttling, `Retry-After`,
36
+ OpenAlex daily-budget handling. `proofpath resolve "<reference>"` on the CLI.
37
+ Hand-built ghost set (106 real, 100 fabricated, 20 mutated) and
38
+ `scripts/eval_ghosts.py`: false-ghost rate 0 %.
39
+ - Persistent cache: one plain SQLite file (`sources`, `raw_text` with 7-day TTL,
40
+ `chunks` with float32 embeddings, `verdicts`), a schema `CHECK` that refuses an
41
+ asserted verdict without a passage, and `proofpath cache` / `cache path` / `ls` /
42
+ `show` / `clear [--expired]`.
43
+ - Numeric claim layer (spec §10): percentages, factors and unit counts with
44
+ direction are compared before NLI; an unambiguous contradiction is refuted by
45
+ rule with both figures named (`Verdict.reason`). Conservative by design: one
46
+ comparable figure on each side, change never against level.
47
+ - Judge settings (`[judge]` in config, Groq default) with `proofpath config check`
48
+ and `proofpath config set judge.<key>`; API key resolved from the environment or
49
+ `.env`, never stored or printed. `.env.example` added.
50
+ - SciFact loader pinned to the AI2 tarball by sha256, evaluation metrics, and
51
+ `scripts/eval_scifact.py`. First measured result: dev accuracy 0.606 vs 0.406
52
+ trivial baseline (`docs/eval/2026-09-11-scifact-dev.md`).
53
+
54
+ - Fetch ladder (spec §7): `httpx` → `curl_cffi` TLS impersonation → browser engine
55
+ behind the §7.1 consent prompt → Wayback Machine; `robots.txt` via `protego`;
56
+ content type from headers; per-host throttling and backoff shared in `polite.py`;
57
+ fetched text cached with the 7-day TTL. Distinct honesty states for blocked,
58
+ blocked-by-robots, browser-not-permitted, unreachable, provider-unavailable and
59
+ network-denied — never collapsed.
60
+ - Consent gate for the ~280 MB browser engine: asks at most once per run, never
61
+ without a TTY, `always`/`never` persist to config, installs with `pip` (or `uv`)
62
+ and `scrapling install`, and reports how many sources were skipped.
63
+ - Open-access chain: Semantic Scholar → Crossref TDM links → Unpaywall (only with a
64
+ contact address) → Europe PMC → arXiv → landing page → abstract (OpenAlex last);
65
+ abstract-only results labelled `LOW CONFIDENCE (abstract only)`. DataCite arXiv
66
+ DOIs resolve straight to the arXiv PDF. `proofpath fetch <url|doi|arXiv id>`.
67
+ - `scripts/eval_coverage.py`: measured 72 % full text / 18 % abstract / 10 % none
68
+ on 50 DOIs (`docs/eval/2026-09-11-coverage.md`).
69
+
70
+ - Document ingest and claim extraction (spec §9): `document.py` value types with
71
+ page/line locators; `ingest.py` for PDF (pymupdf blocks, per-page line numbers,
72
+ running header/footer removal, superscript citations), docx (paragraphs and tables),
73
+ markdown and plain text, with the bibliography kept as raw strings, every
74
+ unparseable page reported as a `PageError` and a page holding an image and no text
75
+ reported as a scan rather than passed on as an empty page; `claims.py` pairs numeric
76
+ markers (`[12]`, `[12,15]`, `[12-15]`) with their sentence, applies the
77
+ `PARAGRAPH-SCOPED` rule, reports author-year markers as `UNSUPPORTED CITATION STYLE`
78
+ and any number the bibliography does not print as unresolved. Hand-built pairing set
79
+ (61 passages, rate 0.98) and `scripts/eval_pairing.py`; four real documents (three
80
+ PDFs and one extracted text) measured in `docs/eval/2026-09-11-pairing.md`.
81
+
82
+ - `proofpath check` (spec §9, §13.2, §15): one `verify()` entry point built as
83
+ `prepare()` (parse, claims, resolve, retractions, fetch) and `decide_all()` (retrieval,
84
+ numeric rule, NLI, cached per source and claim); compiler-style diagnostics with the
85
+ quoted passage, the confidence tier and the exact honesty state; a coverage block in
86
+ every run and a "coverage is weak" line when a quarter or more of the sources could
87
+ not be read; `report.md` written by default, `--format json`, `-q`, `--out`, `check -`
88
+ for stdin, a real Ctrl-C that keeps what was decided (exit 2). A second run of the
89
+ same document re-decides nothing: chunks and verdicts come from the cache and neither
90
+ model scores again, although the models are still loaded and reference resolution and
91
+ the retraction check still go to the network. Cache schema v2: chunks carry the text
92
+ digest they were cut from and a source's verdicts are dropped when its text changes.
93
+ `permissions.network = deny` now also skips reference resolution and the retraction
94
+ check, each reported as not attempted.
95
+
96
+ ### Changed
97
+ - CLI surface (spec §13.3): `permissions` and `judge` groups replaced by `config`
98
+ (`config` / `show` / `path` / `set SECTION.KEY VALUE` / `check`); global
99
+ `--no-color` and `-q`; one `ui.py` layer over `rich` owns every colour and the
100
+ 10-column key/value layout; `resolve --format json`; "provider unavailable"
101
+ exits `1` (a finding), no longer `2`. A resolved but retracted reference and a
102
+ URL that was reached but yielded no text (`reached but no text extracted`) are
103
+ findings too (`1`). `skipped N source(s)` counts sources, not the URLs tried for
104
+ them; `--format json` adds `browser.skipped_urls`. `permissions.network` binds
105
+ the open-access providers as well as the ladder, and `ask` without a TTY is
106
+ `deny`, reported.
107
+ - Retrieval no longer depends on `sqlite-vec`: a numpy cosine scan is faster at
108
+ every measured scale and the plain SQLite file opens in any GUI.
109
+ - Spec: `PARAGRAPH-SCOPED` and `UNSUPPORTED CITATION STYLE` states, three-tier
110
+ confidence display, 7-day raw-text cache TTL, v0.1 limited to numeric citation
111
+ markers.
112
+
113
+ ### Fixed (found by the release's own live runs, `docs/eval/2026-09-12-v0.1-live.md`)
114
+ - A real reference whose author list carries a surname particle (`van der Walt`) was
115
+ called a `GHOST REFERENCE` — product rule 3. Reference resolution now understands
116
+ particles (including glued `al-`/`el-` forms), scores the title against every
117
+ title-like segment, and accepts a DOI whose record agrees on first author and year
118
+ as `RESOLVED (low confidence)` instead of a ghost. The ghost set grew to 258 rows
119
+ (`docs/eval/2026-09-12-ghosts.md`): false-ghost rate 0.0 %, fabricated recall 100 %.
120
+ - A fabricated reference in a numbered bibliography was reported as
121
+ `UNVERIFIED (not in bibliographic indexes)` instead of a ghost, because the printed
122
+ marker (`[7] `) blinded the "is this even a paper" check. The marker is stripped once
123
+ at the resolver's entry, guarded so it can never remove a year, an identifier or a
124
+ title that begins with a number.
125
+ - A markdown draft piped through `proofpath -q check -` lost its `## References`.
126
+ - The consent-gated browser step (fetch-ladder step 3) could not install itself
127
+ (`scrapling` has no `__main__`); it now runs the package's own CLI after `pip`/`uv`,
128
+ and a paywalled Cell landing page was read through it live (3,632 words).
129
+ - A piped run occasionally aborted with exit `134` from ONNX runtime teardown after
130
+ printing a complete report; the engine now releases both models explicitly and the
131
+ CLI flushes its streams before exiting (20 of 20 consecutive runs exit `1`).
132
+ - A document with citation markers but no detected bibliography printed a clean-looking
133
+ `0 %` coverage block; it now says `no bibliography was found; N citation markers could
134
+ not be checked`.
135
+
136
+ ### Known issues
137
+ - Reference resolution and the retraction check are not cached, so a warm re-run is
138
+ still a network run: a 68-reference PDF takes about 13 minutes cold and 3.7 minutes
139
+ cached, a 129-reference one 24 minutes cold. Concurrent resolution and a resolution
140
+ cache are v0.2 work.
141
+ - Two-author lists written `First Last and First Last` resolve to `AMBIGUOUS`, not
142
+ `RESOLVED`; an arXiv id whose record agrees on author and year is not yet rescued the
143
+ way a DOI is.
144
+ - `browser.is_installed()` checks that the packages import, not that a browser binary
145
+ exists; a half-installed environment skips the installer and fails inside the browser
146
+ fetch (reported as an unverified source, never a crash).
147
+
148
+ ## [0.0.1] - 2026-09-10
149
+
150
+ First release. The verification pipeline is not implemented; this reserves the name
151
+ and establishes the interface, packaging and CI that later phases build on.
152
+
153
+ ### Added
154
+ - Design specification with measured source-access data (spec §6), the fetch ladder
155
+ and its permission model (§7), and corrected reference resolution (§8).
156
+ - Phased implementation plan, ordered by risk retired rather than user-visible
157
+ progress.
158
+ - `proofpath` command. A bare invocation is a first-class entry point rather than a
159
+ help screen, which is where the TUI will attach. Exit codes are fixed: `0` clean,
160
+ `1` findings, `2` the run itself failed.
161
+ - Cross-platform CI on Linux, macOS and Windows, and PyPI publishing through trusted
162
+ publishing rather than a stored API token.
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.5
2
+ Name: proofpath
3
+ Version: 0.1.0
4
+ Summary: Check whether the sources behind a claim actually say what the claim says.
5
+ Project-URL: Homepage, https://github.com/Yigtwxx/proofpath
6
+ Project-URL: Repository, https://github.com/Yigtwxx/proofpath
7
+ Project-URL: Issues, https://github.com/Yigtwxx/proofpath/issues
8
+ Project-URL: Changelog, https://github.com/Yigtwxx/proofpath/blob/main/CHANGELOG.md
9
+ Author: Yigit Erdogan
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: citation,cli,entailment,fact-checking,nli,rag,research-integrity,retrieval-augmented-generation
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Text Processing :: Linguistic
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: curl-cffi>=0.16.1
26
+ Requires-Dist: fastembed>=0.5
27
+ Requires-Dist: httpx>=0.28
28
+ Requires-Dist: huggingface-hub>=0.30
29
+ Requires-Dist: numpy>=1.26
30
+ Requires-Dist: onnxruntime>=1.20
31
+ Requires-Dist: orjson>=3.11
32
+ Requires-Dist: platformdirs>=4.3
33
+ Requires-Dist: protego>=0.3
34
+ Requires-Dist: pymupdf>=1.25
35
+ Requires-Dist: python-docx>=1.1
36
+ Requires-Dist: rich>=13
37
+ Requires-Dist: scrapling>=0.4.15
38
+ Requires-Dist: textual>=1.0
39
+ Requires-Dist: tokenizers>=0.20
40
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
41
+ Requires-Dist: typer>=0.15
42
+ Provides-Extra: browser
43
+ Requires-Dist: scrapling[fetchers]>=0.4.15; extra == 'browser'
44
+ Provides-Extra: dev
45
+ Requires-Dist: mypy>=1.14; extra == 'dev'
46
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
47
+ Requires-Dist: pytest>=8.3; extra == 'dev'
48
+ Requires-Dist: respx>=0.22; extra == 'dev'
49
+ Requires-Dist: ruff>=0.9; extra == 'dev'
50
+ Provides-Extra: gpu
51
+ Requires-Dist: sentence-transformers>=3.3; extra == 'gpu'
52
+ Requires-Dist: torch>=2.5; extra == 'gpu'
53
+ Description-Content-Type: text/markdown
54
+
55
+ # proofpath
56
+
57
+ > Don't guess. Show the evidence.
58
+
59
+ `proofpath` checks whether the sources behind a claim actually say what the claim
60
+ says. Point it at a paper or a draft and it verifies every citation on three levels,
61
+ then shows you the passage behind each verdict.
62
+
63
+ 1. **Does the source exist?** — Crossref, Semantic Scholar, arXiv, Open Library, OpenAlex.
64
+ 2. **Is it still valid?** — Crossref's Retraction Watch data, and OpenAlex.
65
+ 3. **Does it support the claim?** — retrieval and entailment against the source text,
66
+ with a numeric rule that runs before the model.
67
+
68
+ Most tools stop at step 1. Step 3 is the point.
69
+
70
+ Runs locally and free: no API key, no Docker, no server. Windows, Linux, macOS.
71
+
72
+ ```bash
73
+ uv tool install proofpath
74
+
75
+ proofpath check paper.pdf # one-shot report → report.md, exit 0/1/2
76
+ proofpath check draft.md --format json | jq '.coverage'
77
+ proofpath -q check - < draft.md # stdin; findings and coverage only
78
+ ```
79
+
80
+ The install carries **no browser engine**. When a publisher blocks the plain fetch,
81
+ proofpath asks once before downloading one — about 280 MB, into its own environment
82
+ and the shared browser cache, never system-wide — and remembers the answer.
83
+ `proofpath config set permissions.install_browser never` stops it asking at all.
84
+
85
+ ## Honesty
86
+
87
+ **Most verdicts are "not enough information", and that is the honest answer.** On
88
+ SciFact dev only **135 of 340** claim–source pairs score above the decision threshold
89
+ at all; the other 60 % are `NEI` rather than guessed. Full text is openly reachable
90
+ for well under half of published citations, so a real document loses more on top.
91
+
92
+ **Every report states its own coverage** (and adds a warning line when it is thin):
93
+
94
+ ```
95
+ 7 refs: 1 ghost, 2 unsupported, 4 ok
96
+ fulltext 72%
97
+ abstract 14%
98
+ unverified 14%
99
+ ```
100
+
101
+ When a quarter or more of the sources could not be read, a further line says so:
102
+ `coverage is weak: unread sources may hold more, so this is a lower bound`.
103
+
104
+ **No verdict without its passage.** `SUPPORTED` and `REFUTED` cannot exist without
105
+ the quoted sentence they rest on — the cache schema itself refuses to store one.
106
+
107
+ **The confidence tiers are measured, not chosen** — read off a sweep on SciFact dev
108
+ ([details](docs/eval/2026-09-12-tiers.md)): `decide = 0.45`, `medium = 0.457948`,
109
+ `high = 0.99933`. Two caveats belong next to those numbers. `high` was fitted on the
110
+ same split it is reported on: an in-sample point estimate over **21 verdicts** (at
111
+ least 18 correct — roughly 0.65–0.95 at 95 % confidence), so read it as "the model
112
+ was near-certain here", not as a guarantee of 85 % precision. And `medium` lands
113
+ almost exactly on `decide`, so `low` is practically empty among asserted verdicts —
114
+ the display is effectively **two tiers**: near-certain, and asserted at all.
115
+
116
+ ## What the states mean
117
+
118
+ Absence of evidence is never reported as evidence of absence. Each of these is a
119
+ distinct, printed state (spec §15), never collapsed into a verdict:
120
+
121
+ | State | Cause |
122
+ |---|---|
123
+ | `LOW CONFIDENCE (abstract only)` | full text unavailable, abstract used |
124
+ | `UNVERIFIED (blocked)` | 403 or bot protection |
125
+ | `UNVERIFIED (blocked, robots.txt)` | the site's `robots.txt` disallows the fetch |
126
+ | `UNVERIFIED (blocked, browser not permitted)` | steps 1–2 blocked and the browser consent was denied or impossible |
127
+ | `UNVERIFIED (unreachable)` | dead link, Wayback miss |
128
+ | `UNVERIFIED (reached, no text extracted)` | 200 answered, nothing readable came back |
129
+ | `UNVERIFIED (network not permitted)` | `permissions.network = deny` |
130
+ | `UNVERIFIED (provider unavailable)` | API down or rate limited after backoff |
131
+ | `UNVERIFIED (not in bibliographic indexes)` | web page, blog, report — indexes do not cover it, so absence proves nothing |
132
+ | `AMBIGUOUS` | several plausible records, all listed |
133
+ | `NEI` | the source was read and neither supports nor contradicts |
134
+ | `PARAGRAPH-SCOPED` | the citation covers a paragraph; each sentence is judged separately |
135
+ | `UNSUPPORTED CITATION STYLE` | an author–year marker; v0.1 pairs numeric markers only |
136
+
137
+ Exit codes: `0` clean, `1` findings (every `UNVERIFIED` and `LOW CONFIDENCE` counts),
138
+ `2` the run itself failed — no text parsing needed to gate a CI job. An earlier build
139
+ sometimes aborted with `134` after printing a complete report (ONNX runtime
140
+ teardown); fixed in this release — 20 of 20 piped runs exit `1` ([live runs](docs/eval/2026-09-12-v0.1-live.md)).
141
+
142
+ ## What v0.1 cannot do yet
143
+
144
+ - **Numeric citation markers only** — `[12]`, `[12,15]`, `[12-15]`; an author–year
145
+ citation is listed as `UNSUPPORTED CITATION STYLE`, not judged.
146
+ - **Superscript citations only when the PDF marks them as superscript**; a PDF that
147
+ draws them as ordinary digits loses them, and `km²` can be read as `[2]`.
148
+ - **Abstract fallback**: when only an abstract is reachable the verdict is labelled
149
+ `LOW CONFIDENCE (abstract only)`. Three sentences is not a source.
150
+ - **A reference list without a `References` heading is not found at all**, so its
151
+ markers are reported as uncheckable rather than checked.
152
+ - **Resolution and the retraction check are not cached**, so even a warm re-run goes
153
+ to the network for them.
154
+ - **The 0 % false-ghost rate is a property of the hand set's citation style.** Live
155
+ runs hit styles that set does not contain, and misjudged real references because of
156
+ it ([the live runs](docs/eval/2026-09-12-v0.1-live.md)).
157
+ - **A refused or failed browser install is reported, not hidden**: the source is
158
+ `UNVERIFIED (blocked, browser not permitted)` with the install log, never silently
159
+ counted as unreachable.
160
+ - **Coverage is not perfectly reproducible**: two runs minutes apart can read a
161
+ different number of sources, depending on which providers answered.
162
+ - The TUI and `--format sarif` arrive in **v0.2**; the LLM judge and `--summarize`
163
+ in **v0.3**.
164
+
165
+ ## Speed
166
+
167
+ Apple Silicon Mac, models already downloaded ([live runs](docs/eval/2026-09-12-v0.1-live.md)):
168
+
169
+ | document | first run | cached re-run |
170
+ |---|---|---|
171
+ | 1-page markdown draft, 7 references | 79.3 s | 15.8 s * |
172
+ | 19-page arXiv PDF, 68 references | 12 m 57 s | 3 m 42 s |
173
+
174
+  * the draft's cached re-run followed the earlier of the two recorded cold runs; the live doc keeps both.
175
+
176
+ The **first ever** run also downloads about 250 MB of ONNX models. A cached re-run
177
+ reads its chunks and verdicts back from the cache instead of recomputing them, but it
178
+ is not a no-op: both models are still loaded, reference resolution and the retraction
179
+ check still query the network, and any source whose text has expired or was never
180
+ read is fetched again — the PDF re-run above still spent 22 s fetching (it went out to
181
+ Wayback and arXiv) and re-scored 3 of 102 claims.
182
+
183
+ ## Looking inside the cache
184
+
185
+ Everything proofpath fetches, embeds and decides lands in one plain SQLite file:
186
+
187
+ ```bash
188
+ proofpath cache # where it is and what it holds
189
+ proofpath cache ls # sources, chunk/verdict counts, text expiry
190
+ proofpath cache show <id> # one source's chunks and verdicts
191
+ proofpath cache clear --expired
192
+ ```
193
+
194
+ Open `proofpath cache path` in [DB Browser for SQLite](https://sqlitebrowser.org/),
195
+ TablePlus or DBeaver — plain tables, no extension. Raw publisher text expires after
196
+ 7 days; verdicts keep the passage they quote. `proofpath resolve REF` and `proofpath
197
+ fetch URL|DOI` run either half on its own.
198
+
199
+ ## Optional LLM judge — arrives in v0.3
200
+
201
+ Everything above runs locally. An LLM is used only at the end, as an opt-in second
202
+ opinion on low-confidence verdicts — it never sees a source document and cannot
203
+ change a verdict. The settings exist today (`proofpath config check` proves a key
204
+ works, default Groq); `check --judge` and `--summarize` are wired in v0.3. The key
205
+ comes from the environment or a `.env` file, never from config, and is never printed.
206
+
207
+ ## Measured
208
+
209
+ | What | Set | Result |
210
+ |---|---|---|
211
+ | Retrieval + entailment | SciFact dev, 340 pairs | 0.609 accuracy, 0.597 macro-F1, against a 0.406 trivial baseline ([details](docs/eval/2026-09-12-scifact-dev.md)) |
212
+ | Reference resolution | hand-built ghost set, 258 references | 0 % false-ghost, 100 % ghost recall ([details](docs/eval/2026-09-12-ghosts.md)) — but see the live-run caveat above |
213
+ | Source access | 50 DOIs | 72 % full text, 18 % abstract only, 10 % nothing ([details](docs/eval/2026-09-11-coverage.md)) — a real biomedical paper in the live runs reached 33 % full text |
214
+ | Citation pairing | 61 hand-built passages | 0.98 ([details](docs/eval/2026-09-11-pairing.md)) |
215
+
216
+ Published SciFact results sit around 70–75 F1, not 95. Nothing is tuned on a test
217
+ split, and no number is quoted without the run that produced it.
218
+
219
+ - [Design specification](docs/superpowers/specs/2026-09-10-proofpath-design.md) — what it does and the measurements behind each decision
220
+ - [Open items](docs/superpowers/OPEN-ITEMS.md) — what is unresolved, and what has not been verified yet · [Changelog](CHANGELOG.md)
221
+
222
+ ## License
223
+
224
+ MIT
@@ -0,0 +1,170 @@
1
+ # proofpath
2
+
3
+ > Don't guess. Show the evidence.
4
+
5
+ `proofpath` checks whether the sources behind a claim actually say what the claim
6
+ says. Point it at a paper or a draft and it verifies every citation on three levels,
7
+ then shows you the passage behind each verdict.
8
+
9
+ 1. **Does the source exist?** — Crossref, Semantic Scholar, arXiv, Open Library, OpenAlex.
10
+ 2. **Is it still valid?** — Crossref's Retraction Watch data, and OpenAlex.
11
+ 3. **Does it support the claim?** — retrieval and entailment against the source text,
12
+ with a numeric rule that runs before the model.
13
+
14
+ Most tools stop at step 1. Step 3 is the point.
15
+
16
+ Runs locally and free: no API key, no Docker, no server. Windows, Linux, macOS.
17
+
18
+ ```bash
19
+ uv tool install proofpath
20
+
21
+ proofpath check paper.pdf # one-shot report → report.md, exit 0/1/2
22
+ proofpath check draft.md --format json | jq '.coverage'
23
+ proofpath -q check - < draft.md # stdin; findings and coverage only
24
+ ```
25
+
26
+ The install carries **no browser engine**. When a publisher blocks the plain fetch,
27
+ proofpath asks once before downloading one — about 280 MB, into its own environment
28
+ and the shared browser cache, never system-wide — and remembers the answer.
29
+ `proofpath config set permissions.install_browser never` stops it asking at all.
30
+
31
+ ## Honesty
32
+
33
+ **Most verdicts are "not enough information", and that is the honest answer.** On
34
+ SciFact dev only **135 of 340** claim–source pairs score above the decision threshold
35
+ at all; the other 60 % are `NEI` rather than guessed. Full text is openly reachable
36
+ for well under half of published citations, so a real document loses more on top.
37
+
38
+ **Every report states its own coverage** (and adds a warning line when it is thin):
39
+
40
+ ```
41
+ 7 refs: 1 ghost, 2 unsupported, 4 ok
42
+ fulltext 72%
43
+ abstract 14%
44
+ unverified 14%
45
+ ```
46
+
47
+ When a quarter or more of the sources could not be read, a further line says so:
48
+ `coverage is weak: unread sources may hold more, so this is a lower bound`.
49
+
50
+ **No verdict without its passage.** `SUPPORTED` and `REFUTED` cannot exist without
51
+ the quoted sentence they rest on — the cache schema itself refuses to store one.
52
+
53
+ **The confidence tiers are measured, not chosen** — read off a sweep on SciFact dev
54
+ ([details](docs/eval/2026-09-12-tiers.md)): `decide = 0.45`, `medium = 0.457948`,
55
+ `high = 0.99933`. Two caveats belong next to those numbers. `high` was fitted on the
56
+ same split it is reported on: an in-sample point estimate over **21 verdicts** (at
57
+ least 18 correct — roughly 0.65–0.95 at 95 % confidence), so read it as "the model
58
+ was near-certain here", not as a guarantee of 85 % precision. And `medium` lands
59
+ almost exactly on `decide`, so `low` is practically empty among asserted verdicts —
60
+ the display is effectively **two tiers**: near-certain, and asserted at all.
61
+
62
+ ## What the states mean
63
+
64
+ Absence of evidence is never reported as evidence of absence. Each of these is a
65
+ distinct, printed state (spec §15), never collapsed into a verdict:
66
+
67
+ | State | Cause |
68
+ |---|---|
69
+ | `LOW CONFIDENCE (abstract only)` | full text unavailable, abstract used |
70
+ | `UNVERIFIED (blocked)` | 403 or bot protection |
71
+ | `UNVERIFIED (blocked, robots.txt)` | the site's `robots.txt` disallows the fetch |
72
+ | `UNVERIFIED (blocked, browser not permitted)` | steps 1–2 blocked and the browser consent was denied or impossible |
73
+ | `UNVERIFIED (unreachable)` | dead link, Wayback miss |
74
+ | `UNVERIFIED (reached, no text extracted)` | 200 answered, nothing readable came back |
75
+ | `UNVERIFIED (network not permitted)` | `permissions.network = deny` |
76
+ | `UNVERIFIED (provider unavailable)` | API down or rate limited after backoff |
77
+ | `UNVERIFIED (not in bibliographic indexes)` | web page, blog, report — indexes do not cover it, so absence proves nothing |
78
+ | `AMBIGUOUS` | several plausible records, all listed |
79
+ | `NEI` | the source was read and neither supports nor contradicts |
80
+ | `PARAGRAPH-SCOPED` | the citation covers a paragraph; each sentence is judged separately |
81
+ | `UNSUPPORTED CITATION STYLE` | an author–year marker; v0.1 pairs numeric markers only |
82
+
83
+ Exit codes: `0` clean, `1` findings (every `UNVERIFIED` and `LOW CONFIDENCE` counts),
84
+ `2` the run itself failed — no text parsing needed to gate a CI job. An earlier build
85
+ sometimes aborted with `134` after printing a complete report (ONNX runtime
86
+ teardown); fixed in this release — 20 of 20 piped runs exit `1` ([live runs](docs/eval/2026-09-12-v0.1-live.md)).
87
+
88
+ ## What v0.1 cannot do yet
89
+
90
+ - **Numeric citation markers only** — `[12]`, `[12,15]`, `[12-15]`; an author–year
91
+ citation is listed as `UNSUPPORTED CITATION STYLE`, not judged.
92
+ - **Superscript citations only when the PDF marks them as superscript**; a PDF that
93
+ draws them as ordinary digits loses them, and `km²` can be read as `[2]`.
94
+ - **Abstract fallback**: when only an abstract is reachable the verdict is labelled
95
+ `LOW CONFIDENCE (abstract only)`. Three sentences is not a source.
96
+ - **A reference list without a `References` heading is not found at all**, so its
97
+ markers are reported as uncheckable rather than checked.
98
+ - **Resolution and the retraction check are not cached**, so even a warm re-run goes
99
+ to the network for them.
100
+ - **The 0 % false-ghost rate is a property of the hand set's citation style.** Live
101
+ runs hit styles that set does not contain, and misjudged real references because of
102
+ it ([the live runs](docs/eval/2026-09-12-v0.1-live.md)).
103
+ - **A refused or failed browser install is reported, not hidden**: the source is
104
+ `UNVERIFIED (blocked, browser not permitted)` with the install log, never silently
105
+ counted as unreachable.
106
+ - **Coverage is not perfectly reproducible**: two runs minutes apart can read a
107
+ different number of sources, depending on which providers answered.
108
+ - The TUI and `--format sarif` arrive in **v0.2**; the LLM judge and `--summarize`
109
+ in **v0.3**.
110
+
111
+ ## Speed
112
+
113
+ Apple Silicon Mac, models already downloaded ([live runs](docs/eval/2026-09-12-v0.1-live.md)):
114
+
115
+ | document | first run | cached re-run |
116
+ |---|---|---|
117
+ | 1-page markdown draft, 7 references | 79.3 s | 15.8 s * |
118
+ | 19-page arXiv PDF, 68 references | 12 m 57 s | 3 m 42 s |
119
+
120
+  * the draft's cached re-run followed the earlier of the two recorded cold runs; the live doc keeps both.
121
+
122
+ The **first ever** run also downloads about 250 MB of ONNX models. A cached re-run
123
+ reads its chunks and verdicts back from the cache instead of recomputing them, but it
124
+ is not a no-op: both models are still loaded, reference resolution and the retraction
125
+ check still query the network, and any source whose text has expired or was never
126
+ read is fetched again — the PDF re-run above still spent 22 s fetching (it went out to
127
+ Wayback and arXiv) and re-scored 3 of 102 claims.
128
+
129
+ ## Looking inside the cache
130
+
131
+ Everything proofpath fetches, embeds and decides lands in one plain SQLite file:
132
+
133
+ ```bash
134
+ proofpath cache # where it is and what it holds
135
+ proofpath cache ls # sources, chunk/verdict counts, text expiry
136
+ proofpath cache show <id> # one source's chunks and verdicts
137
+ proofpath cache clear --expired
138
+ ```
139
+
140
+ Open `proofpath cache path` in [DB Browser for SQLite](https://sqlitebrowser.org/),
141
+ TablePlus or DBeaver — plain tables, no extension. Raw publisher text expires after
142
+ 7 days; verdicts keep the passage they quote. `proofpath resolve REF` and `proofpath
143
+ fetch URL|DOI` run either half on its own.
144
+
145
+ ## Optional LLM judge — arrives in v0.3
146
+
147
+ Everything above runs locally. An LLM is used only at the end, as an opt-in second
148
+ opinion on low-confidence verdicts — it never sees a source document and cannot
149
+ change a verdict. The settings exist today (`proofpath config check` proves a key
150
+ works, default Groq); `check --judge` and `--summarize` are wired in v0.3. The key
151
+ comes from the environment or a `.env` file, never from config, and is never printed.
152
+
153
+ ## Measured
154
+
155
+ | What | Set | Result |
156
+ |---|---|---|
157
+ | Retrieval + entailment | SciFact dev, 340 pairs | 0.609 accuracy, 0.597 macro-F1, against a 0.406 trivial baseline ([details](docs/eval/2026-09-12-scifact-dev.md)) |
158
+ | Reference resolution | hand-built ghost set, 258 references | 0 % false-ghost, 100 % ghost recall ([details](docs/eval/2026-09-12-ghosts.md)) — but see the live-run caveat above |
159
+ | Source access | 50 DOIs | 72 % full text, 18 % abstract only, 10 % nothing ([details](docs/eval/2026-09-11-coverage.md)) — a real biomedical paper in the live runs reached 33 % full text |
160
+ | Citation pairing | 61 hand-built passages | 0.98 ([details](docs/eval/2026-09-11-pairing.md)) |
161
+
162
+ Published SciFact results sit around 70–75 F1, not 95. Nothing is tuned on a test
163
+ split, and no number is quoted without the run that produced it.
164
+
165
+ - [Design specification](docs/superpowers/specs/2026-09-10-proofpath-design.md) — what it does and the measurements behind each decision
166
+ - [Open items](docs/superpowers/OPEN-ITEMS.md) — what is unresolved, and what has not been verified yet · [Changelog](CHANGELOG.md)
167
+
168
+ ## License
169
+
170
+ MIT