Files
oci-deal-accelerator/.githooks/pre-commit
root fba2d23d5d SKILL.md sync: pre-commit hook + CI gate + make install-hooks
Three-layer defense to keep root SKILL.md (Claude Code) and
.agents/skills/oci-deal-accelerator/SKILL.md (Codex) byte-aligned.
Without this, Claude Code and Codex can drift and give the user
contradictory instructions — exactly the failure mode that produced
the workload-driven-mode incident on 2026-04-25.

Layers (least → most enforcement):

1. Local pre-commit hook (.githooks/pre-commit, opt-in via
   ``make install-hooks`` which sets core.hooksPath). When SKILL.md
   is staged, auto-runs scripts/sync-skill.py and stages the
   regenerated .agents/ copy in the same commit. Idempotent.

2. ``make install-hooks`` target + a tip line in ``make venv``'s
   output so any new clone discovers the hook setup.

3. CI gate (.gitea/workflows/skill-sync.yaml) on every push/PR
   touching SKILL.md or the .agents/ copy. Runs
   ``scripts/sync-skill.py --check`` and fails the build on drift.
   Catches anyone who skipped the hook.

README documents the rationale and the three layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:58:33 -03:00

55 lines
1.9 KiB
Bash

#!/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:
# • If SKILL.md is staged but the .agents/ copy is not synced,
# auto-run scripts/sync-skill.py and stage the result so the
# commit ships both files together.
# • 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"
# Only act when SKILL.md is part of the staged change set.
if ! git diff --cached --name-only | grep -qx "$SOURCE"; 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