Newer
Older
programming-template / .claude / hooks / check_sync.sh
#!/usr/bin/env bash
# SessionStart hook: ローカルブランチが origin と同期しているかをチェックし,
# JSON で systemMessage (UI 表示) と additionalContext (モデルに注入) を出力する.
# 読み取り専用 (git fetch のみ). 失敗してもセッション開始は阻害しない.
#
# GUIDE_03「直列運用」「main を常に動作可能」を補助する目的で,
# 古い main から作業を始めるミスをセッション開始時に可視化する.

# JSON 整形は python3 に任せる (jq 非依存. 既存 hook も python3 を利用).
emit() {
  local msg="$1"
  python3 - "$msg" <<'PY'
import json, sys
msg = sys.argv[1]
print(json.dumps({
    "systemMessage": msg,
    "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": msg,
    },
}, ensure_ascii=False))
PY
}

# git リポジトリ外なら静かに終了
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0

# origin が未設定なら静かにスキップ (個人 clone・ローカル実験用途)
git remote get-url origin >/dev/null 2>&1 || exit 0

# fetch (5 秒で打ち切り). 失敗 = オフライン等. 警告だけ出して継続.
if ! timeout 5 git fetch --quiet origin 2>/dev/null; then
  emit "[sync-check] origin への fetch に失敗 — オフラインの可能性"
  exit 0
fi

BRANCH=$(git branch --show-current)
# detached HEAD は判定できないので静かに終了
[ -z "$BRANCH" ] && exit 0

LOCAL=$(git rev-parse HEAD 2>/dev/null) || exit 0
REMOTE=$(git rev-parse "@{u}" 2>/dev/null)
if [ -z "$REMOTE" ]; then
  emit "[sync-check] $BRANCH は upstream 未設定"
  exit 0
fi
BASE=$(git merge-base HEAD "@{u}")

DIRTY=""
[ -n "$(git status --porcelain)" ] && DIRTY=" / 未コミット変更あり"

if [ "$LOCAL" = "$REMOTE" ]; then
  emit "[sync-check] ✓ $BRANCH は origin と同期${DIRTY}"
elif [ "$LOCAL" = "$BASE" ]; then
  AHEAD=$(git rev-list --count "HEAD..@{u}")
  emit "[sync-check] ⚠ $BRANCH は origin より ${AHEAD} コミット遅れ — \`git pull\` 推奨${DIRTY}"
elif [ "$REMOTE" = "$BASE" ]; then
  AHEAD=$(git rev-list --count "@{u}..HEAD")
  emit "[sync-check] ℹ $BRANCH は origin より ${AHEAD} コミット先行${DIRTY}"
else
  AHEAD=$(git rev-list --count "@{u}..HEAD")
  BEHIND=$(git rev-list --count "HEAD..@{u}")
  emit "[sync-check] ⚠ $BRANCH は origin と分岐 (先行 ${AHEAD} / 遅れ ${BEHIND}) — rebase/merge が必要${DIRTY}"
fi