manifest.json and run_results.json without asking anyone. hunch’s files are meant to be read the same way, by CI, dashboards, coding agents and your own scripts. This page is the contract.
Field by field: the results file and the manifest are described in the CLI reference.
What may change
Each file names its format:"version": 1 in results and manifests. Within a version, fields may be added, and enumerated values (question types, accuracy bases, check names, verdicts, review kinds) may gain members, so a reader should ignore what it doesn’t know. A field is never renamed, removed or given another meaning without the version going up, and a new version is listed below with what changed. spec_hash is part of the contract too: how it is computed changes only with a new version, since receipts and on_change: freeze compare it. The schemas are checked in CI against the files hunch writes: every battery’s results.json, and a manifest from hunch docs.
To check a file yourself:
Receipts
A battery ships the numbers it was measured with:hunch test --receipt writes the results beside the spec, without the fields a re-run would change (at, git_sha, cost). On the same answers the file comes out byte for byte the same, whatever hunch version wrote it, so in git a diff of a receipt is a change in what was measured. A battery can hold one receipt per engine. CI checks every receipt against the battery as it is now: each judgment measured, none that is gone, and each spec hash that of the spec today (with that receipt’s engine), so a battery edited without being measured again fails the build.
Verdicts
<judgment>.reviews.csv is gold that people made, so it is plain CSV you can read, edit in review, and take elsewhere. One row per verdict, appended; for a row and question, the last verdict wins.
Building on them
prototype/examples/ci/results_comment.py turns a results file into a pull-request comment: questions, multi questions, metrics and examples, with the standard library only and no hunch import. It reads the version first and refuses one it doesn’t know. The CI guide shows it in a workflow.