axel-setup 0.2.0
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/CHANGELOG.md +218 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/agents/api-design.md +51 -0
- package/agents/bughunter.md +136 -0
- package/agents/changelog.md +89 -0
- package/agents/cleanup.md +126 -0
- package/agents/compare-branch.md +35 -0
- package/agents/cross-repo.md +97 -0
- package/agents/db-check.md +14 -0
- package/agents/debug.md +47 -0
- package/agents/deploy-check.md +100 -0
- package/agents/draft-message.md +19 -0
- package/agents/excelsior-coordinator.md +75 -0
- package/agents/excelsior-verifier.md +94 -0
- package/agents/feature.md +48 -0
- package/agents/harness-optimizer.md +40 -0
- package/agents/incident.md +48 -0
- package/agents/linear-task.md +18 -0
- package/agents/onboard.md +24 -0
- package/agents/perf.md +44 -0
- package/agents/production-validator.md +96 -0
- package/agents/review.md +113 -0
- package/agents/security-check.md +29 -0
- package/agents/sprint-summary.md +15 -0
- package/agents/tdd-mainder.md +178 -0
- package/agents/test-gen.md +39 -0
- package/axel-manifest.json +129 -0
- package/bin/axel-setup.js +597 -0
- package/bootstrap.sh +1087 -0
- package/commands/create-pr.md +13 -0
- package/commands/daily.md +182 -0
- package/commands/deslop.md +13 -0
- package/commands/draft-message.md +23 -0
- package/commands/eod-review.md +154 -0
- package/commands/execute-prp.md +37 -0
- package/commands/generate-prp.md +75 -0
- package/commands/multi-repo-feature.md +60 -0
- package/commands/roadmap.md +31 -0
- package/commands/sprint-status.md +486 -0
- package/commands/style.md +68 -0
- package/commands/visualize.md +17 -0
- package/docs/roadmap/multi-runtime.md +73 -0
- package/docs/superpowers/plans/2026-06-12-setup-hardening-roadmap.md +61 -0
- package/hooks/desktop-notify.sh +26 -0
- package/hooks/enforce-agent-model.jq +14 -0
- package/hooks/gsd-context-monitor.js +156 -0
- package/hooks/linear-lifecycle-sync.sh +112 -0
- package/hooks/memory-dedup.sh +122 -0
- package/hooks/memory-extractor.sh +218 -0
- package/hooks/post-commit-memory-trigger.sh +16 -0
- package/hooks/post-commit-verify.sh +41 -0
- package/hooks/post-edit-lint.sh +43 -0
- package/hooks/precompact-save-context.sh +124 -0
- package/hooks/priority-map-staleness.sh +29 -0
- package/hooks/proactive-resolver.sh +104 -0
- package/hooks/session-auto-title.sh +165 -0
- package/hooks/session-checkpoint.sh +97 -0
- package/hooks/session-cost-log.sh +77 -0
- package/hooks/session-log-action.sh +36 -0
- package/hooks/session-log-prompt.sh +25 -0
- package/hooks/session-restore.sh +45 -0
- package/hooks/session-save.sh +81 -0
- package/hooks/session-summarize.sh +154 -0
- package/hooks/validate-commit-format.sh +38 -0
- package/hooks/weekly-priority-map-review.sh +143 -0
- package/install.sh +46 -0
- package/package.json +67 -0
- package/scripts/ci/bootstrap-dry-run.sh +40 -0
- package/scripts/ci/check.sh +65 -0
- package/scripts/posthog-snapshot-loader.sh +112 -0
- package/skills/context-budget/SKILL.md +86 -0
- package/skills/memory-review/SKILL.md +100 -0
- package/skills/model-routing/SKILL.md +70 -0
- package/skills/posthog-weekly/SKILL.md +271 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/templates/AGENTS.runtime.md +17 -0
- package/templates/CLAUDE.md +252 -0
- package/templates/claude-monitor.plist +35 -0
- package/templates/keybindings.json +13 -0
- package/templates/merge-settings.jq +53 -0
- package/templates/review-upgrades.md +44 -0
- package/templates/settings.json +255 -0
- package/templates/statusline-command.sh +182 -0
- package/tests/fixtures/hooks/events.json +32 -0
- package/tools/session-costs-view.sh +128 -0
- package/tools/session-dashboard-gen.sh +369 -0
- package/tools/session-live.sh +173 -0
- package/tools/session-server.js +441 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto-name session based on user's first prompt.
|
|
3
|
+
# Uses hookSpecificOutput.sessionTitle (Claude Code v2.1.94+).
|
|
4
|
+
#
|
|
5
|
+
# Strategy:
|
|
6
|
+
# 1st prompt: regex fallback immediately + launch LLM synthesis in background
|
|
7
|
+
# 2nd+ prompt: if LLM result ready, replace title with synthesized version
|
|
8
|
+
# Final flag set once synthesized title is applied.
|
|
9
|
+
#
|
|
10
|
+
# Requires: jq, claude CLI in PATH.
|
|
11
|
+
# Guard env var: CLAUDE_HOOK_RUNNING (prevents recursion when background
|
|
12
|
+
# `claude -p` re-enters UserPromptSubmit).
|
|
13
|
+
|
|
14
|
+
[ -n "$CLAUDE_HOOK_RUNNING" ] && exit 0
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
18
|
+
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
|
|
19
|
+
|
|
20
|
+
[ -z "$PROMPT" ] || [ -z "$SESSION_ID" ] && exit 0
|
|
21
|
+
|
|
22
|
+
STATE_DIR="$HOME/.claude/session-env"
|
|
23
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
24
|
+
|
|
25
|
+
FINAL_FLAG="$STATE_DIR/titled-${SESSION_ID}.flag"
|
|
26
|
+
LAUNCHED_FLAG="$STATE_DIR/title-launched-${SESSION_ID}.flag"
|
|
27
|
+
TITLE_FILE="$STATE_DIR/title-${SESSION_ID}.txt"
|
|
28
|
+
|
|
29
|
+
[ -f "$FINAL_FLAG" ] && exit 0
|
|
30
|
+
|
|
31
|
+
# Language of the synthesized title. Override via AXEL_TITLE_LANGUAGE env var.
|
|
32
|
+
# Default injected at install time; leave as placeholder when editing directly.
|
|
33
|
+
TITLE_LANGUAGE="${AXEL_TITLE_LANGUAGE:-{{ASSISTANT_LANGUAGE}}}"
|
|
34
|
+
case "$TITLE_LANGUAGE" in "{{"*"}}"|"") TITLE_LANGUAGE="english" ;; esac
|
|
35
|
+
|
|
36
|
+
# --- Helpers ---
|
|
37
|
+
|
|
38
|
+
emit_title() {
|
|
39
|
+
local t="$1"
|
|
40
|
+
t=$(printf '%s' "$t" | sed 's/"/\\"/g')
|
|
41
|
+
cat <<EOF
|
|
42
|
+
{
|
|
43
|
+
"hookSpecificOutput": {
|
|
44
|
+
"hookEventName": "UserPromptSubmit",
|
|
45
|
+
"sessionTitle": "$t"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
EOF
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
regex_title() {
|
|
52
|
+
local p="$1"
|
|
53
|
+
local CLEAN
|
|
54
|
+
CLEAN=$(printf '%s' "$p" | tr '\n' ' ' | sed 's/[[:space:]]\{2,\}/ /g' | sed 's/^[[:space:]]*//')
|
|
55
|
+
local ASSISTANT_RE='^(axel|claude|assistant|asistente|bot)[,.:;!? ]+'
|
|
56
|
+
CLEAN=$(printf '%s' "$CLEAN" | sed -E "s/$ASSISTANT_RE//I" | sed 's/^[[:space:]]*//')
|
|
57
|
+
local FILLER_RE='^(hola|hey|oye|dale|ok|bueno|bien|mira|vamos a|quiero que|necesito que|por favor|please|hi|hello|can you|could you|I need you to|I want you to)[,.:;!? ]*'
|
|
58
|
+
CLEAN=$(printf '%s' "$CLEAN" | sed -E "s/$FILLER_RE//I" | sed 's/^[[:space:]]*//' | sed -E "s/$FILLER_RE//I" | sed 's/^[[:space:]]*//')
|
|
59
|
+
local GREETING_Q_RE='^[¿?]*(cómo estás|como estas|qué tal|que tal|cómo va|como va|how are you|hows it going)[?!.,;: ]*'
|
|
60
|
+
CLEAN=$(printf '%s' "$CLEAN" | sed -E "s/$GREETING_Q_RE//I" | sed 's/^[[:space:]]*//')
|
|
61
|
+
CLEAN=$(printf '%s' "$CLEAN" | sed -E "s/$FILLER_RE//I" | sed 's/^[[:space:]]*//' | sed -E "s/$FILLER_RE//I" | sed 's/^[[:space:]]*//')
|
|
62
|
+
CLEAN=$(printf '%s' "$CLEAN" | sed -E 's/^\/[a-zA-Z0-9_:-]+[[:space:]]*//')
|
|
63
|
+
local FIRST
|
|
64
|
+
FIRST=$(printf '%s' "$CLEAN" | sed -E 's/[.?!].*//' | head -1)
|
|
65
|
+
if [ ${#FIRST} -gt 55 ]; then
|
|
66
|
+
local SHORT
|
|
67
|
+
SHORT=$(printf '%s' "$FIRST" | cut -c1-55 | sed -E 's/(.*[,;:—–-]).*/\1/' | sed 's/[,;:—–-]$//')
|
|
68
|
+
[ ${#SHORT} -gt 15 ] && FIRST="$SHORT"
|
|
69
|
+
fi
|
|
70
|
+
if [ ${#FIRST} -gt 55 ]; then
|
|
71
|
+
FIRST=$(printf '%s' "$FIRST" | cut -c1-55 | sed 's/[[:space:]][^[:space:]]*$//')
|
|
72
|
+
fi
|
|
73
|
+
local TITLE
|
|
74
|
+
TITLE=$(printf '%s' "$FIRST" | awk '{print toupper(substr($0,1,1)) substr($0,2)}' | sed 's/[[:space:]]*$//')
|
|
75
|
+
if [ ${#TITLE} -lt 5 ]; then
|
|
76
|
+
TITLE=$(printf '%s' "$CLEAN" | cut -c1-40 | sed 's/[[:space:]][^[:space:]]*$//')
|
|
77
|
+
fi
|
|
78
|
+
printf '%s' "$TITLE"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
launch_synthesis_bg() {
|
|
82
|
+
local prompt="$1"
|
|
83
|
+
local outfile="$2"
|
|
84
|
+
local lang="$3"
|
|
85
|
+
|
|
86
|
+
(
|
|
87
|
+
export CLAUDE_HOOK_RUNNING=1
|
|
88
|
+
local HOOK_TMP
|
|
89
|
+
HOOK_TMP=$(mktemp -d 2>/dev/null) || exit 0
|
|
90
|
+
local HOOK_TMP_REAL
|
|
91
|
+
HOOK_TMP_REAL=$(cd "$HOOK_TMP" 2>/dev/null && pwd -P)
|
|
92
|
+
|
|
93
|
+
local SYSTEM_PROMPT="You extract session titles for coding sessions. Read the user's first message and synthesize a brief title that summarizes the TOPIC of the conversation, not the requested action.
|
|
94
|
+
|
|
95
|
+
RULES:
|
|
96
|
+
- Max 55 characters
|
|
97
|
+
- Language: ${lang}
|
|
98
|
+
- No emojis, no quotes, no trailing period
|
|
99
|
+
- Summarize the concrete TOPIC (product, feature, file, bug), not the leading verb
|
|
100
|
+
- Plain text, single line
|
|
101
|
+
|
|
102
|
+
Respond with ONLY the title, nothing else."
|
|
103
|
+
|
|
104
|
+
local SYNTHESIS_PROMPT="User's first message:
|
|
105
|
+
|
|
106
|
+
${prompt:0:1500}"
|
|
107
|
+
|
|
108
|
+
local TITLE
|
|
109
|
+
TITLE=$( \
|
|
110
|
+
cd "$HOOK_TMP" 2>/dev/null && \
|
|
111
|
+
printf '%s' "$SYNTHESIS_PROMPT" | \
|
|
112
|
+
timeout 45 claude -p \
|
|
113
|
+
--model sonnet \
|
|
114
|
+
--append-system-prompt "$SYSTEM_PROMPT" \
|
|
115
|
+
2>/dev/null \
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Ghost JSONL cleanup: claude -p writes under ~/.claude/projects/<slug>
|
|
119
|
+
# even with --no-session-persistence (bug in v2.1.x).
|
|
120
|
+
if [ -n "$HOOK_TMP_REAL" ]; then
|
|
121
|
+
local GHOST_SLUG
|
|
122
|
+
GHOST_SLUG=$(printf '%s' "$HOOK_TMP_REAL" | sed 's|[^a-zA-Z0-9]|-|g')
|
|
123
|
+
rm -rf "$HOOK_TMP" "$HOME/.claude/projects/${GHOST_SLUG}" 2>/dev/null
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
TITLE=$(printf '%s' "$TITLE" | head -1 | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | sed 's/^["'"'"']//' | sed 's/["'"'"']$//')
|
|
127
|
+
TITLE="${TITLE:0:55}"
|
|
128
|
+
|
|
129
|
+
if [ -n "$TITLE" ] && [ ${#TITLE} -ge 5 ]; then
|
|
130
|
+
printf '%s\n' "$TITLE" > "$outfile"
|
|
131
|
+
fi
|
|
132
|
+
) &
|
|
133
|
+
|
|
134
|
+
disown
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
cleanup_session_state() {
|
|
138
|
+
rm -f "$TITLE_FILE" "$LAUNCHED_FLAG" 2>/dev/null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# --- Main logic ---
|
|
142
|
+
|
|
143
|
+
# Case 1: synthesized title ready -> apply + mark final + cleanup
|
|
144
|
+
if [ -f "$TITLE_FILE" ]; then
|
|
145
|
+
SYNTH_TITLE=$(head -1 "$TITLE_FILE" 2>/dev/null)
|
|
146
|
+
if [ -n "$SYNTH_TITLE" ] && [ ${#SYNTH_TITLE} -ge 5 ]; then
|
|
147
|
+
touch "$FINAL_FLAG"
|
|
148
|
+
cleanup_session_state
|
|
149
|
+
emit_title "$SYNTH_TITLE"
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# Case 2: first prompt -> regex fallback + launch bg synthesis
|
|
155
|
+
if [ ! -f "$LAUNCHED_FLAG" ]; then
|
|
156
|
+
touch "$LAUNCHED_FLAG"
|
|
157
|
+
TITLE=$(regex_title "$PROMPT")
|
|
158
|
+
[ -z "$TITLE" ] && exit 0
|
|
159
|
+
launch_synthesis_bg "$PROMPT" "$TITLE_FILE" "$TITLE_LANGUAGE"
|
|
160
|
+
emit_title "$TITLE"
|
|
161
|
+
exit 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Case 3: synthesis launched but not ready yet -> exit silently (regex title stays)
|
|
165
|
+
exit 0
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Periodic session checkpoint: every ~40 tool calls, summarize what's happened
|
|
3
|
+
# since the last checkpoint. Uses Sonnet for quality. Runs async — non-blocking.
|
|
4
|
+
#
|
|
5
|
+
# Ghost-session cleanup: Claude Code v2.1.x has a bug where
|
|
6
|
+
# --no-session-persistence does NOT prevent the JSONL from being written to
|
|
7
|
+
# ~/.claude/projects/. The workaround is to run the subprocess from a temporary
|
|
8
|
+
# cwd (so its JSONL lands in a bucket unique to this hook) and rm -rf both the
|
|
9
|
+
# tmp cwd and its projects/ bucket when done. This leaves zero residue in the
|
|
10
|
+
# /resume history while keeping checkpoint output intact in ~/.claude/sessions/.
|
|
11
|
+
|
|
12
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
13
|
+
COUNTER_FILE="/tmp/claude-checkpoint-counter-${PROJECT_NAME}"
|
|
14
|
+
CHECKPOINT_DIR="$HOME/.claude/sessions/checkpoints"
|
|
15
|
+
LAST_CHECKPOINT="/tmp/claude-last-checkpoint-${PROJECT_NAME}"
|
|
16
|
+
SESSION_LOG="/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
17
|
+
|
|
18
|
+
mkdir -p "$CHECKPOINT_DIR"
|
|
19
|
+
|
|
20
|
+
# Increment counter
|
|
21
|
+
COUNT=0
|
|
22
|
+
if [ -f "$COUNTER_FILE" ]; then
|
|
23
|
+
COUNT=$(cat "$COUNTER_FILE")
|
|
24
|
+
fi
|
|
25
|
+
COUNT=$((COUNT + 1))
|
|
26
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
27
|
+
|
|
28
|
+
# Checkpoint every 40 tool calls
|
|
29
|
+
if [ $((COUNT % 40)) -ne 0 ]; then
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
CHECKPOINT_NUM=$((COUNT / 40))
|
|
34
|
+
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
|
|
35
|
+
|
|
36
|
+
# Gather recent actions from the session log (since last checkpoint)
|
|
37
|
+
LAST_LINE=0
|
|
38
|
+
if [ -f "$LAST_CHECKPOINT" ]; then
|
|
39
|
+
LAST_LINE=$(cat "$LAST_CHECKPOINT")
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
RECENT_ACTIONS=""
|
|
43
|
+
if [ -f "$SESSION_LOG" ]; then
|
|
44
|
+
TOTAL_LINES=$(wc -l < "$SESSION_LOG")
|
|
45
|
+
if [ "$TOTAL_LINES" -gt "$LAST_LINE" ]; then
|
|
46
|
+
RECENT_ACTIONS=$(tail -n +"$((LAST_LINE + 1))" "$SESSION_LOG" | head -c 8000)
|
|
47
|
+
fi
|
|
48
|
+
echo "$TOTAL_LINES" > "$LAST_CHECKPOINT"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Git state since last checkpoint
|
|
52
|
+
GIT_DIFF=""
|
|
53
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
54
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
55
|
+
GIT_DIFF="Branch: $BRANCH | Recent: $(git log --oneline -5 2>/dev/null | tr '\n' ' ') | Modified: $(git diff --name-only 2>/dev/null | head -15 | tr '\n' ', ')"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Skip if nothing meaningful happened
|
|
59
|
+
if [ -z "$RECENT_ACTIONS" ] && [ -z "$GIT_DIFF" ]; then
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Summarize this chunk with Sonnet in background (cwd-isolated — see header comment)
|
|
64
|
+
{
|
|
65
|
+
HOOK_TMP=$(mktemp -d 2>/dev/null)
|
|
66
|
+
HOOK_TMP_REAL=$(cd "$HOOK_TMP" 2>/dev/null && pwd -P)
|
|
67
|
+
SUMMARY=$(cd "$HOOK_TMP" 2>/dev/null && printf "Resume este checkpoint de trabajo (#%s) en español. Máximo 15 líneas. Sé conciso pero completo.\n\nIncluye: qué se hizo, decisiones tomadas, archivos clave, contexto importante.\n\nAcciones recientes:\n%s\n\nGit:\n%s" "$CHECKPOINT_NUM" "$RECENT_ACTIONS" "$GIT_DIFF" | claude -p --model sonnet 2>/dev/null)
|
|
68
|
+
|
|
69
|
+
# Cleanup the ghost session JSONL bucket that claude -p created.
|
|
70
|
+
# Claude slugifies the cwd by replacing any non-alphanumeric char with '-',
|
|
71
|
+
# so use pwd -P (resolved physical path) and the same regex to reconstruct it.
|
|
72
|
+
if [ -n "$HOOK_TMP_REAL" ]; then
|
|
73
|
+
GHOST_SLUG=$(echo "$HOOK_TMP_REAL" | sed 's|[^a-zA-Z0-9]|-|g')
|
|
74
|
+
rm -rf "$HOOK_TMP" "$HOME/.claude/projects/${GHOST_SLUG}" 2>/dev/null
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ -n "$SUMMARY" ]; then
|
|
78
|
+
CHECKPOINT_FILE="$CHECKPOINT_DIR/${PROJECT_NAME}_${TIMESTAMP}_cp${CHECKPOINT_NUM}.md"
|
|
79
|
+
cat > "$CHECKPOINT_FILE" << ENDOFFILE
|
|
80
|
+
---
|
|
81
|
+
project: $PROJECT_NAME
|
|
82
|
+
checkpoint: $CHECKPOINT_NUM
|
|
83
|
+
tool_calls: $COUNT
|
|
84
|
+
date: $(date +%Y-%m-%d)
|
|
85
|
+
time: $(date +%H:%M)
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
$SUMMARY
|
|
89
|
+
ENDOFFILE
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Cleanup: keep last 50 checkpoints per project
|
|
93
|
+
ls -t "$CHECKPOINT_DIR"/${PROJECT_NAME}_*.md 2>/dev/null | tail -n +51 | xargs rm -f 2>/dev/null
|
|
94
|
+
|
|
95
|
+
} &
|
|
96
|
+
|
|
97
|
+
exit 0
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# session-cost-log.sh
|
|
3
|
+
# Runs from Stop hook (fires after EVERY response, not just session close).
|
|
4
|
+
# Uses UPSERT by session_id — one row per session, always updated to latest stats.
|
|
5
|
+
# CSV columns:
|
|
6
|
+
# date,time,session_id,project,cost_usd,input_tokens,output_tokens,ctx_used_pct,five_h_end_pct,five_h_session_pct,model
|
|
7
|
+
|
|
8
|
+
INPUT=$(cat)
|
|
9
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
10
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
|
11
|
+
LOG_FILE="$HOME/.claude/session-costs.log"
|
|
12
|
+
STATS_FILE="$HOME/.claude/session-stats-${SESSION_ID}.json"
|
|
13
|
+
START_FILE="$HOME/.claude/session-stats-${SESSION_ID}-start.json"
|
|
14
|
+
|
|
15
|
+
[ -z "$SESSION_ID" ] && exit 0
|
|
16
|
+
|
|
17
|
+
# Bootstrap log file with header if it doesn't exist
|
|
18
|
+
if [ ! -f "$LOG_FILE" ]; then
|
|
19
|
+
echo "date,time,session_id,project,cost_usd,input_tokens,output_tokens,ctx_used_pct,five_h_end_pct,five_h_session_pct,model" > "$LOG_FILE"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Read persisted stats (written by statusline on each update)
|
|
23
|
+
[ ! -f "$STATS_FILE" ] && exit 0
|
|
24
|
+
STATS=$(cat "$STATS_FILE")
|
|
25
|
+
|
|
26
|
+
# Skip if cost is zero (no real work done)
|
|
27
|
+
COST=$(echo "$STATS" | jq -r '.cost_usd // 0')
|
|
28
|
+
if [ "$COST" = "0" ] || [ "$COST" = "0.0" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Extract fields
|
|
33
|
+
SHORT_ID="${SESSION_ID:0:8}"
|
|
34
|
+
PROJECT=$(basename "${CWD:-$(pwd)}")
|
|
35
|
+
INPUT_TOK=$(echo "$STATS" | jq -r '.total_input_tokens // 0')
|
|
36
|
+
OUTPUT_TOK=$(echo "$STATS" | jq -r '.total_output_tokens // 0')
|
|
37
|
+
CTX_PCT=$(echo "$STATS" | jq -r '.ctx_used_pct // 0' | xargs printf "%.1f")
|
|
38
|
+
FIVE_H_END=$(echo "$STATS" | jq -r '.five_h_pct // 0')
|
|
39
|
+
MODEL=$(echo "$STATS" | jq -r '.model // ""' | sed 's/Claude //')
|
|
40
|
+
DATE=$(date +%Y-%m-%d)
|
|
41
|
+
TIME=$(date +%H:%M)
|
|
42
|
+
|
|
43
|
+
# Calculate per-session 5h% delta using the PRESERVED start file
|
|
44
|
+
# (start file is NOT deleted here — it lives for the whole session)
|
|
45
|
+
FIVE_H_START=0
|
|
46
|
+
if [ -f "$START_FILE" ]; then
|
|
47
|
+
FIVE_H_START=$(jq -r '.five_h_pct_start // 0' "$START_FILE" 2>/dev/null || echo 0)
|
|
48
|
+
fi
|
|
49
|
+
FIVE_H_DELTA=$(awk "BEGIN {
|
|
50
|
+
d = $FIVE_H_END - $FIVE_H_START
|
|
51
|
+
if (d < 0) d = 0
|
|
52
|
+
printf \"%.1f\", d
|
|
53
|
+
}" 2>/dev/null)
|
|
54
|
+
|
|
55
|
+
FIVE_H_END_FMT=$(printf "%.1f" "$FIVE_H_END")
|
|
56
|
+
NEW_LINE="${DATE},${TIME},${SHORT_ID},${PROJECT},${COST},${INPUT_TOK},${OUTPUT_TOK},${CTX_PCT},${FIVE_H_END_FMT},${FIVE_H_DELTA},${MODEL}"
|
|
57
|
+
|
|
58
|
+
# UPSERT: replace existing row for this session_id, or append if new
|
|
59
|
+
if grep -q "^[^,]*,[^,]*,${SHORT_ID}," "$LOG_FILE" 2>/dev/null; then
|
|
60
|
+
# Session already has a row — update it in place
|
|
61
|
+
awk -F',' -v sid="$SHORT_ID" -v newline="$NEW_LINE" '
|
|
62
|
+
NR == 1 { print; next } # keep header
|
|
63
|
+
$3 == sid { print newline; next } # replace matching session row
|
|
64
|
+
{ print }
|
|
65
|
+
' "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
|
66
|
+
else
|
|
67
|
+
# New session — append
|
|
68
|
+
echo "$NEW_LINE" >> "$LOG_FILE"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Clean up stale start files from sessions older than 12 hours
|
|
72
|
+
# (keeps start file alive during the session, removes after enough time)
|
|
73
|
+
find "$HOME/.claude" -maxdepth 1 -name "session-stats-*-start.json" -mmin +720 -delete 2>/dev/null
|
|
74
|
+
# Also clean up stale stats files (session ended long ago)
|
|
75
|
+
find "$HOME/.claude" -maxdepth 1 -name "session-stats-*.json" ! -name "*-start.json" -mmin +720 -delete 2>/dev/null
|
|
76
|
+
|
|
77
|
+
exit 0
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Log significant tool actions during session for context persistence
|
|
3
|
+
# Captures: file edits, bash commands, agent launches — the "what was done"
|
|
4
|
+
|
|
5
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
6
|
+
SESSION_LOG="/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
7
|
+
TIMESTAMP=$(date +%H:%M)
|
|
8
|
+
|
|
9
|
+
TOOL_NAME_VAR="${TOOL_NAME:-unknown}"
|
|
10
|
+
|
|
11
|
+
# Parse tool input for meaningful context
|
|
12
|
+
case "$TOOL_NAME_VAR" in
|
|
13
|
+
Edit|Write)
|
|
14
|
+
FILE_PATH=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('file_path',''))" 2>/dev/null)
|
|
15
|
+
if [ -n "$FILE_PATH" ]; then
|
|
16
|
+
echo "- [$TIMESTAMP] **$TOOL_NAME_VAR**: \`$(basename "$FILE_PATH")\`" >> "$SESSION_LOG"
|
|
17
|
+
fi
|
|
18
|
+
;;
|
|
19
|
+
Bash)
|
|
20
|
+
CMD=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('command','')[:120])" 2>/dev/null)
|
|
21
|
+
if [ -n "$CMD" ]; then
|
|
22
|
+
echo "- [$TIMESTAMP] **Bash**: \`$CMD\`" >> "$SESSION_LOG"
|
|
23
|
+
fi
|
|
24
|
+
;;
|
|
25
|
+
Agent)
|
|
26
|
+
DESC=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('description',''))" 2>/dev/null)
|
|
27
|
+
if [ -n "$DESC" ]; then
|
|
28
|
+
echo "- [$TIMESTAMP] **Agent**: $DESC" >> "$SESSION_LOG"
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
*)
|
|
32
|
+
# Skip less important tools (Read, Glob, Grep, etc.) to keep log concise
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
exit 0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Log each user prompt during the session for context persistence
|
|
3
|
+
# Appends to a temp file that session-save.sh reads at the end
|
|
4
|
+
|
|
5
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
6
|
+
SESSION_LOG="/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
7
|
+
|
|
8
|
+
# Read user prompt from stdin (Claude Code passes it as JSON via $PROMPT)
|
|
9
|
+
USER_TEXT="$PROMPT"
|
|
10
|
+
|
|
11
|
+
if [ -z "$USER_TEXT" ]; then
|
|
12
|
+
# Try reading from hook input
|
|
13
|
+
USER_TEXT=$(echo "$TOOL_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('prompt',''))" 2>/dev/null)
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ -n "$USER_TEXT" ]; then
|
|
17
|
+
TIMESTAMP=$(date +%H:%M)
|
|
18
|
+
# Truncate long prompts to keep log manageable
|
|
19
|
+
TRUNCATED=$(echo "$USER_TEXT" | head -c 500)
|
|
20
|
+
echo "### [$TIMESTAMP] Usuario" >> "$SESSION_LOG"
|
|
21
|
+
echo "$TRUNCATED" >> "$SESSION_LOG"
|
|
22
|
+
echo "" >> "$SESSION_LOG"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
exit 0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Restore recent session context on SessionStart
|
|
3
|
+
# Shows what was discussed, what was done, and current git state
|
|
4
|
+
|
|
5
|
+
SESSION_DIR="$HOME/.claude/sessions"
|
|
6
|
+
PROJECT_DIR=$(pwd)
|
|
7
|
+
PROJECT_NAME=$(basename "$PROJECT_DIR")
|
|
8
|
+
|
|
9
|
+
# Find the 3 most recent sessions for this project (for broader context)
|
|
10
|
+
SESSIONS=$(ls -t "$SESSION_DIR"/${PROJECT_NAME}_*.md 2>/dev/null | head -3)
|
|
11
|
+
|
|
12
|
+
if [ -z "$SESSIONS" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
LATEST=$(echo "$SESSIONS" | head -1)
|
|
17
|
+
SESSION_DATE=$(grep "^date:" "$LATEST" 2>/dev/null | cut -d' ' -f2)
|
|
18
|
+
SESSION_TIME=$(grep "^time:" "$LATEST" 2>/dev/null | cut -d' ' -f2)
|
|
19
|
+
|
|
20
|
+
echo "=== Contexto de sesiones anteriores ($PROJECT_NAME) ==="
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
# Show the latest session in full
|
|
24
|
+
echo "### Última sesión: $SESSION_DATE $SESSION_TIME"
|
|
25
|
+
# Skip frontmatter, show content
|
|
26
|
+
sed -n '/^---$/,/^---$/!p' "$LATEST" | head -80
|
|
27
|
+
echo ""
|
|
28
|
+
|
|
29
|
+
# Show previous sessions as one-liners
|
|
30
|
+
OTHERS=$(echo "$SESSIONS" | tail -n +2)
|
|
31
|
+
if [ -n "$OTHERS" ]; then
|
|
32
|
+
echo "### Sesiones anteriores:"
|
|
33
|
+
for f in $OTHERS; do
|
|
34
|
+
DATE=$(grep "^date:" "$f" 2>/dev/null | cut -d' ' -f2)
|
|
35
|
+
TIME=$(grep "^time:" "$f" 2>/dev/null | cut -d' ' -f2)
|
|
36
|
+
# Extract first user prompt as summary
|
|
37
|
+
SUMMARY=$(grep -A1 "Usuario" "$f" 2>/dev/null | grep -v "Usuario" | grep -v "^--$" | head -1 | head -c 100)
|
|
38
|
+
echo "- **$DATE $TIME**: $SUMMARY"
|
|
39
|
+
done
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo "=== Fin contexto previo ==="
|
|
44
|
+
|
|
45
|
+
exit 0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Compile full session context on Stop for persistence between sessions
|
|
3
|
+
# Combines: user prompts + actions taken + git state → structured session file
|
|
4
|
+
|
|
5
|
+
SESSION_DIR="$HOME/.claude/sessions"
|
|
6
|
+
mkdir -p "$SESSION_DIR"
|
|
7
|
+
|
|
8
|
+
PROJECT_DIR=$(pwd)
|
|
9
|
+
PROJECT_NAME=$(basename "$PROJECT_DIR")
|
|
10
|
+
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
|
|
11
|
+
SESSION_FILE="$SESSION_DIR/${PROJECT_NAME}_${TIMESTAMP}.md"
|
|
12
|
+
SESSION_LOG="/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
13
|
+
|
|
14
|
+
# --- Git State ---
|
|
15
|
+
GIT_INFO=""
|
|
16
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
17
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
18
|
+
LAST_COMMITS=$(git log --oneline -5 2>/dev/null)
|
|
19
|
+
MODIFIED=$(git diff --name-only 2>/dev/null | head -20)
|
|
20
|
+
STAGED=$(git diff --cached --name-only 2>/dev/null | head -20)
|
|
21
|
+
GIT_INFO="## Estado Git
|
|
22
|
+
- **Branch:** $BRANCH
|
|
23
|
+
|
|
24
|
+
**Últimos commits:**
|
|
25
|
+
\`\`\`
|
|
26
|
+
$LAST_COMMITS
|
|
27
|
+
\`\`\`"
|
|
28
|
+
|
|
29
|
+
if [ -n "$MODIFIED" ]; then
|
|
30
|
+
GIT_INFO="$GIT_INFO
|
|
31
|
+
|
|
32
|
+
**Archivos modificados (sin commit):**
|
|
33
|
+
\`\`\`
|
|
34
|
+
$MODIFIED
|
|
35
|
+
\`\`\`"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [ -n "$STAGED" ]; then
|
|
39
|
+
GIT_INFO="$GIT_INFO
|
|
40
|
+
|
|
41
|
+
**Archivos staged:**
|
|
42
|
+
\`\`\`
|
|
43
|
+
$STAGED
|
|
44
|
+
\`\`\`"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# --- Conversation Log ---
|
|
49
|
+
CONVERSATION=""
|
|
50
|
+
if [ -f "$SESSION_LOG" ]; then
|
|
51
|
+
CONVERSATION="## Conversación y Acciones
|
|
52
|
+
|
|
53
|
+
$(cat "$SESSION_LOG")"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# --- Write session file ---
|
|
57
|
+
cat > "$SESSION_FILE" << ENDOFFILE
|
|
58
|
+
---
|
|
59
|
+
project: $PROJECT_NAME
|
|
60
|
+
directory: $PROJECT_DIR
|
|
61
|
+
date: $(date +%Y-%m-%d)
|
|
62
|
+
time: $(date +%H:%M)
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Sesión: $PROJECT_NAME — $(date +%Y-%m-%d) $(date +%H:%M)
|
|
66
|
+
|
|
67
|
+
$CONVERSATION
|
|
68
|
+
|
|
69
|
+
$GIT_INFO
|
|
70
|
+
|
|
71
|
+
## Directorio
|
|
72
|
+
$PROJECT_DIR
|
|
73
|
+
ENDOFFILE
|
|
74
|
+
|
|
75
|
+
# Cleanup: keep last 20 session files per project
|
|
76
|
+
ls -t "$SESSION_DIR"/${PROJECT_NAME}_*.md 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
|
|
77
|
+
|
|
78
|
+
# Clear the temp log for next session
|
|
79
|
+
rm -f "$SESSION_LOG"
|
|
80
|
+
|
|
81
|
+
exit 0
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Final session summarizer: compiles checkpoints + conversation into
|
|
3
|
+
# one coherent session summary. Uses Sonnet for quality. Runs async from Stop hook.
|
|
4
|
+
#
|
|
5
|
+
# Ghost-session cleanup: Claude Code v2.1.x has a bug where
|
|
6
|
+
# --no-session-persistence does NOT prevent the JSONL from being written to
|
|
7
|
+
# ~/.claude/projects/. The workaround is to run the subprocess from a temporary
|
|
8
|
+
# cwd (so its JSONL lands in a bucket unique to this hook) and rm -rf both the
|
|
9
|
+
# tmp cwd and its projects/ bucket when done. The summary output still lands in
|
|
10
|
+
# ~/.claude/sessions/, which is what session-restore reads on the next startup.
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
|
|
15
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
16
|
+
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
17
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
|
18
|
+
|
|
19
|
+
PROJECT_NAME=$(basename "${CWD:-$(pwd)}")
|
|
20
|
+
SESSION_DIR="$HOME/.claude/sessions"
|
|
21
|
+
CHECKPOINT_DIR="$SESSION_DIR/checkpoints"
|
|
22
|
+
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
|
|
23
|
+
SESSION_FILE="$SESSION_DIR/${PROJECT_NAME}_${TIMESTAMP}.md"
|
|
24
|
+
|
|
25
|
+
# Bootstrap substitutes these placeholders at install time via --user-name,
|
|
26
|
+
# --user-context and --language flags. The `case` fallback kicks in when the
|
|
27
|
+
# script runs without bootstrap: sed never replaced the token, so it still
|
|
28
|
+
# starts with `{{` and ends with `}}`. (We cannot compare against the literal
|
|
29
|
+
# token here — bootstrap's sed would rewrite both sides of the comparison.)
|
|
30
|
+
USER_NAME="{{USER_NAME}}"
|
|
31
|
+
case "$USER_NAME" in "{{"*"}}"|"") USER_NAME=$(whoami) ;; esac
|
|
32
|
+
USER_CONTEXT="{{USER_CONTEXT}}"
|
|
33
|
+
case "$USER_CONTEXT" in "{{"*"}}"|"") USER_CONTEXT="a software engineer" ;; esac
|
|
34
|
+
ASSISTANT_LANGUAGE="{{ASSISTANT_LANGUAGE}}"
|
|
35
|
+
case "$ASSISTANT_LANGUAGE" in "{{"*"}}"|"") ASSISTANT_LANGUAGE="english" ;; esac
|
|
36
|
+
|
|
37
|
+
mkdir -p "$SESSION_DIR"
|
|
38
|
+
|
|
39
|
+
# 1. Gather checkpoint summaries from this session
|
|
40
|
+
CHECKPOINTS=""
|
|
41
|
+
TODAY=$(date +%Y-%m-%d)
|
|
42
|
+
if [ -d "$CHECKPOINT_DIR" ]; then
|
|
43
|
+
for f in $(ls -t "$CHECKPOINT_DIR"/${PROJECT_NAME}_${TODAY}*.md 2>/dev/null | head -10); do
|
|
44
|
+
CP_CONTENT=$(sed -n '/^---$/,/^---$/!p' "$f" 2>/dev/null)
|
|
45
|
+
if [ -n "$CP_CONTENT" ]; then
|
|
46
|
+
CHECKPOINTS="$CHECKPOINTS
|
|
47
|
+
--- Checkpoint ---
|
|
48
|
+
$CP_CONTENT
|
|
49
|
+
"
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# 2. Extract conversation from transcript
|
|
55
|
+
CONVERSATION=""
|
|
56
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
57
|
+
CONVERSATION=$(cat "$TRANSCRIPT" | \
|
|
58
|
+
jq -r 'select(.type == "human" or .type == "assistant") |
|
|
59
|
+
if .type == "human" then "USER: " + (.message // .content // "" | tostring)[:500]
|
|
60
|
+
elif .type == "assistant" then "CLAUDE: " + (.message // .content // "" | tostring)[:500]
|
|
61
|
+
else empty end' 2>/dev/null | tail -80 | head -c 30000)
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# 3. Git state
|
|
65
|
+
GIT_INFO=""
|
|
66
|
+
if cd "$CWD" 2>/dev/null && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
67
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
68
|
+
COMMITS=$(git log --oneline -10 2>/dev/null)
|
|
69
|
+
MODIFIED=$(git diff --name-only 2>/dev/null | head -20)
|
|
70
|
+
GIT_INFO="Branch: $BRANCH
|
|
71
|
+
Commits: $COMMITS
|
|
72
|
+
Modified: $MODIFIED"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Skip if nothing to summarize
|
|
76
|
+
if [ -z "$CHECKPOINTS" ] && [ -z "$CONVERSATION" ] && [ -z "$GIT_INFO" ]; then
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# 4. Compile final summary with Sonnet (cwd-isolated — see header comment)
|
|
81
|
+
{
|
|
82
|
+
HOOK_TMP=$(mktemp -d 2>/dev/null)
|
|
83
|
+
HOOK_TMP_REAL=$(cd "$HOOK_TMP" 2>/dev/null && pwd -P)
|
|
84
|
+
SUMMARY=$(cd "$HOOK_TMP" 2>/dev/null && printf "You are an assistant that summarizes programming work sessions for %s (%s). Compile a coherent final summary. Respond in %s.
|
|
85
|
+
|
|
86
|
+
You have 2 sources:
|
|
87
|
+
- Checkpoints (partial summaries made during the session)
|
|
88
|
+
- Recent conversation (user and assistant messages)
|
|
89
|
+
|
|
90
|
+
Generate a structured summary with:
|
|
91
|
+
1. **Session objective** — what the user wanted to accomplish
|
|
92
|
+
2. **What was done** — concrete actions, modified files, tools used
|
|
93
|
+
3. **Key decisions** — technical and architecture decisions, and why
|
|
94
|
+
4. **Status at close** — how things ended, what's left to do
|
|
95
|
+
5. **Important context** — anything the next session needs to know
|
|
96
|
+
6. **User feedback** — if the user corrected or confirmed an approach
|
|
97
|
+
|
|
98
|
+
Maximum 30 lines. No intro, straight to content. If checkpoints cover everything, synthesize and add what's missing from the conversation.
|
|
99
|
+
|
|
100
|
+
Checkpoints:
|
|
101
|
+
%s
|
|
102
|
+
|
|
103
|
+
Recent conversation:
|
|
104
|
+
%s
|
|
105
|
+
|
|
106
|
+
Git:
|
|
107
|
+
%s" "$USER_NAME" "$USER_CONTEXT" "$ASSISTANT_LANGUAGE" "$CHECKPOINTS" "$CONVERSATION" "$GIT_INFO" | claude -p --model sonnet 2>/dev/null)
|
|
108
|
+
|
|
109
|
+
# Cleanup the ghost session JSONL bucket that claude -p created.
|
|
110
|
+
# Claude slugifies the cwd by replacing any non-alphanumeric char with '-',
|
|
111
|
+
# so use pwd -P (resolved physical path) and the same regex to reconstruct it.
|
|
112
|
+
if [ -n "$HOOK_TMP_REAL" ]; then
|
|
113
|
+
GHOST_SLUG=$(echo "$HOOK_TMP_REAL" | sed 's|[^a-zA-Z0-9]|-|g')
|
|
114
|
+
rm -rf "$HOOK_TMP" "$HOME/.claude/projects/${GHOST_SLUG}" 2>/dev/null
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [ -z "$SUMMARY" ]; then
|
|
118
|
+
SUMMARY="## Checkpoints
|
|
119
|
+
$CHECKPOINTS
|
|
120
|
+
|
|
121
|
+
## Recent conversation
|
|
122
|
+
$(echo "$CONVERSATION" | head -c 5000)"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
cat > "$SESSION_FILE" << ENDOFFILE
|
|
126
|
+
---
|
|
127
|
+
project: $PROJECT_NAME
|
|
128
|
+
directory: $CWD
|
|
129
|
+
date: $(date +%Y-%m-%d)
|
|
130
|
+
time: $(date +%H:%M)
|
|
131
|
+
session_id: $SESSION_ID
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# Session: $PROJECT_NAME — $(date +%Y-%m-%d) $(date +%H:%M)
|
|
135
|
+
|
|
136
|
+
$SUMMARY
|
|
137
|
+
|
|
138
|
+
## Git
|
|
139
|
+
- **Branch:** $(cd "$CWD" 2>/dev/null && git branch --show-current 2>/dev/null || echo "N/A")
|
|
140
|
+
$(cd "$CWD" 2>/dev/null && git log --oneline -5 2>/dev/null | sed 's/^/- /' || echo "- N/A")
|
|
141
|
+
ENDOFFILE
|
|
142
|
+
|
|
143
|
+
# Cleanup sessions (keep 30) and old checkpoints
|
|
144
|
+
ls -t "$SESSION_DIR"/${PROJECT_NAME}_*.md 2>/dev/null | tail -n +31 | xargs rm -f 2>/dev/null
|
|
145
|
+
find "$CHECKPOINT_DIR" -name "${PROJECT_NAME}_*.md" -mtime +7 -delete 2>/dev/null
|
|
146
|
+
|
|
147
|
+
# Reset counters for next session
|
|
148
|
+
rm -f "/tmp/claude-checkpoint-counter-${PROJECT_NAME}"
|
|
149
|
+
rm -f "/tmp/claude-last-checkpoint-${PROJECT_NAME}"
|
|
150
|
+
rm -f "/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
151
|
+
|
|
152
|
+
} &
|
|
153
|
+
|
|
154
|
+
exit 0
|