● Open sourceTerminal Recording Diff

divergence.

Where did the two runs stop agreeing?

asciinema records terminal sessions. Nothing diffs them. So "it passed on my machine and failed in CI, where did they diverge?" is a question nobody can answer except by scrolling two logs side by side.

GoStdlib only20 testsasciicast v2
Language Go 1.22+Tests 20License MIT
divergence — diff
$ divergence diff local.cast ci.cast
✗ agreed for 1 step, then diverged
2 npm run build differs
- compiled in 3.2s
+ compiled in 11.7s
01 The problem

The bytes are not what the user saw.

They are instructions for producing what the user saw. Two runs that displayed identical output can share almost no bytes — a progress bar redrawn with \r versus one redrawn with a cursor-position sequence, colour that CI disabled because it is not a TTY. diff reports all of it.

02 The hard part

Where the work actually was.

So the bytes have to be interpreted, which means a real terminal emulator. The parser follows the DEC state machine Paul Williams derived from the VT500 series, whose virtue is being total: every byte in every state has a defined transition, so a recording of a crashed program recovers instead of wedging.

1

Total parser

Bare escapes, unterminated OSC, absurd parameters, invalid UTF-8 — there is a test asserting it recovers from all of them.

2

Deferred wrap

A character in the last column does not move the cursor until the next arrives. Getting it wrong adds a blank line to every wrapped line.

3

Scrollback

A build log is mostly scrollback. Only full-screen scrolls capture, so a TUI redrawing itself does not fill the log with frames.

4

Command alignment

A Myers edit script over commands first, so one extra step does not report everything after it as different.

5

OSC 133

Where the shell emits integration markers, segmentation is exact — including the exit code of each command.

6

Honest fallback

Without markers it matches prompts, and the report says the segmentation was guessed rather than presenting it as fact.

03 Measured

What it actually does.

The fixtures in the repo make the point — two builds drawing progress bars with completely different escape sequences:

✗ the runs agreed for 1 step, then diverged at $ npm run build 2 npm run build differs 100% - ✓ compiled 84 files in 3.2s + ✓ compiled 84 files in 11.7s 3 npm test differs status: exit 0 → 1 - PASS src/retry.test.ts + FAIL src/retry.test.ts the progress bars are never mentioned
04 Engineering

How it's built.

LanguageGo, standard library only
ParserDEC ANSI state machine
Screengrid + scrollback + margins
DiffMyers, two levels
Formatasciicast v2
Exit code1 when they differ — CI-ready
05 Honestly

What it doesn't do.

It does not record — asciinema does that well and reimplementing PTY capture would be work with no payoff. No sixel or ReGIS. And timing is not compared: two runs where one took twice as long are identical unless the output says so, because wall-clock is the least reproducible thing about a build.

Want a closer look?

The README goes deeper, and the tests are the honest documentation. Happy to walk through the trade-offs.