Plugin Settings Pattern for Claude Code Plugins Overview Plugins can store user-configurable settings and state in 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: 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 ) File Structure Basic Template Example: Plugin State File .claude/my-plugi…

\"$FILE\" 2>/dev/null || echo \"0\")\n\nif [[ $MARKER_COUNT -lt 2 ]]; then\n echo \"Invalid settings file: missing frontmatter markers\" >&2\n exit 1\nfi\n```\n\n### Validate Field Values\n\n```bash\nMODE=$(echo \"$FRONTMATTER\" | grep '^mode:' | sed 's/mode: *//')\n\ncase \"$MODE\" in\n strict|standard|lenient)\n # Valid mode\n ;;\n *)\n echo \"Invalid mode: $MODE (must be strict, standard, or lenient)\" >&2\n exit 1\n ;;\nesac\n```\n\n### Validate Numeric Ranges\n\n```bash\nMAX_SIZE=$(echo \"$FRONTMATTER\" | grep '^max_size:' | sed 's/max_size: *//')\n\nif ! [[ \"$MAX_SIZE\" =~ ^[0-9]+$ ]]; then\n echo \"max_size must be a number\" >&2\n exit 1\nfi\n\nif [[ $MAX_SIZE -lt 1 ]] || [[ $MAX_SIZE -gt 10000000 ]]; then\n echo \"max_size out of range (1-10000000)\" >&2\n exit 1\nfi\n```\n\n## Edge Cases and Gotchas\n\n### Quotes in Values\n\nYAML allows both quoted and unquoted strings:\n\n```yaml\n# These are equivalent:\nfield1: value\nfield2: \"value\"\nfield3: 'value'\n```\n\n**Handle both:**\n```bash\n# Remove surrounding quotes if present\nVALUE=$(echo \"$FRONTMATTER\" | grep '^field:' | sed 's/field: *//' | sed 's/^\"\\(.*\\)\"$/\\1/' | sed \"s/^'\\\\(.*\\\\)'$/\\\\1/\")\n```\n\n### --- in Markdown Body\n\nIf the markdown body contains `---`, the parsing still works because we only match the first two:\n\n```markdown\n---\nfield: value\n---\n\n# Body\n\nHere's a separator:\n---\n\nMore content after the separator.\n```\n\nThe `awk '/^---$/{i++; next} i>=2'` pattern handles this correctly.\n\n### Empty Values\n\nHandle missing or empty fields:\n\n```yaml\nfield1:\nfield2: \"\"\nfield3: null\n```\n\n**Parsing:**\n```bash\nVALUE=$(echo \"$FRONTMATTER\" | grep '^field1:' | sed 's/field1: *//')\n# VALUE will be empty string\n\n# Check for empty/null\nif [[ -z \"$VALUE\" ]] || [[ \"$VALUE\" == \"null\" ]]; then\n VALUE=\"default\"\nfi\n```\n\n### Special Characters\n\nValues with special characters need careful handling:\n\n```yaml\nmessage: \"Error: Something went wrong!\"\npath: \"/path/with spaces/file.txt\"\nregex: \"^[a-zA-Z0-9_]+$\"\n```\n\n**Safe parsing:**\n```bash\n# Always quote variables when using\nMESSAGE=$(echo \"$FRONTMATTER\" | grep '^message:' | sed 's/message: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\n\necho \"Message: $MESSAGE\" # Quoted!\n```\n\n## Performance Optimization\n\n### Cache Parsed Values\n\nIf reading settings multiple times:\n\n```bash\n# Parse once\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")\n\n# Extract multiple fields from cached frontmatter\nFIELD1=$(echo \"$FRONTMATTER\" | grep '^field1:' | sed 's/field1: *//')\nFIELD2=$(echo \"$FRONTMATTER\" | grep '^field2:' | sed 's/field2: *//')\nFIELD3=$(echo \"$FRONTMATTER\" | grep '^field3:' | sed 's/field3: *//')\n```\n\n**Don't:** Re-parse file for each field.\n\n### Lazy Loading\n\nOnly parse settings when needed:\n\n```bash\n#!/bin/bash\ninput=$(cat)\n\n# Quick checks first (no file I/O)\ntool_name=$(echo \"$input\" | jq -r '.tool_name')\nif [[ \"$tool_name\" != \"Write\" ]]; then\n exit 0 # Not a write operation, skip\nfi\n\n# Only now check settings file\nif [[ -f \".claude/my-plugin.local.md\" ]]; then\n # Parse settings\n # ...\nfi\n```\n\n## Debugging\n\n### Print Parsed Values\n\n```bash\n#!/bin/bash\nset -x # Enable debug tracing\n\nFILE=\".claude/my-plugin.local.md\"\n\nif [[ -f \"$FILE\" ]]; then\n echo \"Settings file found\" >&2\n\n FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")\n echo \"Frontmatter:\" >&2\n echo \"$FRONTMATTER\" >&2\n\n ENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//')\n echo \"Enabled: $ENABLED\" >&2\nfi\n```\n\n### Validate Parsing\n\n```bash\n# Show what was parsed\necho \"Parsed values:\" >&2\necho \" enabled: $ENABLED\" >&2\necho \" mode: $MODE\" >&2\necho \" max_size: $MAX_SIZE\" >&2\n\n# Verify expected values\nif [[ \"$ENABLED\" != \"true\" ]] && [[ \"$ENABLED\" != \"false\" ]]; then\n echo \"⚠️ Unexpected enabled value: $ENABLED\" >&2\nfi\n```\n\n## Alternative: Using yq\n\nFor complex YAML, consider using `yq`:\n\n```bash\n# Install: brew install yq\n\n# Parse YAML properly\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")\n\n# Extract fields with yq\nENABLED=$(echo \"$FRONTMATTER\" | yq '.enabled')\nMODE=$(echo \"$FRONTMATTER\" | yq '.mode')\nLIST=$(echo \"$FRONTMATTER\" | yq -o json '.list_field')\n\n# Iterate list properly\necho \"$LIST\" | jq -r '.[]' | while read -r item; do\n echo \"Item: $item\"\ndone\n```\n\n**Pros:**\n- Proper YAML parsing\n- Handles complex structures\n- Better list/object support\n\n**Cons:**\n- Requires yq installation\n- Additional dependency\n- May not be available on all systems\n\n**Recommendation:** Use sed/grep for simple fields, yq for complex structures.\n\n## Complete Example\n\n```bash\n#!/bin/bash\nset -euo pipefail\n\n# Configuration\nSETTINGS_FILE=\".claude/my-plugin.local.md\"\n\n# Quick exit if not configured\nif [[ ! -f \"$SETTINGS_FILE\" ]]; then\n # Use defaults\n ENABLED=true\n MODE=standard\n MAX_SIZE=1000000\nelse\n # Parse frontmatter\n FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$SETTINGS_FILE\")\n\n # Extract fields with defaults\n ENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//')\n ENABLED=${ENABLED:-true}\n\n MODE=$(echo \"$FRONTMATTER\" | grep '^mode:' | sed 's/mode: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\n MODE=${MODE:-standard}\n\n MAX_SIZE=$(echo \"$FRONTMATTER\" | grep '^max_size:' | sed 's/max_size: *//')\n MAX_SIZE=${MAX_SIZE:-1000000}\n\n # Validate values\n if [[ \"$ENABLED\" != \"true\" ]] && [[ \"$ENABLED\" != \"false\" ]]; then\n echo \"⚠️ Invalid enabled value, using default\" >&2\n ENABLED=true\n fi\n\n if ! [[ \"$MAX_SIZE\" =~ ^[0-9]+$ ]]; then\n echo \"⚠️ Invalid max_size, using default\" >&2\n MAX_SIZE=1000000\n fi\nfi\n\n# Quick exit if disabled\nif [[ \"$ENABLED\" != \"true\" ]]; then\n exit 0\nfi\n\n# Use configuration\necho \"Configuration loaded: mode=$MODE, max_size=$MAX_SIZE\" >&2\n\n# Apply logic based on settings\ncase \"$MODE\" in\n strict)\n # Strict validation\n ;;\n standard)\n # Standard validation\n ;;\n lenient)\n # Lenient validation\n ;;\nesac\n```\n\nThis provides robust settings handling with defaults, validation, and error recovery.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11513,"content_sha256":"bc90aa020aa5fcbb3483d7316754450dcc91d086ee96f9304fa655e878f7712e"},{"filename":"references/real-world-examples.md","content":"# Real-World Plugin Settings Examples\n\nDetailed analysis of how production plugins use the `.claude/plugin-name.local.md` pattern.\n\n## multi-agent-swarm Plugin\n\n### Settings File Structure\n\n**.claude/multi-agent-swarm.local.md:**\n\n```markdown\n---\nagent_name: auth-implementation\ntask_number: 3.5\npr_number: 1234\ncoordinator_session: team-leader\nenabled: true\ndependencies: [\"Task 3.4\"]\nadditional_instructions: \"Use JWT tokens, not sessions\"\n---\n\n# Task: Implement Authentication\n\nBuild JWT-based authentication for the REST API.\n\n## Requirements\n- JWT token generation and validation\n- Refresh token flow\n- Secure password hashing\n\n## Success Criteria\n- Auth endpoints implemented\n- Tests passing (100% coverage)\n- PR created and CI green\n- Documentation updated\n\n## Coordination\nDepends on Task 3.4 (user model).\nReport status to 'team-leader' session.\n```\n\n### How It's Used\n\n**File:** `hooks/agent-stop-notification.sh`\n\n**Purpose:** Send notifications to coordinator when agent becomes idle\n\n**Implementation:**\n\n```bash\n#!/bin/bash\nset -euo pipefail\n\nSWARM_STATE_FILE=\".claude/multi-agent-swarm.local.md\"\n\n# Quick exit if no swarm active\nif [[ ! -f \"$SWARM_STATE_FILE\" ]]; then\n exit 0\nfi\n\n# Parse frontmatter\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$SWARM_STATE_FILE\")\n\n# Extract configuration\nCOORDINATOR_SESSION=$(echo \"$FRONTMATTER\" | grep '^coordinator_session:' | sed 's/coordinator_session: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\nAGENT_NAME=$(echo \"$FRONTMATTER\" | grep '^agent_name:' | sed 's/agent_name: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\nTASK_NUMBER=$(echo \"$FRONTMATTER\" | grep '^task_number:' | sed 's/task_number: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\nPR_NUMBER=$(echo \"$FRONTMATTER\" | grep '^pr_number:' | sed 's/pr_number: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\nENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//')\n\n# Check if enabled\nif [[ \"$ENABLED\" != \"true\" ]]; then\n exit 0\nfi\n\n# Send notification to coordinator\nNOTIFICATION=\"🤖 Agent ${AGENT_NAME} (Task ${TASK_NUMBER}, PR #${PR_NUMBER}) is idle.\"\n\nif tmux has-session -t \"$COORDINATOR_SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$COORDINATOR_SESSION\" \"$NOTIFICATION\" Enter\n sleep 0.5\n tmux send-keys -t \"$COORDINATOR_SESSION\" Enter\nfi\n\nexit 0\n```\n\n**Key patterns:**\n1. **Quick exit** (line 7-9): Returns immediately if file doesn't exist\n2. **Field extraction** (lines 11-17): Parses each frontmatter field\n3. **Enabled check** (lines 19-21): Respects enabled flag\n4. **Action based on settings** (lines 23-29): Uses coordinator_session to send notification\n\n### Creation\n\n**File:** `commands/launch-swarm.md`\n\nSettings files are created during swarm launch with:\n\n```bash\ncat > \"$WORKTREE_PATH/.claude/multi-agent-swarm.local.md\" \u003c\u003cEOF\n---\nagent_name: $AGENT_NAME\ntask_number: $TASK_ID\npr_number: TBD\ncoordinator_session: $COORDINATOR_SESSION\nenabled: true\ndependencies: [$DEPENDENCIES]\nadditional_instructions: \"$EXTRA_INSTRUCTIONS\"\n---\n\n# Task: $TASK_DESCRIPTION\n\n$TASK_DETAILS\nEOF\n```\n\n### Updates\n\nPR number updated after PR creation:\n\n```bash\n# Update pr_number field\nsed \"s/^pr_number: .*/pr_number: $PR_NUM/\" \\\n \".claude/multi-agent-swarm.local.md\" > temp.md\nmv temp.md \".claude/multi-agent-swarm.local.md\"\n```\n\n## ralph-wiggum Plugin\n\n### Settings File Structure\n\n**.claude/ralph-loop.local.md:**\n\n```markdown\n---\niteration: 1\nmax_iterations: 10\ncompletion_promise: \"All tests passing and build successful\"\nstarted_at: \"2025-01-15T14:30:00Z\"\n---\n\nFix all the linting errors in the project.\nMake sure tests pass after each fix.\nDocument any changes needed in CLAUDE.md.\n```\n\n### How It's Used\n\n**File:** `hooks/stop-hook.sh`\n\n**Purpose:** Prevent session exit and loop Claude's output back as input\n\n**Implementation:**\n\n```bash\n#!/bin/bash\nset -euo pipefail\n\nRALPH_STATE_FILE=\".claude/ralph-loop.local.md\"\n\n# Quick exit if no active loop\nif [[ ! -f \"$RALPH_STATE_FILE\" ]]; then\n exit 0\nfi\n\n# Parse frontmatter\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$RALPH_STATE_FILE\")\n\n# Extract configuration\nITERATION=$(echo \"$FRONTMATTER\" | grep '^iteration:' | sed 's/iteration: *//')\nMAX_ITERATIONS=$(echo \"$FRONTMATTER\" | grep '^max_iterations:' | sed 's/max_iterations: *//')\nCOMPLETION_PROMISE=$(echo \"$FRONTMATTER\" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\n\n# Check max iterations\nif [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then\n echo \"🛑 Ralph loop: Max iterations ($MAX_ITERATIONS) reached.\"\n rm \"$RALPH_STATE_FILE\"\n exit 0\nfi\n\n# Get transcript and check for completion promise\nTRANSCRIPT_PATH=$(echo \"$HOOK_INPUT\" | jq -r '.transcript_path')\nLAST_OUTPUT=$(grep '\"role\":\"assistant\"' \"$TRANSCRIPT_PATH\" | tail -1 | jq -r '.message.content | map(select(.type == \"text\")) | map(.text) | join(\"\\n\")')\n\n# Check for completion\nif [[ \"$COMPLETION_PROMISE\" != \"null\" ]] && [[ -n \"$COMPLETION_PROMISE\" ]]; then\n PROMISE_TEXT=$(echo \"$LAST_OUTPUT\" | perl -0777 -pe 's/.*?\u003cpromise>(.*?)\u003c\\/promise>.*/$1/s; s/^\\s+|\\s+$//g')\n\n if [[ \"$PROMISE_TEXT\" = \"$COMPLETION_PROMISE\" ]]; then\n echo \"✅ Ralph loop: Detected completion\"\n rm \"$RALPH_STATE_FILE\"\n exit 0\n fi\nfi\n\n# Continue loop - increment iteration\nNEXT_ITERATION=$((ITERATION + 1))\n\n# Extract prompt from markdown body\nPROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' \"$RALPH_STATE_FILE\")\n\n# Update iteration counter\nTEMP_FILE=\"${RALPH_STATE_FILE}.tmp.$\"\nsed \"s/^iteration: .*/iteration: $NEXT_ITERATION/\" \"$RALPH_STATE_FILE\" > \"$TEMP_FILE\"\nmv \"$TEMP_FILE\" \"$RALPH_STATE_FILE\"\n\n# Block exit and feed prompt back\njq -n \\\n --arg prompt \"$PROMPT_TEXT\" \\\n --arg msg \"🔄 Ralph iteration $NEXT_ITERATION\" \\\n '{\n \"decision\": \"block\",\n \"reason\": $prompt,\n \"systemMessage\": $msg\n }'\n\nexit 0\n```\n\n**Key patterns:**\n1. **Quick exit** (line 7-9): Skip if not active\n2. **Iteration tracking** (lines 11-20): Count and enforce max iterations\n3. **Promise detection** (lines 25-33): Check for completion signal in output\n4. **Prompt extraction** (line 38): Read markdown body as next prompt\n5. **State update** (lines 40-43): Increment iteration atomically\n6. **Loop continuation** (lines 45-53): Block exit and feed prompt back\n\n### Creation\n\n**File:** `scripts/setup-ralph-loop.sh`\n\n```bash\n#!/bin/bash\nPROMPT=\"$1\"\nMAX_ITERATIONS=\"${2:-0}\"\nCOMPLETION_PROMISE=\"${3:-}\"\n\n# Create state file\ncat > \".claude/ralph-loop.local.md\" \u003c\u003cEOF\n---\niteration: 1\nmax_iterations: $MAX_ITERATIONS\ncompletion_promise: \"$COMPLETION_PROMISE\"\nstarted_at: \"$(date -Iseconds)\"\n---\n\n$PROMPT\nEOF\n\necho \"Ralph loop initialized: .claude/ralph-loop.local.md\"\n```\n\n## Pattern Comparison\n\n| Feature | multi-agent-swarm | ralph-wiggum |\n|---------|-------------------|--------------|\n| **File** | `.claude/multi-agent-swarm.local.md` | `.claude/ralph-loop.local.md` |\n| **Purpose** | Agent coordination state | Loop iteration state |\n| **Frontmatter** | Agent metadata | Loop configuration |\n| **Body** | Task assignment | Prompt to loop |\n| **Updates** | PR number, status | Iteration counter |\n| **Deletion** | Manual or on completion | On loop exit |\n| **Hook** | Stop (notifications) | Stop (loop control) |\n\n## Best Practices from Real Plugins\n\n### 1. Quick Exit Pattern\n\nBoth plugins check file existence first:\n\n```bash\nif [[ ! -f \"$STATE_FILE\" ]]; then\n exit 0 # Not active\nfi\n```\n\n**Why:** Avoids errors when plugin isn't configured and performs fast.\n\n### 2. Enabled Flag\n\nBoth use an `enabled` field for explicit control:\n\n```yaml\nenabled: true\n```\n\n**Why:** Allows temporary deactivation without deleting file.\n\n### 3. Atomic Updates\n\nBoth use temp file + atomic move:\n\n```bash\nTEMP_FILE=\"${FILE}.tmp.$\"\nsed \"s/^field: .*/field: $NEW_VALUE/\" \"$FILE\" > \"$TEMP_FILE\"\nmv \"$TEMP_FILE\" \"$FILE\"\n```\n\n**Why:** Prevents corruption if process is interrupted.\n\n### 4. Quote Handling\n\nBoth strip surrounding quotes from YAML values:\n\n```bash\nsed 's/^\"\\(.*\\)\"$/\\1/'\n```\n\n**Why:** YAML allows both `field: value` and `field: \"value\"`.\n\n### 5. Error Handling\n\nBoth handle missing/corrupt files gracefully:\n\n```bash\nif [[ ! -f \"$FILE\" ]]; then\n exit 0 # No error, just not configured\nfi\n\nif [[ -z \"$CRITICAL_FIELD\" ]]; then\n echo \"Settings file corrupt\" >&2\n rm \"$FILE\" # Clean up\n exit 0\nfi\n```\n\n**Why:** Fails gracefully instead of crashing.\n\n## Anti-Patterns to Avoid\n\n### ❌ Hardcoded Paths\n\n```bash\n# BAD\nFILE=\"/Users/alice/.claude/my-plugin.local.md\"\n\n# GOOD\nFILE=\".claude/my-plugin.local.md\"\n```\n\n### ❌ Unquoted Variables\n\n```bash\n# BAD\necho $VALUE\n\n# GOOD\necho \"$VALUE\"\n```\n\n### ❌ Non-Atomic Updates\n\n```bash\n# BAD: Can corrupt file if interrupted\nsed -i \"s/field: .*/field: $VALUE/\" \"$FILE\"\n\n# GOOD: Atomic\nTEMP_FILE=\"${FILE}.tmp.$\"\nsed \"s/field: .*/field: $VALUE/\" \"$FILE\" > \"$TEMP_FILE\"\nmv \"$TEMP_FILE\" \"$FILE\"\n```\n\n### ❌ No Default Values\n\n```bash\n# BAD: Fails if field missing\nif [[ $MAX -gt 100 ]]; then\n # MAX might be empty!\nfi\n\n# GOOD: Provide default\nMAX=${MAX:-10}\n```\n\n### ❌ Ignoring Edge Cases\n\n```bash\n# BAD: Assumes exactly 2 --- markers\nsed -n '/^---$/,/^---$/{ /^---$/d; p; }'\n\n# GOOD: Handles --- in body\nawk '/^---$/{i++; next} i>=2' # For body\n```\n\n## Conclusion\n\nThe `.claude/plugin-name.local.md` pattern provides:\n- Simple, human-readable configuration\n- Version-control friendly (gitignored)\n- Per-project settings\n- Easy parsing with standard bash tools\n- Supports both structured config (YAML) and freeform content (markdown)\n\nUse this pattern for any plugin that needs user-configurable behavior or state persistence.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9496,"content_sha256":"565c3af7f6190b7f4dbd9623c471bd2ca5b303432224e69165d18d2bdf62eac6"},{"filename":"scripts/parse-frontmatter.sh","content":"#!/bin/bash\n# Frontmatter Parser Utility\n# Extracts YAML frontmatter from .local.md files\n\nset -euo pipefail\n\n# Usage\nshow_usage() {\n echo \"Usage: $0 \u003csettings-file.md> [field-name]\"\n echo \"\"\n echo \"Examples:\"\n echo \" # Show all frontmatter\"\n echo \" $0 .claude/my-plugin.local.md\"\n echo \"\"\n echo \" # Extract specific field\"\n echo \" $0 .claude/my-plugin.local.md enabled\"\n echo \"\"\n echo \" # Extract and use in script\"\n echo \" ENABLED=\\$($0 .claude/my-plugin.local.md enabled)\"\n exit 0\n}\n\nif [ $# -eq 0 ] || [ \"$1\" = \"-h\" ] || [ \"$1\" = \"--help\" ]; then\n show_usage\nfi\n\nFILE=\"$1\"\nFIELD=\"${2:-}\"\n\n# Validate file\nif [ ! -f \"$FILE\" ]; then\n echo \"Error: File not found: $FILE\" >&2\n exit 1\nfi\n\n# Extract frontmatter\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")\n\nif [ -z \"$FRONTMATTER\" ]; then\n echo \"Error: No frontmatter found in $FILE\" >&2\n exit 1\nfi\n\n# If no field specified, output all frontmatter\nif [ -z \"$FIELD\" ]; then\n echo \"$FRONTMATTER\"\n exit 0\nfi\n\n# Extract specific field\nVALUE=$(echo \"$FRONTMATTER\" | grep \"^${FIELD}:\" | sed \"s/${FIELD}: *//\" | sed 's/^\"\\(.*\\)\"$/\\1/' | sed \"s/^'\\\\(.*\\\\)'$/\\\\1/\")\n\nif [ -z \"$VALUE\" ]; then\n echo \"Error: Field '$FIELD' not found in frontmatter\" >&2\n exit 1\nfi\n\necho \"$VALUE\"\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1269,"content_sha256":"1ab8c42d5a19e0b404af3c573d0267fc19b5987e1d7885f522066ffd76e54553"},{"filename":"scripts/validate-settings.sh","content":"#!/bin/bash\n# Settings File Validator\n# Validates .claude/plugin-name.local.md structure\n\nset -euo pipefail\n\n# Usage\nif [ $# -eq 0 ]; then\n echo \"Usage: $0 \u003cpath/to/settings.local.md>\"\n echo \"\"\n echo \"Validates plugin settings file for:\"\n echo \" - File existence and readability\"\n echo \" - YAML frontmatter structure\"\n echo \" - Required --- markers\"\n echo \" - Field format\"\n echo \"\"\n echo \"Example: $0 .claude/my-plugin.local.md\"\n exit 1\nfi\n\nSETTINGS_FILE=\"$1\"\n\necho \"🔍 Validating settings file: $SETTINGS_FILE\"\necho \"\"\n\n# Check 1: File exists\nif [ ! -f \"$SETTINGS_FILE\" ]; then\n echo \"❌ File not found: $SETTINGS_FILE\"\n exit 1\nfi\necho \"✅ File exists\"\n\n# Check 2: File is readable\nif [ ! -r \"$SETTINGS_FILE\" ]; then\n echo \"❌ File is not readable\"\n exit 1\nfi\necho \"✅ File is readable\"\n\n# Check 3: Has frontmatter markers\nMARKER_COUNT=$(grep -c '^---

Plugin Settings Pattern for Claude Code Plugins Overview Plugins can store user-configurable settings and state in 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: 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 ) File Structure Basic Template Example: Plugin State File .claude/my-plugi…

\"$SETTINGS_FILE\" 2>/dev/null || echo \"0\")\n\nif [ \"$MARKER_COUNT\" -lt 2 ]; then\n echo \"❌ Invalid frontmatter: found $MARKER_COUNT '---' markers (need at least 2)\"\n echo \" Expected format:\"\n echo \" ---\"\n echo \" field: value\"\n echo \" ---\"\n echo \" Content...\"\n exit 1\nfi\necho \"✅ Frontmatter markers present\"\n\n# Check 4: Extract and validate frontmatter\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$SETTINGS_FILE\")\n\nif [ -z \"$FRONTMATTER\" ]; then\n echo \"❌ Empty frontmatter (nothing between --- markers)\"\n exit 1\nfi\necho \"✅ Frontmatter not empty\"\n\n# Check 5: Frontmatter has valid YAML-like structure\nif ! echo \"$FRONTMATTER\" | grep -q ':'; then\n echo \"⚠️ Warning: Frontmatter has no key:value pairs\"\nfi\n\n# Check 6: Look for common fields\necho \"\"\necho \"Detected fields:\"\necho \"$FRONTMATTER\" | grep '^[a-z_][a-z0-9_]*:' | while IFS=':' read -r key value; do\n echo \" - $key: ${value:0:50}\"\ndone\n\n# Check 7: Validate common boolean fields\nfor field in enabled strict_mode; do\n VALUE=$(echo \"$FRONTMATTER\" | grep \"^${field}:\" | sed \"s/${field}: *//\" || true)\n if [ -n \"$VALUE\" ]; then\n if [ \"$VALUE\" != \"true\" ] && [ \"$VALUE\" != \"false\" ]; then\n echo \"⚠️ Field '$field' should be boolean (true/false), got: $VALUE\"\n fi\n fi\ndone\n\n# Check 8: Check body exists\nBODY=$(awk '/^---$/{i++; next} i>=2' \"$SETTINGS_FILE\")\n\necho \"\"\nif [ -n \"$BODY\" ]; then\n BODY_LINES=$(echo \"$BODY\" | wc -l | tr -d ' ')\n echo \"✅ Markdown body present ($BODY_LINES lines)\"\nelse\n echo \"⚠️ No markdown body (frontmatter only)\"\nfi\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"✅ Settings file structure is valid\"\necho \"\"\necho \"Reminder: Changes to this file require restarting Claude Code\"\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2712,"content_sha256":"2f4975285cba7f16b82c764a95fe76d34ac488308ba6705c04561267da4f4f75"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Plugin Settings Pattern for Claude Code Plugins","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Plugins can store user-configurable settings and state in ","type":"text"},{"text":".claude/plugin-name.local.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.","type":"text"}]},{"type":"paragraph","content":[{"text":"Key characteristics:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"File location: ","type":"text"},{"text":".claude/plugin-name.local.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" in project root","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Structure: YAML frontmatter + markdown body","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Purpose: Per-project plugin configuration and state","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Usage: Read from hooks, commands, and agents","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lifecycle: User-managed (not in git, should be in ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"File Structure","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Template","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nenabled: true\nsetting1: value1\nsetting2: value2\nnumeric_setting: 42\nlist_setting: [\"item1\", \"item2\"]\n---\n\n# Additional Context\n\nThis markdown body can contain:\n- Task descriptions\n- Additional instructions\n- Prompts to feed back to Claude\n- Documentation or notes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example: Plugin State File","type":"text"}]},{"type":"paragraph","content":[{"text":".claude/my-plugin.local.md:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nenabled: true\nstrict_mode: false\nmax_retries: 3\nnotification_level: info\ncoordinator_session: team-leader\n---\n\n# Plugin Configuration\n\nThis plugin is configured for standard validation mode.\nContact @team-lead with questions.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reading Settings Files","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"From Hooks (Bash Scripts)","type":"text"}]},{"type":"paragraph","content":[{"text":"Pattern: Check existence and parse frontmatter","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"#!/bin/bash\nset -euo pipefail\n\n# Define state file path\nSTATE_FILE=\".claude/my-plugin.local.md\"\n\n# Quick exit if file doesn't exist\nif [[ ! -f \"$STATE_FILE\" ]]; then\n exit 0 # Plugin not configured, skip\nfi\n\n# Parse YAML frontmatter (between --- markers)\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$STATE_FILE\")\n\n# Extract individual fields\nENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\nSTRICT_MODE=$(echo \"$FRONTMATTER\" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')\n\n# Check if enabled\nif [[ \"$ENABLED\" != \"true\" ]]; then\n exit 0 # Disabled\nfi\n\n# Use configuration in hook logic\nif [[ \"$STRICT_MODE\" == \"true\" ]]; then\n # Apply strict validation\n # ...\nfi","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"examples/read-settings-hook.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" for complete working example.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"From Commands","type":"text"}]},{"type":"paragraph","content":[{"text":"Commands can read settings files to customize behavior:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\ndescription: Process data with plugin\nallowed-tools: [\"Read\", \"Bash\"]\n---\n\n# Process Command\n\nSteps:\n1. Check if settings exist at `.claude/my-plugin.local.md`\n2. Read configuration using Read tool\n3. Parse YAML frontmatter to extract settings\n4. Apply settings to processing logic\n5. Execute with configured behavior","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"From Agents","type":"text"}]},{"type":"paragraph","content":[{"text":"Agents can reference settings in their instructions:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nname: configured-agent\ndescription: Agent that adapts to project settings\n---\n\nCheck for plugin settings at `.claude/my-plugin.local.md`.\nIf present, parse YAML frontmatter and adapt behavior according to:\n- enabled: Whether plugin is active\n- mode: Processing mode (strict, standard, lenient)\n- Additional configuration fields","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Parsing Techniques","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Extract Frontmatter","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Extract everything between --- markers\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Read Individual Fields","type":"text"}]},{"type":"paragraph","content":[{"text":"String fields:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"VALUE=$(echo \"$FRONTMATTER\" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')","type":"text"}]},{"type":"paragraph","content":[{"text":"Boolean fields:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"ENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//')\n# Compare: if [[ \"$ENABLED\" == \"true\" ]]; then","type":"text"}]},{"type":"paragraph","content":[{"text":"Numeric fields:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"MAX=$(echo \"$FRONTMATTER\" | grep '^max_value:' | sed 's/max_value: *//')\n# Use: if [[ $MAX -gt 100 ]]; then","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Read Markdown Body","type":"text"}]},{"type":"paragraph","content":[{"text":"Extract content after second ","type":"text"},{"text":"---","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Get everything after closing ---\nBODY=$(awk '/^---$/{i++; next} i>=2' \"$FILE\")","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 1: Temporarily Active Hooks","type":"text"}]},{"type":"paragraph","content":[{"text":"Use settings file to control hook activation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"#!/bin/bash\nSTATE_FILE=\".claude/security-scan.local.md\"\n\n# Quick exit if not configured\nif [[ ! -f \"$STATE_FILE\" ]]; then\n exit 0\nfi\n\n# Read enabled flag\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$STATE_FILE\")\nENABLED=$(echo \"$FRONTMATTER\" | grep '^enabled:' | sed 's/enabled: *//')\n\nif [[ \"$ENABLED\" != \"true\" ]]; then\n exit 0 # Disabled\nfi\n\n# Run hook logic\n# ...","type":"text"}]},{"type":"paragraph","content":[{"text":"Use case:","type":"text","marks":[{"type":"strong"}]},{"text":" Enable/disable hooks without editing hooks.json (requires restart).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 2: Agent State Management","type":"text"}]},{"type":"paragraph","content":[{"text":"Store agent-specific state and configuration:","type":"text"}]},{"type":"paragraph","content":[{"text":".claude/multi-agent-swarm.local.md:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nagent_name: auth-agent\ntask_number: 3.5\npr_number: 1234\ncoordinator_session: team-leader\nenabled: true\ndependencies: [\"Task 3.4\"]\n---\n\n# Task Assignment\n\nImplement JWT authentication for the API.\n\n**Success Criteria:**\n- Authentication endpoints created\n- Tests passing\n- PR created and CI green","type":"text"}]},{"type":"paragraph","content":[{"text":"Read from hooks to coordinate agents:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"AGENT_NAME=$(echo \"$FRONTMATTER\" | grep '^agent_name:' | sed 's/agent_name: *//')\nCOORDINATOR=$(echo \"$FRONTMATTER\" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')\n\n# Send notification to coordinator\ntmux send-keys -t \"$COORDINATOR\" \"Agent $AGENT_NAME completed task\" Enter","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 3: Configuration-Driven Behavior","type":"text"}]},{"type":"paragraph","content":[{"text":".claude/my-plugin.local.md:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nvalidation_level: strict\nmax_file_size: 1000000\nallowed_extensions: [\".js\", \".ts\", \".tsx\"]\nenable_logging: true\n---\n\n# Validation Configuration\n\nStrict mode enabled for this project.\nAll writes validated against security policies.","type":"text"}]},{"type":"paragraph","content":[{"text":"Use in hooks or commands:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"LEVEL=$(echo \"$FRONTMATTER\" | grep '^validation_level:' | sed 's/validation_level: *//')\n\ncase \"$LEVEL\" in\n strict)\n # Apply strict validation\n ;;\n standard)\n # Apply standard validation\n ;;\n lenient)\n # Apply lenient validation\n ;;\nesac","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Creating Settings Files","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"From Commands","type":"text"}]},{"type":"paragraph","content":[{"text":"Commands can create settings files:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"# Setup Command\n\nSteps:\n1. Ask user for configuration preferences\n2. Create `.claude/my-plugin.local.md` with YAML frontmatter\n3. Set appropriate values based on user input\n4. Inform user that settings are saved\n5. Remind user to restart Claude Code for hooks to recognize changes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template Generation","type":"text"}]},{"type":"paragraph","content":[{"text":"Provide template in plugin README:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Configuration\n\nCreate `.claude/my-plugin.local.md` in your project:\n\n\\`\\`\\`markdown\n---\nenabled: true\nmode: standard\nmax_retries: 3\n---\n\n# Plugin Configuration\n\nYour settings are active.\n\\`\\`\\`\n\nAfter creating or editing, restart Claude Code for changes to take effect.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"File Naming","type":"text"}]},{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":"DO:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":".claude/plugin-name.local.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" format","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Match plugin name exactly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":".local.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" suffix for user-local files","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"❌ ","type":"text"},{"text":"DON'T:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use different directory (not ","type":"text"},{"text":".claude/","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use inconsistent naming","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" without ","type":"text"},{"text":".local","type":"text","marks":[{"type":"code_inline"}]},{"text":" (might be committed)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Gitignore","type":"text"}]},{"type":"paragraph","content":[{"text":"Always add to ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"gitignore"},"content":[{"text":".claude/*.local.md\n.claude/*.local.json","type":"text"}]},{"type":"paragraph","content":[{"text":"Document this in plugin README.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Defaults","type":"text"}]},{"type":"paragraph","content":[{"text":"Provide sensible defaults when settings file doesn't exist:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"if [[ ! -f \"$STATE_FILE\" ]]; then\n # Use defaults\n ENABLED=true\n MODE=standard\nelse\n # Read from file\n # ...\nfi","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Validation","type":"text"}]},{"type":"paragraph","content":[{"text":"Validate settings values:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"MAX=$(echo \"$FRONTMATTER\" | grep '^max_value:' | sed 's/max_value: *//')\n\n# Validate numeric range\nif ! [[ \"$MAX\" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then\n echo \"⚠️ Invalid max_value in settings (must be 1-100)\" >&2\n MAX=10 # Use default\nfi","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Restart Requirement","type":"text"}]},{"type":"paragraph","content":[{"text":"Important:","type":"text","marks":[{"type":"strong"}]},{"text":" Settings changes require Claude Code restart.","type":"text"}]},{"type":"paragraph","content":[{"text":"Document in your README:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Changing Settings\n\nAfter editing `.claude/my-plugin.local.md`:\n1. Save the file\n2. Exit Claude Code\n3. Restart: `claude` or `cc`\n4. New settings will be loaded","type":"text"}]},{"type":"paragraph","content":[{"text":"Hooks cannot be hot-swapped within a session.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Considerations","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Sanitize User Input","type":"text"}]},{"type":"paragraph","content":[{"text":"When writing settings files from user input:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Escape quotes in user input\nSAFE_VALUE=$(echo \"$USER_INPUT\" | sed 's/\"/\\\\\"/g')\n\n# Write to file\ncat > \"$STATE_FILE\" \u003c\u003cEOF\n---\nuser_setting: \"$SAFE_VALUE\"\n---\nEOF","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Validate File Paths","type":"text"}]},{"type":"paragraph","content":[{"text":"If settings contain file paths:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"FILE_PATH=$(echo \"$FRONTMATTER\" | grep '^data_file:' | sed 's/data_file: *//')\n\n# Check for path traversal\nif [[ \"$FILE_PATH\" == *\"..\"* ]]; then\n echo \"⚠️ Invalid path in settings (path traversal)\" >&2\n exit 2\nfi","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Permissions","type":"text"}]},{"type":"paragraph","content":[{"text":"Settings files should be:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Readable by user only (","type":"text"},{"text":"chmod 600","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not committed to git","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not shared between users","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Real-World Examples","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"multi-agent-swarm Plugin","type":"text"}]},{"type":"paragraph","content":[{"text":".claude/multi-agent-swarm.local.md:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\nagent_name: auth-implementation\ntask_number: 3.5\npr_number: 1234\ncoordinator_session: team-leader\nenabled: true\ndependencies: [\"Task 3.4\"]\nadditional_instructions: Use JWT tokens, not sessions\n---\n\n# Task: Implement Authentication\n\nBuild JWT-based authentication for the REST API.\nCoordinate with auth-agent on shared types.","type":"text"}]},{"type":"paragraph","content":[{"text":"Hook usage (agent-stop-notification.sh):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checks if file exists (line 15-18: quick exit if not)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parses frontmatter to get coordinator_session, agent_name, enabled","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sends notifications to coordinator if enabled","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Allows quick activation/deactivation via ","type":"text"},{"text":"enabled: true/false","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"ralph-wiggum Plugin","type":"text"}]},{"type":"paragraph","content":[{"text":".claude/ralph-loop.local.md:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"---\niteration: 1\nmax_iterations: 10\ncompletion_promise: \"All tests passing and build successful\"\n---\n\nFix all the linting errors in the project.\nMake sure tests pass after each fix.","type":"text"}]},{"type":"paragraph","content":[{"text":"Hook usage (stop-hook.sh):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checks if file exists (line 15-18: quick exit if not active)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reads iteration count and max_iterations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extracts completion_promise for loop termination","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reads body as the prompt to feed back","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates iteration count on each loop","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"File Location","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"project-root/\n└── .claude/\n └── plugin-name.local.md","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Frontmatter Parsing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Extract frontmatter\nFRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' \"$FILE\")\n\n# Read field\nVALUE=$(echo \"$FRONTMATTER\" | grep '^field:' | sed 's/field: *//' | sed 's/^\"\\(.*\\)\"$/\\1/')","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Body Parsing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Extract body (after second ---)\nBODY=$(awk '/^---$/{i++; next} i>=2' \"$FILE\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Quick Exit Pattern","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"if [[ ! -f \".claude/my-plugin.local.md\" ]]; then\n exit 0 # Not configured\nfi","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Additional Resources","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Reference Files","type":"text"}]},{"type":"paragraph","content":[{"text":"For detailed implementation patterns:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/parsing-techniques.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Complete guide to parsing YAML frontmatter and markdown bodies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/real-world-examples.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Deep dive into multi-agent-swarm and ralph-wiggum implementations","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example Files","type":"text"}]},{"type":"paragraph","content":[{"text":"Working examples in ","type":"text"},{"text":"examples/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"read-settings-hook.sh","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Hook that reads and uses settings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"create-settings-command.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Command that creates settings file","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"example-settings.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Template settings file","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Utility Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"Development tools in ","type":"text"},{"text":"scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"validate-settings.sh","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Validate settings file structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"parse-frontmatter.sh","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Extract frontmatter fields","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Implementation Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"To add settings to a plugin:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Design settings schema (which fields, types, defaults)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create template file in plugin documentation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add gitignore entry for ","type":"text"},{"text":".claude/*.local.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement settings parsing in hooks/commands","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use quick-exit pattern (check file exists, check enabled field)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document settings in plugin README with template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remind users that changes require Claude Code restart","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Focus on keeping settings simple and providing good defaults when settings file doesn't exist.","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"Plugin Settings","author":"@skillopedia","source":{"stars":129313,"repo_name":"claude-code","origin_url":"https://github.com/anthropics/claude-code/blob/HEAD/plugins/plugin-dev/skills/plugin-settings/SKILL.md","repo_owner":"anthropics","body_sha256":"7c8feabfb64dc01f8172269f6ebcbd0c033d4f5be687f5b33fe68df18e8dfbe6","cluster_key":"073e815439d20a2a30f38a6bc1b596dc07df06a06d99fb3cf39860f4de9ba151","clean_bundle":{"format":"clean-skill-bundle-v1","source":"anthropics/claude-code/plugins/plugin-dev/skills/plugin-settings/SKILL.md","attachments":[{"id":"03a2d3ad-0ab1-5871-9e2a-1fdf91c40399","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/03a2d3ad-0ab1-5871-9e2a-1fdf91c40399/attachment.md","path":"examples/create-settings-command.md","size":2177,"sha256":"abf0a36bb3f6425041046f2da1ebfac5505c5b37656fdd4772cfc4d8bd2fa655","contentType":"text/markdown; charset=utf-8"},{"id":"19e47c0f-681b-5942-a62f-ef15ef398bd7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/19e47c0f-681b-5942-a62f-ef15ef398bd7/attachment.md","path":"examples/example-settings.md","size":2930,"sha256":"f07209b6e2fb62d443ae07d01bc1674c6c741c352a04549bfa978e1f34b9769e","contentType":"text/markdown; charset=utf-8"},{"id":"48c003c1-ff08-5174-944d-25998e314dcc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/48c003c1-ff08-5174-944d-25998e314dcc/attachment.sh","path":"examples/read-settings-hook.sh","size":2205,"sha256":"627fce38d37d64c971401ecc85d548f060075670bcac52f65c92ca156e810c70","contentType":"application/x-sh; charset=utf-8"},{"id":"5eb98303-1a13-5e7d-95e8-a43dca632f69","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5eb98303-1a13-5e7d-95e8-a43dca632f69/attachment.md","path":"references/parsing-techniques.md","size":11513,"sha256":"bc90aa020aa5fcbb3483d7316754450dcc91d086ee96f9304fa655e878f7712e","contentType":"text/markdown; charset=utf-8"},{"id":"737ead59-678e-5171-ab61-14146cb46aa6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/737ead59-678e-5171-ab61-14146cb46aa6/attachment.md","path":"references/real-world-examples.md","size":9496,"sha256":"565c3af7f6190b7f4dbd9623c471bd2ca5b303432224e69165d18d2bdf62eac6","contentType":"text/markdown; charset=utf-8"},{"id":"39f50e62-189d-57f4-bfec-5f87c6f95e3c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/39f50e62-189d-57f4-bfec-5f87c6f95e3c/attachment.sh","path":"scripts/parse-frontmatter.sh","size":1269,"sha256":"1ab8c42d5a19e0b404af3c573d0267fc19b5987e1d7885f522066ffd76e54553","contentType":"application/x-sh; charset=utf-8"},{"id":"00d6d155-beb4-5d03-9e6b-33eb9c6ec8c4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/00d6d155-beb4-5d03-9e6b-33eb9c6ec8c4/attachment.sh","path":"scripts/validate-settings.sh","size":2712,"sha256":"2f4975285cba7f16b82c764a95fe76d34ac488308ba6705c04561267da4f4f75","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"b1fc299a0dee9e04f2949255c8590f99edccd3befc402fc4ef88896226c7661d","attachment_count":7,"text_attachments":7,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":20,"skill_md_path":"plugins/plugin-dev/skills/plugin-settings/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"documents-office","category_label":"Documents"},"exact_dupes_collapsed_into_this":19},"version":"v1","category":"documents-office","import_tag":"clean-skills-v1","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."}},"renderedAt":1782979285955}

Plugin Settings Pattern for Claude Code Plugins Overview Plugins can store user-configurable settings and state in 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: 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 ) File Structure Basic Template Example: Plugin State File .claude/my-plugi…