Claude Cursor GitHub Copilot Skill

Plugin Settings

This skill should be used when the user asks about "plugin settings", "store plugin configuration", "user-configurable plugin", ".local.md files", "plugin state files", "read YAML frontmatter", "per-project plugin settings", or wants to make plugin behavior configurable. Document

LLM Mart · 0 points · 16 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download tzachbon-smart-ralph-.agents_skills_Plugin Settings-4890dd3.zip · 17 KB
Part of tzachbon/smart-ralph — 44 skills

Install

skills CLI npx skills add https://github.com/tzachbon/smart-ralph/tree/main/.agents/skills/Plugin Settings
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tzachbon-smart-ralph@llmmart
Git git clone https://github.com/tzachbon/smart-ralph.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tzachbon/smart-ralph collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Plugin Settings Pattern for Claude Code Plugins

Overview

Plugins can store user-configurable settings and state in .claude/plugin-name.local.md files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.

Key characteristics:

  • File location: .claude/plugin-name.local.md in project root
  • Structure: YAML frontmatter + markdown body
  • Purpose: Per-project plugin configuration and state
  • Usage: Read from hooks, commands, and agents
  • Lifecycle: User-managed (not in git, should be in .gitignore)

File Structure

Basic Template

---
enabled: true
setting1: value1
setting2: value2
numeric_setting: 42
list_setting: ["item1", "item2"]
---

# Additional Context

This markdown body can contain:
- Task descriptions
- Additional instructions
- Prompts to feed back to Claude
- Documentation or notes

Example: Plugin State File

.claude/my-plugin.local.md:

---
enabled: true
strict_mode: false
max_retries: 3
notification_level: info
coordinator_session: team-leader
---

# Plugin Configuration

This plugin is configured for standard validation mode.
Contact @team-lead with questions.

Reading Settings Files

From Hooks (Bash Scripts)

Pattern: Check existence and parse frontmatter

#!/bin/bash
set -euo pipefail

# Define state file path
STATE_FILE=".claude/my-plugin.local.md"

# Quick exit if file doesn't exist
if [[ ! -f "$STATE_FILE" ]]; then
  exit 0  # Plugin not configured, skip
fi

# Parse YAML frontmatter (between --- markers)
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")

# Extract individual fields
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')

# Check if enabled
if [[ "$ENABLED" != "true" ]]; then
  exit 0  # Disabled
fi

# Use configuration in hook logic
if [[ "$STRICT_MODE" == "true" ]]; then
  # Apply strict validation
  # ...
fi

See examples/read-settings-hook.sh for complete working example.

From Commands

Commands can read settings files to customize behavior:

---
description: Process data with plugin
allowed-tools: ["Read", "Bash"]
---

# Process Command

Steps:
1. Check if settings exist at `.claude/my-plugin.local.md`
2. Read configuration using Read tool
3. Parse YAML frontmatter to extract settings
4. Apply settings to processing logic
5. Execute with configured behavior

From Agents

Agents can reference settings in their instructions:

---
name: configured-agent
description: Agent that adapts to project settings
---

Check for plugin settings at `.claude/my-plugin.local.md`.
If present, parse YAML frontmatter and adapt behavior according to:
- enabled: Whether plugin is active
- mode: Processing mode (strict, standard, lenient)
- Additional configuration fields

Parsing Techniques

Extract Frontmatter

# Extract everything between --- markers
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

Read Individual Fields

String fields:

VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')

Boolean fields:

ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
# Compare: if [[ "$ENABLED" == "true" ]]; then

Numeric fields:

MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
# Use: if [[ $MAX -gt 100 ]]; then

Read Markdown Body

Extract content after second ---:

# Get everything after closing ---
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")

Common Patterns

Pattern 1: Temporarily Active Hooks

Use settings file to control hook activation:

#!/bin/bash
STATE_FILE=".claude/security-scan.local.md"

# Quick exit if not configured
if [[ ! -f "$STATE_FILE" ]]; then
  exit 0
fi

# Read enabled flag
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')

if [[ "$ENABLED" != "true" ]]; then
  exit 0  # Disabled
fi

# Run hook logic
# ...

Use case: Enable/disable hooks without editing hooks.json (requires restart).

Pattern 2: Agent State Management

Store agent-specific state and configuration:

.claude/multi-agent-swarm.local.md:

---
agent_name: auth-agent
task_number: 3.5
pr_number: 1234
coordinator_session: team-leader
enabled: true
dependencies: ["Task 3.4"]
---

# Task Assignment

Implement JWT authentication for the API.

**Success Criteria:**
- Authentication endpoints created
- Tests passing
- PR created and CI green

Read from hooks to coordinate agents:

AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//')
COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')

# Send notification to coordinator
tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter

Pattern 3: Configuration-Driven Behavior

.claude/my-plugin.local.md:

---
validation_level: strict
max_file_size: 1000000
allowed_extensions: [".js", ".ts", ".tsx"]
enable_logging: true
---

# Validation Configuration

Strict mode enabled for this project.
All writes validated against security policies.

Use in hooks or commands:

LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//')

case "$LEVEL" in
  strict)
    # Apply strict validation
    ;;
  standard)
    # Apply standard validation
    ;;
  lenient)
    # Apply lenient validation
    ;;
esac

Creating Settings Files

From Commands

Commands can create settings files:

# Setup Command

Steps:
1. Ask user for configuration preferences
2. Create `.claude/my-plugin.local.md` with YAML frontmatter
3. Set appropriate values based on user input
4. Inform user that settings are saved
5. Remind user to restart Claude Code for hooks to recognize changes

Template Generation

Provide template in plugin README:

## Configuration

Create `.claude/my-plugin.local.md` in your project:

\`\`\`markdown
---
enabled: true
mode: standard
max_retries: 3
---

# Plugin Configuration

Your settings are active.
\`\`\`

After creating or editing, restart Claude Code for changes to take effect.

Best Practices

File Naming

✅ DO:

  • Use .claude/plugin-name.local.md format
  • Match plugin name exactly
  • Use .local.md suffix for user-local files

❌ DON'T:

  • Use different directory (not .claude/)
  • Use inconsistent naming
  • Use .md without .local (might be committed)

Gitignore

Always add to .gitignore:

.claude/*.local.md
.claude/*.local.json

Document this in plugin README.

Defaults

Provide sensible defaults when settings file doesn't exist:

if [[ ! -f "$STATE_FILE" ]]; then
  # Use defaults
  ENABLED=true
  MODE=standard
else
  # Read from file
  # ...
fi

Validation

Validate settings values:

MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')

# Validate numeric range
if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then
  echo "⚠️  Invalid max_value in settings (must be 1-100)" >&2
  MAX=10  # Use default
fi

Restart Requirement

Important: Settings changes require Claude Code restart.

Document in your README:

## Changing Settings

After editing `.claude/my-plugin.local.md`:
1. Save the file
2. Exit Claude Code
3. Restart: `claude` or `cc`
4. New settings will be loaded

Hooks cannot be hot-swapped within a session.

Security Considerations

Sanitize User Input

When writing settings files from user input:

# Escape quotes in user input
SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g')

# Write to file
cat > "$STATE_FILE" <<EOF
---
user_setting: "$SAFE_VALUE"
---
EOF

Validate File Paths

If settings contain file paths:

FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//')

# Check for path traversal
if [[ "$FILE_PATH" == *".."* ]]; then
  echo "⚠️  Invalid path in settings (path traversal)" >&2
  exit 2
fi

Permissions

Settings files should be:

  • Readable by user only (chmod 600)
  • Not committed to git
  • Not shared between users

Real-World Examples

multi-agent-swarm Plugin

.claude/multi-agent-swarm.local.md:

---
agent_name: auth-implementation
task_number: 3.5
pr_number: 1234
coordinator_session: team-leader
enabled: true
dependencies: ["Task 3.4"]
additional_instructions: Use JWT tokens, not sessions
---

# Task: Implement Authentication

Build JWT-based authentication for the REST API.
Coordinate with auth-agent on shared types.

Hook usage (agent-stop-notification.sh):

  • Checks if file exists (line 15-18: quick exit if not)
  • Parses frontmatter to get coordinator_session, agent_name, enabled
  • Sends notifications to coordinator if enabled
  • Allows quick activation/deactivation via enabled: true/false

ralph-wiggum Plugin

.claude/ralph-loop.local.md:

---
iteration: 1
max_iterations: 10
completion_promise: "All tests passing and build successful"
---

Fix all the linting errors in the project.
Make sure tests pass after each fix.

Hook usage (stop-hook.sh):

  • Checks if file exists (line 15-18: quick exit if not active)
  • Reads iteration count and max_iterations
  • Extracts completion_promise for loop termination
  • Reads body as the prompt to feed back
  • Updates iteration count on each loop

Quick Reference

File Location

project-root/
└── .claude/
    └── plugin-name.local.md

Frontmatter Parsing

# Extract frontmatter
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")

# Read field
VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')

Body Parsing

# Extract body (after second ---)
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")

Quick Exit Pattern

if [[ ! -f ".claude/my-plugin.local.md" ]]; then
  exit 0  # Not configured
fi

Additional Resources

Reference Files

For detailed implementation patterns:

  • references/parsing-techniques.md - Complete guide to parsing YAML frontmatter and markdown bodies
  • references/real-world-examples.md - Deep dive into multi-agent-swarm and ralph-wiggum implementations

Example Files

Working examples in examples/:

  • read-settings-hook.sh - Hook that reads and uses settings
  • create-settings-command.md - Command that creates settings file
  • example-settings.md - Template settings file

Utility Scripts

Development tools in scripts/:

  • validate-settings.sh - Validate settings file structure
  • parse-frontmatter.sh - Extract frontmatter fields

Implementation Workflow

To add settings to a plugin:

  1. Design settings schema (which fields, types, defaults)
  2. Create template file in plugin documentation
  3. Add gitignore entry for .claude/*.local.md
  4. Implement settings parsing in hooks/commands
  5. Use quick-exit pattern (check file exists, check enabled field)
  6. Document settings in plugin README with template
  7. Remind users that changes require Claude Code restart

Focus on keeping settings simple and providing good defaults when settings file doesn't exist.

Files (smart-ralph)
  • examples
    • create-settings-command.md 2.1 KB
      ---
      description: "Create plugin settings file with user preferences"
      allowed-tools: ["Write", "AskUserQuestion"]
      ---
      
      # Create Plugin Settings
      
      This command helps users create a `.claude/my-plugin.local.md` settings file.
      
      ## Steps
      
      ### Step 1: Ask User for Preferences
      
      Use AskUserQuestion to gather configuration:
      
      ```json
      {
        "questions": [
          {
            "question": "Enable plugin for this project?",
            "header": "Enable Plugin",
            "multiSelect": false,
            "options": [
              {
                "label": "Yes",
                "description": "Plugin will be active"
              },
              {
                "label": "No",
                "description": "Plugin will be disabled"
              }
            ]
          },
          {
            "question": "Validation mode?",
            "header": "Mode",
            "multiSelect": false,
            "options": [
              {
                "label": "Strict",
                "description": "Maximum validation and security checks"
              },
              {
                "label": "Standard",
                "description": "Balanced validation (recommended)"
              },
              {
                "label": "Lenient",
                "description": "Minimal validation only"
              }
            ]
          }
        ]
      }
      ```
      
      ### Step 2: Parse Answers
      
      Extract answers from AskUserQuestion result:
      
      - answers["0"]: enabled (Yes/No)
      - answers["1"]: mode (Strict/Standard/Lenient)
      
      ### Step 3: Create Settings File
      
      Use Write tool to create `.claude/my-plugin.local.md`:
      
      ```markdown
      ---
      enabled: <true if Yes, false if No>
      validation_mode: <strict, standard, or lenient>
      max_file_size: 1000000
      notify_on_errors: true
      ---
      
      # Plugin Configuration
      
      Your plugin is configured with <mode> validation mode.
      
      To modify settings, edit this file and restart Claude Code.
      ```
      
      ### Step 4: Inform User
      
      Tell the user:
      - Settings file created at `.claude/my-plugin.local.md`
      - Current configuration summary
      - How to edit manually if needed
      - Reminder: Restart Claude Code for changes to take effect
      - Settings file is gitignored (won't be committed)
      
      ## Implementation Notes
      
      Always validate user input before writing:
      - Check mode is valid
      - Validate numeric fields are numbers
      - Ensure paths don't have traversal attempts
      - Sanitize any free-text fields
      
    • example-settings.md 2.9 KB
      # Example Plugin Settings File
      
      ## Template: Basic Configuration
      
      **.claude/my-plugin.local.md:**
      
      ```markdown
      ---
      enabled: true
      mode: standard
      ---
      
      # My Plugin Configuration
      
      Plugin is active in standard mode.
      ```
      
      ## Template: Advanced Configuration
      
      **.claude/my-plugin.local.md:**
      
      ```markdown
      ---
      enabled: true
      strict_mode: false
      max_file_size: 1000000
      allowed_extensions: [".js", ".ts", ".tsx"]
      enable_logging: true
      notification_level: info
      retry_attempts: 3
      timeout_seconds: 60
      custom_path: "/path/to/data"
      ---
      
      # My Plugin Advanced Configuration
      
      This project uses custom plugin configuration with:
      - Standard validation mode
      - 1MB file size limit
      - JavaScript/TypeScript files allowed
      - Info-level logging
      - 3 retry attempts
      
      ## Additional Notes
      
      Contact @team-lead with questions about this configuration.
      ```
      
      ## Template: Agent State File
      
      **.claude/multi-agent-swarm.local.md:**
      
      ```markdown
      ---
      agent_name: database-implementation
      task_number: 4.2
      pr_number: 5678
      coordinator_session: team-leader
      enabled: true
      dependencies: ["Task 3.5", "Task 4.1"]
      additional_instructions: "Use PostgreSQL, not MySQL"
      ---
      
      # Task Assignment: Database Schema Implementation
      
      Implement the database schema for the new features module.
      
      ## Requirements
      
      - Create migration files
      - Add indexes for performance
      - Write tests for constraints
      - Document schema in README
      
      ## Success Criteria
      
      - Migrations run successfully
      - All tests pass
      - PR created with CI green
      - Schema documented
      
      ## Coordination
      
      Depends on:
      - Task 3.5: API endpoint definitions
      - Task 4.1: Data model design
      
      Report status to coordinator session 'team-leader'.
      ```
      
      ## Template: Feature Flag Pattern
      
      **.claude/experimental-features.local.md:**
      
      ```markdown
      ---
      enabled: true
      features:
        - ai_suggestions
        - auto_formatting
        - advanced_refactoring
      experimental_mode: false
      ---
      
      # Experimental Features Configuration
      
      Current enabled features:
      - AI-powered code suggestions
      - Automatic code formatting
      - Advanced refactoring tools
      
      Experimental mode is OFF (stable features only).
      ```
      
      ## Usage in Hooks
      
      These templates can be read by hooks:
      
      ```bash
      # Check if plugin is configured
      if [[ ! -f ".claude/my-plugin.local.md" ]]; then
        exit 0  # Not configured, skip hook
      fi
      
      # Read settings
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' ".claude/my-plugin.local.md")
      ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
      
      # Apply settings
      if [[ "$ENABLED" == "true" ]]; then
        # Hook is active
        # ...
      fi
      ```
      
      ## Gitignore
      
      Always add to project `.gitignore`:
      
      ```gitignore
      # Plugin settings (user-local, not committed)
      .claude/*.local.md
      .claude/*.local.json
      ```
      
      ## Editing Settings
      
      Users can edit settings files manually:
      
      ```bash
      # Edit settings
      vim .claude/my-plugin.local.md
      
      # Changes take effect after restart
      exit  # Exit Claude Code
      claude  # Restart
      ```
      
      Changes require Claude Code restart - hooks can't be hot-swapped.
      
    • read-settings-hook.sh 2.2 KB
      #!/bin/bash
      # Example hook that reads plugin settings from .claude/my-plugin.local.md
      # Demonstrates the complete pattern for settings-driven hook behavior
      
      set -euo pipefail
      
      # Define settings file path
      SETTINGS_FILE=".claude/my-plugin.local.md"
      
      # Quick exit if settings file doesn't exist
      if [[ ! -f "$SETTINGS_FILE" ]]; then
        # Plugin not configured - use defaults or skip
        exit 0
      fi
      
      # Parse YAML frontmatter (everything between --- markers)
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE")
      
      # Extract configuration fields
      ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
      STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')
      MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_file_size:' | sed 's/max_file_size: *//')
      
      # Quick exit if disabled
      if [[ "$ENABLED" != "true" ]]; then
        exit 0
      fi
      
      # Read hook input
      input=$(cat)
      file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
      
      # Apply configured validation
      if [[ "$STRICT_MODE" == "true" ]]; then
        # Strict mode: apply all checks
        if [[ "$file_path" == *".."* ]]; then
          echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Path traversal blocked (strict mode)"}' >&2
          exit 2
        fi
      
        if [[ "$file_path" == *".env"* ]] || [[ "$file_path" == *"secret"* ]]; then
          echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Sensitive file blocked (strict mode)"}' >&2
          exit 2
        fi
      else
        # Standard mode: basic checks only
        if [[ "$file_path" == "/etc/"* ]] || [[ "$file_path" == "/sys/"* ]]; then
          echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "System path blocked"}' >&2
          exit 2
        fi
      fi
      
      # Check file size if configured
      if [[ -n "$MAX_SIZE" ]] && [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
        content=$(echo "$input" | jq -r '.tool_input.content // empty')
        content_size=${#content}
      
        if [[ $content_size -gt $MAX_SIZE ]]; then
          echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "File exceeds configured max size: '"$MAX_SIZE"' bytes"}' >&2
          exit 2
        fi
      fi
      
      # All checks passed
      exit 0
      
  • references
    • parsing-techniques.md 11.2 KB
      # Settings File Parsing Techniques
      
      Complete guide to parsing `.claude/plugin-name.local.md` files in bash scripts.
      
      ## File Structure
      
      Settings files use markdown with YAML frontmatter:
      
      ```markdown
      ---
      field1: value1
      field2: "value with spaces"
      numeric_field: 42
      boolean_field: true
      list_field: ["item1", "item2", "item3"]
      ---
      
      # Markdown Content
      
      This body content can be extracted separately.
      It's useful for prompts, documentation, or additional context.
      ```
      
      ## Parsing Frontmatter
      
      ### Extract Frontmatter Block
      
      ```bash
      #!/bin/bash
      FILE=".claude/my-plugin.local.md"
      
      # Extract everything between --- markers (excluding the markers themselves)
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
      ```
      
      **How it works:**
      - `sed -n` - Suppress automatic printing
      - `/^---$/,/^---$/` - Range from first `---` to second `---`
      - `{ /^---$/d; p; }` - Delete the `---` lines, print everything else
      
      ### Extract Individual Fields
      
      **String fields:**
      ```bash
      # Simple value
      VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//')
      
      # Quoted value (removes surrounding quotes)
      VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')
      ```
      
      **Boolean fields:**
      ```bash
      ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
      
      # Use in condition
      if [[ "$ENABLED" == "true" ]]; then
        # Enabled
      fi
      ```
      
      **Numeric fields:**
      ```bash
      MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
      
      # Validate it's a number
      if [[ "$MAX" =~ ^[0-9]+$ ]]; then
        # Use in numeric comparison
        if [[ $MAX -gt 100 ]]; then
          # Too large
        fi
      fi
      ```
      
      **List fields (simple):**
      ```bash
      # YAML: list: ["item1", "item2", "item3"]
      LIST=$(echo "$FRONTMATTER" | grep '^list:' | sed 's/list: *//')
      # Result: ["item1", "item2", "item3"]
      
      # For simple checks:
      if [[ "$LIST" == *"item1"* ]]; then
        # List contains item1
      fi
      ```
      
      **List fields (proper parsing with jq):**
      ```bash
      # For proper list handling, use yq or convert to JSON
      # This requires yq to be installed (brew install yq)
      
      # Extract list as JSON array
      LIST=$(echo "$FRONTMATTER" | yq -o json '.list' 2>/dev/null)
      
      # Iterate over items
      echo "$LIST" | jq -r '.[]' | while read -r item; do
        echo "Processing: $item"
      done
      ```
      
      ## Parsing Markdown Body
      
      ### Extract Body Content
      
      ```bash
      #!/bin/bash
      FILE=".claude/my-plugin.local.md"
      
      # Extract everything after the closing ---
      # Counts --- markers: first is opening, second is closing, everything after is body
      BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
      ```
      
      **How it works:**
      - `/^---$/` - Match `---` lines
      - `{i++; next}` - Increment counter and skip the `---` line
      - `i>=2` - Print all lines after second `---`
      
      **Handles edge case:** If `---` appears in the markdown body, it still works because we only count the first two `---` at the start.
      
      ### Use Body as Prompt
      
      ```bash
      # Extract body
      PROMPT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")
      
      # Feed back to Claude
      echo '{"decision": "block", "reason": "'"$PROMPT"'"}' | jq .
      ```
      
      **Important:** Use `jq -n --arg` for safer JSON construction with user content:
      
      ```bash
      PROMPT=$(awk '/^---$/{i++; next} i>=2' "$FILE")
      
      # Safe JSON construction
      jq -n --arg prompt "$PROMPT" '{
        "decision": "block",
        "reason": $prompt
      }'
      ```
      
      ## Common Parsing Patterns
      
      ### Pattern: Field with Default
      
      ```bash
      VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')
      
      # Use default if empty
      if [[ -z "$VALUE" ]]; then
        VALUE="default_value"
      fi
      ```
      
      ### Pattern: Optional Field
      
      ```bash
      OPTIONAL=$(echo "$FRONTMATTER" | grep '^optional_field:' | sed 's/optional_field: *//' | sed 's/^"\(.*\)"$/\1/')
      
      # Only use if present
      if [[ -n "$OPTIONAL" ]] && [[ "$OPTIONAL" != "null" ]]; then
        # Field is set, use it
        echo "Optional field: $OPTIONAL"
      fi
      ```
      
      ### Pattern: Multiple Fields at Once
      
      ```bash
      # Parse all fields in one pass
      while IFS=': ' read -r key value; do
        # Remove quotes if present
        value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/')
      
        case "$key" in
          enabled)
            ENABLED="$value"
            ;;
          mode)
            MODE="$value"
            ;;
          max_size)
            MAX_SIZE="$value"
            ;;
        esac
      done <<< "$FRONTMATTER"
      ```
      
      ## Updating Settings Files
      
      ### Atomic Updates
      
      Always use temp file + atomic move to prevent corruption:
      
      ```bash
      #!/bin/bash
      FILE=".claude/my-plugin.local.md"
      NEW_VALUE="updated_value"
      
      # Create temp file
      TEMP_FILE="${FILE}.tmp.$$"
      
      # Update field using sed
      sed "s/^field_name: .*/field_name: $NEW_VALUE/" "$FILE" > "$TEMP_FILE"
      
      # Atomic replace
      mv "$TEMP_FILE" "$FILE"
      ```
      
      ### Update Single Field
      
      ```bash
      # Increment iteration counter
      CURRENT=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
      NEXT=$((CURRENT + 1))
      
      # Update file
      TEMP_FILE="${FILE}.tmp.$$"
      sed "s/^iteration: .*/iteration: $NEXT/" "$FILE" > "$TEMP_FILE"
      mv "$TEMP_FILE" "$FILE"
      ```
      
      ### Update Multiple Fields
      
      ```bash
      # Update several fields at once
      TEMP_FILE="${FILE}.tmp.$$"
      
      sed -e "s/^iteration: .*/iteration: $NEXT_ITERATION/" \
          -e "s/^pr_number: .*/pr_number: $PR_NUMBER/" \
          -e "s/^status: .*/status: $NEW_STATUS/" \
          "$FILE" > "$TEMP_FILE"
      
      mv "$TEMP_FILE" "$FILE"
      ```
      
      ## Validation Techniques
      
      ### Validate File Exists and Is Readable
      
      ```bash
      FILE=".claude/my-plugin.local.md"
      
      if [[ ! -f "$FILE" ]]; then
        echo "Settings file not found" >&2
        exit 1
      fi
      
      if [[ ! -r "$FILE" ]]; then
        echo "Settings file not readable" >&2
        exit 1
      fi
      ```
      
      ### Validate Frontmatter Structure
      
      ```bash
      # Count --- markers (should be exactly 2 at start)
      MARKER_COUNT=$(grep -c '^---$' "$FILE" 2>/dev/null || echo "0")
      
      if [[ $MARKER_COUNT -lt 2 ]]; then
        echo "Invalid settings file: missing frontmatter markers" >&2
        exit 1
      fi
      ```
      
      ### Validate Field Values
      
      ```bash
      MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//')
      
      case "$MODE" in
        strict|standard|lenient)
          # Valid mode
          ;;
        *)
          echo "Invalid mode: $MODE (must be strict, standard, or lenient)" >&2
          exit 1
          ;;
      esac
      ```
      
      ### Validate Numeric Ranges
      
      ```bash
      MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')
      
      if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
        echo "max_size must be a number" >&2
        exit 1
      fi
      
      if [[ $MAX_SIZE -lt 1 ]] || [[ $MAX_SIZE -gt 10000000 ]]; then
        echo "max_size out of range (1-10000000)" >&2
        exit 1
      fi
      ```
      
      ## Edge Cases and Gotchas
      
      ### Quotes in Values
      
      YAML allows both quoted and unquoted strings:
      
      ```yaml
      # These are equivalent:
      field1: value
      field2: "value"
      field3: 'value'
      ```
      
      **Handle both:**
      ```bash
      # Remove surrounding quotes if present
      VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\\(.*\\)'$/\\1/")
      ```
      
      ### --- in Markdown Body
      
      If the markdown body contains `---`, the parsing still works because we only match the first two:
      
      ```markdown
      ---
      field: value
      ---
      
      # Body
      
      Here's a separator:
      ---
      
      More content after the separator.
      ```
      
      The `awk '/^---$/{i++; next} i>=2'` pattern handles this correctly.
      
      ### Empty Values
      
      Handle missing or empty fields:
      
      ```yaml
      field1:
      field2: ""
      field3: null
      ```
      
      **Parsing:**
      ```bash
      VALUE=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')
      # VALUE will be empty string
      
      # Check for empty/null
      if [[ -z "$VALUE" ]] || [[ "$VALUE" == "null" ]]; then
        VALUE="default"
      fi
      ```
      
      ### Special Characters
      
      Values with special characters need careful handling:
      
      ```yaml
      message: "Error: Something went wrong!"
      path: "/path/with spaces/file.txt"
      regex: "^[a-zA-Z0-9_]+$"
      ```
      
      **Safe parsing:**
      ```bash
      # Always quote variables when using
      MESSAGE=$(echo "$FRONTMATTER" | grep '^message:' | sed 's/message: *//' | sed 's/^"\(.*\)"$/\1/')
      
      echo "Message: $MESSAGE"  # Quoted!
      ```
      
      ## Performance Optimization
      
      ### Cache Parsed Values
      
      If reading settings multiple times:
      
      ```bash
      # Parse once
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
      
      # Extract multiple fields from cached frontmatter
      FIELD1=$(echo "$FRONTMATTER" | grep '^field1:' | sed 's/field1: *//')
      FIELD2=$(echo "$FRONTMATTER" | grep '^field2:' | sed 's/field2: *//')
      FIELD3=$(echo "$FRONTMATTER" | grep '^field3:' | sed 's/field3: *//')
      ```
      
      **Don't:** Re-parse file for each field.
      
      ### Lazy Loading
      
      Only parse settings when needed:
      
      ```bash
      #!/bin/bash
      input=$(cat)
      
      # Quick checks first (no file I/O)
      tool_name=$(echo "$input" | jq -r '.tool_name')
      if [[ "$tool_name" != "Write" ]]; then
        exit 0  # Not a write operation, skip
      fi
      
      # Only now check settings file
      if [[ -f ".claude/my-plugin.local.md" ]]; then
        # Parse settings
        # ...
      fi
      ```
      
      ## Debugging
      
      ### Print Parsed Values
      
      ```bash
      #!/bin/bash
      set -x  # Enable debug tracing
      
      FILE=".claude/my-plugin.local.md"
      
      if [[ -f "$FILE" ]]; then
        echo "Settings file found" >&2
      
        FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
        echo "Frontmatter:" >&2
        echo "$FRONTMATTER" >&2
      
        ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
        echo "Enabled: $ENABLED" >&2
      fi
      ```
      
      ### Validate Parsing
      
      ```bash
      # Show what was parsed
      echo "Parsed values:" >&2
      echo "  enabled: $ENABLED" >&2
      echo "  mode: $MODE" >&2
      echo "  max_size: $MAX_SIZE" >&2
      
      # Verify expected values
      if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then
        echo "⚠️  Unexpected enabled value: $ENABLED" >&2
      fi
      ```
      
      ## Alternative: Using yq
      
      For complex YAML, consider using `yq`:
      
      ```bash
      # Install: brew install yq
      
      # Parse YAML properly
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
      
      # Extract fields with yq
      ENABLED=$(echo "$FRONTMATTER" | yq '.enabled')
      MODE=$(echo "$FRONTMATTER" | yq '.mode')
      LIST=$(echo "$FRONTMATTER" | yq -o json '.list_field')
      
      # Iterate list properly
      echo "$LIST" | jq -r '.[]' | while read -r item; do
        echo "Item: $item"
      done
      ```
      
      **Pros:**
      - Proper YAML parsing
      - Handles complex structures
      - Better list/object support
      
      **Cons:**
      - Requires yq installation
      - Additional dependency
      - May not be available on all systems
      
      **Recommendation:** Use sed/grep for simple fields, yq for complex structures.
      
      ## Complete Example
      
      ```bash
      #!/bin/bash
      set -euo pipefail
      
      # Configuration
      SETTINGS_FILE=".claude/my-plugin.local.md"
      
      # Quick exit if not configured
      if [[ ! -f "$SETTINGS_FILE" ]]; then
        # Use defaults
        ENABLED=true
        MODE=standard
        MAX_SIZE=1000000
      else
        # Parse frontmatter
        FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE")
      
        # Extract fields with defaults
        ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
        ENABLED=${ENABLED:-true}
      
        MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//' | sed 's/^"\(.*\)"$/\1/')
        MODE=${MODE:-standard}
      
        MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_size:' | sed 's/max_size: *//')
        MAX_SIZE=${MAX_SIZE:-1000000}
      
        # Validate values
        if [[ "$ENABLED" != "true" ]] && [[ "$ENABLED" != "false" ]]; then
          echo "⚠️  Invalid enabled value, using default" >&2
          ENABLED=true
        fi
      
        if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
          echo "⚠️  Invalid max_size, using default" >&2
          MAX_SIZE=1000000
        fi
      fi
      
      # Quick exit if disabled
      if [[ "$ENABLED" != "true" ]]; then
        exit 0
      fi
      
      # Use configuration
      echo "Configuration loaded: mode=$MODE, max_size=$MAX_SIZE" >&2
      
      # Apply logic based on settings
      case "$MODE" in
        strict)
          # Strict validation
          ;;
        standard)
          # Standard validation
          ;;
        lenient)
          # Lenient validation
          ;;
      esac
      ```
      
      This provides robust settings handling with defaults, validation, and error recovery.
      
    • real-world-examples.md 9.3 KB
      # Real-World Plugin Settings Examples
      
      Detailed analysis of how production plugins use the `.claude/plugin-name.local.md` pattern.
      
      ## multi-agent-swarm Plugin
      
      ### Settings File Structure
      
      **.claude/multi-agent-swarm.local.md:**
      
      ```markdown
      ---
      agent_name: auth-implementation
      task_number: 3.5
      pr_number: 1234
      coordinator_session: team-leader
      enabled: true
      dependencies: ["Task 3.4"]
      additional_instructions: "Use JWT tokens, not sessions"
      ---
      
      # Task: Implement Authentication
      
      Build JWT-based authentication for the REST API.
      
      ## Requirements
      - JWT token generation and validation
      - Refresh token flow
      - Secure password hashing
      
      ## Success Criteria
      - Auth endpoints implemented
      - Tests passing (100% coverage)
      - PR created and CI green
      - Documentation updated
      
      ## Coordination
      Depends on Task 3.4 (user model).
      Report status to 'team-leader' session.
      ```
      
      ### How It's Used
      
      **File:** `hooks/agent-stop-notification.sh`
      
      **Purpose:** Send notifications to coordinator when agent becomes idle
      
      **Implementation:**
      
      ```bash
      #!/bin/bash
      set -euo pipefail
      
      SWARM_STATE_FILE=".claude/multi-agent-swarm.local.md"
      
      # Quick exit if no swarm active
      if [[ ! -f "$SWARM_STATE_FILE" ]]; then
        exit 0
      fi
      
      # Parse frontmatter
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SWARM_STATE_FILE")
      
      # Extract configuration
      COORDINATOR_SESSION=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//' | sed 's/^"\(.*\)"$/\1/')
      AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//' | sed 's/^"\(.*\)"$/\1/')
      TASK_NUMBER=$(echo "$FRONTMATTER" | grep '^task_number:' | sed 's/task_number: *//' | sed 's/^"\(.*\)"$/\1/')
      PR_NUMBER=$(echo "$FRONTMATTER" | grep '^pr_number:' | sed 's/pr_number: *//' | sed 's/^"\(.*\)"$/\1/')
      ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
      
      # Check if enabled
      if [[ "$ENABLED" != "true" ]]; then
        exit 0
      fi
      
      # Send notification to coordinator
      NOTIFICATION="🤖 Agent ${AGENT_NAME} (Task ${TASK_NUMBER}, PR #${PR_NUMBER}) is idle."
      
      if tmux has-session -t "$COORDINATOR_SESSION" 2>/dev/null; then
        tmux send-keys -t "$COORDINATOR_SESSION" "$NOTIFICATION" Enter
        sleep 0.5
        tmux send-keys -t "$COORDINATOR_SESSION" Enter
      fi
      
      exit 0
      ```
      
      **Key patterns:**
      1. **Quick exit** (line 7-9): Returns immediately if file doesn't exist
      2. **Field extraction** (lines 11-17): Parses each frontmatter field
      3. **Enabled check** (lines 19-21): Respects enabled flag
      4. **Action based on settings** (lines 23-29): Uses coordinator_session to send notification
      
      ### Creation
      
      **File:** `commands/launch-swarm.md`
      
      Settings files are created during swarm launch with:
      
      ```bash
      cat > "$WORKTREE_PATH/.claude/multi-agent-swarm.local.md" <<EOF
      ---
      agent_name: $AGENT_NAME
      task_number: $TASK_ID
      pr_number: TBD
      coordinator_session: $COORDINATOR_SESSION
      enabled: true
      dependencies: [$DEPENDENCIES]
      additional_instructions: "$EXTRA_INSTRUCTIONS"
      ---
      
      # Task: $TASK_DESCRIPTION
      
      $TASK_DETAILS
      EOF
      ```
      
      ### Updates
      
      PR number updated after PR creation:
      
      ```bash
      # Update pr_number field
      sed "s/^pr_number: .*/pr_number: $PR_NUM/" \
        ".claude/multi-agent-swarm.local.md" > temp.md
      mv temp.md ".claude/multi-agent-swarm.local.md"
      ```
      
      ## ralph-wiggum Plugin
      
      ### Settings File Structure
      
      **.claude/ralph-loop.local.md:**
      
      ```markdown
      ---
      iteration: 1
      max_iterations: 10
      completion_promise: "All tests passing and build successful"
      started_at: "2025-01-15T14:30:00Z"
      ---
      
      Fix all the linting errors in the project.
      Make sure tests pass after each fix.
      Document any changes needed in CLAUDE.md.
      ```
      
      ### How It's Used
      
      **File:** `hooks/stop-hook.sh`
      
      **Purpose:** Prevent session exit and loop Claude's output back as input
      
      **Implementation:**
      
      ```bash
      #!/bin/bash
      set -euo pipefail
      
      RALPH_STATE_FILE=".claude/ralph-loop.local.md"
      
      # Quick exit if no active loop
      if [[ ! -f "$RALPH_STATE_FILE" ]]; then
        exit 0
      fi
      
      # Parse frontmatter
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$RALPH_STATE_FILE")
      
      # Extract configuration
      ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
      MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')
      COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
      
      # Check max iterations
      if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
        echo "🛑 Ralph loop: Max iterations ($MAX_ITERATIONS) reached."
        rm "$RALPH_STATE_FILE"
        exit 0
      fi
      
      # Get transcript and check for completion promise
      TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path')
      LAST_OUTPUT=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1 | jq -r '.message.content | map(select(.type == "text")) | map(.text) | join("\n")')
      
      # Check for completion
      if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then
        PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g')
      
        if [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
          echo "✅ Ralph loop: Detected completion"
          rm "$RALPH_STATE_FILE"
          exit 0
        fi
      fi
      
      # Continue loop - increment iteration
      NEXT_ITERATION=$((ITERATION + 1))
      
      # Extract prompt from markdown body
      PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")
      
      # Update iteration counter
      TEMP_FILE="${RALPH_STATE_FILE}.tmp.$$"
      sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$RALPH_STATE_FILE" > "$TEMP_FILE"
      mv "$TEMP_FILE" "$RALPH_STATE_FILE"
      
      # Block exit and feed prompt back
      jq -n \
        --arg prompt "$PROMPT_TEXT" \
        --arg msg "🔄 Ralph iteration $NEXT_ITERATION" \
        '{
          "decision": "block",
          "reason": $prompt,
          "systemMessage": $msg
        }'
      
      exit 0
      ```
      
      **Key patterns:**
      1. **Quick exit** (line 7-9): Skip if not active
      2. **Iteration tracking** (lines 11-20): Count and enforce max iterations
      3. **Promise detection** (lines 25-33): Check for completion signal in output
      4. **Prompt extraction** (line 38): Read markdown body as next prompt
      5. **State update** (lines 40-43): Increment iteration atomically
      6. **Loop continuation** (lines 45-53): Block exit and feed prompt back
      
      ### Creation
      
      **File:** `scripts/setup-ralph-loop.sh`
      
      ```bash
      #!/bin/bash
      PROMPT="$1"
      MAX_ITERATIONS="${2:-0}"
      COMPLETION_PROMISE="${3:-}"
      
      # Create state file
      cat > ".claude/ralph-loop.local.md" <<EOF
      ---
      iteration: 1
      max_iterations: $MAX_ITERATIONS
      completion_promise: "$COMPLETION_PROMISE"
      started_at: "$(date -Iseconds)"
      ---
      
      $PROMPT
      EOF
      
      echo "Ralph loop initialized: .claude/ralph-loop.local.md"
      ```
      
      ## Pattern Comparison
      
      | Feature | multi-agent-swarm | ralph-wiggum |
      |---------|-------------------|--------------|
      | **File** | `.claude/multi-agent-swarm.local.md` | `.claude/ralph-loop.local.md` |
      | **Purpose** | Agent coordination state | Loop iteration state |
      | **Frontmatter** | Agent metadata | Loop configuration |
      | **Body** | Task assignment | Prompt to loop |
      | **Updates** | PR number, status | Iteration counter |
      | **Deletion** | Manual or on completion | On loop exit |
      | **Hook** | Stop (notifications) | Stop (loop control) |
      
      ## Best Practices from Real Plugins
      
      ### 1. Quick Exit Pattern
      
      Both plugins check file existence first:
      
      ```bash
      if [[ ! -f "$STATE_FILE" ]]; then
        exit 0  # Not active
      fi
      ```
      
      **Why:** Avoids errors when plugin isn't configured and performs fast.
      
      ### 2. Enabled Flag
      
      Both use an `enabled` field for explicit control:
      
      ```yaml
      enabled: true
      ```
      
      **Why:** Allows temporary deactivation without deleting file.
      
      ### 3. Atomic Updates
      
      Both use temp file + atomic move:
      
      ```bash
      TEMP_FILE="${FILE}.tmp.$$"
      sed "s/^field: .*/field: $NEW_VALUE/" "$FILE" > "$TEMP_FILE"
      mv "$TEMP_FILE" "$FILE"
      ```
      
      **Why:** Prevents corruption if process is interrupted.
      
      ### 4. Quote Handling
      
      Both strip surrounding quotes from YAML values:
      
      ```bash
      sed 's/^"\(.*\)"$/\1/'
      ```
      
      **Why:** YAML allows both `field: value` and `field: "value"`.
      
      ### 5. Error Handling
      
      Both handle missing/corrupt files gracefully:
      
      ```bash
      if [[ ! -f "$FILE" ]]; then
        exit 0  # No error, just not configured
      fi
      
      if [[ -z "$CRITICAL_FIELD" ]]; then
        echo "Settings file corrupt" >&2
        rm "$FILE"  # Clean up
        exit 0
      fi
      ```
      
      **Why:** Fails gracefully instead of crashing.
      
      ## Anti-Patterns to Avoid
      
      ### ❌ Hardcoded Paths
      
      ```bash
      # BAD
      FILE="/Users/alice/.claude/my-plugin.local.md"
      
      # GOOD
      FILE=".claude/my-plugin.local.md"
      ```
      
      ### ❌ Unquoted Variables
      
      ```bash
      # BAD
      echo $VALUE
      
      # GOOD
      echo "$VALUE"
      ```
      
      ### ❌ Non-Atomic Updates
      
      ```bash
      # BAD: Can corrupt file if interrupted
      sed -i "s/field: .*/field: $VALUE/" "$FILE"
      
      # GOOD: Atomic
      TEMP_FILE="${FILE}.tmp.$$"
      sed "s/field: .*/field: $VALUE/" "$FILE" > "$TEMP_FILE"
      mv "$TEMP_FILE" "$FILE"
      ```
      
      ### ❌ No Default Values
      
      ```bash
      # BAD: Fails if field missing
      if [[ $MAX -gt 100 ]]; then
        # MAX might be empty!
      fi
      
      # GOOD: Provide default
      MAX=${MAX:-10}
      ```
      
      ### ❌ Ignoring Edge Cases
      
      ```bash
      # BAD: Assumes exactly 2 --- markers
      sed -n '/^---$/,/^---$/{ /^---$/d; p; }'
      
      # GOOD: Handles --- in body
      awk '/^---$/{i++; next} i>=2'  # For body
      ```
      
      ## Conclusion
      
      The `.claude/plugin-name.local.md` pattern provides:
      - Simple, human-readable configuration
      - Version-control friendly (gitignored)
      - Per-project settings
      - Easy parsing with standard bash tools
      - Supports both structured config (YAML) and freeform content (markdown)
      
      Use this pattern for any plugin that needs user-configurable behavior or state persistence.
      
  • scripts
    • parse-frontmatter.sh 1.2 KB
      #!/bin/bash
      # Frontmatter Parser Utility
      # Extracts YAML frontmatter from .local.md files
      
      set -euo pipefail
      
      # Usage
      show_usage() {
        echo "Usage: $0 <settings-file.md> [field-name]"
        echo ""
        echo "Examples:"
        echo "  # Show all frontmatter"
        echo "  $0 .claude/my-plugin.local.md"
        echo ""
        echo "  # Extract specific field"
        echo "  $0 .claude/my-plugin.local.md enabled"
        echo ""
        echo "  # Extract and use in script"
        echo "  ENABLED=\$($0 .claude/my-plugin.local.md enabled)"
        exit 0
      }
      
      if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
        show_usage
      fi
      
      FILE="$1"
      FIELD="${2:-}"
      
      # Validate file
      if [ ! -f "$FILE" ]; then
        echo "Error: File not found: $FILE" >&2
        exit 1
      fi
      
      # Extract frontmatter
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
      
      if [ -z "$FRONTMATTER" ]; then
        echo "Error: No frontmatter found in $FILE" >&2
        exit 1
      fi
      
      # If no field specified, output all frontmatter
      if [ -z "$FIELD" ]; then
        echo "$FRONTMATTER"
        exit 0
      fi
      
      # Extract specific field
      VALUE=$(echo "$FRONTMATTER" | grep "^${FIELD}:" | sed "s/${FIELD}: *//" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\\(.*\\)'$/\\1/")
      
      if [ -z "$VALUE" ]; then
        echo "Error: Field '$FIELD' not found in frontmatter" >&2
        exit 1
      fi
      
      echo "$VALUE"
      exit 0
      
    • validate-settings.sh 2.6 KB
      #!/bin/bash
      # Settings File Validator
      # Validates .claude/plugin-name.local.md structure
      
      set -euo pipefail
      
      # Usage
      if [ $# -eq 0 ]; then
        echo "Usage: $0 <path/to/settings.local.md>"
        echo ""
        echo "Validates plugin settings file for:"
        echo "  - File existence and readability"
        echo "  - YAML frontmatter structure"
        echo "  - Required --- markers"
        echo "  - Field format"
        echo ""
        echo "Example: $0 .claude/my-plugin.local.md"
        exit 1
      fi
      
      SETTINGS_FILE="$1"
      
      echo "🔍 Validating settings file: $SETTINGS_FILE"
      echo ""
      
      # Check 1: File exists
      if [ ! -f "$SETTINGS_FILE" ]; then
        echo "❌ File not found: $SETTINGS_FILE"
        exit 1
      fi
      echo "✅ File exists"
      
      # Check 2: File is readable
      if [ ! -r "$SETTINGS_FILE" ]; then
        echo "❌ File is not readable"
        exit 1
      fi
      echo "✅ File is readable"
      
      # Check 3: Has frontmatter markers
      MARKER_COUNT=$(grep -c '^---$' "$SETTINGS_FILE" 2>/dev/null || echo "0")
      
      if [ "$MARKER_COUNT" -lt 2 ]; then
        echo "❌ Invalid frontmatter: found $MARKER_COUNT '---' markers (need at least 2)"
        echo "   Expected format:"
        echo "   ---"
        echo "   field: value"
        echo "   ---"
        echo "   Content..."
        exit 1
      fi
      echo "✅ Frontmatter markers present"
      
      # Check 4: Extract and validate frontmatter
      FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE")
      
      if [ -z "$FRONTMATTER" ]; then
        echo "❌ Empty frontmatter (nothing between --- markers)"
        exit 1
      fi
      echo "✅ Frontmatter not empty"
      
      # Check 5: Frontmatter has valid YAML-like structure
      if ! echo "$FRONTMATTER" | grep -q ':'; then
        echo "⚠️  Warning: Frontmatter has no key:value pairs"
      fi
      
      # Check 6: Look for common fields
      echo ""
      echo "Detected fields:"
      echo "$FRONTMATTER" | grep '^[a-z_][a-z0-9_]*:' | while IFS=':' read -r key value; do
        echo "  - $key: ${value:0:50}"
      done
      
      # Check 7: Validate common boolean fields
      for field in enabled strict_mode; do
        VALUE=$(echo "$FRONTMATTER" | grep "^${field}:" | sed "s/${field}: *//" || true)
        if [ -n "$VALUE" ]; then
          if [ "$VALUE" != "true" ] && [ "$VALUE" != "false" ]; then
            echo "⚠️  Field '$field' should be boolean (true/false), got: $VALUE"
          fi
        fi
      done
      
      # Check 8: Check body exists
      BODY=$(awk '/^---$/{i++; next} i>=2' "$SETTINGS_FILE")
      
      echo ""
      if [ -n "$BODY" ]; then
        BODY_LINES=$(echo "$BODY" | wc -l | tr -d ' ')
        echo "✅ Markdown body present ($BODY_LINES lines)"
      else
        echo "⚠️  No markdown body (frontmatter only)"
      fi
      
      echo ""
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "✅ Settings file structure is valid"
      echo ""
      echo "Reminder: Changes to this file require restarting Claude Code"
      exit 0
      
  • SKILL.md 11.8 KB
    ---
    name: Plugin Settings
    description: This skill should be used when the user asks about "plugin settings", "store plugin configuration", "user-configurable plugin", ".local.md files", "plugin state files", "read YAML frontmatter", "per-project plugin settings", or wants to make plugin behavior configurable. Documents the .claude/plugin-name.local.md pattern for storing plugin-specific configuration with YAML frontmatter and markdown content.
    version: 0.1.0
    ---
    
    # Plugin Settings Pattern for Claude Code Plugins
    
    ## Overview
    
    Plugins can store user-configurable settings and state in `.claude/plugin-name.local.md` files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.
    
    **Key characteristics:**
    - File location: `.claude/plugin-name.local.md` in project root
    - Structure: YAML frontmatter + markdown body
    - Purpose: Per-project plugin configuration and state
    - Usage: Read from hooks, commands, and agents
    - Lifecycle: User-managed (not in git, should be in `.gitignore`)
    
    ## File Structure
    
    ### Basic Template
    
    ```markdown
    ---
    enabled: true
    setting1: value1
    setting2: value2
    numeric_setting: 42
    list_setting: ["item1", "item2"]
    ---
    
    # Additional Context
    
    This markdown body can contain:
    - Task descriptions
    - Additional instructions
    - Prompts to feed back to Claude
    - Documentation or notes
    ```
    
    ### Example: Plugin State File
    
    **.claude/my-plugin.local.md:**
    ```markdown
    ---
    enabled: true
    strict_mode: false
    max_retries: 3
    notification_level: info
    coordinator_session: team-leader
    ---
    
    # Plugin Configuration
    
    This plugin is configured for standard validation mode.
    Contact @team-lead with questions.
    ```
    
    ## Reading Settings Files
    
    ### From Hooks (Bash Scripts)
    
    **Pattern: Check existence and parse frontmatter**
    
    ```bash
    #!/bin/bash
    set -euo pipefail
    
    # Define state file path
    STATE_FILE=".claude/my-plugin.local.md"
    
    # Quick exit if file doesn't exist
    if [[ ! -f "$STATE_FILE" ]]; then
      exit 0  # Plugin not configured, skip
    fi
    
    # Parse YAML frontmatter (between --- markers)
    FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
    
    # Extract individual fields
    ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
    STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')
    
    # Check if enabled
    if [[ "$ENABLED" != "true" ]]; then
      exit 0  # Disabled
    fi
    
    # Use configuration in hook logic
    if [[ "$STRICT_MODE" == "true" ]]; then
      # Apply strict validation
      # ...
    fi
    ```
    
    See `examples/read-settings-hook.sh` for complete working example.
    
    ### From Commands
    
    Commands can read settings files to customize behavior:
    
    ```markdown
    ---
    description: Process data with plugin
    allowed-tools: ["Read", "Bash"]
    ---
    
    # Process Command
    
    Steps:
    1. Check if settings exist at `.claude/my-plugin.local.md`
    2. Read configuration using Read tool
    3. Parse YAML frontmatter to extract settings
    4. Apply settings to processing logic
    5. Execute with configured behavior
    ```
    
    ### From Agents
    
    Agents can reference settings in their instructions:
    
    ```markdown
    ---
    name: configured-agent
    description: Agent that adapts to project settings
    ---
    
    Check for plugin settings at `.claude/my-plugin.local.md`.
    If present, parse YAML frontmatter and adapt behavior according to:
    - enabled: Whether plugin is active
    - mode: Processing mode (strict, standard, lenient)
    - Additional configuration fields
    ```
    
    ## Parsing Techniques
    
    ### Extract Frontmatter
    
    ```bash
    # Extract everything between --- markers
    FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
    ```
    
    ### Read Individual Fields
    
    **String fields:**
    ```bash
    VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')
    ```
    
    **Boolean fields:**
    ```bash
    ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
    # Compare: if [[ "$ENABLED" == "true" ]]; then
    ```
    
    **Numeric fields:**
    ```bash
    MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
    # Use: if [[ $MAX -gt 100 ]]; then
    ```
    
    ### Read Markdown Body
    
    Extract content after second `---`:
    
    ```bash
    # Get everything after closing ---
    BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
    ```
    
    ## Common Patterns
    
    ### Pattern 1: Temporarily Active Hooks
    
    Use settings file to control hook activation:
    
    ```bash
    #!/bin/bash
    STATE_FILE=".claude/security-scan.local.md"
    
    # Quick exit if not configured
    if [[ ! -f "$STATE_FILE" ]]; then
      exit 0
    fi
    
    # Read enabled flag
    FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
    ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
    
    if [[ "$ENABLED" != "true" ]]; then
      exit 0  # Disabled
    fi
    
    # Run hook logic
    # ...
    ```
    
    **Use case:** Enable/disable hooks without editing hooks.json (requires restart).
    
    ### Pattern 2: Agent State Management
    
    Store agent-specific state and configuration:
    
    **.claude/multi-agent-swarm.local.md:**
    ```markdown
    ---
    agent_name: auth-agent
    task_number: 3.5
    pr_number: 1234
    coordinator_session: team-leader
    enabled: true
    dependencies: ["Task 3.4"]
    ---
    
    # Task Assignment
    
    Implement JWT authentication for the API.
    
    **Success Criteria:**
    - Authentication endpoints created
    - Tests passing
    - PR created and CI green
    ```
    
    Read from hooks to coordinate agents:
    
    ```bash
    AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//')
    COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')
    
    # Send notification to coordinator
    tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter
    ```
    
    ### Pattern 3: Configuration-Driven Behavior
    
    **.claude/my-plugin.local.md:**
    ```markdown
    ---
    validation_level: strict
    max_file_size: 1000000
    allowed_extensions: [".js", ".ts", ".tsx"]
    enable_logging: true
    ---
    
    # Validation Configuration
    
    Strict mode enabled for this project.
    All writes validated against security policies.
    ```
    
    Use in hooks or commands:
    
    ```bash
    LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//')
    
    case "$LEVEL" in
      strict)
        # Apply strict validation
        ;;
      standard)
        # Apply standard validation
        ;;
      lenient)
        # Apply lenient validation
        ;;
    esac
    ```
    
    ## Creating Settings Files
    
    ### From Commands
    
    Commands can create settings files:
    
    ```markdown
    # Setup Command
    
    Steps:
    1. Ask user for configuration preferences
    2. Create `.claude/my-plugin.local.md` with YAML frontmatter
    3. Set appropriate values based on user input
    4. Inform user that settings are saved
    5. Remind user to restart Claude Code for hooks to recognize changes
    ```
    
    ### Template Generation
    
    Provide template in plugin README:
    
    ```markdown
    ## Configuration
    
    Create `.claude/my-plugin.local.md` in your project:
    
    \`\`\`markdown
    ---
    enabled: true
    mode: standard
    max_retries: 3
    ---
    
    # Plugin Configuration
    
    Your settings are active.
    \`\`\`
    
    After creating or editing, restart Claude Code for changes to take effect.
    ```
    
    ## Best Practices
    
    ### File Naming
    
    ✅ **DO:**
    - Use `.claude/plugin-name.local.md` format
    - Match plugin name exactly
    - Use `.local.md` suffix for user-local files
    
    ❌ **DON'T:**
    - Use different directory (not `.claude/`)
    - Use inconsistent naming
    - Use `.md` without `.local` (might be committed)
    
    ### Gitignore
    
    Always add to `.gitignore`:
    
    ```gitignore
    .claude/*.local.md
    .claude/*.local.json
    ```
    
    Document this in plugin README.
    
    ### Defaults
    
    Provide sensible defaults when settings file doesn't exist:
    
    ```bash
    if [[ ! -f "$STATE_FILE" ]]; then
      # Use defaults
      ENABLED=true
      MODE=standard
    else
      # Read from file
      # ...
    fi
    ```
    
    ### Validation
    
    Validate settings values:
    
    ```bash
    MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
    
    # Validate numeric range
    if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then
      echo "⚠️  Invalid max_value in settings (must be 1-100)" >&2
      MAX=10  # Use default
    fi
    ```
    
    ### Restart Requirement
    
    **Important:** Settings changes require Claude Code restart.
    
    Document in your README:
    
    ```markdown
    ## Changing Settings
    
    After editing `.claude/my-plugin.local.md`:
    1. Save the file
    2. Exit Claude Code
    3. Restart: `claude` or `cc`
    4. New settings will be loaded
    ```
    
    Hooks cannot be hot-swapped within a session.
    
    ## Security Considerations
    
    ### Sanitize User Input
    
    When writing settings files from user input:
    
    ```bash
    # Escape quotes in user input
    SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g')
    
    # Write to file
    cat > "$STATE_FILE" <<EOF
    ---
    user_setting: "$SAFE_VALUE"
    ---
    EOF
    ```
    
    ### Validate File Paths
    
    If settings contain file paths:
    
    ```bash
    FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//')
    
    # Check for path traversal
    if [[ "$FILE_PATH" == *".."* ]]; then
      echo "⚠️  Invalid path in settings (path traversal)" >&2
      exit 2
    fi
    ```
    
    ### Permissions
    
    Settings files should be:
    - Readable by user only (`chmod 600`)
    - Not committed to git
    - Not shared between users
    
    ## Real-World Examples
    
    ### multi-agent-swarm Plugin
    
    **.claude/multi-agent-swarm.local.md:**
    ```markdown
    ---
    agent_name: auth-implementation
    task_number: 3.5
    pr_number: 1234
    coordinator_session: team-leader
    enabled: true
    dependencies: ["Task 3.4"]
    additional_instructions: Use JWT tokens, not sessions
    ---
    
    # Task: Implement Authentication
    
    Build JWT-based authentication for the REST API.
    Coordinate with auth-agent on shared types.
    ```
    
    **Hook usage (agent-stop-notification.sh):**
    - Checks if file exists (line 15-18: quick exit if not)
    - Parses frontmatter to get coordinator_session, agent_name, enabled
    - Sends notifications to coordinator if enabled
    - Allows quick activation/deactivation via `enabled: true/false`
    
    ### ralph-wiggum Plugin
    
    **.claude/ralph-loop.local.md:**
    ```markdown
    ---
    iteration: 1
    max_iterations: 10
    completion_promise: "All tests passing and build successful"
    ---
    
    Fix all the linting errors in the project.
    Make sure tests pass after each fix.
    ```
    
    **Hook usage (stop-hook.sh):**
    - Checks if file exists (line 15-18: quick exit if not active)
    - Reads iteration count and max_iterations
    - Extracts completion_promise for loop termination
    - Reads body as the prompt to feed back
    - Updates iteration count on each loop
    
    ## Quick Reference
    
    ### File Location
    
    ```
    project-root/
    └── .claude/
        └── plugin-name.local.md
    ```
    
    ### Frontmatter Parsing
    
    ```bash
    # Extract frontmatter
    FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
    
    # Read field
    VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')
    ```
    
    ### Body Parsing
    
    ```bash
    # Extract body (after second ---)
    BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
    ```
    
    ### Quick Exit Pattern
    
    ```bash
    if [[ ! -f ".claude/my-plugin.local.md" ]]; then
      exit 0  # Not configured
    fi
    ```
    
    ## Additional Resources
    
    ### Reference Files
    
    For detailed implementation patterns:
    
    - **`references/parsing-techniques.md`** - Complete guide to parsing YAML frontmatter and markdown bodies
    - **`references/real-world-examples.md`** - Deep dive into multi-agent-swarm and ralph-wiggum implementations
    
    ### Example Files
    
    Working examples in `examples/`:
    
    - **`read-settings-hook.sh`** - Hook that reads and uses settings
    - **`create-settings-command.md`** - Command that creates settings file
    - **`example-settings.md`** - Template settings file
    
    ### Utility Scripts
    
    Development tools in `scripts/`:
    
    - **`validate-settings.sh`** - Validate settings file structure
    - **`parse-frontmatter.sh`** - Extract frontmatter fields
    
    ## Implementation Workflow
    
    To add settings to a plugin:
    
    1. Design settings schema (which fields, types, defaults)
    2. Create template file in plugin documentation
    3. Add gitignore entry for `.claude/*.local.md`
    4. Implement settings parsing in hooks/commands
    5. Use quick-exit pattern (check file exists, check enabled field)
    6. Document settings in plugin README with template
    7. Remind users that changes require Claude Code restart
    
    Focus on keeping settings simple and providing good defaults when settings file doesn't exist.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related