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,218 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Extract key decisions and learnings from session and persist to memory.
|
|
3
|
+
# Runs async from Stop hook. Uses Sonnet for quality extraction.
|
|
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. Memory output still lands in
|
|
10
|
+
# ~/.claude/memory/, which is completely unaffected.
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
# Guard: prevent recursive invocation. This script spawns `claude -p`, which
|
|
15
|
+
# fires Stop hooks again when it finishes. Without this guard the hook loops
|
|
16
|
+
# infinitely (each ghost session triggering another extraction).
|
|
17
|
+
if [ -n "$CLAUDE_MEMORY_EXTRACTOR" ]; then
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Rate limit: debounce consecutive commits — skip if run within last 5 minutes.
|
|
22
|
+
LAST_RUN_FILE="$HOME/.claude/memory/.last_extraction"
|
|
23
|
+
if [ -f "$LAST_RUN_FILE" ]; then
|
|
24
|
+
LAST_TS=$(stat -f %m "$LAST_RUN_FILE" 2>/dev/null || stat -c %Y "$LAST_RUN_FILE" 2>/dev/null)
|
|
25
|
+
NOW_TS=$(date +%s)
|
|
26
|
+
if [ -n "$LAST_TS" ] && [ $((NOW_TS - LAST_TS)) -lt 300 ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
mkdir -p "$HOME/.claude/memory"
|
|
31
|
+
touch "$LAST_RUN_FILE"
|
|
32
|
+
|
|
33
|
+
# Bootstrap substitutes these placeholders at install time via --user-context
|
|
34
|
+
# and --language flags. The `case` fallback kicks in when the script runs
|
|
35
|
+
# without bootstrap: sed never replaced the token, so it still starts with
|
|
36
|
+
# `{{` and ends with `}}`. (We cannot compare against the literal token here
|
|
37
|
+
# — bootstrap's sed would rewrite both sides of the comparison at install.)
|
|
38
|
+
USER_CONTEXT="{{USER_CONTEXT}}"
|
|
39
|
+
case "$USER_CONTEXT" in "{{"*"}}"|"") USER_CONTEXT="a software engineer" ;; esac
|
|
40
|
+
ASSISTANT_LANGUAGE="{{ASSISTANT_LANGUAGE}}"
|
|
41
|
+
case "$ASSISTANT_LANGUAGE" in "{{"*"}}"|"") ASSISTANT_LANGUAGE="english" ;; esac
|
|
42
|
+
|
|
43
|
+
INPUT=$(cat)
|
|
44
|
+
|
|
45
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
|
|
46
|
+
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
47
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
|
48
|
+
|
|
49
|
+
MEMORY_DIR="$HOME/.claude/memory"
|
|
50
|
+
DECISIONS_DIR="$MEMORY_DIR/decisions"
|
|
51
|
+
TIMESTAMP=$(date +%Y-%m-%d)
|
|
52
|
+
|
|
53
|
+
mkdir -p "$DECISIONS_DIR"
|
|
54
|
+
|
|
55
|
+
# Extract conversation from transcript if available
|
|
56
|
+
CONVERSATION=""
|
|
57
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
58
|
+
CONVERSATION=$(cat "$TRANSCRIPT" | \
|
|
59
|
+
jq -r 'select(.type == "human" or .type == "assistant") |
|
|
60
|
+
if .type == "human" then "USER: " + (.message // .content // "" | tostring)[:500]
|
|
61
|
+
elif .type == "assistant" then "CLAUDE: " + (.message // .content // "" | tostring)[:500]
|
|
62
|
+
else empty end' 2>/dev/null | tail -60 | head -c 25000)
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Fallback: use checkpoints + latest session summary
|
|
66
|
+
if [ -z "$CONVERSATION" ] || [ ${#CONVERSATION} -lt 200 ]; then
|
|
67
|
+
PROJECT_NAME=$(basename "${CWD:-$(pwd)}")
|
|
68
|
+
SESSION_DIR="$HOME/.claude/sessions"
|
|
69
|
+
CHECKPOINT_DIR="$SESSION_DIR/checkpoints"
|
|
70
|
+
TODAY=$(date +%Y-%m-%d)
|
|
71
|
+
|
|
72
|
+
CHECKPOINTS=""
|
|
73
|
+
if [ -d "$CHECKPOINT_DIR" ]; then
|
|
74
|
+
for f in $(ls -t "$CHECKPOINT_DIR"/${PROJECT_NAME}_${TODAY}*.md 2>/dev/null | head -10); do
|
|
75
|
+
CP_CONTENT=$(sed -n '/^---$/,/^---$/!p' "$f" 2>/dev/null)
|
|
76
|
+
if [ -n "$CP_CONTENT" ]; then
|
|
77
|
+
CHECKPOINTS="$CHECKPOINTS
|
|
78
|
+
$CP_CONTENT
|
|
79
|
+
---
|
|
80
|
+
"
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
LATEST_SUMMARY=""
|
|
86
|
+
LATEST_FILE=$(ls -t "$SESSION_DIR"/${PROJECT_NAME}_${TODAY}*.md 2>/dev/null | head -1)
|
|
87
|
+
if [ -n "$LATEST_FILE" ] && [ -f "$LATEST_FILE" ]; then
|
|
88
|
+
LATEST_SUMMARY=$(cat "$LATEST_FILE" | head -c 10000)
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
CONVERSATION="$CHECKPOINTS
|
|
92
|
+
$LATEST_SUMMARY"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Skip if no meaningful content
|
|
96
|
+
if [ -z "$CONVERSATION" ] || [ ${#CONVERSATION} -lt 200 ]; then
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Read current MEMORY.md for dedup context
|
|
101
|
+
CURRENT_MEMORY=""
|
|
102
|
+
if [ -f "$MEMORY_DIR/MEMORY.md" ]; then
|
|
103
|
+
CURRENT_MEMORY=$(cat "$MEMORY_DIR/MEMORY.md")
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Use Sonnet to extract decisions worth remembering
|
|
107
|
+
{
|
|
108
|
+
PROMPT=$(printf 'You are an intelligent memory extractor for a programming assistant. Analyze the session conversation and determine if there is valuable information that should persist in memory for future sessions. Respond in %s.
|
|
109
|
+
|
|
110
|
+
The user is %s.
|
|
111
|
+
|
|
112
|
+
MEMORY TYPES:
|
|
113
|
+
- user: info about the user (role, preferences, skills, personal context)
|
|
114
|
+
- feedback: corrections or confirmations about how to work (dos and donts)
|
|
115
|
+
- project: technical decisions, architecture changes, project state, team info
|
|
116
|
+
- reference: where to find info in external systems (URLs, IDs, channels)
|
|
117
|
+
|
|
118
|
+
STRICT RULES:
|
|
119
|
+
- Only extract NON-OBVIOUS information that cannot be derived by reading the code.
|
|
120
|
+
- DEDUPLICATION: walk the full MEMORY.md index before creating anything. If a topic is already covered by an existing memory — even under a different name, or as part of a consolidated entry — do NOT create a new one. What matters is whether the INFORMATION is already captured, not whether the filename matches.
|
|
121
|
+
- For feedback and project types, include **Why:** and **How to apply:** lines.
|
|
122
|
+
- If nothing significant to persist, respond EXACTLY: NOTHING
|
|
123
|
+
- If something exists, respond in JSON array format (max 2 entries per session).
|
|
124
|
+
- Filenames must be descriptive: type_topic.md (e.g.: project_people_finder_v2.md).
|
|
125
|
+
- Keep index_line descriptions under 60 characters.
|
|
126
|
+
|
|
127
|
+
JSON format when there is something:
|
|
128
|
+
[{"filename": "type_topic.md", "name": "Descriptive name", "description": "One line for MEMORY.md index", "type": "user|feedback|project|reference", "content": "Full memory content", "index_line": "- [Name](type_topic.md) — short description"}]
|
|
129
|
+
|
|
130
|
+
Current memory (to avoid duplicates):
|
|
131
|
+
%s
|
|
132
|
+
|
|
133
|
+
Session conversation:
|
|
134
|
+
%s' "$ASSISTANT_LANGUAGE" "$USER_CONTEXT" "$CURRENT_MEMORY" "$CONVERSATION")
|
|
135
|
+
|
|
136
|
+
HOOK_TMP=$(mktemp -d 2>/dev/null)
|
|
137
|
+
HOOK_TMP_REAL=$(cd "$HOOK_TMP" 2>/dev/null && pwd -P)
|
|
138
|
+
EXTRACTION=$(cd "$HOOK_TMP" 2>/dev/null && printf '%s' "$PROMPT" | CLAUDE_MEMORY_EXTRACTOR=1 claude -p --model sonnet 2>/dev/null)
|
|
139
|
+
|
|
140
|
+
# Cleanup the ghost session JSONL bucket that claude -p created.
|
|
141
|
+
# Claude slugifies the cwd by replacing any non-alphanumeric char with '-',
|
|
142
|
+
# so use pwd -P (resolved physical path) and the same regex to reconstruct it.
|
|
143
|
+
if [ -n "$HOOK_TMP_REAL" ]; then
|
|
144
|
+
GHOST_SLUG=$(echo "$HOOK_TMP_REAL" | sed 's|[^a-zA-Z0-9]|-|g')
|
|
145
|
+
rm -rf "$HOOK_TMP" "$HOME/.claude/projects/${GHOST_SLUG}" 2>/dev/null
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [ -z "$EXTRACTION" ] || echo "$EXTRACTION" | grep -qi "NOTHING"; then
|
|
149
|
+
exit 0
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Extract JSON from response
|
|
153
|
+
JSON_EXTRACTION=$(echo "$EXTRACTION" | sed -n '/^\[/,/^\]/p' | head -c 10000)
|
|
154
|
+
if [ -z "$JSON_EXTRACTION" ]; then
|
|
155
|
+
JSON_EXTRACTION=$(echo "$EXTRACTION" | sed -n '/```json/,/```/p' | sed '1d;$d' | head -c 10000)
|
|
156
|
+
fi
|
|
157
|
+
if [ -z "$JSON_EXTRACTION" ]; then
|
|
158
|
+
JSON_EXTRACTION="$EXTRACTION"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Validate JSON
|
|
162
|
+
if ! echo "$JSON_EXTRACTION" | jq '.' >/dev/null 2>&1; then
|
|
163
|
+
exit 0
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Process each memory entry
|
|
167
|
+
ENTRIES=$(echo "$JSON_EXTRACTION" | jq -r 'length')
|
|
168
|
+
for i in $(seq 0 $((ENTRIES - 1))); do
|
|
169
|
+
FILENAME=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].filename")
|
|
170
|
+
NAME=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].name")
|
|
171
|
+
DESC=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].description")
|
|
172
|
+
TYPE=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].type")
|
|
173
|
+
CONTENT=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].content")
|
|
174
|
+
INDEX_LINE=$(echo "$JSON_EXTRACTION" | jq -r ".[$i].index_line")
|
|
175
|
+
|
|
176
|
+
if [ -z "$FILENAME" ] || [ "$FILENAME" = "null" ]; then
|
|
177
|
+
continue
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
FILEPATH="$MEMORY_DIR/$FILENAME"
|
|
181
|
+
|
|
182
|
+
# Don't overwrite manually curated memory files
|
|
183
|
+
if [ -f "$FILEPATH" ] && [[ "$FILENAME" != decisions/* ]]; then
|
|
184
|
+
continue
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
cat > "$FILEPATH" << ENDOFFILE
|
|
188
|
+
---
|
|
189
|
+
name: $NAME
|
|
190
|
+
description: $DESC
|
|
191
|
+
type: $TYPE
|
|
192
|
+
auto_extracted: true
|
|
193
|
+
date: $TIMESTAMP
|
|
194
|
+
session_id: $SESSION_ID
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
$CONTENT
|
|
198
|
+
ENDOFFILE
|
|
199
|
+
|
|
200
|
+
# Add to MEMORY.md index if not already there
|
|
201
|
+
if [ -f "$MEMORY_DIR/MEMORY.md" ]; then
|
|
202
|
+
if ! grep -qF "$FILENAME" "$MEMORY_DIR/MEMORY.md"; then
|
|
203
|
+
echo "$INDEX_LINE" >> "$MEMORY_DIR/MEMORY.md"
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
|
|
208
|
+
# Cleanup: keep only last 20 decision files
|
|
209
|
+
if [ -d "$DECISIONS_DIR" ]; then
|
|
210
|
+
ls -t "$DECISIONS_DIR"/*.md 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Run dedup check
|
|
214
|
+
QUIET=true zsh "$HOME/.claude/hooks/memory-dedup.sh" 2>/dev/null || true
|
|
215
|
+
|
|
216
|
+
} &
|
|
217
|
+
|
|
218
|
+
exit 0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Trigger memory-extractor.sh after a real git commit.
|
|
3
|
+
# Runs from PostToolUse Bash hook (async).
|
|
4
|
+
# memory-extractor.sh has an internal 5-minute rate limit to batch consecutive commits.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
8
|
+
|
|
9
|
+
# Only trigger after a real `git commit` (not git log, diff, status, etc.)
|
|
10
|
+
if ! echo "$COMMAND" | grep -qE '(^|[[:space:]&;|(])git[[:space:]]+commit([[:space:]]|$)'; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Forward the full hook payload to the extractor
|
|
15
|
+
echo "$INPUT" | bash "$HOME/.claude/hooks/memory-extractor.sh"
|
|
16
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
# PostToolUse hook: After a git commit, outputs a reminder for AXEL to
|
|
3
|
+
# launch excelsior-verifier on the committed files.
|
|
4
|
+
# This is a lightweight trigger — the actual verification runs as a subagent.
|
|
5
|
+
|
|
6
|
+
# Only trigger on Bash tool calls that contain 'git commit'
|
|
7
|
+
TOOL_INPUT="${TOOL_INPUT:-}"
|
|
8
|
+
if [[ "$TOOL_INPUT" != *"git commit"* ]]; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Check if the commit actually succeeded (exit code 0 from the tool)
|
|
13
|
+
TOOL_IS_ERROR="${TOOL_RESULT_IS_ERROR:-0}"
|
|
14
|
+
if [[ "$TOOL_IS_ERROR" == "1" ]] || [[ "$TOOL_IS_ERROR" == "true" ]]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Get the files from the last commit
|
|
19
|
+
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | head -20)
|
|
20
|
+
if [ -z "$CHANGED_FILES" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ')
|
|
25
|
+
COMMIT_MSG=$(git log --oneline -1 2>/dev/null)
|
|
26
|
+
|
|
27
|
+
# Only trigger for non-trivial commits (2+ files or test/feature commits)
|
|
28
|
+
if [[ "$FILE_COUNT" -lt 2 ]] && [[ "$COMMIT_MSG" != *"feat"* ]] && [[ "$COMMIT_MSG" != *"fix"* ]]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Output advisory to AXEL to launch verifier
|
|
33
|
+
cat << EOF
|
|
34
|
+
Post-commit verification advisory: Commit "$COMMIT_MSG" modified $FILE_COUNT files:
|
|
35
|
+
$(echo "$CHANGED_FILES" | sed 's/^/ - /')
|
|
36
|
+
|
|
37
|
+
Consider launching excelsior-verifier as a background agent to verify this commit.
|
|
38
|
+
Command: Agent({ subagent_type: "excelsior-verifier", run_in_background: true, prompt: "Verify commit: $COMMIT_MSG. Files: $CHANGED_FILES" })
|
|
39
|
+
EOF
|
|
40
|
+
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
# PostToolUse hook: Auto-lint/fix files after Edit or Write.
|
|
3
|
+
# Uses HOOK_TOOL_INPUT (JSON stdin) for richer context when available,
|
|
4
|
+
# falls back to TOOL_INPUT_FILE_PATH env var.
|
|
5
|
+
|
|
6
|
+
# Try to extract file path from JSON stdin first, then env var
|
|
7
|
+
FILE=""
|
|
8
|
+
if [ -n "$TOOL_INPUT_FILE_PATH" ]; then
|
|
9
|
+
FILE="$TOOL_INPUT_FILE_PATH"
|
|
10
|
+
elif [ -n "$HOOK_TOOL_INPUT" ]; then
|
|
11
|
+
FILE=$(echo "$HOOK_TOOL_INPUT" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('file_path',''))" 2>/dev/null)
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Ruby files — rubocop autocorrect
|
|
19
|
+
if [[ "$FILE" == *.rb ]] && [[ -f Gemfile ]]; then
|
|
20
|
+
bundle exec rubocop --autocorrect "$FILE" --format simple 2>/dev/null | tail -5
|
|
21
|
+
|
|
22
|
+
# TypeScript/JavaScript — eslint fix
|
|
23
|
+
elif [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]] || [[ "$FILE" == *.js ]] || [[ "$FILE" == *.jsx ]]; then
|
|
24
|
+
if [[ -f node_modules/.bin/eslint ]]; then
|
|
25
|
+
npx eslint --fix "$FILE" 2>/dev/null | tail -5
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Python — ruff fix (fast, modern linter)
|
|
29
|
+
elif [[ "$FILE" == *.py ]]; then
|
|
30
|
+
if command -v ruff >/dev/null 2>&1; then
|
|
31
|
+
ruff check --fix "$FILE" 2>/dev/null | tail -5
|
|
32
|
+
elif command -v autopep8 >/dev/null 2>&1; then
|
|
33
|
+
autopep8 --in-place "$FILE" 2>/dev/null
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# ERB templates — erb lint
|
|
37
|
+
elif [[ "$FILE" == *.erb ]] && [[ -f Gemfile ]]; then
|
|
38
|
+
if bundle show erb_lint >/dev/null 2>&1; then
|
|
39
|
+
bundle exec erblint --autocorrect "$FILE" 2>/dev/null | tail -3
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
exit 0
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
# PreCompact hook: saves rich context before compaction discards messages.
|
|
3
|
+
# Extracts key files, pending work, decisions, and timeline.
|
|
4
|
+
# Pattern inspired by claw-code compact.rs — preserve what matters.
|
|
5
|
+
|
|
6
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
7
|
+
CONTEXT_DIR="$HOME/.claude/sessions/context"
|
|
8
|
+
TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
|
|
9
|
+
|
|
10
|
+
mkdir -p "$CONTEXT_DIR"
|
|
11
|
+
|
|
12
|
+
# Read stdin for compaction context
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
COMPACT_TYPE=$(echo "$INPUT" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('type','auto'))" 2>/dev/null || echo "auto")
|
|
15
|
+
|
|
16
|
+
CONTEXT_FILE="$CONTEXT_DIR/${PROJECT_NAME}_${TIMESTAMP}.md"
|
|
17
|
+
|
|
18
|
+
# --- Git state ---
|
|
19
|
+
GIT_STATE=""
|
|
20
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
21
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
22
|
+
RECENT_COMMITS=$(git log --oneline -10 2>/dev/null)
|
|
23
|
+
MODIFIED=$(git diff --name-only 2>/dev/null | head -20)
|
|
24
|
+
STAGED=$(git diff --cached --name-only 2>/dev/null | head -20)
|
|
25
|
+
GIT_STATE="Branch: $BRANCH
|
|
26
|
+
Recent commits:
|
|
27
|
+
$RECENT_COMMITS
|
|
28
|
+
Modified files: $MODIFIED
|
|
29
|
+
Staged files: $STAGED"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# --- Key files referenced in session ---
|
|
33
|
+
KEY_FILES=""
|
|
34
|
+
LOG_FILE="/tmp/claude-session-log-${PROJECT_NAME}.md"
|
|
35
|
+
if [ -f "$LOG_FILE" ]; then
|
|
36
|
+
# Extract file paths mentioned in the session log (pattern: paths with extensions)
|
|
37
|
+
KEY_FILES=$(grep -oE '[a-zA-Z0-9_/.-]+\.(rb|ts|tsx|js|jsx|py|rs|json|yml|yaml|md|sql|erb|rake)' "$LOG_FILE" 2>/dev/null | sort -u | head -15)
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# --- Pending work / TODOs ---
|
|
41
|
+
PENDING_WORK=""
|
|
42
|
+
if [ -f "$LOG_FILE" ]; then
|
|
43
|
+
PENDING_WORK=$(grep -iE '(todo|next|pending|follow.up|remaining|fixme|hack|later)' "$LOG_FILE" 2>/dev/null | tail -10)
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# --- Recent session actions ---
|
|
47
|
+
SESSION_LOG=""
|
|
48
|
+
if [ -f "$LOG_FILE" ]; then
|
|
49
|
+
SESSION_LOG=$(tail -40 "$LOG_FILE")
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# --- Active tasks (if GSD is running) ---
|
|
53
|
+
ACTIVE_TASKS=""
|
|
54
|
+
PLANNING_DIR="$(pwd)/.planning"
|
|
55
|
+
if [ -d "$PLANNING_DIR" ]; then
|
|
56
|
+
# Find current phase
|
|
57
|
+
CURRENT_PHASE=$(ls -d "$PLANNING_DIR"/phase-* 2>/dev/null | tail -1)
|
|
58
|
+
if [ -n "$CURRENT_PHASE" ]; then
|
|
59
|
+
ACTIVE_TASKS="Current phase: $(basename "$CURRENT_PHASE")"
|
|
60
|
+
if [ -f "$CURRENT_PHASE/PLAN.md" ]; then
|
|
61
|
+
# Extract incomplete tasks from plan
|
|
62
|
+
INCOMPLETE=$(grep -E '^\s*-\s*\[ \]' "$CURRENT_PHASE/PLAN.md" 2>/dev/null | head -10)
|
|
63
|
+
if [ -n "$INCOMPLETE" ]; then
|
|
64
|
+
ACTIVE_TASKS="$ACTIVE_TASKS
|
|
65
|
+
Incomplete tasks:
|
|
66
|
+
$INCOMPLETE"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# --- Decisions made (look for patterns in session) ---
|
|
73
|
+
DECISIONS=""
|
|
74
|
+
if [ -f "$LOG_FILE" ]; then
|
|
75
|
+
DECISIONS=$(grep -iE '(decided|decision|chose|picked|went with|selected|agreed|confirmed)' "$LOG_FILE" 2>/dev/null | tail -5)
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# --- Memory files modified this session ---
|
|
79
|
+
MEMORY_CHANGES=""
|
|
80
|
+
MEMORY_DIR="$HOME/.claude/memory"
|
|
81
|
+
if [ -d "$MEMORY_DIR" ]; then
|
|
82
|
+
# Files modified in the last 2 hours
|
|
83
|
+
MEMORY_CHANGES=$(find "$MEMORY_DIR" -name "*.md" -mmin -120 -type f 2>/dev/null | while read f; do basename "$f"; done | head -10)
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
cat > "$CONTEXT_FILE" << ENDOFFILE
|
|
87
|
+
---
|
|
88
|
+
project: $PROJECT_NAME
|
|
89
|
+
compact_type: $COMPACT_TYPE
|
|
90
|
+
date: $(date +%Y-%m-%d)
|
|
91
|
+
time: $(date +%H:%M)
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
# Pre-compact context snapshot
|
|
95
|
+
|
|
96
|
+
## Git state
|
|
97
|
+
$GIT_STATE
|
|
98
|
+
|
|
99
|
+
## Key files referenced
|
|
100
|
+
$KEY_FILES
|
|
101
|
+
|
|
102
|
+
## Pending work / TODOs
|
|
103
|
+
$PENDING_WORK
|
|
104
|
+
|
|
105
|
+
## Active tasks
|
|
106
|
+
$ACTIVE_TASKS
|
|
107
|
+
|
|
108
|
+
## Decisions made
|
|
109
|
+
$DECISIONS
|
|
110
|
+
|
|
111
|
+
## Memory files updated
|
|
112
|
+
$MEMORY_CHANGES
|
|
113
|
+
|
|
114
|
+
## Recent actions (last 40)
|
|
115
|
+
$SESSION_LOG
|
|
116
|
+
ENDOFFILE
|
|
117
|
+
|
|
118
|
+
# Keep only last 5 context snapshots per project
|
|
119
|
+
ls -t "$CONTEXT_DIR"/${PROJECT_NAME}_*.md 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null
|
|
120
|
+
|
|
121
|
+
# Output message to Claude about context being saved
|
|
122
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreCompact","additionalContext":"Se guardó un snapshot enriquecido del contexto antes de compactación en '"$CONTEXT_FILE"'. Incluye: archivos clave, trabajo pendiente, decisiones, tareas activas, y estado git. La memoria persistente en ~/.claude/memory/ sigue intacta."}}'
|
|
123
|
+
|
|
124
|
+
exit 0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SessionStart hook — warns if priority-map.md is stale (>14 days) or missing.
|
|
3
|
+
# Output is injected into the session's initial context.
|
|
4
|
+
|
|
5
|
+
PRIORITY_MAP="$HOME/.claude/memory/priority-map.md"
|
|
6
|
+
MAX_AGE_DAYS=14
|
|
7
|
+
|
|
8
|
+
if [[ ! -f "$PRIORITY_MAP" ]]; then
|
|
9
|
+
echo "⚠️ priority-map.md does not exist at ~/.claude/memory/ — skills like sprint-status/daily/eod-review will run without it"
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
14
|
+
FILE_MTIME=$(stat -f %m "$PRIORITY_MAP")
|
|
15
|
+
else
|
|
16
|
+
FILE_MTIME=$(stat -c %Y "$PRIORITY_MAP")
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
NOW=$(date +%s)
|
|
20
|
+
AGE_SECONDS=$((NOW - FILE_MTIME))
|
|
21
|
+
AGE_DAYS=$((AGE_SECONDS / 86400))
|
|
22
|
+
|
|
23
|
+
if (( AGE_DAYS > MAX_AGE_DAYS )); then
|
|
24
|
+
echo "⚠️ priority-map.md is ${AGE_DAYS} days old (threshold: ${MAX_AGE_DAYS}). Review and refresh current-sprint P0/P1 before running /daily or /sprint-status."
|
|
25
|
+
elif (( AGE_DAYS >= 7 )); then
|
|
26
|
+
echo "ℹ️ priority-map.md is ${AGE_DAYS} days old — consider a refresh if sprint focus changed."
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
exit 0
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Excelsior Proactive Resolver — PostToolUse hook for Bash
|
|
3
|
+
# Philosophy: ANY failure that can be auto-resolved SHOULD be auto-resolved.
|
|
4
|
+
# This hook handles the fast, known patterns. For everything else,
|
|
5
|
+
# the CLAUDE.md Excelsior principle tells the model to investigate and resolve.
|
|
6
|
+
|
|
7
|
+
TOOL_OUTPUT="${TOOL_OUTPUT:-}"
|
|
8
|
+
EXIT_CODE="${TOOL_EXIT_CODE:-0}"
|
|
9
|
+
|
|
10
|
+
# Only act on failures
|
|
11
|
+
[ "$EXIT_CODE" = "0" ] && exit 0
|
|
12
|
+
|
|
13
|
+
RESOLVED=""
|
|
14
|
+
|
|
15
|
+
# === SERVICE DETECTION & AUTO-START ===
|
|
16
|
+
|
|
17
|
+
# Docker (any Docker-related error)
|
|
18
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "docker daemon|Cannot connect to the Docker|docker\.sock|Is the docker daemon running|docker:.*command not found"; then
|
|
19
|
+
if [ "$(uname)" = "Darwin" ] && ! docker info >/dev/null 2>&1; then
|
|
20
|
+
open -a "Docker" 2>/dev/null
|
|
21
|
+
for i in $(seq 1 15); do docker info >/dev/null 2>&1 && break; sleep 1; done
|
|
22
|
+
if docker info >/dev/null 2>&1; then
|
|
23
|
+
RESOLVED="Docker Desktop started automatically."
|
|
24
|
+
else
|
|
25
|
+
RESOLVED="HINT: Docker Desktop is launching. Wait a few seconds and retry."
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# PostgreSQL (any PG connection error)
|
|
31
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "PG::|postgresql.*refused|could not connect.*5432\|could not connect.*5433\|connection to server.*failed.*postgres"; then
|
|
32
|
+
if [ "$(uname)" = "Darwin" ]; then
|
|
33
|
+
brew services start postgresql@16 2>/dev/null || brew services start postgresql 2>/dev/null
|
|
34
|
+
STOPPED_PG=$(docker ps -a --filter "ancestor=postgres" --filter "status=exited" --format "{{.Names}}" 2>/dev/null | head -1)
|
|
35
|
+
[ -n "$STOPPED_PG" ] && docker start "$STOPPED_PG" 2>/dev/null
|
|
36
|
+
RESOLVED="PostgreSQL restart attempted."
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Redis
|
|
41
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "Redis.*refused|ECONNREFUSED.*6379|Redis::CannotConnectError|redis.*not connect"; then
|
|
42
|
+
brew services start redis 2>/dev/null && RESOLVED="Redis started via Homebrew."
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# MySQL
|
|
46
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "mysql.*refused|ECONNREFUSED.*3306|Access denied for user.*mysql"; then
|
|
47
|
+
brew services start mysql 2>/dev/null && RESOLVED="MySQL started via Homebrew."
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# === DEPENDENCY RESOLUTION ===
|
|
51
|
+
|
|
52
|
+
# Node modules missing
|
|
53
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "Cannot find module|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND" && [ -f "package.json" ]; then
|
|
54
|
+
if [ -f "pnpm-lock.yaml" ]; then
|
|
55
|
+
RESOLVED="HINT: Run 'pnpm install' — missing node_modules detected."
|
|
56
|
+
elif [ -f "yarn.lock" ]; then
|
|
57
|
+
RESOLVED="HINT: Run 'yarn install' — missing node_modules detected."
|
|
58
|
+
elif [ -f "package-lock.json" ]; then
|
|
59
|
+
RESOLVED="HINT: Run 'npm install' — missing node_modules detected."
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Ruby gems missing
|
|
64
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "Could not find gem|Bundler::GemNotFound|bundle install|Gem::MissingSpecError"; then
|
|
65
|
+
RESOLVED="HINT: Run 'bundle install' — missing gems detected."
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Python deps missing
|
|
69
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "ModuleNotFoundError|No module named|ImportError.*No module"; then
|
|
70
|
+
if [ -f "requirements.txt" ]; then
|
|
71
|
+
RESOLVED="HINT: Run 'pip install -r requirements.txt' — missing Python module."
|
|
72
|
+
elif [ -f "pyproject.toml" ]; then
|
|
73
|
+
RESOLVED="HINT: Run 'pip install -e .' or 'poetry install' — missing Python module."
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# === ENVIRONMENT ISSUES ===
|
|
78
|
+
|
|
79
|
+
# Port in use — identify the blocker
|
|
80
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "EADDRINUSE|Address already in use|port.*already.*use|bind.*address already"; then
|
|
81
|
+
PORT=$(echo "$TOOL_OUTPUT" | grep -oE "[0-9]{4,5}" | head -1)
|
|
82
|
+
[ -n "$PORT" ] && PID=$(lsof -ti ":$PORT" 2>/dev/null | head -1)
|
|
83
|
+
[ -n "$PID" ] && PROC=$(ps -p "$PID" -o comm= 2>/dev/null)
|
|
84
|
+
[ -n "$PROC" ] && RESOLVED="HINT: Port $PORT blocked by $PROC (PID $PID). Kill with: kill $PID"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Database not created
|
|
88
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "database.*does not exist|Unknown database|FATAL.*database.*not exist"; then
|
|
89
|
+
DB=$(echo "$TOOL_OUTPUT" | grep -oE '"[^"]*"' | head -1 | tr -d '"')
|
|
90
|
+
RESOLVED="HINT: Database '$DB' doesn't exist. Create it: rails db:create or createdb $DB"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Migrations pending
|
|
94
|
+
if echo "$TOOL_OUTPUT" | grep -qiE "Migrations are pending|pending migration|migrate.*first"; then
|
|
95
|
+
RESOLVED="HINT: Pending migrations. Run: rails db:migrate RAILS_ENV=test"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# === OUTPUT ===
|
|
99
|
+
if [ -n "$RESOLVED" ]; then
|
|
100
|
+
echo "PROACTIVE: $RESOLVED Retry your command." >&2
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Always exit 0 — this hook advises, never blocks
|
|
104
|
+
exit 0
|