100 lines
3.4 KiB
Bash
Executable File
100 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# session-end.sh — SessionEnd hook
|
|
# Fires at session end; appends a journal entry to the vault's daily log.
|
|
# Exit code is ignored by Claude Code — used for cleanup/logging only.
|
|
|
|
# Capture stdin
|
|
STDIN=$(cat)
|
|
|
|
# Extract fields
|
|
SESSION_ID=$(echo "$STDIN" | jq -r '.session_id // empty')
|
|
REASON=$(echo "$STDIN" | jq -r '.reason // empty')
|
|
|
|
# Guard: if SESSION_ID is empty, use PID as fallback to ensure unique path
|
|
if [ -z "$SESSION_ID" ]; then
|
|
SESSION_ID="unknown-$$"
|
|
fi
|
|
|
|
# Parse config with python3+PyYAML; handles missing file, missing key, and tilde
|
|
VAULT_PATH=$(python3 -c '
|
|
import yaml, os
|
|
p = os.path.expanduser("~/.claude/plugins/memory/config.yaml")
|
|
cfg = (yaml.safe_load(open(p)) or {}) if os.path.exists(p) else {}
|
|
print(os.path.expanduser(cfg.get("vault_path", "~/Documents/SecondBrain")))
|
|
')
|
|
|
|
# Compute journal path
|
|
JOURNAL_DIR="$VAULT_PATH/journal"
|
|
JOURNAL_PATH="$JOURNAL_DIR/$(date -u +%Y-%m-%d).md"
|
|
|
|
# Ensure journal directory exists
|
|
mkdir -p "$JOURNAL_DIR"
|
|
|
|
# Create journal note if absent (minimal frontmatter)
|
|
if [[ ! -f "$JOURNAL_PATH" ]]; then
|
|
cat >"$JOURNAL_PATH" <<'EOF'
|
|
---
|
|
summary: Daily session log
|
|
tags: [scope/global, type/log]
|
|
---
|
|
EOF
|
|
fi
|
|
|
|
# Read touched paths from temp file
|
|
TOUCH_FILE="/tmp/claude-vault-touched-$SESSION_ID"
|
|
TOUCHED=$(cat "$TOUCH_FILE" 2>/dev/null || echo "")
|
|
|
|
# Format touched paths list
|
|
if [[ -z "$TOUCHED" ]]; then
|
|
TOUCHED_LIST="(none)"
|
|
else
|
|
TOUCHED_LIST="$TOUCHED"
|
|
fi
|
|
|
|
# Determine project context
|
|
PROJECT=$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-unknown}")
|
|
|
|
# UTC timestamp for entry header
|
|
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
# Append journal entry
|
|
{
|
|
echo ""
|
|
echo "## Session — $TIMESTAMP"
|
|
echo ""
|
|
echo "**Project:** $PROJECT"
|
|
echo "**Reason:** $REASON"
|
|
echo "**Vault notes touched:**"
|
|
echo "$TOUCHED_LIST"
|
|
} >> "$JOURNAL_PATH"
|
|
|
|
# Cleanup temp file
|
|
rm -f "$TOUCH_FILE"
|
|
|
|
# ── memsearch memory git sync (fail-safe) ───────────────────────────────────
|
|
# Auto-commit + push the memsearch memory store (~/.memsearch) on session end.
|
|
# We own this sync here so we never patch the marketplace memsearch plugin
|
|
# (which clobbers on update). The whitelist .gitignore in ~/.memsearch tracks
|
|
# only memory/*.md + .gitignore, so `add -A` is safe.
|
|
#
|
|
# Robustness contract: this block MUST NEVER fail session shutdown.
|
|
# - Skips silently if ~/.memsearch is not a git repo.
|
|
# - Only commits when something is staged (no empty commits).
|
|
# - Push is hard-timeboxed and failures are swallowed; the next session's
|
|
# hook catches up since the daily memory files are append-only.
|
|
# Wrapped in a subshell with `|| true` so nothing here can abort the hook.
|
|
(
|
|
MEMSEARCH_DIR="/home/jared/.memsearch"
|
|
if [ -d "$MEMSEARCH_DIR/.git" ]; then
|
|
git -C "$MEMSEARCH_DIR" add -A >/dev/null 2>&1
|
|
if ! git -C "$MEMSEARCH_DIR" diff --cached --quiet; then
|
|
git -C "$MEMSEARCH_DIR" commit -m "memsearch: session memory $(date +%F)" >/dev/null 2>&1
|
|
echo "[session-end] memsearch memory committed"
|
|
fi
|
|
timeout 30 git -C "$MEMSEARCH_DIR" push --quiet origin main >/dev/null 2>&1 || true
|
|
fi
|
|
) || true
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
exit 0
|