This page shows how to check a receipt's hashes against the repository, find every receipt that covers a file, write a new receipt skeleton, and read the readability report. Both tools are tested repository scripts (scripts/receipt-check.ts, scripts/readability.ts), not archive scripts. There is no npm script for either tool, so run them with node.
Check one receipt
node scripts/receipt-check.ts check docs/<name>-receipt.json
Receipt shapes are not uniform (docs/agents/factory-model.md section 7): some pin source and test hashes under files or final_sources; some hash only review and browser evidence under evidenceSha256. The checker does not guess which field holds paths. It treats a JSON object as a hash map only when every value is a 64-character hex string, wherever the object sits in the receipt.
A 64-character hex string that sits anywhere else, such as a lone scalar field or a value beside non-hash fields, is never silently dropped. The report lists every one of these as unattributed (field path and value, with no path to check it against), and the receipt's shape is partial whenever unattributed is not empty, even when some other field in the same receipt is a recognised map. A receipt with no hash anywhere, recognised or not, is shape: "unknown". Unknown is never reported as trivially matching, and an unattributed hash is never silently ignored (Factory rule 1). Unknown is never zero.
A map key that is a label rather than a file path is reported as missing; that does not establish a lost file.
For each path a recognised map holds, the report gives one of five states:
| State | Meaning |
|---|---|
matches | The file exists and hashes the same |
changed | The file exists and hashes differently |
missing | No such file exists |
unreadable | The path exists but is not a readable regular file, most often a directory |
not-checked | The path is a symbolic link (never followed, so it can never hash a file outside the repository), or is outside the repository: absolute, escaping through .., or under <local evidence directory>, which holds ignored session evidence, not stable repository content |
Exit status:
0: every checked path matches,unattributedis empty, and no path isunreadable.1: no unattributed hash and nothing unreadable, but at least one path changed or is missing. A changed path is not always a fault: receipts are history, so an old receipt's files are expected to have moved on since.2: the shape isunknownorpartial, or at least one hashed path isunreadable. This is never conflated with an ordinary mismatch: unknown or unreadable means the tool could not fully account for the receipt's hashes, not that it found and confirmed a difference.64: invalid arguments or JSON, or a refusednewrequest (including an empty file list).70: any other error, such as the receipt file itself not existing.
Find what covers one file
node scripts/receipt-check.ts coverage src/repository.ts
Lists every docs/*-receipt.json that hashes the given path, with its state. This read-only report exits 0 and never changes a receipt. Receipts that cannot be read or parsed appear in unreadable, with a reason. An unreadable hashed file has its own entry state; it does not make its receipt unreadable. An empty coverage list therefore retains gaps in receipt coverage.
Write a new receipt skeleton
node scripts/receipt-check.ts new --out docs/<name>-receipt.json --files <path> [<path> ...] [--base <commit>] [--component <name>]
The command writes:
- each given file's SHA-256;
base: the given commit, or the currentgit rev-parse --short HEAD, orunknownoutside a Git repository;branch: the current Git branch, orunknown;component: the given name, or the output file's own name with-receipt.jsonremoved;- the fixed disposition
implemented-awaiting-independent-review; - an empty
reviewlist.
The command checks every file before it writes anything. It refuses, and writes nothing, in five cases: the file list is empty; the output already exists; a given path leaves the repository; a given path is a symbolic link, directly or through a symbolic-linked ancestor directory; a given file cannot be read. The same escape and symbolic-link checks apply to --out itself and --files, so the command can never write outside the repository, even through a symbolic-linked ancestor directory the destination path passes through. It never edits an existing receipt; a fresh review still adds a new one, kept alongside the old as history.
Read a readability report
node scripts/readability.ts [root]
The Node tool follows the archived Python script and counts the same files: README.md, AGENTS.md, CLAUDE.md, handover.md, and docs/*.md, docs/agents/, docs/guide/, docs/brand/, docs/ui/design-system.md, docs/tutorials/, docs/how-to/, docs/explanation/ and docs/reference/. The columns show words, prose words outside fenced code and table rows, sentence count and length (mean, 90th percentile, the share over 30 and 50 words), the share of words in tables and in fenced code, second-person words, sentence-initial imperatives, and words that date a claim. root defaults to the working directory, so run it from the repository root. The report supports review. It marks a file it cannot read as unavailable, and the command always exits 0.
