From 0d49b41d56b40ca63242e9f9eb431ea301bc832d Mon Sep 17 00:00:00 2001 From: Moon-python Date: Fri, 9 Oct 2026 15:42:05 +0900 Subject: [PATCH 1/2] docs: draw the scorecard from held-out set v2 on 1.3.1 Holdout-v1 was used to tune #30 and #33, so the chart's headline no longer measured unseen references. The chart now comes from the 99-item held-out v2 run on 1.3.1, and the retracted row says how many of its retractions CrossRef cannot mark, so the 8/8 rate and the 12-item bar no longer read as a contradiction. Co-Authored-By: Claude Opus 5.5 --- .github/assets/scorecard-dark.svg | 63 ++++++++++++++++-------------- .github/assets/scorecard-light.svg | 63 ++++++++++++++++-------------- scripts/render_scorecard.py | 11 +++++- 3 files changed, 75 insertions(+), 62 deletions(-) diff --git a/.github/assets/scorecard-dark.svg b/.github/assets/scorecard-dark.svg index 187550c..52d8ac2 100644 --- a/.github/assets/scorecard-dark.svg +++ b/.github/assets/scorecard-dark.svg @@ -1,9 +1,9 @@ -How check-bib judged 86 held-out references -Real papers: 32 pass, 8 warn, 0 reject; Fabricated references: 1 pass, 19 warn, 6 reject; Retracted papers: 0 pass, 0 warn, 10 reject; Real, not in CrossRef: 0 pass, 10 warn, 0 reject +How check-bib judged 99 held-out references +Real papers: 40 pass, 5 warn, 0 reject; Fabricated references: 0 pass, 20 warn, 9 reject; Retracted papers: 0 pass, 3 warn, 9 reject; Real, not in CrossRef: 1 pass, 12 warn, 0 reject -How check-bib judged 86 held-out references -ref-verify 1.2.2 · commit 01c7a37 · 2026-10-08 · live CrossRef · set frozen before the run +How check-bib judged 99 held-out references +ref-verify 1.3.1 · commit 8170e66 · 2026-10-09 · live CrossRef · set frozen before the run PASS @@ -11,35 +11,38 @@ REJECT Real papers -n = 40 -Real papers: 32 of 40 PASS (80%) -32 -Real papers: 8 of 40 WARN (20%) -8 -80% passed cleanly -PASS only · 95% CI 65–90% +n = 45 +Real papers: 40 of 45 PASS (89%) +40 +Real papers: 5 of 45 WARN (11%) +5 +89% passed cleanly +PASS only · 95% CI 77–95% Fabricated references -n = 26 -Fabricated references: 1 of 26 PASS (4%) -Fabricated references: 19 of 26 WARN (73%) -19 -Fabricated references: 6 of 26 REJECT (23%) -6 -96% flagged -WARN or REJECT · 95% CI 81–99% +n = 29 +Fabricated references: 20 of 29 WARN (69%) +20 +Fabricated references: 9 of 29 REJECT (31%) +9 +100% flagged +WARN or REJECT · 95% CI 88–100% Retracted papers -n = 10 -Retracted papers: 10 of 10 REJECT (100%) -10 -100% caught as retracted -PAPER_RETRACTED · 95% CI 72–100% +n = 12 (4 unmarked) +Retracted papers: 3 of 12 WARN (25%) +3 +Retracted papers: 9 of 12 REJECT (75%) +9 +8/8 marked ones caught +PAPER_RETRACTED · 95% CI 68–100% Real, not in CrossRef -n = 10 -Real, not in CrossRef: 10 of 10 WARN (100%) -10 -0 of 10 wrongly rejected -should be WARN · 95% CI 0–28% -Whole set: 79 s on a cold cache, 0.1 s cached · 4 workers +n = 13 +Real, not in CrossRef: 1 of 13 PASS (8%) +1 +Real, not in CrossRef: 12 of 13 WARN (92%) +12 +0 of 13 wrongly rejected +should be WARN · 95% CI 0–23% +Whole set: 97 s on a cold cache, 0.1 s cached · 4 workers Development set (142 refs, used while fixing the tool, in-sample): real 66/66 PASS · fabricated 43/43 flagged retracted 16/16 caught · unindexed 1/17 rejected diff --git a/.github/assets/scorecard-light.svg b/.github/assets/scorecard-light.svg index 97cc688..675826c 100644 --- a/.github/assets/scorecard-light.svg +++ b/.github/assets/scorecard-light.svg @@ -1,9 +1,9 @@ -How check-bib judged 86 held-out references -Real papers: 32 pass, 8 warn, 0 reject; Fabricated references: 1 pass, 19 warn, 6 reject; Retracted papers: 0 pass, 0 warn, 10 reject; Real, not in CrossRef: 0 pass, 10 warn, 0 reject +How check-bib judged 99 held-out references +Real papers: 40 pass, 5 warn, 0 reject; Fabricated references: 0 pass, 20 warn, 9 reject; Retracted papers: 0 pass, 3 warn, 9 reject; Real, not in CrossRef: 1 pass, 12 warn, 0 reject -How check-bib judged 86 held-out references -ref-verify 1.2.2 · commit 01c7a37 · 2026-10-08 · live CrossRef · set frozen before the run +How check-bib judged 99 held-out references +ref-verify 1.3.1 · commit 8170e66 · 2026-10-09 · live CrossRef · set frozen before the run PASS @@ -11,35 +11,38 @@ REJECT Real papers -n = 40 -Real papers: 32 of 40 PASS (80%) -32 -Real papers: 8 of 40 WARN (20%) -8 -80% passed cleanly -PASS only · 95% CI 65–90% +n = 45 +Real papers: 40 of 45 PASS (89%) +40 +Real papers: 5 of 45 WARN (11%) +5 +89% passed cleanly +PASS only · 95% CI 77–95% Fabricated references -n = 26 -Fabricated references: 1 of 26 PASS (4%) -Fabricated references: 19 of 26 WARN (73%) -19 -Fabricated references: 6 of 26 REJECT (23%) -6 -96% flagged -WARN or REJECT · 95% CI 81–99% +n = 29 +Fabricated references: 20 of 29 WARN (69%) +20 +Fabricated references: 9 of 29 REJECT (31%) +9 +100% flagged +WARN or REJECT · 95% CI 88–100% Retracted papers -n = 10 -Retracted papers: 10 of 10 REJECT (100%) -10 -100% caught as retracted -PAPER_RETRACTED · 95% CI 72–100% +n = 12 (4 unmarked) +Retracted papers: 3 of 12 WARN (25%) +3 +Retracted papers: 9 of 12 REJECT (75%) +9 +8/8 marked ones caught +PAPER_RETRACTED · 95% CI 68–100% Real, not in CrossRef -n = 10 -Real, not in CrossRef: 10 of 10 WARN (100%) -10 -0 of 10 wrongly rejected -should be WARN · 95% CI 0–28% -Whole set: 79 s on a cold cache, 0.1 s cached · 4 workers +n = 13 +Real, not in CrossRef: 1 of 13 PASS (8%) +1 +Real, not in CrossRef: 12 of 13 WARN (92%) +12 +0 of 13 wrongly rejected +should be WARN · 95% CI 0–23% +Whole set: 97 s on a cold cache, 0.1 s cached · 4 workers Development set (142 refs, used while fixing the tool, in-sample): real 66/66 PASS · fabricated 43/43 flagged retracted 16/16 caught · unindexed 1/17 rejected diff --git a/scripts/render_scorecard.py b/scripts/render_scorecard.py index 1aa360a..3d5b8c4 100644 --- a/scripts/render_scorecard.py +++ b/scripts/render_scorecard.py @@ -136,7 +136,11 @@ def render_svg(results: dict[str, Any], theme_name: str, dev: dict[str, Any] | N counts = composition.get(category, {"n": 0, "pass": 0, "warn": 0, "reject": 0}) y = TOP + index * ROW_PITCH parts.append(_text(LABEL_X, y + 12, label, fill=theme["ink"], size=15, weight=600)) - parts.append(_text(LABEL_X, y + 31, f"n = {counts['n']}", fill=theme["muted"], size=13)) + # Retractions CrossRef cannot mark (DOIs at other agencies) are in the bar but not in + # the rate, so the label and headline say which denominator the rate uses. + unmarked = aggregates.get("retracted_not_marked_by_crossref", 0) if category == "RETRACTED" else 0 + size_label = f"n = {counts['n']} ({unmarked} unmarked)" if unmarked else f"n = {counts['n']}" + parts.append(_text(LABEL_X, y + 31, size_label, fill=theme["muted"], size=13)) nonzero = [(seg, counts[seg]) for seg, _ in SEGMENTS if counts[seg]] gap = 2.0 @@ -166,7 +170,10 @@ def render_svg(results: dict[str, Any], theme_name: str, dev: dict[str, Any] | N ) x += width + gap - parts.append(_text(HEADLINE_X, y + 12, _headline(aggregates, key, suffix), fill=theme["ink"], size=15, weight=600)) + headline = _headline(aggregates, key, suffix) + if unmarked: + headline = f"{aggregates[key]['k']}/{aggregates[key]['n']} marked ones caught" + parts.append(_text(HEADLINE_X, y + 12, headline, fill=theme["ink"], size=15, weight=600)) parts.append( _text(HEADLINE_X, y + 31, f"{rule} · {_interval(aggregates, key)}", fill=theme["muted"], size=13) ) From d4dad03e6a6eb46f40cfda4167c6f11e93fc11d6 Mon Sep 17 00:00:00 2001 From: Moon-python Date: Fri, 9 Oct 2026 15:42:05 +0900 Subject: [PATCH 2/2] docs: rewrite the README for first-time readers and AI assistants The first screen did not say what ref-verify is, who it is for, how to try it, or how to read a verdict; the reference-list check sat below the skill install, and reference material buried the story. The README now leads with the problem, a 30-second run on a labelled sample list with real 1.3.1 output, what each verdict asks of the reader (with how often UNVERIFIED was legitimate in the test sets), three ways to use it, the held-out scorecard, limits, privacy and network use, and the facts an agent needs. Commands, modes, error codes, the cache, scope, and examples move unchanged to docs/REFERENCE(.ko).md, and the doc tests now read them there. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 + README.ko.md | 587 ++++++++++++-------------------------- README.md | 595 ++++++++++++--------------------------- benchmarks/README.md | 8 +- docs/REFERENCE.ko.md | 488 ++++++++++++++++++++++++++++++++ docs/REFERENCE.md | 492 ++++++++++++++++++++++++++++++++ examples/references.txt | 24 ++ tests/test_skill_docs.py | 125 +++++--- 8 files changed, 1451 insertions(+), 872 deletions(-) create mode 100644 docs/REFERENCE.ko.md create mode 100644 docs/REFERENCE.md create mode 100644 examples/references.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 629364b..6c79ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Versioning follows [Semantic Versioning](https://semver.org/). - An end-to-end skill evaluation (`benchmarks/e2e/`): eight manuscripts (Markdown, LaTeX + BibTeX, Korean) with 40 planted errors and 65 correct references, a harness that runs each through an isolated `claude -p` session with and without the skill (no user hooks, plugins, or MCP servers), a stdlib scorer, and the 2026-10-09 results: 80/80 planted errors caught with the skill and 77/80 without, no false alarms in either, at about twice the time per run. It needs the network and a logged-in Claude Code, and is not run in CI. - A browser page (`web/`) that runs `check-bib` on a pasted list or a `.bib`/`.ris`/`.txt` file with no server: the engine runs in Pyodide (pinned 314.0.7 from jsDelivr) inside a Web Worker and calls CrossRef and doi.org directly. It shows progress, the same verdict tables and legend as the HTML report, a Korean/English toggle, and downloads the same HTML report the CLI writes (in English). In Korean, the page shows each reason in Korean from a template table (`web/reasons.js`) with DOIs, years, and titles kept as written, and shows any reason it cannot translate in English marked "(원문)"; `tests/test_web_reasons.py` fails when the engine gains a `check-bib` reason without a Korean template. Nothing is cached or stored in the browser. `scripts/build_web.py` builds the site; `.github/workflows/pages.yml` deploys it once Pages is enabled and the `PAGES_ENABLED` repository variable is `true`. +### Changed + +- README (EN/KO) rewritten for a first-time reader and for AI assistants: what it is and who it is for, a 30-second try on `examples/references.txt` (new; seven labelled references, real 1.3.1 output), how to read each verdict, including how often `UNVERIFIED` was a legitimate but unindexed reference in the test sets, three ways to use it, the scorecard, what is and is not checked, privacy and network use, and a section for AI agents. Commands, modes, error codes, the cache, scope, and examples moved to `docs/REFERENCE.md` and `docs/REFERENCE.ko.md`. The scorecard now reports held-out set v2 measured on 1.3.1; the retracted row of the chart names how many retractions CrossRef cannot mark. + ### Fixed - `check-bib` no longer attaches a reference without a DOI to a different CrossRef record that shares its title. Supplementary-information components are never taken as the cited work; replies, comments, and retraction or correction notices ("Retraction—", "Authors' reply") join review reports as records about another work; when several records match, the one whose first author agrees wins over search order (a 1998 *Lancet* letter titled like Wakefield's paper no longer hides the retraction); a title that only mostly appears in the reference must come with an agreeing first author; a reference that reads as a book (publisher or edition, no journal volume and page) matches only book records, not journal reviews of the book; and a reference that says preprint prefers the preprint record. diff --git a/README.ko.md b/README.ko.md index f7e0d1c..cce09c0 100644 --- a/README.ko.md +++ b/README.ko.md @@ -8,486 +8,251 @@ [한국어](https://github.com/Moonweave-Research/ref-verify/blob/main/README.ko.md) | [English](https://github.com/Moonweave-Research/ref-verify/blob/main/README.md) -**논문에 없는 내용을 인용하지 않도록 막습니다.** +**제출 전에 참고문헌 목록을 점검해, 존재하지 않거나 다른 논문을 가리키거나 철회된 참고문헌을 찾아냅니다.** -`ref-verify`는 연구 인용 검증용 에이전트 스킬입니다. Claude Code, -Cursor, Codex 같은 스킬 지원 에이전트가 초안에 참고문헌을 넣기 전에 -반복 가능한 검증 절차를 따르도록 합니다. +AI 도우미가 만들어 준 참고문헌 목록이나 이전 원고에서 옮겨 온 목록에는 실제로 없는 논문, +다른 논문의 DOI, 철회된 논문이 섞여 들어가기 쉽습니다. ref-verify는 참고문헌을 하나씩 +CrossRef와 대조해 어떤 항목을 다시 봐야 하는지 알려 줍니다. -논문 찾기, DOI 확인, 특정 주장을 논문이 실제로 뒷받침하는지 확인, -제출 전 참고문헌 점검에 사용할 수 있습니다. 서버를 시작하거나 MCP를 설정할 필요가 없습니다. +- **누가 쓰나요:** 논문, 학위논문, 연구계획서를 마무리하는 연구자와 대학원생, 그리고 이들을 돕는 AI 도우미. +- **무엇을 얻나요:** 참고문헌마다 판정(`PASS`, `WARN`, `REJECT`, `UNVERIFIED`)과 그 이유. 터미널, JSON, HTML 보고서 중에서 고를 수 있습니다. +- **어떻게 쓰나요:** 연구 인용 검증용 에이전트 스킬(Claude Code, Cursor, Codex)이나 명령줄 도구로 씁니다. 서버를 시작하거나 MCP를 설정할 필요가 없습니다. 계정이나 API 키도 필요 없습니다. --- -## 채점표 - -<picture> - <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-dark.svg"> - <img src="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-light.svg" alt="보류 세트 참고문헌 86개에 대한 check-bib 판정 막대 그래프: 진짜 80% 깨끗이 통과, 가짜 96% 잡음, 철회 100% 잡음, CrossRef 미등재 정상 문헌 10개 중 0개 REJECT." width="830"> -</picture> - -보류(held-out) 세트: 도구를 돌리기 전에 작성하고 커밋해 둔 참고문헌 86개이며, 개발 세트와 겹치는 논문이 -없습니다. +## 30초 만에 써 보기 -| 측정 항목 (보류 세트) | 결과 | n | 95% 신뢰구간 | -|---|---|---|---| -| 진짜 논문을 깨끗이 통과시킨 비율 | **80%** | 40 | 65–90% | -| 진짜 논문을 "확인 필요"(WARN)로 보낸 비율 | 20% | 40 | 10–35% | -| 진짜 논문을 틀렸다고(REJECT) 판정한 비율 | 0% | 40 | 0–9% | -| 가짜 참고문헌을 잡은 비율 (WARN 또는 REJECT) | **96%** | 26 | 81–99% | -| 철회 논문을 `PAPER_RETRACTED`로 잡은 비율 | **100%** | 10 | 72–100% | -| CrossRef에 없는 정상 문헌을 REJECT한 수 | 0/10 | 10 | 0–28% | - -- 가짜 유형별: 가짜 DOI 5/5 · DOI 없음 5/5 · DOI 바꿔치기 4/4 · 저자·연도 틀림 4/5 · 공개 보고 사례 7/7. -- 통과하지 못한 진짜 논문 8개 중 7개는 논문 제목을 생략하는 물리·화학 인용 형식이라 CrossRef와 - 비교할 제목이 없었습니다. -- 86개 전체 시간: 캐시 없이 79초(참고문헌당 중앙값 1.1초), 캐시 사용 시 0.1초. - -개발 세트(참고문헌 142개, [#27](https://github.com/Moonweave-Research/ref-verify/pull/27)에서 도구를 고칠 때 -사용했으므로 표본 내 점수): 진짜 66/66 통과(94–100%), 가짜 43/43 잡음, 철회 16/16 잡음, -CrossRef 미등재 정상 문헌 17개 중 1개 REJECT. - -2026-10-08에 ref-verify 1.2.2(커밋 `01c7a37`)로 실제 CrossRef에 조회해 측정했습니다. 두 세트 모두 BibTeX, RIS, -일반 텍스트 목록으로 `check-bib`에 넣었습니다. 측정하지 않은 것: 논문이 주장을 뒷받침하는지(작은 수치 -fixture 외), 한국어 몇 건을 제외한 영어 외 문헌, 본문. 놓친 항목은 결과 파일([보류](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-08-01c7a37-holdout-v1.json), -[개발](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-08-01c7a37-v1.json))에 항목별로 있고, 데이터셋, 방법, 재실행 방법은 -[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md)에 있습니다. - ---- - -## 스킬 설치 +Python 3.10 이상이 필요합니다. ```bash -# Node.js에 포함된 npx 필요 -npx skills add Moonweave-Research/ref-verify -g \ - --skill ref-verify \ - --agent claude-code cursor codex \ - -y +pipx install ref-verify # 설치 없이 바로: uvx ref-verify check-bib ... +curl -O https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/examples/references.txt +ref-verify check-bib references.txt ``` -**Claude Code, Cursor, Codex** 및 `npx skills` 생태계를 지원하는 -에이전트에서 사용할 수 있습니다. - -설치 후에는 일반 에이전트 스킬처럼 자연어로 요청하면 됩니다. 이 -워크플로우에는 MCP 서버가 필요하지 않습니다. 서버를 시작하거나 MCP를 -설정하지 않습니다. - -스킬 안에 CLI 엔진이 함께 들어 있고 에이전트가 스킬 폴더에서 직접 실행하므로 -따로 설치할 것이 없습니다. `python3`로 Python 3.10 이상을 실행할 수 있어야 합니다. - -에이전트가 CLI를 호출할 때 따라야 할 명시적 규칙은 -[AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md)를 참고하세요. - ---- - -## 사용 방법 - -자연스럽게 요청하면 됩니다. +[`examples/references.txt`](https://github.com/Moonweave-Research/ref-verify/blob/main/examples/references.txt)에는 +참고문헌 7개가 들어 있습니다. 각 항목 위 주석에 실제로 어떤 문헌인지 적어 두었습니다. 진짜 논문 3개, +연도를 잘못 쓴 진짜 논문 1개, 지어낸 참고문헌 1개, 철회된 논문 1개, CrossRef에 없는 국내 +박사학위논문 1개입니다. 아래는 ref-verify 1.3.1을 실행한 결과 그대로입니다. ```text -제출 전에 이 인용들을 검증해줘: [DOI list] -이 논문이 "actuation strain above 100%"라는 주장을 실제로 뒷받침해? -claim X를 뒷받침하는 논문 3개를 찾고, 각 인용을 검증해줘 -이 title/year와 DOI 10.1126/science.287.5454.836이 맞는지 확인해줘 -제출 전에 참고문헌 전체를 점검해줘 +7 references: 3 PASS, 1 WARN, 2 REJECT, 1 UNVERIFIED + +VERDICT REFERENCE DOI +PASS 1. Pelrine, R., Kornbluh, R., Pei, Q., &… 10.1126/science.287.5454.836 +PASS 2. Watts, D. J., & Strogatz, S. H. (1998)… 10.1038/30918 (resolved) +PASS 3. 김가은 (2024). IPA 분석을 통한 프로스… 10.5392/jkca.2024.24.09.361 +WARN 4. Hochreiter, S., & Schmidhuber, J. (199… 10.1162/neco.1997.9.8.1735 + DOI matches, but the year differs (reference: 1999; CrossRef: 1997). +REJECT 5. Pelrine, R., Kornbluh, R., & Pei, Q. (… 10.1002/adma.200390974 + CrossRef has no record for this DOI (HTTP 404), and doi.org does not list it either. +REJECT 6. Wakefield, A. J., Murch, S. H., Anthon… 10.1016/s0140-6736(97)11096-0 + CrossRef records this paper as retracted (notice DOI 10.1016/s0140-6736(10)60175-4); do + not use it as a source. +UNVERIFIED 7. 강동휘 (2025). 폴리(비닐피리딘) 기반… - + No matching CrossRef record was found; verify this reference manually. + +PASS: matches CrossRef. WARN: check the difference named under it. REJECT: dead DOI, a different +paper, or retracted. UNVERIFIED: could not be confirmed automatically; that alone does not mean the +reference is wrong. ``` -일반 주제 설명, 문장 다듬기, APA/IEEE 형식 정리, 인용 스타일 질문에는 -조용히 있습니다. +모든 항목이 통과하지는 않았으므로 종료 코드는 `2`입니다. CrossRef 데이터는 계속 바뀌므로 나중에 +실행하면 결과가 조금 다를 수 있습니다. 내 목록을 점검하려면 Zotero, EndNote, Mendeley에서 +BibTeX이나 RIS로 내보내거나, 목록을 복사해 `.txt` 파일로 저장한 뒤 +`ref-verify check-bib <파일>`을 실행합니다. --- -## 참고문헌 목록 전체 점검하기 - -논문이나 학위논문의 참고문헌 중에 존재하지 않거나(ChatGPT가 지어낸 것 등), DOI가 -다른 논문을 가리키거나, 철회된 논문이 섞여 있는지 한 번에 확인합니다. - -**에이전트에게 맡길 때:** 스킬을 설치한 뒤 "references.bib 참고문헌 전체를 -ref-verify로 점검해줘"라고 요청하면 됩니다. - -**터미널에서 직접 할 때:** - -1. 목록을 파일로 준비합니다. - - Zotero: 컬렉션 우클릭 → 컬렉션 내보내기 → BibTeX → `references.bib` - (EndNote·Mendeley는 BibTeX이나 RIS로 내보내기) - - Word·한글 원고: 참고문헌 목록을 복사해 메모장 등에 붙여 넣고 - `references.txt`로 저장합니다. `[1]`, `1.` 번호나 줄바꿈이 있어도 됩니다. - `.docx`, `.hwp`, `.pdf`는 직접 읽지 못합니다. -2. 설치합니다(Python 3.10 이상). - - ```bash - pipx install ref-verify - ``` - - `uv`가 있다면 설치 없이 `uvx ref-verify check-bib references.bib`로 - 바로 실행해도 됩니다. +## 결과 읽는 법 -3. 실행합니다. - - ```bash - ref-verify check-bib references.bib - ``` - - 처음 실행할 때는 참고문헌 하나에 1초 정도 걸립니다(150개면 2분 남짓, - `Checking references: 37/150`처럼 진행 상황이 보입니다). 같은 목록을 다시 돌리면 - 캐시 덕분에 몇 초면 끝납니다. `REF_VERIFY_MAILTO=내이메일@학교.ac.kr`를 앞에 - 붙이면 CrossRef의 polite pool을 써서 약 3배 빨라집니다. - - 지도교수나 공동 저자에게 보낼 파일이 필요하면 `--report 점검결과.html`을 붙입니다. - 브라우저로 열면 확인이 필요한 항목이 맨 위에 모여 있습니다. - -**결과 읽는 법** - -| 결과 | 뜻 | 할 일 | +| 판정 | 뜻 | 할 일 | |---|---|---| -| `PASS` | DOI(또는 검색으로 찾은 기록)의 제목·제1저자·연도가 CrossRef와 일치 | 없음 | -| `WARN` | 찾았지만 무언가 다름. 바로 아래 줄에 무엇이 다른지 나옵니다(연도, 저자, DOI가 가리키는 다른 논문 제목 등) | 그 항목만 원문과 대조 | -| `REJECT` | DOI가 어디에도 없음, 전혀 다른 논문을 가리킴, 또는 철회된 논문 | 인용을 고치거나 빼기 | -| `UNVERIFIED` | 자동으로 확인하지 못함. 학위논문, 국내 학회 초록, 일부 책, CrossRef가 아닌 곳(arXiv, KISTI 등)에 등록된 DOI가 흔히 여기에 옵니다. 틀렸다는 뜻이 아닙니다 | 직접 확인 | - -DOI가 없는 가짜 참고문헌은 `REJECT`가 아니라 `UNVERIFIED`로만 나올 수 있습니다. -`UNVERIFIED` 항목은 Google Scholar나 RISS에서 실제로 있는지 한 번씩 찾아보세요. +| `PASS` | DOI의 CrossRef 기록(DOI가 없으면 검색으로 찾은 기록)과 제목, 제1저자, 연도가 일치합니다. | 없음. | +| `WARN` | 찾기는 했지만 무언가 다릅니다. 바로 아래 줄에 연도, 제1저자, 또는 그 DOI가 실제로 가리키는 논문 제목이 나옵니다. | 그 항목만 원문과 대조합니다. | +| `REJECT` | DOI가 어디에도 없거나, 다른 논문의 DOI이거나, 철회된 논문입니다. | 인용을 고치거나 뺍니다. | +| `UNVERIFIED` | 자동으로 확인하지 못했습니다. 틀렸다는 판정이 아닙니다. | 직접 찾아봅니다. | + +`UNVERIFIED`는 주의해서 읽어야 합니다. DOI 없이 지어낸 참고문헌은 반박할 근거가 없으니 +`REJECT`가 아니라 `UNVERIFIED`로만 나옵니다. CrossRef에 없는 진짜 학위논문, KCI 논문, 책, +프리프린트도 똑같이 `UNVERIFIED`로 나옵니다. 직접 만든 시험 세트 세 개를 ref-verify 1.3.1로 +2026-10-09에 돌렸을 때 `UNVERIFIED`는 다음과 같았습니다. + +| 시험 세트 | `UNVERIFIED` | 지어낸 것 | CrossRef에 없는 정상 문헌 | CrossRef에 없는 철회 논문 | 진짜 논문 | +|---|---|---|---|---|---| +| [홀드아웃 세트 v2](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v2.json) | 29 | 13 | 12 | 3 | 1 | +| [홀드아웃 세트 v1](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v1.json) | 22 | 14 | 8 | 0 | 0 | +| [개발 세트](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-v1.json) | 31 | 15 | 16 | 0 | 0 | +| 합계 | 82 | 42 | 36 | 3 | 1 | + +대략 절반은 가짜, 절반은 정상 문헌이었다는 뜻이니 `UNVERIFIED` 항목은 Google Scholar, RISS, +KCI에서 한 번씩 직접 찾아보세요. 이 비율은 시험 세트의 구성에서 나온 것이라 실제 참고문헌 목록의 +비율과는 다릅니다. --- -## 선택적 CLI 엔진 - -스킬이 에이전트 워크플로우입니다. Python CLI는 설치된 스킬이 터미널에서 -호출할 수 있는 skill-level execution engine입니다. - -Python 패키지는 CLI 전용입니다. `SKILL.md`를 설치하지 않습니다. 에이전트 -스킬은 위의 `npx skills add` 명령으로 GitHub에서 설치합니다. - -이것은 스킬/플러그인 수준 워크플로우이며 MCP 서버가 아닙니다. CLI는 -현재 직접 자동화해도 안전한 부분만 담당합니다. - -- CrossRef 메타데이터 확인: `ref-verify verify-doi` -- DOI에 묶인 abstract 기반 주장 확인: `ref-verify check-claim` -- 여러 DOI 기반 주장 일괄 확인: `ref-verify check-file` - - 문장 그대로 드러나는 text claim - - efficiency, response rate, actuation strain 같은 subject가 일치하는 percentage claim - - cycles, patients, voltage, temperature, concentration 같은 단순 unit/count claim - - CrossRef를 먼저 쓰고, CrossRef에 abstract가 없으면 DOI가 일치하는 OpenAlex, Semantic Scholar, PubMed fallback 사용 -- 참고문헌 목록 확인(BibTeX, RIS, 일반 텍스트, Markdown): `ref-verify check-bib` -- 에이전트가 읽기 쉬운 JSON 출력 -- `WARN`, `REJECT`, `UNVERIFIABLE` 결과에 대한 non-zero exit code - -`p-value`, AUC/AUROC, F1 score, hazard ratio, odds ratio, confidence interval -같은 통계 지표는 아직 수동 스킬 프로토콜을 따릅니다. DOI landing page 확인은 스킬 프로토콜을 따릅니다. Unpaywall, arXiv, 두 개 이상의 독립 출처로 존재 확인도 스킬 프로토콜이 담당합니다. -CrossRef가 철회 공지를 기록한 DOI는 CLI가 바로 `REJECT`합니다. CrossRef에 없는 철회 배너 확인은 여전히 `SKILL.md`의 스킬 프로토콜이 담당합니다. - -CLI에는 third-party Python runtime dependency가 없지만, offline verifier는 -아닙니다. 실제 검증에는 CrossRef, OpenAlex, Semantic Scholar, PubMed 같은 공개 학술 -API로 outbound HTTPS 요청을 보낼 수 있어야 합니다. - -### 캐시 - -CLI는 API 응답을 디스크에 7일 동안 보관하므로, 같은 검사를 다시 돌려도 -CrossRef와 abstract 소스에 다시 요청하지 않습니다. HTTP 404가 나온 DOI는 -1일만 보관해 새로 등록된 DOI를 곧 다시 확인합니다. 요청 제한(429)과 서버 -오류(5xx)는 backoff를 두고 최대 3번 재시도하며(`Retry-After`는 10초까지 -따름), 캐시에 남기지 않습니다. - -- 위치: `$REF_VERIFY_CACHE_DIR`, 없으면 `$XDG_CACHE_HOME/ref-verify`, 그것도 없으면 `~/.cache/ref-verify`. -- 보관 기간: `REF_VERIFY_CACHE_TTL_DAYS` (기본 `7`). -- 끄기: 모든 명령에서 `--no-cache`, 또는 `REF_VERIFY_NO_CACHE=1`. 비우려면 디렉터리를 지웁니다. +## 세 가지 사용 방법 -CLI를 직접 쓰려면 PyPI에서 설치합니다. +### 에이전트 스킬로 ```bash -uvx ref-verify --help # 설치 없이 실행 (uv) -pipx install ref-verify # 또는 `ref-verify` 명령 설치 -``` - -로컬 체크아웃에서 설치할 수도 있습니다. - -```bash -git clone https://github.com/Moonweave-Research/ref-verify.git -cd ref-verify -python3 -m pip install -e . -``` - -CLI 사용 가능 여부를 확인합니다. - -```bash -ref-verify --help -``` - -설치하지 않은 소스 체크아웃에서는 모듈 엔트리포인트를 사용합니다. - -```bash -PYTHONPATH=src python3 -m ref_verify.cli --help +# Node.js에 포함된 npx 필요 +npx skills add Moonweave-Research/ref-verify -g \ + --skill ref-verify \ + --agent claude-code cursor codex \ + -y ``` -DOI 메타데이터를 확인합니다. +설치한 뒤 에이전트에게 평소 말투로 요청하면 됩니다. -```bash -ref-verify verify-doi 10.1126/science.287.5454.836 \ - --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ - --first-author Pelrine \ - --year 2000 \ - --json +```text +references.bib 참고문헌 전체를 ref-verify로 점검해줘 ``` -DOI에 묶인 abstract에 대해 특정 주장을 확인합니다. - -```bash -ref-verify check-claim 10.1126/science.287.5454.836 \ - --claim "actuation strain above 100%" \ - --json -``` +스킬 안에 명령줄 엔진이 함께 들어 있고 `python3`(3.10 이상)로 실행하므로 따로 설치할 것이 +없습니다. Python 패키지는 CLI 전용이라 `SKILL.md`를 설치하지 않습니다. 에이전트 스킬은 위 명령으로 GitHub에서 설치합니다. +다른 요청 예시는 [에이전트 스킬](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md#에이전트-스킬)에 있습니다. -기본값으로 `check-claim`은 CrossRef를 먼저 사용합니다. CrossRef에 abstract가 없으면 DOI가 일치하는 OpenAlex, Semantic Scholar, PubMed fallback을 시도합니다. 특정 소스만 디버깅하려면 `--source crossref`, `--source openalex`, `--source semantic-scholar`, `--source pubmed`를 사용합니다. 명시적으로 non-CrossRef 소스를 고르면 CrossRef를 거치지 않습니다. +### 명령줄에서 -소스 체크아웃에서 바로 실행하는 예시는 다음과 같습니다. +| 명령 | 확인하는 것 | +|---|---| +| `ref-verify check-bib references.bib` | 참고문헌 목록: BibTeX, RIS, 일반 텍스트·Markdown(`.txt`, `.md`) | +| `ref-verify verify-doi <doi> --title "..." --first-author <성> --year <연도>` | DOI 하나를 내가 적은 제목, 제1저자, 연도와 대조 | +| `ref-verify check-claim <doi> --claim "..."` | 논문 초록에 특정 숫자나 문구가 명시되어 있는지 | +| `ref-verify check-file claims.jsonl` | DOI와 주장 쌍 여러 개를 한 번에(JSONL 또는 CSV) | -```bash -PYTHONPATH=src python3 -m ref_verify.cli verify-doi 10.1126/science.287.5454.836 \ - --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ - --first-author Pelrine \ - --year 2000 \ - --json - -PYTHONPATH=src python3 -m ref_verify.cli check-claim 10.1126/science.287.5454.836 \ - --claim "actuation strain above 100%" \ - --json -``` +프로그램에서 읽을 출력이 필요하면 `--json`을 붙입니다. 모든 명령, 옵션, 오류 코드는 +[명령줄 엔진](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md#명령줄-엔진)에 있습니다. -개발 중 테스트는 다음으로 실행합니다. +### 저장한 보고서로 ```bash -PYTHONPATH=src python3 -m unittest discover -s tests -v +ref-verify check-bib references.bib --report report.html ``` -릴리즈 안전성 검사는 Python 패키지를 빌드하고, 메타데이터를 확인하고, -빌드된 wheel을 새 virtualenv에 설치해 본 뒤 배포 경로를 확인합니다. 공개 -학술 API를 실제로 호출하는 live check는 수동 GitHub Actions workflow로 -분리되어 있어, 일반 CI가 upstream API 일시 장애 때문에 실패하지 않습니다. +지도교수나 공동 저자에게 보낼 수 있는 HTML 파일 하나가 만들어집니다. 맨 위에 판정별 개수가 +나오고 확인이 필요한 참고문헌이 먼저 정리됩니다. DOI에는 `https://doi.org/` 링크만 붙이고 다른 +외부 자원은 불러오지 않습니다. Markdown으로 받으려면 `--report report.md`를 씁니다. 자세한 내용은 +[저장한 보고서](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md#저장한-보고서)에 있습니다. --- -## 잡아내는 문제 - -| 문제 | ref-verify 없이 생기는 일 | -|---|---| -| **잘못된 DOI** | 그럴듯한 DOI가 완전히 다른 논문으로 연결됨 | -| **잘못된 저자** | "Smith et al. (2020)"라고 썼지만 CrossRef에는 단독 저자로 등록됨 | -| **잘못된 연도** | 실제 출판 연도는 2008년인데 초안에는 2011년으로 들어감 | -| **지어낸 내용** | abstract에 없는 결과를 논문이 보여준 것처럼 설명함 | -| **Near-miss citation** | 필요한 숫자는 나오지만 맥락이 다름 | -| **철회 논문** | DOI는 유효하지만 논문이 철회됨 | - ---- - -## 범위 — 선택적 CLI와 수동 감사의 경계 - -`ref-verify`는 만능 판정기가 아니라 보수적인 안전장치입니다. 일부러 -flag를 더 자주 냅니다. `ACCEPT`는 높은 확신의 통과이고, 그 외 결과는 -"자동으로 확인할 수 없으니 사람이 확인해야 한다"는 뜻이지 "인용이 -틀렸다"는 뜻은 아닙니다. - -**선택적 CLI가 확인하는 것** - -- DOI 메타데이터: 제목, 첫 번째 저자 성, 연도를 CrossRef와 비교합니다. -- DOI-bound **abstract**가 특정 숫자 또는 literal claim을 명시적으로 - 뒷받침하는지 확인합니다. abstract에 접근할 수 없으면 추측하지 않고 - `UNVERIFIABLE`을 반환합니다. - -**선택적 CLI가 확인하지 않는 것** (의도적으로 범위 밖이며 버그가 아닙니다) - -- **본문, figure, table, supplementary 값** — abstract-only입니다. 숫자가 - 본문에만 있으면 `UNVERIFIABLE`로 남습니다. -- **관계형/정성적 claim** — proportionality, mechanism, "더 강하다/넓다" - 같은 표현은 자동 판정하지 않습니다. value+unit 또는 literal claim 중심입니다. -- **publisher가 abstract를 공개 API에 제공하지 않는 논문** — CrossRef나 - OpenAlex에서 abstract가 없으면 `UNVERIFIABLE`입니다. 이는 claim 판단이 - 아니라 접근 가능성 판단입니다. -- **복잡한 통계 지표** — p-value, AUC/AUROC, F1, hazard/odds ratio, - confidence interval은 CLI가 아니라 수동 skill protocol 범위입니다. -- **논문 품질, novelty, 학계 consensus**, 또는 논문 전체가 넓은 주장을 - 뒷받침하는지 여부. +## 채점표 -에이전트 스킬의 수동 Full Audit 프로토콜은 mechanism, implementation, -procedure claim에 대해 선택적 CLI보다 더 깊게 확인합니다. 이 경우 -full-text passage를 직접 가져와야 하며, 본문에 접근하지 못하면 abstract의 -주제 일치만으로 `ACCEPT`하지 않고 `WARN (ABSTRACT-ONLY)`로 남깁니다. +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-dark.svg"> + <img src="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-light.svg" alt="홀드아웃 세트 v2 참고문헌 99개에 대한 check-bib 판정 막대 그래프: 진짜 논문 89% 통과, 지어낸 참고문헌 100% 잡음, CrossRef가 기록한 철회 8건 중 8건 잡음, CrossRef에 없는 정상 문헌 13개 중 0개 REJECT." width="830"> +</picture> -**CLI 판정 읽는 법** +홀드아웃(held-out) 세트 v2는 도구를 돌리기 전에 작성하고 커밋해 둔 참고문헌 99개입니다. 앞선 +세트들과 겹치는 논문은 없습니다. 2026-10-09에 ref-verify 1.3.1로 실제 CrossRef에 조회해 한 번 +측정했습니다. -| Verdict | 의미 | -|---|---| -| `ACCEPT` | 가져온 abstract가 claim을 명시적으로 뒷받침함. 높은 확신의 통과. | -| `WARN` / `PARTIAL` | abstract는 읽었지만 정확한 claim을 명시적으로 뒷받침하지 않음. 원문 확인 필요. | -| `UNVERIFIABLE` | 확인할 abstract에 접근하지 못함. claim 자체가 틀렸다는 뜻은 아님. | -| `REJECT` | DOI가 죽었거나, 다른 논문으로 연결되거나, 모순/철회 근거가 있음. | +| 측정 항목 (홀드아웃 세트 v2) | 결과 | n | 95% 신뢰구간 | +|---|---|---|---| +| 진짜 논문을 깨끗이 통과시킴(`PASS`) | **89%** (40) | 45 | 77–95% | +| 진짜 논문을 확인 필요로 보냄(`WARN`) | 11% (5) | 45 | 5–23% | +| 진짜 논문을 틀렸다고 판정함(`REJECT`) | 0% (0) | 45 | 0–8% | +| 지어낸 참고문헌을 잡음(`WARN` 또는 `REJECT`) | **100%** (29) | 29 | 88–100% | +| CrossRef가 기록한 철회를 `PAPER_RETRACTED`로 잡음 | **100%** (8) | 8 | 68–100% | +| CrossRef에 없는 정상 문헌을 틀렸다고 판정함 | 0% (0) | 13 | 0–23% | + +- 지어낸 참고문헌 유형별: 가짜 DOI 6/6, DOI 없음 6/6, 다른 논문의 DOI 5/5, 저자·연도 틀림 5/5, 공개 보고된 사례 7/7. +- `WARN`으로 간 진짜 논문 5건: 학술지 약어를 쓴 제목 없는 인용 1건, 단체 저자 인용 2건, 무관한 기록에 연결된 1건, 인쇄 연도로 인용한 책 1건. +- 이 밖에 Retraction Watch에는 철회로 올라 있지만 CrossRef가 표시할 수 없는 철회 4건이 있습니다(DOI가 KISTI, ISTIC, DataCite에 등록됐거나, 학회 초록의 DOI가 철회 공지로 연결되는 경우). 4건 모두 `PAPER_RETRACTED`를 받지 못했습니다. 3건은 `UNVERIFIED`, 1건은 다른 논문으로 보아 `REJECT`였습니다. +- CrossRef에 없는 책 1건(Feynman Lectures)은 같은 제목의 1964년 학술지 서평에 연결되어 통과했습니다. +- 99개 전체 시간: 캐시 없이 97초(참고문헌당 중앙값 1.1초), 캐시 사용 시 0.1초. +- 표본 내 세트(도구를 고칠 때 이미 써서 점수가 실제보다 좋게 나옴): 홀드아웃 세트 v1(86개) 진짜 40/40 `PASS`, 가짜 26/26 잡음, 철회 10/10, CrossRef 미등재 문헌 0/10 REJECT. 개발 세트(142개) 진짜 66/66, 가짜 43/43, 철회 16/16, CrossRef 미등재 문헌 1/17 REJECT. + +방법: 각 세트를 BibTeX, RIS, 일반 텍스트 목록으로 `check-bib`에 넣고 빈 캐시에서 실행했습니다. +항목별 판정은 결과 파일([홀드아웃 세트 v2](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v2.json))에 +모두 남겼습니다. 측정하지 않은 것: 논문이 주장을 뒷받침하는지(작은 수치 fixture 외), 한국어를 뺀 +영어 외 문헌, 본문. 데이터셋, 방법, 재실행 방법은 +[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md)에 있습니다. --- -## 모드 +## 확인하는 것과 확인하지 않는 것 -**Quick Screen**은 이미 DOI가 있을 때 사용합니다. CrossRef로 DOI, 제목, -첫 번째 저자의 성, 연도를 비교합니다. - -```bash -ref-verify verify-doi <doi> --title "<title>" --first-author <last-name> --year <year> --json -``` - -`verify-doi`는 `PASS`일 때만 exit code `0`을 반환합니다. `WARN`과 -`REJECT`는 non-zero exit code를 반환하므로, 약하거나 맞지 않는 -메타데이터가 자동화 단계를 조용히 통과할 수 없습니다. +| 질문 | 확인 여부 | 방법 | +|---|---|---| +| DOI가 실제로 있는가? | 예 | CrossRef에서 찾고 없으면 doi.org에서 다른 등록기관(arXiv·Zenodo의 DataCite, KISTI 등)의 DOI인지 확인합니다. 그런 DOI는 없는 DOI가 아니라 `UNVERIFIED`입니다. | +| 제목, 제1저자, 연도가 DOI와 맞는가? | 예 | CrossRef 기록과 비교합니다. | +| DOI 없는 참고문헌이 실재하는가? | 일부 | CrossRef에 색인돼 있으면 검색으로 찾습니다. 색인돼 있지 않으면 `REJECT`가 아니라 `UNVERIFIED`입니다. | +| 철회된 논문인가? | CrossRef에 기록이 있을 때 | CrossRef의 철회 공지로 판단합니다. CrossRef가 담고 있는 Retraction Watch 데이터도 포함됩니다. CrossRef에 없는 논문의 철회는 놓칩니다. | +| 초록에 특정 숫자나 문구가 있는가? | 예, 초록만 | `check-claim`, `check-file`. 초록은 CrossRef, OpenAlex, Semantic Scholar, PubMed에서 가져옵니다. | +| 본문, 그림, 표, 보충자료 | 아니요 | | +| p-value, AUC, odds ratio 같은 통계 | 아니요 | 스킬의 수동 절차에 맡깁니다. | +| 논문의 질, 새로움, 학계 합의 | 아니요 | | -**Full Audit**은 논문을 처음 찾거나 제출 전 최종 점검을 할 때 사용합니다. -스킬은 필요한 경우 CrossRef, OpenAlex, Semantic Scholar, Unpaywall, arXiv, PubMed를 -통해 source text를 가져옵니다. topline claim은 abstract에서 확인하고, -mechanism, implementation, procedure claim은 full text passage까지 확인한 -뒤에만 지지 판정을 내립니다. +범위에 대한 자세한 설명은 [범위](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md#범위--선택적-cli와-수동-감사의-경계)에 있습니다. -단일 DOI 기반 주장에 대해서는 CLI가 abstract 확인을 수행할 수 있습니다. +--- -```bash -ref-verify check-claim <doi> --claim "<specific claim>" --json -``` +## 개인정보와 네트워크 -`check-claim`은 `ACCEPT`일 때만 exit code `0`을 반환합니다. `WARN`, -`PARTIAL`, `UNVERIFIABLE`은 non-zero exit code를 반환합니다. JSON 출력에는 -`abstract_source`, `source_attempts`, `error_code`가 포함되어 abstract 부재, -소스 실패, DOI 불일치, 애매한 근거를 구분할 수 있습니다. +ref-verify는 외부 Python 패키지(third-party Python runtime dependency)가 없지만 오프라인으로 돌지는 +않습니다. 공개 학술 API로 outbound HTTPS 요청을 보낼 수 있어야 합니다. 연결하는 곳은 CrossRef, OpenAlex, Semantic Scholar, PubMed, doi.org뿐입니다. -초안, 리서치 메모, AI 에이전트 출력처럼 DOI/claim 쌍이 여러 개 있을 때는 -`check-file`을 사용합니다. +| 서비스 | 언제 | 보내는 것 | +|---|---|---| +| CrossRef (`api.crossref.org`) | 모든 명령 | DOI, 또는 DOI가 없는 참고문헌이면 참고문헌 문장 자체를 검색어로 | +| doi.org | CrossRef에 DOI 기록이 없을 때 | DOI | +| OpenAlex, Semantic Scholar, PubMed(NCBI) | `check-claim`, `check-file`에서 CrossRef에 초록이 없을 때 | DOI(arXiv DOI는 Semantic Scholar에 arXiv ID로) | -JSONL: +- 파일이나 주장 문장을 어딘가에 올리지 않습니다. 주장과 초록의 비교는 내 컴퓨터에서 하고, 위에 없는 서비스로는 아무것도 보내지 않습니다. +- 모든 요청에는 `ref-verify/<버전> (+https://github.com/Moonweave-Research/ref-verify)` user agent가 붙습니다. 이메일 주소는 `REF_VERIFY_MAILTO`를 설정했을 때만 CrossRef에 보냅니다. OpenAlex 요청에는 `REF_VERIFY_OPENALEX_MAILTO`가 붙고, 기본값은 임시 주소(`verify@ref-verify.local`)입니다. Semantic Scholar 키는 `SEMANTIC_SCHOLAR_API_KEY`를 설정했을 때만 보냅니다. +- 응답은 디스크에 7일(찾지 못한 DOI는 1일) 캐시합니다. 위치는 `$REF_VERIFY_CACHE_DIR`, 없으면 `$XDG_CACHE_HOME/ref-verify`, 그것도 없으면 `~/.cache/ref-verify`입니다. 캐시 파일에는 요청 URL(DOI나 참고문헌 문장이 들어 있음)과 응답이 남습니다. +- `--no-cache`나 `REF_VERIFY_NO_CACHE=1`로 캐시를 끄고, 그 폴더를 지우면 비워집니다. 자세한 내용은 [캐시](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md#캐시)에 있습니다. -```bash -ref-verify check-file claims.jsonl -ref-verify check-file claims.jsonl --json -``` +--- -CSV: +## AI 에이전트를 위한 안내 -```bash -ref-verify check-file claims.csv -``` +AI 도우미가 ref-verify를 제대로 호출하는 데 필요한 사항입니다. 전체 계약은 +[AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md), 스킬 지침은 +[SKILL.md](https://github.com/Moonweave-Research/ref-verify/blob/main/SKILL.md)에 있습니다. -각 행에는 `doi`와 `claim`이 필요합니다. `id`, `source`, `note`는 선택 -필드입니다. 기본으로 4행씩 동시에 확인하며(`--workers N`), 출력 순서는 입력 -순서를 그대로 따릅니다. CrossRef와 Semantic Scholar 공개 API는 동시 요청을 거절하므로 -이 두 곳에는 한 번에 하나씩 보냅니다. 터미널에서 실행하면 stderr에 -`Checking claims: N/M` 진행 표시가 나옵니다(`--json`일 때는 나오지 않음). Ctrl-C로 -멈출 수 있고, 끝난 조회는 캐시에 남으므로 같은 명령을 다시 실행하면 빠르게 이어집니다. 배치 모드는 기존의 보수적인 `check-claim` 엔진을 그대로 -사용합니다. `ACCEPT`는 abstract가 숫자 claim을 명시적으로 지지한다는 -뜻입니다. `WARN`, `PARTIAL`, `REJECT`, `UNVERIFIABLE`은 검증된 claim으로 -취급하면 안 됩니다. - -현재 `check-claim` error code는 다음과 같습니다. - -- `CLAIM_SUPPORTED`: abstract 안에 명시적 근거가 있음 -- `CLAIM_NOT_EXPLICIT`: abstract는 있지만 claim을 명시적으로 뒷받침하지 않음 -- `CLAIM_AMBIGUOUS`: 숫자나 맥락은 있으나 subject/숫자 연결이 애매함 -- `NO_ABSTRACT`: 시도한 DOI-bound source에서 abstract text를 얻지 못함 -- `DOI_NOT_FOUND`: CrossRef와 doi.org 어디에도 없는 DOI이거나 선택한 source에서 DOI-bound record를 찾지 못함. JSON에는 `verdict: REJECT`가 함께 담김 -- `DOI_NOT_IN_CROSSREF`: CrossRef에는 없지만 doi.org에 다른 등록기관(arXiv·Zenodo의 DataCite, KISTI, JaLC 등)으로 등록된 DOI. `verify-doi`는 서지 정보를 비교하지 않고 `verdict: WARN`, `status: UNVERIFIED`를 반환하고, `check-claim`은 OpenAlex, Semantic Scholar(arXiv DOI는 arXiv 식별자로), PubMed에서 초록을 찾아 있으면 주장을 판정하고 없으면 이 코드와 함께 `status: UNVERIFIABLE`, `verdict: WARN`을 반환합니다. 없는 DOI라는 뜻이 아닙니다 -- `PAPER_RETRACTED`: CrossRef에 철회 공지가 있어 초록을 읽기 전에 거절함 -- `DOI_MISMATCH`: primary 또는 명시적으로 선택한 DOI-bound record가 요청 DOI와 다름 -- `SOURCE_API_ERROR`, `SOURCE_TIMEOUT`, `SOURCE_RATE_LIMITED`, `SOURCE_UNSUPPORTED`: source lookup 실패, timeout, rate limit, 사용 불가 - -DOI/claim 쌍이 아니라 참고문헌 목록이 있을 때는 `check-bib`을 사용합니다. +**명령** ```bash -ref-verify check-bib references.bib -ref-verify check-bib references.ris --json -ref-verify check-bib references.md --format txt +ref-verify check-bib references.bib --json # 참고문헌 목록 +ref-verify check-file claims.jsonl --json # DOI와 주장 쌍 +ref-verify verify-doi <doi> --title "..." --first-author <성> --year <연도> --json +ref-verify check-claim <doi> --claim "..." --json +# 설치된 스킬 안에서는 함께 들어 있는 엔진을 실행: +PYTHONPATH="$SKILL_DIR/src" python3 -m ref_verify.cli check-bib references.bib --json ``` -BibTeX, RIS, 일반 텍스트·Markdown 목록(문단마다, 줄마다, 또는 -`[1]`/`1.`/`1)` 번호마다 참고문헌 하나)을 읽습니다. DOI가 있는 항목은 -`verify-doi`처럼 CrossRef 기록과 대조하고, 일반 텍스트 항목은 본문에 -CrossRef 제목과 제1저자가 드러날 때만 통과합니다. DOI가 없는 항목은 -CrossRef 서지 검색으로 찾아 제목이 일치하고 연도 차이가 1년 이내일 때만 -받아들입니다. 인쇄본 연도와 온라인 선공개 연도 모두, 부제나 판 표기를 뺀 제목, -BibTeX 제목의 TeX 수식(`$\beta$`는 β), CrossRef에 -등록된 원어 제목(예: 『폴리머』 논문의 한글 제목), 한글 저자명과 CrossRef의 로마자 -표기(윤 → Yoon/Yun)를 같은 것으로 봅니다. CrossRef에 없는 DOI는 doi.org에 등록기관을 -물어보므로 arXiv, Zenodo, KISTI DOI를 없는 DOI로 판정하지 않습니다. 논문 자체가 아니라 -그 논문에 관한 기록(동료 심사 보고서, Faculty Opinions 추천, addendum·correction)은 검색 -결과에서 건너뜁니다. 논문 제목을 쓰지 않는 물리·화학 인용 형식(`J. Bardeen, L. N. Cooper, and -J. R. Schrieffer, Phys. Rev. 108, 1175 (1957)`)은 학술지(전체 이름 또는 약어), 권, 첫 쪽 -또는 논문 번호, 연도, 제1저자로 비교하며, 모두 맞으면 통과하고 다르면 어느 항목이 다른지 -이유에 적습니다. DOI가 없고 일반 검색에서 찾지 못했으며 제목 없는 인용으로 보이면 제1저자, 나머지 인용 정보, 인용 연도로 -CrossRef를 한 번 더 검색해 `A. G. Riess et al., Astron. J. 116, 1009 (1998).` 같은 짧은 인용도 -찾으며, 이때도 모든 항목이 일치해야 받아들입니다. 제1저자는 저자 목록의 첫 이름에서만 읽으므로 공저자를 맨 앞에 쓴 -참고문헌은 통과하지 않습니다. 터미널 출력은 -`19 references: 11 PASS, 2 WARN, 5 REJECT, 1 UNVERIFIED` 같은 개수 줄로 시작하고, -참고문헌마다 한 줄(인용 키, 붙여 넣은 목록이면 참고문헌 앞부분)을 보여 주며, `PASS`가 -아닌 줄 아래에는 이유를, 끝에는 판정 설명을 붙입니다. `--json`이면 `summary`(`total`, `pass`, `warn`, -`reject`, `unverified`, `failed`; `warn`에는 `UNVERIFIED` 항목도 포함)와 `results`를 담은 객체입니다. 모든 항목이 -`PASS`일 때만 exit `0`입니다. - -`check-bib` error code: - -- `REFERENCE_RESOLVED`: DOI가 없던 항목을 CrossRef 검색으로 찾았고 `resolved_doi`에 기록함. 연도가 1년 다르거나 제1저자가 다르면 `WARN` -- `REFERENCE_UNMATCHED`: DOI가 없고 일치하는 CrossRef 기록도 찾지 못함(`status: UNVERIFIED`, `verdict: WARN`). 도구가 자동으로 확인하지 못했다는 뜻이지 참고문헌이 틀렸다는 뜻은 아니므로 직접 확인합니다 -- `DOI_NOT_IN_CROSSREF`: DOI가 CrossRef가 아닌 다른 등록기관(arXiv·Zenodo의 DataCite, KISTI, JaLC 등)에 등록되어 있어 서지 정보를 비교하지 못함(`status: UNVERIFIED`, `verdict: WARN`). DOI를 직접 열어 확인합니다 -- `DOI_NOT_FOUND`: CrossRef와 doi.org 어디에도 없는 DOI(`REJECT`) -- `PAPER_RETRACTED`, `ROW_CHECK_ERROR`: `check-claim`, `check-file`과 같음. 그 밖의 DOI 기반 결과는 `error_code: null`이며 `verdict`, `mismatches`, 그리고 무엇이 다른지 적힌 `reason`(예: `the year differs (reference: 2009; CrossRef: 2010)`)을 봅니다. 일반 텍스트 참고문헌의 DOI가 본문에 없는 다른 논문을 가리키면 `status: MISMATCH`, `verdict: WARN`이고 그 논문 제목이 `reason`에 나옵니다 - -결과를 공동 저자나 지도교수에게 넘기려면 `check-bib` 또는 `check-file`에 -`--report`를 붙입니다. 파일 확장자가 형식을 정합니다. +**종료 코드** -```bash -ref-verify check-bib references.bib --report report.html -ref-verify check-file claims.jsonl --report report.md -``` +| 코드 | 뜻 | +|---|---| +| `0` | 모든 항목이 `PASS`(`check-bib`, `verify-doi`)이거나 `ACCEPT`(`check-claim`, `check-file`). | +| `2` | 실행은 끝났지만 통과하지 못한 항목이 있음. 그래도 JSON은 읽어야 합니다. | +| `1` | 입력이나 실행 오류. | -HTML 파일은 그 자체로 완결됩니다(인라인 CSS, 스크립트 없음, `https://doi.org/` -링크 외 외부 자원 없음). 맨 위에 합이 전체 개수와 맞는 판정별 개수, 각 판정의 뜻과 -할 일을 적은 설명이 있고, 그 아래 통과하지 못한 항목만 모은 "Needs a look" 표와 -통과한 항목의 "Passed" 표가 이어집니다. 각 행은 색으로 구분되며(`PASS`/`ACCEPT` 초록, -`WARN` 호박색, `REJECT` 빨강, `UNVERIFIED` 회색) 이유와 근거를 보여 줍니다. -`UNVERIFIED`는 도구가 자동으로 확인하지 못했다는 표시이지 참고문헌이 틀렸다는 -판정이 아닙니다. Markdown 파일도 같은 내용입니다. `--report` 경로의 폴더가 없으면 -조회를 시작하기 전에 알려 주므로 긴 실행이 헛되지 않습니다. `--json` 출력은 바뀌지 않습니다. - -> 핵심 규칙: 논문 내용에 대한 모든 설명은 claim에 필요한 깊이의 -> live-fetched source에서 나와야 합니다. topline claim에는 abstract, -> mechanism, implementation, procedure claim에는 full text가 필요합니다. -> 필요한 source에 접근하지 못하면 그 사실을 밝히고 기억으로 빈칸을 -> 채우지 않습니다. +- **입력:** `check-bib`은 `.bib`, `.ris`, `.txt`, `.md`(다른 확장자면 `--format bib|ris|txt`). `check-file`은 `{"doi": ..., "claim": ..., "id": ..., "note": ...}` 형식의 JSONL 행(`doi`, `claim`은 필수). +- **중요한 JSON 키:** `summary`(`check-bib`은 `total`, `pass`, `warn`, `reject`, `unverified`, `failed`)와 결과마다 `verdict`, `status`, `error_code`, `resolved_doi`, `mismatches`, `reason`. +- **확인된 것으로 보고하면 안 되는 결과:** `check-bib`의 `WARN`, `REJECT`, `UNVERIFIED`. 주장 확인의 `WARN`, `PARTIAL`, `REJECT`, `UNVERIFIABLE`. `failed > 0`인 실행. 참고문헌은 `PASS`, 주장은 `ACCEPT`만 확인된 것으로 봅니다. +- **표현:** `REFERENCE_UNMATCHED`는 CrossRef 검색에서 일치하는 기록을 찾지 못했다는 뜻입니다. 확인되지 않았다고 알리고 사용자에게 확인을 부탁하되, 지어냈다고 말하지 않습니다. `DOI_NOT_IN_CROSSREF`는 다른 등록기관의 DOI라는 뜻이며 없는 DOI가 아닙니다. `REFERENCE_RESOLVED`이면 `resolved_doi`를 그 참고문헌의 DOI로 씁니다. +- **하지 말 것:** 초록이 없을 때 기억으로 채우지 않습니다. 초록만 확인했으면서 본문, 표, 그림까지 뒷받침된다고 하지 않습니다. --- -## 예시 - -**이미 가진 인용 확인** - -```text -사용자: "제출 전에 이 인용 3개를 검증해줘" - -Shahinpoor & Kim (2001) 10.1088/0964-1726/10/4/327 - PASS -Bar-Cohen (2004) 10.1117/3.547465 - WARN (listed as author; CrossRef: editor) -Carpi et al. (2011) 10.1016/B978-0-08-047488-5.00001-0 - REJECT -``` - -**특정 주장 확인** - -```text -사용자: "Pelrine 2000 논문이 DEA가 100% 넘는 strain에 도달한다고 실제로 말해?" - -내용: 뒷받침됨 -"Actuated strains up to 117% were demonstrated with silicone elastomers, -and up to 215% with acrylic elastomers." -[출처: CrossRef raw JSON, 기억에서 가져온 설명 아님] -``` - -**Near-miss 인용** +## 더 자세한 문서 -후보 논문 abstract에 "500% strain"이 들어 있어도, 그 숫자가 actuation -result가 아니라 pre-strain condition일 수 있습니다. `ref-verify`는 이런 -경우 citation을 받아들이지 않고 `WARN (PARTIAL)`로 표시합니다. +- [docs/REFERENCE.ko.md](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md): 모든 명령, 모드, 오류 코드, 캐시, 범위, 예시. +- [AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md): CLI를 호출하는 에이전트를 위한 계약(영어). +- [SKILL.md](https://github.com/Moonweave-Research/ref-verify/blob/main/SKILL.md): 에이전트 스킬 본문(영어). +- [benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md): 시험 세트와 재실행 방법(영어). +- [CHANGELOG.md](https://github.com/Moonweave-Research/ref-verify/blob/main/CHANGELOG.md): 릴리스별 변경 사항(영어). --- -## 관련 프로젝트 +## 기여, 라이선스, 관련 프로젝트 -- [anneal-skill](https://github.com/Moonweave-Systems/anneal-skill) - AI agent용 measure-first decision discipline -- [decide-skill](https://github.com/Moonweave-Systems/decide-skill) - 비전문가 domain을 위한 decision automation +- 이슈와 풀 리퀘스트를 환영합니다. 변경을 보내기 전에 `PYTHONPATH=src python3 -m unittest discover -s tests`로 테스트를 돌려 주세요. +- 라이선스: [MIT](https://github.com/Moonweave-Research/ref-verify/blob/main/LICENSE). +- 관련 프로젝트: [decision-kernel](https://github.com/Moonweave-Systems/decision-kernel). 코딩 에이전트를 위한 근거 기반 결정, 작업 이탈 점검, 완료 점검 스킬 모음입니다. diff --git a/README.md b/README.md index b300f41..4c44130 100644 --- a/README.md +++ b/README.md @@ -8,493 +8,252 @@ [English](https://github.com/Moonweave-Research/ref-verify/blob/main/README.md) | [한국어](https://github.com/Moonweave-Research/ref-verify/blob/main/README.ko.md) -**Stop citing papers that do not say what you think they say.** +**Check a reference list before you submit it, and find the references that do not exist, point to a different paper, or were retracted.** -`ref-verify` is an agent skill for citation verification. It helps Claude Code, -Cursor, Codex, and other skill-aware agents check references before they land in -your draft. +Reference lists drafted by AI assistants, or copied from one manuscript to the next, pick up +references that do not exist, DOIs that belong to another paper, and papers that were +retracted. ref-verify compares each reference with CrossRef and tells you which ones to look at. -Use it when you want an agent to find papers, verify a DOI, check whether a paper -supports a specific claim, or audit references before submission. No server setup is required. +- **Who it is for:** researchers and students finishing a paper, thesis, or proposal, and the AI assistants that help them. +- **What you get:** one verdict per reference (`PASS`, `WARN`, `REJECT`, `UNVERIFIED`) with the reason, in the terminal, as JSON, or as an HTML report. +- **How it runs:** as an agent skill for citation verification (Claude Code, Cursor, Codex) or as a command-line tool. No server setup is required, and no account or API key. --- -## Scorecard - -<picture> - <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-dark.svg"> - <img src="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-light.svg" alt="Bar chart of check-bib verdicts on 86 held-out references: real 80% passed cleanly, fabricated 96% flagged, retracted 100% caught, 0 of 10 unindexed references rejected." width="830"> -</picture> - -Held-out set: 86 references written and committed before the tool was run on them, with no paper -shared with the development set. +## Try it in 30 seconds -| What was measured (held-out set) | Result | n | 95% CI | -|---|---|---|---| -| Real papers passed cleanly | **80%** | 40 | 65–90% | -| Real papers sent for a manual check (WARN) | 20% | 40 | 10–35% | -| Real papers wrongly rejected | 0% | 40 | 0–9% | -| Fabricated references flagged (WARN or REJECT) | **96%** | 26 | 81–99% | -| Retracted papers caught as `PAPER_RETRACTED` | **100%** | 10 | 72–100% | -| Legitimate references missing from CrossRef that were rejected | 0 of 10 | 10 | 0–28% | - -- Fabricated, by type: invented DOI 5/5 · no DOI 5/5 · DOI swap 4/4 · wrong author/year 4/5 · publicly reported cases 7/7. -- 7 of the 8 real papers that did not pass are cited in the physics/chemistry style that omits the - article title, which leaves nothing to compare against CrossRef. -- Time for all 86 references: 79 s on a cold cache (1.1 s median per reference), 0.1 s cached. - -Development set (142 references, used while fixing the tool in -[#27](https://github.com/Moonweave-Research/ref-verify/pull/27), so these are in-sample scores): real -66/66 passed (94–100%), fabricated 43/43 flagged, retracted 16/16 caught, 1 of -17 unindexed references rejected. - -Measured 2026-10-08 with ref-verify 1.2.2 (commit `01c7a37`) against live CrossRef, checking each set with -`check-bib` as BibTeX, RIS, and plain-text lists. Not measured: whether a paper supports a claim -(beyond a small numeric fixture), non-English literature beyond a few Korean items, and full text. -Every miss is listed per item in the results files ([held-out](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-08-01c7a37-holdout-v1.json), -[development](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-08-01c7a37-v1.json)); dataset, method, and how to rerun: -[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md). - ---- - -## Install the skill +You need Python 3.10 or newer. ```bash -# requires npx (comes with Node.js) -npx skills add Moonweave-Research/ref-verify -g \ - --skill ref-verify \ - --agent claude-code cursor codex \ - -y +pipx install ref-verify # or run without installing: uvx ref-verify check-bib ... +curl -O https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/examples/references.txt +ref-verify check-bib references.txt ``` -Works with **Claude Code, Cursor, Codex**, and any agent that supports the -`npx skills` ecosystem. - -After installation, use it like a normal agent skill. You do not start a server and you do not configure MCP for this workflow. No MCP server is required for this workflow. - -The skill includes its own copy of the CLI engine, and the agent runs it from the skill folder, so nothing else needs to be installed. Python 3.10 or newer must be available as `python3`. - -For explicit agent tool-calling rules, see [AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md). - ---- - -## Use it - -Ask naturally: +[`examples/references.txt`](https://github.com/Moonweave-Research/ref-verify/blob/main/examples/references.txt) +holds seven references, and a comment above each one says what it really is: three real papers, +one real paper cited with the wrong year, one made-up reference, one retracted paper, and a +Korean doctoral thesis that CrossRef does not index. This is the output of ref-verify 1.3.1: ```text -verify these citations before I submit: [DOI list] -does this paper actually support the claim "actuation strain above 100%"? -find 3 papers supporting the claim that X, and verify each citation -check doi 10.1126/science.287.5454.836 against this title and year -audit all my references before submission +7 references: 3 PASS, 1 WARN, 2 REJECT, 1 UNVERIFIED + +VERDICT REFERENCE DOI +PASS 1. Pelrine, R., Kornbluh, R., Pei, Q., &… 10.1126/science.287.5454.836 +PASS 2. Watts, D. J., & Strogatz, S. H. (1998)… 10.1038/30918 (resolved) +PASS 3. 김가은 (2024). IPA 분석을 통한 프로스… 10.5392/jkca.2024.24.09.361 +WARN 4. Hochreiter, S., & Schmidhuber, J. (199… 10.1162/neco.1997.9.8.1735 + DOI matches, but the year differs (reference: 1999; CrossRef: 1997). +REJECT 5. Pelrine, R., Kornbluh, R., & Pei, Q. (… 10.1002/adma.200390974 + CrossRef has no record for this DOI (HTTP 404), and doi.org does not list it either. +REJECT 6. Wakefield, A. J., Murch, S. H., Anthon… 10.1016/s0140-6736(97)11096-0 + CrossRef records this paper as retracted (notice DOI 10.1016/s0140-6736(10)60175-4); do + not use it as a source. +UNVERIFIED 7. 강동휘 (2025). 폴리(비닐피리딘) 기반… - + No matching CrossRef record was found; verify this reference manually. + +PASS: matches CrossRef. WARN: check the difference named under it. REJECT: dead DOI, a different +paper, or retracted. UNVERIFIED: could not be confirmed automatically; that alone does not mean the +reference is wrong. ``` -`ref-verify` stays quiet for general topic questions, prose editing, APA/IEEE -formatting, and citation style questions. +The command exits with code `2` because not every reference passed. CrossRef's data changes +over time, so a later run can differ slightly. To check your own list, export it from Zotero, +EndNote, or Mendeley as BibTeX or RIS, or paste it into a `.txt` file, and run +`ref-verify check-bib <file>`. --- -## Check a whole reference list - -Find references in a paper or thesis that do not exist (for example ones a -chatbot made up), whose DOI points to a different paper, or that were -retracted, in one run. - -**With the agent:** after installing the skill, ask "check every reference in -references.bib with ref-verify". - -**From a terminal:** - -1. Put the list in a file. - - Zotero: right-click the collection → Export Collection → BibTeX → - `references.bib` (EndNote and Mendeley export BibTeX or RIS). - - A Word or other manuscript: copy the reference list into a plain-text - editor and save it as `references.txt`. `[1]` or `1.` numbering and - wrapped lines are fine. `.docx` and `.pdf` files are not read directly. -2. Install (Python 3.10 or newer): - - ```bash - pipx install ref-verify - ``` - - With `uv`, skip the install: - `uvx ref-verify check-bib references.bib`. +## How to read a result -3. Run: - - ```bash - ref-verify check-bib references.bib - ``` - - A first run takes about a second per reference (a little over two minutes - for 150, with a `Checking references: 37/150` counter). Running the same - list again takes seconds thanks to the cache. Prefixing - `REF_VERIFY_MAILTO=you@university.edu` uses CrossRef's polite pool and is - about three times faster. - - Add `--report check.html` for a file to send to an advisor or co-author; - it opens in a browser with the items that need a look at the top. - -**Reading the result** - -| Result | Meaning | What to do | +| Verdict | What it means | What to do | |---|---|---| -| `PASS` | Title, first author, and year match the CrossRef record for the DOI (or the record found by search) | Nothing | -| `WARN` | Found, but something differs; the line below says what (year, author, the title of the paper the DOI really points to) | Compare that one with the source | -| `REJECT` | The DOI exists nowhere, points to a different paper, or the paper is retracted | Fix or drop the citation | -| `UNVERIFIED` | Could not be confirmed automatically; theses, local conference abstracts, some books, and DOIs registered outside CrossRef (arXiv, KISTI) often land here. It does not mean the reference is wrong | Check it yourself | - -A made-up reference without a DOI can only show as `UNVERIFIED`, not `REJECT`, -so look each `UNVERIFIED` item up once (for example in Google Scholar). +| `PASS` | Title, first author, and year match the CrossRef record for the DOI, or for the record found by search. | Nothing. | +| `WARN` | The reference was found, but something differs. The line under it says what: the year, the first author, or the title of the paper the DOI really belongs to. | Compare that reference with the source. | +| `REJECT` | The DOI exists nowhere, belongs to a different paper, or the paper is retracted. | Fix the reference or remove it. | +| `UNVERIFIED` | ref-verify could not confirm the reference automatically. This is not a finding that it is wrong. | Look it up yourself. | + +`UNVERIFIED` needs care. A made-up reference without a DOI can only come out as `UNVERIFIED`, +never `REJECT`, because there is nothing to disprove. A real thesis, KCI paper, book, or preprint +that CrossRef does not index comes out the same way. In our three test sets, run with +ref-verify 1.3.1 on 2026-10-09, the `UNVERIFIED` results were: + +| Test set | `UNVERIFIED` | Made up | Legitimate, not in CrossRef | Retracted, not in CrossRef | Real paper | +|---|---|---|---|---|---| +| [Held-out v2](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v2.json) | 29 | 13 | 12 | 3 | 1 | +| [Held-out v1](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v1.json) | 22 | 14 | 8 | 0 | 0 | +| [Development](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-v1.json) | 31 | 15 | 16 | 0 | 0 | +| Total | 82 | 42 | 36 | 3 | 1 | + +Roughly half were made up and half were genuine, so look each `UNVERIFIED` reference up once, +for example in Google Scholar, RISS, or KCI. These proportions come from how the test sets were +built, not from real reference lists. --- -## Optional CLI engine - -The skill is the agent workflow. The Python CLI is the skill-level execution engine that the installed skill can call from a terminal. - -The Python package is CLI-only. It does not install `SKILL.md`; install the agent skill from GitHub with `npx skills add` as shown above. - -This is a skill/plugin-level workflow, not an MCP server. The CLI covers the -checks that are currently safe to automate directly: - -- CrossRef metadata check: `ref-verify verify-doi` -- DOI-bound abstract claim check: `ref-verify check-claim` -- Batch DOI-bound claim checks: `ref-verify check-file` - - literal text claims - - subject-matched percentage claims such as efficiency, response rate, or actuation strain - - simple unit/count claims such as cycles, patients, voltage, temperature, and concentration - - CrossRef first, then DOI-bound OpenAlex, Semantic Scholar, and PubMed fallback when CrossRef has no abstract -- Reference-list check (BibTeX, RIS, plain text, Markdown): `ref-verify check-bib` -- JSON output for agent-readable routing -- Non-zero exit codes for `WARN`, `REJECT`, and `UNVERIFIABLE` results - -Statistical metrics such as p-values, AUC/AUROC, F1 score, hazard ratio, odds ratio, and confidence intervals still use the manual skill protocol. DOI landing-page checks still use the skill protocol. The CLI rejects a DOI that CrossRef records as retracted (via its retraction notice); retraction banners CrossRef does not know about, Unpaywall, arXiv, and two-source existence checks remain in the `SKILL.md` protocol. - -The CLI has zero third-party Python runtime dependencies, but it is not an -offline verifier. Functional checks require outbound HTTPS access to public -academic APIs such as CrossRef, OpenAlex, Semantic Scholar, and PubMed. - -### Cache - -The CLI keeps API responses on disk for 7 days, so re-running a check does not -query CrossRef and the abstract sources again. A DOI that returned HTTP 404 is -kept for 1 day only, so a newly registered DOI is re-checked soon. Rate limits -(429) and server errors (5xx) are retried up to 3 times with backoff, honouring -`Retry-After` up to 10 s, and are never cached. - -- Location: `$REF_VERIFY_CACHE_DIR`, else `$XDG_CACHE_HOME/ref-verify`, else `~/.cache/ref-verify`. -- Lifetime: `REF_VERIFY_CACHE_TTL_DAYS` (default `7`). -- Disable: `--no-cache` on any command, or `REF_VERIFY_NO_CACHE=1`. Delete the directory to clear it. +## Three ways to use it -To run the CLI yourself, install it from PyPI: +### As an agent skill ```bash -uvx ref-verify --help # run without installing (uv) -pipx install ref-verify # or install the `ref-verify` command -``` - -Or install it from a local checkout: - -```bash -git clone https://github.com/Moonweave-Research/ref-verify.git -cd ref-verify -python3 -m pip install -e . -``` - -Check whether the CLI is available: - -```bash -ref-verify --help -``` - -If you are working from an uninstalled source checkout, use the module -entrypoint: - -```bash -PYTHONPATH=src python3 -m ref_verify.cli --help +# requires npx (comes with Node.js) +npx skills add Moonweave-Research/ref-verify -g \ + --skill ref-verify \ + --agent claude-code cursor codex \ + -y ``` -Run a DOI metadata check: +Then ask your agent in plain words, for example: -```bash -ref-verify verify-doi 10.1126/science.287.5454.836 \ - --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ - --first-author Pelrine \ - --year 2000 \ - --json +```text +check every reference in references.bib with ref-verify ``` -Run a DOI-bound abstract claim check: - -```bash -ref-verify check-claim 10.1126/science.287.5454.836 \ - --claim "actuation strain above 100%" \ - --json -``` +The skill carries its own copy of the command-line engine and runs it with `python3` (3.10 or +newer), so nothing else needs to be installed. The Python package is CLI-only: it does not install `SKILL.md`, so install the agent skill from GitHub with the command above. +More example requests: [Agent skill](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md#agent-skill). -By default, `check-claim` uses CrossRef first. If CrossRef has no abstract, it tries DOI-bound OpenAlex, Semantic Scholar, and PubMed fallback sources. Use `--source crossref`, `--source openalex`, `--source semantic-scholar`, or `--source pubmed` for source-specific debugging; explicit non-CrossRef source selection bypasses CrossRef. +### From the command line -Source-checkout equivalents: +| Command | Checks | +|---|---| +| `ref-verify check-bib references.bib` | A reference list: BibTeX, RIS, or plain text and Markdown (`.txt`, `.md`). | +| `ref-verify verify-doi <doi> --title "..." --first-author <name> --year <year>` | One DOI against the title, first author, and year you give. | +| `ref-verify check-claim <doi> --claim "..."` | Whether the paper's abstract explicitly states a specific number or phrase. | +| `ref-verify check-file claims.jsonl` | Many DOI and claim pairs at once (JSONL or CSV). | -```bash -PYTHONPATH=src python3 -m ref_verify.cli verify-doi 10.1126/science.287.5454.836 \ - --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ - --first-author Pelrine \ - --year 2000 \ - --json - -PYTHONPATH=src python3 -m ref_verify.cli check-claim 10.1126/science.287.5454.836 \ - --claim "actuation strain above 100%" \ - --json -``` +Add `--json` for machine-readable output. All commands, options, and error codes: +[Command-line engine](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md#command-line-engine). -For local development, run: +### As a saved report ```bash -PYTHONPATH=src python3 -m unittest discover -s tests -v +ref-verify check-bib references.bib --report report.html ``` -Release safety checks also build the Python package, validate metadata, and -install the built wheel in a fresh virtualenv before publishing. Live checks -against public academic APIs are kept in a manual GitHub Actions workflow so -normal CI does not fail because an upstream API is temporarily unavailable. +This writes a single HTML file you can send to a supervisor or co-author. It opens with a count +per verdict, lists the references that need a look first, and links each DOI to +`https://doi.org/`; it loads nothing else. Use `--report report.md` for Markdown. Details: +[Saved reports](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md#saved-reports). --- -## What it catches - -| Problem | What happens without ref-verify | -|---|---| -| **Wrong DOI** | An agent lists a plausible DOI that resolves to a different paper | -| **Wrong authors** | A citation says "Smith et al. (2020)", but CrossRef shows one author | -| **Wrong year** | The paper was published in 2008, but the draft says 2011 | -| **Made-up content** | The draft says a paper shows a result that is not in the abstract | -| **Near-miss citation** | The right number appears, but in the wrong context | -| **Retracted paper** | The DOI is valid, but the paper was retracted | - ---- - -## Scope — optional CLI versus manual audit - -`ref-verify` is a conservative guard, not an oracle. It errs toward flagging: an -`ACCEPT` is high-confidence, and **anything else means "not auto-verifiable — -check it yourself", not "the citation is wrong."** - -**The optional CLI verifies** - -- DOI metadata: title, first-author surname, and year against CrossRef. -- Whether a DOI-bound **abstract** explicitly supports a specific numeric or - literal claim, quoted verbatim. If no abstract is reachable, it returns - `UNVERIFIABLE` rather than guessing. - -**The optional CLI does not verify** (out of scope by design, not bugs) - -- **Full-text, figure, table, or supplementary values** — abstract-only. A number - that appears only in the body stays `UNVERIFIABLE`. -- **Relational or qualitative claims** — proportionalities, mechanisms, - "broader/stronger than". Only value+unit and literal claims are checked. -- **Papers whose publisher withholds the abstract** — some titles expose no - abstract to CrossRef or OpenAlex. No abstract → `UNVERIFIABLE`, which reflects - reachability, not the claim. -- **Statistical metrics** (p-value, AUC/AUROC, F1, hazard/odds ratio, confidence - intervals) — handled by the manual skill protocol, not the CLI. -- **Paper quality, novelty, field consensus**, or whether the *full* paper - supports a broader statement. +## Scorecard -The agent skill's manual Full Audit protocol goes beyond the optional CLI for -mechanism, implementation, and procedural claims. It requires a fetched -full-text passage at that source depth; when full text is unavailable, it -returns `WARN (ABSTRACT-ONLY)` instead of upgrading an abstract topic match to -`ACCEPT`. +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-dark.svg"> + <img src="https://raw.githubusercontent.com/Moonweave-Research/ref-verify/main/.github/assets/scorecard-light.svg" alt="Bar chart of check-bib verdicts on 99 held-out references: real papers 89% passed cleanly, fabricated references 100% flagged, 8 of 8 CrossRef-marked retractions caught, 0 of 13 unindexed references rejected." width="830"> +</picture> -**Reading a CLI verdict** +Held-out set v2: 99 references written and committed before the tool was run on them, sharing +no paper with the earlier sets. It was measured once with ref-verify 1.3.1 against live +CrossRef on 2026-10-09. -| Verdict | Meaning | -|---|---| -| `ACCEPT` | The fetched abstract explicitly supports the claim. High-confidence pass. | -| `WARN` / `PARTIAL` | An abstract was read but does not explicitly support the exact claim. Check the source. | -| `UNVERIFIABLE` | No abstract was reachable to check against. Not a judgment on the claim. | -| `REJECT` | DOI is dead, resolves to a different paper, contradicted, or retracted. | +| What was measured (held-out set v2) | Result | n | 95% CI | +|---|---|---|---| +| Real papers passed cleanly (`PASS`) | **89%** (40) | 45 | 77–95% | +| Real papers sent for a manual check (`WARN`) | 11% (5) | 45 | 5–23% | +| Real papers wrongly rejected (`REJECT`) | 0% (0) | 45 | 0–8% | +| Made-up references flagged (`WARN` or `REJECT`) | **100%** (29) | 29 | 88–100% | +| Retractions CrossRef records, caught as `PAPER_RETRACTED` | **100%** (8) | 8 | 68–100% | +| Legitimate references CrossRef does not index, wrongly rejected | 0% (0) | 13 | 0–23% | + +- Made-up references by type: invented DOI 6/6, no DOI 6/6, DOI of another paper 5/5, wrong author or year 5/5, publicly reported cases 7/7. +- Four more retractions are listed by Retraction Watch but cannot be marked in CrossRef (DOIs registered with KISTI, ISTIC, or DataCite, and one conference-abstract DOI that resolves to its withdrawal notice). None got `PAPER_RETRACTED`: three came out `UNVERIFIED` and one `REJECT` as a different paper. +- The five real papers sent to `WARN`: a title-less citation with an abbreviated journal name, two citations under a group author, one matched to an unrelated record, and a book cited by its print year. +- One unindexed book (Feynman Lectures) passed by matching a 1964 journal review that has the book's title. +- Time for all 99 references: 97 s on an empty cache (1.1 s median per reference), 0.1 s from the cache. +- In-sample sets, used while tuning the tool and therefore optimistic: held-out v1 (86 references) real 40/40 `PASS`, made-up 26/26 flagged, retracted 10/10, unindexed 0/10 rejected; development set (142) real 66/66, made-up 43/43, retracted 16/16, unindexed 1/17 rejected. + +Method: each set is checked with `check-bib` as BibTeX, RIS, and plain-text lists, on an empty +cache, and every item's verdict is stored in a results file +([held-out v2](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/results/2026-10-09-8170e66-holdout-v2.json)). +Not measured: whether a paper supports a claim (beyond a small numeric fixture), non-English +literature other than Korean, and full text. Dataset, method, and how to rerun: +[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md). --- -## Modes +## What it checks and what it does not -**Quick Screen** is for DOIs you already have. It uses CrossRef to compare the -provided DOI, title, first-author surname, and year. - -```bash -ref-verify verify-doi <doi> --title "<title>" --first-author <last-name> --year <year> --json -``` - -`verify-doi` exits `0` only for `PASS`. `WARN` and `REJECT` return a non-zero -exit code, so weak or mismatched metadata cannot silently pass automation gates. +| Question | Checked? | How | +|---|---|---| +| Does the DOI exist? | Yes | CrossRef, then doi.org for DOIs registered elsewhere (arXiv, Zenodo, KISTI): those are `UNVERIFIED`, not dead. | +| Do the title, first author, and year match the DOI? | Yes | Compared with the CrossRef record. | +| Is a reference without a DOI real? | Partly | Found by CrossRef search when it is indexed; otherwise `UNVERIFIED`, never `REJECT`. | +| Was the paper retracted? | When CrossRef records it | A retraction notice in CrossRef, including the Retraction Watch data CrossRef carries. Retractions of papers CrossRef does not hold are missed. | +| Does the abstract state a specific number or phrase? | Yes, abstract only | `check-claim` and `check-file`, with CrossRef, OpenAlex, Semantic Scholar, and PubMed as abstract sources. | +| Full text, figures, tables, supplementary data | No | | +| Statistics such as p-values, AUC, or odds ratios | No | Left to the skill's manual protocol. | +| Paper quality, novelty, or field consensus | No | | -**Full Audit** is for literature search and final pre-submission review. The -skill fetches abstracts through CrossRef, OpenAlex, Semantic Scholar, Unpaywall, -arXiv, and PubMed where needed. For a topline claim, it checks the abstract; for -a mechanism, implementation, or procedural claim, it continues to a fetched -full-text passage before assigning support. +More on the limits: [Scope](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md#scope-optional-cli-versus-manual-audit). -For a single DOI-backed claim, the CLI can run the abstract check: +--- -```bash -ref-verify check-claim <doi> --claim "<specific claim>" --json -``` +## Privacy and network use -`check-claim` exits `0` only for `ACCEPT`. `WARN`, `PARTIAL`, and -`UNVERIFIABLE` return a non-zero exit code. JSON output includes -`abstract_source`, `source_attempts`, and `error_code` so agents can distinguish -missing abstracts, source failures, DOI mismatches, and ambiguous evidence. +ref-verify has zero third-party Python runtime dependencies, but it is not an offline verifier: +it needs outbound HTTPS access to public academic APIs. It contacts only these services: -Use `check-file` when a draft, literature note, or AI-agent output has many -DOI/claim pairs. +| Service | When | What is sent | +|---|---|---| +| CrossRef (`api.crossref.org`) | Every command | The DOI, or, for a reference without a DOI, the reference text as a search query | +| doi.org | When CrossRef has no record of a DOI | The DOI | +| OpenAlex, Semantic Scholar, and PubMed (NCBI) | `check-claim` and `check-file`, when CrossRef has no abstract | The DOI (an arXiv DOI goes to Semantic Scholar as its arXiv ID) | -JSONL: +- Your files and claim texts are not uploaded anywhere: claims are compared with the abstract on your machine, and nothing is sent to any other service. +- Every request carries the user agent `ref-verify/<version> (+https://github.com/Moonweave-Research/ref-verify)`. Your email address is sent to CrossRef only if you set `REF_VERIFY_MAILTO`. OpenAlex requests carry `REF_VERIFY_OPENALEX_MAILTO`, which defaults to a placeholder (`verify@ref-verify.local`). A Semantic Scholar key is sent only if you set `SEMANTIC_SCHOLAR_API_KEY`. +- Responses are cached on disk for 7 days (a DOI that was not found, for 1 day) in `$REF_VERIFY_CACHE_DIR`, else `$XDG_CACHE_HOME/ref-verify`, else `~/.cache/ref-verify`. Each cache file holds the request URL, which includes the DOI or the reference text, and the response. +- Turn the cache off with `--no-cache` or `REF_VERIFY_NO_CACHE=1`, and clear it by deleting that folder. Details: [Cache](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md#cache). -```bash -ref-verify check-file claims.jsonl -ref-verify check-file claims.jsonl --json -``` +--- -CSV: +## For AI agents -```bash -ref-verify check-file claims.csv -``` +Facts an assistant needs to call ref-verify correctly. The full contract is in +[AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md), and the +skill's own instructions are in [SKILL.md](https://github.com/Moonweave-Research/ref-verify/blob/main/SKILL.md). -Each row must include `doi` and `claim`. Optional fields are `id`, `source`, -and `note`. Rows are checked 4 at a time by default (`--workers N`); output keeps -the input order, and CrossRef and Semantic Scholar requests go one at a time -because their public APIs reject parallel requests. In a terminal, a -`Checking claims: N/M` counter on stderr shows progress (never with `--json`). -Ctrl-C stops the run; finished lookups stay cached, so rerunning resumes quickly. Batch mode reuses the same conservative `check-claim` engine: -`ACCEPT` means the abstract explicitly supports the numeric claim. `WARN`, -`PARTIAL`, `REJECT`, or `UNVERIFIABLE` means the claim should not be treated as -verified. - -Current `check-claim` error codes: - -- `CLAIM_SUPPORTED`: explicit abstract support found. -- `CLAIM_NOT_EXPLICIT`: an abstract was available, but the claim was not explicitly supported. -- `CLAIM_AMBIGUOUS`: numeric evidence or context exists, but binding is ambiguous. -- `NO_ABSTRACT`: attempted DOI-bound sources did not provide abstract text. -- `DOI_NOT_FOUND`: neither CrossRef nor doi.org knows the DOI, or the selected source did not find a DOI-bound record. The JSON still carries a `verdict` of `REJECT`. -- `DOI_NOT_IN_CROSSREF`: CrossRef has no record, but doi.org lists the DOI with another agency (DataCite for arXiv and Zenodo, KISTI, JaLC, ...). `verify-doi` returns `verdict: WARN`, `status: UNVERIFIED` without comparing metadata; `check-claim` tries OpenAlex, Semantic Scholar (arXiv DOIs by arXiv identifier), and PubMed for the abstract and judges the claim if one has it, otherwise returns `status: UNVERIFIABLE`, `verdict: WARN` with this code. Not a dead DOI. -- `PAPER_RETRACTED`: CrossRef lists a retraction notice for the DOI; the claim is rejected before any abstract is read. -- `DOI_MISMATCH`: the primary or explicitly selected DOI-bound record did not match the requested DOI. -- `SOURCE_API_ERROR`, `SOURCE_TIMEOUT`, `SOURCE_RATE_LIMITED`, `SOURCE_UNSUPPORTED`: source lookup failed, timed out, was rate-limited, or could not be used. - -Use `check-bib` when you have a reference list rather than DOI/claim pairs: +**Commands** ```bash -ref-verify check-bib references.bib -ref-verify check-bib references.ris --json -ref-verify check-bib references.md --format txt +ref-verify check-bib references.bib --json # a reference list +ref-verify check-file claims.jsonl --json # DOI and claim pairs +ref-verify verify-doi <doi> --title "..." --first-author <name> --year <year> --json +ref-verify check-claim <doi> --claim "..." --json +# inside the installed skill, run the bundled engine: +PYTHONPATH="$SKILL_DIR/src" python3 -m ref_verify.cli check-bib references.bib --json ``` -It reads BibTeX, RIS, and plain-text or Markdown lists (one reference per -paragraph, per line, or per `[1]`/`1.`/`1)` item). A reference with a DOI is -compared with its CrossRef record like `verify-doi`; a plain-text reference -passes only when its text shows the CrossRef title and first author. A -reference without a DOI is looked up with CrossRef bibliographic search and -accepted only when the title matches and the year is within one. Matching -accepts the print or the online-first year, a title with or without its -subtitle or edition note, TeX math in BibTeX titles (`$\beta$` reads as β), -CrossRef's original-language title (for example the Korean title of -a *Polymer Korea* paper), and Hangul author names against CrossRef's -romanized ones (윤 → Yoon/Yun). When a DOI is unknown to CrossRef, doi.org is -asked which agency registered it, so arXiv, Zenodo, or KISTI DOIs are not -reported as dead. Search results that are about the paper rather than the -paper itself (peer-review reports, Faculty Opinions recommendations, -addenda and corrections) are skipped. A citation in a style that omits the -article title (`J. Bardeen, L. N. Cooper, and J. R. Schrieffer, Phys. Rev. 108, -1175 (1957)`) is compared on journal (full name or abbreviation), volume, first -page or article number, year, and first author; it passes when all of them -agree, and otherwise the reason names each field that differs. Without a DOI, -when the plain search finds nothing and the citation looks title-less, a second -CrossRef search by first author, -the rest of the citation, and the cited year finds short citations such as -`A. G. Riess et al., Astron. J. 116, 1009 (1998).`; the same full agreement is -required. The first author -is read only from the first name in the list, so a reference that puts a -co-author first does not pass. The terminal output starts with a count line -(`19 references: 11 PASS, 2 WARN, 5 REJECT, 1 UNVERIFIED`), lists one row per -reference (citation key, or the start of the reference for a pasted list), puts -the reason under every row that is not `PASS`, and ends with a one-paragraph -legend. With `--json` it is an object with `summary` (`total`, `pass`, `warn`, -`reject`, `unverified`, `failed`; `warn` includes the `UNVERIFIED` rows) and -`results`. `check-bib` exits `0` only -when every reference is `PASS`. - -`check-bib` error codes: - -- `REFERENCE_RESOLVED`: the reference had no DOI; CrossRef search found a matching record, reported as `resolved_doi`. `WARN` when the year differs by one or the first author differs. -- `REFERENCE_UNMATCHED`: the reference had no DOI and no CrossRef record matched (`status: UNVERIFIED`, `verdict: WARN`). The tool could not confirm it automatically; that does not mean the reference is wrong. Verify it manually. -- `DOI_NOT_IN_CROSSREF`: the DOI is registered with another agency (DataCite for arXiv and Zenodo, KISTI, JaLC, ...), so its metadata was not compared (`status: UNVERIFIED`, `verdict: WARN`). Open the DOI to confirm it. -- `DOI_NOT_FOUND`: neither CrossRef nor doi.org knows the DOI (`REJECT`). -- `PAPER_RETRACTED`, `ROW_CHECK_ERROR`: as for `check-claim` and `check-file`. Other DOI-backed results carry `error_code: null`; read `verdict`, `mismatches`, and `reason`, which names what differs (for example `the year differs (reference: 2009; CrossRef: 2010)`). A plain-text reference whose DOI belongs to a paper it does not mention is `status: MISMATCH`, `verdict: WARN`, with that paper's title in `reason`. - -To hand the result to a co-author or supervisor, add `--report` to `check-bib` -or `check-file`. The file extension picks the format: +**Exit codes** -```bash -ref-verify check-bib references.bib --report report.html -ref-verify check-file claims.jsonl --report report.md -``` +| Code | Meaning | +|---|---| +| `0` | Every item is `PASS` (`check-bib`, `verify-doi`) or `ACCEPT` (`check-claim`, `check-file`). | +| `2` | The run completed, but at least one item did not pass. Read the JSON anyway. | +| `1` | Input or runtime error. | -The HTML file is self-contained (inline CSS, no scripts, no external resources -other than `https://doi.org/` links). It opens with counts that add up to the -total (one box per verdict as shown), a plain-language line on what each -verdict means and asks you to do, then a "Needs a look" table with every -non-passing reference or claim and a "Passed" table below it. Each row is -coloured (`PASS`/`ACCEPT` green, `WARN` amber, `REJECT` red, `UNVERIFIED` -grey) and shows the reason and evidence. `UNVERIFIED` marks a result the tool -could not confirm automatically; it is not a finding that the reference is -wrong. The Markdown file has the same content. A `--report` path whose folder -does not exist is rejected before any lookup, so a long run is never lost. -`--json` output is unchanged. - -> Core rule: every content statement about a paper must come from a live-fetched -> source at the depth the claim requires — abstract for topline claims, full -> text for mechanism, implementation, or procedural claims. If the required -> source is inaccessible, say so. Do not fill the gap from memory. +- **Inputs:** `.bib`, `.ris`, `.txt`, `.md` for `check-bib` (`--format bib|ris|txt` for other extensions); JSONL rows `{"doi": ..., "claim": ..., "id": ..., "note": ...}` for `check-file` (`doi` and `claim` required). +- **JSON keys that matter:** `summary` (`total`, `pass`, `warn`, `reject`, `unverified`, `failed` for `check-bib`), and per result `verdict`, `status`, `error_code`, `resolved_doi`, `mismatches`, `reason`. +- **Never report these as verified:** `WARN`, `REJECT`, or `UNVERIFIED` from `check-bib`; `WARN`, `PARTIAL`, `REJECT`, or `UNVERIFIABLE` from claim checks; any run with `failed > 0`. Treat only `PASS` (references) and `ACCEPT` (claims) as confirmed. +- **Wording:** `REFERENCE_UNMATCHED` means CrossRef search found no match. Report it as unverified and ask the user to check it; never call it fabricated. `DOI_NOT_IN_CROSSREF` means the DOI is registered elsewhere; it is unverified, not dead. For `REFERENCE_RESOLVED`, use `resolved_doi` as the reference's DOI. +- **Do not** fill a missing abstract from memory, or infer full-text, table, or figure support from an abstract-only check. --- -## Examples - -**Checking citations you already have** - -```text -User: "verify these 3 citations before I submit" - -Shahinpoor & Kim (2001) 10.1088/0964-1726/10/4/327 - PASS -Bar-Cohen (2004) 10.1117/3.547465 - WARN (listed as author; CrossRef: editor) -Carpi et al. (2011) 10.1016/B978-0-08-047488-5.00001-0 - REJECT -``` - -**Checking a specific claim** - -```text -User: "does the Pelrine 2000 paper actually say DEAs reach over 100% strain?" - -CONTENT: Supported -"Actuated strains up to 117% were demonstrated with silicone elastomers, -and up to 215% with acrylic elastomers." -[Source: CrossRef raw JSON, not recalled from memory] -``` - -**Near-miss citation** +## More documentation -A candidate paper may contain "500% strain", but the abstract can show that the -number is a pre-strain condition, not an actuation result. `ref-verify` reports -that as `WARN (PARTIAL)` instead of accepting the citation. +- [docs/REFERENCE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md): every command, mode, error code, the cache, the limits, and worked examples. +- [AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md): the contract for agents calling the CLI. +- [SKILL.md](https://github.com/Moonweave-Research/ref-verify/blob/main/SKILL.md): the agent skill itself. +- [benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md): the test sets and how to rerun them. +- [CHANGELOG.md](https://github.com/Moonweave-Research/ref-verify/blob/main/CHANGELOG.md): what changed in each release. --- -## Related +## Contributing, license, and related projects -- [decision-kernel](https://github.com/Moonweave-Systems/decision-kernel) - evidence-gated decisions and drift/done checks for coding agents +- Issues and pull requests are welcome. Run the tests with `PYTHONPATH=src python3 -m unittest discover -s tests` before sending a change. +- License: [MIT](https://github.com/Moonweave-Research/ref-verify/blob/main/LICENSE). +- Related: [decision-kernel](https://github.com/Moonweave-Systems/decision-kernel), evidence-gated decisions and drift and done checks for coding agents. diff --git a/benchmarks/README.md b/benchmarks/README.md index 543a461..de772a6 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -6,8 +6,8 @@ fabricated, retracted, and unindexed references against live CrossRef. | Set | Items | Role | |---|---|---| | `references-regress-v3.jsonl` | 23 | **Regression set for the search-path and Hangul-title fixes.** Not a held-out score: it holds the failure cases found by the E2E evaluation (#37) and holdout-v2 (a same-title letter or review taken for the paper, an SI component taken for the paper, a book matched to a journal review of it, a Korean reference with a title read as title-less) and close-call controls where the cited record really is a letter, a review, a book, or a preprint. Frozen before the before/after runs. | -| `references-holdout-v2.jsonl` | 99 | **Held-out set v2.** Written and committed before any run, after #30 and #33 were tuned on holdout-v1's misses. It shares no paper (DOI or title) with the other two sets and stresses title-less physics/chemistry citations (13 real, 8 fabricated), group authors, particle surnames, Korean-titled Korean journal papers with Hangul authors, books, conference papers, retractions marked in CrossRef only through Retraction Watch data, and retractions CrossRef cannot mark (KISTI, ISTIC, and DataCite DOIs). Measured once, on 1.3.1; the README scorecard is not yet switched to it. | -| `references-holdout-v1.jsonl` | 86 | **Held-out set v1, now in-sample:** its misses were used to tune #30 and #33. Originally written and committed before the tool was run on it; no paper in it appears in the development set, and it uses other journals, fields, and citation styles (IEEE, Nature, Harvard, Chicago, biblatex, and physics/chemistry styles that omit the article title). The README headline numbers and chart come from this set. | +| `references-holdout-v2.jsonl` | 99 | **Held-out set v2.** Written and committed before any run, after #30 and #33 were tuned on holdout-v1's misses. It shares no paper (DOI or title) with the other two sets and stresses title-less physics/chemistry citations (13 real, 8 fabricated), group authors, particle surnames, Korean-titled Korean journal papers with Hangul authors, books, conference papers, retractions marked in CrossRef only through Retraction Watch data, and retractions CrossRef cannot mark (KISTI, ISTIC, and DataCite DOIs). Measured once, on 1.3.1; the README headline numbers and chart come from this set. | +| `references-holdout-v1.jsonl` | 86 | **Held-out set v1, now in-sample:** its misses were used to tune #30 and #33. Originally written and committed before the tool was run on it; no paper in it appears in the development set, and it uses other journals, fields, and citation styles (IEEE, Nature, Harvard, Chicago, biblatex, and physics/chemistry styles that omit the article title). | | `references-v1.jsonl` | 142 | **Development set (used while fixing the tool).** Its misses drove the matching fixes in #27, so its scores are in-sample and are shown only as a secondary line. | Rules for the held-out set: it is frozen at the commit that added it, and items are @@ -26,7 +26,9 @@ measure again after the tool is tuned against v1's misses. before the held-out set existed). - `../scripts/benchmark_references.py`: runs a set (standard library only). - `../scripts/render_scorecard.py`: draws `.github/assets/scorecard-{light,dark}.svg` - from a held-out results file, with the development-set result as a footnote. + from a held-out results file, with the development-set result as a footnote. The README chart + is drawn from `results/2026-10-09-8170e66-holdout-v2.json` with + `--dev results/2026-10-09-8170e66-v1.json`. ## Holdout-v2 measurement (2026-10-09, ref-verify 1.3.1) diff --git a/docs/REFERENCE.ko.md b/docs/REFERENCE.ko.md new file mode 100644 index 0000000..e52bc8a --- /dev/null +++ b/docs/REFERENCE.ko.md @@ -0,0 +1,488 @@ +# ref-verify 참고 문서 + +[한국어](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md) | [English](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md) + +[README](https://github.com/Moonweave-Research/ref-verify/blob/main/README.ko.md)에서 다루지 않은 세부 +내용을 모았습니다. 모든 명령, 모드, 오류 코드, 캐시, 그리고 확인 범위의 한계를 설명합니다. +PyPI의 현재 릴리스인 ref-verify 1.3.1 기준입니다. + +## 목차 + +- [에이전트 스킬](#에이전트-스킬) +- [참고문헌 목록 점검(check-bib)](#참고문헌-목록-점검check-bib) +- [명령줄 엔진](#명령줄-엔진) +- [모드](#모드) +- [오류 코드](#오류-코드) +- [저장한 보고서](#저장한-보고서) +- [캐시](#캐시) +- [범위 — 선택적 CLI와 수동 감사의 경계](#범위--선택적-cli와-수동-감사의-경계) +- [잡아내는 문제](#잡아내는-문제) +- [예시](#예시) +- [개발과 릴리스 점검](#개발과-릴리스-점검) + +--- + +## 에이전트 스킬 + +`ref-verify`는 연구 인용 검증용 에이전트 스킬입니다. Claude Code, +Cursor, Codex 같은 스킬 지원 에이전트가 초안에 참고문헌을 넣기 전에 +반복 가능한 검증 절차를 따르도록 합니다. 논문 찾기, DOI 확인, 특정 주장을 논문이 실제로 +뒷받침하는지 확인, 제출 전 참고문헌 점검에 사용할 수 있습니다. + +### 스킬 설치 + +```bash +# Node.js에 포함된 npx 필요 +npx skills add Moonweave-Research/ref-verify -g \ + --skill ref-verify \ + --agent claude-code cursor codex \ + -y +``` + +**Claude Code, Cursor, Codex** 및 `npx skills` 생태계를 지원하는 +에이전트에서 사용할 수 있습니다. + +설치 후에는 일반 에이전트 스킬처럼 자연어로 요청하면 됩니다. 이 +워크플로우에는 MCP 서버가 필요하지 않습니다. 서버를 시작하거나 MCP를 +설정하지 않습니다. + +스킬 안에 CLI 엔진이 함께 들어 있고 에이전트가 스킬 폴더에서 직접 실행하므로 +따로 설치할 것이 없습니다. `python3`로 Python 3.10 이상을 실행할 수 있어야 합니다. + +에이전트가 CLI를 호출할 때 따라야 할 명시적 규칙은 +[AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md)를 참고하세요. + +### 요청 예시 + +자연스럽게 요청하면 됩니다. + +```text +제출 전에 이 인용들을 검증해줘: [DOI list] +이 논문이 "actuation strain above 100%"라는 주장을 실제로 뒷받침해? +claim X를 뒷받침하는 논문 3개를 찾고, 각 인용을 검증해줘 +이 title/year와 DOI 10.1126/science.287.5454.836이 맞는지 확인해줘 +제출 전에 참고문헌 전체를 점검해줘 +references.bib 참고문헌 전체를 ref-verify로 점검해줘 +``` + +일반 주제 설명, 문장 다듬기, APA/IEEE 형식 정리, 인용 스타일 질문에는 +조용히 있습니다. + +--- + +## 참고문헌 목록 점검(check-bib) + +논문이나 학위논문의 참고문헌 중에 존재하지 않거나(ChatGPT가 지어낸 것 등), DOI가 +다른 논문을 가리키거나, 철회된 논문이 섞여 있는지 한 번에 확인합니다. + +**에이전트에게 맡길 때:** 스킬을 설치한 뒤 "references.bib 참고문헌 전체를 +ref-verify로 점검해줘"라고 요청하면 됩니다. + +**터미널에서 직접 할 때:** + +1. 목록을 파일로 준비합니다. + - Zotero: 컬렉션 우클릭 → 컬렉션 내보내기 → BibTeX → `references.bib` + (EndNote·Mendeley는 BibTeX이나 RIS로 내보내기) + - Word·한글 원고: 참고문헌 목록을 복사해 메모장 등에 붙여 넣고 + `references.txt`로 저장합니다. `[1]`, `1.` 번호나 줄바꿈이 있어도 됩니다. + `.docx`, `.hwp`, `.pdf`는 직접 읽지 못합니다. +2. 설치합니다(Python 3.10 이상). + + ```bash + pipx install ref-verify + ``` + + `uv`가 있다면 설치 없이 `uvx ref-verify check-bib references.bib`로 + 바로 실행해도 됩니다. + +3. 실행합니다. + + ```bash + ref-verify check-bib references.bib + ref-verify check-bib references.ris --json + ref-verify check-bib references.md --format txt + ``` + + 처음 실행할 때는 참고문헌 하나에 1초 정도 걸립니다(150개면 2분 남짓, + `Checking references: 37/150`처럼 진행 상황이 보입니다). 같은 목록을 다시 돌리면 + 캐시 덕분에 몇 초면 끝납니다. `REF_VERIFY_MAILTO=내이메일@학교.ac.kr`를 앞에 + 붙이면 CrossRef의 polite pool을 써서 약 3배 빨라집니다. + + 지도교수나 공동 저자에게 보낼 파일이 필요하면 `--report 점검결과.html`을 붙입니다. + 브라우저로 열면 확인이 필요한 항목이 맨 위에 모여 있습니다. + +### check-bib이 참고문헌을 대조하는 방식 + +BibTeX, RIS, 일반 텍스트·Markdown 목록(문단마다, 줄마다, 또는 +`[1]`/`1.`/`1)` 번호마다 참고문헌 하나)을 읽습니다. DOI가 있는 항목은 +`verify-doi`처럼 CrossRef 기록과 대조하고, 일반 텍스트 항목은 본문에 +CrossRef 제목과 제1저자가 드러날 때만 통과합니다. DOI가 없는 항목은 +CrossRef 서지 검색으로 찾아 제목이 일치하고 연도 차이가 1년 이내일 때만 +받아들입니다. 인쇄본 연도와 온라인 선공개 연도 모두, 부제나 판 표기를 뺀 제목, +BibTeX 제목의 TeX 수식(`$\beta$`는 β), CrossRef에 +등록된 원어 제목(예: 『폴리머』 논문의 한글 제목), 한글 저자명과 CrossRef의 로마자 +표기(윤 → Yoon/Yun)를 같은 것으로 봅니다. CrossRef에 없는 DOI는 doi.org에 등록기관을 +물어보므로 arXiv, Zenodo, KISTI DOI를 없는 DOI로 판정하지 않습니다. 논문 자체가 아니라 +그 논문에 관한 기록(동료 심사 보고서, Faculty Opinions 추천, addendum·correction)은 검색 +결과에서 건너뜁니다. + +논문 제목을 쓰지 않는 물리·화학 인용 형식(`J. Bardeen, L. N. Cooper, and +J. R. Schrieffer, Phys. Rev. 108, 1175 (1957)`)은 학술지(전체 이름 또는 약어), 권, 첫 쪽 +또는 논문 번호, 연도, 제1저자로 비교하며, 모두 맞으면 통과하고 다르면 어느 항목이 다른지 +이유에 적습니다. DOI가 없고 일반 검색에서 찾지 못했으며 제목 없는 인용으로 보이면 제1저자, +나머지 인용 정보, 인용 연도로 CrossRef를 한 번 더 검색해 +`A. G. Riess et al., Astron. J. 116, 1009 (1998).` 같은 짧은 인용도 찾으며, 이때도 모든 +항목이 일치해야 받아들입니다. 제1저자는 저자 목록의 첫 이름에서만 읽으므로 공저자를 맨 앞에 쓴 +참고문헌은 통과하지 않습니다. + +### 출력 + +터미널 출력은 `19 references: 11 PASS, 2 WARN, 5 REJECT, 1 UNVERIFIED` 같은 개수 줄로 +시작하고, 참고문헌마다 한 줄(인용 키, 붙여 넣은 목록이면 참고문헌 앞부분)을 보여 주며, `PASS`가 +아닌 줄 아래에는 이유를, 끝에는 판정 설명을 붙입니다. `--json`이면 `summary`(`total`, `pass`, `warn`, +`reject`, `unverified`, `failed`; `warn`에는 `UNVERIFIED` 항목도 포함)와 `results`를 담은 객체입니다. +모든 항목이 `PASS`일 때만 exit `0`입니다. + +### check-bib 결과 읽는 법 + +| 결과 | 뜻 | 할 일 | +|---|---|---| +| `PASS` | DOI(또는 검색으로 찾은 기록)의 제목·제1저자·연도가 CrossRef와 일치 | 없음 | +| `WARN` | 찾았지만 무언가 다름. 바로 아래 줄에 무엇이 다른지 나옵니다(연도, 저자, DOI가 가리키는 다른 논문 제목 등) | 그 항목만 원문과 대조 | +| `REJECT` | DOI가 어디에도 없음, 전혀 다른 논문을 가리킴, 또는 철회된 논문 | 인용을 고치거나 빼기 | +| `UNVERIFIED` | 자동으로 확인하지 못함. 학위논문, 국내 학회 초록, 일부 책, CrossRef가 아닌 곳(arXiv, KISTI 등)에 등록된 DOI가 흔히 여기에 옵니다. 틀렸다는 뜻이 아닙니다 | 직접 확인 | + +DOI가 없는 가짜 참고문헌은 `REJECT`가 아니라 `UNVERIFIED`로만 나올 수 있습니다. +`UNVERIFIED` 항목은 Google Scholar나 RISS에서 실제로 있는지 한 번씩 찾아보세요. + +--- + +## 명령줄 엔진 + +스킬이 에이전트 워크플로우입니다. Python CLI는 설치된 스킬이 터미널에서 +호출할 수 있는 skill-level execution engine입니다. + +Python 패키지는 CLI 전용입니다. `SKILL.md`를 설치하지 않습니다. 에이전트 +스킬은 위의 `npx skills add` 명령으로 GitHub에서 설치합니다. + +이것은 스킬/플러그인 수준 워크플로우이며 MCP 서버가 아닙니다. CLI는 +현재 직접 자동화해도 안전한 부분만 담당합니다. + +- CrossRef 메타데이터 확인: `ref-verify verify-doi` +- DOI에 묶인 abstract 기반 주장 확인: `ref-verify check-claim` +- 여러 DOI 기반 주장 일괄 확인: `ref-verify check-file` + - 문장 그대로 드러나는 text claim + - efficiency, response rate, actuation strain 같은 subject가 일치하는 percentage claim + - cycles, patients, voltage, temperature, concentration 같은 단순 unit/count claim + - CrossRef를 먼저 쓰고, CrossRef에 abstract가 없으면 DOI가 일치하는 OpenAlex, Semantic Scholar, PubMed fallback 사용 +- 참고문헌 목록 확인(BibTeX, RIS, 일반 텍스트, Markdown): `ref-verify check-bib` +- 에이전트가 읽기 쉬운 JSON 출력 +- `WARN`, `REJECT`, `UNVERIFIABLE` 결과에 대한 non-zero exit code + +`p-value`, AUC/AUROC, F1 score, hazard ratio, odds ratio, confidence interval +같은 통계 지표는 아직 수동 스킬 프로토콜을 따릅니다. DOI landing page 확인은 스킬 프로토콜을 따릅니다. Unpaywall, arXiv, 두 개 이상의 독립 출처로 존재 확인도 스킬 프로토콜이 담당합니다. +CrossRef가 철회 공지를 기록한 DOI는 CLI가 바로 `REJECT`합니다. CrossRef에 없는 철회 배너 확인은 여전히 `SKILL.md`의 스킬 프로토콜이 담당합니다. + +CLI에는 third-party Python runtime dependency가 없지만, offline verifier는 +아닙니다. 실제 검증에는 CrossRef, OpenAlex, Semantic Scholar, PubMed 같은 공개 학술 +API로 outbound HTTPS 요청을 보낼 수 있어야 합니다. + +### CLI 설치 + +CLI를 직접 쓰려면 PyPI에서 설치합니다. + +```bash +uvx ref-verify --help # 설치 없이 실행 (uv) +pipx install ref-verify # 또는 `ref-verify` 명령 설치 +``` + +로컬 체크아웃에서 설치할 수도 있습니다. + +```bash +git clone https://github.com/Moonweave-Research/ref-verify.git +cd ref-verify +python3 -m pip install -e . +``` + +CLI 사용 가능 여부를 확인합니다. + +```bash +ref-verify --help +``` + +설치하지 않은 소스 체크아웃에서는 모듈 엔트리포인트를 사용합니다. + +```bash +PYTHONPATH=src python3 -m ref_verify.cli --help +``` + +DOI 메타데이터를 확인합니다. + +```bash +ref-verify verify-doi 10.1126/science.287.5454.836 \ + --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ + --first-author Pelrine \ + --year 2000 \ + --json +``` + +DOI에 묶인 abstract에 대해 특정 주장을 확인합니다. + +```bash +ref-verify check-claim 10.1126/science.287.5454.836 \ + --claim "actuation strain above 100%" \ + --json +``` + +기본값으로 `check-claim`은 CrossRef를 먼저 사용합니다. CrossRef에 abstract가 없으면 DOI가 일치하는 OpenAlex, Semantic Scholar, PubMed fallback을 시도합니다. 특정 소스만 디버깅하려면 `--source crossref`, `--source openalex`, `--source semantic-scholar`, `--source pubmed`를 사용합니다. 명시적으로 non-CrossRef 소스를 고르면 CrossRef를 거치지 않습니다. + +소스 체크아웃에서 바로 실행하는 예시는 다음과 같습니다. + +```bash +PYTHONPATH=src python3 -m ref_verify.cli verify-doi 10.1126/science.287.5454.836 \ + --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ + --first-author Pelrine \ + --year 2000 \ + --json + +PYTHONPATH=src python3 -m ref_verify.cli check-claim 10.1126/science.287.5454.836 \ + --claim "actuation strain above 100%" \ + --json +``` + +--- + +## 모드 + +**Quick Screen**은 이미 DOI가 있을 때 사용합니다. CrossRef로 DOI, 제목, +첫 번째 저자의 성, 연도를 비교합니다. + +```bash +ref-verify verify-doi <doi> --title "<title>" --first-author <last-name> --year <year> --json +``` + +`verify-doi`는 `PASS`일 때만 exit code `0`을 반환합니다. `WARN`과 +`REJECT`는 non-zero exit code를 반환하므로, 약하거나 맞지 않는 +메타데이터가 자동화 단계를 조용히 통과할 수 없습니다. + +**Full Audit**은 논문을 처음 찾거나 제출 전 최종 점검을 할 때 사용합니다. +스킬은 필요한 경우 CrossRef, OpenAlex, Semantic Scholar, Unpaywall, arXiv, PubMed를 +통해 source text를 가져옵니다. topline claim은 abstract에서 확인하고, +mechanism, implementation, procedure claim은 full text passage까지 확인한 +뒤에만 지지 판정을 내립니다. + +단일 DOI 기반 주장에 대해서는 CLI가 abstract 확인을 수행할 수 있습니다. + +```bash +ref-verify check-claim <doi> --claim "<specific claim>" --json +``` + +`check-claim`은 `ACCEPT`일 때만 exit code `0`을 반환합니다. `WARN`, +`PARTIAL`, `UNVERIFIABLE`은 non-zero exit code를 반환합니다. JSON 출력에는 +`abstract_source`, `source_attempts`, `error_code`가 포함되어 abstract 부재, +소스 실패, DOI 불일치, 애매한 근거를 구분할 수 있습니다. + +초안, 리서치 메모, AI 에이전트 출력처럼 DOI/claim 쌍이 여러 개 있을 때는 +`check-file`을 사용합니다. + +JSONL: + +```bash +ref-verify check-file claims.jsonl +ref-verify check-file claims.jsonl --json +``` + +CSV: + +```bash +ref-verify check-file claims.csv +``` + +각 행에는 `doi`와 `claim`이 필요합니다. `id`, `source`, `note`는 선택 +필드입니다. 기본으로 4행씩 동시에 확인하며(`--workers N`), 출력 순서는 입력 +순서를 그대로 따릅니다. CrossRef와 Semantic Scholar 공개 API는 동시 요청을 거절하므로 +이 두 곳에는 한 번에 하나씩 보냅니다. 터미널에서 실행하면 stderr에 +`Checking claims: N/M` 진행 표시가 나옵니다(`--json`일 때는 나오지 않음). Ctrl-C로 +멈출 수 있고, 끝난 조회는 캐시에 남으므로 같은 명령을 다시 실행하면 빠르게 이어집니다. 배치 모드는 기존의 보수적인 `check-claim` 엔진을 그대로 +사용합니다. `ACCEPT`는 abstract가 숫자 claim을 명시적으로 지지한다는 +뜻입니다. `WARN`, `PARTIAL`, `REJECT`, `UNVERIFIABLE`은 검증된 claim으로 +취급하면 안 됩니다. + +DOI/claim 쌍이 아니라 참고문헌 목록이 있을 때는 `check-bib`을 사용합니다. +[참고문헌 목록 점검](#참고문헌-목록-점검check-bib)을 보세요. + +--- + +## 오류 코드 + +현재 `check-claim` error code는 다음과 같습니다. + +- `CLAIM_SUPPORTED`: abstract 안에 명시적 근거가 있음 +- `CLAIM_NOT_EXPLICIT`: abstract는 있지만 claim을 명시적으로 뒷받침하지 않음 +- `CLAIM_AMBIGUOUS`: 숫자나 맥락은 있으나 subject/숫자 연결이 애매함 +- `NO_ABSTRACT`: 시도한 DOI-bound source에서 abstract text를 얻지 못함 +- `DOI_NOT_FOUND`: CrossRef와 doi.org 어디에도 없는 DOI이거나 선택한 source에서 DOI-bound record를 찾지 못함. JSON에는 `verdict: REJECT`가 함께 담김 +- `DOI_NOT_IN_CROSSREF`: CrossRef에는 없지만 doi.org에 다른 등록기관(arXiv·Zenodo의 DataCite, KISTI, JaLC 등)으로 등록된 DOI. `verify-doi`는 서지 정보를 비교하지 않고 `verdict: WARN`, `status: UNVERIFIED`를 반환하고, `check-claim`은 OpenAlex, Semantic Scholar(arXiv DOI는 arXiv 식별자로), PubMed에서 초록을 찾아 있으면 주장을 판정하고 없으면 이 코드와 함께 `status: UNVERIFIABLE`, `verdict: WARN`을 반환합니다. 없는 DOI라는 뜻이 아닙니다 +- `PAPER_RETRACTED`: CrossRef에 철회 공지가 있어 초록을 읽기 전에 거절함 +- `DOI_MISMATCH`: primary 또는 명시적으로 선택한 DOI-bound record가 요청 DOI와 다름 +- `SOURCE_API_ERROR`, `SOURCE_TIMEOUT`, `SOURCE_RATE_LIMITED`, `SOURCE_UNSUPPORTED`: source lookup 실패, timeout, rate limit, 사용 불가 + +`check-file`은 행마다 같은 코드를 쓰고, 한 행을 확인하지 못하면 `ROW_CHECK_ERROR`를 씁니다. + +`check-bib` error code: + +- `REFERENCE_RESOLVED`: DOI가 없던 항목을 CrossRef 검색으로 찾았고 `resolved_doi`에 기록함. 연도가 1년 다르거나 제1저자가 다르면 `WARN` +- `REFERENCE_UNMATCHED`: DOI가 없고 일치하는 CrossRef 기록도 찾지 못함(`status: UNVERIFIED`, `verdict: WARN`). 도구가 자동으로 확인하지 못했다는 뜻이지 참고문헌이 틀렸다는 뜻은 아니므로 직접 확인합니다 +- `DOI_NOT_IN_CROSSREF`: DOI가 CrossRef가 아닌 다른 등록기관(arXiv·Zenodo의 DataCite, KISTI, JaLC 등)에 등록되어 있어 서지 정보를 비교하지 못함(`status: UNVERIFIED`, `verdict: WARN`). DOI를 직접 열어 확인합니다 +- `DOI_NOT_FOUND`: CrossRef와 doi.org 어디에도 없는 DOI(`REJECT`) +- `PAPER_RETRACTED`, `ROW_CHECK_ERROR`: `check-claim`, `check-file`과 같음. 그 밖의 DOI 기반 결과는 `error_code: null`이며 `verdict`, `mismatches`, 그리고 무엇이 다른지 적힌 `reason`(예: `the year differs (reference: 2009; CrossRef: 2010)`)을 봅니다. 일반 텍스트 참고문헌의 DOI가 본문에 없는 다른 논문을 가리키면 `status: MISMATCH`, `verdict: WARN`이고 그 논문 제목이 `reason`에 나옵니다 + +--- + +## 저장한 보고서 + +결과를 공동 저자나 지도교수에게 넘기려면 `check-bib` 또는 `check-file`에 +`--report`를 붙입니다. 파일 확장자가 형식을 정합니다. + +```bash +ref-verify check-bib references.bib --report report.html +ref-verify check-file claims.jsonl --report report.md +``` + +HTML 파일은 그 자체로 완결됩니다(인라인 CSS, 스크립트 없음, `https://doi.org/` +링크 외 외부 자원 없음). 맨 위에 합이 전체 개수와 맞는 판정별 개수, 각 판정의 뜻과 +할 일을 적은 설명이 있고, 그 아래 통과하지 못한 항목만 모은 "Needs a look" 표와 +통과한 항목의 "Passed" 표가 이어집니다. 각 행은 색으로 구분되며(`PASS`/`ACCEPT` 초록, +`WARN` 호박색, `REJECT` 빨강, `UNVERIFIED` 회색) 이유와 근거를 보여 줍니다. +`UNVERIFIED`는 도구가 자동으로 확인하지 못했다는 표시이지 참고문헌이 틀렸다는 +판정이 아닙니다. Markdown 파일도 같은 내용입니다. `--report` 경로의 폴더가 없으면 +조회를 시작하기 전에 알려 주므로 긴 실행이 헛되지 않습니다. `--json` 출력은 바뀌지 않습니다. + +--- + +## 캐시 + +CLI는 API 응답을 디스크에 7일 동안 보관하므로, 같은 검사를 다시 돌려도 +CrossRef와 abstract 소스에 다시 요청하지 않습니다. HTTP 404가 나온 DOI는 +1일만 보관해 새로 등록된 DOI를 곧 다시 확인합니다. 요청 제한(429)과 서버 +오류(5xx)는 backoff를 두고 최대 3번 재시도하며(`Retry-After`는 10초까지 +따름), 캐시에 남기지 않습니다. + +- 위치: `$REF_VERIFY_CACHE_DIR`, 없으면 `$XDG_CACHE_HOME/ref-verify`, 그것도 없으면 `~/.cache/ref-verify`. +- 보관 기간: `REF_VERIFY_CACHE_TTL_DAYS` (기본 `7`). +- 끄기: 모든 명령에서 `--no-cache`, 또는 `REF_VERIFY_NO_CACHE=1`. 비우려면 디렉터리를 지웁니다. +- 내용: 요청 하나당 JSON 파일 하나에 요청 URL, HTTP 상태, 저장 시각, 응답 본문이 들어갑니다. URL에는 + 조회한 내용이 그대로 담깁니다. DOI이거나, DOI 없는 참고문헌이면 CrossRef 검색에 보낸 참고문헌 문장이고, + `REF_VERIFY_MAILTO`를 설정했다면 그 주소도 들어갑니다. + +--- + +## 범위 — 선택적 CLI와 수동 감사의 경계 + +`ref-verify`는 만능 판정기가 아니라 보수적인 안전장치입니다. 일부러 +flag를 더 자주 냅니다. `ACCEPT`는 높은 확신의 통과이고, 그 외 결과는 +"자동으로 확인할 수 없으니 사람이 확인해야 한다"는 뜻이지 "인용이 +틀렸다"는 뜻은 아닙니다. + +**선택적 CLI가 확인하는 것** + +- DOI 메타데이터: 제목, 첫 번째 저자 성, 연도를 CrossRef와 비교합니다. +- DOI-bound **abstract**가 특정 숫자 또는 literal claim을 명시적으로 + 뒷받침하는지 확인합니다. abstract에 접근할 수 없으면 추측하지 않고 + `UNVERIFIABLE`을 반환합니다. + +**선택적 CLI가 확인하지 않는 것** (의도적으로 범위 밖이며 버그가 아닙니다) + +- **본문, figure, table, supplementary 값** — abstract-only입니다. 숫자가 + 본문에만 있으면 `UNVERIFIABLE`로 남습니다. +- **관계형/정성적 claim** — proportionality, mechanism, "더 강하다/넓다" + 같은 표현은 자동 판정하지 않습니다. value+unit 또는 literal claim 중심입니다. +- **publisher가 abstract를 공개 API에 제공하지 않는 논문** — CrossRef나 + OpenAlex에서 abstract가 없으면 `UNVERIFIABLE`입니다. 이는 claim 판단이 + 아니라 접근 가능성 판단입니다. +- **복잡한 통계 지표** — p-value, AUC/AUROC, F1, hazard/odds ratio, + confidence interval은 CLI가 아니라 수동 skill protocol 범위입니다. +- **논문 품질, novelty, 학계 consensus**, 또는 논문 전체가 넓은 주장을 + 뒷받침하는지 여부. + +에이전트 스킬의 수동 Full Audit 프로토콜은 mechanism, implementation, +procedure claim에 대해 선택적 CLI보다 더 깊게 확인합니다. 이 경우 +full-text passage를 직접 가져와야 하며, 본문에 접근하지 못하면 abstract의 +주제 일치만으로 `ACCEPT`하지 않고 `WARN (ABSTRACT-ONLY)`로 남깁니다. + +**CLI 주장 판정 읽는 법** + +| Verdict | 의미 | +|---|---| +| `ACCEPT` | 가져온 abstract가 claim을 명시적으로 뒷받침함. 높은 확신의 통과. | +| `WARN` / `PARTIAL` | abstract는 읽었지만 정확한 claim을 명시적으로 뒷받침하지 않음. 원문 확인 필요. | +| `UNVERIFIABLE` | 확인할 abstract에 접근하지 못함. claim 자체가 틀렸다는 뜻은 아님. | +| `REJECT` | DOI가 죽었거나, 다른 논문으로 연결되거나, 모순/철회 근거가 있음. | + +> 핵심 규칙: 논문 내용에 대한 모든 설명은 claim에 필요한 깊이의 +> live-fetched source에서 나와야 합니다. topline claim에는 abstract, +> mechanism, implementation, procedure claim에는 full text가 필요합니다. +> 필요한 source에 접근하지 못하면 그 사실을 밝히고 기억으로 빈칸을 +> 채우지 않습니다. + +--- + +## 잡아내는 문제 + +| 문제 | ref-verify 없이 생기는 일 | +|---|---| +| **잘못된 DOI** | 그럴듯한 DOI가 완전히 다른 논문으로 연결됨 | +| **잘못된 저자** | "Smith et al. (2020)"라고 썼지만 CrossRef에는 단독 저자로 등록됨 | +| **잘못된 연도** | 실제 출판 연도는 2008년인데 초안에는 2011년으로 들어감 | +| **지어낸 내용** | abstract에 없는 결과를 논문이 보여준 것처럼 설명함 | +| **Near-miss citation** | 필요한 숫자는 나오지만 맥락이 다름 | +| **철회 논문** | DOI는 유효하지만 논문이 철회됨 | + +--- + +## 예시 + +**이미 가진 인용 확인** + +```text +사용자: "제출 전에 이 인용 3개를 검증해줘" + +Shahinpoor & Kim (2001) 10.1088/0964-1726/10/4/327 - PASS +Bar-Cohen (2004) 10.1117/3.547465 - WARN (listed as author; CrossRef: editor) +Carpi et al. (2011) 10.1016/B978-0-08-047488-5.00001-0 - REJECT +``` + +**특정 주장 확인** + +```text +사용자: "Pelrine 2000 논문이 DEA가 100% 넘는 strain에 도달한다고 실제로 말해?" + +내용: 뒷받침됨 +"Actuated strains up to 117% were demonstrated with silicone elastomers, +and up to 215% with acrylic elastomers." +[출처: CrossRef raw JSON, 기억에서 가져온 설명 아님] +``` + +**Near-miss 인용** + +후보 논문 abstract에 "500% strain"이 들어 있어도, 그 숫자가 actuation +result가 아니라 pre-strain condition일 수 있습니다. `ref-verify`는 이런 +경우 citation을 받아들이지 않고 `WARN (PARTIAL)`로 표시합니다. + +--- + +## 개발과 릴리스 점검 + +개발 중 테스트는 다음으로 실행합니다. + +```bash +PYTHONPATH=src python3 -m unittest discover -s tests -v +``` + +릴리즈 안전성 검사는 Python 패키지를 빌드하고, 메타데이터를 확인하고, +빌드된 wheel을 새 virtualenv에 설치해 본 뒤 배포 경로를 확인합니다. 공개 +학술 API를 실제로 호출하는 live check는 수동 GitHub Actions workflow로 +분리되어 있어, 일반 CI가 upstream API 일시 장애 때문에 실패하지 않습니다. + +참고문헌 점검 벤치마크와 재실행 방법은 +[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md)(영어)에 있습니다. diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md new file mode 100644 index 0000000..109c825 --- /dev/null +++ b/docs/REFERENCE.md @@ -0,0 +1,492 @@ +# ref-verify reference + +[English](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md) | [한국어](https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md) + +This page holds the details behind the [README](https://github.com/Moonweave-Research/ref-verify/blob/main/README.md): +every command, mode, error code, the cache, and the limits of what is checked. It describes +ref-verify 1.3.1, the current release on PyPI. + +## Contents + +- [Agent skill](#agent-skill) +- [Checking a reference list (check-bib)](#checking-a-reference-list-check-bib) +- [Command-line engine](#command-line-engine) +- [Modes](#modes) +- [Error codes](#error-codes) +- [Saved reports](#saved-reports) +- [Cache](#cache) +- [Scope: optional CLI versus manual audit](#scope-optional-cli-versus-manual-audit) +- [What it catches](#what-it-catches) +- [Examples](#examples) +- [Development and release checks](#development-and-release-checks) + +--- + +## Agent skill + +`ref-verify` is an agent skill for citation verification. It helps Claude Code, +Cursor, Codex, and other skill-aware agents check references before they land in +your draft. Use it when you want an agent to find papers, verify a DOI, check whether +a paper supports a specific claim, or audit references before submission. + +### Install the skill + +```bash +# requires npx (comes with Node.js) +npx skills add Moonweave-Research/ref-verify -g \ + --skill ref-verify \ + --agent claude-code cursor codex \ + -y +``` + +Works with **Claude Code, Cursor, Codex**, and any agent that supports the +`npx skills` ecosystem. + +After installation, use it like a normal agent skill. You do not start a server and you do not configure MCP for this workflow. No MCP server is required for this workflow. + +The skill includes its own copy of the CLI engine, and the agent runs it from the skill folder, so nothing else needs to be installed. Python 3.10 or newer must be available as `python3`. + +For explicit agent tool-calling rules, see [AGENT_USAGE.md](https://github.com/Moonweave-Research/ref-verify/blob/main/AGENT_USAGE.md). + +### What to ask + +Ask naturally: + +```text +verify these citations before I submit: [DOI list] +does this paper actually support the claim "actuation strain above 100%"? +find 3 papers supporting the claim that X, and verify each citation +check doi 10.1126/science.287.5454.836 against this title and year +audit all my references before submission +check every reference in references.bib with ref-verify +``` + +`ref-verify` stays quiet for general topic questions, prose editing, APA/IEEE +formatting, and citation style questions. + +--- + +## Checking a reference list (check-bib) + +Find references in a paper or thesis that do not exist (for example ones a +chatbot made up), whose DOI points to a different paper, or that were +retracted, in one run. + +**With the agent:** after installing the skill, ask "check every reference in +references.bib with ref-verify". + +**From a terminal:** + +1. Put the list in a file. + - Zotero: right-click the collection → Export Collection → BibTeX → + `references.bib` (EndNote and Mendeley export BibTeX or RIS). + - A Word or other manuscript: copy the reference list into a plain-text + editor and save it as `references.txt`. `[1]` or `1.` numbering and + wrapped lines are fine. `.docx` and `.pdf` files are not read directly. +2. Install (Python 3.10 or newer): + + ```bash + pipx install ref-verify + ``` + + With `uv`, skip the install: + `uvx ref-verify check-bib references.bib`. + +3. Run: + + ```bash + ref-verify check-bib references.bib + ref-verify check-bib references.ris --json + ref-verify check-bib references.md --format txt + ``` + + A first run takes about a second per reference (a little over two minutes + for 150, with a `Checking references: 37/150` counter). Running the same + list again takes seconds thanks to the cache. Prefixing + `REF_VERIFY_MAILTO=you@university.edu` uses CrossRef's polite pool and is + about three times faster. + + Add `--report check.html` for a file to send to an advisor or co-author; + it opens in a browser with the items that need a look at the top. + +### How check-bib matches a reference + +It reads BibTeX, RIS, and plain-text or Markdown lists (one reference per +paragraph, per line, or per `[1]`/`1.`/`1)` item). A reference with a DOI is +compared with its CrossRef record like `verify-doi`; a plain-text reference +passes only when its text shows the CrossRef title and first author. A +reference without a DOI is looked up with CrossRef bibliographic search and +accepted only when the title matches and the year is within one. Matching +accepts the print or the online-first year, a title with or without its +subtitle or edition note, TeX math in BibTeX titles (`$\beta$` reads as β), +CrossRef's original-language title (for example the Korean title of +a *Polymer Korea* paper), and Hangul author names against CrossRef's +romanized ones (윤 → Yoon/Yun). When a DOI is unknown to CrossRef, doi.org is +asked which agency registered it, so arXiv, Zenodo, or KISTI DOIs are not +reported as dead. Search results that are about the paper rather than the +paper itself (peer-review reports, Faculty Opinions recommendations, +addenda and corrections) are skipped. + +A citation in a style that omits the article title (`J. Bardeen, L. N. Cooper, +and J. R. Schrieffer, Phys. Rev. 108, 1175 (1957)`) is compared on journal (full +name or abbreviation), volume, first page or article number, year, and first +author; it passes when all of them agree, and otherwise the reason names each +field that differs. Without a DOI, when the plain search finds nothing and the +citation looks title-less, a second CrossRef search by first author, the rest of +the citation, and the cited year finds short citations such as +`A. G. Riess et al., Astron. J. 116, 1009 (1998).`; the same full agreement is +required. The first author is read only from the first name in the list, so a +reference that puts a co-author first does not pass. + +### Output + +The terminal output starts with a count line +(`19 references: 11 PASS, 2 WARN, 5 REJECT, 1 UNVERIFIED`), lists one row per +reference (citation key, or the start of the reference for a pasted list), puts +the reason under every row that is not `PASS`, and ends with a one-paragraph +legend. With `--json` it is an object with `summary` (`total`, `pass`, `warn`, +`reject`, `unverified`, `failed`; `warn` includes the `UNVERIFIED` rows) and +`results`. `check-bib` exits `0` only when every reference is `PASS`. + +### Reading a check-bib result + +| Result | Meaning | What to do | +|---|---|---| +| `PASS` | Title, first author, and year match the CrossRef record for the DOI (or the record found by search) | Nothing | +| `WARN` | Found, but something differs; the line below says what (year, author, the title of the paper the DOI really points to) | Compare that one with the source | +| `REJECT` | The DOI exists nowhere, points to a different paper, or the paper is retracted | Fix or drop the citation | +| `UNVERIFIED` | Could not be confirmed automatically; theses, local conference abstracts, some books, and DOIs registered outside CrossRef (arXiv, KISTI) often land here. It does not mean the reference is wrong | Check it yourself | + +A made-up reference without a DOI can only show as `UNVERIFIED`, not `REJECT`, +so look each `UNVERIFIED` item up once (for example in Google Scholar). + +--- + +## Command-line engine + +The skill is the agent workflow. The Python CLI is the skill-level execution engine that the installed skill can call from a terminal. + +The Python package is CLI-only. It does not install `SKILL.md`; install the agent skill from GitHub with `npx skills add` as shown above. + +This is a skill/plugin-level workflow, not an MCP server. The CLI covers the +checks that are currently safe to automate directly: + +- CrossRef metadata check: `ref-verify verify-doi` +- DOI-bound abstract claim check: `ref-verify check-claim` +- Batch DOI-bound claim checks: `ref-verify check-file` + - literal text claims + - subject-matched percentage claims such as efficiency, response rate, or actuation strain + - simple unit/count claims such as cycles, patients, voltage, temperature, and concentration + - CrossRef first, then DOI-bound OpenAlex, Semantic Scholar, and PubMed fallback when CrossRef has no abstract +- Reference-list check (BibTeX, RIS, plain text, Markdown): `ref-verify check-bib` +- JSON output for agent-readable routing +- Non-zero exit codes for `WARN`, `REJECT`, and `UNVERIFIABLE` results + +Statistical metrics such as p-values, AUC/AUROC, F1 score, hazard ratio, odds ratio, and confidence intervals still use the manual skill protocol. DOI landing-page checks still use the skill protocol. The CLI rejects a DOI that CrossRef records as retracted (via its retraction notice); retraction banners CrossRef does not know about, Unpaywall, arXiv, and two-source existence checks remain in the `SKILL.md` protocol. + +The CLI has zero third-party Python runtime dependencies, but it is not an +offline verifier. Functional checks require outbound HTTPS access to public +academic APIs such as CrossRef, OpenAlex, Semantic Scholar, and PubMed. + +### Installing the CLI + +To run the CLI yourself, install it from PyPI: + +```bash +uvx ref-verify --help # run without installing (uv) +pipx install ref-verify # or install the `ref-verify` command +``` + +Or install it from a local checkout: + +```bash +git clone https://github.com/Moonweave-Research/ref-verify.git +cd ref-verify +python3 -m pip install -e . +``` + +Check whether the CLI is available: + +```bash +ref-verify --help +``` + +If you are working from an uninstalled source checkout, use the module +entrypoint: + +```bash +PYTHONPATH=src python3 -m ref_verify.cli --help +``` + +Run a DOI metadata check: + +```bash +ref-verify verify-doi 10.1126/science.287.5454.836 \ + --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ + --first-author Pelrine \ + --year 2000 \ + --json +``` + +Run a DOI-bound abstract claim check: + +```bash +ref-verify check-claim 10.1126/science.287.5454.836 \ + --claim "actuation strain above 100%" \ + --json +``` + +By default, `check-claim` uses CrossRef first. If CrossRef has no abstract, it tries DOI-bound OpenAlex, Semantic Scholar, and PubMed fallback sources. Use `--source crossref`, `--source openalex`, `--source semantic-scholar`, or `--source pubmed` for source-specific debugging; explicit non-CrossRef source selection bypasses CrossRef. + +Source-checkout equivalents: + +```bash +PYTHONPATH=src python3 -m ref_verify.cli verify-doi 10.1126/science.287.5454.836 \ + --title "High-Speed Electrically Actuated Elastomers with Strain Greater Than 100%" \ + --first-author Pelrine \ + --year 2000 \ + --json + +PYTHONPATH=src python3 -m ref_verify.cli check-claim 10.1126/science.287.5454.836 \ + --claim "actuation strain above 100%" \ + --json +``` + +--- + +## Modes + +**Quick Screen** is for DOIs you already have. It uses CrossRef to compare the +provided DOI, title, first-author surname, and year. + +```bash +ref-verify verify-doi <doi> --title "<title>" --first-author <last-name> --year <year> --json +``` + +`verify-doi` exits `0` only for `PASS`. `WARN` and `REJECT` return a non-zero +exit code, so weak or mismatched metadata cannot silently pass automation gates. + +**Full Audit** is for literature search and final pre-submission review. The +skill fetches abstracts through CrossRef, OpenAlex, Semantic Scholar, Unpaywall, +arXiv, and PubMed where needed. For a topline claim, it checks the abstract; for +a mechanism, implementation, or procedural claim, it continues to a fetched +full-text passage before assigning support. + +For a single DOI-backed claim, the CLI can run the abstract check: + +```bash +ref-verify check-claim <doi> --claim "<specific claim>" --json +``` + +`check-claim` exits `0` only for `ACCEPT`. `WARN`, `PARTIAL`, and +`UNVERIFIABLE` return a non-zero exit code. JSON output includes +`abstract_source`, `source_attempts`, and `error_code` so agents can distinguish +missing abstracts, source failures, DOI mismatches, and ambiguous evidence. + +Use `check-file` when a draft, literature note, or AI-agent output has many +DOI/claim pairs. + +JSONL: + +```bash +ref-verify check-file claims.jsonl +ref-verify check-file claims.jsonl --json +``` + +CSV: + +```bash +ref-verify check-file claims.csv +``` + +Each row must include `doi` and `claim`. Optional fields are `id`, `source`, +and `note`. Rows are checked 4 at a time by default (`--workers N`); output keeps +the input order, and CrossRef and Semantic Scholar requests go one at a time +because their public APIs reject parallel requests. In a terminal, a +`Checking claims: N/M` counter on stderr shows progress (never with `--json`). +Ctrl-C stops the run; finished lookups stay cached, so rerunning resumes quickly. Batch mode reuses the same conservative `check-claim` engine: +`ACCEPT` means the abstract explicitly supports the numeric claim. `WARN`, +`PARTIAL`, `REJECT`, or `UNVERIFIABLE` means the claim should not be treated as +verified. + +Use `check-bib` when you have a reference list rather than DOI/claim pairs; see +[Checking a reference list](#checking-a-reference-list-check-bib). + +--- + +## Error codes + +Current `check-claim` error codes: + +- `CLAIM_SUPPORTED`: explicit abstract support found. +- `CLAIM_NOT_EXPLICIT`: an abstract was available, but the claim was not explicitly supported. +- `CLAIM_AMBIGUOUS`: numeric evidence or context exists, but binding is ambiguous. +- `NO_ABSTRACT`: attempted DOI-bound sources did not provide abstract text. +- `DOI_NOT_FOUND`: neither CrossRef nor doi.org knows the DOI, or the selected source did not find a DOI-bound record. The JSON still carries a `verdict` of `REJECT`. +- `DOI_NOT_IN_CROSSREF`: CrossRef has no record, but doi.org lists the DOI with another agency (DataCite for arXiv and Zenodo, KISTI, JaLC, ...). `verify-doi` returns `verdict: WARN`, `status: UNVERIFIED` without comparing metadata; `check-claim` tries OpenAlex, Semantic Scholar (arXiv DOIs by arXiv identifier), and PubMed for the abstract and judges the claim if one has it, otherwise returns `status: UNVERIFIABLE`, `verdict: WARN` with this code. Not a dead DOI. +- `PAPER_RETRACTED`: CrossRef lists a retraction notice for the DOI; the claim is rejected before any abstract is read. +- `DOI_MISMATCH`: the primary or explicitly selected DOI-bound record did not match the requested DOI. +- `SOURCE_API_ERROR`, `SOURCE_TIMEOUT`, `SOURCE_RATE_LIMITED`, `SOURCE_UNSUPPORTED`: source lookup failed, timed out, was rate-limited, or could not be used. + +`check-file` uses the same codes per row, plus `ROW_CHECK_ERROR` when one row could not be checked. + +`check-bib` error codes: + +- `REFERENCE_RESOLVED`: the reference had no DOI; CrossRef search found a matching record, reported as `resolved_doi`. `WARN` when the year differs by one or the first author differs. +- `REFERENCE_UNMATCHED`: the reference had no DOI and no CrossRef record matched (`status: UNVERIFIED`, `verdict: WARN`). The tool could not confirm it automatically; that does not mean the reference is wrong. Verify it manually. +- `DOI_NOT_IN_CROSSREF`: the DOI is registered with another agency (DataCite for arXiv and Zenodo, KISTI, JaLC, ...), so its metadata was not compared (`status: UNVERIFIED`, `verdict: WARN`). Open the DOI to confirm it. +- `DOI_NOT_FOUND`: neither CrossRef nor doi.org knows the DOI (`REJECT`). +- `PAPER_RETRACTED`, `ROW_CHECK_ERROR`: as for `check-claim` and `check-file`. Other DOI-backed results carry `error_code: null`; read `verdict`, `mismatches`, and `reason`, which names what differs (for example `the year differs (reference: 2009; CrossRef: 2010)`). A plain-text reference whose DOI belongs to a paper it does not mention is `status: MISMATCH`, `verdict: WARN`, with that paper's title in `reason`. + +--- + +## Saved reports + +To hand the result to a co-author or supervisor, add `--report` to `check-bib` +or `check-file`. The file extension picks the format: + +```bash +ref-verify check-bib references.bib --report report.html +ref-verify check-file claims.jsonl --report report.md +``` + +The HTML file is self-contained (inline CSS, no scripts, no external resources +other than `https://doi.org/` links). It opens with counts that add up to the +total (one box per verdict as shown), a plain-language line on what each +verdict means and asks you to do, then a "Needs a look" table with every +non-passing reference or claim and a "Passed" table below it. Each row is +coloured (`PASS`/`ACCEPT` green, `WARN` amber, `REJECT` red, `UNVERIFIED` +grey) and shows the reason and evidence. `UNVERIFIED` marks a result the tool +could not confirm automatically; it is not a finding that the reference is +wrong. The Markdown file has the same content. A `--report` path whose folder +does not exist is rejected before any lookup, so a long run is never lost. +`--json` output is unchanged. + +--- + +## Cache + +The CLI keeps API responses on disk for 7 days, so re-running a check does not +query CrossRef and the abstract sources again. A DOI that returned HTTP 404 is +kept for 1 day only, so a newly registered DOI is re-checked soon. Rate limits +(429) and server errors (5xx) are retried up to 3 times with backoff, honouring +`Retry-After` up to 10 s, and are never cached. + +- Location: `$REF_VERIFY_CACHE_DIR`, else `$XDG_CACHE_HOME/ref-verify`, else `~/.cache/ref-verify`. +- Lifetime: `REF_VERIFY_CACHE_TTL_DAYS` (default `7`). +- Disable: `--no-cache` on any command, or `REF_VERIFY_NO_CACHE=1`. Delete the directory to clear it. +- Contents: one JSON file per request, holding the request URL, the HTTP status, the time it + was stored, and the response body. The URL carries what was asked: a DOI, or for a reference + without a DOI the reference text sent to CrossRef search (and your `REF_VERIFY_MAILTO` + address, if set). + +--- + +## Scope: optional CLI versus manual audit + +`ref-verify` is a conservative guard, not an oracle. It errs toward flagging: an +`ACCEPT` is high-confidence, and **anything else means "not auto-verifiable — +check it yourself", not "the citation is wrong."** + +**The optional CLI verifies** + +- DOI metadata: title, first-author surname, and year against CrossRef. +- Whether a DOI-bound **abstract** explicitly supports a specific numeric or + literal claim, quoted verbatim. If no abstract is reachable, it returns + `UNVERIFIABLE` rather than guessing. + +**The optional CLI does not verify** (out of scope by design, not bugs) + +- **Full-text, figure, table, or supplementary values** — abstract-only. A number + that appears only in the body stays `UNVERIFIABLE`. +- **Relational or qualitative claims** — proportionalities, mechanisms, + "broader/stronger than". Only value+unit and literal claims are checked. +- **Papers whose publisher withholds the abstract** — some titles expose no + abstract to CrossRef or OpenAlex. No abstract → `UNVERIFIABLE`, which reflects + reachability, not the claim. +- **Statistical metrics** (p-value, AUC/AUROC, F1, hazard/odds ratio, confidence + intervals) — handled by the manual skill protocol, not the CLI. +- **Paper quality, novelty, field consensus**, or whether the *full* paper + supports a broader statement. + +The agent skill's manual Full Audit protocol goes beyond the optional CLI for +mechanism, implementation, and procedural claims. It requires a fetched +full-text passage at that source depth; when full text is unavailable, it +returns `WARN (ABSTRACT-ONLY)` instead of upgrading an abstract topic match to +`ACCEPT`. + +**Reading a CLI claim verdict** + +| Verdict | Meaning | +|---|---| +| `ACCEPT` | The fetched abstract explicitly supports the claim. High-confidence pass. | +| `WARN` / `PARTIAL` | An abstract was read but does not explicitly support the exact claim. Check the source. | +| `UNVERIFIABLE` | No abstract was reachable to check against. Not a judgment on the claim. | +| `REJECT` | DOI is dead, resolves to a different paper, contradicted, or retracted. | + +> Core rule: every content statement about a paper must come from a live-fetched +> source at the depth the claim requires — abstract for topline claims, full +> text for mechanism, implementation, or procedural claims. If the required +> source is inaccessible, say so. Do not fill the gap from memory. + +--- + +## What it catches + +| Problem | What happens without ref-verify | +|---|---| +| **Wrong DOI** | An agent lists a plausible DOI that resolves to a different paper | +| **Wrong authors** | A citation says "Smith et al. (2020)", but CrossRef shows one author | +| **Wrong year** | The paper was published in 2008, but the draft says 2011 | +| **Made-up content** | The draft says a paper shows a result that is not in the abstract | +| **Near-miss citation** | The right number appears, but in the wrong context | +| **Retracted paper** | The DOI is valid, but the paper was retracted | + +--- + +## Examples + +**Checking citations you already have** + +```text +User: "verify these 3 citations before I submit" + +Shahinpoor & Kim (2001) 10.1088/0964-1726/10/4/327 - PASS +Bar-Cohen (2004) 10.1117/3.547465 - WARN (listed as author; CrossRef: editor) +Carpi et al. (2011) 10.1016/B978-0-08-047488-5.00001-0 - REJECT +``` + +**Checking a specific claim** + +```text +User: "does the Pelrine 2000 paper actually say DEAs reach over 100% strain?" + +CONTENT: Supported +"Actuated strains up to 117% were demonstrated with silicone elastomers, +and up to 215% with acrylic elastomers." +[Source: CrossRef raw JSON, not recalled from memory] +``` + +**Near-miss citation** + +A candidate paper may contain "500% strain", but the abstract can show that the +number is a pre-strain condition, not an actuation result. `ref-verify` reports +that as `WARN (PARTIAL)` instead of accepting the citation. + +--- + +## Development and release checks + +For local development, run: + +```bash +PYTHONPATH=src python3 -m unittest discover -s tests -v +``` + +Release safety checks also build the Python package, validate metadata, and +install the built wheel in a fresh virtualenv before publishing. Live checks +against public academic APIs are kept in a manual GitHub Actions workflow so +normal CI does not fail because an upstream API is temporarily unavailable. + +The reference-checking benchmark and how to rerun it are described in +[benchmarks/README.md](https://github.com/Moonweave-Research/ref-verify/blob/main/benchmarks/README.md). diff --git a/examples/references.txt b/examples/references.txt new file mode 100644 index 0000000..2c1d0c4 --- /dev/null +++ b/examples/references.txt @@ -0,0 +1,24 @@ +# A small reference list for trying ref-verify. The comment above each reference +# says what it really is, so you can compare that with the verdict. +# Lines starting with # are ignored by `ref-verify check-bib`. + +# Real paper, with a DOI. +Pelrine, R., Kornbluh, R., Pei, Q., & Joseph, J. (2000). High-speed electrically actuated elastomers with strain greater than 100%. Science, 287(5454), 836–839. https://doi.org/10.1126/science.287.5454.836 + +# Real paper, without a DOI (ref-verify finds it by searching CrossRef). +Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of 'small-world' networks. Nature, 393(6684), 440–442. + +# Real Korean paper, cited by its Korean title, with a DOI. +김가은 (2024). IPA 분석을 통한 프로스포츠 관람 제약 요인 개선 방안 연구. 한국콘텐츠학회논문지, 24(9), 361-379. https://doi.org/10.5392/jkca.2024.24.09.361 + +# Real paper, but the year is wrong: published in 1997, cited here as 1999. +Hochreiter, S., & Schmidhuber, J. (1999). Long short-term memory. Neural Computation, 9(8), 1735–1780. https://doi.org/10.1162/neco.1997.9.8.1735 + +# Made up: a plausible title and real authors, but this paper and its DOI do not exist. +Pelrine, R., Kornbluh, R., & Pei, Q. (2003). Dielectric elastomer actuators with self-healing compliant electrodes for soft robotic grippers. Advanced Materials, 15(9), 765–770. https://doi.org/10.1002/adma.200390974 + +# Retracted: Wakefield et al. 1998, retracted by The Lancet in 2010. +Wakefield, A. J., Murch, S. H., Anthony, A., et al. (1998). Ileal-lymphoid-nodular hyperplasia, non-specific colitis, and pervasive developmental disorder in children. The Lancet, 351(9103), 637–641. https://doi.org/10.1016/S0140-6736(97)11096-0 + +# Real Korean doctoral thesis that CrossRef does not index, so it cannot be confirmed automatically. +강동휘 (2025). 폴리(비닐피리딘) 기반 블록 공중합체로부터 형성된 나노구조체 [박사학위논문, 서울대학교 대학원]. diff --git a/tests/test_skill_docs.py b/tests/test_skill_docs.py index 5953653..ee5046f 100644 --- a/tests/test_skill_docs.py +++ b/tests/test_skill_docs.py @@ -29,6 +29,18 @@ ) CHECK_BIB_ERROR_CODES = ("REFERENCE_RESOLVED", "REFERENCE_UNMATCHED", "DOI_NOT_IN_CROSSREF") AGENT_USAGE_ERROR_CODES = CHECK_CLAIM_ERROR_CODES + ("ROW_CHECK_ERROR",) + CHECK_BIB_ERROR_CODES +GITHUB_REFERENCE_URL = "https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.md" +GITHUB_KOREAN_REFERENCE_URL = "https://github.com/Moonweave-Research/ref-verify/blob/main/docs/REFERENCE.ko.md" + + +def _user_docs(korean=False): + # The README keeps the story; commands, modes, error codes, cache, and scope live in + # docs/REFERENCE*.md. Facts the README used to carry must still be in one of the two. + readme = "README.ko.md" if korean else "README.md" + reference = "REFERENCE.ko.md" if korean else "REFERENCE.md" + return (REPO_ROOT / readme).read_text(encoding="utf-8") + "\n" + (REPO_ROOT / "docs" / reference).read_text( + encoding="utf-8" + ) class SkillDocsTests(unittest.TestCase): @@ -97,23 +109,27 @@ def test_skill_requires_source_depth_for_mechanism_claims(self): self.assertNotIn("/Users/", skill) self.assertNotIn("verify@ref-verify.local", skill) - self.assertIn("manual Full Audit protocol", readme) - self.assertIn("수동 Full Audit 프로토콜", readme_ko) + self.assertIn("manual Full Audit protocol", _user_docs()) + self.assertIn("수동 Full Audit 프로토콜", _user_docs(korean=True)) def test_readme_positions_cli_as_skill_execution_engine_not_mcp(self): - readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") - - self.assertIn(f"[English]({GITHUB_README_URL})", readme) - self.assertIn(f"[한국어]({GITHUB_KOREAN_README_URL})", readme) - self.assertIn(RAW_MARK_URL, readme) - self.assertIn("agent skill for citation verification", readme) - self.assertIn("--skill ref-verify", readme) - self.assertIn("--agent claude-code cursor codex", readme) + front = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + readme = _user_docs() + + self.assertIn(f"[English]({GITHUB_README_URL})", front) + self.assertIn(f"[한국어]({GITHUB_KOREAN_README_URL})", front) + self.assertIn(RAW_MARK_URL, front) + self.assertIn("agent skill for citation verification", front) + self.assertIn("--skill ref-verify", front) + self.assertIn("--agent claude-code cursor codex", front) + self.assertIn(f"[AGENT_USAGE.md]({GITHUB_AGENT_USAGE_URL})", front) + self.assertIn(f"]({GITHUB_REFERENCE_URL}", front) + self.assertIn("ref-verify check-bib", front) + self.assertIn("--report report.html", front) self.assertIn("skill/plugin-level", readme) self.assertIn("skill-level execution engine", readme) self.assertIn("No MCP server is required for this workflow", readme) self.assertIn("You do not start a server and you do not configure MCP", readme) - self.assertIn(f"[AGENT_USAGE.md]({GITHUB_AGENT_USAGE_URL})", readme) self.assertIn("DOI-bound abstract claim check", readme) self.assertIn("OpenAlex, Semantic Scholar, and PubMed fallback", readme) self.assertIn("Current `check-claim` error codes", readme) @@ -134,17 +150,23 @@ def test_readme_positions_cli_as_skill_execution_engine_not_mcp(self): self.assertNotIn("future MCP", readme) def test_korean_readme_matches_current_workflow_positioning(self): - readme_ko = (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8") - - self.assertIn(f"[한국어]({GITHUB_KOREAN_README_URL})", readme_ko) - self.assertIn(f"[English]({GITHUB_README_URL})", readme_ko) - self.assertIn(RAW_MARK_URL, readme_ko) - self.assertIn("연구 인용 검증용 에이전트 스킬", readme_ko) - self.assertIn("--skill ref-verify", readme_ko) - self.assertIn("--agent claude-code cursor codex", readme_ko) + front_ko = (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8") + readme_ko = _user_docs(korean=True) + + self.assertIn(f"[한국어]({GITHUB_KOREAN_README_URL})", front_ko) + self.assertIn(f"[English]({GITHUB_README_URL})", front_ko) + self.assertIn(RAW_MARK_URL, front_ko) + self.assertIn("연구 인용 검증용 에이전트 스킬", front_ko) + self.assertIn("--skill ref-verify", front_ko) + self.assertIn("--agent claude-code cursor codex", front_ko) + self.assertIn(f"[AGENT_USAGE.md]({GITHUB_AGENT_USAGE_URL})", front_ko) + self.assertIn(f"]({GITHUB_KOREAN_REFERENCE_URL}", front_ko) + self.assertIn("ref-verify check-bib", front_ko) + self.assertIn("--report report.html", front_ko) + self.assertIn("README.ko.md", front_ko) + self.assertIn("SKILL.md", front_ko) self.assertIn("스킬/플러그인 수준", readme_ko) self.assertIn("MCP 서버가 필요하지 않습니다", readme_ko) - self.assertIn(f"[AGENT_USAGE.md]({GITHUB_AGENT_USAGE_URL})", readme_ko) self.assertIn("문장 그대로 드러나는 text claim", readme_ko) self.assertIn("subject가 일치하는 percentage claim", readme_ko) self.assertIn("단순 unit/count claim", readme_ko) @@ -159,8 +181,6 @@ def test_korean_readme_matches_current_workflow_positioning(self): self.assertIn("--report report.html", readme_ko) self.assertIn("ref-verify verify-doi", readme_ko) self.assertIn("ref-verify check-claim", readme_ko) - self.assertIn("README.ko.md", readme_ko) - self.assertIn("SKILL.md", readme_ko) def test_packaged_readme_uses_publish_safe_language_links(self): pyproject = (REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8") @@ -268,36 +288,61 @@ def test_bundled_engine_runs_from_another_working_directory(self): def test_readmes_prioritize_user_workflow_before_architecture_details(self): readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") readme_ko = (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8") + reference = (REPO_ROOT / "docs" / "REFERENCE.md").read_text(encoding="utf-8") + reference_ko = (REPO_ROOT / "docs" / "REFERENCE.ko.md").read_text(encoding="utf-8") self.assertInOrder( readme, ( - "## Install the skill", - "## Use it", - "## Optional CLI engine", - "## What it catches", - "## Modes", - "## Examples", + "## Try it in 30 seconds", + "## How to read a result", + "## Three ways to use it", + "## Scorecard", + "## What it checks and what it does not", + "## Privacy and network use", + "## For AI agents", + "## More documentation", + "## Contributing, license, and related projects", ), ) self.assertInOrder( readme_ko, ( - "## 스킬 설치", - "## 사용 방법", - "## 선택적 CLI 엔진", - "## 잡아내는 문제", - "## 모드", - "## 예시", + "## 30초 만에 써 보기", + "## 결과 읽는 법", + "## 세 가지 사용 방법", + "## 채점표", + "## 확인하는 것과 확인하지 않는 것", + "## 개인정보와 네트워크", + "## AI 에이전트를 위한 안내", + "## 더 자세한 문서", + "## 기여, 라이선스, 관련 프로젝트", ), ) + # Reference material stays in the reference documents, in the same order in both languages. + self.assertInOrder( + reference, + ("## Agent skill", "## Checking a reference list", "## Command-line engine", "## Modes", + "## Error codes", "## Saved reports", "## Cache", "## Scope", "## What it catches", "## Examples"), + ) + self.assertInOrder( + reference_ko, + ("## 에이전트 스킬", "## 참고문헌 목록 점검", "## 명령줄 엔진", "## 모드", + "## 오류 코드", "## 저장한 보고서", "## 캐시", "## 범위", "## 잡아내는 문제", "## 예시"), + ) + # Both languages keep the same section structure so a reader can switch. + for english, korean in ((readme, readme_ko), (reference, reference_ko)): + self.assertEqual( + [line.split()[0] for line in english.splitlines() if line.startswith("#")], + [line.split()[0] for line in korean.splitlines() if line.startswith("#")], + ) def test_readmes_avoid_cli_scope_contradictions_and_internal_first_language(self): - readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") - readme_ko = (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8") + readme = _user_docs() + readme_ko = _user_docs(korean=True) - self.assertIn("No server setup is required", readme) - self.assertIn("서버를 시작하거나 MCP를 설정할 필요가 없습니다", readme_ko) + self.assertIn("No server setup is required", (REPO_ROOT / "README.md").read_text(encoding="utf-8")) + self.assertIn("서버를 시작하거나 MCP를 설정할 필요가 없습니다", (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8")) self.assertIn("CrossRef metadata check", readme) self.assertIn("DOI-bound abstract claim check", readme) self.assertIn("CrossRef 메타데이터 확인", readme_ko) @@ -309,8 +354,8 @@ def test_readmes_avoid_cli_scope_contradictions_and_internal_first_language(self self.assertNotIn("논문이 실제로 말하지 않은 내용을 인용하지 않게 막습니다", readme_ko) def test_korean_readme_localizes_user_facing_examples(self): - readme_ko = (REPO_ROOT / "README.ko.md").read_text(encoding="utf-8") - examples = readme_ko.split("## 예시", 1)[1] + reference_ko = (REPO_ROOT / "docs" / "REFERENCE.ko.md").read_text(encoding="utf-8") + examples = reference_ko.split("## 예시", 1)[1] self.assertIn("사용자:", examples) self.assertIn("내용: 뒷받침됨", examples)