Skip to content
dsh-market Browse plugins GitHub 中文

tmpdot/dsh-checkpoint-diff

Browses checkpoints produced by dsh-checkpoint-rewind as a timeline of time nodes and shows per-file line diffs between any two of them, with trace replay from session logs (no snapshot producer needed) and preview-first workspace rollback with single-shot undo, via /diff and /rollback commands, a JSON HTTP API, and a GUI panel.

Stars ★ 1 Category Sessions & Messages Listed 2026-08-20 npm dsh-checkpoint-diff

Install

Inside DeepSeek Harness, with dsh-market

dsh plugin --profile web add dshmarket

Or from the command line

dsh plugin --profile web add dsh-checkpoint-diff

Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.

README

File-diff visualization between checkpoint time nodes for DeepSeek Harness.

Every time a mutating tool runs, dsh-checkpoint-rewind captures a snapshot of your workspace — a checkpoint time node. This plugin turns those nodes into a browsable timeline and shows you exactly what changed between any two of them: a GUI panel with a per-file line diff, a /diff command for headless use, and a small JSON API.

Read-only by design, with one explicit exception: rollback. You can restore workspace files from any time node — the whole node or a single file — through the panel, /rollback, or the API. Rollback is overwrite only: it never deletes files created after the node, never touches .git/.dsh, and never writes to snapshot storage, git, or sessions. The single exception to "never deletes" is rollback's single-shot undo (/rollback --undo, panel button): it may remove a file only when the restore itself created it — and nothing else (in-process, lost on restart).

CI

中文版(默认):README.md

Why it exists: the idea

The situation. AI agents write code and modify files for you. Their thinking is a black box, but their actions land in your workspace — and with that come the questions: what did it change? When? And what if it broke something? These questions must never be unanswerable.

The stance. Tools like this usually go one of two ways: save you the effort — summarize, judge, filter on your behalf; or let you rest easy — hand you the raw truth and guarantee you can always inspect it, trace it, and roll it back. This plugin is unambiguously the second kind: no "read it for you" summaries or filtering — it hands you the full power to see clearly and step back.

  • You don't have to look — but you must be able to. Ignore it day to day; but whenever you want, every mutating action maps to a fine-grained snapshot time node, and any two nodes can be diffed file by file, line by line.
  • When something goes wrong, you can trace it to the bottom. Which step, which tool, which files — precisely reconstructed; and if it was a mistake, restore the workspace from any time node (overwrite-only, never deletes, undoable).
  • Peace of mind comes not from infallibility, but from traceability and reversibility.

One line: you may not look, but it must never be un-inspectable. People may choose not to look and not to investigate; what they must never lack is the means to do both. That is why this plugin exists.

Demo

GUI panel

Real captures from a live harness session. Panel overview — scope switch, from/to node selects (intent labels, (HEAD) prefix), tree file list (A/M/D), per-line diff (red/green + ↑/↓ change-block jumping), and the Restore workspace card:

Panel overview

Trace (session log) scope — session-log replay with the three-lane drag-select timeline (Input / Model / Tools spans, turn boundaries; a drag range maps to the nearest tool-call boundaries in from/to):

Trace scope

This session scope — the current session's checkpoint timeline.Select the version to roll back and click Preview Restore to preview the changes:

This session scope

This project scope — cross-session merge with the fork-lineage branch dropdown:

This project scope

Re-capture and update the README after any significant UI change — capture rules in docs/screenshots/README.md.

Command line (verbatim output format)

/diff lists this session's timeline — rewind leaves a snapshot time node before every mutating tool run (format is the real output, data is illustrative):

diff: 3 checkpoint(s) for this session
  #a1b2c3d4  20m ago  turn 2 step 1  copy  3 file(s)  18 KiB  edit lib/engine.mjs
  #b2c3d4e5  10m ago  turn 3 step 1  copy  2 file(s)  12 KiB  edit README.md
  #c3d4e5f6   5m ago  turn 3 step 2  copy  4 file(s)  25 KiB  bash pnpm test
usage: /diff <from> <to>  (id prefix or "latest" for either side)

/diff <from> <to> between any two nodes — file summary + line diff (the GUI panel shows the same red/green diff side by side):

diff: #b2c3d4e5 (14:02 · edit README.md) → #c3d4e5f6 (14:07 · bash pnpm test)
  4 file(s) changed: +1 added, 2 modified, 1 deleted
  M  lib/engine.mjs
    - function lcs(a, b) {
    + function lcs(a, b, opts) {
  M  README.md
    - Read-only by design, one exception: rollback
    + Read-only by design, one explicit exception: rollback
  A  test/engine.test.mjs
  D  lib/legacy.mjs

At a glance

  • What it is — a DeepSeek Harness plugin that turns dsh-checkpoint-rewind snapshots into a browsable timeline with per-file, line-level diffs between any two time nodes (GUI panel / /diff / HTTP API), plus preview-first rollback (overwrite-only, never deletes, undoable).
  • What it solves — after an AI agent mutates your workspace: what changed, when, and how to trace and recover when things go wrong.
  • Who it's for — developers using DeepSeek Harness with dsh-checkpoint-rewind installed.
  • Compatibility — DSH 0.1.0-rc.5 / rc.6 · Node ^22.19 || >=24 · Apache-2.0.
  • Entry points — Diff button in the session header · /diff · /rollback · /checkpoint-diff/api.

Features

  • Timeline — every checkpoint as a selectable node (#short-id, time, turn/step, provider, trigger tool; /rewind guard checkpoints are marked).
  • Cross-session / same-project timelines — switch to This project to merge checkpoints from every session sharing the workspace key, organized by /rewind fork lineage: branch dropdown (root + side branches, session titles), per-session tags, and fork / root-missing markers. Falls back to a flat merge when the optional dsh-session-query service is absent.
  • Intent labels — each node is named from the session log: the tool/call events of its (turn, step) are looked up to produce labels like edit README.md or bash pnpm test (in the GUI, /diff output and the JSON API), falling back to the raw trigger tool when the log is unavailable. Cold sessions are resolved read-only through sessionQuery.readSession.
  • File summary — changed files between two nodes with A (added) / M (modified) / D (deleted) badges.
  • Tree file view — the changed-file list renders as a collapsible directory tree with per-directory A/M/D counts; click a file for its line diff.
  • Line diff — a self-contained LCS engine (no git required for copy snapshots) renders ctx/del/add lines with aligned line numbers.
  • Trace replay (works without rewind) — switch the panel scope to Trace (session log) or run /diff --trace: every tool/call boundary in the session log (session.jsonl.zstd) becomes a time node (trace:<seq>), and selecting any two nodes diffs that interval by replaying write/edit/str_replace_editor arguments (full file contents are in the tool parameters). No snapshot producer is needed — past sessions work out of the box, and rewind absence, quota eviction or git gc no longer silence the "what changed" question. Shell-command edits are invisible to the trace; any replay divergence is reported in notes (never silent). Data comes from sessionQuery.readSession first, with a direct zstd read (Node ≥ 23.5) as the last-resort fallback.
  • Two snapshot providersgit (unreferenced stash/commit-tree objects, read via git diff-tree/git show) and copy (snapshot directories + manifests), dispatched per record; mixed-provider pairs are rejected loudly.
  • Rollback — restore the workspace (or a single file) from any time node: preview first, then apply. Overwrite only — files created after the node are reported and left in place, never deleted. Works in session and project scope (cross-session nodes address the same workspace). Paths are validated (no traversal, no absolute paths, .git/.dsh never touched); the git provider only uses read-only git primitives and refuses to run unless the session cwd is the repository root.
  • Restore preview diff — while previewing a restore, click any file in the plan to see the current workspace → target snapshot line diff right in the diff pane ("current → #target"), so you can see exactly what the restore would change before applying it.
  • Single-shot undo — after applying a restore, one ↩ Undo this restore action (panel button, /rollback --undo, POST /api/rollback-undo) reverts it: overwritten files get their pre-restore content back, and files the restore created are removed (the one "never deletes" exception). In-process only (lost on restart), no redo; files changed since the restore are skipped untouched.
  • Diff view niceties — ↑/↓ buttons jump between change blocks of the current diff (a block is a maximal run of changed lines — a modification's red and green regions count as one block, a run of added lines as one); each jump centers the block's middle line in the viewport; at the last block ↓ shows a "Last change block (click again to wrap)" toast and only wraps on the next press (↑ symmetric at the first block); opening a file auto-scrolls to its first change block; a small "Last view" line under the diff pane jumps back to the previously viewed node pair (persisted in localStorage).
  • Position labels — the newest node is tagged (HEAD) (uppercase, prefixed to the id: (HEAD) #bbbbbbbb 20:26 · edit b.txt) in the from/to/restore-target selects, in the summary range line and in the restore-preview tag. HEAD always means the current snapshot — the global newest node — so it never moves when you pick other versions or filter branches; refreshing the timeline moves it to a newer snapshot.
  • Separate restore section — rollback lives in its own collapsible "Restore workspace" card below the toolbar, clearly separated from the node-diff area; its plan/result/undo status renders inside the card, and the restore preview (tagged "Restore preview: current workspace → …") is independent of the from/to diff: picking other versions never clears it, and changing the restore target reloads it against the new target. Preview-plan file rows are obvious links (🔍 file).
  • Graceful degradation — git checkpoints whose snapshot object was reclaimed by git gc (or lost on re-clone) are marked ⚠ degraded in the selects and excluded from the default selection; the timeline shows a "N checkpoint(s) degraded" banner, and any diff/rollback error names the exact dead node (checkpoint #9312717a (to side) is missing from this repository (bad object …)). No data is ever deleted; pick newer nodes or run rewind's /rewind clear to reset the timeline. Pruned checkpoints, missing records and missing files keep their existing clear errors.

Alternatives compared

Through the lens of "an AI agent just modified my workspace", against the common options (facts per each project's official docs):

Tool Snapshot granularity Diff between any two nodes Restore / rollback Session awareness
dsh-checkpoint-diff (this plugin) before every mutating tool run ✅ yes, per file and per line (GUI / /diff / API; intent labels, tree view, change-block jumping) preview-first → apply; never deletes; single-shot undo; whole node or single file session + cross-session project scope (fork lineage, branches)
dsh-checkpoint-rewind (upstream producer) before every mutating tool run (0.5.0+: + per-turn / scheduled / manual) ✅ settings-page pairwise compare (0.5.0+: file change-set + config line diff; no per-file workspace line diff) /rewind preview → restore → fork (guard checkpoint; 0.5.0+: seed-replay session rewind) single session
dsh-turn-rewind per user message (coarser) ❌ no (changeLedger service, no visual diff) conversation + workspace rewind; web restore dialog single session
dsh-snapshot before each write/edit/delete (target file content) ❌ no line-level diff roll back a whole "conversation" (re-rollbackable) single session (project-level quota)
Claude Code native checkpointing + /rewind per conversation turn (automatic) no node-to-node diff view in the docs (see official docs) /rewind restores files (git-tracked) inside Claude Code sessions
git baseline (git diff/restore, GitLens etc.) per manual commit ✅ any two commits, line-level (mature ecosystem) git restore/checkout; no preview planning, no undo guard none (bare repo)
IDE local history (JetBrains Local History / VS Code Timeline) per edit/save (per file) single-file revision level restore any single-file revision none
OS-level snapshots (Time Machine / Windows File History) hourly/daily ❌ no line-level diff whole file/directory restore none

Note: the DSH ecosystem also has timeline-style plugins such as dsh-message-timeline, dsh-session-timeline, dsh-chat-timeline and dsh-undo-savepoint (DSH-config domain); this table focuses on the diff-visualization + rollback dimension.

Ecological position (verified 2026-08-17): upstream rewind 0.5.0 now ships its own settings-page timeline with pairwise compare (file change-set + config diff; source-verified: no per-file workspace line diff). This plugin does not compete with the producer over "having a diff"; it stays a read-only consumer + analysis layer — intent labels, cross-session fork lineage, non-git workspaces (copy), preview-first rollback with single-shot undo, /diff and the HTTP API — and aims to degrade gracefully even when rewind is absent or snapshots are gone. Ecosystem assessment, compatibility facts (rewind 0.5.0 domain v2) and the differentiation roadmap: docs/competitive-analysis.md.

Install

dsh plugin --profile web add dsh-checkpoint-rewind   # checkpoint producer (first)
dsh plugin --profile web add dsh-checkpoint-diff     # this plugin

Both are bundle plugins: dsh plugin add appends them to the profile's dsh.profile.bundles, and each package's cordis.patch.yml inserts its row. Restart the harness for host-side changes; the browser bundle is served with cache-control: no-cache, so a page refresh picks up client fixes.

The bundle layers must mount rewind before diff (the diff plugin reuses rewind's checkpoints storage domain; the bundle order in dsh.profile.bundles decides this — verify it after remove/add cycles, which re-sort dependencies).

Usage

GUI

A Diff button appears in the session header action row. It opens a panel:

  1. the toolbar has a scope switch — This session (current session only, default) or This project (every session sharing the workspace, with a branch dropdown when fork lineage is available: root branch + side branches, titles from readTitle);
  2. pick two time nodes with the from / to selects (defaults: first → latest); each option shows the derived intent label when one is available (e.g. #a1b2c3d4 14:02 · edit README.md), prefixed with the owning session's short id in project scope;
  3. the left pane lists changed files as a collapsible directory tree — directory rows show their A/M/D counts and toggle their subtree; file rows keep the A/M/D badge;
  4. click a file for its line diff (red = deleted, green = added); the diff head carries ↑/↓ buttons that jump between the change blocks (block center aligned; at the last block ↓ first shows a toast and only wraps on the next press — ↑ symmetric), and the view opens at the first change block of the file.

Rollback. A collapsible Restore workspace card sits below the toolbar, separate from the node-diff area: pick the node you want to restore to (defaults to the latest node; every file row also carries a button to restore just that file), press Preview restore to see the plan (would-restore / unchanged / skipped files, plus files created after the node that will be left in place), then Apply restore. The panel refreshes the timeline afterwards.

While a restore plan is showing, click any would-restore file (shown as an obvious 🔍 file link) to see the current workspace → target snapshot diff in the right pane, tagged "Restore preview: …" — the toolbar from/to selects switch to a read-only "current workspace → target" pair so the comparison direction is always obvious (click a file in the tree to exit back to the normal from/to diff). The preview belongs to the restore section: picking other from/to versions never clears it, and changing the restore target reloads it against the new target. After an apply, the status area lists what was restored without offering stale preview rows; an ↩ Undo this restore button reverts the restore in one shot (overwritten files get their previous content back; files the restore created are removed; anything changed since is left alone). The diff pane also remembers your last viewed node pair: a small Last view line below it jumps back to it (even across session/project scope).

Fork junctions crossed by the selected range are shown as ⤷ fork markers; an incomplete lineage renders an "earlier history unavailable" hint.

Click anywhere outside the panel to close it.

Command

/diff                      list this session's checkpoint timeline
/diff <from> <to>          file summary + per-file line diffs between two nodes
/diff --project            list the project timeline (all sessions, branch header)
/diff --project <from> <to>  diff between two nodes across sessions
/rollback [--project] [--dry-run] <node> [<path>...]
                           restore workspace files from a time node
/rollback --undo           revert the most recent restore (single-shot)

<from> / <to> accept an id prefix or latest, e.g. /diff a1b2c3d4 latest. In project scope, ambiguous prefixes prefer the current session's records.

/rollback restores every file of the node, or only the given paths; latest is a valid node. --dry-run prints the plan without writing anything (would restore N file(s), M unchanged, K skipped + the files that will be left in place); --project addresses nodes from any session sharing this workspace. Rollback never deletes files — contents are overwritten in place. /rollback --undo reverts the most recent restore (in this process): files return to their pre-restore content, and files the restore created are removed; files changed since the restore are skipped (all skipped → error).

HTTP API

Served by the harness webServer at the /checkpoint-diff/api prefix (same-origin JSON). Read endpoints are GET-only and accept an optional scope=project parameter to address records across sessions; rollback and rollback-undo are POST endpoints (the only endpoints that write, and they only write into the session workspace):

Endpoint Method Params / Body Returns
/api/timeline GET session, scope? {ok, records[]} — newest-first timeline views; scope=project adds branches[] and markers[]
/api/summary GET session, from, to, scope? {ok, from, to, files[{path,status}], totalFiles, truncated}
/api/file-diff GET session, from, to, path, scope? {ok, ops[{type:'ctx'|'del'|'add', text, a?, b?}], truncated, binary}
/api/preview-diff GET session, target, path, scope? {ok, path, ops[], truncated, binary, present} — current workspace → target snapshot diff (read-only)
/api/rollback POST JSON {session, target, scope?, paths?, dryRun?} {ok, dryRun, scope, target, files[{rel,action,reason?,mode?}], restored, unchanged, skipped, leftovers, notes}
/api/rollback-undo POST JSON {session} {ok, target, time, restored, removed, skipped[]} — reverts the most recent restore

For /api/rollback: target is an id prefix or latest; paths limits the restore to those files (omit = whole node); dryRun: true plans without writing (restored then means would restore). files[].action is restore | unchanged | skip; leftovers lists files created after the node that were left in place (never deleted).

For /api/preview-diff: the diff direction is current → snapshot (del = lines the restore would remove, add = lines it would bring back); present is both or workspace-missing (file does not exist in the workspace yet).

For /api/rollback-undo: restored = files rewritten to their pre-restore content, removed = files the restore created that were deleted (the one "never deletes" exception), skipped = files left alone because they changed since the restore (all skipped → 409). Undo state lives in process memory only — a harness restart clears it (then 404 nothing to undo).

Errors: {ok:false, error} (400 bad params / mixed providers / bad scope / unsafe paths, 404 unknown session, file absent from the node, or endpoint, 405 wrong method, 413 oversized body, 500 storage or git failures).

Data contract

The public contract of this plugin's guarantees — consumption contract + rollback safety contract: docs/contract.md.

  • Reads the checkpoints storage domain (schema re-declared in lib/domain.mjs; the spec is not exported by rewind). Reuses the domain already opened by rewind (storageDomain.get() first, open() fallback with an already-open retry window).
  • Records belong to a (sessionId, cwd) pair. scope=session (default) filters to the current session; scope=project merges every session sharing the workspace key and organizes them by /rewind fork lineage (sessionQuery.traceSession, optional service — absent = flat merge with no branch markers).
  • Intent labels are derived read-only from the session log: tool/call events whose (turn, step) matches the checkpoint are indexed per request; a name matching the record's triggerTool wins, fs/*-intent triggers prefer the step's first mutating tool, otherwise the step's first call, and a missing log falls back to the raw trigger tool (lib/labels.mjs). Live sessions are read via session.events; cold sessions via sessionQuery.readSession (both fail soft).
  • Snapshots are pre-mutation states: /diff <from> <to> compares the from snapshot against the to snapshot — to does not include changes made after it.
  • Rollback restores a node's snapshot file set into the current workspace: for git nodes that is the tracked file tree of the snapshot object, for copy nodes the manifest file list. Anything in the workspace that is not in the node's set is left in place and reported (leftovers). The git provider refuses to roll back when the session cwd is not the repository root (snapshot tree paths are root-relative). The most recent apply can be reverted once with /rollback --undo (in-process only).
  • Unreferenced git objects are reaped by git gc (default ~2 weeks) and maxSnapshots/maxSnapshotBytes quotas prune old records — missing nodes degrade gracefully.

Development

pnpm install
pnpm test                 # unit tests (diff engine, timeline, labels, tree, jsdom panel smoke tests)
pnpm test:integration     # assembled headless integration vs. real dsh-checkpoint-rewind
pnpm build:client         # rebuild the browser bundle -> lib/client.js

See CONTRIBUTING.md for conventions and the ARCHITECTURE.md for the design. The integration suite uses the local harness deployment (rc.5 packages are not on the npm registry; see scripts/link-profile-deps.mjs).

Roadmap

  • Projection-based queries — the scope=project timeline currently scans the whole checkpoints table per request; a sessionProjections unit could index by workspace key once the host event vocabulary covers checkpoint/* events.
  • Branch-aware diffing UI — visual branch lines between time nodes (the panel currently shows fork markers in the summary area only).

Acknowledgements

This plugin is built on top of dsh-checkpoint-rewind by PerryLink — the checkpoint producer whose read-only storage domain (checkpoints) and snapshot layout this project consumes. It is not a fork and shares no code with it: the two integrate through the storage contract only. Third-party notices: THIRD_PARTY_NOTICES.md.

License

Apache-2.0 — see LICENSE. Third-party notices in THIRD_PARTY_NOTICES.md.

Content from the project README on GitHub ↗