elliot-stack 1.0.18 → 1.0.20

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.
Files changed (50) hide show
  1. package/README.md +11 -0
  2. package/bin/install.cjs +134 -49
  3. package/package.json +1 -1
  4. package/skills/estack-read-claude-session-history/SKILL.md +203 -0
  5. package/skills/estack-read-claude-session-history/references/jsonl-schema.md +126 -0
  6. package/skills/estack-read-claude-session-history/references/modes.md +423 -0
  7. package/skills/estack-read-claude-session-history/references/recipes.md +271 -0
  8. package/skills/estack-read-claude-session-history/scripts/lib/__init__.py +1 -0
  9. package/skills/estack-read-claude-session-history/scripts/lib/parser.py +460 -0
  10. package/skills/estack-read-claude-session-history/scripts/lib/paths.py +234 -0
  11. package/skills/estack-read-claude-session-history/scripts/lib/search.py +179 -0
  12. package/skills/estack-read-claude-session-history/scripts/lib/subagents.py +88 -0
  13. package/skills/estack-read-claude-session-history/scripts/lib/tools.py +144 -0
  14. package/skills/estack-read-claude-session-history/scripts/read_transcript.py +1776 -0
  15. package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +40 -0
  16. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +20 -0
  17. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +4 -0
  18. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +2 -0
  19. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +9 -0
  20. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +7 -0
  21. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +3 -0
  22. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +3 -0
  23. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +5 -0
  24. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +2 -0
  25. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +8 -0
  26. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +2 -0
  27. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +2 -0
  28. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +2 -0
  29. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +2 -0
  30. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +1 -0
  31. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +4 -0
  32. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +6 -0
  33. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +5 -0
  34. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +10 -0
  35. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +3 -0
  36. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +2 -0
  37. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +3 -0
  38. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +5 -0
  39. package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +2 -0
  40. package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +56 -0
  41. package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +239 -0
  42. package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +201 -0
  43. package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +199 -0
  44. package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +195 -0
  45. package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +133 -0
  46. package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +78 -0
  47. package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +43 -0
  48. package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +179 -0
  49. package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +212 -0
  50. package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +80 -0
@@ -0,0 +1,126 @@
1
+ # JSONL schema reference
2
+
3
+ What's actually inside a Claude Code session `.jsonl`. Only relevant when extending the script or debugging an unexpected empty result.
4
+
5
+ ## File location
6
+
7
+ ```
8
+ C:\Users\<user>\.claude\projects\<encoded-cwd>\<session-uuid>.jsonl
9
+ ```
10
+
11
+ The `<encoded-cwd>` is the original working directory with every `:`, `\`, `/`, and whitespace character replaced by `-`. Examples on this machine:
12
+
13
+ | Original CWD | Encoded directory |
14
+ |---|---|
15
+ | `C:\Users\2supe\Other Claude Code` | `C--Users-2supe-Other-Claude-Code` |
16
+ | `C:\Users\2supe\Other Claude Code\Personal Brand Project` | `C--Users-2supe-Other-Claude-Code-Personal-Brand-Project` |
17
+ | `C:\Users\2supe\AppData\Local\Temp` | `C--Users-2supe-AppData-Local-Temp` |
18
+
19
+ The encoding is lossy — single hyphens in the original path collapse into the same hyphens that separate segments. Use `--mode lookup` / `--mode find` to recover the actual session path; `decode_project_name()` produces a display-only approximation.
20
+
21
+ ## Backup roots
22
+
23
+ The same encoded-directory layout exists under each `.claude-backups\<name>\projects\` root:
24
+
25
+ ```
26
+ C:\Users\2supe\.claude-backups\
27
+ ├── mirror\projects\<encoded-cwd>\<uuid>.jsonl
28
+ ├── snapshot-24h\projects\<encoded-cwd>\<uuid>.jsonl
29
+ ├── snapshot-1w\projects\<encoded-cwd>\<uuid>.jsonl
30
+ └── snapshot-1mo\projects\<encoded-cwd>\<uuid>.jsonl
31
+ ```
32
+
33
+ `--root mirror|snapshot-24h|snapshot-1w|snapshot-1mo` rebases all path resolution to that snapshot. An absolute path argument is also accepted.
34
+
35
+ ## Subagent transcripts
36
+
37
+ When a session spawns subagents (via the `Agent` / `Task` tool), each subagent's own transcript is written to:
38
+
39
+ ```
40
+ <project-dir>\<session-uuid>\subagents\agent-<id>.jsonl
41
+ ```
42
+
43
+ A sidecar metadata file lives next to it:
44
+
45
+ ```
46
+ <project-dir>\<session-uuid>\subagents\agent-<id>.meta.json
47
+ ```
48
+
49
+ The meta file contains:
50
+
51
+ ```json
52
+ {
53
+ "agentType": "Explore",
54
+ "description": "Find every reference to X"
55
+ }
56
+ ```
57
+
58
+ When the meta file is missing, `subagents.load_meta` returns `{"agentType": "unknown", "description": ""}`.
59
+
60
+ Subagent entries inside the parent transcript are marked with `isSidechain: true` and carry an `agentId` field.
61
+
62
+ ## Entry types
63
+
64
+ Each line in a `.jsonl` is a JSON object with a `type` field. Entry classifications:
65
+
66
+ | `type` value | Classification | Notes |
67
+ |---|---|---|
68
+ | `user` | signal (user) | `message.content` may be string or array. Compact markers live here. |
69
+ | `assistant` | signal (assistant) | `message.content` is always an array of blocks. |
70
+ | `ai-title` / `custom-title` | title | `aiTitle` / `customTitle` field carries the session title. |
71
+ | `permission-mode` | noise | mode change events |
72
+ | `attachment` | noise | file attachments |
73
+ | `last-prompt` | noise | cached last prompt |
74
+ | `queue-operation` | noise | internal queueing |
75
+ | `file-history-snapshot` | noise | file state snapshots |
76
+ | `system` | noise | system events |
77
+ | `agent-name` | noise | agent name metadata |
78
+ | `pr-link` | noise | PR linkage |
79
+
80
+ The `noise` and `title` entries are skipped by `get_messages()`. `debug` mode prints the full distribution.
81
+
82
+ ## Assistant content block types
83
+
84
+ The `message.content` array for assistant entries can hold:
85
+
86
+ | Block `type` | Field of interest | Meaning |
87
+ |---|---|---|
88
+ | `text` | `text` | The actual assistant text output. |
89
+ | `thinking` | `thinking` (or `text`) | Model internal reasoning. |
90
+ | `tool_use` | `name`, `input`, `id` | A regular tool call. `id` is the matching id for any later `tool_result`. |
91
+ | `server_tool_use` | `name`, `input` | Server-side tool call (e.g., advisor invocation). |
92
+ | `advisor_tool_result` | `content.text` | The advisor's reply. Always nested as `block.content.text`. |
93
+ | `tool_result` | `tool_use_id`, `content` | Result for a prior `tool_use`. `content` is a string or an array of `{type:"text", text:"..."}`. |
94
+
95
+ ## Compact marker
96
+
97
+ A `/compact` event appears as a `type:"user"` entry whose first text content starts with:
98
+
99
+ > `"This session is being continued from a previous conversation"`
100
+
101
+ `classify_entry` returns `"compact"` for these. Everything before the most-recent compact marker is the pre-compact conversation.
102
+
103
+ ## Timestamps
104
+
105
+ Most entries carry a `timestamp` field in ISO-8601 form (`2026-05-01T10:00:05Z`). `_parse_timestamp` accepts ISO strings, naive ISO, and numeric epoch values. Timezone-aware values are stripped for comparison with `--since`/`--until` (which use local naive datetimes).
106
+
107
+ ## Title entries
108
+
109
+ Both `ai-title` and `custom-title` entries surface a `aiTitle` / `customTitle` string. `session_summary()` prefers `aiTitle` when both are present.
110
+
111
+ ## Truncation behavior
112
+
113
+ `iter_lines` drops the final line if it lacks a trailing newline AND fails to parse as JSON, printing `[note: dropped truncated trailing line in <name>]` to stderr. Malformed mid-file lines are dropped silently.
114
+
115
+ ## Status inference
116
+
117
+ `infer_status(lines, mtime, current_session_id, session_uuid)` returns one of:
118
+
119
+ | Status | Glyph | Heuristic |
120
+ |---|---|---|
121
+ | `active` | ● | `current_session_id == session_uuid` AND `mtime` within 5 minutes |
122
+ | `interrupted` | ! | Any `tool_use` block lacks a paired `tool_result` |
123
+ | `pending-user` | ? | Last assistant text message ends with `?` |
124
+ | `clean` | ✓ | none of the above |
125
+
126
+ This is heuristic — it's correct for the majority of real sessions on this machine but can be fooled by, e.g., an assistant message that legitimately ends with a question.
@@ -0,0 +1,423 @@
1
+ # Mode reference
2
+
3
+ Every mode of `read_transcript.py`, with all flags, exit codes, and worked examples.
4
+
5
+ For the high-level decision tree, see `../SKILL.md`. For the JSONL schema, see `jsonl-schema.md`. For multi-step workflows, see `recipes.md`.
6
+
7
+ ## CLI grammar
8
+
9
+ ```
10
+ python read_transcript.py [--root <root>] [--cwd <path> | --all-projects | --project <name> | --file <path>]
11
+ [--since <spec>] [--until <spec>] [--tz <spec>]
12
+ --mode <mode> [mode-specific flags]
13
+ [--format json] [--exclude-current] [--include-subagents] [-n N]
14
+ [--force-dump]
15
+ ```
16
+
17
+ Global flag notes:
18
+ - `--project <name>` — case-insensitive substring filter on project directory names (encoded or decoded form). Applies to `list`, `journal`, `search`, `count`, `find`, `timeline`, `engagement`. Exit 1 when nothing matches.
19
+ - `--tz <spec>` — display timezone: IANA name (`America/New_York`), `UTC`, or fixed offset (`+5`, `-4`, `+05:30`, `UTC-4`). Default is system local time. All displayed timestamps AND `--since/--until/--date` interpretation use this zone.
20
+ - `--format json` (alias `--json`) — structured output on every mode except the legacy `--list`/`--list-subagents` aliases. Shapes per mode are listed below.
21
+
22
+ Legacy flags are preserved unchanged:
23
+ - `--list` (alias for `--mode list` with the v1 column layout)
24
+ - `--list-subagents` (alias for `--mode subagent-list` with the v1 column layout)
25
+
26
+ ---
27
+
28
+ ## Single-session modes
29
+
30
+ ### `last`
31
+
32
+ Last N assistant text outputs.
33
+
34
+ ```bash
35
+ python read_transcript.py --file <path> --mode last [-n 5]
36
+ ```
37
+
38
+ - Default N = 5.
39
+ - With `--include-subagents`, appends each subagent's final assistant message tagged `[subagent <id-short> · <agentType>]`.
40
+
41
+ ### `advisor`
42
+
43
+ All advisor responses (the contents of every `advisor_tool_result` block).
44
+
45
+ ```bash
46
+ python read_transcript.py --file <path> --mode advisor
47
+ ```
48
+
49
+ ### `pre-compact`
50
+
51
+ 40 message-exchanges before the most recent `/compact`.
52
+
53
+ ```bash
54
+ python read_transcript.py --file <path> --mode pre-compact
55
+ ```
56
+
57
+ If no `/compact` marker is present, falls back to `mode_last(10)` with a note.
58
+
59
+ ### `dump`
60
+
61
+ Human-readable conversation dump (text only, last 80 messages by default).
62
+
63
+ ```bash
64
+ python read_transcript.py --file <path> --mode dump [--include-subagents] [--force-dump]
65
+ ```
66
+
67
+ **Size-aware fallback:** transcripts larger than 5 MB auto-degrade to `pre-compact` (or `last` if no compact marker), with a stderr note. Override with `--force-dump`.
68
+
69
+ ### `debug`
70
+
71
+ Structural diagnostic — prints entry type distribution, content block types, advisor probe, compact-marker probe, and sample signal entries. Use this first when a mode returns empty/unexpected results.
72
+
73
+ ```bash
74
+ python read_transcript.py --file <path> --mode debug
75
+ ```
76
+
77
+ ### `brief`
78
+
79
+ 6-line single-session summary. Designed for fan-out triage: 14 parallel `brief` calls reproduce a 14-subagent investigation deterministically.
80
+
81
+ ```bash
82
+ python read_transcript.py --file <path> --mode brief [--include-subagents]
83
+ ```
84
+
85
+ Output format:
86
+ ```
87
+ <uuid> · <decoded-project> · <mtime> · <status> [*]<if current>
88
+ intent: <first user prompt, 200ch>
89
+ last: <last assistant text, 200ch>
90
+ edits: <N> files — <top-3 paths>
91
+ tools: Bash=X Edit=Y Read=Z …
92
+ subagents: <N> spawned [<agentType counts>]
93
+ ```
94
+
95
+ With `--include-subagents`, appends each subagent's final assistant message.
96
+
97
+ ### `changelog`
98
+
99
+ `HH:MM:SS TOOL one-line-summary`, day-grouped.
100
+
101
+ ```bash
102
+ python read_transcript.py --file <path> --mode changelog
103
+ ```
104
+
105
+ ### `file-edits`
106
+
107
+ Unique file paths touched, sorted alphabetically. Multiple operations on the same file get a count suffix.
108
+
109
+ ```bash
110
+ python read_transcript.py --file <path> --mode file-edits
111
+ ```
112
+
113
+ ### `tool-calls`
114
+
115
+ Timestamped per-call blocks, with formatted args.
116
+
117
+ ```bash
118
+ python read_transcript.py --file <path> --mode tool-calls [--tool Bash,Edit]
119
+ ```
120
+
121
+ `--tool` filters to a comma-separated subset.
122
+
123
+ ---
124
+
125
+ ## Discovery modes
126
+
127
+ ### `list`
128
+
129
+ Enriched session table.
130
+
131
+ ```bash
132
+ # Single project
133
+ python read_transcript.py --cwd <path> --mode list [--since 7d] [--exclude-current]
134
+
135
+ # All projects
136
+ python read_transcript.py --all-projects --mode list [--since today]
137
+ ```
138
+
139
+ Columns: marker (`[*]` if current), mtime, size, uuid-short, msg count, flags (`[C]`=compact, `[S]`=subagents), status (✓!?●), decoded project name (when multi-project), title.
140
+
141
+ For byte-identical v1 output, use the legacy `--list` flag.
142
+
143
+ ### `lookup`
144
+
145
+ Resolve a UUID prefix to an absolute path.
146
+
147
+ ```bash
148
+ python read_transcript.py --mode lookup --uuid <prefix>
149
+ ```
150
+
151
+ - Exit 0: prints absolute path.
152
+ - Exit 1: no match.
153
+ - Exit 2: ambiguous prefix — prints all matches.
154
+
155
+ ### `find`
156
+
157
+ Search session metadata by title or first prompt.
158
+
159
+ ```bash
160
+ python read_transcript.py --mode find --title "supabase"
161
+ python read_transcript.py --mode find --first-prompt "fix the bug"
162
+ ```
163
+
164
+ ### `resume-cmd`
165
+
166
+ Generate a `cd <cwd>; claude --resume <uuid>` snippet for a UUID prefix.
167
+
168
+ ```bash
169
+ python read_transcript.py --mode resume-cmd --uuid <prefix>
170
+ ```
171
+
172
+ The original CWD cannot be unambiguously recovered from the encoded directory name; the snippet includes a `<original cwd>` placeholder plus the decoded display name as a hint.
173
+
174
+ ---
175
+
176
+ ## Search modes
177
+
178
+ ### `search`
179
+
180
+ Cross-scope search with role + channel filters.
181
+
182
+ ```bash
183
+ # Single file
184
+ python read_transcript.py --file <path> --mode search --query "<q>"
185
+
186
+ # Whole project
187
+ python read_transcript.py --cwd <path> --mode search --query "<q>"
188
+
189
+ # All projects
190
+ python read_transcript.py --all-projects --mode search --query "<q>"
191
+ ```
192
+
193
+ Flags:
194
+ - `--role {user,assistant,both}` (default `both`)
195
+ - `--in {text,tool_use,thinking,all}` (default `text`)
196
+ - `--since` / `--until`
197
+
198
+ `--in tool_use` searches the `name + JSON-stringified input` of every `tool_use` block — useful for finding "the session where I ran `git push --force`".
199
+
200
+ `--in thinking` searches `thinking` blocks (model reasoning).
201
+
202
+ ### `count`
203
+
204
+ Count sessions matching a query.
205
+
206
+ ```bash
207
+ python read_transcript.py --mode count --query "<q>" [--all-projects] [--since 30d]
208
+ ```
209
+
210
+ stdout: integer session count.
211
+ stderr: `<N> sessions, <M> total messages, <K> matches`.
212
+
213
+ ---
214
+
215
+ ## Subagent modes
216
+
217
+ ### `subagent-list`
218
+
219
+ List sibling subagents for a parent session.
220
+
221
+ ```bash
222
+ python read_transcript.py --file <parent> --mode subagent-list
223
+ ```
224
+
225
+ Output: `mtime size agent-id type=<agentType> "<description>"`. The legacy `--list-subagents` flag preserves v1's simpler columns.
226
+
227
+ ### `subagent-finals`
228
+
229
+ Every subagent's final assistant message, separated by `=== agent-<id> (<type>) ===` headers.
230
+
231
+ ```bash
232
+ python read_transcript.py --file <parent> --mode subagent-finals
233
+ ```
234
+
235
+ ### `subagent-tools`
236
+
237
+ Tool-call forensics on a single subagent (same output shape as `tool-calls`).
238
+
239
+ ```bash
240
+ python read_transcript.py --mode subagent-tools --subagent <subagent-path>
241
+ ```
242
+
243
+ ### `subagent-files`
244
+
245
+ Files touched by a single subagent (same output shape as `file-edits`).
246
+
247
+ ```bash
248
+ python read_transcript.py --mode subagent-files --subagent <subagent-path>
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Resume modes
254
+
255
+ ### `resume-prev`
256
+
257
+ Banner + dump-style tail of the last 10 exchanges from the most-recent prior session in a project.
258
+
259
+ ```bash
260
+ python read_transcript.py --cwd <path> --mode resume-prev [-n 10]
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Aggregation modes
266
+
267
+ ### `journal`
268
+
269
+ Per-session 5-line block: date·uuid·project / prompt / ended / edits / tools.
270
+
271
+ ```bash
272
+ python read_transcript.py --mode journal --since 7d [--cwd <path> | --all-projects | --project <name>]
273
+ ```
274
+
275
+ JSON shape: array of session-summary objects (same fields as `list`).
276
+
277
+ ### `timeline`
278
+
279
+ Block-grouped activity timeline across all sessions in a time window, with idle
280
+ gaps. Answers "what was I doing, when?". It is a *map* of session activity —
281
+ Claude's work included — and makes no claim about attention time; for "how much
282
+ time did this consume?" use `engagement`. Defaults to all projects and today.
283
+
284
+ ```bash
285
+ # Yesterday, everything
286
+ python read_transcript.py --mode timeline --date yesterday
287
+
288
+ # One project, today, stricter idle threshold
289
+ python read_transcript.py --mode timeline --project keel --date today --gap 5m
290
+
291
+ # Arbitrary window
292
+ python read_transcript.py --mode timeline --since 2026-06-01 --until 2026-06-03
293
+ ```
294
+
295
+ How it works: every signal-message timestamp in the window is an activity event;
296
+ events across all sessions are merged chronologically and grouped into blocks
297
+ separated by gaps longer than `--gap` (default 15m). Each block lists the sessions
298
+ active in it with message counts; idle gaps are printed between blocks; a totals
299
+ line gives block count, span, and session count.
300
+
301
+ Flags:
302
+ - `--date <spec>` — single-day window (midnight to midnight). Wins over `--since/--until`.
303
+ - `--since/--until` — arbitrary window (until defaults to now).
304
+ - `--gap <spec>` — idle threshold: `15m`, `20`, `1h`.
305
+ - `--cwd` / `--project` / `--all-projects` — scope (default: all projects).
306
+ - `--tz` — display timezone (timestamps render in it; the window is interpreted in it).
307
+
308
+ JSON shape: `{since, until, gap_minutes, blocks: [{start, end, duration_minutes,
309
+ sessions: [{uuid, project, title, path, events}]}], totals: {blocks,
310
+ span_minutes, sessions}}`.
311
+
312
+ ### `engagement`
313
+
314
+ Attention-time accounting: how much focused time a window, project, or session
315
+ actually consumed — *your* time, not Claude's. Answers "how long did X actually
316
+ take me?". Defaults to all projects and today; with `--file` and no time flags,
317
+ the window is the session's own first→last prompt.
318
+
319
+ ```bash
320
+ # Today's real work time, everything
321
+ python read_transcript.py --mode engagement --date today
322
+
323
+ # One project over a week
324
+ python read_transcript.py --mode engagement --project keel --since 7d
325
+
326
+ # One session, strict 5-minute break threshold
327
+ python read_transcript.py --mode engagement --file <session.jsonl> --break 5m
328
+ ```
329
+
330
+ How it works — three deterministic rules over ONE merged stream:
331
+
332
+ 1. Real user prompts (typed messages and slash commands — not tool results,
333
+ hook/skill injections, or compact continuations) from EVERY project are
334
+ merged into a single chronological stream. A gap between consecutive prompts
335
+ ≤ `--break` (default 10m) counts fully as active time, attributed to the
336
+ session of the *later* prompt — the chat being read/typed in. One stream
337
+ means a moment of wall clock is never counted twice across parallel chats.
338
+ 2. A longer gap still counts in full if Claude was working in that session
339
+ during the gap and the user replied within `--break` of Claude's last event
340
+ (sitting-there-waiting credit). Long runs you walked away from get nothing.
341
+ 3. Everything else is a break and contributes zero. A session left open with no
342
+ prompts accrues nothing.
343
+
344
+ Output: one row per session (active, ratio = active/elapsed, prompt count,
345
+ first–last), a totals line (already interval-merged — safe to quote), breaks in
346
+ the merged stream, and (single-session view) median/p90 prompt gaps. Ratio is
347
+ capped at 1.0: composing time leading into a chat's first prompt is credited to
348
+ that chat, so raw active can slightly exceed its first–last span.
349
+
350
+ Scoping caveat: `--project`/`--cwd`/`--file` filter which sessions are
351
+ *reported*; the stream is always computed across all projects under `--root` so
352
+ parallel-chat math stays correct. A scoped total can be less than the global
353
+ total for the same window.
354
+
355
+ Flags:
356
+ - `--break <spec>` — break threshold: `5m`, `20m`, `1h` (default 10m).
357
+ - `--date` / `--since` / `--until` — window (same semantics as timeline).
358
+ - `--cwd` / `--project` / `--all-projects` / `--file` — reporting scope.
359
+ - `--tz` — display timezone.
360
+ - `--exclude-current` — drop the current session from stream and report.
361
+
362
+ JSON shape: `{since, until, break_minutes, sessions: [{uuid, project, title,
363
+ path, first, last, elapsed_minutes, active_minutes, active_seconds, ratio,
364
+ user_messages}], totals: {sessions, active_minutes, active_seconds,
365
+ span_minutes}, stream_breaks: [{start, end, minutes}]}`.
366
+
367
+ ---
368
+
369
+ ## Comparison modes
370
+
371
+ ### `diff`
372
+
373
+ Timestamp-interleaved comparison of two sessions.
374
+
375
+ ```bash
376
+ python read_transcript.py --mode diff --file-a <s1> --file-b <s2>
377
+ ```
378
+
379
+ For sibling-subagent comparison (when a fan-out spawned multiple agents with similar tasks):
380
+
381
+ ```bash
382
+ python read_transcript.py --mode diff --subagents-of <parent>
383
+ ```
384
+
385
+ Output is prefixed `A>` / `B>` (or with subagent id shorts).
386
+
387
+ ---
388
+
389
+ ## JSON shapes per mode (`--format json`)
390
+
391
+ | Mode | Shape |
392
+ |---|---|
393
+ | `last` | `[{n_from_end, timestamp, text}]` |
394
+ | `advisor` | `[<advisor text>, …]` |
395
+ | `pre-compact` | `{found_compact, messages: [{role, timestamp, is_compact, text}]}` |
396
+ | `dump` | `[{role, timestamp, is_compact, text}]` |
397
+ | `debug` | `{entry_types, block_types, advisor_blocks, compact_markers}` |
398
+ | `brief` | session-summary object (+ `subagent_finals` with `--include-subagents`) |
399
+ | `list` / `journal` / `find` | array of session-summary objects |
400
+ | `lookup` | `{prefix, path, matches}` (same exit codes as text) |
401
+ | `resume-cmd` | `{uuid, path, project, encoded, command}` |
402
+ | `changelog` | `[{timestamp, tool, summary}]` |
403
+ | `tool-calls` / `subagent-tools` | `[{timestamp, tool, summary, input}]` |
404
+ | `file-edits` / `subagent-files` | `[{path, ops}]` |
405
+ | `search` | `[{session, mtime_iso, role, where, timestamp, window}]` |
406
+ | `count` | `{sessions, messages, matches}` |
407
+ | `subagent-list` | `[{id, agentType, description, path, size_kb, mtime_iso}]` |
408
+ | `subagent-finals` | `[{id, agentType, text}]` |
409
+ | `resume-prev` | `{session, path, mtime_iso, messages}` |
410
+ | `diff` | `{a, b, messages: [{source, role, timestamp, text}]}` |
411
+ | `timeline` | see the `timeline` section above |
412
+ | `engagement` | see the `engagement` section above |
413
+
414
+ Session-summary object fields: `path, uuid, mtime, mtime_iso, size, exists, title,
415
+ first_prompt, last_assistant, last_activity, msg_count, edit_count, tool_counts,
416
+ files_touched, subagent_count, subagent_types, has_compact, has_subagents, cwd,
417
+ decoded_project, status, is_current`.
418
+
419
+ ## Exit codes
420
+
421
+ - `0`: success
422
+ - `1`: missing required flag, no match (including `--project` with zero matches), or file not found
423
+ - `2`: ambiguous result (e.g. UUID prefix matches multiple sessions)