Skip to main content

Run history & diffs

Every compile and test run bleep performs is recorded as a transcript: an immutable JSON file the compile daemon writes into the workspace itself. Every summary ends with a History: #N line pointing at the transcript it just wrote. Because a completed run is a value on disk — not state inside a daemon — you can list runs, read any run's full diagnostics after the fact, and diff two runs as a pure function over two files. That's the whole API:

The edit-run-what-changed loop in under a minute:

What gets recorded

Transcripts live at .bleep/builds/normal/history/<id>.json (the last 32 are kept). History is per worktree — each checkout has its own numbering — and it is shared by every client of that workspace: the CLI, your IDE's BSP connection, and the MCP server all read and write the same files, and the records survive daemon and server restarts.

$ bleep history
#1 2026-08-13 09:29:37 compile client=bleep targets=mathy,mathy-test
#2 2026-08-13 09:29:38 test client=bleep targets=mathy-test
#3 2026-08-13 09:29:39 test client=bleep targets=mathy-test

Reading a transcript: bleep history show

bleep history show <id> prints the full record of a run as JSON — the latest run when no id is given. A test transcript carries pass/fail counts and every failure with its message and stack trace; a compile transcript carries every diagnostic with severity, path, and lines.

bleep history show # the latest run
bleep history show 6 # run #6
bleep history show 6 --query 'Timeout|NullPointer' # only matching items
bleep history show 6 --project mathy-test # only one project's events
bleep history show 6 --limit 5 --offset 5 # paginate large runs

--query is a case-insensitive regex over messages, paths, suite/test names, and stack traces — the intended way to pull the two interesting failures out of a run with hundreds.

What changed: bleep history diff

bleep history diff <base> <target> compares the logical outcome of two runs. Time never enters the comparison — durations are not part of the compared data — so two runs with the same outcome diff as identical no matter how their timings jittered. The result renders for humans by default:

compile diff #3 → #4
1 project changed

core success → failed
+ error value withTag is not a member of com.example.Record7
core/src/scala/com/example/Record7.scala:10

Pass --output json (-o json) to get the underlying diff document instead — the same structure the MCP server returns, for scripts and agents:

{
"base" : { "historyId": 5, "workspace": "~/demo" },
"target" : { "historyId": 6, "workspace": "~/demo" },
"mode" : "test",
"identical" : true,
"summary" : "No logical differences."
}

For test runs, differences are grouped by transition — newlyFailing (with the failure message), fixed, stillFailing (context; only counts as a difference when the failure message changed), newlySkipped, unskipped, added, removed, and suiteOutcomeChanges. A test's identity is (project, suite, test).

For compile runs, each changed project reports what moved: compile status and reason (up-to-dateincremental), invalidatedFilesAdded/Removed, changedDependenciesAdded/Removed, and — the interesting part — newDiagnostics and resolvedDiagnostics. A diagnostic's identity is (severity, path, message): the line number is an attribute, not identity, so an edit that only shifts a warning down the file does not report it as new plus resolved.

What got slower: --timing

bleep history diff <base> <target> --timing is the other half: it compares durations, and treats jitter as the enemy. Per-item deltas are reported only above a significance threshold of max(50ms, 20% of base); everything below is counted in insignificantDeltasSuppressed rather than listed. You get slower, faster, and the target run's slowestInTarget (at most --limit entries each, default 15), plus totals:

timing diff (compile) #1 → #3
total 2608ms → 3845ms (+1237ms)
threshold max(50ms, 20% of base), 12 insignificant deltas suppressed

slower
2403ms → 3750ms (+1347ms) core

--output json applies here too. The same two transcripts always render the same result: ordering ties break on item identity, never on hash order.

The edit loop: compile --diff and test --diff

You rarely need to name two ids. After an edit:

bleep test --diff # run, then print only what changed vs the previous test run
bleep compile --diff # same, for diagnostics
bleep test --diff=12 # pin a fixed baseline, e.g. the last green run

Bare --diff compares against the most recent run of the same mode — a sliding baseline, which is exactly the edit-loop question. The corollary: rerunning without an edit reports identical, because nothing did change since the last run (the header always names the two ids being compared, and when both runs are identically red the diff says so instead of reading like an all-clear). When you want a fixed reference — "compare everything I do against the last green run" — pin it with --diff=<id>. With --watch, bare --diff compares each cycle against the previous cycle, while --diff=<id> keeps a fixed baseline for every cycle. Every --diff output ends with a hint (timing: bleep history diff 5 6 --timing) so the duration comparison of the exact same pair is one paste away. --diff-watch is the terse variant: watch mode printing only the per-project diffs between cycles.

The whole loop in one sitting — a transcript fetched as JSON, a break pinpointed by compile --diff, the fix confirmed the same way:

Across worktrees: --base-dir

History ids only mean something together with their workspace, so comparing a fork against the worktree it was forked from takes one extra flag:

bleep history diff 12 3 --base-dir ../main-worktree

Identity is computed against each side's own workspace root: paths under the root are relativized (separator-agnostic, so a transcript written on Windows compares cleanly anywhere), and the root embedded in diagnostic messages is normalized away — the same failure in a parent and its fork compares as the same failure. Paths outside any workspace (coursier jars, the JDK) stay absolute, which is correct on one machine: the same jar genuinely is the same file in both worktrees.

For agents: the MCP surface

The MCP server exposes the same records: bleep.compile and bleep.test accept diffBase ("previous" or a historyId) and return a diff section with their summary, and bleep.history.list / show / diff / diff-timing mirror the CLI — including baseDirectory for cross-worktree diffs. The base is validated before the build starts, so a bad id fails without costing a build.