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:
olekhondera
2026-03-24 20:16:20 +02:00
parent cf86a91e4a
commit db5ba04fb9
26 changed files with 1361 additions and 58 deletions

49
.claude/hooks/auto-tmux-dev.sh Executable file
View 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

View 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

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

View 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