メインコンテンツまでスキップ

Run store and regression comparison (runs / --baseline)

River Review can persist review runs to a project-local Run store (.river/runs/) and compare against past runs to see regressions (new vs. resolved findings). Wired into CI, this lets you mechanically track "did this PR add findings?".

For the full list of flags and subcommands, see Stable Interfaces (CLI reference). This page focuses on the how-to.

Overview

What you wantCommand
Save a review runriver run <path> --save
List saved runsriver runs list
Diff saved runs (regression / oscillation)river runs diff <id1> <id2> [<id3>...]
See an aggregate dashboard across saved runsriver runs summary
Compare against an arbitrary baseline JSONriver run <path> --baseline <prev.json>

1. Save a review run (--save)

Passing --save to river run persists the result to the Run store.

river run . --save

On success, the destination is printed to stderr.

Run saved: 2026-06-06T08-30-00-000Z-a1b2c3 → /path/to/repo/.river/runs/2026-06-06T08-30-00-000Z-a1b2c3.json

Where the Run store lives

  • Default: .river/runs/ directly under the reviewed repository (project-local)
  • Fallback when no repository can be resolved: ~/.river/runs/ (global)

Each run is saved as a single <runId>.json file. The runId is an ISO timestamp plus a short hash (e.g. 2026-06-06T08-30-00-000Z-a1b2c3), designed so that lexicographic filename order equals chronological order.

.river/runs/ is a run log and does not need to be committed. It is common to add it to .gitignore.

What is saved

Each run record (JSON) contains:

  • runId / timestamp / phase / reviewMode
  • mergeBase / defaultBranch / changedFiles
  • findings and suppressedFindings
  • finalSummary: findingsCount / suppressedCount / overviewCount / changedFilesCount / tokenEstimate

2. List saved runs (river runs list)

Lists the runs in the Run store, newest first.

river runs list
Stored runs (/path/to/repo/.river/runs):

2026-06-06T08-30-00-000Z-a1b2c3 phase=midstream findings=4 suppressed=1 files=7 2026-06-06T08:30:00.000Z
2026-06-05T17-10-00-000Z-d4e5f6 phase=midstream findings=6 suppressed=0 files=5 2026-06-05T17:10:00.000Z

If there are no saved runs, No stored runs found in ... is printed.

3. Compare two or more runs (river runs diff)

Specify two saved runs by runId to compare finding regressions.

river runs diff 2026-06-05T17-10-00-000Z-d4e5f6 2026-06-06T08-30-00-000Z-a1b2c3

It prints a ## Regression Review Summary Markdown block.

ItemMeaning
New findingsPresent in the second run but not the first (a regression)
Resolved findingsPresent in the first run but gone in the second (resolved)
PersistingPresent in both, with a small score change
Score changedPresent in both, but the composite score moved by ±0.05 or more
Regression scoreNew − Resolved. Positive means a net increase (worse)

The New / Resolved / Score changes sections list the affected file and the finding content.

Finding identity is determined by a fingerprint. Findings with the same file path, rule, and message gist are treated as identical; line-number shifts are ignored.

Oscillation detection with 3+ runs

Passing three or more run IDs enables oscillation detection.

river runs diff <run-id-1> <run-id-2> <run-id-3>

An oscillated finding is one that was Resolved in an intermediate run but reappears as New in a later run. This is a signal that an AI-driven revise cycle introduced a different problem — a self-correction loop. The Oscillated findings section in the output lists these re-emergent findings. With --output json the same data is available in the oscillated array for machine consumption.

4. Aggregate dashboard (river runs summary)

Prints a Markdown dashboard aggregated across the whole Run store.

river runs summary
## River Review Dashboard

| Metric | Value |
|---|---|
| Total runs | 12 |
| Total findings | 48 |
| Total suppressed | 6 |
| Suppress rate | 11.1% |
| Avg findings/run | 4.0 |

It also prints Severity / Confidence distribution tables. Use it to grasp quality trends across many runs.

5. Compare against an arbitrary baseline (--baseline)

Whereas runs diff compares two saved runs, --baseline compares a freshly executed result against any past review JSON. This suits keeping a baseline generated on main as a file and checking for regressions on a PR branch.

river run . --baseline ./baseline-findings.json

The JSON passed to --baseline accepts either form:

  • A bare array of findings ([ { ... }, { ... } ])
  • An object with a findings (or issues) key ({ "findings": [ ... ] })

The output is the same ## Regression Review Summary format as runs diff. If the comparison fails, Warning: --baseline comparison failed: ... is printed and the review itself continues.

Creating a baseline file

If you save a result with --output json, you can reuse it directly as a baseline.

# Create the baseline on main
git switch main
river run . --output json > baseline-findings.json

# Check for regressions on the PR branch
git switch feature/my-change
river run . --baseline ./baseline-findings.json

Example CI integration

A minimal example that flags a net increase in findings on a PR.

# Assumes the baseline (main's findings) is kept as an artifact
river run . --baseline ./baseline-findings.json --save

Combining --save accumulates each PR's run in the Run store, so you can later review trends with river runs summary.