elliot-stack 1.0.38 → 1.0.40
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.
- package/package.json +1 -1
- package/skills/estack-migrate-claude-session-history/SKILL.md +3 -2
- package/skills/estack-migrate-claude-session-history/scripts/__pycache__/validate-migration.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/SKILL.md +30 -4
- package/skills/estack-read-claude-session-history/references/modes.md +65 -9
- package/skills/estack-read-claude-session-history/references/recipes.md +7 -1
- package/skills/estack-read-claude-session-history/scripts/__pycache__/read_transcript.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/parser.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/paths.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/subagents.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/tools.cpython-313.pyc +0 -0
- package/skills/estack-read-claude-session-history/scripts/lib/parser.py +2 -1
- package/skills/estack-read-claude-session-history/scripts/lib/search.py +27 -9
- package/skills/estack-read-claude-session-history/scripts/read_transcript.py +267 -84
- package/skills/estack-migrate-claude-session-history/scripts/test-append-note.js +0 -48
- package/skills/estack-migrate-claude-session-history/scripts/test-validate-migration.py +0 -326
- package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +0 -40
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +0 -20
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +0 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +0 -9
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +0 -7
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +0 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +0 -8
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +0 -1
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +0 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +0 -6
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +0 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-usage-parent/subagents/agent-sub1.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-usage-parent.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +0 -10
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +0 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +0 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +0 -2
- package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +0 -56
- package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +0 -239
- package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +0 -201
- package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +0 -323
- package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +0 -195
- package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +0 -133
- package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +0 -78
- package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +0 -43
- package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +0 -179
- package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +0 -212
- package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +0 -80
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-migrate-claude-session-history
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.2
|
|
4
4
|
description: >-
|
|
5
5
|
(migrate-claude-session-history) Use whenever the user wants to move a Claude
|
|
6
6
|
Code session (the .jsonl transcript plus its subagent sidecar files) from one
|
|
@@ -204,7 +204,8 @@ These are the failure modes that have actually happened — read `references/tro
|
|
|
204
204
|
|
|
205
205
|
- `scripts/migrate-claude-history.js` — the migration script. Supports full-project and single-session modes, CLI overrides for old/new repo, auto-append of the migration note (on by default), `--no-migration-note` opt-out, and `--dry-run`. Run `node scripts/migrate-claude-history.js --help` for the full CLI.
|
|
206
206
|
- `scripts/validate-migration.py` — post-migration validator. Runs structural, schema, path-consistency, sidecar, and (optional) backup-cross-validation checks on a migrated `.jsonl`. Exits 0 if every check passes, 1 otherwise. Run `python scripts/validate-migration.py --help` for the full CLI. Use this in step 7 instead of writing ad-hoc Python.
|
|
207
|
-
- `
|
|
207
|
+
- `tests/estack-migrate-claude-session-history/test-append-note.js` — smoke test for the note-append routine (path relative to repo root). Run with `node tests/estack-migrate-claude-session-history/test-append-note.js` from the repo root. Useful if you edit the migration script and want to sanity-check the duplicate-detection and non-meta entry shape.
|
|
208
|
+
- `tests/estack-migrate-claude-session-history/test-validate-migration.py` — self-test for the validator (path relative to repo root). Runs 27 synthetic cases covering every check. Run with `python tests/estack-migrate-claude-session-history/test-validate-migration.py` from the repo root. Exit 0 on full pass, 1 if any case fails.
|
|
208
209
|
- `references/path-encoding.md` — the 9 path-encoding variants, why each exists, which entries use which form.
|
|
209
210
|
- `references/troubleshooting.md` — recoveries for stale-reference false positives, missed sidecars, ambiguous lookups, and the true-stale grep recipe.
|
|
210
211
|
---
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-read-claude-session-history
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.3.0
|
|
4
4
|
description: >-
|
|
5
5
|
(read-claude-session-history) Invoke for ANY task involving Claude Code
|
|
6
6
|
session history, transcripts, or .jsonl files - this is the only way to read,
|
|
@@ -60,6 +60,12 @@ python "$PY" --mode timeline --date yesterday
|
|
|
60
60
|
# How much focused time did today actually consume? (your attention, not Claude's)
|
|
61
61
|
python "$PY" --mode engagement --date today
|
|
62
62
|
|
|
63
|
+
# Per-session "what did I do" report — one block each: active+ran time, you/assistant
|
|
64
|
+
# message counts, intent, last message. The whole "review my day" answer in one call.
|
|
65
|
+
python "$PY" --mode session-report --date yesterday
|
|
66
|
+
# Scope to part of a day with --since/--until (omit --date — date wins over since):
|
|
67
|
+
python "$PY" --mode session-report --since "2026-06-19 19:00" --until "2026-06-20 00:00"
|
|
68
|
+
|
|
63
69
|
# Any mode as structured JSON for piping into the next step
|
|
64
70
|
python "$PY" --mode list --project keel --since 7d --format json
|
|
65
71
|
```
|
|
@@ -96,7 +102,8 @@ What are you trying to do?
|
|
|
96
102
|
│ ├─ One session ─────────────────────────────── --mode search --file …
|
|
97
103
|
│ ├─ One project ─────────────────────────────── --mode search --cwd …
|
|
98
104
|
│ ├─ All projects ────────────────────────────── --mode search --all-projects
|
|
99
|
-
│
|
|
105
|
+
│ ├─ Expand a wide search to full windows ───── --full
|
|
106
|
+
│ └─ Filter by role / content channel ──────── --role user --in tool_use|tool_result|thinking|all
|
|
100
107
|
│
|
|
101
108
|
├─ Forensics on a session
|
|
102
109
|
│ ├─ Chronological tool-call log ────────────── --mode changelog
|
|
@@ -110,6 +117,7 @@ What are you trying to do?
|
|
|
110
117
|
│ └─ Forensics on one subagent ──────────────── --mode subagent-tools|subagent-files --subagent …
|
|
111
118
|
│
|
|
112
119
|
├─ Cross-cutting reporting
|
|
120
|
+
│ ├─ "What did I do, per session?" (day review) ─ --mode session-report --date … | --since/--until …
|
|
113
121
|
│ ├─ "What did I do this week?" ──────────────── --mode journal --since 7d
|
|
114
122
|
│ ├─ "What was I doing, when?" / day map ─────── --mode timeline --date yesterday
|
|
115
123
|
│ ├─ "How long did X actually take ME?" ──────── --mode engagement --date … | --project … | --file …
|
|
@@ -128,7 +136,7 @@ What are you trying to do?
|
|
|
128
136
|
| `advisor` | `--file` | All `advisor_tool_result` payloads |
|
|
129
137
|
| `pre-compact` | `--file` | 40 exchanges before the most recent `/compact` |
|
|
130
138
|
| `dump` | `--file` | Human-readable dump (auto-degrades on transcripts >5MB) |
|
|
131
|
-
| `search` | `--query` + scope |
|
|
139
|
+
| `search` | `--query` + scope | Single file: full match windows. Wide scope (`--cwd`/`--project`/`--all-projects`): per-session summary by default; `--full` expands to windows. Either way the full view is bounded by a char budget and degrades back to a summary (with a note) if it would overflow. Supports `--role`, `--in text\|tool_use\|tool_result\|thinking\|all` |
|
|
132
140
|
| `debug` | `--file` | Entry/block type distributions + probes |
|
|
133
141
|
| `brief` | `--file` | 6-line summary: uuid·project·mtime·status / intent / last / edits / tools / subagents |
|
|
134
142
|
| `list` | `--cwd` or `--all-projects` | Rich table: mtime, size, uuid, msg count, flags, status, title |
|
|
@@ -147,7 +155,8 @@ What are you trying to do?
|
|
|
147
155
|
| `count` | `--query` (+ scope) | `<N>` to stdout, summary to stderr |
|
|
148
156
|
| `journal` | `--since` (+ scope) | Per-session 5-line block: date·uuid / prompt / ended / edits / tools |
|
|
149
157
|
| `timeline` | `--date` or `--since/--until` (defaults: today, all projects) | Map of WHAT was active WHEN: blocks + idle gaps (no attention claim — that's `engagement`) |
|
|
150
|
-
| `engagement` | `--date` or `--since/--until` or `--file` (defaults: today, all projects) | YOUR attention time: active vs elapsed + ratio per session, parallel-chat-safe totals, breaks |
|
|
158
|
+
| `engagement` | `--date` or `--since/--until` or `--file` (defaults: today, all projects) | YOUR attention time: active vs elapsed + ratio per session, **you/assistant message counts**, parallel-chat-safe totals, breaks |
|
|
159
|
+
| `session-report` | `--date` or `--since/--until` (defaults: today, all projects) | Per-session day review, chronological: one numbered block each with both clocks (ran = own span, overlaps OK; active = deduped attention), you/assistant message counts, intent, last message, files edited. All windowed to the range. The one-call "review my day" answer. |
|
|
151
160
|
| `diff` | `--file-a` + `--file-b` OR `--subagents-of` | Timestamp-interleaved A>/B> output |
|
|
152
161
|
|
|
153
162
|
## Global flags
|
|
@@ -157,6 +166,7 @@ What are you trying to do?
|
|
|
157
166
|
- `--all-projects` — walk every project under `--root`.
|
|
158
167
|
- `--project <name>` — filter projects by name substring, case-insensitive, matches encoded or decoded form (`--project keel`, `--project "Other Claude Code"`). Works on `list`, `journal`, `search`, `count`, `find`, `timeline`, `engagement`, `tool-usage`. Use this instead of `--cwd` when you know the project's name but not its exact path. (Note: for `engagement`, scope filters which sessions are *reported* — the attention stream is always computed across all projects so parallel chats never double-count.)
|
|
159
168
|
- `--file <path>` — single-session scope.
|
|
169
|
+
- `--full` — for wide-scope `search` (`--cwd`/`--project`/`--all-projects`), expand the default per-session summary into full match windows. Single-file searches (`--file`) are always full and ignore this flag. In every case the full view is bounded by a character budget (~10k tokens); if the windows would overflow it, the output degrades back to the summary with a note.
|
|
160
170
|
- `--since <spec>` / `--until <spec>` — accepts ISO date, ISO datetime, relative (`30m`, `24h`, `7d`, `1w`, `1mo`), named (`today`, `yesterday`, `now`).
|
|
161
171
|
- `--date <spec>` — single-day window for `timeline` (`--date yesterday`, `--date 2026-06-01`).
|
|
162
172
|
- `--gap <spec>` — idle-gap threshold for `timeline` blocks (`15m` default, `1h`).
|
|
@@ -201,6 +211,8 @@ See `references/recipes.md` → "Deletion-incident recovery" for the full playbo
|
|
|
201
211
|
| Find "that session where I asked about supabase rate limits" | `--mode search --all-projects --query "supabase rate limits"` |
|
|
202
212
|
| Resume a project after a few days away | `--mode resume-prev --cwd "<project path>"` |
|
|
203
213
|
| Daily/weekly journal | `--mode journal --since 7d --all-projects` |
|
|
214
|
+
| "What did I do yesterday, per session?" (day review) | `--mode session-report --date yesterday` |
|
|
215
|
+
| "Break down what I did from 7pm on" | `--mode session-report --since "<date> 19:00" --until "<date+1> 00:00"` |
|
|
204
216
|
| "Where did yesterday go?" (map of activity) | `--mode timeline --date yesterday` |
|
|
205
217
|
| "How much did I actually work today?" | `--mode engagement --date today` |
|
|
206
218
|
| "How much time on Keel today?" | `--mode engagement --project keel --date today` |
|
|
@@ -210,6 +222,20 @@ See `references/recipes.md` → "Deletion-incident recovery" for the full playbo
|
|
|
210
222
|
|
|
211
223
|
See `references/recipes.md` for fuller multi-step workflows.
|
|
212
224
|
|
|
225
|
+
## Presentation defaults for a human day-review
|
|
226
|
+
|
|
227
|
+
When the user asks a natural-language "what did I do" / "review my day" / "break down what I worked on" question (as opposed to feeding JSON to a script), lead with `session-report` — it carries everything one such answer needs in a single call — and present it this way unless the user says otherwise:
|
|
228
|
+
|
|
229
|
+
- **Number the sessions** and separate them into clear blocks. Users read a numbered, sectioned list far more easily than a wall of prose.
|
|
230
|
+
- **Drop UUIDs.** They are noise in a human review; only surface them if the user is going to resume or look one up.
|
|
231
|
+
- **One to two sentences per session** describing what they did and why — synthesized from the `intent` + `last` + files, not a raw dump of either. The mode hands you the inputs; you write the sentence.
|
|
232
|
+
- **Show both clocks and name the overlap.** `active` is deduped attention (parallel chats never double-counted); `ran`/elapsed is the session's own first→last span and *will* overlap others. Say so once — users work on several things at once and want the overlap reflected in the span but removed from active minutes.
|
|
233
|
+
- **Counts are clean already.** `you N msgs` is real typed prompts (tool-results and hook/skill injections excluded); `assistant N msgs` is text replies (tool-only turns excluded). Do NOT hand-count raw `type:user`/`type:assistant` entries from the .jsonl — that over-counts both (tool-result envelopes inflate "user"; multi-block turns inflate "assistant"). Trust the mode's numbers.
|
|
234
|
+
- **24-hour time** as the CLI emits it; convert only if the user asks.
|
|
235
|
+
- **Scope to a sub-window with `--since/--until`** (omit `--date` — `--date` overrides `--since`). The metrics are windowed to the range, so "from 7pm" counts and active-time reflect only that slice.
|
|
236
|
+
|
|
237
|
+
For a normal day (16–20 sessions) `session-report` text output stays well within the read budget; prefer it over `--mode list --format json`, which dumps full per-session arrays and can overflow into a persisted-file round-trip.
|
|
238
|
+
|
|
213
239
|
## Windows notes
|
|
214
240
|
|
|
215
241
|
- Use `python` (not `python3`) on this Windows setup.
|
|
@@ -15,6 +15,7 @@ python read_transcript.py [--root <root>] [--cwd <path> | --all-projects | --pro
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Global flag notes:
|
|
18
|
+
- `--since <spec>` / `--until <spec>` — the time window is half-open `[since, until)`: `since` is inclusive, `until` is exclusive, so an event stamped exactly at `--until` is not included. This holds for every message-level mode (`search`, `timeline`, `engagement`, `tool-usage`). Session-level modes (`list`, `journal`, `count`) instead filter whole sessions by file mtime, so a session last written at or before `--until` is shown.
|
|
18
19
|
- `--project <name>` — case-insensitive substring filter on project directory names (encoded or decoded form). Applies to `list`, `journal`, `search`, `count`, `find`, `timeline`, `engagement`, `tool-usage`. Exit 1 when nothing matches.
|
|
19
20
|
- `--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
21
|
- `--format json` (alias `--json`) — structured output on every mode except the legacy `--list`/`--list-subagents` aliases. Shapes per mode are listed below.
|
|
@@ -188,17 +189,27 @@ python read_transcript.py --cwd <path> --mode search --query "<q>"
|
|
|
188
189
|
|
|
189
190
|
# All projects
|
|
190
191
|
python read_transcript.py --all-projects --mode search --query "<q>"
|
|
192
|
+
|
|
193
|
+
# Expand a wide search to full match windows
|
|
194
|
+
python read_transcript.py --all-projects --mode search --query "<q>" --full
|
|
191
195
|
```
|
|
192
196
|
|
|
193
197
|
Flags:
|
|
194
198
|
- `--role {user,assistant,both}` (default `both`)
|
|
195
|
-
- `--in {text,tool_use,thinking,all}` (default `text`)
|
|
199
|
+
- `--in {text,tool_use,tool_result,thinking,all}` (default `text`)
|
|
200
|
+
- `--full` — wide scope only (see Output below)
|
|
196
201
|
- `--since` / `--until`
|
|
197
202
|
|
|
198
203
|
`--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
204
|
|
|
200
205
|
`--in thinking` searches `thinking` blocks (model reasoning).
|
|
201
206
|
|
|
207
|
+
`--in tool_result` searches the text content of `tool_result` blocks (what tools returned). `--in all` covers text + tool_use + tool_result + thinking.
|
|
208
|
+
|
|
209
|
+
**Output.** A single-file search (`--file`) prints full match windows. A wide search (`--cwd` / `--project` / `--all-projects`) prints a **per-session summary** by default — one line per session (`mtime · uuid8 · project · hit-count · first snippet`), sorted newest first, with a header counting total hits and sessions. This keeps cross-project searches well under the harness's ~25k-token Read cap instead of dumping tens of thousands of tokens that the reader then refuses. Add `--full` to expand a wide search into full windows. In every case the full view (single-file, or wide + `--full`) is bounded by a character budget (~10k tokens) and degrades back to the summary with a note if it would overflow — so even a single huge session can't blow the Read cap. Sessions past the 200-line summary cap are counted in a footer, never silently dropped. JSON mirrors this: wide scope returns compact per-session metadata (`uuid`, `project`, `hits`, `first_snippet`) by default, full per-match objects (with `window`) under `--full`.
|
|
210
|
+
|
|
211
|
+
Progress (`Searching i/N…`) prints to stderr only when stderr is an interactive terminal — captured/piped runs stay clean.
|
|
212
|
+
|
|
202
213
|
### `count`
|
|
203
214
|
|
|
204
215
|
Count sessions matching a query.
|
|
@@ -341,11 +352,17 @@ How it works — three deterministic rules over ONE merged stream:
|
|
|
341
352
|
3. Everything else is a break and contributes zero. A session left open with no
|
|
342
353
|
prompts accrues nothing.
|
|
343
354
|
|
|
344
|
-
Output: one row per session (active, ratio = active/elapsed,
|
|
345
|
-
first–last), a totals line (already interval-merged — safe to quote),
|
|
346
|
-
the merged stream, and (single-session view) median/p90 prompt gaps.
|
|
347
|
-
capped at 1.0: composing time leading into a chat's first prompt is
|
|
348
|
-
that chat, so raw active can slightly exceed its first–last span.
|
|
355
|
+
Output: one row per session (active, ratio = active/elapsed, `you`/`ai` message
|
|
356
|
+
counts, first–last), a totals line (already interval-merged — safe to quote),
|
|
357
|
+
breaks in the merged stream, and (single-session view) median/p90 prompt gaps.
|
|
358
|
+
Ratio is capped at 1.0: composing time leading into a chat's first prompt is
|
|
359
|
+
credited to that chat, so raw active can slightly exceed its first–last span.
|
|
360
|
+
|
|
361
|
+
Message counts are honest, not raw entry counts: `you` (`user_messages`) is real
|
|
362
|
+
typed prompts only — tool-result envelopes, hook/skill `isMeta` injections, and
|
|
363
|
+
compact continuations are excluded; `ai` (`assistant_messages`) is assistant
|
|
364
|
+
turns bearing visible text — tool-only turns don't count. Both are windowed to
|
|
365
|
+
`[since, until)`.
|
|
349
366
|
|
|
350
367
|
Scoping caveat: `--project`/`--cwd`/`--file` filter which sessions are
|
|
351
368
|
*reported*; the stream is always computed across all projects under `--root` so
|
|
@@ -361,8 +378,47 @@ Flags:
|
|
|
361
378
|
|
|
362
379
|
JSON shape: `{since, until, break_minutes, sessions: [{uuid, project, title,
|
|
363
380
|
path, first, last, elapsed_minutes, active_minutes, active_seconds, ratio,
|
|
364
|
-
user_messages}], totals: {sessions, active_minutes,
|
|
365
|
-
span_minutes}, stream_breaks: [{start, end, minutes}]}`.
|
|
381
|
+
user_messages, assistant_messages}], totals: {sessions, active_minutes,
|
|
382
|
+
active_seconds, span_minutes}, stream_breaks: [{start, end, minutes}]}`.
|
|
383
|
+
|
|
384
|
+
### `session-report`
|
|
385
|
+
|
|
386
|
+
The per-session "what did I do" day review. Same windowed, overlap-safe
|
|
387
|
+
attention engine as `engagement`, but rendered as one numbered block per
|
|
388
|
+
session, **chronological** (oldest first by first prompt), carrying everything a
|
|
389
|
+
human review needs in a single call — so a "break down my day" answer doesn't
|
|
390
|
+
require stitching `timeline` + `lookup` + `engagement` + raw message counts by
|
|
391
|
+
hand.
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
# Yesterday, every project
|
|
395
|
+
python read_transcript.py --mode session-report --date yesterday
|
|
396
|
+
|
|
397
|
+
# Just the evening — omit --date so --since/--until take effect
|
|
398
|
+
python read_transcript.py --mode session-report --since "2026-06-19 19:00" --until "2026-06-20 00:00"
|
|
399
|
+
|
|
400
|
+
# One project
|
|
401
|
+
python read_transcript.py --mode session-report --project keel --date today
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Each block shows: title, project, first–last span with **both clocks** —
|
|
405
|
+
`ran` (the session's own first→last elapsed, which can overlap other sessions)
|
|
406
|
+
and `active` (deduped attention, parallel chats never double-counted) — then
|
|
407
|
+
`you`/`assistant` message counts (same honest definitions as `engagement`),
|
|
408
|
+
files edited, the `intent` (first prompt) and `last` (final assistant message).
|
|
409
|
+
The intent/last are raw inputs for you to synthesize a one-sentence description
|
|
410
|
+
from, not the description itself. A totals line closes with deduped active time
|
|
411
|
+
and the overlap-inclusive span.
|
|
412
|
+
|
|
413
|
+
Flags: same as `engagement` (`--break`, `--date`/`--since`/`--until`,
|
|
414
|
+
`--cwd`/`--project`/`--all-projects`, `--tz`, `--exclude-current`). As with
|
|
415
|
+
`engagement`, `--date` takes precedence over `--since`/`--until`; to scope to a
|
|
416
|
+
sub-window of a day, pass `--since`/`--until` and omit `--date`.
|
|
417
|
+
|
|
418
|
+
JSON shape: `{since, until, break_minutes, sessions: [{uuid, project, title,
|
|
419
|
+
path, first, last, elapsed_minutes, active_minutes, user_messages,
|
|
420
|
+
assistant_messages, edits, intent, last_message}], totals: {sessions,
|
|
421
|
+
active_minutes, span_minutes}}`. Sessions are ordered chronologically.
|
|
366
422
|
|
|
367
423
|
### `tool-usage`
|
|
368
424
|
|
|
@@ -434,7 +490,7 @@ Output is prefixed `A>` / `B>` (or with subagent id shorts).
|
|
|
434
490
|
| `tool-calls` / `subagent-tools` | `[{timestamp, tool, summary, input}]` |
|
|
435
491
|
| `tool-usage` | `{total, sessions, tools: [{tool, count}], skills: [{skill, count}]}` |
|
|
436
492
|
| `file-edits` / `subagent-files` | `[{path, ops}]` |
|
|
437
|
-
| `search` | `[{session, mtime_iso, role, where, timestamp, window}]` |
|
|
493
|
+
| `search` | single-file or `--full`: `[{session, mtime_iso, role, where, timestamp, window}]`; wide scope default: `[{session, uuid, project, mtime_iso, hits, first_snippet}]` |
|
|
438
494
|
| `count` | `{sessions, messages, matches}` |
|
|
439
495
|
| `subagent-list` | `[{id, agentType, description, path, size_kb, mtime_iso}]` |
|
|
440
496
|
| `subagent-finals` | `[{id, agentType, text}]` |
|
|
@@ -236,13 +236,19 @@ python "$PY" --file <unknown-session>.jsonl --mode changelog | tail -30
|
|
|
236
236
|
## 8. Tool-call forensics ("when did I last `git push --force`?")
|
|
237
237
|
|
|
238
238
|
```bash
|
|
239
|
-
# Find
|
|
239
|
+
# Find which sessions contain a matching tool_use, across all projects
|
|
240
|
+
# (returns a per-session summary by default: one line per session with hit count + snippet)
|
|
240
241
|
python "$PY" --all-projects --mode search --query "git push --force" --in tool_use
|
|
241
242
|
|
|
243
|
+
# Add --full to expand to match windows instead of the per-session summary
|
|
244
|
+
python "$PY" --all-projects --mode search --query "git push --force" --in tool_use --full
|
|
245
|
+
|
|
242
246
|
# Get full forensics on the session that matched
|
|
243
247
|
python "$PY" --file <matching-session>.jsonl --mode tool-calls --tool Bash
|
|
244
248
|
```
|
|
245
249
|
|
|
250
|
+
Wide-scope search (`--all-projects`, `--cwd`, `--project`) returns a **per-session summary** by default — one line per matching session with a hit count and first snippet — so the output stays under the harness's Read cap. Add `--full` to expand to match windows (bounded by a ~10k-token budget; degrades back to summary with a note if it overflows). Single-file search (`--file`) always returns full windows.
|
|
251
|
+
|
|
246
252
|
---
|
|
247
253
|
|
|
248
254
|
## 8b. "Which skills (or tools) do I actually use?" — true invocation counts
|
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/parser.cpython-313.pyc
ADDED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/paths.cpython-313.pyc
ADDED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/search.cpython-313.pyc
ADDED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/subagents.cpython-313.pyc
ADDED
|
Binary file
|
package/skills/estack-read-claude-session-history/scripts/lib/__pycache__/tools.cpython-313.pyc
ADDED
|
Binary file
|
|
@@ -282,9 +282,10 @@ def filter_by_time(
|
|
|
282
282
|
# Strip tzinfo for naive comparison
|
|
283
283
|
if ts.tzinfo is not None:
|
|
284
284
|
ts = ts.replace(tzinfo=None)
|
|
285
|
+
# Half-open window [since, until): since inclusive, until exclusive.
|
|
285
286
|
if since is not None and ts < since:
|
|
286
287
|
continue
|
|
287
|
-
if until is not None and ts
|
|
288
|
+
if until is not None and ts >= until:
|
|
288
289
|
continue
|
|
289
290
|
out.append(m)
|
|
290
291
|
return out
|
|
@@ -70,6 +70,16 @@ def _entry_search_text(obj: dict, in_channel: str) -> list[tuple[str, str]]:
|
|
|
70
70
|
return out
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def _show_progress(progress: bool) -> bool:
|
|
74
|
+
"""Progress is for a live terminal only.
|
|
75
|
+
|
|
76
|
+
When stderr is captured to a file or pipe (e.g. by a tool harness), the
|
|
77
|
+
per-file ``Searching i/N…\\r`` stream lands as hundreds of literal lines and
|
|
78
|
+
inflates the captured output. Suppress it unless stderr is an interactive TTY.
|
|
79
|
+
"""
|
|
80
|
+
return progress and sys.stderr.isatty()
|
|
81
|
+
|
|
82
|
+
|
|
73
83
|
def _window(text: str, q: str, n: int = 200) -> str:
|
|
74
84
|
"""Return up to n chars of context around the first match of q."""
|
|
75
85
|
if not text or not q:
|
|
@@ -90,7 +100,7 @@ def search_session(
|
|
|
90
100
|
path: Path,
|
|
91
101
|
query: str,
|
|
92
102
|
role: Literal["user", "assistant", "both"] = "both",
|
|
93
|
-
in_channel: Literal["text", "tool_use", "thinking", "all"] = "text",
|
|
103
|
+
in_channel: Literal["text", "tool_use", "tool_result", "thinking", "all"] = "text",
|
|
94
104
|
since: datetime | None = None,
|
|
95
105
|
until: datetime | None = None,
|
|
96
106
|
) -> list[Match]:
|
|
@@ -117,9 +127,10 @@ def search_session(
|
|
|
117
127
|
continue
|
|
118
128
|
if ts_dt.tzinfo is not None:
|
|
119
129
|
ts_dt = ts_dt.replace(tzinfo=None)
|
|
130
|
+
# Half-open window [since, until): since inclusive, until exclusive.
|
|
120
131
|
if since is not None and ts_dt < since:
|
|
121
132
|
continue
|
|
122
|
-
if until is not None and ts_dt
|
|
133
|
+
if until is not None and ts_dt >= until:
|
|
123
134
|
continue
|
|
124
135
|
for where, text in _entry_search_text(obj, in_channel):
|
|
125
136
|
if q in text.lower():
|
|
@@ -138,15 +149,19 @@ def search_project(
|
|
|
138
149
|
project_dir: Path,
|
|
139
150
|
query: str,
|
|
140
151
|
role: Literal["user", "assistant", "both"] = "both",
|
|
141
|
-
in_channel: Literal["text", "tool_use", "thinking", "all"] = "text",
|
|
152
|
+
in_channel: Literal["text", "tool_use", "tool_result", "thinking", "all"] = "text",
|
|
142
153
|
since: datetime | None = None,
|
|
143
154
|
until: datetime | None = None,
|
|
144
155
|
progress: bool = True,
|
|
145
156
|
) -> Iterator[Match]:
|
|
146
157
|
"""Search every transcript in a project directory, newest first."""
|
|
147
|
-
files
|
|
158
|
+
# Filter files by mtime >= since only. A session last written after `until`
|
|
159
|
+
# may still contain messages inside the window, so we don't bound file mtime
|
|
160
|
+
# above — search_session() applies the `until` cut per-message instead.
|
|
161
|
+
files = _paths.list_transcripts(project_dir, since=since)
|
|
162
|
+
show = _show_progress(progress)
|
|
148
163
|
for i, f in enumerate(files, 1):
|
|
149
|
-
if
|
|
164
|
+
if show:
|
|
150
165
|
print(
|
|
151
166
|
f"Searching {i}/{len(files)}: {f.name}...",
|
|
152
167
|
file=sys.stderr,
|
|
@@ -156,8 +171,11 @@ def search_project(
|
|
|
156
171
|
for m in search_session(f, query, role, in_channel, since, until):
|
|
157
172
|
yield m
|
|
158
173
|
except Exception as e:
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
# Lead with \n only to clear an active \r progress line; when progress
|
|
175
|
+
# is suppressed there's no line to clear, so don't add a blank one.
|
|
176
|
+
prefix = "\n" if show else ""
|
|
177
|
+
print(f"{prefix}Error reading {f.name}: {e}", file=sys.stderr)
|
|
178
|
+
if show:
|
|
161
179
|
print(file=sys.stderr)
|
|
162
180
|
|
|
163
181
|
|
|
@@ -165,14 +183,14 @@ def search_all_projects(
|
|
|
165
183
|
root: Path,
|
|
166
184
|
query: str,
|
|
167
185
|
role: Literal["user", "assistant", "both"] = "both",
|
|
168
|
-
in_channel: Literal["text", "tool_use", "thinking", "all"] = "text",
|
|
186
|
+
in_channel: Literal["text", "tool_use", "tool_result", "thinking", "all"] = "text",
|
|
169
187
|
since: datetime | None = None,
|
|
170
188
|
until: datetime | None = None,
|
|
171
189
|
progress: bool = True,
|
|
172
190
|
) -> Iterator[Match]:
|
|
173
191
|
"""Walk every project directory under root."""
|
|
174
192
|
for project_dir in _paths.list_projects(root):
|
|
175
|
-
if progress:
|
|
193
|
+
if _show_progress(progress):
|
|
176
194
|
print(f"--- {project_dir.name} ---", file=sys.stderr)
|
|
177
195
|
yield from search_project(
|
|
178
196
|
project_dir, query, role, in_channel, since, until, progress
|