Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:55:59 +08:00
commit 7449ea6e8b
60 changed files with 21848 additions and 0 deletions

View File

@@ -0,0 +1,549 @@
# 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.

View File

@@ -0,0 +1,395 @@
# 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.