diff --git a/hallucheck-pro/.claude-plugin/plugin.json b/hallucheck-pro/.claude-plugin/plugin.json new file mode 100644 index 00000000..5ba9ec28 --- /dev/null +++ b/hallucheck-pro/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "hallucheck-pro", + "version": "0.3.0", + "description": "Verify academic references via CrossRef, OpenLibrary, and Google Books APIs. Extract references from argument trees, validate metadata live, and generate markdown hallucheck reports. ISBNs use OpenLibrary with automatic Google Books fallback.", + "author": { + "name": "Roland", + "url": "https://github.com/luctaesch" + }, + "keywords": ["academic", "references", "verification", "CrossRef", "OpenLibrary", "GoogleBooks", "hallucheck", "citation"] +} diff --git a/hallucheck-pro/.mcp.json b/hallucheck-pro/.mcp.json new file mode 100644 index 00000000..3f33e0d9 --- /dev/null +++ b/hallucheck-pro/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "hallucheck-verifier": { + "command": "python3", + "args": ["${CLAUDE_PLUGIN_ROOT}/mcps/hallucheck-verifier.py"], + "env": { + "PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}" + } + } + } +} diff --git a/hallucheck-pro/LICENSE b/hallucheck-pro/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/hallucheck-pro/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/hallucheck-pro/README.md b/hallucheck-pro/README.md new file mode 100644 index 00000000..b4243343 --- /dev/null +++ b/hallucheck-pro/README.md @@ -0,0 +1,272 @@ +# Hallucheck Pro + +**Verify academic references via CrossRef and OpenLibrary APIs.** + +A Claude Cowork plugin that extracts references from argument trees, validates them live against academic databases, and generates markdown hallucheck reports. + +--- + +## What It Does + +- **Reads** argument tree files from your vault (or wikilinks) +- **Extracts** all DOIs, ISBNs, and URLs +- **Verifies** each reference live via: + - **CrossRef API** for journal articles (DOIs) + - **OpenLibrary API** for books (ISBNs) + - **Direct fetch** for bare URLs +- **Compares** returned metadata (title, authors, year, journal, volume, issue, pages) against what you cited +- **Generates** a markdown hallucheck report with four verification sections: + 1. **Inference** — Do sub-arguments back main arguments? + 2. **Claims** — Do cited insights match the actual works? + 3. **Reference plausibility** (knowledge-based) + 4. **Live URL verification** (API-based) — the critical addition + +--- + +## Installation + +1. Download `hallucheck-pro.plugin` +2. Install it in Cowork (drag-and-drop or via plugin manager) +3. Restart Cowork +4. The `/hallucheck` command becomes available +5. **Run `/hallucheck-test` immediately after installing** — this checks which APIs are reachable in your environment and tells you exactly what will and won't work before you run a real verification. + +--- + +## Usage + +### Basic Command + +``` +/hallucheck path/to/argument-tree.md +/hallucheck [[my-argument-tree]] +``` + +Both path and wikilink formats work. + +### What Happens + +1. File is read from your vault +2. All references (DOIs, ISBNs, URLs) are extracted +3. Each is verified live via API (30–60 seconds depending on count) +4. A markdown report is generated and saved to `x/AI_output/` + +### Example Output + +```markdown +# Hallucheck Report — power of slowness-argument tree + +## Summary +- v Halluref (95%): Knowledge-based plausibility all checks pass +- v Halluclaim (100%): All insights match cited works +- v Halluinfer (100%): Argument chains solid +- ! Halluurl (88%): 1 DOI mismatch, 1 ISBN timeout + +## Inference Status +- v Arg 1 ← 1.1 + 1.2 both support "speed as concealment" +- v Arg 2 ← 2.1 + 2.2 back "speed = info, slowness = understanding" +... + +## Claim Status +- v 1.1 Hsee (2010) — "Idleness aversion" accurately represents the paper +- v 1.2 Hayes (1996) — Experiential avoidance concept accurate +... + +## Reference Plausibility (Knowledge-Based) +- v 1.1 Hsee, C. K., Yang, A. X., & Wang, L. (2010) — Format/metadata all consistent +- v 1.2 Hayes et al. (1996) — Authors, journal, year all plausible +... + +## Live URL Verification +- v 1.1 DOI resolves ✓ Title: "Idleness aversion and the need for justifiable busyness" +- v 1.2 DOI resolves ✓ Authors: Hayes, S.C., Wilson, K.G., Gifford, E.V., ... +- ! 4.2 Title mismatch — cited "NOMAD", returned "Novelty-related motivation..." +- ! 9.2 ISBN timeout — OpenLibrary API unreachable (retry later) +``` + +--- + +## Components + +### Commands + +- **`/hallucheck`** — Run verification on a file +- **`/hallucheck-test`** — API connectivity health check (run this first if you suspect network issues) + +### Skills + +- **`hallucheck-v4`** — Standalone skill documenting the hallucheck V4 methodology + - Use to understand the framework + - Reference for what each check does + - Load anytime, not just via `/hallucheck` + +### MCP Server + +- **`hallucheck-verifier`** — Python server that calls CrossRef and OpenLibrary APIs + - Runs automatically when `/hallucheck` is invoked + - Returns structured JSON with verification results + - Handles errors gracefully (continues even if one reference fails) + +--- + +## Output Format + +Reports are saved with versioning: +- First run: `hallucheck-{filename}-v1.md` +- Second run: `hallucheck-{filename}-v2.md` +- Etc. + +All reports go to `x/AI_output/` in your vault. + +--- + +## Network Requirements + +The plugin requires internet access to: +- `api.crossref.org` (CrossRef) +- `doi.org` (DOI resolution) +- `openlibrary.org` (OpenLibrary) +- `googleapis.com` (Google Books fallback) +- Any bare URLs cited (e.g., `hbr.org`) + +These are public, free APIs with no authentication required. + +### Cowork Domain Allowlist + +Claude's sandbox restricts outbound network access by default. You must add the required domains manually in: + +**Settings → Capabilities → Additional allowed domains** + +Add each of the following: + +``` +api.crossref.org +doi.org +openlibrary.org +googleapis.com +``` + +Add any other domains your cited URLs reference (e.g. `hbr.org`, `pubmed.ncbi.nlm.nih.gov`). + +> Without these entries, the `halluurl` phase will report all references as `⚠ Unverifiable (network)`. Run `/hallucheck-test` after adding the domains to confirm connectivity. + +--- + +## Interpreting Results + +### ✓ Green (Clean) + +All checks pass for that reference. Use it with confidence. + +### ! Orange (Flagged) + +One or more checks found an issue: + +| Flag | Meaning | Action | +|------|---------|--------| +| **Halluref issue** | DOI/ISBN format or metadata seems off (knowledge-based) | Review manually; might be an unusual format | +| **Halluclaim issue** | Cited insight may not match what the paper actually says | Check the paper; may need to revise the insight | +| **Halluinfer issue** | Sub-argument doesn't clearly support the main argument | Strengthen the logical chain | +| **Halluurl issue** | DOI/ISBN doesn't resolve OR metadata doesn't match | Critical: check if you cited the right paper | + +--- + +## Testing + +### /hallucheck-test + +Before running `/hallucheck` on a real document, you can verify that all three external API types are reachable: + +``` +/hallucheck-test +``` + +This runs against three fixed fixture references — one per API type — and reports PASS/FAIL: + +| # | API type | Fixture | +|---|----------|---------| +| 1 | CrossRef (DOI) | Hsee et al. (2010) — Psychological Science | +| 2a | OpenLibrary (ISBN — primary) | Piaget (1950) — The Psychology of Intelligence | +| 2b | Google Books (ISBN — fallback) | Same ISBN via googleapis.com (tested if 2a fails) | +| 3 | Bare URL (HBR) | Argyris (1977) — Double loop learning in organizations | + +Expected output if all APIs are up: + +``` +Result: 3/3 APIs reachable — halluurl phase OPERATIONAL ✅ +``` + +If you see `DEGRADED` or `FAIL`, your session network is blocking one or more external APIs. The main `/hallucheck` command will still run but will mark affected references as `Unverifiable (network)`. + +**Why these fixtures?** They are well-established references in stable databases, unlikely to be removed or modified. The Düzel 4.2 mismatch discovered during testing (title truncation) is intentionally excluded — test fixtures should be clean references that return exact matches. + +--- + +## Known Limitations + +1. **API rate limits** — CrossRef and OpenLibrary have rate limits. If you run hallucheck on 100+ references, you may hit limits (waits 1 sec between calls to avoid this) + +2. **Title matching is fuzzy** — If a title is truncated or abbreviated differently, it may flag as a mismatch even if it's the same paper. Manual review recommended. + +3. **Special characters** — Some DOIs with encoded characters may not resolve. Test first. + +4. **OpenLibrary ISBN gaps** — Not all ISBNs are in OpenLibrary. If a book is rare or very recent, it might timeout. + +--- + +## Troubleshooting + +### "DOI does not resolve" +- Check the DOI format (should be `10.xxxx/...`) +- Try the DOI manually: https://doi.org/10.1177/... +- Some DOIs may be temporarily unavailable + +### "ISBN not found" +- Verify the ISBN is correct (13 digits) +- Check on Amazon or OpenLibrary manually +- Rare/old books may not be indexed + +### "API unreachable" +- Check your internet connection +- If behind a corporate proxy, check network settings in Cowork +- CrossRef/OpenLibrary may be temporarily down + +### Command not found +- Restart Cowork after installing the plugin +- Verify plugin is listed in plugin manager + +--- + +## Building on This Plugin + +Hallucheck Pro is designed to be extended. Future versions could add: + +- Zotero integration (read references from Zotero library) +- Semantic Scholar API (additional verification source) +- PDF extraction (parse references from PDFs directly) +- Batch scheduling (hallucheck documents on a schedule) +- Report aggregation (summary across multiple documents) + +--- + +## License & Attribution + +**Hallucheck Pro** was created by Roland for personal use in verifying academic argument trees. + +**Methodology**: Hallucheck V4 extends Hallucheck V3 (knowledge-based verification) with live API verification via CrossRef and OpenLibrary. + +**APIs Used**: +- [CrossRef API](https://www.crossref.org/) — Journal article metadata +- [OpenLibrary API](https://openlibrary.org/developers/) — Book metadata (primary) +- [Google Books API](https://developers.google.com/books) — Book metadata (fallback, no auth required) + +--- + +## Support + +For issues or feature requests, open an issue on [GitHub](https://github.com/luctaesch/hallucheck-pro/issues). Contributions and PRs welcome. + +--- + +*Plugin version: 0.3.0* +*Created: 2026-02-28* diff --git a/hallucheck-pro/commands/hallucheck-test.md b/hallucheck-pro/commands/hallucheck-test.md new file mode 100644 index 00000000..58f18fae --- /dev/null +++ b/hallucheck-pro/commands/hallucheck-test.md @@ -0,0 +1,154 @@ +--- +name: hallucheck-test +description: Run a connectivity health check on all halluurl API types (CrossRef, OpenLibrary, Google Books fallback, bare URL). Use this before running /hallucheck on a real document to confirm which external APIs are reachable. +--- + +# /hallucheck-test + +Run a quick API connectivity test for the halluurl phase of Hallucheck Pro. + +**Run this immediately after installing the plugin** to know which APIs are reachable in your environment before running `/hallucheck` on a real document. + +--- + +## Step 1 — Network Domain Reachability + +Before testing the APIs, verify that each required domain is reachable. For each domain, attempt a basic HTTP request and report success or failure. + +| # | Domain | Used for | Required? | +|---|--------|----------|-----------| +| A | `api.crossref.org` | DOI verification (CrossRef) | Yes — DOIs unverifiable if blocked | +| B | `openlibrary.org` | ISBN verification (primary) | No — Google Books is the fallback | +| C | `www.googleapis.com` | ISBN verification (Google Books fallback) | No — OpenLibrary is the primary | +| D | `hbr.org` | Bare URL example (HBR) | No — only needed for HBR-cited articles | + +At least one of B or C must be reachable for ISBN verification to work. + +Output this section first: + +``` +## Network Domain Check + +| Domain | Status | Note | +|----------------------|-------------|-----------------------------------| +| api.crossref.org | ✅ Reachable | DOI verification available | +| openlibrary.org | ❌ Blocked | ISBN primary unavailable | +| www.googleapis.com | ✅ Reachable | Google Books fallback available | +| hbr.org | ✅ Reachable | Bare URL checks available | + +ISBN resolution: Google Books (fallback active — OpenLibrary blocked) +``` + +--- + +## Step 2 — API Fixture Tests + +After the domain check, test each API type against a fixed, known-stable fixture reference: + +| # | API type | Fixture reference | +|---|----------|------------------| +| 1 | CrossRef (DOI) | Hsee et al. (2010). *Psychological Science* 21(7). DOI: `10.1177/0956797610374738` | +| 2a | OpenLibrary (ISBN — primary) | Piaget (1950/2001). *The Psychology of Intelligence*. Routledge. ISBN: `978-0415254014` | +| 2b | Google Books (ISBN — fallback) | Same ISBN: `978-0415254014` via `googleapis.com/books/v1/volumes?q=isbn=...` | +| 3 | Bare URL (HBR) | Argyris (1977). *Harvard Business Review*. `hbr.org/1977/09/double-loop-learning-in-organizations` | + +**Instructions:** +1. For each fixture, fetch the API endpoint and compare returned metadata to the expected values below. +2. For ISBN: test OpenLibrary (2a) first; if it fails, test Google Books (2b) as fallback. +3. Report PASS or FAIL per test. For ISBN, report the source that succeeded (or both failures). +4. Do NOT save a report file — output results directly in chat. + +--- + +## Expected values (ground truth) + +### Fixture 1 — CrossRef DOI +- **URL:** `https://api.crossref.org/works/10.1177/0956797610374738` +- **Expected title:** "Idleness Aversion and the Need for Justifiable Busyness" (case-insensitive) +- **Expected authors:** Hsee, Yang, Wang (3 authors) +- **Expected journal:** Psychological Science +- **Expected year:** 2010 +- **Expected vol/issue/pages:** 21 / 7 / 926-930 + +### Fixture 2a — OpenLibrary ISBN (primary) +- **URL:** `https://openlibrary.org/isbn/9780415254014.json` +- **Expected title:** "The psychology of intelligence" (case-insensitive) +- **Expected author:** Piaget +- **Expected publisher:** Routledge +- **Note:** Year will show as 2001 (Routledge reissue) — correct for this edition. Not a failure. + +### Fixture 2b — Google Books ISBN (fallback) +- **URL:** `https://www.googleapis.com/books/v1/volumes?q=isbn:9780415254014` +- **Expected title:** contains "Psychology of Intelligence" (case-insensitive) +- **Expected author:** Piaget (in `volumeInfo.authors`) +- **Expected publisher:** Routledge (in `volumeInfo.publisher`) +- **Note:** Test 2b only if 2a fails. If 2b also fails, ISBN resolution is UNAVAILABLE. + +### Fixture 3 — Bare URL (HBR) +- **URL:** `https://hbr.org/1977/09/double-loop-learning-in-organizations` +- **Expected:** HTTP 200 response + +--- + +## Output format + +Full output when all APIs pass: +``` +# Hallucheck-Pro — API Connectivity Test + +## Network Domain Check + +| Domain | Status | Note | +|----------------------|--------------|-----------------------------------| +| api.crossref.org | ✅ Reachable | DOI verification available | +| openlibrary.org | ✅ Reachable | ISBN primary available | +| www.googleapis.com | ✅ Reachable | Google Books fallback available | +| hbr.org | ✅ Reachable | Bare URL checks available | + +ISBN resolution: OpenLibrary (primary) + +## API Fixture Tests + +| # | Type | Endpoint | Status | Note | +|---|-------------------------|------------------------------------------------|---------|-----------------------------| +| 1 | CrossRef (DOI) | api.crossref.org/works/10.1177/... | ✅ PASS | All fields match | +| 2 | OpenLibrary (ISBN) | openlibrary.org/isbn/9780415254014.json | ✅ PASS | Year 2001 expected | +| 3 | Bare URL (HBR) | hbr.org/1977/09/double-loop-learning-… | ✅ PASS | HTTP 200 | + +Result: 3/3 APIs operational — halluurl phase FULLY OPERATIONAL ✅ +``` + +When OpenLibrary is blocked but Google Books succeeds: +``` +## Network Domain Check + +| Domain | Status | Note | +|----------------------|--------------|-----------------------------------| +| api.crossref.org | ✅ Reachable | DOI verification available | +| openlibrary.org | ❌ Blocked | ISBN primary unavailable | +| www.googleapis.com | ✅ Reachable | Google Books fallback available | +| hbr.org | ✅ Reachable | Bare URL checks available | + +ISBN resolution: Google Books (fallback active — OpenLibrary blocked) + +## API Fixture Tests + +| # | Type | Endpoint | Status | Note | +|---|----------------------------|---------------------------------------------|---------|-----------------------------| +| 1 | CrossRef (DOI) | api.crossref.org/works/10.1177/... | ✅ PASS | All fields match | +| 2a | OpenLibrary (primary) | openlibrary.org/isbn/9780415254014.json | ❌ FAIL | EGRESS_BLOCKED | +| 2b | Google Books (fallback) | googleapis.com/books/v1/volumes?q=isbn:... | ✅ PASS | Title and publisher match | +| 3 | Bare URL (HBR) | hbr.org/1977/09/double-loop-learning-… | ✅ PASS | HTTP 200 | + +Result: 3/3 effective — halluurl phase OPERATIONAL (degraded) ⚠ +OpenLibrary blocked — Google Books fallback ACTIVE. ISBNs will resolve via Google Books. +``` + +When both ISBN APIs fail: +``` +| 2a | OpenLibrary (primary) | openlibrary.org/isbn/... | ❌ FAIL | EGRESS_BLOCKED | +| 2b | Google Books (fallback) | googleapis.com/books/v1/volumes?q=isbn:... | ❌ FAIL | EGRESS_BLOCKED | + +Result: 2/3 — halluurl phase DEGRADED ⚠ +ISBN resolution UNAVAILABLE — both OpenLibrary and Google Books blocked. ISBN references will be flagged ⚠ Unverifiable. +``` diff --git a/hallucheck-pro/commands/hallucheck.md b/hallucheck-pro/commands/hallucheck.md new file mode 100644 index 00000000..23fcc2c7 --- /dev/null +++ b/hallucheck-pro/commands/hallucheck.md @@ -0,0 +1,74 @@ +--- +name: hallucheck +description: Verify academic references in an argument tree file via CrossRef, OpenLibrary, and Google Books APIs. Extracts all DOIs, ISBNs, and URLs from the file, validates metadata live, and generates a markdown hallucheck report in x/AI_output/. +--- + +# /hallucheck + +Verify academic references in an argument tree or reference document. + +## Usage + +``` +/hallucheck path/to/argument-tree.md +/hallucheck [[power of slowness-argument tree]] +``` + +Accepts both: +- **Relative/absolute file paths**: `Luc25/Efforts/Works/Articles/my-tree.md` +- **Wikilink format**: `[[my-argument-tree]]` + +## What it does + +1. **Read** the file from your vault +2. **Extract** all references with DOIs, ISBNs, and bare URLs +3. **Verify** each reference live using the resolution chain below +4. **Compare** returned metadata (title, authors, year, journal, volume, issue, pages) against what's cited +5. **Generate** a markdown hallucheck report with four sections: + - Summary (overall % clean) + - Hallucheck V4 prompt (reference) + - Inference status (do sub-arguments back main arguments?) + - Claim status (do insights match cited works?) + - Reference plausibility (knowledge-based check) + - Live URL status (API verification results) +6. **Output** report to `x/AI_output/hallucheck-{filename}-v1.md` (increments versions if file exists) + +## API resolution chain + +| Reference type | Primary API | Fallback API | +|----------------|-------------|--------------| +| DOI | CrossRef (`api.crossref.org`) | — | +| ISBN | OpenLibrary (`openlibrary.org`) | **Google Books** (`googleapis.com`) | +| Bare URL | Direct HTTP check | — | + +If OpenLibrary is blocked or unavailable, ISBNs are automatically retried via Google Books. The report records which source was used (`source: OpenLibrary` or `source: GoogleBooks (fallback)`). If both are unreachable, the reference is flagged `⚠ Unverifiable`. + +## Example output + +``` +# Hallucheck Report — power of slowness-argument tree + +## Summary +- v References (98%): All verified. 1 minor mismatch flagged. +- v Claims (100%): All accurate. +- v Inferences (100%): Argument chains solid. + +## Live Verification Results +- v 1.1 Hsee 2010 — DOI resolves, metadata matches ✓ +- v 2.2 Piaget 1950 — ISBN resolves via Google Books (fallback), title/publisher match ✓ +- ! 9.1 Wilson 2014 — DOI resolves, author mismatch: "Ellsworth" → "Ellerbeck" +... +``` + +## Requirements + +- File must be readable Markdown with structured references (DOI: URL, ISBN: number, or bare URLs) +- CrossRef must be accessible for DOI verification +- OpenLibrary **or** Google Books must be accessible for ISBN verification + +## Notes + +- Reports are versioned: first run creates `v1`, second creates `v2`, etc. +- If a reference cannot be verified (API timeout, invalid format), the report flags it with `⚠ Unverifiable` +- Run `/hallucheck-test` first to check which APIs are reachable in your environment +- The hallucheck methodology is documented in the **hallucheck-v4** skill (loadable separately) diff --git a/hallucheck-pro/mcps/hallucheck-verifier.py b/hallucheck-pro/mcps/hallucheck-verifier.py new file mode 100644 index 00000000..947d466e --- /dev/null +++ b/hallucheck-pro/mcps/hallucheck-verifier.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +""" +Hallucheck Verifier MCP Server + +Verifies academic references via CrossRef, OpenLibrary, and Google Books APIs. +Implements the Model Context Protocol (MCP) over stdio. + +ISBN resolution strategy (fallback chain): + 1. OpenLibrary (openlibrary.org) — primary; returns rich metadata + 2. Google Books (googleapis.com) — fallback if OpenLibrary is blocked/unavailable + +Tools: +- verify_reference: Verify a single reference (DOI, ISBN, or URL) +""" + +import json +import sys +import urllib.request +import urllib.error +from typing import Optional, Dict, Any, List +import re + + +def fetch_crossref(doi: str) -> Optional[Dict[str, Any]]: + """Fetch metadata from CrossRef API for a DOI.""" + try: + url = f"https://api.crossref.org/works/{doi}" + with urllib.request.urlopen(url, timeout=5) as response: + data = json.loads(response.read().decode()) + if data.get("status") == "ok" and data.get("message"): + msg = data["message"] + return { + "success": True, + "source": "CrossRef", + "title": msg.get("title", [None])[0] if msg.get("title") else None, + "authors": [f"{a.get('family', '')} {a.get('given', '')}".strip() for a in msg.get("author", [])], + "year": msg.get("published", {}).get("date-parts", [[None]])[0][0], + "journal": msg.get("container-title", [None])[0] if msg.get("container-title") else None, + "volume": msg.get("volume"), + "issue": msg.get("issue"), + "pages": msg.get("page"), + "doi": msg.get("DOI") + } + except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError, Exception) as e: + return {"success": False, "source": "CrossRef", "error": str(e)} + return None + + +def fetch_openlibrary(isbn: str) -> Optional[Dict[str, Any]]: + """Fetch metadata from OpenLibrary API for an ISBN.""" + try: + isbn_clean = isbn.replace("-", "") + url = f"https://openlibrary.org/isbn/{isbn_clean}.json" + with urllib.request.urlopen(url, timeout=5) as response: + data = json.loads(response.read().decode()) + return { + "success": True, + "source": "OpenLibrary", + "title": data.get("title"), + "authors": [a.get("name", "") for a in data.get("authors", [])], + "publishers": data.get("publishers", []), + "publish_date": data.get("publish_date"), + "isbn": isbn_clean + } + except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError, Exception) as e: + return {"success": False, "source": "OpenLibrary", "error": str(e)} + return None + + +def fetch_google_books(isbn: str) -> Optional[Dict[str, Any]]: + """Fetch metadata from Google Books API for an ISBN (fallback for OpenLibrary).""" + try: + isbn_clean = isbn.replace("-", "") + url = f"https://www.googleapis.com/books/v1/volumes?q=isbn:{isbn_clean}" + with urllib.request.urlopen(url, timeout=5) as response: + data = json.loads(response.read().decode()) + if data.get("totalItems", 0) == 0 or not data.get("items"): + return {"success": False, "source": "GoogleBooks", "error": "No results found"} + info = data["items"][0].get("volumeInfo", {}) + # Normalize publishedDate to year only + raw_date = info.get("publishedDate", "") + year = raw_date[:4] if raw_date else None + return { + "success": True, + "source": "GoogleBooks", + "title": info.get("title"), + "authors": info.get("authors", []), + "publishers": [info.get("publisher")] if info.get("publisher") else [], + "publish_date": raw_date, + "year": year, + "isbn": isbn_clean + } + except (urllib.error.HTTPError, urllib.error.URLError, json.JSONDecodeError, Exception) as e: + return {"success": False, "source": "GoogleBooks", "error": str(e)} + return None + + +def fetch_isbn(isbn: str) -> Dict[str, Any]: + """ + Resolve an ISBN using the fallback chain: + 1. OpenLibrary (primary) + 2. Google Books (fallback) + Returns the first successful result, or a combined error dict if both fail. + """ + result = fetch_openlibrary(isbn) + if result and result.get("success"): + return result + + ol_error = result.get("error", "unknown") if result else "no response" + + # OpenLibrary failed — try Google Books + gb_result = fetch_google_books(isbn) + if gb_result and gb_result.get("success"): + gb_result["fallback"] = True + gb_result["openlibrary_error"] = ol_error + return gb_result + + # Both failed + return { + "success": False, + "source": "OpenLibrary+GoogleBooks", + "fallback_attempted": True, + "openlibrary_error": ol_error, + "google_books_error": gb_result.get("error", "unknown") if gb_result else "no response", + "error": f"Both OpenLibrary and Google Books unavailable. OL: {ol_error}" + } + + +def fetch_url(url: str) -> Optional[Dict[str, Any]]: + """Fetch a bare URL and check if it's accessible.""" + try: + with urllib.request.urlopen(url, timeout=5) as response: + content = response.read().decode(errors='ignore') + return { + "success": True, + "url": url, + "status_code": response.status, + "content_length": len(content), + "accessible": True + } + except (urllib.error.HTTPError, urllib.error.URLError, Exception) as e: + return {"success": False, "url": url, "error": str(e), "accessible": False} + + +def identify_reference_type(ref_id: str) -> tuple[str, str]: + """Identify the type of reference and normalize it.""" + if ref_id.startswith("10.") or "doi:" in ref_id.lower(): + doi = ref_id.replace("doi:", "").replace("DOI:", "").strip() + return "doi", doi + + if ref_id.startswith("978") or ref_id.startswith("979"): + return "isbn", ref_id + + if ref_id.startswith("http://") or ref_id.startswith("https://"): + return "url", ref_id + + return "unknown", ref_id + + +def verify_reference(ref_id: str, expected_data: Optional[Dict[str, str]] = None) -> Dict[str, Any]: + """ + Verify a single reference. + + Args: + ref_id: DOI, ISBN, or URL to verify + expected_data: Optional dict with expected metadata (title, authors, year, journal, volume, issue, pages) + + Returns: + Dict with verification results including success, source, metadata, and mismatches. + For ISBNs, 'source' will be 'OpenLibrary' (primary) or 'GoogleBooks' (fallback), + and 'fallback': True will be set if Google Books was used. + """ + ref_type, identifier = identify_reference_type(ref_id) + + if ref_type == "doi": + result = fetch_crossref(identifier) + elif ref_type == "isbn": + result = fetch_isbn(identifier) # OpenLibrary → Google Books fallback chain + elif ref_type == "url": + result = fetch_url(identifier) + else: + result = {"success": False, "error": "Unknown reference type"} + + if not result: + result = {"success": False, "error": "No data returned"} + + # Compare with expected data if provided + mismatches = [] + if expected_data and result.get("success"): + if "title" in expected_data and expected_data["title"]: + if result.get("title", "").lower() != expected_data["title"].lower(): + mismatches.append({ + "field": "title", + "expected": expected_data["title"], + "returned": result.get("title") + }) + + if "year" in expected_data and expected_data["year"]: + if str(result.get("year")) != str(expected_data["year"]): + mismatches.append({ + "field": "year", + "expected": expected_data["year"], + "returned": result.get("year") + }) + + if "authors" in expected_data and expected_data["authors"]: + expected_authors = set(expected_data["authors"].lower().split(";")) + returned_authors = set(" ".join(result.get("authors", [])).lower().split(";")) + if not expected_authors.issubset(returned_authors): + mismatches.append({ + "field": "authors", + "expected": expected_data["authors"], + "returned": "; ".join(result.get("authors", [])) + }) + + result["mismatches"] = mismatches + result["reference_type"] = ref_type + return result + + +def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: + """Handle an MCP request.""" + method = request.get("method") + params = request.get("params", {}) + request_id = request.get("id") + + try: + if method == "tools/list": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": [ + { + "name": "verify_reference", + "description": ( + "Verify an academic reference via CrossRef (DOI), " + "OpenLibrary with Google Books fallback (ISBN), or bare URL check. " + "ISBNs are tried against OpenLibrary first; if blocked or unavailable, " + "Google Books is used automatically as fallback." + ), + "inputSchema": { + "type": "object", + "properties": { + "reference_id": { + "type": "string", + "description": "DOI (10.xxx/...), ISBN (978...), or URL (https://...)" + }, + "expected_title": { + "type": "string", + "description": "Expected article/book title (optional, for comparison)" + }, + "expected_year": { + "type": "integer", + "description": "Expected publication year (optional)" + }, + "expected_authors": { + "type": "string", + "description": "Expected authors semicolon-separated (optional)" + } + }, + "required": ["reference_id"] + } + } + ] + } + } + + elif method == "tools/call": + tool_name = params.get("name") + tool_input = params.get("arguments", {}) + + if tool_name == "verify_reference": + ref_id = tool_input.get("reference_id") + expected = {} + if tool_input.get("expected_title"): + expected["title"] = tool_input["expected_title"] + if tool_input.get("expected_year"): + expected["year"] = tool_input["expected_year"] + if tool_input.get("expected_authors"): + expected["authors"] = tool_input["expected_authors"] + + result = verify_reference(ref_id, expected if expected else None) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "type": "tool_result", + "content": [{"type": "text", "text": json.dumps(result, indent=2)}] + } + } + + else: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32601, "message": f"Method not found: {method}"} + } + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32603, "message": f"Internal error: {str(e)}"} + } + + +def main(): + """Main MCP server loop.""" + while True: + try: + line = sys.stdin.readline() + if not line: + break + + request = json.loads(line) + response = handle_request(request) + + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() + + except json.JSONDecodeError: + continue + except KeyboardInterrupt: + break + except Exception as e: + error_response = { + "jsonrpc": "2.0", + "error": {"code": -32700, "message": f"Parse error: {str(e)}"} + } + sys.stdout.write(json.dumps(error_response) + "\n") + sys.stdout.flush() + + +if __name__ == "__main__": + main() diff --git a/hallucheck-pro/skills/hallucheck-v4/SKILL.md b/hallucheck-pro/skills/hallucheck-v4/SKILL.md new file mode 100644 index 00000000..e3928d43 --- /dev/null +++ b/hallucheck-pro/skills/hallucheck-v4/SKILL.md @@ -0,0 +1,143 @@ +--- +name: hallucheck-v4 +description: Hallucheck V4 methodology for verifying academic references. Combines knowledge-based plausibility checks (halluref, halluclaim, halluinfer) with live API verification (halluurl) via CrossRef and OpenLibrary. Explains each section and output format. Use this skill to understand the hallucheck framework or as a reference guide. +--- + +# Hallucheck V4 Methodology + +A systematic framework for verifying academic references in argument trees and reference lists. + +## Core Concept + +Hallucheck V4 extends hallucheck V3 with a **live verification layer** (`halluurl`). It checks references at four levels: + +1. **halluref** (knowledge-based) — Does the DOI/ISBN format match the author, title, journal? +2. **halluclaim** (knowledge-based) — Does the cited insight actually appear in the work? +3. **halluinfer** (logical) — Do sub-arguments back the main argument? Is the reasoning chain sound? +4. **halluurl** (live) — Does the DOI/ISBN resolve? Do the returned metadata match what's cited? + +--- + +## The Four Checks + +### 1. Halluref (Reference Plausibility) + +**Knowledge-based assessment** of whether the identifier (DOI, ISBN, URL) and cited metadata are mutually consistent. + +- DOI format is correct for the journal (SAGE, Elsevier, APA, etc.) +- Author names match the journal/publisher typical patterns +- Year, volume, issue are plausible +- Title length and style match the work type (article, book, etc.) + +**Output:** `- v {ref}` if consistent, `- ! {ref} (issue details)` if flagged. + +### 2. Halluclaim (Insight Accuracy) + +**Knowledge-based assessment** of whether the cited insight or claim is actually present in or warranted by the work. + +- Does the paper/book actually discuss what the insight describes? +- Is the attribution accurate or is it a distortion/misrepresentation? +- Does the insight map onto the work's actual contribution? + +**Output:** `- v` if accurate, `- !` with specific misalignment if not. + +### 3. Halluinfer (Inference Chain) + +**Logical assessment** of whether sub-arguments (x.1, x.2) actually back their main argument, and whether the argument chain supports the thesis. + +- Do 1.1 + 1.2 together establish argument 1? +- Do arguments 1–10 collectively support the thesis? +- Are any inferences speculative or unsupported? + +**Output:** `- v` if chain is sound, `- !` with the weak link identified. + +### 4. Halluurl (Live Verification) + +**API-based verification** that the identifier resolves and the metadata matches. + +Uses: +- **CrossRef API** (`api.crossref.org/works/{DOI}`) for journal articles +- **OpenLibrary API** (`openlibrary.org/isbn/{ISBN}.json`) for books +- **Direct fetch** for bare URLs (HBR, Amazon, etc.) + +**Compared fields:** +- Title (must match exactly or very closely) +- Authors (all author names present) +- Year (must match published year) +- Journal/Publisher (exact match) +- Volume, issue, pages (if cited, must match) + +**Output:** `- v {ref}` if all fields match, `- ! {ref} (specific field mismatch: expected vs. returned)` if any field diverges. + +--- + +## Output Format + +### Summary +One line per check type: +``` +- ! Halluref (95%): 1 minor format mismatch (Piaget book scope) +- v Halluclaim (100%): All insights accurate to cited works +- v Halluinfer (100%): Argument chains solid +- ! Halluurl (85%): 3 DOI mismatches, 1 ISBN timeout +``` + +### Section 1: Halluref Status +List every reference: +``` +- v 1.1 Hsee (2010) — DOI format correct, authors/title/journal consistent +- ! 2.2 Piaget (1950) — Book scope mismatch (cited for concepts better in different work) +``` + +### Section 2: Halluclaim Status +List insights vs. works: +``` +- v 1.1 — "Idleness aversion" insight accurately represents Hsee et al. (2010) +- ! 10.1 — "Mindfulness definition" more precisely sourced from 2003 academic paper, not 1994 popular book +``` + +### Section 3: Halluinfer Status +Argument chains: +``` +- v Argument 1 ← 1.1 + 1.2 both support "speed as concealment" +- ! Argument 7 ← 7.1 is analogical (self-handicapping) not direct +``` + +### Section 4: Halluurl Status +Live verification: +``` +- v 1.1 Hsee 2010 DOI resolves, title "Idleness aversion...", authors match, year 2010, journal Psychological Science ✓ +- ! 4.2 Düzel 2010 DOI resolves but title mismatch: cited "NOMAD", returned "Novelty-related motivation of anticipation..." +- ! 5.2 Rogers ISBN timeout — OpenLibrary API unreachable +``` + +--- + +## When to Use Hallucheck V4 + +- **Finalizing argument trees** before publication +- **Verifying reference accuracy** in academic work +- **Catching hallucinated citations** (real DOIs pointing to wrong papers, non-existent ISBNs) +- **Building confidence in your reasoning** (do your backings actually support your claims?) + +## When NOT to Use + +- Early drafting phase (too rigid, slows exploration) +- Informal research notes (overkill) +- When references are intentionally vague or secondary + +--- + +## Common Issues Caught by Hallucheck V4 + +| Issue | Detection | Example | +|-------|-----------|---------| +| Wrong DOI (resolves to different paper) | halluurl | Cited "Smith 2010 on X", DOI resolves to "Smith 2010 on Y" | +| Non-existent ISBN | halluurl | ISBN doesn't exist in OpenLibrary; 404 | +| Misquoted insight | halluclaim | Citation describes paper's finding, but paper actually argues the opposite | +| Weak inference | halluinfer | Sub-argument 2.1 doesn't actually support main argument 2 | +| Incorrect year/volume | halluurl | Cited "vol 5", CrossRef returns "vol 6" | + +--- + +See `references/hallucheck-v4-full-prompt.md` for the complete operational prompt. diff --git a/hallucheck-pro/skills/hallucheck-v4/references/hallucheck-v4-full-prompt.md b/hallucheck-pro/skills/hallucheck-v4/references/hallucheck-v4-full-prompt.md new file mode 100644 index 00000000..5a01dd1e --- /dev/null +++ b/hallucheck-pro/skills/hallucheck-v4/references/hallucheck-v4-full-prompt.md @@ -0,0 +1,82 @@ +# Hallucheck V4 — Full Operational Prompt + +## Core Checks + +In the given input, verify the following assertions in order: + +- **(halluref)** For each reference, assess whether the DOI / ISBN / URL and the cited metadata (author, title, journal or publisher, year) are mutually consistent — based on known literature and format plausibility. +- **(halluclaim)** For each reference, assess whether the insight or claim attached is actually present in or warranted by the cited work. +- **(halluinfer)** Retrace the inference stack: do the sub-arguments (x.1, x.2) back their main argument? Do the main arguments collectively back the thesis? +- **(halluurl)** Live-verify each reference identifier using the appropriate API or fetch: + - **DOI** → fetch `https://api.crossref.org/works/{DOI}` and compare returned `title`, `author` (family + given), `container-title`, `published` date-parts, `volume`, `issue`, `page` against what is cited in the document. + - **ISBN** → fetch `https://openlibrary.org/isbn/{ISBN}.json` and compare returned `title`, `authors`, `publishers` against what is cited. + - **Bare URL** (e.g. HBR, Amazon) → fetch the page directly and confirm the article or book title and author are present and match. + - For each field compared: flag any mismatch, even partial (e.g. year off, middle author missing, title truncated differently). + +--- + +## Output Structure + +**1 — Summary** (one line per check, with overall %) + +Prefix `- v` if 100%, `- !` otherwise. Format: `- ! Halluurl (78%): [brief description of issues]` + +**2 — Prompt** — spell this full prompt inside a fenced codeblock. + +**3 — Inference status** — one line per main argument. +Prefix `- v` if sub-arguments back the main argument, `- !` with issue if not. + +**4 — Claim status** — one line per sub-argument (x.1, x.2). +Prefix `- v` if insight matches the cited work, `- !` with issue if not. + +**5 — Ref plausibility** (halluref, knowledge-based) — one line per reference. +Prefix `- v` if consistent, `- !` with specific field mismatch if not. Keep argument number. + +**6 — Live URL status** (halluurl) — one line per reference. +- `- v {ref}` — DOI/ISBN/URL resolves, all compared fields match. +- `- ! {ref}` — (issue) state which fields mismatch or that the identifier does not resolve. Show the expected value vs the returned value. Keep argument number. + +--- + +## CrossRef API Response Fields + +```json +{ + "message": { + "title": ["Article Title"], + "author": [ + {"family": "Smith", "given": "John"}, + {"family": "Doe", "given": "Jane"} + ], + "container-title": ["Journal Name"], + "published": {"date-parts": [[2010]]}, + "volume": "21", + "issue": "7", + "page": "926-930", + "DOI": "10.1177/..." + } +} +``` + +- A CrossRef 404 or empty response = DOI does not exist in the registry → flag as hallucinated. +- A response where `message.title` does not match → flag as wrong paper. + +--- + +## OpenLibrary API Response Fields + +```json +{ + "title": "Book Title", + "authors": [{"name": "Author Name"}], + "publishers": ["Publisher Name"], + "publish_date": "2010" +} +``` + +- An OpenLibrary 404 or empty response = ISBN does not exist → flag as hallucinated. +- A response where `title` does not match → flag as wrong book. + +--- + +*Hallucheck V4 — created 2026-02-28*