#!/usr/bin/env bash # Pre-commit hook: keep .agents/skills/oci-deal-accelerator/SKILL.md # byte-aligned with the root SKILL.md (the source of truth that # CLAUDE.md / Claude Code reads). The Codex copy under .agents/ # follows the same content with an auto-generated banner — they MUST # stay in lock-step or Claude Code and Codex will give the user # different instructions. # # Behavior: # 1. Only the .agents/ copy staged (no root SKILL.md change): # REJECT — the .agents/ file is auto-generated, edits MUST land # in root SKILL.md. Otherwise the next sync silently destroys # the change. # 2. Root SKILL.md staged: auto-run scripts/sync-skill.py and # stage the regenerated .agents/ copy in the same commit. # 3. Neither: nothing to do. # • If the script can't run (no python, etc.), abort with a clear # message rather than committing a desync silently. # # Install once per clone: make install-hooks (sets core.hooksPath) set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" SOURCE="SKILL.md" TARGET=".agents/skills/oci-deal-accelerator/SKILL.md" SCRIPT="scripts/sync-skill.py" STAGED="$(git diff --cached --name-only)" SOURCE_STAGED=0 TARGET_STAGED=0 echo "$STAGED" | grep -qx "$SOURCE" && SOURCE_STAGED=1 || true echo "$STAGED" | grep -qx "$TARGET" && TARGET_STAGED=1 || true # Case 1: only the auto-generated .agents/ copy is staged. Reject — # the source of truth is root SKILL.md and a future sync will # silently overwrite this change. if [ "$TARGET_STAGED" = "1" ] && [ "$SOURCE_STAGED" = "0" ]; then echo "" >&2 echo "pre-commit: REJECTED — you staged $TARGET in isolation." >&2 echo " This file is auto-generated from $SOURCE and the next" >&2 echo " ``make sync-skill`` (or this pre-commit hook firing on a" >&2 echo " later commit) will overwrite your changes silently." >&2 echo "" >&2 echo " Workflow:" >&2 echo " 1. Move your edits into $SOURCE." >&2 echo " 2. ``git restore --staged --worktree $TARGET``" >&2 echo " 3. ``git add $SOURCE`` and re-commit — the hook will" >&2 echo " regenerate the .agents/ copy automatically." >&2 echo "" >&2 exit 1 fi # Nothing else to do unless root SKILL.md changed. if [ "$SOURCE_STAGED" = "0" ]; then exit 0 fi if [ ! -f "$ROOT/$SCRIPT" ]; then echo "pre-commit: $SCRIPT missing — cannot verify SKILL.md sync. Aborting." >&2 exit 1 fi PYTHON="$(command -v python3 || command -v python || true)" if [ -z "$PYTHON" ]; then echo "pre-commit: no python found in PATH — cannot run $SCRIPT. Aborting." >&2 exit 1 fi # Run the full sync, then stage the regenerated target so it travels # in the same commit. (Idempotent — if it was already in sync, the # file write is a no-op.) "$PYTHON" "$ROOT/$SCRIPT" >&2 git add "$ROOT/$TARGET" # Final check — if for any reason the result still drifts, fail loud. if ! "$PYTHON" "$ROOT/$SCRIPT" --check >/dev/null 2>&1; then echo "pre-commit: SKILL.md and $TARGET are still out of sync after auto-run." >&2 echo " Investigate scripts/sync-skill.py before committing." >&2 exit 1 fi exit 0