neander-checkpoints 0.1.0__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.
Files changed (32) hide show
  1. neander_checkpoints-0.1.0/LICENSE +21 -0
  2. neander_checkpoints-0.1.0/PKG-INFO +8 -0
  3. neander_checkpoints-0.1.0/README.md +199 -0
  4. neander_checkpoints-0.1.0/pyproject.toml +23 -0
  5. neander_checkpoints-0.1.0/setup.cfg +4 -0
  6. neander_checkpoints-0.1.0/src/neander_checkpoints/__init__.py +3 -0
  7. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/hooks/hooks_config.json +26 -0
  8. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/hooks/project_claude_snippet.md +36 -0
  9. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/checkpoint.sh +173 -0
  10. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/detect_commit.sh +55 -0
  11. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/link_commit.sh +29 -0
  12. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/on_stop.sh +45 -0
  13. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/parse_jsonl.py +1103 -0
  14. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/persist_summary.sh +28 -0
  15. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/redact.py +239 -0
  16. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/restore.sh +111 -0
  17. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/scripts/save_summary.sh +112 -0
  18. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-redact/SKILL.md +39 -0
  19. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-search/SKILL.md +42 -0
  20. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-session-stats/SKILL.md +28 -0
  21. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-status/SKILL.md +22 -0
  22. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-summarize/SKILL.md +94 -0
  23. neander_checkpoints-0.1.0/src/neander_checkpoints/bundled/skills/neander-transcript/SKILL.md +37 -0
  24. neander_checkpoints-0.1.0/src/neander_checkpoints/cli.py +80 -0
  25. neander_checkpoints-0.1.0/src/neander_checkpoints/install.py +295 -0
  26. neander_checkpoints-0.1.0/src/neander_checkpoints/resume.py +195 -0
  27. neander_checkpoints-0.1.0/src/neander_checkpoints.egg-info/PKG-INFO +8 -0
  28. neander_checkpoints-0.1.0/src/neander_checkpoints.egg-info/SOURCES.txt +30 -0
  29. neander_checkpoints-0.1.0/src/neander_checkpoints.egg-info/dependency_links.txt +1 -0
  30. neander_checkpoints-0.1.0/src/neander_checkpoints.egg-info/entry_points.txt +2 -0
  31. neander_checkpoints-0.1.0/src/neander_checkpoints.egg-info/top_level.txt +1 -0
  32. neander_checkpoints-0.1.0/tests/test_parse_jsonl.py +1013 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neander.ai
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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: neander-checkpoints
3
+ Version: 0.1.0
4
+ Summary: Checkpoint management for Claude Code sessions
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ License-File: LICENSE
8
+ Dynamic: license-file
@@ -0,0 +1,199 @@
1
+ # neander_checkpoints
2
+
3
+ Capture Claude Code sessions alongside your Git history. Understand *why* code changed, not just *what*. Resume where you left off — even on a different machine.
4
+
5
+ Built natively for Claude Code using hooks, skills, and scripts. No external binaries. Install once, works automatically.
6
+
7
+ ## Why
8
+
9
+ Your git log shows what code changed. But when AI writes your code, the *how* and *why* live in the conversation — prompts, reasoning, tool calls, dead ends, decisions. Without capturing that, you lose context the moment a session ends.
10
+
11
+ neander_checkpoints solves this:
12
+
13
+ - **Understand why code changed** — see the full prompt/response transcript and files touched for any checkpoint
14
+ - **Keep git history clean** — checkpoint data lives on a separate orphan branch, never pollutes your working tree
15
+ - **Onboard faster** — show teammates the path from prompt to change to commit
16
+ - **Search across checkpoints** — find the checkpoint where you fixed the auth bug, by keyword, branch, file, or just asking in natural language
17
+ - **Remote checkpoint support** — fetch checkpoints from remote with `--fetch`, search and browse across machines
18
+ - **Cross-machine resume** — push checkpoints to remote, pull them on another machine, continue exactly where you left off
19
+
20
+ ## How it works
21
+
22
+ Once installed, everything is automatic:
23
+
24
+ 1. **You code with Claude** — business as usual
25
+ 2. **On every commit**, a hook captures the session transcript and metadata as a checkpoint on the `neander/checkpoints/v1` orphan branch
26
+ 3. **On session end**, a final checkpoint captures everything even if no commits were made
27
+ 4. **Commits get linked** — a `Claude-Session` trailer is added so you can trace any commit back to the conversation that produced it
28
+ 5. **All reads go through the checkpoint branch** — commands read directly from git, not local files. Use `--fetch` to pull remote checkpoint data first
29
+
30
+ When you need context, just ask naturally:
31
+
32
+ | You say | What happens |
33
+ |---|---|
34
+ | "What did I do yesterday?" | Claude searches checkpoints, shows relevant results |
35
+ | "Why did we make this change?" | Claude finds the checkpoint that touched the file, reads the transcript |
36
+
37
+ | "Go back to before that change" | Claude lists checkpoints and offers to restore |
38
+ | "How much did that checkpoint cost?" | Claude shows token usage and cost estimate |
39
+
40
+ ## Commands
41
+
42
+ All commands work as Claude Code skills — Claude auto-invokes them based on conversation context. You can also use them explicitly as slash commands.
43
+
44
+ All commands accept checkpoint IDs (16-char hex like `a3f8b9c1d2e4`), session IDs (UUIDs), or partial IDs.
45
+
46
+ | Command | Description |
47
+ |---|---|
48
+ | `/neander-status` | Overview of active sessions and recent checkpoints |
49
+ | `/neander-search` | Search checkpoints by keyword, branch, file, date, commit, or natural language |
50
+ | `/neander-transcript` | View the condensed conversation transcript for a checkpoint |
51
+ | `/neander-summarize` | Generate an AI summary (intent, outcome, learnings, friction, open items) and persist it |
52
+ | `/neander-session-stats` | Token usage, cost estimate, duration, files modified for a checkpoint |
53
+ | `/neander-redact` | Scan a transcript for secrets and PII before sharing |
54
+
55
+ ## Features
56
+
57
+ ### Status
58
+
59
+ Shows the current session and recent checkpoints at a glance.
60
+
61
+ ```
62
+ Current: 17e8f125 · opus · feat/impl-tasks-from-td · 0.4k tokens · 0 files
63
+ (not yet checkpointed)
64
+
65
+ == Checkpoints (18 total) ==
66
+
67
+ Checkpoint Commit Session Date Files Topic
68
+ ------------ -------- -------- ---------------- ----- -----
69
+ dfe7c7132205 70e684cf 37252de3 2026-03-28 13:20 9 Simplify the generate_tasks flow...
70
+ b4d88d5d4fe9 70e684cf 37252de3 2026-03-28 13:20 9
71
+ 7b02e43d74db 67ff5c5c 37252de3 2026-03-28 13:18 9
72
+ ```
73
+
74
+ ### Search
75
+
76
+ Find any checkpoint by keyword, branch, file, date, or commit — or just describe what you're looking for.
77
+
78
+ ```
79
+ > "find the checkpoint where I fixed the WebSocket reconnection bugs"
80
+ > /neander-search OAuth on feat/auth
81
+ ```
82
+
83
+ ### Transcripts
84
+
85
+ Clean, readable conversation flow — strips IDE noise, tool results, and thinking blocks.
86
+
87
+ ```
88
+ --- 2026-03-22 ---
89
+
90
+ 12:21 [User] Implement the following plan...
91
+
92
+ 12:21 [Assistant] I'll read both files in parallel.
93
+
94
+ [Tool] Read: modules/chat/chat_websocket_handler.py
95
+
96
+ [Tool] Edit: modules/chat/chat_websocket_handler.py
97
+
98
+ [Tool] Bash: Run chat module tests
99
+
100
+ 12:22 [Assistant] Both fixes are done.
101
+
102
+ 12:30 [User] Can we also handle the edge case for...
103
+
104
+ 12:30 [Assistant] Good catch, I'll add validation.
105
+
106
+ [Tool] Edit: modules/chat/chat_websocket_handler.py
107
+ ```
108
+
109
+ ### AI summaries
110
+
111
+ Structured summaries generated once, persisted to the checkpoint branch, cached across sessions and machines.
112
+
113
+ ```
114
+ ### Intent
115
+ Fix two reliability bugs in chat streaming: replay time window and upsert atomicity.
116
+
117
+ ### Outcome
118
+ Both fixes implemented and tested.
119
+
120
+ ### Learnings
121
+ **Code**:
122
+ - `message_repository.py:94-121` — arrayFilters with $set is the right pattern for atomic MongoDB array updates
123
+
124
+ ### Friction
125
+ - Unused datetime import needed a second cleanup pass
126
+
127
+ ### Open Items
128
+ - Pre-existing test failure needs investigation
129
+ ```
130
+
131
+ ### Cross-machine resume
132
+
133
+ Checkpoints are pushed to the remote automatically. On another machine:
134
+
135
+ ```
136
+ # Fetches transcript from remote, prints: claude --resume <session-id>
137
+ ```
138
+
139
+
140
+ ### Secret redaction
141
+
142
+ Three-layer detection before transcripts leave your machine:
143
+ 1. **Shannon entropy** — high-entropy strings (API keys, tokens)
144
+ 2. **Pattern matching** — 15+ known formats (AWS keys, GitHub PATs, JWTs, connection strings)
145
+ 3. **PII detection** — emails, phone numbers, SSNs
146
+
147
+ See [EXAMPLES.md](EXAMPLES.md) for full output examples of every command.
148
+
149
+ ## Checkpoint format
150
+
151
+ Stored on `neander/checkpoints/v1` — a versioned orphan branch that never touches your code history.
152
+
153
+ ```
154
+ neander/checkpoints/v1/
155
+ ├── index.log # fast lookup index
156
+ ├── a3/
157
+ │ └── f8b9c1d2e4567890/
158
+ │ ├── metadata.json # checkpoint ID, session IDs, commit, files, AI summary
159
+ │ ├── transcript-<session-1>.jsonl
160
+ │ └── transcript-<session-2>.jsonl
161
+ ```
162
+
163
+ - **Multi-session** — concurrent sessions on the same commit don't collide
164
+ - **Persisted summaries** — AI summaries cached in metadata.json
165
+ - **Auto-push** — checkpoints pushed to remote after creation
166
+
167
+ ## Install
168
+
169
+ ```bash
170
+ pip install neander-checkpoints
171
+ ```
172
+
173
+ Then in any project:
174
+
175
+ ```bash
176
+ cd /path/to/project
177
+ neander-checkpoints install
178
+ ```
179
+
180
+ That's it. The installer validates prerequisites (git repo, Claude Code, Python 3.10+), then copies scripts, skills, hooks, and permissions into the project's `.claude/` directory. Everything is self-contained — anyone who clones the repo gets it working out of the box.
181
+
182
+ ### What gets installed
183
+
184
+ - **Scripts** — JSONL parser, checkpoint creator, secret redaction, session restore
185
+ - **Skills** — 6 auto-invoked skills that Claude triggers based on conversation context
186
+ - **Hooks** — `Stop` and `PostToolUse:Bash` hooks for automatic checkpointing and commit linking
187
+ - **Permissions** — auto-allow rules so scripts run without approval prompts
188
+ - **CLAUDE.md** — instructions telling Claude when to proactively use checkpoint tools
189
+ - **Pre-push hook** — redacts secrets from transcripts before they leave your machine
190
+
191
+ ## Requirements
192
+
193
+ - Python 3.10+
194
+ - Claude Code 2.x
195
+ - git
196
+
197
+ ## License
198
+
199
+ MIT
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "neander-checkpoints"
3
+ version = "0.1.0"
4
+ description = "Checkpoint management for Claude Code sessions"
5
+ requires-python = ">=3.10"
6
+ license = "MIT"
7
+
8
+ [project.scripts]
9
+ neander-checkpoints = "neander_checkpoints.cli:main"
10
+
11
+ [build-system]
12
+ requires = ["setuptools>=68.0"]
13
+ build-backend = "setuptools.build_meta"
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
17
+
18
+ [tool.setuptools.package-data]
19
+ neander_checkpoints = [
20
+ "bundled/scripts/*",
21
+ "bundled/skills/**/*",
22
+ "bundled/hooks/*",
23
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Checkpoint management for Claude Code sessions."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash __SCRIPTS_DIR__/on_stop.sh 2>/dev/null || true"
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "PostToolUse": [
15
+ {
16
+ "matcher": "Bash",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "bash __SCRIPTS_DIR__/detect_commit.sh 2>/dev/null || true"
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,36 @@
1
+ ## Checkpoint Management (neander_checkpoints)
2
+
3
+ This project has checkpoint management tools installed in `.claude/scripts/` and `.claude/skills/`.
4
+
5
+ ### IMPORTANT: Always use skills, not raw scripts
6
+
7
+ When the user asks about checkpoints, sessions, summaries, transcripts, or history, **invoke the corresponding skill using the Skill tool** — do NOT try to do it yourself with raw git commands or scripts. The skills handle persistence and formatting correctly.
8
+
9
+ | User says | Invoke this skill |
10
+ |---|---|
11
+ | "summarize checkpoint ..." | `/neander-summarize` |
12
+ | "show transcript ...", "what happened in ..." | `/neander-transcript` |
13
+ | "search checkpoints ...", "find the checkpoint where ..." | `/neander-search` |
14
+ | "what did I do yesterday/last week" | `/neander-search` |
15
+ | "checkpoint stats", "how much did it cost" | `/neander-session-stats` |
16
+ | "recent checkpoints", "what's been going on" | `/neander-status` |
17
+
18
+ ### When to use these tools proactively
19
+
20
+ You don't need to wait for the user to run a slash command. Use the skills naturally when the context calls for it:
21
+
22
+ - **User asks about previous work** → invoke `/neander-search`
23
+ - **User asks about code history beyond git** → invoke `/neander-search`, then `/neander-transcript`
24
+ - **User seems lost or is re-doing work** → invoke `/neander-search` to check if a previous checkpoint solved this
25
+
26
+ ### Available scripts (for direct use only when skills don't cover the case)
27
+
28
+ All commands read from the git checkpoint branch. Use `--fetch` to pull remote data first. The primary flag is `--checkpoint`/`-c` (`--session`/`-s` still works as alias).
29
+
30
+ ```bash
31
+ python3 __SCRIPTS_DIR__/parse_jsonl.py list --project <cwd>
32
+ python3 __SCRIPTS_DIR__/parse_jsonl.py search --project <cwd> --keyword "text" --branch "name" --fetch
33
+ python3 __SCRIPTS_DIR__/parse_jsonl.py stats --checkpoint <checkpoint-id>
34
+ python3 __SCRIPTS_DIR__/parse_jsonl.py transcript --checkpoint <checkpoint-id>
35
+ bash __SCRIPTS_DIR__/restore.sh <session-id> <cwd>
36
+ ```
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # checkpoint.sh — Save current session transcript to a git orphan branch.
4
+ #
5
+ # Creates/updates a neander/checkpoints/v1 orphan branch with session
6
+ # transcripts and metadata. Supports multiple sessions per checkpoint.
7
+ #
8
+ # Usage:
9
+ # checkpoint.sh <session_jsonl_path> [commit_sha]
10
+ # checkpoint.sh --commit <sha> <path1> [path2] ...
11
+ #
12
+
13
+ set -euo pipefail
14
+
15
+ CHECKPOINT_BRANCH="neander/checkpoints/v1"
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ PARSER="$SCRIPT_DIR/parse_jsonl.py"
18
+
19
+ # Parse args
20
+ SESSION_FILES=()
21
+ COMMIT_SHA=""
22
+
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --commit)
26
+ COMMIT_SHA="$2"
27
+ shift 2
28
+ ;;
29
+ *)
30
+ SESSION_FILES+=("$1")
31
+ shift
32
+ ;;
33
+ esac
34
+ done
35
+
36
+ if [ ${#SESSION_FILES[@]} -eq 0 ]; then
37
+ echo "Usage: checkpoint.sh <session_jsonl_path> [commit_sha]" >&2
38
+ exit 1
39
+ fi
40
+
41
+ # Backward compat: if two positional args and second isn't a file, it's commit sha
42
+ if [ ${#SESSION_FILES[@]} -ge 2 ] && [ -z "$COMMIT_SHA" ]; then
43
+ LAST_IDX=$(( ${#SESSION_FILES[@]} - 1 ))
44
+ LAST="${SESSION_FILES[$LAST_IDX]}"
45
+ if [ ! -f "$LAST" ]; then
46
+ COMMIT_SHA="$LAST"
47
+ unset "SESSION_FILES[$LAST_IDX]"
48
+ fi
49
+ fi
50
+
51
+ [ -z "$COMMIT_SHA" ] && COMMIT_SHA="$(git rev-parse HEAD 2>/dev/null || echo 'none')"
52
+
53
+ TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
54
+
55
+ # Collect valid session IDs and files
56
+ VALID_FILES=()
57
+ SESSION_IDS=()
58
+ for f in "${SESSION_FILES[@]}"; do
59
+ if [ ! -f "$f" ]; then
60
+ echo "Warning: Session file not found, skipping: $f" >&2
61
+ continue
62
+ fi
63
+ VALID_FILES+=("$f")
64
+ SESSION_IDS+=("$(basename "$f" .jsonl)")
65
+ done
66
+
67
+ if [ ${#VALID_FILES[@]} -eq 0 ]; then
68
+ echo "Error: No valid session files provided" >&2
69
+ exit 1
70
+ fi
71
+
72
+ # Generate checkpoint ID
73
+ CHECKPOINT_INPUT=""
74
+ for sid in "${SESSION_IDS[@]}"; do
75
+ CHECKPOINT_INPUT="${CHECKPOINT_INPUT}${sid}"
76
+ done
77
+ CHECKPOINT_INPUT="${CHECKPOINT_INPUT}${TIMESTAMP}"
78
+ CHECKPOINT_ID="$(echo "$CHECKPOINT_INPUT" | shasum -a 256 | cut -c1-16)"
79
+
80
+ # Shard directory
81
+ SHARD_DIR="${CHECKPOINT_ID:0:2}/${CHECKPOINT_ID:2}"
82
+
83
+ # Collect modified files from all sessions
84
+ ALL_FILES_JSON="[]"
85
+ for f in "${VALID_FILES[@]}"; do
86
+ stats="$(python3 "$PARSER" stats --session "$f" --json 2>/dev/null || echo '{}')"
87
+ files="$(echo "$stats" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('modified_files',[])))" 2>/dev/null || echo '[]')"
88
+ ALL_FILES_JSON="$(python3 -c "
89
+ import json
90
+ a = json.loads('$ALL_FILES_JSON')
91
+ b = json.loads('$files')
92
+ print(json.dumps(sorted(set(a + b))))
93
+ ")"
94
+ done
95
+
96
+ # Build session IDs JSON
97
+ SESSION_IDS_JSON="$(python3 -c "import json; print(json.dumps($(printf '"%s",' "${SESSION_IDS[@]}" | sed 's/,$//' | sed 's/^/[/;s/$/]/')))")"
98
+
99
+ # Build metadata
100
+ METADATA="$(python3 -c "
101
+ import json
102
+ metadata = {
103
+ 'id': '$CHECKPOINT_ID',
104
+ 'session_ids': $SESSION_IDS_JSON,
105
+ 'commit_sha': '$COMMIT_SHA',
106
+ 'created_at': '$TIMESTAMP',
107
+ 'merged_files': $ALL_FILES_JSON,
108
+ 'summary': None
109
+ }
110
+ print(json.dumps(metadata, indent=2))
111
+ ")"
112
+
113
+ # Save current branch
114
+ ORIGINAL_BRANCH="$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse HEAD)"
115
+ STASH_NEEDED=false
116
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
117
+ STASH_NEEDED=true
118
+ git stash push -m "checkpoint-auto-stash" --quiet
119
+ fi
120
+
121
+ cleanup() {
122
+ git checkout "$ORIGINAL_BRANCH" --quiet 2>/dev/null || true
123
+ if [ "$STASH_NEEDED" = true ]; then
124
+ git stash pop --quiet 2>/dev/null || true
125
+ fi
126
+ }
127
+ trap cleanup EXIT
128
+
129
+ # Create orphan branch if it doesn't exist
130
+ if ! git rev-parse --verify "$CHECKPOINT_BRANCH" >/dev/null 2>&1; then
131
+ git checkout --orphan "$CHECKPOINT_BRANCH" --quiet
132
+ git rm -rf . --quiet 2>/dev/null || true
133
+ echo "# Claude Code Session Checkpoints (v1)" > README.md
134
+ git add README.md
135
+ git commit -m "Initialize checkpoint branch" --quiet
136
+ else
137
+ git checkout "$CHECKPOINT_BRANCH" --quiet
138
+ fi
139
+
140
+ # Create checkpoint directory
141
+ mkdir -p "$SHARD_DIR"
142
+
143
+ # Copy transcripts — one per session
144
+ for i in "${!VALID_FILES[@]}"; do
145
+ f="${VALID_FILES[$i]}"
146
+ sid="${SESSION_IDS[$i]}"
147
+ cp "$f" "$SHARD_DIR/transcript-${sid}.jsonl"
148
+ done
149
+
150
+ # Write metadata
151
+ echo "$METADATA" > "$SHARD_DIR/metadata.json"
152
+
153
+ # Update index
154
+ for sid in "${SESSION_IDS[@]}"; do
155
+ echo "$CHECKPOINT_ID|$sid|$COMMIT_SHA|$TIMESTAMP" >> index.log
156
+ done
157
+
158
+ # Commit
159
+ git add "$SHARD_DIR" index.log
160
+ git commit -m "checkpoint: ${CHECKPOINT_ID} sessions=${#SESSION_IDS[@]} commit=${COMMIT_SHA:0:8}" --quiet
161
+
162
+ CHECKPOINT_REF="$(git rev-parse HEAD)"
163
+
164
+ # Push to remote if one exists
165
+ if git remote get-url origin >/dev/null 2>&1; then
166
+ git push origin "$CHECKPOINT_BRANCH" --quiet 2>/dev/null || true
167
+ fi
168
+
169
+ echo "Checkpoint created: $CHECKPOINT_ID"
170
+ echo " Sessions: ${SESSION_IDS[*]}"
171
+ echo " Commit: $COMMIT_SHA"
172
+ echo " Branch: $CHECKPOINT_BRANCH"
173
+ echo " Ref: $CHECKPOINT_REF"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # detect_commit.sh — Hook script for PostToolUse:Bash
4
+ #
5
+ # Receives JSON on stdin from Claude Code hook system.
6
+ # Checks if the Bash command was a git commit on a user branch
7
+ # (not our checkpoint branch or internal scripts).
8
+ # If so, triggers checkpoint.sh and link_commit.sh.
9
+ #
10
+ # Usage: detect_commit.sh (reads JSON from stdin)
11
+ #
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+
17
+ # Read hook input from stdin
18
+ INPUT="$(cat)"
19
+
20
+ # Extract fields from JSON
21
+ COMMAND="$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")"
22
+ SESSION_ID="$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('session_id',''))" 2>/dev/null || echo "")"
23
+ TRANSCRIPT_PATH="$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('transcript_path',''))" 2>/dev/null || echo "")"
24
+
25
+ # Only trigger on user git commits, not our internal scripts
26
+ # Skip if:
27
+ # - command doesn't contain "git commit"
28
+ # - command is from checkpoint.sh, save_summary.sh, or link_commit.sh
29
+ # - current branch is the checkpoint branch
30
+ echo "$COMMAND" | grep -qE "git commit" || exit 0
31
+ echo "$COMMAND" | grep -qE "checkpoint|save_summary|persist_summary" && exit 0
32
+ [ -z "$SESSION_ID" ] && exit 0
33
+
34
+ CURRENT_BRANCH="$(git symbolic-ref --short HEAD 2>/dev/null || echo "")"
35
+ echo "$CURRENT_BRANCH" | grep -q "neander/checkpoints" && exit 0
36
+
37
+ # Link the commit to this session
38
+ "$SCRIPT_DIR/link_commit.sh" "$SESSION_ID"
39
+
40
+ # Find the session JSONL and checkpoint it at this commit
41
+ COMMIT_SHA="$(git rev-parse HEAD 2>/dev/null || echo 'none')"
42
+
43
+ SESSION_FILE=""
44
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
45
+ SESSION_FILE="$TRANSCRIPT_PATH"
46
+ else
47
+ PROJECTS_DIR="$HOME/.claude/projects"
48
+ if [ -d "$PROJECTS_DIR" ]; then
49
+ SESSION_FILE="$(find "$PROJECTS_DIR" -name "${SESSION_ID}.jsonl" -type f 2>/dev/null | head -1)"
50
+ fi
51
+ fi
52
+
53
+ if [ -n "$SESSION_FILE" ]; then
54
+ "$SCRIPT_DIR/checkpoint.sh" "$SESSION_FILE" "$COMMIT_SHA" &
55
+ fi
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # link_commit.sh — Add session metadata trailer to the most recent commit.
4
+ #
5
+ # Called by PostToolUse:Bash hook when a git commit is detected.
6
+ # Adds a "Claude-Session" trailer linking the commit to the active session.
7
+ #
8
+ # Usage: link_commit.sh <session_id>
9
+ #
10
+
11
+ set -euo pipefail
12
+
13
+ SESSION_ID="${1:?Usage: link_commit.sh <session_id>}"
14
+
15
+ # Only proceed if we're in a git repo
16
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
17
+ exit 0
18
+ fi
19
+
20
+ # Get the latest commit message
21
+ CURRENT_MSG="$(git log -1 --format=%B)"
22
+
23
+ # Don't add trailer if already present
24
+ if echo "$CURRENT_MSG" | grep -q "Claude-Session:"; then
25
+ exit 0
26
+ fi
27
+
28
+ # Amend the commit with the trailer
29
+ git commit --amend --no-edit --trailer "Claude-Session: $SESSION_ID" --quiet 2>/dev/null || true
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # on_stop.sh — Hook script for Stop event
4
+ #
5
+ # Receives JSON on stdin from Claude Code hook system.
6
+ # Only creates a checkpoint if the session actually modified files
7
+ # (i.e., used Write/Edit tools). Skips read-only sessions like
8
+ # running /neander-summarize or asking questions.
9
+ #
10
+ # Usage: on_stop.sh (reads JSON from stdin)
11
+ #
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ PARSER="$SCRIPT_DIR/parse_jsonl.py"
17
+
18
+ # Read hook input from stdin
19
+ INPUT="$(cat)"
20
+
21
+ # Extract fields from JSON
22
+ SESSION_ID="$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('session_id',''))" 2>/dev/null || echo "")"
23
+ TRANSCRIPT_PATH="$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('transcript_path',''))" 2>/dev/null || echo "")"
24
+
25
+ # Find session file
26
+ SESSION_FILE=""
27
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
28
+ SESSION_FILE="$TRANSCRIPT_PATH"
29
+ elif [ -n "$SESSION_ID" ]; then
30
+ PROJECTS_DIR="$HOME/.claude/projects"
31
+ if [ -d "$PROJECTS_DIR" ]; then
32
+ SESSION_FILE="$(find "$PROJECTS_DIR" -name "${SESSION_ID}.jsonl" -type f 2>/dev/null | head -1)"
33
+ fi
34
+ fi
35
+
36
+ [ -z "$SESSION_FILE" ] && exit 0
37
+
38
+ # Only checkpoint if the session modified files
39
+ FILE_COUNT="$(python3 "$PARSER" files --session "$SESSION_FILE" 2>/dev/null | wc -l | tr -d ' ')"
40
+ if [ "$FILE_COUNT" -eq 0 ]; then
41
+ exit 0
42
+ fi
43
+
44
+ COMMIT_SHA="$(git rev-parse HEAD 2>/dev/null || echo 'none')"
45
+ "$SCRIPT_DIR/checkpoint.sh" "$SESSION_FILE" "$COMMIT_SHA"