Skip to content

Commit 947df98

Browse files
feat(v1.0.0): fork-on-delete + heatmap + true token counts (#4)
Major rewrite. Every `delete` now produces a NEW session file with a fresh UUID instead of mutating the source. Output prints a `claude --resume <new-id>` command. The new id forces a fresh prefix- cache slot in Claude Code, so /context immediately reflects the smaller size — no stale cache. - delete: forks instead of mutating; output adds new_session_id, new_path, resume_command. Source file is never touched. - heatmap: new subcommand. Ranks conversational turns by true token count (tiktoken on whole JSONL line — text + tool args + tool stdout + metadata). Drop the heaviest first. - info / show: token counts now reflect wire size (typically 2-3x the old text-only estimate). - list / search: forked files are tagged with a cc-session-fork sentinel line and surface as is_fork=true with an [edited] title prefix. - Removed: --force flag, lsof concurrent-open detection, .bak backup, restore subcommand. None are needed when the source is never mutated. - AGENTS.md: agent guide moved out of source into a real markdown file at the repo root. `cc-session agent-guide` reads it via include_str! so GitHub renders it natively and the binary stays self-contained. - v1.0.0 bump.
1 parent d7883a1 commit 947df98

18 files changed

Lines changed: 962 additions & 612 deletions

File tree

AGENTS.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# cc-session agent guide
2+
3+
You are an LLM driving cc-session non-interactively. This guide is the
4+
single source of truth for how to use it. Read it once, then operate.
5+
6+
## What this CLI does
7+
8+
Edits Claude Code session JSONL files at `~/.claude/projects/<slug>/<uuid>.jsonl`.
9+
It can browse, search, inspect, and surgically delete messages from any session
10+
while keeping `tool_use` / `tool_result` pairs and conversational turns intact.
11+
12+
**Important behavioral note (v1+):** `delete` NEVER mutates the source file.
13+
It always writes a NEW session file with a fresh UUID and prints a
14+
`claude --resume <new-id>` command. The original is never touched. There is
15+
no `--force` flag, no lsof check, no `.bak` file — none of those are needed
16+
when forking.
17+
18+
## Standard workflow
19+
20+
1. Discover sessions:
21+
```sh
22+
cc-session list --json --limit 20
23+
cc-session search "<query>" --json --limit 10
24+
```
25+
2. Inspect one session:
26+
```sh
27+
cc-session info <id-or-path> --json
28+
cc-session show <id-or-path> --json
29+
```
30+
3. Find heaviest turns to drop:
31+
```sh
32+
cc-session heatmap <id-or-path> --json --limit 10
33+
```
34+
4. Plan an edit (always dry-run first):
35+
```sh
36+
cc-session delete <id> --indices 4,6 --dry-run --json
37+
```
38+
5. Apply (writes a new session file, original untouched):
39+
```sh
40+
cc-session delete <id> --indices 4,6 --json
41+
```
42+
Output includes `new_session_id`, `new_path`, `resume_command`.
43+
6. (Optional) self-update:
44+
```sh
45+
cc-session update [--version v1.0.0]
46+
```
47+
48+
## Target argument (`<id-or-path>`)
49+
50+
For `show` / `info` / `heatmap` / `delete` the first positional arg accepts:
51+
52+
- a full filesystem path to a `.jsonl` file
53+
- a full session UUID (preferred — unambiguous)
54+
- any unique substring of a session UUID (8+ chars usually fine)
55+
56+
If a substring matches multiple sessions, the command errors and lists the
57+
candidates. Pass a longer prefix to disambiguate.
58+
59+
## Index semantics
60+
61+
Indices are 0-based positions in the raw JSONL (one per line). Use
62+
`cc-session show --json` to map message text → index. Note:
63+
64+
- "Visible" messages (user / assistant text) are a subset; system messages,
65+
`tool_use` blocks, `tool_result` blocks, attachments, and harness wrappers
66+
(`<bash-input>`, `<system-reminder>`, etc.) are hidden by default. Pass
67+
`--include-hidden` to see them in `show`.
68+
- Indices in the SOURCE session are stable across deletes (because deletes
69+
fork instead of mutating). Each new fork has its own index space — if you
70+
chain edits, re-run `show` against the new session id.
71+
72+
## Auto-pair (always on)
73+
74+
Two safety extensions run on every delete request:
75+
76+
1. `tool_use``tool_result` blocks always travel together. Marking either
77+
side pulls the other.
78+
2. Turn-level pairing: a "turn" = visible user msg + every message that
79+
follows it until the next visible user msg. Marking ANY message in a
80+
turn marks the whole turn (user prompt + assistant reply + intermediate
81+
tool calls).
82+
83+
The delete output reports `requested` (what you asked) and `paired_added`
84+
(what auto-pair added). Always inspect both before applying.
85+
86+
## Resume safety: parentUuid auto-relink
87+
88+
Every fork rewrites surviving messages whose `parentUuid` would point to
89+
a deleted ancestor, walking up to the nearest surviving ancestor (or null
90+
at the root). Reported as `parent_uuid_relinked`. Foreign parent uuids
91+
(referring to messages not in the file) are preserved verbatim.
92+
93+
## `delete` output JSON
94+
95+
```json
96+
{
97+
"source_path": "<absolute path of input session>",
98+
"new_session_id": "<uuid>",
99+
"new_path": "<absolute path of fork>",
100+
"resume_command": "claude --resume <uuid>",
101+
"parent_uuid_relinked": 0,
102+
"requested": [],
103+
"after_auto_pair": [],
104+
"paired_added": [],
105+
"total_messages_before": 0,
106+
"total_messages_after": 0,
107+
"dry_run": false,
108+
"saved": true,
109+
"warnings": []
110+
}
111+
```
112+
113+
`new_session_id`, `new_path`, `resume_command` are populated even in dry-run
114+
(preview values). `saved` is `true` when the fork file was actually written.
115+
116+
## `show` output JSON (per message)
117+
118+
```json
119+
{
120+
"index": 0,
121+
"role": "user",
122+
"type": "user",
123+
"timestamp": "2026-06-12T00:00:00Z",
124+
"tokens": 0,
125+
"visible": true,
126+
"has_tool_use": false,
127+
"has_tool_result": false,
128+
"tool_use_ids": [],
129+
"tool_result_ids": [],
130+
"text": "...",
131+
"truncated": false
132+
}
133+
```
134+
135+
`tokens` is tiktoken `cl100k_base` counted on the WHOLE raw JSONL line
136+
(text + `tool_use` input + `tool_result` content + metadata). `text` is a
137+
400-char preview by default; pass `--full` to get the full body.
138+
139+
## `info` output JSON
140+
141+
```json
142+
{
143+
"path": "...", "project": "...", "session_id": "...", "title": "...",
144+
"modified": "...", "size": 0,
145+
"is_fork": false,
146+
"fork_origin": null,
147+
"total_messages": 0, "visible_messages": 0,
148+
"user_messages": 0, "assistant_messages": 0,
149+
"tool_use_count": 0, "tool_result_count": 0,
150+
"orphan_result_indices": [],
151+
"estimated_tokens": 0
152+
}
153+
```
154+
155+
`estimated_tokens` is the sum of true per-msg counts.
156+
157+
## `heatmap` output JSON
158+
159+
```json
160+
{
161+
"path": "<absolute path>",
162+
"session_id": "<uuid>",
163+
"total_messages": 0,
164+
"total_tokens": 0,
165+
"turns": [
166+
{
167+
"anchor_idx": 0,
168+
"start_idx": 0,
169+
"end_idx": 0,
170+
"msg_count": 0,
171+
"tokens": 0,
172+
"has_tool_use": false,
173+
"preview": "..."
174+
}
175+
]
176+
}
177+
```
178+
179+
`turns` is sorted by `tokens` descending. Drop the heaviest first.
180+
181+
## `list` / `search` output JSON (per entry)
182+
183+
```json
184+
{
185+
"project": "...", "session_id": "...", "title": "...",
186+
"modified": "...", "size": 0, "path": "...",
187+
"is_fork": false,
188+
"fork_origin": null
189+
}
190+
```
191+
192+
`title` carries an `[edited] ` prefix when `is_fork` is true.
193+
194+
## Selection flags for `delete`
195+
196+
You may combine any/all; the union is taken before auto-pair runs.
197+
198+
```
199+
--indices 3,5,7 # exact indices (comma-separated)
200+
--range lo..hi # inclusive range, both ints
201+
--from-top N # first N messages
202+
--from-bottom N # last N messages
203+
```
204+
205+
At least one selection flag is required.
206+
207+
## Exit codes
208+
209+
| Code | Meaning |
210+
|------|---------|
211+
| `0` | success |
212+
| `1` | generic error (parse failure, IO error, ambiguous target, ...) |
213+
| `2+` | reserved for future structured errors |
214+
215+
Always inspect stderr on non-zero exit for the human-readable cause.
216+
217+
## Environment overrides
218+
219+
| Var | Effect |
220+
|-----|--------|
221+
| `CC_SESSION_VERSION` | pin a specific release (used by `update`) |
222+
| `CC_SESSION_INSTALL_DIR` | where `install.sh` drops the binary |
223+
| `CC_SESSION_INSTALLER_URL` | override installer URL for `update` (testing) |
224+
225+
## End-to-end recipe (real run, copy this shape)
226+
227+
```sh
228+
# 1. Locate the session id. Inside Claude Code: /status -> Session ID.
229+
cc-session list --json --limit 10
230+
231+
# 2. See the wire size. estimated_tokens reflects whole-line tiktoken
232+
# (text + tool_use args + tool_result content + metadata) — usually
233+
# 2-3x larger than what plain message text would suggest.
234+
cc-session info <id> --json
235+
236+
# 3. Find the heaviest CONVERSATIONAL TURNS. A turn rolls up the user
237+
# prompt + every assistant/tool message it triggered up to the next
238+
# visible user prompt. This matches what auto-pair will delete.
239+
cc-session heatmap <id> --json --limit 10
240+
241+
# 4. Pick a contiguous block of turns that are clearly noise (long
242+
# iteration loops, exploratory tool dumps, repeated re-reviews of
243+
# the same doc, etc). Prefer one --range over many --indices: it's
244+
# less likely to leave parentUuid orphans, and even when it does,
245+
# auto-relink fixes them (and reports parent_uuid_relinked).
246+
cc-session delete <id> --range <lo>..<hi> --dry-run --json
247+
248+
# 5. Apply. The output's `resume_command` is ready to paste.
249+
cc-session delete <id> --range <lo>..<hi> --json
250+
251+
# 6. Resume the NEW id in Claude Code:
252+
# claude --resume <new_session_id>
253+
#
254+
# The new id forces a fresh prefix-cache slot, so Claude Code's
255+
# /context immediately reflects the smaller size — no stale cache.
256+
```
257+
258+
## Useful examples (one-liners)
259+
260+
```sh
261+
# find the heaviest turns and drop the worst three
262+
cc-session heatmap <id> --json --limit 5
263+
cc-session delete <id> --indices <a>,<b>,<c> --dry-run --json
264+
cc-session delete <id> --indices <a>,<b>,<c> --json
265+
266+
# delete top 50 messages of a long session, dry run first
267+
cc-session delete <id> --from-top 50 --dry-run --json
268+
269+
# purge messages 200..280 inclusive
270+
cc-session delete <id> --range 200..280 --dry-run --json
271+
272+
# find a session about "auth middleware" and inspect
273+
cc-session search "auth middleware" --json --limit 1
274+
cc-session show <id-from-above> --json
275+
276+
# chain edits: each delete produces a new id; pass that id back to
277+
# cc-session for the next trim. Forks are marked is_fork=true in list.
278+
cc-session delete <new_id_from_step_5> --range ... --json
279+
```
280+
281+
## Things this CLI will NOT do
282+
283+
- Edit message contents in place.
284+
- Reorder messages.
285+
- Merge or split sessions.
286+
- Mutate the source session (every delete forks).

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cc-session"
3-
version = "0.4.0"
3+
version = "1.0.0"
44
edition = "2021"
55
rust-version = "1.75"
66
description = "Interactive TUI editor for Claude Code session JSONL files. Browse, search, and surgically delete messages while preserving tool_use/tool_result pairing."
@@ -28,6 +28,7 @@ thiserror = "2"
2828
clap = { version = "4", features = ["derive"] }
2929
chrono = { version = "0.4", default-features = false, features = ["std", "clock", "serde"] }
3030
nucleo-matcher = "0.3"
31+
uuid = { version = "1", features = ["v4"] }
3132

3233
[dev-dependencies]
3334
assert_fs = "1"

0 commit comments

Comments
 (0)