Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions docs/activation-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Activation Modes

Superpowers supports multiple activation modes to fit your workflow.

## Modes

### `always` (Default)
Superpowers activates on every session. Maximum automation.

```yaml
# .superpowers/config.yaml
activation:
mode: always
```

### `opt-in`
Superpowers only activates when explicitly enabled.

```yaml
activation:
mode: opt-in
```

Then create the enable marker:
```bash
touch .superpowers/enabled
```

### `opt-out`
Superpowers activates unless explicitly disabled.

```yaml
activation:
mode: opt-out
```

To disable for a specific project:
```bash
touch .superpowers/disabled
```

### `never`
Superpowers never activates (but remains installed).

```yaml
activation:
mode: never
```

## Activation Levels

Control how much of Superpowers loads:

### `full` (Default)
Complete workflow: brainstorming, TDD, subagent-driven-development, etc.

```yaml
activation:
level: full
```

### `lightweight`
Only core skills: brainstorming, using-superpowers.

```yaml
activation:
level: lightweight
```

### `minimal`
Only using-superpowers skill.

```yaml
activation:
level: minimal
```

## Context-Aware Activation

Superpowers can adapt based on git context:

```yaml
activation:
context_aware:
# Don't activate when on main/master branch
disable_on_main_branch: true

# Use lightweight mode in detached HEAD
lightweight_on_detached_head: true

# Only activate in git repositories
require_git_repo: true
```

## Per-Skill Control

Fine-grained control over individual skills:

```yaml
skills:
brainstorming:
enabled: true
auto_trigger: true

test-driven-development:
enabled: true
enforce_red_green_refactor: true

dispatching-parallel-agents:
enabled: false # Disable advanced feature
```

## Environment Variable Override

Quick toggle without editing config:

```bash
# Disable for this session
export SUPERPOWERS_MODE=disabled

# Force enable
export SUPERPOWERS_MODE=enabled
```

## Commands

In Claude Code, use these commands:

- `/superpowers on` - Enable for this project
- `/superpowers off` - Disable for this project
- `/superpowers level full|lightweight|minimal` - Change activation level
- `/superpowers status` - Show current configuration

## Migration from Older Versions

If you have custom skills in `~/.config/superpowers/skills`, move them:

```bash
mkdir -p ~/.claude/skills
mv ~/.config/superpowers/skills/* ~/.claude/skills/
rmdir ~/.config/superpowers/skills
```

This removes the migration warning from startup.
227 changes: 206 additions & 21 deletions hooks/session-start
Original file line number Diff line number Diff line change
@@ -1,25 +1,134 @@
#!/usr/bin/env bash
# SessionStart hook for superpowers plugin
# session-start with opt-in activation support

set -euo pipefail

# Determine plugin root directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
PROJECT_ROOT="$(pwd)"

# Check if legacy skills directory exists and build warning
warning_message=""
legacy_skills_dir="${HOME}/.config/superpowers/skills"
if [ -d "$legacy_skills_dir" ]; then
warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills</important-reminder>"
# Default configuration
SUPERPOWERS_ENABLED=true
INJECTION_MODE="full"
LEGACY_WARNING=""
DISABLE_REASON="unknown"
PROJECT_OVERRIDES_ENABLED=true

# ==============================================================================
# Configuration Parsing
# ==============================================================================

# Parse YAML config (simple implementation)
parse_config() {
local config_file="$1"
if [ -f "$config_file" ]; then
# Check per_project_enabled flag
if grep -q "per_project_enabled: false" "$config_file"; then
PROJECT_OVERRIDES_ENABLED=false
fi

# Extract mode
if grep -q "mode: opt-in" "$config_file"; then
if [ ! -f "${PROJECT_ROOT}/.superpowers/enabled" ]; then
SUPERPOWERS_ENABLED=false
fi
elif grep -q "mode: never" "$config_file"; then
SUPERPOWERS_ENABLED=false
elif grep -q "mode: opt-out" "$config_file"; then
if [ -f "${PROJECT_ROOT}/.superpowers/disabled" ]; then
SUPERPOWERS_ENABLED=false
fi
fi

# Extract level
if grep -q "level: lightweight" "$config_file"; then
INJECTION_MODE="lightweight"
elif grep -q "level: minimal" "$config_file"; then
INJECTION_MODE="minimal"
fi

# Check context-aware settings
if grep -q "disable_on_main_branch: true" "$config_file"; then
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
SUPERPOWERS_ENABLED=false
DISABLE_REASON="disabled_on_main_branch"
fi
fi

if grep -q "lightweight_on_detached_head: true" "$config_file"; then
CURRENT_BRANCH=${CURRENT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")}
if [ "$CURRENT_BRANCH" = "HEAD" ] && [ "$INJECTION_MODE" = "full" ]; then
INJECTION_MODE="lightweight"
fi
fi

if grep -q "require_git_repo: true" "$config_file"; then
if ! git rev-parse --git-dir > /dev/null 2>&1; then
SUPERPOWERS_ENABLED=false
DISABLE_REASON="require_git_repo"
fi
fi
fi
}

# ==============================================================================
# Check Activation State
# ==============================================================================

# 1. Check global disable file
if [ -f "${HOME}/.superpowers/disabled" ]; then
SUPERPOWERS_ENABLED=false
fi

# 2. Check configs (layered: plugin defaults → project overrides)
if [ -f "${PLUGIN_ROOT}/lib/config.yaml" ]; then
parse_config "${PLUGIN_ROOT}/lib/config.yaml"
fi

if [ "$PROJECT_OVERRIDES_ENABLED" = "true" ] && [ -f "${PROJECT_ROOT}/.superpowers/config.yaml" ]; then
parse_config "${PROJECT_ROOT}/.superpowers/config.yaml"
fi

# 3. Check environment variable override (highest priority)
SUPERPOWERS_SESSION_MODE="${SUPERPOWERS_MODE:-}"
if [ -n "$SUPERPOWERS_SESSION_MODE" ]; then
case "$SUPERPOWERS_SESSION_MODE" in
enabled|always)
SUPERPOWERS_ENABLED=true
;;
opt-out)
SUPERPOWERS_ENABLED=true
if [ -f "${PROJECT_ROOT}/.superpowers/disabled" ]; then
SUPERPOWERS_ENABLED=false
fi
;;
disabled|never)
SUPERPOWERS_ENABLED=false
;;
opt-in)
# For opt-in, require explicit .superpowers/enabled
SUPERPOWERS_ENABLED=true
if [ ! -f "${PROJECT_ROOT}/.superpowers/enabled" ]; then
SUPERPOWERS_ENABLED=false
fi
;;
*)
# Unknown mode, warn but continue
;;
esac
fi

# 4. Legacy skills directory warning
LEGACY_SKILLS_DIR="${HOME}/.config/superpowers/skills"
if [ -d "$LEGACY_SKILLS_DIR" ]; then
LEGACY_WARNING=$'\n\n<important-reminder>⚠️ **WARNING:** Superpowers now uses Claude Code'\''s skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move to ~/.claude/skills instead. Remove the directory to dismiss this message.</important-reminder>'
fi

# Read using-superpowers content
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
# ==============================================================================
# Context Injection
# ==============================================================================

# Escape string for JSON embedding using bash parameter substitution.
# Each ${s//old/new} is a single C-level pass - orders of magnitude
# faster than the character-by-character loop this replaces.
escape_for_json() {
local s="$1"
s="${s//\\/\\\\}"
Expand All @@ -30,20 +139,96 @@ escape_for_json() {
printf '%s' "$s"
}

using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
warning_escaped=$(escape_for_json "$warning_message")
session_context="<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
if [ "$SUPERPOWERS_ENABLED" = "true" ]; then
# Initialize variables
CONTENT=""
CONTENT_FILE=""

# Determine which content to inject based on mode
case "$INJECTION_MODE" in
full)
CONTENT_FILE="${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md"
;;
lightweight)
# Create lightweight content (only core skills)
CONTENT=$'<EXTREMELY_IMPORTANT>
Superpowers is active in lightweight mode.\n\nCore skills available:\n- brainstorming: Design before implementation\n- using-superpowers: How to use Superpowers\n\nFull workflow (TDD, subagent-driven-development) is disabled.\nEnable with: /superpowers level full\n</EXTREMELY_IMPORTANT>'
;;
minimal)
CONTENT=$'<EXTREMELY_IMPORTANT>
Superpowers is active in minimal mode.\n\nOnly using-superpowers skill is loaded.\nEnable more: /superpowers level lightweight|full\n</EXTREMELY_IMPORTANT>'
;;
esac

if [ -n "$CONTENT_FILE" ] && [ -f "$CONTENT_FILE" ]; then
CONTENT=$(cat "$CONTENT_FILE")
fi

CONTENT="${CONTENT}${LEGACY_WARNING}"

else
# Determine why Superpowers is disabled for accurate notice
DISABLE_REASON="unknown"

if [ -n "$SUPERPOWERS_SESSION_MODE" ]; then
DISABLE_REASON="env_var"
elif [ -f "${HOME}/.superpowers/disabled" ]; then
DISABLE_REASON="global_disabled"
elif [ -f "${PROJECT_ROOT}/.superpowers/config.yaml" ] && \
grep -q "mode: never" "${PROJECT_ROOT}/.superpowers/config.yaml"; then
DISABLE_REASON="config_never"
elif [ -f "${PROJECT_ROOT}/.superpowers/config.yaml" ] && \
grep -q "mode: opt-in" "${PROJECT_ROOT}/.superpowers/config.yaml"; then
DISABLE_REASON="opt_in"
elif grep -q "mode: opt-in" "${PLUGIN_ROOT}/lib/config.yaml" 2>/dev/null; then
DISABLE_REASON="opt_in"
fi

# Generate mode-specific notice
case "$DISABLE_REASON" in
env_var)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers disabled by environment variable.\n\nTo enable:\n- Unset SUPERPOWERS_MODE or set to '\''enabled'\''
- Current: SUPERPOWERS_MODE='"$SUPERPOWERS_SESSION_MODE"$'\n</SUPERPOWERS_AVAILABLE>'
;;
global_disabled)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers disabled globally.\n\nTo enable:\n- Remove ~/.superpowers/disabled file\n</SUPERPOWERS_AVAILABLE>'
;;
config_never)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers set to '\''never'\'' in config.\n\nTo enable:\n- Edit .superpowers/config.yaml and change mode\n</SUPERPOWERS_AVAILABLE>'
;;
disabled_on_main_branch)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers disabled on main/master branch.\n\nThis is a context-aware setting.\n- Switch to a feature branch to enable\n</SUPERPOWERS_AVAILABLE>'
;;
require_git_repo)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers requires a git repository.\n\nThis is a context-aware setting.\n- Navigate to a git repository to enable\n</SUPERPOWERS_AVAILABLE>'
;;
opt_in)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers in opt-in mode.\n\nTo enable:\n- Create .superpowers/enabled file\n- Or run: /superpowers on\n</SUPERPOWERS_AVAILABLE>'
;;
*)
CONTENT=$'<SUPERPOWERS_AVAILABLE>
Superpowers is installed but not active.\n\nTo enable:\n- Create .superpowers/enabled file\n- Or run: /superpowers on\n</SUPERPOWERS_AVAILABLE>'
;;
esac
fi

CONTENT_ESCAPED=$(escape_for_json "$CONTENT")

SESSION_CONTEXT="<EXTREMELY_IMPORTANT>\n${CONTENT_ESCAPED}\n</EXTREMELY_IMPORTANT>"

# Output context injection as JSON.
# Keep both shapes for compatibility:
# - Cursor hooks expect additional_context.
# - Claude hooks expect hookSpecificOutput.additionalContext.
# Output JSON
cat <<EOF
{
"additional_context": "${session_context}",
"additional_context": "${SESSION_CONTEXT}",
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "${session_context}"
"additionalContext": "${SESSION_CONTEXT}"
}
}
EOF
Expand Down
Loading