From 3eb37defd1ec3a5fc6dc74012c5f05b2ed287cbd Mon Sep 17 00:00:00 2001 From: Jakub Czajka Date: Sat, 20 Jun 2026 21:18:20 +0200 Subject: [PATCH] [ai] Add a Claude Code hook to format and compile guile files. --- .claude/hooks/guix-check | 79 ++++++++++++++++++++++++++++++++++++++++ .claude/settings.json | 27 ++++++++++++++ 2 files changed, 106 insertions(+) create mode 100755 .claude/hooks/guix-check create mode 100644 .claude/settings.json diff --git a/.claude/hooks/guix-check b/.claude/hooks/guix-check new file mode 100755 index 0000000..19c5402 --- /dev/null +++ b/.claude/hooks/guix-check @@ -0,0 +1,79 @@ +#!/bin/sh +# Copyright (c) 2026 Jakub Czajka +# License: GPL-3.0 or later. +# +# guix-check — Checks to be applied after every edit of a guile file. If a check +# fails, the script exits with status code 2 to make Claude Code fix the issues. + +file=$($GUIX_BIN/sed --regexp-extended --quiet \ + 's/.*"file_path"\s*:\s*"([^"]*)".*/\1/p') +[ -z "$file" ] && exit 0 + +if ! $GUIX_BIN/guix style --whole-file "$file"; then + exec 1>&2 + $GUIX_BIN/echo "❌ guix style failed on $file." + exit 2 +fi + +if $GUIX_BIN/grep --quiet '^unbalanced$' "$file"; then + exec 1>&2 + $GUIX_BIN/echo "❌ guix style added \`unbalanced\` token to $file for each" + $GUIX_BIN/echo "extra closing parenthesis. Remove these tokens AND fix the" + $GUIX_BIN/echo "closing-paren count. Edit to re-trigger the check." + exit 2 +fi + +long_lines=$($GUIX_BIN/grep --line-number --extended-regexp '^.{81,}$' "$file") +if [ -n "$long_lines" ]; then + exec 1>&2 + $GUIX_BIN/echo "❌ Lines exceed 80 characters in $file:" + $GUIX_BIN/echo "" + $GUIX_BIN/echo "$long_lines" + $GUIX_BIN/echo "" + $GUIX_BIN/echo "To avoid feedback loops with guix style, do not re-indent." + $GUIX_BIN/echo "Instead use let-bindings or move code into a conf/ module." + $GUIX_BIN/echo "Edit to re-trigger formatting." + exit 2 +fi + +# guild compile needs an output file, but the hook only cares about warnings. +# Create a disposable directory and schedule its removal. +tmpdir=$($GUIX_BIN/mktemp --directory) +trap '$GUIX_BIN/rm --recursive --force "$tmpdir"' EXIT + +LOAD_PATH="$HOME/.config/guix/current/share/guile/site/3.0" +LOAD_COMPILED_PATH="$HOME/.config/guix/current/lib/guile/3.0/site-ccache" + +out=$( + GUIX_PACKAGE_PATH="$CLAUDE_PROJECT_DIR" \ + GUILE_LOAD_PATH="$LOAD_PATH:$GUILE_LOAD_PATH" \ + GUILE_LOAD_COMPILED_PATH="$LOAD_COMPILED_PATH:$GUILE_LOAD_COMPILED_PATH" \ + $GUIX_BIN/guild compile \ + --optimize=0 \ + --warn=unbound-variable \ + --warn=macro-use-before-definition \ + --warn=use-before-definition \ + --warn=non-idempotent-definition \ + --warn=arity-mismatch \ + --warn=duplicate-case-datum \ + --warn=bad-case-datum \ + --warn=format \ + --load-path="$CLAUDE_PROJECT_DIR" \ + --output="$tmpdir/out.go" \ + "$file" 2>&1 +) + +# guild exits non-zero for fatal errors but still exits 0 for warnings so we +# check both. +if [ $? -ne 0 ] || $GUIX_BIN/echo "$out" | $GUIX_BIN/grep --quiet warning; then + exec 1>&2 + $GUIX_BIN/echo "❌ Compilation issues in $file:" + $GUIX_BIN/echo "" + $GUIX_BIN/echo "$out" + $GUIX_BIN/echo "" + $GUIX_BIN/echo "Fix errors and warnings. Edit the file to re-trigger the" + $GUIX_BIN/echo "build check." + exit 2 +fi + +$GUIX_BIN/echo '{"systemMessage":"✅ Format and build Guile file."}' diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..4a8d302 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,27 @@ +{ + "env": { + "GUIX_BIN": "/run/current-system/profile/bin", + "PROFILE": "nonfree" + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guix-check", + "if": "Edit(*.scm)", + "statusMessage": "Format and build Guile file." + }, + { + "type": "command", + "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guix-check", + "if": "Write(*.scm)", + "statusMessage": "Format and build Guile file." + } + ] + } + ] + } +} -- 2.47.3