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.
Files changed (117) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/LICENSE +21 -0
  4. package/README.md +518 -0
  5. package/agents/api-design.md +51 -0
  6. package/agents/bughunter.md +136 -0
  7. package/agents/changelog.md +89 -0
  8. package/agents/cleanup.md +126 -0
  9. package/agents/compare-branch.md +35 -0
  10. package/agents/cross-repo.md +97 -0
  11. package/agents/db-check.md +14 -0
  12. package/agents/debug.md +47 -0
  13. package/agents/deploy-check.md +100 -0
  14. package/agents/draft-message.md +19 -0
  15. package/agents/excelsior-coordinator.md +75 -0
  16. package/agents/excelsior-verifier.md +94 -0
  17. package/agents/feature.md +48 -0
  18. package/agents/harness-optimizer.md +40 -0
  19. package/agents/incident.md +48 -0
  20. package/agents/linear-task.md +18 -0
  21. package/agents/onboard.md +24 -0
  22. package/agents/perf.md +44 -0
  23. package/agents/production-validator.md +96 -0
  24. package/agents/review.md +113 -0
  25. package/agents/security-check.md +29 -0
  26. package/agents/sprint-summary.md +15 -0
  27. package/agents/tdd-mainder.md +178 -0
  28. package/agents/test-gen.md +39 -0
  29. package/axel-manifest.json +129 -0
  30. package/bin/axel-setup.js +597 -0
  31. package/bootstrap.sh +1087 -0
  32. package/commands/create-pr.md +13 -0
  33. package/commands/daily.md +182 -0
  34. package/commands/deslop.md +13 -0
  35. package/commands/draft-message.md +23 -0
  36. package/commands/eod-review.md +154 -0
  37. package/commands/execute-prp.md +37 -0
  38. package/commands/generate-prp.md +75 -0
  39. package/commands/multi-repo-feature.md +60 -0
  40. package/commands/roadmap.md +31 -0
  41. package/commands/sprint-status.md +486 -0
  42. package/commands/style.md +68 -0
  43. package/commands/visualize.md +17 -0
  44. package/docs/roadmap/multi-runtime.md +73 -0
  45. package/docs/superpowers/plans/2026-06-12-setup-hardening-roadmap.md +61 -0
  46. package/hooks/desktop-notify.sh +26 -0
  47. package/hooks/enforce-agent-model.jq +14 -0
  48. package/hooks/gsd-context-monitor.js +156 -0
  49. package/hooks/linear-lifecycle-sync.sh +112 -0
  50. package/hooks/memory-dedup.sh +122 -0
  51. package/hooks/memory-extractor.sh +218 -0
  52. package/hooks/post-commit-memory-trigger.sh +16 -0
  53. package/hooks/post-commit-verify.sh +41 -0
  54. package/hooks/post-edit-lint.sh +43 -0
  55. package/hooks/precompact-save-context.sh +124 -0
  56. package/hooks/priority-map-staleness.sh +29 -0
  57. package/hooks/proactive-resolver.sh +104 -0
  58. package/hooks/session-auto-title.sh +165 -0
  59. package/hooks/session-checkpoint.sh +97 -0
  60. package/hooks/session-cost-log.sh +77 -0
  61. package/hooks/session-log-action.sh +36 -0
  62. package/hooks/session-log-prompt.sh +25 -0
  63. package/hooks/session-restore.sh +45 -0
  64. package/hooks/session-save.sh +81 -0
  65. package/hooks/session-summarize.sh +154 -0
  66. package/hooks/validate-commit-format.sh +38 -0
  67. package/hooks/weekly-priority-map-review.sh +143 -0
  68. package/install.sh +46 -0
  69. package/package.json +67 -0
  70. package/scripts/ci/bootstrap-dry-run.sh +40 -0
  71. package/scripts/ci/check.sh +65 -0
  72. package/scripts/posthog-snapshot-loader.sh +112 -0
  73. package/skills/context-budget/SKILL.md +86 -0
  74. package/skills/memory-review/SKILL.md +100 -0
  75. package/skills/model-routing/SKILL.md +70 -0
  76. package/skills/posthog-weekly/SKILL.md +271 -0
  77. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  78. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  79. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  80. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  81. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  82. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  83. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  84. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  85. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  86. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  87. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  88. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  89. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  90. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  91. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  92. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  93. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  94. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  95. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  96. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  97. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  98. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  99. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  100. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  101. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  102. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  103. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  104. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
  105. package/templates/AGENTS.runtime.md +17 -0
  106. package/templates/CLAUDE.md +252 -0
  107. package/templates/claude-monitor.plist +35 -0
  108. package/templates/keybindings.json +13 -0
  109. package/templates/merge-settings.jq +53 -0
  110. package/templates/review-upgrades.md +44 -0
  111. package/templates/settings.json +255 -0
  112. package/templates/statusline-command.sh +182 -0
  113. package/tests/fixtures/hooks/events.json +32 -0
  114. package/tools/session-costs-view.sh +128 -0
  115. package/tools/session-dashboard-gen.sh +369 -0
  116. package/tools/session-live.sh +173 -0
  117. 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