chsum 2.2.1__tar.gz → 3.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
1
  __pycache__/
2
2
  *.pyc
3
+
4
+ # Build artefacts: `python -m build` writes them, the release publishes from CI.
5
+ dist/
6
+ *.egg-info/
3
7
  .venv/
4
8
  .DS_Store
5
9
  .devharness/*
@@ -0,0 +1,91 @@
1
+ # Contributing
2
+
3
+ `README.md` is the user-facing doc. This file is the mechanics.
4
+
5
+ ## Setup
6
+
7
+ ```sh
8
+ pipx install --editable .
9
+ ```
10
+
11
+ The `chsum` package in this checkout is then what runs, from anywhere. Python
12
+ ≥3.10, stdlib only — `dependencies` in `pyproject.toml` stays empty, which is
13
+ what lets chsum run against any corpus with no venv. `claude-history` is a
14
+ runtime requirement but a separate binary; only the session listing works
15
+ without it.
16
+
17
+ ## Layout
18
+
19
+ ```
20
+ chsum.py launcher: a package directory cannot be run by path, and
21
+ the plugin's hooks and CI both need one that can
22
+ chsum/
23
+ __init__.py what `import chsum` gives the hooks; `main`, `entrypoint`
24
+ __main__.py `python3 -m chsum`
25
+ core.py the commands, the readers, the renderers
26
+ checkpoints.py the git layer: one commit per call that changed the tree
27
+ sources/
28
+ __init__.py the registry, and the translated-transcript cache
29
+ claude.py `~/.claude/projects`, read where it lies
30
+ codex.py `~/.codex/sessions`, translated to the common shape
31
+ ```
32
+
33
+ `checkpoints.py` holds the git plumbing and imports nothing from `core` — the
34
+ dependency runs one way, and `core` installs its tracer with `set_tracer` rather
35
+ than the layer reaching back for it. The views that render checkpoints and the
36
+ `checkpoints` command stay in `core`.
37
+
38
+ Adding a tool means one file under `sources/` and one `register(...)` call:
39
+ where its sessions sit, which directory each ran in, and a translation to the
40
+ record shape `core` reads. Nothing in `core` names a format.
41
+
42
+ A translated file is cached against its original's size and mtime **and** the
43
+ bytes of the translator module. Editing `codex.py` therefore rebuilds every
44
+ Codex translation on the next run; without that, a corrected rule would serve
45
+ its old output forever, since a finished session is never written to again.
46
+
47
+ ## Checking a change
48
+
49
+ ```sh
50
+ python3 -m unittest discover -s tests -t tests
51
+ ```
52
+
53
+ Stdlib `unittest`, no test dependency, ~10s. Each git test builds a throwaway
54
+ repository under `tempfile` and binds every call with `git -C <repo>`, so a test
55
+ that resolves a path wrongly fails against that repository rather than reaching
56
+ the checkout it runs from. `publish.yml` runs the suite before it builds.
57
+
58
+ The suite covers what fails outside chsum — a moved `HEAD`, a disturbed index, a
59
+ lost history — and the translation and view rules that were measured rather than
60
+ assumed. It does not cover the renderers' prose. Compile as well, then run the
61
+ commands the change could reach:
62
+
63
+ ```sh
64
+ python3 -m compileall -q chsum chsum.py hooks
65
+ chsum # listing
66
+ chsum digest --last # digest, incl. a subagent one if the session had any
67
+ chsum --since 7d -n 0 # listing over a window
68
+ chsum --all --source codex # another tool's sessions, translated on the way in
69
+ chsum find "…" --all # search, which runs one pass per corpus and merges
70
+ chsum mark --list # from inside Claude Code — marks need a live session
71
+ ```
72
+
73
+ Read the output rather than checking it exits 0. Every line lands in a future
74
+ context window, so a wrong or unmarked-truncated one is the failure mode.
75
+
76
+ ## Releasing
77
+
78
+ The version lives in `pyproject.toml`, and `publish.yml` refuses a tag that
79
+ disagrees with it.
80
+
81
+ ```sh
82
+ # bump it, commit, then
83
+ git tag v1.0.4 && git push origin v1.0.4
84
+ ```
85
+
86
+ The tag publishes to PyPI via trusted publishing (no stored token) and asks
87
+ [InDate/indate-tools](https://github.com/InDate/indate-tools) to repin. The
88
+ marketplace opens a PR: publishing and pushing an update to installed users are
89
+ separate decisions.
90
+
91
+ PyPI version numbers cannot be reused. A bad release is fixed by the next one.
chsum-3.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 InDate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,18 @@
1
+ Metadata-Version: 2.5
2
+ Name: chsum
3
+ Version: 3.0.2
4
+ Summary: Work logs and reload-ready context from coding-agent conversations. Deterministic: no model, nothing invented.
5
+ Author: InDate
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: claude,claude-code,context,transcripts,work-log
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+
1
12
  # chsum
2
13
 
3
- Work logs and reload-ready context from your Claude Code conversations.
14
+ Work logs and reload-ready context from your coding-agent conversations —
15
+ Claude Code and Codex CLI, listed and digested side by side.
4
16
 
5
17
  **Digests and listings are never generated by a model.** Every line of that output
6
18
  is either copied verbatim from a transcript or computed from it, so nothing can be
@@ -32,6 +44,9 @@ from.
32
44
  ends included. → [`chsum`](#chsum-1)
33
45
  - **You remember what was said, not when.** — `chsum find "…"` searches the text of
34
46
  every conversation; `--notes` searches what you noted. → [`chsum find`](#chsum-find)
47
+ - **chsum printed `01a0acf9:31` and you want that record.** — `chsum where
48
+ 01a0acf9:31` prints the `sed` command for it, resolved to a real path. Pipe it
49
+ to `sh` to run it. → [`chsum where`](#chsum-where)
35
50
  - **You need the exact command Claude ran, or its full output.** — `chsum digest
36
51
  <ref> --commands` lists every Bash call in order; `--call <id>` prints one whole.
37
52
  → [`chsum digest`](#the-row-views)
@@ -49,6 +64,8 @@ from.
49
64
  - **`recap`'s files-touched section misses edits made outside Edit/Write.** — opt in
50
65
  to git checkpoints; the plugin's hooks run `chsum hook`. → [`chsum hook`](#chsum-hook)
51
66
 
67
+ Coming from 2.x: [what changed](#upgrading-to-30).
68
+
52
69
  The commands, one section each:
53
70
 
54
71
  | command | what it is for |
@@ -56,6 +73,8 @@ The commands, one section each:
56
73
  | [`chsum`](#chsum-1) | list this project's sessions, newest activity first |
57
74
  | [`chsum recap`](#chsum-recap) | what happened over a window of turns, with a model-written timeline |
58
75
  | [`chsum digest`](#chsum-digest) | one conversation, verbatim and computed, plus row-level views |
76
+ | [`chsum where`](#chsum-where) | a locator to the `sed` command that prints that row |
77
+ | [`chsum checkpoints`](#chsum-hook) | the git checkpoint chains this repo holds, and dropping old ones |
59
78
  | [`chsum find`](#chsum-find) | locate a conversation or a note by what it said |
60
79
  | [`chsum note`](#chsum-note) | mark the moment that mattered |
61
80
  | [`chsum name`](#chsum-name) | rename a session to what it actually was |
@@ -356,16 +375,17 @@ same from here.
356
375
 
357
376
  One conversation, verbatim and computed: your prompts in order, each with the
358
377
  row it sits on; the files changed and the commands that did something; the last
359
- exchange; and the `sed` line that opens any row. A bare run prints the digest of
360
- the session you are in; naming a session (a ref, `--last`, `--file`) writes it to
361
- a file and prints the path, and `--stdout` prints it instead.
378
+ exchange; and the `sed` line that opens any row. Every run writes a file and prints its
379
+ path — a bare one for the session you are in, a named one (a ref, `--last`,
380
+ `--file`) for that conversation — and `--stdout` prints the document instead.
381
+ One rule for every view below, not the digest alone.
362
382
 
363
383
  https://github.com/user-attachments/assets/93f3cdac-cf82-403a-8333-5c8e42159fd7
364
384
 
365
385
  ```sh
366
- chsum digest # this session, printed
367
- chsum digest <ch_ref> # write a digest file
368
- chsum digest <ch_ref> --stdout # print it instead
386
+ chsum digest # this session, written
387
+ chsum digest <ch_ref> # that conversation
388
+ chsum digest <ch_ref> --stdout # print it instead of writing
369
389
  chsum digest --file path/to/session.jsonl # address by file
370
390
  chsum digest <ch_ref>/<agent-id> # one subagent's own digest
371
391
  chsum digest --list # this project's digest files
@@ -426,24 +446,55 @@ rest:
426
446
 
427
447
  ### The row views
428
448
 
429
- Four flags print rather than writing a file: they are lookups reached from a
430
- hint inside a digest, not artifacts to keep.
449
+ Each writes a file of its own, `<uuid>-<view>.md` beside the digest, and prints
450
+ the path; `--stdout` prints the document instead. The same rule the digest
451
+ itself follows. `chsum digest --list` names the view beside the session each
452
+ file belongs to.
431
453
 
432
454
  ```sh
433
- chsum digest <ch_ref> --messages # every message in order, unfiltered
455
+ chsum digest <ch_ref> --messages # every message whole, each call a line between them
434
456
  chsum digest <ch_ref> --tools # every tool call in order
435
457
  chsum digest <ch_ref> --commands # every Bash call in order
436
458
  chsum digest <ch_ref> --call <id> # one tool call whole, with its output
437
459
  chsum digest <ch_ref> --agents # every subagent and what it reported back
438
460
  chsum digest <ch_ref>/<agent-id> --commands # narrowed to that sidecar
461
+ chsum digest <ch_ref>/<agent-id> --messages # everything that agent said, and every call it made
462
+ chsum digest <ch_ref> --messages --stdout # print it rather than write it
463
+ chsum digest <ch_ref> --messages > out.md # or your own path
464
+ ```
465
+
466
+ All three print in timestamp order across the transcript and its sidecars, with
467
+ nothing filtered, deduplicated or collapsed. `--tools` and `--commands` print one
468
+ line per row — the call id, a `<session>:<line>` locator, local time, the tool
469
+ name and the first line of the call. A row runs long and lets the terminal
470
+ soft-wrap it rather than folding at a space: a command broken across lines can
471
+ no longer be copied in one selection.
472
+
473
+ `--messages` prints its message rows **whole**, on every ref. Messages are
474
+ prose, and a conversation clipped to a line each is the one thing this view
475
+ cannot be used for. There is no flag or size limit behind that: the turn numbers
476
+ below already select the part of a conversation you want, and a second way to
477
+ ask for less would only be a worse one.
478
+
479
+ Between them it prints every call, each the same single clipped line `--tools`
480
+ gives it. A turn headed `2 tool calls · 8 commands` states how many ran and
481
+ names none of them, which leaves the work between two replies unreadable; the
482
+ calls sitting in the conversation's own order name each one. They stay clipped
483
+ inside a turn window too, so a read of the conversation never runs through a
484
+ heredoc's body — `--call <id>` opens one whole.
485
+
486
+ Every view is broken by turn, each headed with the line the digest already
487
+ prints under that prompt:
488
+
489
+ ```
490
+ *turn 8 · 16m · 6 tool calls · 5 files · 108 commands · 12 replies*
439
491
  ```
440
492
 
441
- `--messages`, `--tools` and `--commands` each print one line per row — the id
442
- where there is one, a `<session>:<line>` locator, local time, the role or tool
443
- name, and the first line of the text — in timestamp order across the transcript
444
- and its sidecars, with nothing filtered, deduplicated or collapsed. `--call <id>`
445
- prints one tool call and its captured output whole, which is where the text a row
446
- clipped actually lives.
493
+ The head names the turn a row sits in, not the row that opens one, so a view
494
+ that keeps no messages — `--tools`, `--commands` — still shows its boundaries,
495
+ and a window opening mid-conversation says which turn it landed in. `--call <id>`
496
+ prints one tool call and its captured output whole, which is where the text a
497
+ clipped call row actually lives.
447
498
 
448
499
  `--agents` lists every subagent the session ran and each report it sent back,
449
500
  numbered where an agent returned more than once. A report is the agent's own
@@ -451,15 +502,22 @@ document and runs to thousands of characters, so it is clipped with the cut
451
502
  marked; the row it came from names where the whole text is.
452
503
 
453
504
  One or two numbers beside `--messages`, `--tools` or `--commands` narrow the view
454
- to a window of your turns and print those rows whole, so a stretch of
455
- conversation reads without leaving chsum:
505
+ to a window of your turns. `--tools` and `--commands` print those rows whole, so
506
+ a stretch of conversation reads without leaving chsum; `--messages` keeps the
507
+ shape it has unwindowed, messages whole and calls a line each:
456
508
 
457
509
  ```sh
458
510
  chsum digest --last --messages -1 # your last turn, and everything after it
459
511
  chsum digest <ch_ref> --messages 10 11 # your 10th and 11th turns
460
512
  chsum digest <ch_ref> --tools -5 -1 # what Claude called across your last five
513
+ chsum digest <ch_ref>/<agent-id> --tools 2 -1 # an agent's own turns, not yours
461
514
  ```
462
515
 
516
+ On an agent ref the turns counted are the sidecar's own: the task it was handed
517
+ is turn 1, and a prompt sent to it while it worked opens the next. Most agents
518
+ have exactly one turn, so the numbers earn their keep on a `/btw` fork that was
519
+ talked to repeatedly.
520
+
463
521
  Inside a window the rows a view steps over are listed rather than skipped
464
522
  silently, one line each with its own locator, so a jump from `2500` to `2513`
465
523
  reads as:
@@ -476,20 +534,135 @@ A tool call carries its tool and the size of what it returned, and its result ro
476
534
  folds into it. A `thinking` row is named and nothing more — the JSONL keeps its
477
535
  signature and drops the text. Harness bookkeeping rows are left out.
478
536
 
479
- A `## Sources` block at the top expands every locator to a full path and gives a
480
- worked `sed` line, so a row reaches its raw record without chsum:
537
+ A `## Sources` block at the top expands every locator to a path, `~`-relative so
538
+ it fits the width and still pastes into a shell. Where the view clipped its rows
539
+ it also gives a worked `sed` line, so a row reaches its raw record without chsum:
481
540
 
482
541
  ```
483
542
  ## Sources
484
543
 
485
- - `f1b9bbc6` — `~/.claude/projects/…/f1b9bbc6-….jsonl`
486
- - `f1b9bbc6/a190d601` — `~/.claude/projects/…/f1b9bbc6-…/subagents/agent-a190d601….jsonl`
544
+ - `f1b9bbc6` `~/.claude/projects/…/f1b9bbc6-….jsonl`
545
+ - `f1b9bbc6/a190d601` `~/.claude/projects/…/subagents/agent-a190d601….jsonl`
546
+
547
+ A row's whole text — `sed` the line its locator names:
487
548
 
488
- Open a record: `sed -n '18p' ~/.claude/projects/…/f1b9bbc6-….jsonl`
549
+ - `sed -n '18p' ~/.claude/projects/…/f1b9bbc6-….jsonl | jq -r '…'`
489
550
 
490
551
  - `01CPKjYaSD` `f1b9bbc6:26` 11:30:28 Bash `ls && wc -l chsum.py`
491
552
  ```
492
553
 
554
+ The `sed` line is an escape hatch for text a row clipped, so a row printed
555
+ whole — a message under `--messages`, any row inside a turn window — leaves it
556
+ out. One source prints as the path alone: there is nothing for a locator to pick
557
+ out, and the bare path is a line the terminal leaves intact to copy.
558
+
559
+ ## Upgrading to 3.0
560
+
561
+ Two changes break a script written against 2.x.
562
+
563
+ **Every view writes a file and prints its path.** In 2.x a bare `chsum digest`
564
+ and the row views printed their document to stdout; now they write it and stdout
565
+ carries the path alone, so `chsum digest <ref> --messages > out.md` puts a path
566
+ in that file rather than the messages. `--stdout` restores the old behaviour:
567
+
568
+ ```sh
569
+ chsum digest <ref> --messages --stdout > out.md # the document, as 2.x gave it
570
+ code $(chsum digest <ref> --messages) # what the path is for
571
+ ```
572
+
573
+ **Checkpoints are kept until dropped.** 2.x committed each turn and reset it
574
+ away, leaving it in `HEAD`'s reflog to expire in about thirty days. 3.0 chains
575
+ them under `refs/chsum/<session>`, which git keeps — they survive a `gc`, the
576
+ reflog expiring, and the removal of the worktree they were written in, and they
577
+ no longer clear themselves. `chsum checkpoints --prune 30d` is the retention
578
+ that was previously accidental, and `chsum checkpoints --migrate` rebuilds
579
+ checkpoints written by 2.x so they gain the same durability. Both are described
580
+ under [`chsum hook`](#chsum-hook).
581
+
582
+ ## Which tools' sessions it reads
583
+
584
+ Claude Code sessions, from `~/.claude/projects`, and Codex CLI sessions, from
585
+ `~/.codex/sessions`. Both appear in every listing, digest and search. A `source`
586
+ column appears where the rows disagree and stays absent where one tool wrote
587
+ them all; `chsum --source codex` narrows a listing to one tool.
588
+
589
+ Claude Code writes the record shape chsum reads, so its transcripts are read
590
+ where they lie. A Codex rollout is written once into that shape under
591
+ `<data dir>/chsum/translated/codex/`, and rebuilt whenever the rollout grows or
592
+ the translation rule changes. Every line number a drill-down prints names that
593
+ translated file, so `sed -n '1454p' <path>` lands on the record the digest
594
+ quoted.
595
+
596
+ Three properties of a Codex rollout show up in the output, and each is the file
597
+ speaking rather than a fault in the reading:
598
+
599
+ - **No titles.** Codex records no title for a thread, so a row reads
600
+ `(untitled)` until `chsum name` gives it one. That name holds in chsum; it
601
+ does not travel back to Codex, which never reads the translated copy.
602
+ - **`0s` durations.** A thread imported into Codex carries one timestamp on
603
+ every record, stamped when the import landed. Duration is the spread of the
604
+ stamps, so such a session reports `0s` however many turns it holds. Of 102
605
+ rollouts measured, 46 are imports.
606
+ - **No reasoning.** Codex encrypts model reasoning into `encrypted_content`,
607
+ sealed server-side with no key on the machine. The plaintext `summary` beside
608
+ it, present on roughly a third, becomes a thinking block. chsum skips thinking
609
+ rows for both tools, so nothing printed changes.
610
+
611
+ Rollouts older than seven days are rewritten as `.jsonl.zst` by recent Codex
612
+ versions. Reading one needs a zstd decoder, and chsum holds no dependency
613
+ outside the standard library, so compressed rollouts are left alone.
614
+
615
+ Adding a third tool costs one file in `chsum/sources/`: where its sessions sit,
616
+ which directory each ran in, and a translation to the common record shape. No
617
+ reader below that layer names a format.
618
+
619
+ ## `chsum where`
620
+
621
+ Every locator chsum prints — `01a0acf9:31` beside a turn, `f1b9bbc6:26` on a
622
+ row — names a line in a file. This turns one into the command that prints it:
623
+
624
+ ```
625
+ $ chsum where 01a0acf9:31
626
+ sed -n '31p' /Users/joshua/.local/share/chsum/translated/codex/…/01a0acf9-….jsonl | jq
627
+ ```
628
+
629
+ The command alone goes to stdout, so it pipes and substitutes:
630
+
631
+ ```sh
632
+ chsum where 01a0acf9:31 | sh # print that record
633
+ vim $(chsum where 01a0acf9 | awk '{print $4}')
634
+ ```
635
+
636
+ The file it resolved to, and where the line sits in it, go to stderr — beside
637
+ the command on a terminal, out of the way in a pipe.
638
+
639
+ Four forms, all of them things chsum printed:
640
+
641
+ | you have | you type |
642
+ |---|---|
643
+ | one row | `chsum where 01a0acf9:31` |
644
+ | a run of rows | `chsum where 01a0acf9:31-40` |
645
+ | a subagent's row | `chsum where 01a0acf9/a9f0f78b:3` |
646
+ | a session, no row yet | `chsum where 01a0acf9` — prints the path and the template |
647
+ | a tool call id | `chsum where toolu_01V7rDx5Le` — resolves to the row it sits on |
648
+ | a checkpoint stamp | `chsum where <session>:<call-id>` — both halves, as the reflog holds them |
649
+
650
+ A `ch_…` ref works wherever a session id does. A line past the end of the file
651
+ stops and says how many rows the file holds, rather than handing back a `sed`
652
+ command that prints nothing.
653
+
654
+ A checkpoint commit is stamped `chsum-checkpoint: <session> @ <time> <call-id>`,
655
+ so a line read out of `git reflog` reaches the call that caused it:
656
+
657
+ ```sh
658
+ git reflog --format='%gs' | grep chsum-checkpoint | head -1 |
659
+ sed 's/.*checkpoint: \([^ ]*\) @ [^ ]* \(.*\)/\1:\2/' |
660
+ xargs chsum where | sh
661
+ ```
662
+
663
+ A bare id works too — `chsum where toolu_01V7rDx5Le` searches this project, then
664
+ every project. Naming the session beside it reads that one file directly.
665
+
493
666
  ## `chsum find`
494
667
 
495
668
  Locate a conversation, or a note, by what it said. This is the one command that
@@ -705,19 +878,62 @@ every repository — and do nothing until that repository opts in. The gate is
705
878
  and never committed. When the file is absent, the SessionStart hook asks Claude
706
879
  to put the question to you once, and the answer is what writes the file.
707
880
 
708
- Once enabled, the Stop hook commits the working tree on every turn with
709
- `--no-verify`, then `git reset --soft` back to where `HEAD` was. The checkpoint
710
- commit ends up referenced by no branch — invisible to `git log` and `git status`
711
- — but sits in `HEAD`'s reflog, tagged `chsum-checkpoint: <session> @
712
- <timestamp>`. `recap` reads those back and diffs consecutive checkpoints, which
713
- sees every change however it was made and carries current line ranges; the
714
- transcript scan sees `Edit`/`Write`/`MultiEdit` only. Retention is the reflog's
715
- (~90 days, shorter once `git gc` runs), so the transcript stays the permanent
716
- fallback and the recap states, as counts, which source each turn came from.
881
+ Once enabled, each tool call that changes the tree is committed under
882
+ `refs/chsum/<session>`, chained onto the previous checkpoint and tagged
883
+ `chsum-checkpoint: <session> @ <timestamp> <tool-call-id>`. The commit is built
884
+ with `git commit-tree` from a throwaway index, so **`HEAD`, your index and your
885
+ working tree are never written** — nothing here can leave a checkpoint as your
886
+ branch tip, and your own commit hooks never fire. It shows in no normal git
887
+ command: `git log`, `git branch` and `git status` are unchanged. `git log --all`
888
+ does list it, since that means every ref.
889
+
890
+ `recap` and `chsum digest --writes` read the chain and diff consecutive
891
+ checkpoints, which sees every change however it was made and carries current
892
+ line ranges; the transcript scan sees `Edit`/`Write`/`MultiEdit` only.
893
+
894
+ Because each checkpoint is parented on the last, `git show <checkpoint>` is that
895
+ call's own diff and `git log refs/chsum/<session>` is the session's history. A
896
+ ref is a gc root, so a chain survives `git gc`, survives the reflog expiring,
897
+ and survives the removal of the worktree it was written in — all three of which
898
+ lost the old reflog-based checkpoints.
899
+
900
+ That durability is why retention is asked for rather than waited for:
901
+
902
+ ```sh
903
+ chsum checkpoints # the chains this repo holds, and the gate
904
+ chsum checkpoints --enable # turn checkpointing on for this project
905
+ chsum checkpoints --disable # off; chains already recorded stay readable
906
+ chsum checkpoints --prune 30d # drop chains older than 30 days
907
+ chsum checkpoints --prune 30d --dry-run
908
+ ```
909
+
910
+ The gate is per project and per worktree, held in the git directory. `--enable`
911
+ and `--disable` are both decisions, so either one stops the session-start prompt
912
+ asking again.
913
+
914
+ Dropping a chain leaves the transcript untouched and makes its commits
915
+ unreachable, which git reclaims on its next `gc`.
916
+
917
+ Checkpoints written by an older chsum sit in `HEAD`'s reflog instead. They are
918
+ still read — the two sources merge on the stamp each checkpoint carries — but
919
+ they keep the old fragility: unreachable, so a `gc` takes them, and held in the
920
+ worktree's own reflog, so `git worktree remove` takes them with it. Rebuild them
921
+ as chains to keep them:
922
+
923
+ ```sh
924
+ chsum checkpoints --migrate --dry-run # sessions still reflog-only, and counts
925
+ chsum checkpoints --migrate # rebuild each as a chain
926
+ ```
927
+
928
+ A project still holding reflog-only checkpoints is told so once at session
929
+ start, the same way the opt-in is raised, and the message names this command.
717
930
 
718
- The hook writes a state file before the commit-and-reset and removes it after,
719
- so a process killed in between leaves a checkpoint as the branch tip only until
720
- the next invocation's self-heal resets it away.
931
+ Each checkpoint is rebuilt from the tree and message it already carries, so the
932
+ content is identical and only the shas change — a parent is part of what a sha
933
+ hashes, so an existing commit cannot be re-parented. Nothing downstream resolves
934
+ a checkpoint by sha; `--writes`, `recap` and `chsum where <call-id>` all match on
935
+ the session and call id in the message. The reflog entries stay where they are,
936
+ and a second run finds nothing to do.
721
937
 
722
938
  ## Where chsum writes
723
939
 
@@ -726,8 +942,10 @@ Digests, your session names and the turn store share one directory:
726
942
  ```
727
943
  <data dir>/chsum/
728
944
  digests/<uuid>.md `chsum digest <ref>`'s output (`--out` to change)
945
+ digests/<uuid>-<view>.md a row view's output — messages, tools, commands, agents, call-<id>
729
946
  names.json your names for sessions
730
947
  turns/<project>/<turn-uuid>.json one turn's recap bullets and notes
948
+ translated/<tool>/projects/<project>/<id>.jsonl another tool's session, in the record shape chsum reads
731
949
  ```
732
950
 
733
951
  `CHSUM_DIR` in the environment names the directory outright. Otherwise
@@ -737,7 +955,10 @@ A `~/.chsum` from an earlier version moves there on the next run, once, with a
737
955
  line on stderr naming both paths.
738
956
 
739
957
  Nothing is written to a transcript, with one exception: `chsum name` appends an
740
- `ai-title` record, the shape Claude Code itself appends.
958
+ `ai-title` record, the shape Claude Code itself appends. That append is skipped
959
+ for a translated session, whose file is rebuilt from its original and whose
960
+ original is never read back by chsum — the name holds in `names.json`, which is
961
+ what every listing reads.
741
962
 
742
963
  ## Which version am I running
743
964