diff --git a/.gitea/workflows/skill-sync.yaml b/.gitea/workflows/skill-sync.yaml index cb07f47..4b94fe3 100644 --- a/.gitea/workflows/skill-sync.yaml +++ b/.gitea/workflows/skill-sync.yaml @@ -47,3 +47,21 @@ jobs: - name: Verify SKILL.md is in sync with the Codex copy run: | python3 scripts/sync-skill.py --check + + - name: Reject .agents/ edits without a matching root SKILL.md change + if: github.event_name == 'pull_request' + run: | + # If only .agents/skills/.../SKILL.md is in the diff (no + # change to root SKILL.md), someone edited the auto-generated + # copy directly. That edit will be silently destroyed by the + # next sync. Block the PR with a clear message. + base="${{ github.event.pull_request.base.sha }}" + head="${{ github.event.pull_request.head.sha }}" + changed=$(git diff --name-only "$base" "$head" || true) + if echo "$changed" | grep -qx ".agents/skills/oci-deal-accelerator/SKILL.md"; then + if ! echo "$changed" | grep -qx "SKILL.md"; then + echo "::error::PR edits .agents/skills/.../SKILL.md without touching root SKILL.md." + echo "::error::That file is auto-generated. Move the edit to SKILL.md and run 'make sync-skill'." + exit 1 + fi + fi diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 6461d5a..099a635 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -7,9 +7,13 @@ # 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. +# 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. # @@ -22,8 +26,33 @@ 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 +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 diff --git a/README.md b/README.md index 1c36d30..14b372c 100644 --- a/README.md +++ b/README.md @@ -472,6 +472,8 @@ Three layers of defense, run from least to most enforcement: 3. **CI gate (`.gitea/workflows/skill-sync.yaml`):** every push/PR runs `python scripts/sync-skill.py --check` and fails the build if the two files have drifted. Catches anyone who skipped the hook. +**Direction is always root → `.agents/`.** The `.agents/` copy is auto-generated; do NOT edit it directly — the next sync will silently overwrite your changes. Both the local pre-commit hook and the CI gate REJECT a commit/PR that stages `.agents/skills/oci-deal-accelerator/SKILL.md` without a matching root `SKILL.md` change. If you find yourself wanting to tweak the Codex side specifically, that signal belongs in `AGENTS.md` (Codex-only project instructions) — `SKILL.md` content stays unified across both agents. + ## Roadmap ### ECAL Completeness (see `docs/ecal-gaps-backlog.md` for full list)