#!/usr/bin/env bash # commit_msg.sh — génère un message de commit depuis les changements en cours # Fonctionne avec git (fichiers stagés) et jj (working copy). # # Usage : bash .claude/hooks/commit_msg.sh set -euo pipefail log() { printf '\033[1;34m==>\033[0m %s\n' "$*" >&2; } info() { printf ' \033[0;37m%s\033[0m\n' "$*" >&2; } ok() { printf ' \033[0;32m✓\033[0m %s\n' "$*" >&2; } # --- Détection du VCS --- _detect_vcs() { if jj root &>/dev/null; then echo "jj" elif git rev-parse --git-dir &>/dev/null; then echo "git" else echo "none" fi } # --- Fichiers modifiés --- _changed_files() { case "$VCS" in jj) jj diff --name-only ;; git) git diff --staged --name-only ;; esac } # --- Diff lisible par fichier --- _readable_diff() { local file="$1" local raw_diff case "$VCS" in jj) raw_diff=$(jj diff -- "$file") ;; git) raw_diff=$(git diff --staged -- "$file") ;; esac [[ -z "$raw_diff" ]] && return 0 local max_len max_len=$(grep '^[+-]' <<< "$raw_diff" 2>/dev/null \ | awk '{ if (length > m) m = length } END { print m+0 }') if (( max_len <= 500 )); then printf '%s' "$raw_diff" return fi # Pretty-print pour les formats pathologiques (JSON, JS minifié…) local ext="${file##*.}" local pretty_old pretty_new case "$ext" in json) case "$VCS" in jj) pretty_old=$(jj file show "${file}@-" 2>/dev/null | python3 -m json.tool 2>/dev/null || true) pretty_new=$(jj file show "$file" 2>/dev/null | python3 -m json.tool 2>/dev/null || true) ;; git) pretty_old=$(git show "HEAD:${file}" 2>/dev/null | python3 -m json.tool 2>/dev/null || true) pretty_new=$(git show ":${file}" 2>/dev/null | python3 -m json.tool 2>/dev/null || true) ;; esac ;; js|mjs|cjs|css|ts) local node_fmt=' const chunks = []; process.stdin.on("data", d => chunks.push(d)); process.stdin.on("end", () => { const src = chunks.join(""); const out = src .replace(/([{(])/g, "$1\n ") .replace(/([;}])/g, "\n$1\n") .replace(/,\s*/g, ",\n "); process.stdout.write(out); });' case "$VCS" in jj) pretty_old=$(jj file show "${file}@-" 2>/dev/null | node -e "$node_fmt" 2>/dev/null || true) pretty_new=$(jj file show "$file" 2>/dev/null | node -e "$node_fmt" 2>/dev/null || true) ;; git) pretty_old=$(git show "HEAD:${file}" 2>/dev/null | node -e "$node_fmt" 2>/dev/null || true) pretty_new=$(git show ":${file}" 2>/dev/null | node -e "$node_fmt" 2>/dev/null || true) ;; esac ;; *) case "$VCS" in jj) pretty_old=$(jj file show "${file}@-" 2>/dev/null | fold -s -w 120 || true) pretty_new=$(jj file show "$file" 2>/dev/null | fold -s -w 120 || true) ;; git) pretty_old=$(git show "HEAD:${file}" 2>/dev/null | fold -s -w 120 || true) pretty_new=$(git show ":${file}" 2>/dev/null | fold -s -w 120 || true) ;; esac ;; esac if [[ -n "$pretty_old" && -n "$pretty_new" ]]; then diff <(printf '%s\n' "$pretty_old") <(printf '%s\n' "$pretty_new") \ --label "a/${file}" --label "b/${file}" -u || true else printf '%s' "$raw_diff" fi } # --- Main --- VCS=$(_detect_vcs) if [[ "$VCS" == "none" ]]; then echo "ERREUR : ni git ni jj détecté dans ce répertoire." >&2 exit 1 fi log "VCS détecté : $VCS" changed_files=$(_changed_files) if [[ -z "$changed_files" ]]; then echo "Aucun fichier modifié." >&2 exit 1 fi file_count=$(wc -l <<< "$changed_files" | tr -d ' ') log "Fichiers modifiés : $file_count" summaries="" n=0 while IFS= read -r file; do diff=$(_readable_diff "$file") [[ -z "$diff" ]] && continue n=$((n + 1)) log "[$n/$file_count] Résumé de $file …" summary=$(printf '%s' "$diff" \ | aichat "In 2-3 lines, summarise what this diff changes in the file '$file'. Be concise and technical.") while IFS= read -r line; do info "$line"; done <<< "$summary" summaries+="### $file $summary " done <<< "$changed_files" if [[ -z "$summaries" ]]; then echo "Aucun diff non-vide trouvé." >&2 exit 1 fi log "Génération du message de commit ($n fichier(s)) …" result=$(printf '%s' "$summaries" \ | aichat "From these per-file summaries of a diff, write a single conventional git commit message in English. First line: short imperative summary (max 72 chars). Then a blank line. Then a short paragraph with more detail if needed. Output only the commit message, nothing else.") ok "Terminé" printf '\n' >&2 printf '%s\n' "$result"