Add transcript backup tracking system and pre-compact hook improvements

- pre-compact-backup.sh: derive transcript path from session_id+cwd (no
  longer relies on transcript_path field that Claude Code stopped providing);
  register each backup in tracking.json after saving
- extract-transcripts.py: new script managing tracking.json — register,
  list, extract conversation text, mark-processed modes
- list-transcripts-here.sh: thin wrapper for extract-transcripts --list
  using $(pwd); needed because SKILL.md bang commands reject $() substitution
- install-hooks.sh: now also symlinks skill-helper scripts into
  ~/.claude/scripts/ via a curated SKILL_HELPERS list
- Memory docs: new script-extract-transcripts.md, script-list-transcripts-here.md;
  updated skill-log.md, script-install-hooks.md, MEMORY.md index

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-04-13 14:06:09 +12:00
parent 52b0f2b5c6
commit 6fd0dac218
9 changed files with 439 additions and 24 deletions

View File

@@ -1,17 +1,36 @@
#!/usr/bin/env bash
# Pre-compact hook: save a copy of the session transcript before compaction.
# Claude Code pipes JSON to stdin with session_id, transcript_path, cwd, etc.
# Claude Code pipes JSON with session_id, cwd, hook_event_name (no transcript_path).
# Derives the transcript location from session_id + cwd.
# After saving, registers the backup in ~/.claude/transcript-backups/tracking.json.
set -euo pipefail
# Parse stdin JSON for transcript path and session ID
# Parse stdin JSON
INPUT="$(cat)"
TRANSCRIPT_PATH="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null)"
SESSION_ID="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null)"
TRIGGER="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('trigger','unknown'))" 2>/dev/null)"
CWD="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('cwd',''))" 2>/dev/null)"
PROVIDED_PATH="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null)"
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
echo "pre-compact-backup: no transcript found, skipping" >&2
if [ -z "$SESSION_ID" ]; then
echo "pre-compact-backup: missing session_id in hook input, skipping" >&2
exit 0
fi
# Try provided path first (older Claude Code versions), then derive from session_id + cwd
if [ -n "$PROVIDED_PATH" ] && [ -f "$PROVIDED_PATH" ]; then
TRANSCRIPT_PATH="$PROVIDED_PATH"
elif [ -n "$CWD" ]; then
# Derive transcript path: /home/paul/dev/claude -> -home-paul-dev-claude
PROJECT_DIR="$(echo "$CWD" | tr '/' '-')"
TRANSCRIPT_PATH="$HOME/.claude/projects/${PROJECT_DIR}/${SESSION_ID}.jsonl"
else
echo "pre-compact-backup: cannot locate transcript (no cwd or transcript_path), skipping" >&2
exit 0
fi
if [ ! -f "$TRANSCRIPT_PATH" ]; then
echo "pre-compact-backup: transcript not found at ${TRANSCRIPT_PATH}, skipping" >&2
exit 0
fi
@@ -20,11 +39,21 @@ BACKUP_DIR="$HOME/.claude/transcript-backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP_FILE="${BACKUP_DIR}/${TIMESTAMP}-${SESSION_ID:0:8}-${TRIGGER}.jsonl"
BACKUP_NAME="${TIMESTAMP}-${SESSION_ID:0:8}.jsonl"
BACKUP_FILE="${BACKUP_DIR}/${BACKUP_NAME}"
cp "$TRANSCRIPT_PATH" "$BACKUP_FILE"
# Prune backups older than 30 days
find "$BACKUP_DIR" -name "*.jsonl" -mtime +30 -delete 2>/dev/null || true
echo "pre-compact-backup: saved $(wc -l < "$BACKUP_FILE") lines to ${BACKUP_FILE##*/}" >&2
# Register backup in tracking.json using the register helper
python3 "$HOME/.claude/scripts/extract-transcripts.py" \
--register "$BACKUP_NAME" \
--session-id "$SESSION_ID" \
--cwd "$CWD" \
2>/dev/null \
&& TRACKED=" [tracked]" \
|| TRACKED=" [tracking failed]"
echo "pre-compact-backup: saved $(wc -l < "$BACKUP_FILE") lines to ${BACKUP_NAME} [project: ${CWD}]${TRACKED}" >&2