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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ Skills follow the [Claude Code skills documentation](https://docs.anthropic.com/

**Guards (PreToolUse):**

- `validate-bash-commands.py` — Validates Bash tool invocations
- `validate-bash-commands.py` — Suggests dedicated tools when Bash invokes grep, find, cat, sed, or awk
- `config-protection.sh` — Warns when editing linter/formatter config files (eslint, biome, prettier, tsconfig, etc.)
- `prompt-injection-guard.py` — Detects prompt injection patterns in content being written to files

**Formatters (PostToolUse):**

- `auto-format.sh` — Runs prettier on Edit/Write automatically
- `auto-format.sh` — Runs prettier/gofmt on Edit/Write automatically

**Monitors (PostToolUse):**

- `suggest-compact.sh` — Nudges compaction every ~30 tool calls
- `context-monitor.sh` — Tracks context window usage and emits warnings at thresholds

**Session lifecycle:**

Expand Down
58 changes: 29 additions & 29 deletions hooks/auto-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,46 @@
file_path="${TOOL_FILE_PATH:-}"

if [ -z "$file_path" ]; then
# Fallback: read from stdin JSON (PostToolUse payload structure)
if command -v jq &>/dev/null; then
if ! file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); then
exit 0 # Graceful exit if JSON parsing fails
fi
else
exit 0 # No jq available, can't parse stdin
fi
# Fallback: read from stdin JSON (PostToolUse payload structure)
if command -v jq &>/dev/null; then
if ! file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); then
exit 0 # Graceful exit if JSON parsing fails
fi
else
exit 0 # No jq available, can't parse stdin
fi
fi

if [ -z "$file_path" ]; then
exit 0
exit 0
fi

# Run a formatter if available, log failures
try_format() {
local tool="$1" file="$2"
shift 2
if command -v "$tool" &>/dev/null; then
if ! "$tool" "$@" "$file" 2>/dev/null; then
echo "[auto-format] $tool failed on $file" >&2
fi
fi
local tool="$1" file="$2"
shift 2
if command -v "$tool" &>/dev/null; then
if ! "$tool" "$@" "$file" 2>/dev/null; then
echo "[auto-format] $tool failed on $file" >&2
fi
fi
}

# Format based on file extension
case "$file_path" in
*.go)
try_format gofmt "$file_path" -w
;;
*.ts | *.tsx | *.js | *.jsx | *.json | *.yaml | *.yml)
try_format prettier "$file_path" --write
;;
*.md)
basename=$(basename "$file_path")
if [ "$basename" = "walkthrough.md" ]; then
exit 0 # Managed by showboat — prettier would break verified output blocks
fi
try_format prettier "$file_path" --write
;;
*.go)
try_format gofmt "$file_path" -w
;;
*.ts | *.tsx | *.js | *.jsx | *.json | *.yaml | *.yml)
try_format bunx "$file_path" prettier --write
;;
*.md)
basename=$(basename "$file_path")
if [ "$basename" = "walkthrough.md" ]; then
exit 0 # Managed by showboat — prettier would break verified output blocks
fi
try_format bunx "$file_path" prettier --write
;;
esac

exit 0
47 changes: 24 additions & 23 deletions hooks/config-protection.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,39 @@
file_path="${TOOL_FILE_PATH:-}"

if [ -z "$file_path" ]; then
if command -v jq &>/dev/null; then
if ! file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); then
exit 0
fi
else
exit 0
fi
if command -v jq &>/dev/null; then
if ! file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null); then
exit 0
fi
else
exit 0
fi
fi

if [ -z "$file_path" ]; then
exit 0
exit 0
fi

basename=$(basename "$file_path")

# Protected config file patterns
case "$basename" in
.eslintrc* | eslint.config.* | \
biome.json | \
.prettierrc* | prettier.config.* | \
.golangci.yml | .golangci.yaml | \
ruff.toml | \
.shellcheckrc | \
.markdownlint* | \
.yamllint | .yamllint.yaml | \
.editorconfig | \
.stylelintrc*)
echo "Warning: modifying linter/formatter config ($basename). Fix the code, not the config." >&2
;;
pyproject.toml)
echo "Warning: modifying pyproject.toml — if changing ruff/linter settings, fix the code instead." >&2
;;
.eslintrc* | eslint.config.* | \
biome.json | \
.prettierrc* | prettier.config.* | \
.golangci.yml | .golangci.yaml | \
ruff.toml | \
.shellcheckrc | \
.markdownlint* | \
.yamllint | .yamllint.yaml | \
.editorconfig | \
.stylelintrc* | \
tsconfig.json | tsconfig.*.json)
echo "Warning: modifying linter/formatter config ($basename). Fix the code, not the config." >&2
;;
pyproject.toml)
echo "Warning: modifying pyproject.toml — if changing ruff/linter settings, fix the code instead." >&2
;;
esac

exit 0
33 changes: 15 additions & 18 deletions hooks/suggest-compact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@
#
# Note: Intentionally no 'set -euo pipefail' - hooks must always exit 0

counter_file="/tmp/claude-compact-count-$$"
# Use session ID from stdin JSON if available, fall back to PID
session_id=""
if raw=$(cat 2>/dev/null); then
if command -v jq &>/dev/null && [ -n "$raw" ]; then
session_id=$(echo "$raw" | jq -r '.session_id // empty' 2>/dev/null) || true
fi
fi
session_id="${session_id:-$$}"
counter_file="/tmp/claude-compact-count-${session_id}"

# SessionStart resets the counter (hook_event env var from Claude Code)
if [ "${HOOK_EVENT:-}" = "SessionStart" ]; then
# Clean up any stale counter files from previous sessions
rm -f /tmp/claude-compact-count-* 2>/dev/null
rm -f /tmp/claude-ctx-*.json /tmp/claude-ctx-warn-* 2>/dev/null
echo "0" >"$counter_file"
exit 0
fi

# Find existing counter file for this session or any recent one
if [ ! -f "$counter_file" ]; then
# Try to find an existing counter file
existing=$(find /tmp -maxdepth 1 -name 'claude-compact-count-*' -print -quit 2>/dev/null)
if [ -n "$existing" ]; then
counter_file="$existing"
else
echo "0" >"$counter_file"
fi
# Clean up any stale counter files from previous sessions
rm -f /tmp/claude-compact-count-* 2>/dev/null
rm -f /tmp/claude-ctx-*.json /tmp/claude-ctx-warn-* 2>/dev/null
echo "0" >"$counter_file"
exit 0
fi

# Increment counter
Expand All @@ -33,7 +30,7 @@ echo "$count" >"$counter_file"

# Suggest compaction every 30 tool calls
if [ $((count % 30)) -eq 0 ]; then
echo "You've made $count tool calls this session. Consider running /compact at a natural breakpoint." >&2
echo "You've made $count tool calls this session. Consider running /compact at a natural breakpoint." >&2
fi

exit 0
14 changes: 10 additions & 4 deletions hooks/validate-bash-commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@
warnings.append(message)

if warnings:
print("⚠️ Command suggestions:")
for warning in warnings:
print(f" • {warning}")
# Don't block, just inform (output to stdout so Claude sees it)
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": (
"Command suggestions: "
+ "; ".join(warnings)
),
}
}
json.dump(output, sys.stdout)

sys.exit(0)

Expand Down
1 change: 1 addition & 0 deletions settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"Write(*.tsx)",
"Write(*.yaml)",
"Write(*.yml)",
"Write(*.sh)",
"Bash(brew:*)",
"Bash(bun:*)",
"Bash(bunx:*)",
Expand Down
1 change: 0 additions & 1 deletion skills/obsidian-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---
name: obsidian-cli
description: Interacts with Obsidian vaults using the Obsidian CLI to read, create, and manage notes, tasks, properties, tags, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, add to daily note, find notes about a topic, check tasks, append to a note, query the vault, list tags, list files, manage bookmarks, check sync status, view file history, use templates, query bases, run QuickAdd, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.
allowed-tools: Bash
---
Expand Down
Loading