@loomantix/review-ledger
v1.3.0
Published
Deterministic local-review ledger protocol, verification, and CLI for multi-engine code review relays.
Readme
@loomantix/review-ledger
Deterministic local-review ledger protocol, verification, and CLI for multi-engine code review relays (codex, claude, gemini, antigravity).
Overview
The review ledger uses an open draft pull request as a durable ledger of actor-owned, content-integrity-checked records for local adversarial code reviews and cleanup passes. @loomantix/review-ledger provides:
- Strict Protocol Validation: Deterministic v3 marker serialization, SHA-256 content hashing, sequential occurrence verification, and blocker resolution enforcement.
- Multi-Engine Support: Unified contract for
codex,claude,gemini, andantigravityreviewer engines. - Declared Rosters & Coverage: A pull request declares its author engine and zero, one, or two reviewer engines; coverage is then derived from attestations naming the exact current head.
- Published Protocol: The engine-neutral contract every engine follows ships in the tarball at
protocol/local-review-ledger.md, so each platform repository vendors one source of truth instead of maintaining its own copy. - Standalone CLI & TypeScript API: Zero npm runtime dependencies, dual ESM/CJS distribution, and CLI executable (
review-ledger).
Installation
# Package install
pnpm add @loomantix/review-ledger
# Or invoke directly via npx
npx @loomantix/review-ledger --protocol-versionMost GitHub-backed commands require Node.js 18 or later, Git with the reviewed
history available locally, and an authenticated GitHub CLI
session. File-only result validation and --protocol-version do not use GitHub.
Vendored single-file build
The published tarball also ships dist/review-ledger.bundle.js: the whole CLI
as one self-contained, unminified ES module with a #!/usr/bin/env node
shebang. It imports no sibling chunk and needs no node_modules, so it can be
copied anywhere and run as node review-ledger.js. It answers --version with
the version it was built from, so a vendored copy can always identify itself. Invoke it through node
rather than executing it directly: npm normalises non-bin files to mode 0644
in the tarball, so the extracted file is not executable even though the shebang
is present.
This is the artifact the engine repos vendor. They commit it verbatim from a pinned version's tarball rather than rebuilding it, so a consumer never needs an install step and the committed bytes can be checked against the registry:
npm pack @loomantix/review-ledger@<version>
tar xzf loomantix-review-ledger-<version>.tgz
cmp package/dist/review-ledger.bundle.js <vendored path>Rebuilding the bundle locally is not a supported way to produce that file — only bytes extracted from the published tarball are comparable.
CLI Usage
The review-ledger binary exposes all subcommands:
Check Versions
review-ledger --version # e.g. 1.0.2 — this package's version
review-ledger --protocol-version # 3 — the ledger protocol it speaksThe two are independent: --protocol-version is the cross-engine compatibility
gate that must agree with every other implementation, while --version
identifies this build. --version is baked in at build time rather than read
from package.json, so it still answers correctly for a vendored single-file
build sitting on its own in a consumer repo.
Check Protocol Version
review-ledger --protocol-version
# 3Preflight Diff Anchor
Verify that an inline review comment targets an exact diff line in GitHub's patch:
review-ledger preflight-anchor \
--repo owner/repo \
--pr 123 \
--head 0123456789abcdef0123456789abcdef01234567 \
--path src/utils.ts \
--line 42 \
--side RIGHTPost Inline Finding
Post an inline finding with an actor-owned v3 protocol marker and content hash:
review-ledger post-finding \
--repo owner/repo \
--pr 123 \
--head 0123456789abcdef0123456789abcdef01234567 \
--path src/utils.ts \
--line 42 \
--engine gemini \
--round 1 \
--fingerprint auth-token-leak \
--occurrence 1 \
--severity blocking \
--lens security-reviewer \
--content-file ./finding-notes.txtDispose Finding
Post a disposition reply and resolve the review thread:
review-ledger dispose \
--repo owner/repo \
--pr 123 \
--head 0123456789abcdef0123456789abcdef01234567 \
--engine gemini \
--round 1 \
--fingerprint auth-token-leak \
--occurrence 1 \
--outcome fixed \
--comment-id 1001 \
--thread-id PRRT_kwDO123 \
--content-file ./fix-rationale.txtWrite & Validate Structured Results
Write pass result file:
review-ledger write-result \
--repo owner/repo \
--pr 123 \
--head 0123456789abcdef0123456789abcdef01234567 \
--engine gemini \
--round 1 \
--base 0000000000000000000000000000000000000000 \
--before 1111111111111111111111111111111111111111 \
--result-file ./review-result.json \
--classification minorWrite blocked result:
review-ledger write-blocked-result \
--head 0123456789abcdef0123456789abcdef01234567 \
--engine gemini \
--round 1 \
--base 0000000000000000000000000000000000000000 \
--before 1111111111111111111111111111111111111111 \
--result-file ./review-result.json \
--blocker-file ./blocked-reason.txtDeclare the Roster & Check Coverage
Participation is declared, never inferred: an engine that has not attested is otherwise indistinguishable from one that was never going to run.
# Claude wrote the change; Codex and Gemini will review it
review-ledger post-roster --repo owner/repo --pr 123 --head <sha> \
--author claude --reviewers codex,gemini --content-file ./roster-reason.txt
# Solo review is allowed, but must be declared with a recorded reason
review-ledger post-roster --repo owner/repo --pr 123 --head <sha> \
--author claude --reviewers none --content-file ./solo-reason.txt
# Changed your mind? Post again. The new roster supersedes the old one and
# both stay on the pull request, so the narrowing is visible and ordered.
review-ledger post-roster --repo owner/repo --pr 123 --head <new-sha> \
--author claude --reviewers none --content-file ./narrowed-reason.txt
review-ledger read-roster --repo owner/repo --pr 123
# Report coverage at the exact current head
review-ledger coverage --repo owner/repo --pr 123 --head <sha>
# ...or fail when the ledger would assert something untrue about what happened
review-ledger verify-coverage --repo owner/repo --pr 123 --head <sha>The roster marker is local-review-roster:v2. Its declaration-sha256 covers
author, reviewers, head, and supersedes together with the recorded
reason, so no field can be edited in place after the fact; head= binds the
declaration to the commit it was made over; and supersedes= chains each
replacement to the roster it replaces. Pull requests carrying the older
local-review-roster:v1 marker still read, but v1 puts the declaration outside
its own hash and names no commit, so a v1 roster is advisory: re-post it to
record the same choice as v2 evidence.
Neither coverage nor verify-coverage is a merge gate, and neither should be
wired into one. A developer who has looked at a change and judged its review
sufficient is always free to ship it. Solo review with a recorded reason is a
legitimate outcome, not a degraded one — what these commands owe you is an
accurate record of what happened, not a verdict on it.
coverage reports a tier over distinct non-author engines that attested
the exact head: solo (none), cross (one), full (two or more). The author
engine's own pass is reported as authorAttested but never counted — it re-reads
a change while still holding the rationale that produced it.
Because coverage is keyed to the exact head, it is also the invalidation rule. An engine whose newest attestation names an earlier commit has not reviewed what the pull request currently contains; an engine whose attestation names the current commit has, whatever moved the head. A fix therefore invalidates only the attestations that named the old head, rather than restarting a whole round.
Classify the Changeset
The source-versus-docs rule every lane skips on is executable code rather than prose, so four skills reading the same paragraph cannot drift into four different answers.
# Which classes does the pinned review range touch, and may the pass skip?
review-ledger classify-changeset --base <base-sha> --head <head-sha>
# Classify a diff captured elsewhere, e.g. in CI
review-ledger classify-changeset --diff-file ./range.diff \
--prompt-surface prompts/Two answers come back per file and they are independent. class is what kind
of work a line represents — app, test, docsConfig, generated — which is
what makes tokens-per-application-line comparable. reviewSignificant is
whether a lane must run at all. A lockfile proves they cannot be one field: it
must be reviewed and it must stay out of every ratio.
Record What a Pass Cost
review-ledger emit-telemetry --repo owner/repo --pr 123 \
--engine claude --engine-version 2.1.237 \
--pass-type review --review-tier deep --trigger interactive \
--round 3 --stance convergence --status changed \
--base <base-sha> --head <head-sha> \
--token-source session-log-delta --tokens-file ./tokens.json \
--findings-file ./findings.json --duration-seconds 512
# Render the record without posting it
review-ledger emit-telemetry ... --dry-runThe marker is local-review-telemetry:v1 followed by a JSON payload. Four
properties are load-bearing:
- Numbers arrive as arguments. This package never reads a session transcript, a home directory, or any other ambient state. Each engine extracts its own usage and passes it in.
engineandlensare open tokens and the payload is versioned JSON validated against a schema, not regex-parsed inline attributes. Instrumenting a new engine needs no release of this package.- Unavailable never becomes zero. A missing measurement is
nulland a measured zero is0; the two never collapse, and nothing is zero-filled. - Emission never fails a review. A telemetry error is reported on stdout and the command still exits 0.
Records are written, never read back into a review: a pass must not see prior
telemetry. Callers must exclude them by marker prefix with
isTelemetryComment or excludeTelemetryComments, so a record type added
later is excluded without teaching the caller each versioned marker.
Reconcile & Verify Ledger
# Reconcile finding state
review-ledger reconcile --repo owner/repo --pr 123 --head <sha> --fingerprint auth-token-leak
# Verify the complete thread ledger against live GitHub state
review-ledger verify-ledger --repo owner/repo --pr 123 --head <sha>
# ...or against a snapshot captured through a trusted channel. The digest
# detects later byte changes; it does not prove that the file came from GitHub.
review-ledger verify-ledger --repo owner/repo --pr 123 --head <sha> \
--threads-file ./threads.json \
--expected-threads-sha256 "$(sha256sum ./threads.json | cut -d' ' -f1)"Trust model
Three inputs are assertions the ledger checks, never values it takes on trust:
--actorasserts who the authenticated GitHub session belongs to. It is compared against the livegh api userlogin and fails on mismatch; it cannot select whose comments count as actor-owned.AGENT_LOOP_REVIEW_ACTORpins the same identity for a whole relay.--threads-fileis offline input, not independently authenticated evidence. Capture it through a trusted channel and pass--expected-threads-sha256(or setAGENT_LOOP_REVIEW_THREADS_SHA256) from that channel. The digest detects changes after capture; a digest computed from the same untrusted file proves no GitHub provenance. Prefer a live fetch whenever provenance is required.- Every thread must carry the repository and PR number it came from, and must
match the
--repo/--prunder review.
Programmatic API
import {
writeResult,
writeBlockedResult,
readResult,
formatFindings,
verifyLedger,
reconcile,
postRoster,
readRoster,
coverage,
verifyCoverage,
classifyRange,
buildTelemetryRecord,
emitTelemetry,
prCommentSink,
} from '@loomantix/review-ledger';
// Format findings table
const markdown = formatFindings([
{
path: 'packages/logging/src/index.ts',
line: 15,
severity: 'blocking',
lens: 'security-reviewer',
rootCause: 'Missing redaction on tenant ID',
},
]);Migration from review-ledger.py
The subcommands and flags mirror review-ledger.py. For a marker whose protocol
version and engine both implementations support, the marker layout, content
hashing, and verification rules are intended to match. This package's tests pin
those rules for this implementation only — they do not execute
review-ledger.py or compare against its output, so they cannot detect drift
between the two. Verify compatibility against the implementation you exchange
records with rather than relying on this suite.
- Execution: replace
python3 <skill>/scripts/review-ledger.py ...withreview-ledger ...ornpx @loomantix/review-ledger .... No Python runtime is needed; the external Git and GitHub CLI prerequisites above still apply. - Fingerprints are supplied, not derived. As in the Python, a fingerprint
is a caller-chosen stable token for one root cause, passed as
--fingerprint. This package deliberately ships no fingerprint generator: an engine-local hashing scheme would drift from the tokens already recorded on open PRs and would not agree across engines. - Engine set. This package accepts
codex,claude,gemini, andantigravity. Before exchanging records with another implementation, confirm that it supports the selected engine and protocol version. formatFindingsis additive, with no counterpart in the Python. It is a presentation helper and no verification path depends on its output.
License
Apache-2.0. Copyright 2026 Loomantix Inc.
