pi-rewind
The rewind experience Pi deserves — 1 checkpoint per turn, /rewind command, safe restore, redo stack
Pi already had checkpoint extensions. This one combines the best of all of them.
checkpoint-pi brought safe restore and smart filtering. pi-rewind-hook added resume checkpoints and status UI. But neither had a dedicated /rewind command, per-tool granularity, or a redo stack — features that Claude Code, Cline, and OpenCode ship built-in.
We researched 11 coding agents (820K tokens of analysis), studied both existing Pi extensions, and merged the best ideas into one extension that closes every gap.
| Feature | Trigger | Status |
|---|---|---|
1 checkpoint per turnAfter all tools finish — like Cline. Label: "prompt" → tools | turn_end | Done |
| Descriptive labelsUser prompt + tool list persisted in git refs | Automatic | Done |
/rewind commandCheckpoint browser → diff preview → restore | Type /rewind | Done |
Esc+Esc shortcutQuick files-only rewind | Double Escape | Done |
| Restore modesFiles + conversation, files only, conversation only | Picker menu | Done |
| Redo stackMulti-level undo of rewinds | "↩ Undo last rewind" | Done |
| Safe restoreNever deletes node_modules, .venv, large files | Automatic | Done |
| Branch safetyBlocks cross-branch restore (avoids OpenCode bug) | Automatic | Done |
| Smart filteringIgnores 13 dir patterns + files >10MiB + dirs >200 files | Automatic | Done |
| Resume checkpointSnapshot on session start for fallback | session_start | Done |
Footer status◆ 5 checkpoints in footer | Automatic | Done |
| Auto-pruningKeeps max 100 checkpoints per session | turn_end | Done |
| Fork/tree integrationRestore prompts on /fork and /tree navigation | session_before_fork/tree | Done |
Git refs storageSurvives restarts under refs/pi-checkpoints/ | Automatic | Done |
| Diff previewShow changes before confirming restore | /rewind flow | Done |
| "Summarize from here"Compact conversation from checkpoint forward | ctx.compact() | Planned |
| 15 features | 14 done |
Two-layer split: core is independently testable
core.ts has zero Pi dependency — pure git operations via spawn(). The extension layer (index.ts) wires Pi events to core functions. This means the 646-line core can be tested with just git and Node.js, no Pi runtime needed.
📦 Source files
src/core.ts— 654 LOCGit operations, filtering, safe restoresrc/index.ts— 253 LOCPi event hooks, turn-end checkpointingsrc/commands.ts— 339 LOC/rewind, Esc+Esc, fork/tree handlerssrc/state.ts— 70 LOCShared mutable statesrc/ui.ts— 33 LOCFooter status indicator
🧪 Testing
tests/core.test.ts— 327 LOC- 20 tests covering: utils, git helpers, CRUD, snapshot/restore, safe restore, branch safety, tool checkpoints, pruning
- Runs on disposable temp repos — no fixtures
npx tsx tests/core.test.ts
Checkpoint data structure
Identity:
id, sessionId, trigger (turn/tool/resume/before-restore), turnIndex, toolNameGit state:
headSha, indexTreeSha (staged), worktreeTreeSha (full snapshot)Safety:
preexistingUntrackedFiles[], skippedLargeFiles[], skippedLargeDirs[]Restore is a 4-step process:
reset --hard HEAD → read-tree -u worktree → safe clean (only new files) → read-tree index
Checkpoint lifecycle: when snapshots are created, stored, and restored.
flowchart TD
SS["session_start"]:::event --> RC["Create resume checkpoint"]:::checkpoint
RC --> STATUS["Update footer: ◆ N checkpoints"]:::ui
BA["before_agent_start"]:::event --> PROMPT["Capture user prompt"]:::step
TC["tool_call"]:::event --> ARGS["Capture tool args (path, cmd)"]:::step
TEE["tool_execution_end"]:::tool --> MUT{"write / edit / bash?"}:::decision
MUT -->|"Yes"| ACC["Accumulate description"]:::step
MUT -->|"No (read, grep...)"| SKIP["Skip"]:::skip
TEND["turn_end"]:::event --> HAD{"Had mutations?"}:::decision
HAD -->|"No"| SKIP2["Skip — no checkpoint needed"]:::skip
HAD -->|"Yes"| CREATE["Create 1 checkpoint"]:::checkpoint
CREATE --> DESC["Label: prompt + all tools"]:::step
DESC --> STATUS
CREATE --> PRUNE["Auto-prune if > 100"]:::prune
classDef event fill:#1e1f32,stroke:#818cf8,stroke-width:2px,color:#e5e7eb
classDef checkpoint fill:#0c3d2e,stroke:#34d399,stroke-width:2px,color:#e5e7eb
classDef tool fill:#2e1f0c,stroke:#fbbf24,stroke-width:2px,color:#e5e7eb
classDef decision fill:#1e1f32,stroke:#a78bfa,stroke-width:2px,color:#e5e7eb
classDef step fill:#1e1f32,stroke:#60a5fa,stroke-width:2px,color:#e5e7eb
classDef ui fill:#1e1f32,stroke:#22d3ee,stroke-width:2px,color:#e5e7eb
classDef skip fill:#1e1f32,stroke:#4b5563,stroke-width:1px,color:#9ca3af
classDef prune fill:#1e1f32,stroke:#f87171,stroke-width:2px,color:#e5e7eb
flowchart TD
TRIGGER["/rewind or Esc+Esc"]:::event --> LIST["List checkpoints (newest first)"]:::step
LIST --> PICK["User picks checkpoint"]:::ui
PICK --> DIFF["Show git diff --stat preview"]:::step
DIFF --> CONFIRM{"Confirm?"}:::decision
CONFIRM -->|"Cancel"| DONE["Done"]:::skip
CONFIRM -->|"Yes"| MODE["Pick restore mode"]:::ui
MODE --> M_ALL["Files + conversation"]:::restore
MODE --> M_FILES["Files only"]:::restore
MODE --> M_CONV["Conversation only"]:::restore
M_ALL --> BEFORE["Create before-restore checkpoint (safety net)"]:::checkpoint
M_FILES --> BEFORE
BEFORE --> RESTORE["Restore worktree from snapshot"]:::restore
RESTORE --> S1["1. git reset --hard HEAD"]:::git
S1 --> S2["2. git read-tree -u worktree-tree"]:::git
S2 --> S3["3. Safe clean (only NEW files)"]:::git
S3 --> S4["4. git read-tree index-tree"]:::git
S4 --> PUSH_REDO["Push to redo stack"]:::checkpoint
M_CONV --> NAV["ctx.navigateTree(entryId)"]:::step
M_ALL --> NAV
PUSH_REDO --> NOTIFY["Notify: Rewound to checkpoint N"]:::ui
classDef event fill:#1e1f32,stroke:#818cf8,stroke-width:2px,color:#e5e7eb
classDef step fill:#1e1f32,stroke:#60a5fa,stroke-width:2px,color:#e5e7eb
classDef ui fill:#1e1f32,stroke:#22d3ee,stroke-width:2px,color:#e5e7eb
classDef decision fill:#1e1f32,stroke:#a78bfa,stroke-width:2px,color:#e5e7eb
classDef restore fill:#2e1f0c,stroke:#fbbf24,stroke-width:2px,color:#e5e7eb
classDef checkpoint fill:#0c3d2e,stroke:#34d399,stroke-width:2px,color:#e5e7eb
classDef git fill:#14151f,stroke:#9ca3af,stroke-width:1px,color:#e5e7eb
classDef skip fill:#1e1f32,stroke:#4b5563,stroke-width:1px,color:#9ca3af
flowchart LR
UNTRACKED["Current untracked files"]:::step --> CHECK{"Was it there at checkpoint time?"}:::decision
CHECK -->|"Yes (pre-existing)"| KEEP["KEEP - never delete"]:::safe
CHECK -->|"No (new file)"| CHECK2{"In ignored dir? Large file?"}:::decision
CHECK2 -->|"Yes"| KEEP2["KEEP - protected"]:::safe
CHECK2 -->|"No"| DELETE["DELETE - git clean"]:::danger
classDef step fill:#1e1f32,stroke:#60a5fa,stroke-width:2px,color:#e5e7eb
classDef decision fill:#1e1f32,stroke:#a78bfa,stroke-width:2px,color:#e5e7eb
classDef safe fill:#0c3d2e,stroke:#34d399,stroke-width:2px,color:#e5e7eb
classDef danger fill:#2e0c0c,stroke:#f87171,stroke-width:2px,color:#e5e7eb
What gets protected during safe restore?
Ignored directories (13 patterns):
node_modules, .venv, venv, env, .env, dist, build, .pytest_cache, .mypy_cache, .cache, .tox, __pycache__Large files: Files exceeding 10 MiB are skipped during snapshot and never deleted on restore.
Large directories: Directories with 200+ files are skipped entirely — prevents accidentally snapshotting or deleting generated content.
Based on research across 11 agents (820K tokens analyzed via Codex). Ranked by rewind completeness.
| # | Agent | Rewind feature | Per-tool | Redo | Files + Conv |
|---|---|---|---|---|---|
| 1 | Cline | Checkpoints per tool call, Compare/Restore UI | Yes | No | Yes |
| 2 | Gemini CLI | /rewind + Esc+Esc + /restore | No | No | Yes |
| 3 | Claude Code | /rewind + Esc+Esc, "Summarize from here" | No | No | Yes |
| 4 | OpenCode | /undo + /redo, step-level patches | Yes | Yes | Yes |
| — | pi-rewind | /rewind + Esc+Esc, 1 per turn, redo stack, diff preview, safe restore | Yes | Yes | Yes |
| 9 | Aider | /undo — last aider commit only | No | No | No |
| 10 | Codex CLI | Esc+Esc thread rollback (conversation only) | No | No | Conv only |
What pi-rewind combines from the best
- From Cline: 1 checkpoint per model response (not per tool — less noise)
- From Claude Code / Gemini:
/rewindcommand +Esc+Escshortcut - From OpenCode: Redo stack for multi-level undo
- From Claude Code: Diff preview before restore (planned: "Summarize from here")
- Unique to pi-rewind: Descriptive labels (
"user prompt" → write → file.ts, edit → other.ts) - Unique to pi-rewind: Safe restore that never deletes node_modules, .venv, or large files
| Capability | checkpoint-pi | pi-rewind-hook | pi-rewind |
|---|---|---|---|
Dedicated /rewind command | No | No | Yes |
Esc+Esc shortcut | No | No | Yes |
| 1 checkpoint per turn (not per tool) | No | No | Yes |
| Diff preview before restore | No | No | Yes |
| Redo stack (multi-level undo) | No | No | Yes |
| Safe restore (preserve untracked) | Yes | No | Yes |
| Branch safety (cross-branch protection) | Yes | No | N/A (shadow git) |
| Smart filtering (13 dirs, large files) | Yes | No | Yes |
| HEAD + index + worktree capture | Yes | No | Yes |
| Resume checkpoint (session start) | No | Yes | Yes |
| Footer status indicator | No | Yes | Yes |
| Auto-pruning | No | Yes | Yes |
| Undo last rewind | Yes | Yes | Yes |
| Unit tests | 906 LOC | None | 327 LOC |
| Two-layer architecture (testable core) | Yes | No | Yes |
No shell injection (spawn() only) | Yes | Partial | Yes |
| 15 capabilities | 8 | 5 | 15 |
Built on the shoulders of two great Pi extensions
pi-rewind is not a clone — it's a clean-room rewrite that combines the best architectural decisions from both projects with new features inspired by the top coding agents.
UX inspiration: pi-rewind-hook by nicobailon — resume checkpoint, footer status, notifications, auto-pruning
Feature targets: Cline (per-tool), Claude Code (/rewind + Esc+Esc), OpenCode (/undo + /redo), Gemini CLI (/rewind + /restore)
MIT License · github.com/arpagon/pi-rewind