proofpath 0.2.1__tar.gz → 0.3.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.
- {proofpath-0.2.1 → proofpath-0.3.0}/CHANGELOG.md +46 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/PKG-INFO +53 -8
- {proofpath-0.2.1 → proofpath-0.3.0}/README.md +52 -7
- proofpath-0.3.0/docs/eval/2026-09-15-judge-live.md +95 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/superpowers/OPEN-ITEMS.md +46 -7
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/superpowers/plans/2026-09-10-proofpath-implementation-plan.md +13 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/superpowers/specs/2026-09-10-proofpath-design.md +22 -4
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/__init__.py +1 -1
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/cache.py +91 -4
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/cli.py +60 -6
- proofpath-0.3.0/src/proofpath/judge.py +827 -0
- proofpath-0.3.0/src/proofpath/prompts/__init__.py +1 -0
- proofpath-0.3.0/src/proofpath/prompts/review.md +40 -0
- proofpath-0.3.0/src/proofpath/prompts/summarize.md +30 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/report.py +190 -6
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/app.py +104 -3
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/runs.py +13 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/run_block.py +13 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/ui.py +35 -4
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/verify.py +294 -3
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_cache.py +148 -5
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_check_cli.py +400 -8
- proofpath-0.3.0/tests/test_judge.py +959 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_report.py +228 -1
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_sarif.py +30 -2
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_app.py +115 -5
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_ui.py +60 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_verify.py +563 -3
- proofpath-0.2.1/src/proofpath/judge.py +0 -149
- proofpath-0.2.1/tests/test_judge.py +0 -106
- {proofpath-0.2.1 → proofpath-0.3.0}/.editorconfig +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.env.example +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.gitattributes +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/ISSUE_TEMPLATE/wrong_verdict.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/workflows/ci.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.github/workflows/release.yml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/.gitignore +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/CLAUDE.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/CONTRIBUTING.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/LICENSE +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/SECURITY.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-11-coverage.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-11-ghosts.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-11-pairing.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-11-scifact-dev.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-12-ghosts.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-12-pairing-author-year.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-12-scifact-dev.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-12-tiers.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-12-v0.1-live.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-15-tui-v2-live.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/2026-09-15-v0.2-live.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/tui-v2-plain.svg +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/eval/tui-v2-rich.svg +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/research/2026-09-11-free-llm-api-tiers.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/superpowers/plans/2026-09-12-phases-9-10-plan.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/docs/superpowers/specs/2026-09-15-tui-v2-design.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/pyproject.toml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/scripts/eval_coverage.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/scripts/eval_ghosts.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/scripts/eval_pairing.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/scripts/eval_scifact.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/scripts/zero_network_check.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/browser.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/claims.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/commands.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/config.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/device.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/document.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/entailment.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/eval/__init__.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/eval/metrics.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/eval/scifact.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/events.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/fetch.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/ingest.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/models.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/numerics.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/oa.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/paths.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/pipeline.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/polite.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/py.typed +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/resolve.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/retrieval.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/sarif.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/__init__.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/banner.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/commands.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/pet.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/theme.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/verbs.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/__init__.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/_shared.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/banner.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/finding.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/footer.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/src/proofpath/tui/widgets/prompt.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/__init__.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/conftest.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/draft-author-year.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/draft-live.md +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/ghost_set.jsonl +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/pairing_author_year.jsonl +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/pairing_set.jsonl +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/data/sarif-schema-2.1.0.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fakes.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/crossref_work.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/crossref_work_no_abstract.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/europepmc_fulltext.xml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/europepmc_search.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/europepmc_search_empty.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/europepmc_search_not_in_epmc.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/openalex_work.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/s2_paper.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/s2_paper_no_abstract.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/unpaywall.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/unpaywall_closed.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/oa/unpaywall_landing_only.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/arxiv_id_roberta.xml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/arxiv_title_fabricated.xml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/arxiv_title_roberta.xml +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_alphafold.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_fabricated.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_numpy.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_rag.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_retracted_wakefield.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_roberta.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_work_alphafold.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/crossref_work_numpy.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openalex_alphafold.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openalex_fabricated.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openalex_rag.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openalex_retracted_wakefield.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openalex_roberta.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openlibrary_fabricated.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/openlibrary_using_language.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/s2_alphafold.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/s2_fabricated.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/s2_rag.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/fixtures/resolve/s2_roberta.json +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_browser.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_cache_cli.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_claims.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_cli.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_commands.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_config.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_config_cli.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_device.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_document.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_entailment.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_eval_coverage.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_eval_pairing.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_eval_scifact.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_fetch.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_fetch_cli.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_ingest.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_metrics.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_models.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_numerics.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_oa.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_pipeline.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_polite.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_resolve.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_resolve_cli.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_retrieval.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_scifact.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_banner.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_commands.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_pet.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_rich.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_runs.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_tui_theme.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/tests/test_version.py +0 -0
- {proofpath-0.2.1 → proofpath-0.3.0}/uv.lock +0 -0
|
@@ -6,6 +6,52 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-09-15
|
|
10
|
+
|
|
11
|
+
The judge layer: an opt-in LLM second opinion and an opt-in model-written summary.
|
|
12
|
+
The default run still makes zero LLM calls, and nothing the model says can change a
|
|
13
|
+
verdict. Live run on Groq in `docs/eval/2026-09-15-judge-live.md`.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **`check --judge`** (`judge.py`, `verify.py`). After the local verdicts are final, the
|
|
17
|
+
`low`-tier ones — never a numeric mismatch, never a claim without a quoted passage —
|
|
18
|
+
go to the model in batches of up to 20 items (about 7k tokens), each with its claim and
|
|
19
|
+
passage. The opinion (`SUPPORTED | REFUTED | NEI` plus a one-sentence rationale) is
|
|
20
|
+
attached beside the verdict: a `= judge (groq openai/gpt-oss-120b): …` line under a
|
|
21
|
+
finding it disagrees with, a `judge` column in the markdown `## Checked` table, and
|
|
22
|
+
`judge` fields in the JSON. The local `Verdict`, the finding kind and every state are
|
|
23
|
+
untouched (spec §11.1). Opinions are cached in the new `judgements` table (schema v4,
|
|
24
|
+
additive; wiped with the verdicts when a source's text changes), so a re-run asks
|
|
25
|
+
nothing, and `model_id` is untouched, so toggling `--judge` never invalidates a verdict.
|
|
26
|
+
- **`check --summarize`** and the TUI's **`/summarize`**: one extra call over the finished
|
|
27
|
+
markdown report, run after the report is final, off by default in both front-ends,
|
|
28
|
+
printed as `summary (model-written, groq openai/gpt-oss-120b) …` and as
|
|
29
|
+
`## Summary (model-written, …)` in the file. `--summarize` alone is exactly one call.
|
|
30
|
+
- **`JudgeClient`**: one adapter for Groq (default `openai/gpt-oss-120b`), Gemini and
|
|
31
|
+
Ollama over the OpenAI `chat/completions` shape; strict JSON-schema output with a
|
|
32
|
+
`json_object` fallback, `reasoning_effort=low` with a fallback for providers that
|
|
33
|
+
reject it, `Retry-After` on 429 (capped, accounted), exponential backoff on 5xx, then
|
|
34
|
+
`JudgeUnavailable`. The key comes from the environment or `.env`, never from config,
|
|
35
|
+
never appears in `repr`, errors or logs; provider bodies are never echoed. Prompts are
|
|
36
|
+
packaged template files (`proofpath/prompts/review.md`, `summarize.md`).
|
|
37
|
+
- **Cost on every surface**: the `Judging` and `Summarising` stage lines carry calls and
|
|
38
|
+
prompt/completion tokens; the footer counts the calls; `Report.judge_cost` and
|
|
39
|
+
`models["judge"]` land in the JSON.
|
|
40
|
+
- **An unanswered judge is reported, not hidden**: `judge unavailable after N calls
|
|
41
|
+
(HTTP 401 from …); local verdicts stand` in the stage line, the report header
|
|
42
|
+
(`judge status:` / `summary status:`), the JSON and an unsuppressed terminal line — a
|
|
43
|
+
`-q` or piped run cannot look like a judged-clean one. The SARIF log does not carry it.
|
|
44
|
+
The run never fails because of the judge.
|
|
45
|
+
- Gemini prints its data-use warning once per run (spec §11).
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- `check --judge` / `--summarize` no longer exit with `arrives in v0.3`.
|
|
49
|
+
- Cache schema **v4** (`judgements`); a v1 file still migrates through the whole chain.
|
|
50
|
+
- The markdown `## Checked` table always carries a `judge` column; without `--judge` every
|
|
51
|
+
cell is `—`, so a v0.2 report and a v0.3 one differ by that column alone.
|
|
52
|
+
- `--format json` gained `summary`, `summary_model`, `judge_cost` and per-result `judge`
|
|
53
|
+
fields; every one of them is `null` on a default run.
|
|
54
|
+
|
|
9
55
|
## [0.2.1] - 2026-09-15
|
|
10
56
|
|
|
11
57
|
The TUI's second look. No behaviour change: every state word, every honesty sentence,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: proofpath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Check whether the sources behind a claim actually say what the claim says.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Yigtwxx/proofpath
|
|
6
6
|
Project-URL: Repository, https://github.com/Yigtwxx/proofpath
|
|
@@ -239,7 +239,12 @@ runtime teardown); fixed in v0.1 — 20 of 20 piped runs exit `1`
|
|
|
239
239
|
counted as unreachable.
|
|
240
240
|
- **Coverage is not perfectly reproducible**: two runs minutes apart can read a
|
|
241
241
|
different number of sources, depending on which providers answered.
|
|
242
|
-
- The
|
|
242
|
+
- **The judge is a second opinion, not a second verdict.** `--judge` asks the model only
|
|
243
|
+
about the low-tier verdicts (1 of 10 on the live draft), and its answer is printed
|
|
244
|
+
beside the local verdict, never in place of it. `--summarize` is one extra call over
|
|
245
|
+
the finished report, labelled model-written; if the provider does not answer, the
|
|
246
|
+
markdown report, the JSON and the terminal say so (`judge status` / `summary status`
|
|
247
|
+
in the header). The SARIF log does not carry it — it is a findings document.
|
|
243
248
|
|
|
244
249
|
## Speed
|
|
245
250
|
|
|
@@ -273,13 +278,53 @@ TablePlus or DBeaver — plain tables, no extension. Raw publisher text expires
|
|
|
273
278
|
7 days; verdicts keep the passage they quote; a provider outage is never stored.
|
|
274
279
|
`proofpath resolve REF` and `proofpath fetch URL|DOI` run either half on its own.
|
|
275
280
|
|
|
276
|
-
## Optional LLM judge
|
|
281
|
+
## Optional LLM judge (v0.3)
|
|
277
282
|
|
|
278
|
-
Everything above runs locally
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
Everything above runs locally, and the default run makes **zero** LLM calls. Two flags
|
|
284
|
+
add an LLM at the end, and only there:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
proofpath check paper.pdf --judge # a second opinion on the low-confidence verdicts
|
|
288
|
+
proofpath check paper.pdf --summarize # one model-written paragraph over the finished report
|
|
289
|
+
proofpath config check # proves the key works before you spend a run on it
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**What `--judge` does.** After the local verdicts are final, the verdicts the models
|
|
293
|
+
were least sure about — the `low` tier, never a numeric mismatch and never a claim
|
|
294
|
+
without a quoted passage — go to the model in batches of up to 20 (about 7k tokens),
|
|
295
|
+
each with its claim and the passage it was checked against. The model answers from the
|
|
296
|
+
passage alone, and its label and one-sentence rationale are printed **beside** the local
|
|
297
|
+
verdict: `= judge (groq openai/gpt-oss-120b): NEI — …`. The local verdict, the finding
|
|
298
|
+
kind and the report's states never change. Opinions are cached with the verdict, so a
|
|
299
|
+
re-run asks nothing.
|
|
300
|
+
|
|
301
|
+
**What it cannot do.** It never sees a source document, so it cannot introduce a claim
|
|
302
|
+
or an evidence passage of its own; it cannot turn `NEI` into `SUPPORTED`; it cannot
|
|
303
|
+
hide a source that could not be read. If the provider is down, rate-limited or the key
|
|
304
|
+
is wrong, the run finishes on the local verdicts and says so in the stage line, the
|
|
305
|
+
report header (`judge status:`) and the JSON — `-q` cannot hide it.
|
|
306
|
+
|
|
307
|
+
**What `--summarize` does.** One final call turns the finished markdown report into 3–5
|
|
308
|
+
plain sentences a reader can act on. It runs after the report is complete, its only
|
|
309
|
+
input is that report, it is off by default in the CLI and the TUI (`/summarize` there),
|
|
310
|
+
and the output is labelled `(model-written, <provider> <model>)`. `--summarize` alone
|
|
311
|
+
is exactly one call; with `--judge` the escalation runs first.
|
|
312
|
+
|
|
313
|
+
**Cost.** The footer counts the calls and the stage line the tokens:
|
|
314
|
+
`Judging … 1 of 10 verdicts reviewed, 1 call, 613 prompt · 193 completion tokens` and
|
|
315
|
+
`Summarising … 98 words, 1 call, 1,608 prompt · 343 completion tokens`
|
|
316
|
+
on the live draft ([details](docs/eval/2026-09-15-judge-live.md)). Groq's free tier
|
|
317
|
+
allows roughly one call a minute. Only the low-tier verdicts are sent — 1 of 10 on that
|
|
318
|
+
draft — and up to 20 go in one call, so a long bibliography costs a handful of calls, not
|
|
319
|
+
one per citation.
|
|
320
|
+
|
|
321
|
+
**Providers.** Default is Groq `openai/gpt-oss-120b` (free without a card, no training
|
|
322
|
+
on submitted data). `proofpath config set judge.provider gemini` switches to Gemini —
|
|
323
|
+
note that Google trains on free-tier prompts outside the EEA/UK/CH, and proofpath prints
|
|
324
|
+
that warning once per run. `judge.provider ollama` runs fully offline. Gemini and Ollama are
|
|
325
|
+
fixture-tested and were not exercised live in v0.3.0. All three speak
|
|
326
|
+
the OpenAI `chat/completions` shape. The key comes from `GROQ_API_KEY` / `GEMINI_API_KEY`
|
|
327
|
+
in the environment or a `.env` file, never from config, and is never printed.
|
|
283
328
|
|
|
284
329
|
## Measured
|
|
285
330
|
|
|
@@ -183,7 +183,12 @@ runtime teardown); fixed in v0.1 — 20 of 20 piped runs exit `1`
|
|
|
183
183
|
counted as unreachable.
|
|
184
184
|
- **Coverage is not perfectly reproducible**: two runs minutes apart can read a
|
|
185
185
|
different number of sources, depending on which providers answered.
|
|
186
|
-
- The
|
|
186
|
+
- **The judge is a second opinion, not a second verdict.** `--judge` asks the model only
|
|
187
|
+
about the low-tier verdicts (1 of 10 on the live draft), and its answer is printed
|
|
188
|
+
beside the local verdict, never in place of it. `--summarize` is one extra call over
|
|
189
|
+
the finished report, labelled model-written; if the provider does not answer, the
|
|
190
|
+
markdown report, the JSON and the terminal say so (`judge status` / `summary status`
|
|
191
|
+
in the header). The SARIF log does not carry it — it is a findings document.
|
|
187
192
|
|
|
188
193
|
## Speed
|
|
189
194
|
|
|
@@ -217,13 +222,53 @@ TablePlus or DBeaver — plain tables, no extension. Raw publisher text expires
|
|
|
217
222
|
7 days; verdicts keep the passage they quote; a provider outage is never stored.
|
|
218
223
|
`proofpath resolve REF` and `proofpath fetch URL|DOI` run either half on its own.
|
|
219
224
|
|
|
220
|
-
## Optional LLM judge
|
|
225
|
+
## Optional LLM judge (v0.3)
|
|
221
226
|
|
|
222
|
-
Everything above runs locally
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
Everything above runs locally, and the default run makes **zero** LLM calls. Two flags
|
|
228
|
+
add an LLM at the end, and only there:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
proofpath check paper.pdf --judge # a second opinion on the low-confidence verdicts
|
|
232
|
+
proofpath check paper.pdf --summarize # one model-written paragraph over the finished report
|
|
233
|
+
proofpath config check # proves the key works before you spend a run on it
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**What `--judge` does.** After the local verdicts are final, the verdicts the models
|
|
237
|
+
were least sure about — the `low` tier, never a numeric mismatch and never a claim
|
|
238
|
+
without a quoted passage — go to the model in batches of up to 20 (about 7k tokens),
|
|
239
|
+
each with its claim and the passage it was checked against. The model answers from the
|
|
240
|
+
passage alone, and its label and one-sentence rationale are printed **beside** the local
|
|
241
|
+
verdict: `= judge (groq openai/gpt-oss-120b): NEI — …`. The local verdict, the finding
|
|
242
|
+
kind and the report's states never change. Opinions are cached with the verdict, so a
|
|
243
|
+
re-run asks nothing.
|
|
244
|
+
|
|
245
|
+
**What it cannot do.** It never sees a source document, so it cannot introduce a claim
|
|
246
|
+
or an evidence passage of its own; it cannot turn `NEI` into `SUPPORTED`; it cannot
|
|
247
|
+
hide a source that could not be read. If the provider is down, rate-limited or the key
|
|
248
|
+
is wrong, the run finishes on the local verdicts and says so in the stage line, the
|
|
249
|
+
report header (`judge status:`) and the JSON — `-q` cannot hide it.
|
|
250
|
+
|
|
251
|
+
**What `--summarize` does.** One final call turns the finished markdown report into 3–5
|
|
252
|
+
plain sentences a reader can act on. It runs after the report is complete, its only
|
|
253
|
+
input is that report, it is off by default in the CLI and the TUI (`/summarize` there),
|
|
254
|
+
and the output is labelled `(model-written, <provider> <model>)`. `--summarize` alone
|
|
255
|
+
is exactly one call; with `--judge` the escalation runs first.
|
|
256
|
+
|
|
257
|
+
**Cost.** The footer counts the calls and the stage line the tokens:
|
|
258
|
+
`Judging … 1 of 10 verdicts reviewed, 1 call, 613 prompt · 193 completion tokens` and
|
|
259
|
+
`Summarising … 98 words, 1 call, 1,608 prompt · 343 completion tokens`
|
|
260
|
+
on the live draft ([details](docs/eval/2026-09-15-judge-live.md)). Groq's free tier
|
|
261
|
+
allows roughly one call a minute. Only the low-tier verdicts are sent — 1 of 10 on that
|
|
262
|
+
draft — and up to 20 go in one call, so a long bibliography costs a handful of calls, not
|
|
263
|
+
one per citation.
|
|
264
|
+
|
|
265
|
+
**Providers.** Default is Groq `openai/gpt-oss-120b` (free without a card, no training
|
|
266
|
+
on submitted data). `proofpath config set judge.provider gemini` switches to Gemini —
|
|
267
|
+
note that Google trains on free-tier prompts outside the EEA/UK/CH, and proofpath prints
|
|
268
|
+
that warning once per run. `judge.provider ollama` runs fully offline. Gemini and Ollama are
|
|
269
|
+
fixture-tested and were not exercised live in v0.3.0. All three speak
|
|
270
|
+
the OpenAI `chat/completions` shape. The key comes from `GROQ_API_KEY` / `GEMINI_API_KEY`
|
|
271
|
+
in the environment or a `.env` file, never from config, and is never printed.
|
|
227
272
|
|
|
228
273
|
## Measured
|
|
229
274
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Judge layer live runs — 2026-09-15 (v0.3.0)
|
|
2
|
+
|
|
3
|
+
`proofpath check tests/data/draft-live.md --judge --summarize` on Groq
|
|
4
|
+
(`openai/gpt-oss-120b`, key from `.env`), Apple Silicon, models already downloaded.
|
|
5
|
+
Three runs: the first against the pre-fix build (it found the defect), then a cold run
|
|
6
|
+
(`--no-cache`) and a warm run against the shipped build. Stage lines are copied from
|
|
7
|
+
the terminal; the key never appears in any output.
|
|
8
|
+
|
|
9
|
+
## Run 1 — pre-fix build: the summary came back empty
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Judging groq openai/gpt-oss-120b 1 of 10 verdicts reviewed, 1 call, 661 prompt · 211 completion tokens 0.9s
|
|
13
|
+
Summarising groq openai/gpt-oss-120b summary unavailable after 0 calls (no completion in the 200 response from https://api.groq.com/openai/v1/chat/completions (finish_reason=length)); local verdicts stand 1.0s
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`openai/gpt-oss-120b` is a reasoning model: with the planned `max_tokens=400` the
|
|
17
|
+
completion budget was spent on reasoning and the visible content was empty, and the
|
|
18
|
+
written `report.md` said nothing about the summary at all (the 9.3 reviewer had found
|
|
19
|
+
the same gap from the code). Both are fixed in the shipped build: requests carry
|
|
20
|
+
`reasoning_effort=low` (dropped on a 400 from providers that reject it), the review
|
|
21
|
+
budget is 4096 tokens and the summary budget 1500, and an unanswered summary is
|
|
22
|
+
reported on the stage line, the `summary` CLI line and a `- summary status:` header
|
|
23
|
+
line in the file. The 1,888-test suite was green before this run; the live run found it.
|
|
24
|
+
|
|
25
|
+
## Run 2 — cold (`--no-cache`), shipped build
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Parsing text 1 pages, 7 refs 0.0s
|
|
29
|
+
Claims rules 7 citations, 0 unresolved 0.0s
|
|
30
|
+
Resolving Crossref, Semantic Scholar 6 ok, 0 amb, 1 ghost 9.3s
|
|
31
|
+
Retractions Retraction Watch none 2.5s
|
|
32
|
+
Fetching arXiv, Semantic Scholar 5 full text, 1 abstract, 0 unverified 23.8s
|
|
33
|
+
Verifying coreml 10 claims: 1 supported, 2 not supported, 7 NEI 44.5s
|
|
34
|
+
Judging groq openai/gpt-oss-120b 1 of 10 verdicts reviewed, 1 call, 613 prompt · 193 completion tokens 1.1s
|
|
35
|
+
Summarising groq openai/gpt-oss-120b 98 words, 1 call, 1,608 prompt · 343 completion tokens 1.0s
|
|
36
|
+
… written · 2 API calls · 82.1s
|
|
37
|
+
judge 2,221 prompt · 536 completion tokens
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The escalation set was one verdict — the `low`-tier `NOT SUPPORTED` on the SciPy
|
|
41
|
+
sentence — and the judge's opinion was printed beside it, the local verdict untouched:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
error[not-supported]: claim is not supported by the cited source (confidence: low)
|
|
45
|
+
= judge (groq openai/gpt-oss-120b): NEI — Passage states "SciPy provides fundamental
|
|
46
|
+
algorithms for scientific computing" but does not mention SciPy building on anything.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The numeric mismatch (`high`, rule-decided) and the ghost reference were not sent, as
|
|
50
|
+
specified. The summary, verbatim:
|
|
51
|
+
|
|
52
|
+
> The report checked seven cited references, and it found two claims not supported and
|
|
53
|
+
> one ghost reference; it does not give a total number of claims that were examined. The
|
|
54
|
+
> most serious problem is the high‑confidence "NOT SUPPORTED" error on line 6, where the
|
|
55
|
+
> claim about training time for the Transformer contradicts the source. The coverage
|
|
56
|
+
> analysis shows that only 72 % of the material was verified against full text, 14 % was
|
|
57
|
+
> limited to abstracts, and the remaining 14 % could not be read, so the counts above
|
|
58
|
+
> apply only to the portion that was actually examined.
|
|
59
|
+
|
|
60
|
+
It repeats the coverage caveat as instructed and invents no finding. Two things it
|
|
61
|
+
gets slightly wrong, which is why it is labelled model-written and sits under the
|
|
62
|
+
computed report: it calls the line-6 numeric mismatch a "NOT SUPPORTED" error (the
|
|
63
|
+
report says `numeric-mismatch: claim contradicts the cited source`), and "it does not
|
|
64
|
+
give a total number of claims" is true of the markdown file (the stage table is not
|
|
65
|
+
printed there) but not of the run (`10 claims`).
|
|
66
|
+
|
|
67
|
+
## Run 3 — warm, shipped build
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Resolving cache 6 ok, 0 amb, 1 ghost 0.0s
|
|
71
|
+
Fetching cache 5 full text, 1 abstract, 0 unverified 0.0s
|
|
72
|
+
Verifying coreml 10 claims: 1 supported, 2 not supported, 7 NEI, 10 cached 0.8s
|
|
73
|
+
Judging groq openai/gpt-oss-120b 1 of 10 verdicts reviewed, 0 calls, 0 prompt · 0 completion tokens 0.0s
|
|
74
|
+
Summarising groq openai/gpt-oss-120b 106 words, 1 call, 1,615 prompt · 263 completion tokens 0.9s
|
|
75
|
+
… written · 1 API calls · 1.8s
|
|
76
|
+
judge 1,615 prompt · 263 completion tokens
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The judgement came back from the schema-v4 `judgements` table (0 calls); the summary
|
|
80
|
+
is always one fresh call. A plain `check` of the same file (no flags) reports
|
|
81
|
+
`api_calls: 0` and `summary: null`.
|
|
82
|
+
|
|
83
|
+
## What this does and does not show
|
|
84
|
+
|
|
85
|
+
- The judge layer works end to end on Groq's free tier: batching, strict-JSON output,
|
|
86
|
+
cost accounting, caching, attribution, and the report unchanged by the model.
|
|
87
|
+
- One document, one low-tier verdict, one provider. Nothing here measures the judge's
|
|
88
|
+
accuracy; the escalation band is narrow by design (OPEN-ITEMS 14.1), so a paper with
|
|
89
|
+
118 citations would send a handful of verdicts, not dozens.
|
|
90
|
+
- Gemini and Ollama were not exercised live; they share the adapter and are covered by
|
|
91
|
+
the `respx` tests only.
|
|
92
|
+
- These runs were made with the batch cap at 7,000 prompt tokens. The whole-phase review
|
|
93
|
+
lowered it to 3,500 so that a full batch plus its 4,096-token answer budget stays under
|
|
94
|
+
Groq's 8K-per-minute tier; with one escalated item the runs above never approached
|
|
95
|
+
either figure, so nothing here measures the cap.
|
|
@@ -390,18 +390,15 @@ Findings from inspecting the installed base environment, and the decisions they
|
|
|
390
390
|
| 12.6 | ~~TUI finding rows print the entry's marker twice~~ **Closed 2026-09-15 (final review fix: `strip_marker` on the label)** | `[7] [7] Marchetti, …` in the pty session: `Reference.raw` keeps its printed marker (Phase 5) and `FindingLine` prefixes the number again. The CLI diagnostic prints the entry once. Cosmetic; `strip_marker` on the label is the fix |
|
|
391
391
|
| 12.7 | `loading models …` is drawn under the `Verifying` row in the TUI, above it on the CLI | the true order: `verify` emits the note inside the stage, after `StageStart`, and the TUI draws the row on `StageStart` while the CLI prints only `StageEnd`. Moving the note before `StageStart` in `verify.py` would put it outside the stage it belongs to; a `StageLine` that shows notes as its own children is the tidier fix. Cosmetic, not a `report`/`ui` one-liner |
|
|
392
392
|
| 12.8 | The SARIF artifact for `check -` is `-` | `to_sarif(report, artifact=target)` takes the target as typed; a stdin run has no file, and `-` under `%SRCROOT%` is what a viewer gets. `report.document.name` (`stdin`) is no better an address; a `--artifact` override is the only honest option |
|
|
393
|
-
| 12.9 | `api_calls` in the footer counts LLM calls only | a cold resolution of five references (33.5 s against Crossref and Semantic Scholar) still prints `0 API calls`; the counter is the judge's (Phase 9) and the wording says nothing about provider lookups. Rename or count — one line either way |
|
|
393
|
+
| 12.9 | `api_calls` in the footer counts LLM calls only — **restated 2026-09-15 (v0.3.0)**: it is now the judge's real counter (answers, not attempts); provider lookups are still uncounted | a cold resolution of five references (33.5 s against Crossref and Semantic Scholar) still prints `0 API calls`; the counter is the judge's (Phase 9) and the wording says nothing about provider lookups. Rename or count — one line either way |
|
|
394
394
|
| 12.10 | A cached resolution reprints its notes as if current | the live draft's ghost still carries `openalex unavailable (HTTP 429, retry after 65567s)` from the day the resolution was stored. Correct (nothing was re-checked) but it reads like today's outage; a `cached:` prefix on stored notes would say so |
|
|
395
395
|
| 12.11 | A provider body that is valid JSON but not an object escapes `resolve` as an error | `_json` guards against a non-JSON body (`ProviderError`), but a bare list or string parses and then fails the `.get(...)` that follows with an `AttributeError`: exit 2 instead of `UNVERIFIED (provider unavailable)`. A type check in `_json` is the fix |
|
|
396
396
|
| 12.12 | `/quit` waits for an in-flight mirrored `/fetch` | a `/check` run is cancelled through its scheduler; a mirrored verb runs in `asyncio.to_thread` with no cancel hook, and the loop's shutdown joins that executor, so the app exits only when the ladder returns. Bounded by the fetch timeouts, but a browser step can take a while |
|
|
397
|
-
| 12.13 | Phase-9 briefs are untracked
|
|
397
|
+
| 12.13 | ~~Phase-9 briefs are untracked~~ **Closed 2026-09-15**: Phase 9 shipped from the tracked `docs/superpowers/plans/2026-09-12-phases-9-10-plan.md`; the drift amendments live in the same git-ignored briefs and are summarised in §14 | — |
|
|
398
398
|
|
|
399
|
-
### Next session
|
|
399
|
+
### Next session (as of v0.2.0 — superseded by §14)
|
|
400
400
|
|
|
401
|
-
1.
|
|
402
|
-
resumes, the judge layer (v0.3) and the social provider (v0.4) are planned in
|
|
403
|
-
`docs/superpowers/plans/2026-09-12-phases-9-10-plan.md` (briefs under
|
|
404
|
-
`.superpowers/sdd/phase9/`), with 12.1–12.10 above as the backlog beside them.
|
|
401
|
+
1. Phases 9–10 were deferred after v0.2.0 and resumed 2026-09-15; Phase 9 is §14.
|
|
405
402
|
|
|
406
403
|
## 13. TUI v2 — v0.2.1, 2026-09-15
|
|
407
404
|
|
|
@@ -427,3 +424,45 @@ Findings from inspecting the installed base environment, and the decisions they
|
|
|
427
424
|
| 13.3 | `PLAIN` keeps the flat v0.2.0 layout the author rated 2/10 (now pure ASCII, with `= note:` rows) | By design (v2 §2, "nothing regresses where borders cannot draw"): the flat rows, the three-line pet and the `kv` footer are v0.2.0's layout with ASCII glyphs, so a legacy-conhost, `NO_COLOR` or `-q` user sees the prototype. A `PLAIN` pass — the fixed-column stage table needs no border and would fit — is the one improvement that does not touch the ASCII rule |
|
|
428
425
|
| 13.4 | The exported SVGs reference a webfont | `App.save_screenshot` (Rich's exporter) emits an `@font-face` for Fira Code with `local()` first and a `cdnjs` URL second; the files are 96 KB and 66 KB, no network is needed to read them, but an offline viewer without Fira Code falls back to its own monospace and the box drawing may not join |
|
|
429
426
|
| 13.5 | 12.7 still shows in the live session | `loading models …` is drawn under the `Verifying` row in both themes (true event order); the tidier fix — a `StageLine` that owns its notes — was out of T1–T5's scope |
|
|
427
|
+
|
|
428
|
+
## 14. Phase 9 — v0.3.0, 2026-09-15
|
|
429
|
+
|
|
430
|
+
- Shipped: `JudgeClient` (9.1; Groq / Gemini / Ollama over `chat/completions`, packaged
|
|
431
|
+
prompts, retry/backoff, cost), `Judge` escalation with the `judgements` cache (9.2;
|
|
432
|
+
schema v4, `check --judge`), `--summarize` and the TUI `/summarize` (9.3). Briefs under
|
|
433
|
+
`.superpowers/sdd/phase9/` with their 2026-09-15 drift amendments; plan
|
|
434
|
+
`plans/2026-09-12-phases-9-10-plan.md`.
|
|
435
|
+
- Live (`docs/eval/2026-09-15-judge-live.md`): on `tests/data/draft-live.md` the judge
|
|
436
|
+
escalated 1 of 10 verdicts (1 call, 613 prompt · 193 completion tokens) and answered
|
|
437
|
+
`NEI` beside a low-tier `NOT SUPPORTED`; the summary cost 1 call (1,608 · 343); the first `--summarize` attempt came back
|
|
438
|
+
empty with `finish_reason=length` — `openai/gpt-oss-120b` spends its completion budget
|
|
439
|
+
on reasoning — which is why requests now carry `reasoning_effort=low` and larger
|
|
440
|
+
budgets. The v0.1 lesson held again: the suite was green, the live run found the defect.
|
|
441
|
+
- Reviewer-found before the tag: an empty 200 was counted as an answer (9.1); an
|
|
442
|
+
unavailable judge reached only the terminal `Note` (9.2); an unanswered summary was
|
|
443
|
+
invisible in the markdown file (9.3). All three were rule-2/6 failures and were fixed.
|
|
444
|
+
|
|
445
|
+
### New open items
|
|
446
|
+
|
|
447
|
+
| # | Item | Note |
|
|
448
|
+
|---|---|---|
|
|
449
|
+
| 14.1 | The `NEI`-with-a-passage escalation clause is unreachable | `pipeline.aggregate` and `decide_indexed` return `Verdict(NEI, …, passage=None)`, so the escalation set is the `low` tier only (a ~0.008-wide band, 1 of 10 verdicts on the live draft). Widening it is a pipeline decision: attach the best passage to an `NEI` and let the judge see it, or narrow the clause in the spec |
|
|
450
|
+
| 14.2 | An agreeing opinion leaves no mark on a rendered finding | by design only a disagreement adds the `= judge:` line; agreement shows in the markdown `judge` column and in JSON. A markdown reader of a low-tier `NOT SUPPORTED` cannot tell "judge confirmed" from "judge never saw it" without the table |
|
|
451
|
+
| 14.3 | Judgements are matched by `(claim_hash, source_id)` | two results with identical claim text against the same source share one opinion and are asked twice in one batch. Rare; carrying the result index would settle it |
|
|
452
|
+
| 14.4 | `Progress` on the Judging stage counts asked items, the summary counts all results | a fully warm run emits no `Progress` and reports `1 of 10 verdicts reviewed` |
|
|
453
|
+
| 14.5 | `report.py` and `cache.py` import `judge` | no cycle, but `httpx` now loads behind the output and storage layers; `JudgeOpinion`/`JudgeCost` could move to `models.py` |
|
|
454
|
+
| 14.6 | `Cache.detail()` / `Cleared` do not count judgements | they are removed by cascade and on `clear`, just not reported |
|
|
455
|
+
| 14.7 | A wrong or revoked key (401/403) arrives as `JudgeUnavailable` | same type as an outage; the detail names the HTTP status, so it is reportable, but a config error and a provider outage are one exception type |
|
|
456
|
+
| 14.8 | ~~`--format json` carries `summary` with no inline model-written label~~ **Closed by the whole-phase review: `summary_model` sits beside it** | attribution is `models["judge"]` plus the `Summarising` stage; an inline label would change the schema |
|
|
457
|
+
| 14.9 | An empty 200 is not retried while a 5xx is | right for `content_filter`, pessimistic for a flaky truncation |
|
|
458
|
+
| 14.10 | Small test gaps | the bare-429 ladder (no `Retry-After`) is untested; `finish_reason` is echoed unbounded in the unavailable detail; the packaged-prompt test cannot fail on an editable install (the wheel listing is the evidence) |
|
|
459
|
+
| 14.13 | The CLI's unavailable-judge line stutters | it prints `judge judge unavailable after 0 calls (…)` because the line returns the one shared sentence verbatim while the markdown header and the sibling `summary` line strip the leading subject. Kept for v0.3.0: byte-identical wording on every surface was the point, and the same string is also printed as a `Note` where the subject is needed |
|
|
460
|
+
| 14.14 | The 3,500-token batch cap rests on a rough estimator | `estimate_tokens` is `len(text) // 4 + 8` and says so; 3,500 + 4,096 leaves ~400 tokens under Groq's 8K tier, so a badly estimated batch can still meet a 429 (waited out, then reported). Calibrating the estimator against the `usage` block the provider already returns would settle it |
|
|
461
|
+
| 14.11 | The unanswered judge is absent from the SARIF log | `_run_properties` carries `models` and `apiCalls` but no `judge status` / `summary status`, so a `--format sarif` log of a run whose provider was down reads like one with nothing to escalate. SARIF is a findings document, and the markdown, JSON and terminal all say it; still, a viewer-only workflow does not see it |
|
|
462
|
+
| 14.12 | Groq's free tier is metered on prompt **plus** requested answer | the whole-phase review caught `TOKEN_CAP = 7000` left over from the 1,024-token answer budget: with the 4,096-token budget of 9.3 a full batch would ask for ~11.1k against an 8K/minute tier. The cap is now 3,500. Nothing measured this live — the live escalation set was one item |
|
|
463
|
+
|
|
464
|
+
### Next session
|
|
465
|
+
|
|
466
|
+
1. **Phase 10** (`providers/` refactor, Bluesky/HN, Reddit/Mastodon/X, AVeriTeC) runs
|
|
467
|
+
from `plans/2026-09-12-phases-9-10-plan.md`; Task 10.4a (AVeriTeC loader and scorer)
|
|
468
|
+
is already implemented and reviewed, unstaged behind the v0.3.0 tag.
|
|
@@ -299,6 +299,19 @@ below are the original outline and stay as the scope statement.
|
|
|
299
299
|
limit, and disabling the judge changes cost to zero without changing the report
|
|
300
300
|
format. `--summarize` adds exactly one call and its absence changes nothing else.
|
|
301
301
|
|
|
302
|
+
**Status (2026-09-15): released as v0.3.0.** Tasks 9.1–9.3 (`.superpowers/sdd/phase9/`,
|
|
303
|
+
briefs in `2026-09-12-phases-9-10-plan.md`): `JudgeClient` with packaged prompts and cost
|
|
304
|
+
accounting; `Judge` escalation (low tier with a passage; ≤ 20 items / ~7k tokens per
|
|
305
|
+
call), the `judgements` cache (schema v4) and `check --judge`; `--summarize` and the TUI
|
|
306
|
+
`/summarize`. Gates: 118 items pack into ≤ 6 calls at the 7k cap (unit test); a judge
|
|
307
|
+
disabled run is byte-identical minus the judge fields with `api_calls == 0`;
|
|
308
|
+
`--summarize` alone is exactly one call and leaves `results`/`findings` identical
|
|
309
|
+
(tests). Live on Groq (`docs/eval/2026-09-15-judge-live.md`): 1 of 10 verdicts
|
|
310
|
+
escalated, 1 call; the first summary attempt came back empty (`finish_reason=length`)
|
|
311
|
+
and led to `reasoning_effort=low` plus larger budgets. The adapter table above (Ollama
|
|
312
|
+
default, OpenRouter) was superseded by the 2026-09-11 decision: Groq default, Gemini and
|
|
313
|
+
Ollama selectable.
|
|
314
|
+
|
|
302
315
|
---
|
|
303
316
|
|
|
304
317
|
## Phase 10 — Social provider (v0.4)
|
|
@@ -469,14 +469,28 @@ the judge enabled. With Ollama there is no wait at all.
|
|
|
469
469
|
|
|
470
470
|
**Provider (decided 2026-09-11).** Default is Groq `openai/gpt-oss-120b`: free
|
|
471
471
|
without a card, no training on submitted data, strict JSON schema output, but an
|
|
472
|
-
8K tokens-per-minute cap that forces batches
|
|
473
|
-
per minute. Gemini 3.8 Flash is selectable but Google trains on free-tier prompts
|
|
472
|
+
8K tokens-per-minute cap that forces small batches (as built: 3,500 prompt tokens plus
|
|
473
|
+
a 4,096-token answer budget) and roughly one call per minute. Gemini 3.8 Flash is selectable but Google trains on free-tier prompts
|
|
474
474
|
outside the EEA/UK/CH, so choosing it prints a data-use warning. Ollama is the
|
|
475
475
|
offline option. All three speak the OpenAI ``chat/completions`` shape, so one
|
|
476
476
|
adapter covers them. The API key is read from an environment variable or a
|
|
477
477
|
``.env`` file, never written to config and never printed. Survey:
|
|
478
478
|
``docs/research/2026-09-11-free-llm-api-tiers.md``.
|
|
479
479
|
|
|
480
|
+
**As built (v0.3.0).** The escalation set is the `low` tier plus `NEI` verdicts that
|
|
481
|
+
carry a passage; a numeric mismatch (rule-decided) and any result without a passage are
|
|
482
|
+
never sent (rule 1 binds the judge too). `openai/gpt-oss-120b` is a reasoning model, so
|
|
483
|
+
requests carry `reasoning_effort=low` (dropped on a 400 from providers that reject it)
|
|
484
|
+
and budgets of 4096 tokens per review batch and 1500 for the summary; the first live run
|
|
485
|
+
with a 400-token summary budget came back empty with `finish_reason=length`. The batch
|
|
486
|
+
cap counts the prompt only, so it is set to 3,500 tokens: a full batch plus its answer
|
|
487
|
+
budget stays under the 8K-per-minute tier. The `NEI`-with-a-passage half of the
|
|
488
|
+
escalation rule is currently unreachable — the pipeline never attaches a passage to an
|
|
489
|
+
`NEI` (OPEN-ITEMS 14.1) — so in practice the judge sees the `low` tier only. A judge
|
|
490
|
+
that does not answer is not a §15 state — the verdicts stand — but it is reported on the
|
|
491
|
+
stage line, the `judge status:` / `summary status:` header lines, the JSON and an
|
|
492
|
+
unsuppressed terminal line; `-q` cannot hide it. The SARIF log carries findings only.
|
|
493
|
+
|
|
480
494
|
## 12. Cross-platform constraints
|
|
481
495
|
|
|
482
496
|
| Component | Risk | Decision |
|
|
@@ -929,8 +943,12 @@ pairing at 0.940 on its hand set (`docs/eval/2026-09-12-pairing-author-year.md`)
|
|
|
929
943
|
beyond the plan, a resolution and retraction cache (warm re-run 1.35 s,
|
|
930
944
|
`docs/eval/2026-09-15-v0.2-live.md`).
|
|
931
945
|
|
|
932
|
-
**v0.3 — judge layer.** Opt-in LLM second opinion
|
|
933
|
-
Gemini
|
|
946
|
+
**v0.3 — judge layer.** Opt-in LLM second opinion (Groq `openai/gpt-oss-120b` default,
|
|
947
|
+
Gemini and Ollama selectable), batching and cost reporting, `--summarize`.
|
|
948
|
+
*Shipped 2026-09-15 as v0.3.0:* `check --judge` escalates the `low`-tier verdicts with a
|
|
949
|
+
passage in batches of ≤ 20 / ~7k tokens and attaches the opinion beside the verdict;
|
|
950
|
+
judgements cached in schema v4; `--summarize` and the TUI `/summarize` as one extra call;
|
|
951
|
+
an unanswered judge reported on every surface but SARIF. Live run: `docs/eval/2026-09-15-judge-live.md`.
|
|
934
952
|
|
|
935
953
|
**v0.4 — social provider.** Bluesky and Hacker News first, Reddit via user-supplied
|
|
936
954
|
OAuth app (missing credentials are reported, never silently skipped), Mastodon
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""The persistent cache: one plain SQLite file under the user cache dir.
|
|
2
2
|
|
|
3
3
|
Holds fetched source text (with a 7-day TTL), sentence chunks with their
|
|
4
|
-
embeddings, verdicts keyed ``(claim_hash, source_id, model_id)
|
|
4
|
+
embeddings, verdicts keyed ``(claim_hash, source_id, model_id)``, the optional
|
|
5
|
+
judge's opinions beside them, and the two
|
|
5
6
|
network lookups a run makes before it fetches anything -- reference resolution and
|
|
6
7
|
the retraction check -- so a re-run of the same document costs nothing. No
|
|
7
8
|
extension, no server: any SQLite GUI can open the file (spec sections 5.1, 12, 16).
|
|
@@ -21,12 +22,13 @@ from typing import Any
|
|
|
21
22
|
|
|
22
23
|
import numpy as np
|
|
23
24
|
|
|
25
|
+
from proofpath.judge import JudgeOpinion
|
|
24
26
|
from proofpath.models import Label, Passage, Tier, Verdict
|
|
25
27
|
from proofpath.paths import cache_dir
|
|
26
28
|
from proofpath.pipeline import CUT_DECIMALS, Thresholds
|
|
27
29
|
from proofpath.resolve import Candidate, FieldMatch, ResolveResult, Retraction, State, strip_marker
|
|
28
30
|
|
|
29
|
-
SCHEMA_VERSION = "
|
|
31
|
+
SCHEMA_VERSION = "4"
|
|
30
32
|
RAW_TEXT_TTL_DAYS = 7
|
|
31
33
|
# A resolution is a statement about a published record, which does not change; the
|
|
32
34
|
# month is there so a reference an index had not yet ingested is looked at again.
|
|
@@ -130,7 +132,28 @@ _LOOKUPS_DDL: tuple[str, ...] = (
|
|
|
130
132
|
)
|
|
131
133
|
_LOOKUPS_SCHEMA = ";\n".join(_LOOKUPS_DDL) + ";\n"
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
# The optional judge's opinions, beside the verdicts and never instead of them (spec
|
|
136
|
+
# section 11.1). Keyed by the judge model as well, so a run with another judge asks
|
|
137
|
+
# again rather than reading back an opinion that model never gave. ``model_id`` is
|
|
138
|
+
# deliberately absent: an opinion is about the claim and the passage, not about which
|
|
139
|
+
# local NLI happened to escalate it, so toggling ``--judge`` cannot invalidate a
|
|
140
|
+
# cached verdict and re-tuning the thresholds cannot invalidate a cached opinion.
|
|
141
|
+
_JUDGEMENTS_DDL: tuple[str, ...] = (
|
|
142
|
+
"""
|
|
143
|
+
CREATE TABLE IF NOT EXISTS judgements (
|
|
144
|
+
claim_hash TEXT NOT NULL,
|
|
145
|
+
source_id TEXT NOT NULL REFERENCES sources(source_id) ON DELETE CASCADE,
|
|
146
|
+
judge_model TEXT NOT NULL, -- "groq openai/gpt-oss-120b"
|
|
147
|
+
label TEXT NOT NULL,
|
|
148
|
+
rationale TEXT NOT NULL,
|
|
149
|
+
created_at TEXT NOT NULL,
|
|
150
|
+
PRIMARY KEY (claim_hash, source_id, judge_model)
|
|
151
|
+
)
|
|
152
|
+
""",
|
|
153
|
+
)
|
|
154
|
+
_JUDGEMENTS_SCHEMA = ";\n".join(_JUDGEMENTS_DDL) + ";\n"
|
|
155
|
+
|
|
156
|
+
_SCHEMA = _BASE_SCHEMA + _CHUNKS_SCHEMA + _LOOKUPS_SCHEMA + _JUDGEMENTS_SCHEMA
|
|
134
157
|
|
|
135
158
|
|
|
136
159
|
def _migrate_to_v2(conn: sqlite3.Connection) -> None:
|
|
@@ -156,7 +179,19 @@ def _migrate_to_v3(conn: sqlite3.Connection) -> None:
|
|
|
156
179
|
conn.execute(statement)
|
|
157
180
|
|
|
158
181
|
|
|
159
|
-
|
|
182
|
+
def _migrate_to_v4(conn: sqlite3.Connection) -> None:
|
|
183
|
+
"""v3 -> v4: the ``judgements`` table.
|
|
184
|
+
|
|
185
|
+
Additive, like v3: nothing stored before v4 holds a second opinion, and the
|
|
186
|
+
verdicts are untouched on purpose -- their ``model_id`` says nothing about the
|
|
187
|
+
judge, so a file that gains this table hands back exactly the verdicts it held
|
|
188
|
+
before and a run with ``--judge`` costs no re-verification (spec section 11.1).
|
|
189
|
+
"""
|
|
190
|
+
for statement in _JUDGEMENTS_DDL:
|
|
191
|
+
conn.execute(statement)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# One step per schema version, oldest first.
|
|
160
195
|
#
|
|
161
196
|
# Invariant, on which the self-healing in ``Cache.__init__`` rests: every step runs
|
|
162
197
|
# inside the one transaction that also records the new version, so a step must issue
|
|
@@ -166,6 +201,7 @@ def _migrate_to_v3(conn: sqlite3.Connection) -> None:
|
|
|
166
201
|
_MIGRATIONS: tuple[tuple[str, Callable[[sqlite3.Connection], None]], ...] = (
|
|
167
202
|
("2", _migrate_to_v2),
|
|
168
203
|
("3", _migrate_to_v3),
|
|
204
|
+
("4", _migrate_to_v4),
|
|
169
205
|
)
|
|
170
206
|
|
|
171
207
|
|
|
@@ -473,6 +509,10 @@ class Cache:
|
|
|
473
509
|
}
|
|
474
510
|
if digests - {text_sha256}:
|
|
475
511
|
self._conn.execute("DELETE FROM verdicts WHERE source_id = ?", (source_id,))
|
|
512
|
+
# The judge quoted the same passages, so its opinions go the same way:
|
|
513
|
+
# a second opinion about text the source no longer serves is evidence
|
|
514
|
+
# nobody can check (product rule 1).
|
|
515
|
+
self._conn.execute("DELETE FROM judgements WHERE source_id = ?", (source_id,))
|
|
476
516
|
self._conn.execute(
|
|
477
517
|
"DELETE FROM chunks WHERE source_id = ? AND embed_model = ?",
|
|
478
518
|
(source_id, embed_model),
|
|
@@ -553,6 +593,53 @@ class Cache:
|
|
|
553
593
|
)
|
|
554
594
|
return Verdict(Label(label), float(score), _tier(tier), passage, reason=reason)
|
|
555
595
|
|
|
596
|
+
# --- judgements ---------------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
def put_judgement(
|
|
599
|
+
self,
|
|
600
|
+
claim_hash_: str,
|
|
601
|
+
source_id: str,
|
|
602
|
+
judge_model: str,
|
|
603
|
+
opinion: JudgeOpinion,
|
|
604
|
+
*,
|
|
605
|
+
now: datetime | None = None,
|
|
606
|
+
) -> None:
|
|
607
|
+
"""Store one judge opinion. Re-asking the same judge replaces what it said."""
|
|
608
|
+
with self._conn:
|
|
609
|
+
self._conn.execute(
|
|
610
|
+
"INSERT INTO judgements(claim_hash, source_id, judge_model, label, rationale, "
|
|
611
|
+
"created_at) VALUES (?, ?, ?, ?, ?, ?) "
|
|
612
|
+
"ON CONFLICT(claim_hash, source_id, judge_model) DO UPDATE SET "
|
|
613
|
+
"label = excluded.label, rationale = excluded.rationale, "
|
|
614
|
+
"created_at = excluded.created_at",
|
|
615
|
+
(
|
|
616
|
+
claim_hash_,
|
|
617
|
+
source_id,
|
|
618
|
+
judge_model,
|
|
619
|
+
opinion.label.value,
|
|
620
|
+
opinion.rationale,
|
|
621
|
+
_iso(now or _now()),
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def get_judgement(
|
|
626
|
+
self, claim_hash_: str, source_id: str, judge_model: str
|
|
627
|
+
) -> JudgeOpinion | None:
|
|
628
|
+
"""What this judge said about this claim and source, or ``None`` if unasked.
|
|
629
|
+
|
|
630
|
+
No TTL: an opinion is about a claim and a passage, both of which are fixed.
|
|
631
|
+
The passage changing is what retires it, and ``put_chunks`` does that.
|
|
632
|
+
"""
|
|
633
|
+
row = self._conn.execute(
|
|
634
|
+
"SELECT label, rationale FROM judgements "
|
|
635
|
+
"WHERE claim_hash = ? AND source_id = ? AND judge_model = ?",
|
|
636
|
+
(claim_hash_, source_id, judge_model),
|
|
637
|
+
).fetchone()
|
|
638
|
+
if row is None:
|
|
639
|
+
return None
|
|
640
|
+
label, rationale = row
|
|
641
|
+
return JudgeOpinion(label=Label(label), rationale=str(rationale), model=judge_model)
|
|
642
|
+
|
|
556
643
|
# --- resolutions and retractions ---------------------------------------------
|
|
557
644
|
|
|
558
645
|
def get_resolution(self, raw: str, *, now: datetime | None = None) -> ResolveResult | None:
|