#!/usr/bin/env bash # PostToolUse hook: format and lint edited/written files. # Dispatches to project-local formatters/ directory (symlinks to claude-foundations/formatters/). # # Input: JSON on stdin with tool_name, file_path, cwd fields # Exit 0: silent pass (no formatter, clean lint, or missing tools) # Exit 2: lint errors remain — stderr fed back to Claude as feedback # Parse stdin JSON for file_path and cwd INPUT="$(cat)" FILE_PATH="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('file_path',''))" 2>/dev/null)" CWD="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('cwd',''))" 2>/dev/null)" # No file path — nothing to do [ -z "$FILE_PATH" ] && exit 0 # Make path absolute if relative if [[ "$FILE_PATH" != /* ]]; then FILE_PATH="${CWD:-.}/$FILE_PATH" fi # File must exist [ -f "$FILE_PATH" ] || exit 0 # Extract extension (normalise yml → yaml) EXT="${FILE_PATH##*.}" case "$EXT" in yml) EXT="yaml" ;; esac # No extension — nothing to do [ -z "$EXT" ] || [ "$EXT" = "$FILE_PATH" ] && exit 0 # Find the project's formatters/ directory by walking up from cwd find_formatters_dir() { local dir="${1:-$CWD}" while [ "$dir" != "/" ] && [ -n "$dir" ]; do if [ -d "$dir/formatters" ]; then echo "$dir/formatters" return 0 fi dir="$(dirname "$dir")" done return 1 } FORMATTERS_DIR="$(find_formatters_dir "$CWD")" [ -z "$FORMATTERS_DIR" ] && exit 0 # Check if a formatter exists for this extension FORMATTER="$FORMATTERS_DIR/$EXT" [ -x "$FORMATTER" ] || exit 0 # --- Checkpoint: save pre-format content --- if git -C "$(dirname "$FILE_PATH")" rev-parse --git-dir >/dev/null 2>&1; then # Inside a git repo — use git hash-object for efficient checkpoint BLOB="$(git -C "$(dirname "$FILE_PATH")" hash-object -w "$FILE_PATH" 2>/dev/null)" if [ -n "$BLOB" ]; then echo "$BLOB" > "${FILE_PATH}.pre-lint" fi else # Not a git repo — fall back to file copy cp "$FILE_PATH" "${FILE_PATH}.pre-lint" fi # --- Run formatter --- LINT_OUTPUT=$("$FORMATTER" "$FILE_PATH" 2>&1) FORMATTER_EXIT=$? if [ $FORMATTER_EXIT -eq 0 ]; then # Clean — remove checkpoint rm -f "${FILE_PATH}.pre-lint" exit 0 fi # --- Lint errors remain --- # Keep .pre-lint for revert, exit 2 to feed errors back to Claude { echo "Lint errors in ${FILE_PATH}:" echo "$LINT_OUTPUT" echo "" echo "To fix: use the Agent tool to resolve these lint errors (keeps fix loop out of main context)." echo "To revert formatting: git cat-file blob \$(cat ${FILE_PATH}.pre-lint) > ${FILE_PATH}" } >&2 exit 2