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,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