feat: expand agents (10), skills (20), and hooks (11) with profile system
Agents: - Add YAML frontmatter (model, tools) to all 7 existing agents - New agents: planner (opus), build-error-resolver (sonnet), loop-operator (sonnet) Skills: - search-first: research before building (Adopt/Extend/Compose/Build) - verification-loop: full quality gate pipeline (Build→TypeCheck→Lint→Test→Security→Diff) - strategic-compact: when and how to run /compact effectively - autonomous-loops: 6 patterns for autonomous agent workflows - continuous-learning: extract session learnings into instincts Hooks: - Profile system (minimal/standard/strict) via run-with-profile.sh - config-protection: block linter/formatter config edits (standard) - suggest-compact: remind about /compact every ~50 tool calls (standard) - auto-tmux-dev: suggest tmux for dev servers (standard) - session-save/session-load: persist and restore session context (Stop/SessionStart) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
49
.claude/hooks/auto-tmux-dev.sh
Executable file
49
.claude/hooks/auto-tmux-dev.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Auto-start dev server in tmux session when build/dev commands are detected
|
||||
# Event: PreToolUse | Matcher: Bash
|
||||
# Profile: standard
|
||||
# Non-blocking (exit 0)
|
||||
|
||||
INPUT=$(cat)
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
if [ -z "$COMMAND" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect dev server commands
|
||||
DEV_PATTERNS=(
|
||||
'npm\s+run\s+dev'
|
||||
'pnpm\s+(run\s+)?dev'
|
||||
'yarn\s+dev'
|
||||
'next\s+dev'
|
||||
'vite\s*$'
|
||||
'vite\s+dev'
|
||||
)
|
||||
|
||||
IS_DEV_COMMAND=false
|
||||
for pattern in "${DEV_PATTERNS[@]}"; do
|
||||
if echo "$COMMAND" | grep -qE "$pattern"; then
|
||||
IS_DEV_COMMAND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$IS_DEV_COMMAND" = false ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if tmux is available
|
||||
if ! command -v tmux &>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if dev server is already running in a tmux session
|
||||
SESSION_NAME="claude-dev"
|
||||
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
||||
echo "Dev server already running in tmux session '$SESSION_NAME'. Use 'tmux attach -t $SESSION_NAME' to view." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Tip: Long-running dev servers work better in tmux. Run: tmux new -d -s $SESSION_NAME '$COMMAND'" >&2
|
||||
exit 0
|
||||
43
.claude/hooks/config-protection.sh
Executable file
43
.claude/hooks/config-protection.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# Block modifications to linter/formatter configuration files
|
||||
# Event: PreToolUse | Matcher: Edit|Write
|
||||
# Profile: standard
|
||||
# Exit 2 = block, Exit 0 = allow
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Protected config files (linters, formatters, build configs)
|
||||
PROTECTED_CONFIGS=(
|
||||
".eslintrc"
|
||||
".eslintrc.js"
|
||||
".eslintrc.json"
|
||||
".eslintrc.yml"
|
||||
"eslint.config.js"
|
||||
"eslint.config.mjs"
|
||||
".prettierrc"
|
||||
".prettierrc.js"
|
||||
".prettierrc.json"
|
||||
"prettier.config.js"
|
||||
"biome.json"
|
||||
"biome.jsonc"
|
||||
".editorconfig"
|
||||
"tsconfig.json"
|
||||
"tsconfig.base.json"
|
||||
)
|
||||
|
||||
FILENAME=$(basename "$FILE_PATH")
|
||||
|
||||
for config in "${PROTECTED_CONFIGS[@]}"; do
|
||||
if [ "$FILENAME" = "$config" ]; then
|
||||
echo "Blocked: modifying config file '$FILENAME'. These files affect the entire project." >&2
|
||||
echo "If this change is intentional, disable this hook: CLAUDE_DISABLED_HOOKS=config-protection.sh" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
46
.claude/hooks/run-with-profile.sh
Executable file
46
.claude/hooks/run-with-profile.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Hook profile gate — wraps hooks to enable/disable by profile
|
||||
# Profiles: minimal (safety only), standard (safety + quality), strict (everything)
|
||||
#
|
||||
# Usage in settings.json:
|
||||
# "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-with-profile.sh standard $CLAUDE_PROJECT_DIR/.claude/hooks/some-hook.sh"
|
||||
#
|
||||
# Environment variables:
|
||||
# CLAUDE_HOOK_PROFILE — Override profile (minimal|standard|strict). Default: standard
|
||||
# CLAUDE_DISABLED_HOOKS — Comma-separated list of hook filenames to skip. E.g.: "suggest-compact.sh,auto-tmux-dev.sh"
|
||||
|
||||
REQUIRED_PROFILE="${1:?Usage: run-with-profile.sh <profile> <hook-script>}"
|
||||
HOOK_SCRIPT="${2:?Usage: run-with-profile.sh <profile> <hook-script>}"
|
||||
shift 2
|
||||
|
||||
# Current profile (default: standard)
|
||||
CURRENT_PROFILE="${CLAUDE_HOOK_PROFILE:-standard}"
|
||||
|
||||
# Profile hierarchy: minimal < standard < strict
|
||||
profile_level() {
|
||||
case "$1" in
|
||||
minimal) echo 1 ;;
|
||||
standard) echo 2 ;;
|
||||
strict) echo 3 ;;
|
||||
*) echo 2 ;; # default to standard
|
||||
esac
|
||||
}
|
||||
|
||||
CURRENT_LEVEL=$(profile_level "$CURRENT_PROFILE")
|
||||
REQUIRED_LEVEL=$(profile_level "$REQUIRED_PROFILE")
|
||||
|
||||
# Skip if current profile is lower than required
|
||||
if [ "$CURRENT_LEVEL" -lt "$REQUIRED_LEVEL" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if hook is explicitly disabled
|
||||
HOOK_NAME=$(basename "$HOOK_SCRIPT")
|
||||
if [ -n "$CLAUDE_DISABLED_HOOKS" ]; then
|
||||
if echo ",$CLAUDE_DISABLED_HOOKS," | grep -q ",$HOOK_NAME,"; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Execute the hook, passing stdin through
|
||||
exec "$HOOK_SCRIPT" "$@"
|
||||
31
.claude/hooks/session-load.sh
Executable file
31
.claude/hooks/session-load.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Restore session context from previous session
|
||||
# Event: SessionStart
|
||||
# Reads .claude/sessions/latest.json and outputs a context summary
|
||||
|
||||
SESSION_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/sessions/latest.json"
|
||||
|
||||
if [ ! -f "$SESSION_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read session data
|
||||
TIMESTAMP=$(jq -r '.timestamp // "unknown"' "$SESSION_FILE" 2>/dev/null)
|
||||
BRANCH=$(jq -r '.branch // "unknown"' "$SESSION_FILE" 2>/dev/null)
|
||||
PHASE=$(jq -r '.phase // "unknown"' "$SESSION_FILE" 2>/dev/null)
|
||||
MODIFIED=$(jq -r '.modified_files // [] | length' "$SESSION_FILE" 2>/dev/null)
|
||||
STAGED=$(jq -r '.staged_files // [] | length' "$SESSION_FILE" 2>/dev/null)
|
||||
COMMITS=$(jq -r '.recent_commits // [] | join("\n ")' "$SESSION_FILE" 2>/dev/null)
|
||||
|
||||
echo "Previous session ($TIMESTAMP):"
|
||||
echo " Branch: $BRANCH"
|
||||
echo " Phase: $PHASE"
|
||||
echo " Modified files: $MODIFIED, Staged: $STAGED"
|
||||
if [ -n "$COMMITS" ] && [ "$COMMITS" != "" ]; then
|
||||
echo " Recent commits:"
|
||||
echo " $COMMITS"
|
||||
fi
|
||||
echo ""
|
||||
echo "Reminder: check RULES.md and RECOMMENDATIONS.md for project conventions."
|
||||
|
||||
exit 0
|
||||
31
.claude/hooks/session-save.sh
Executable file
31
.claude/hooks/session-save.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Save session context on stop for restoration in next session
|
||||
# Event: Stop
|
||||
# Saves: branch, modified files, recent commits, phase
|
||||
|
||||
SESSION_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/sessions"
|
||||
mkdir -p "$SESSION_DIR"
|
||||
|
||||
SESSION_FILE="$SESSION_DIR/latest.json"
|
||||
|
||||
# Gather session state
|
||||
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
MODIFIED=$(git diff --name-only 2>/dev/null | head -20 | jq -R -s 'split("\n") | map(select(. != ""))')
|
||||
STAGED=$(git diff --cached --name-only 2>/dev/null | head -20 | jq -R -s 'split("\n") | map(select(. != ""))')
|
||||
RECENT_COMMITS=$(git log --oneline -5 2>/dev/null | jq -R -s 'split("\n") | map(select(. != ""))')
|
||||
PHASE=$(grep -m1 'Current Phase' "${CLAUDE_PROJECT_DIR:-.}/docs/phases-plan.md" 2>/dev/null || echo "unknown")
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Write session file
|
||||
cat > "$SESSION_FILE" << ENDJSON
|
||||
{
|
||||
"timestamp": "$TIMESTAMP",
|
||||
"branch": "$BRANCH",
|
||||
"phase": "$PHASE",
|
||||
"modified_files": $MODIFIED,
|
||||
"staged_files": $STAGED,
|
||||
"recent_commits": $RECENT_COMMITS
|
||||
}
|
||||
ENDJSON
|
||||
|
||||
exit 0
|
||||
24
.claude/hooks/suggest-compact.sh
Executable file
24
.claude/hooks/suggest-compact.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Suggest /compact after ~50 tool calls
|
||||
# Event: PreToolUse | Matcher: Edit|Write
|
||||
# Profile: standard
|
||||
# Non-blocking reminder (exit 0)
|
||||
|
||||
COUNTER_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/.tool-counter"
|
||||
|
||||
# Initialize counter if it doesn't exist
|
||||
if [ ! -f "$COUNTER_FILE" ]; then
|
||||
echo "0" > "$COUNTER_FILE"
|
||||
fi
|
||||
|
||||
# Increment counter
|
||||
COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
|
||||
COUNT=$((COUNT + 1))
|
||||
echo "$COUNT" > "$COUNTER_FILE"
|
||||
|
||||
# Suggest compact every 50 calls
|
||||
if [ $((COUNT % 50)) -eq 0 ]; then
|
||||
echo "Context optimization: $COUNT tool calls in this session. Consider running /strategic-compact if context feels bloated." >&2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user