]> git.ekhem.eu.org Git - guix.git/commitdiff
[ai] Add a Claude Code hook to format and compile guile files.
authorJakub Czajka <jakub@ekhem.eu.org>
Sat, 20 Jun 2026 19:18:20 +0000 (21:18 +0200)
committerJakub Czajka <jakub@ekhem.eu.org>
Wed, 24 Jun 2026 22:53:10 +0000 (00:53 +0200)
.claude/hooks/guix-check [new file with mode: 0755]
.claude/settings.json [new file with mode: 0644]

diff --git a/.claude/hooks/guix-check b/.claude/hooks/guix-check
new file mode 100755 (executable)
index 0000000..19c5402
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+# Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+# 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 (file)
index 0000000..4a8d302
--- /dev/null
@@ -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."
+          }
+        ]
+      }
+    ]
+  }
+}