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>
55 lines
1.9 KiB
Bash
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
|