From 9f84ea2c7854e35ae30c0fb5fbd63f7b7115fb41 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 28 May 2026 09:11:47 -0500 Subject: feat(sync-check): canonical/mirror drift detection + pre-commit hook scripts/sync-check.sh diffs claude-templates/.ai/{protocols.org, workflows,scripts} against the .ai/ mirror. Exits 0 when clean, 1 with a diff report on drift, 2 outside a rulesets-shaped repo or git checkout. --fix mode rsyncs canonical -> mirror and re-checks, then prompts to re-stage. githooks/pre-commit wraps the script. Commits abort on drift so the issue surfaces at publish time, not at the next session's startup rsync. Two new Makefile targets: - make sync-check [FIX=1] runs the script (FIX=1 passes --fix through). - make install-githooks sets core.hooksPath=githooks (idempotent). scripts/tests/sync-check.bats holds 8 tests covering clean, drift-per-path, --fix, extra-file removal, missing canonical, and outside-git. All eight pass. This catches the exact drift I had to fix manually during this morning's audit pass. The mirror's open-tasks.org PROPERTIES drawer sat below a sub-heading because the mirror commit was older than canonical. --- scripts/tests/sync-check.bats | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 scripts/tests/sync-check.bats (limited to 'scripts/tests/sync-check.bats') diff --git a/scripts/tests/sync-check.bats b/scripts/tests/sync-check.bats new file mode 100644 index 0000000..df775b3 --- /dev/null +++ b/scripts/tests/sync-check.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats +# +# Tests for scripts/sync-check.sh +# +# Sandboxes a fake rulesets-shaped git repo under $BATS_TMPDIR, drops the +# real script in via PATH override, and exercises clean / drift / --fix +# behavior. + +setup() { + SANDBOX="$BATS_TEST_TMPDIR/repo" + SCRIPT_SRC="$BATS_TEST_DIRNAME/../sync-check.sh" + mkdir -p "$SANDBOX/scripts" "$SANDBOX/claude-templates/.ai/workflows" \ + "$SANDBOX/claude-templates/.ai/scripts" "$SANDBOX/.ai/workflows" \ + "$SANDBOX/.ai/scripts" + cp "$SCRIPT_SRC" "$SANDBOX/scripts/sync-check.sh" + chmod +x "$SANDBOX/scripts/sync-check.sh" + cd "$SANDBOX" + git init -q + git config user.email test@example.com + git config user.name test + echo "protocols content" > claude-templates/.ai/protocols.org + echo "protocols content" > .ai/protocols.org + echo "workflow a" > claude-templates/.ai/workflows/a.org + echo "workflow a" > .ai/workflows/a.org + echo "script b" > claude-templates/.ai/scripts/b.sh + echo "script b" > .ai/scripts/b.sh +} + +@test "clean tree: exit 0" { + run scripts/sync-check.sh + [ "$status" -eq 0 ] +} + +@test "drift in protocols.org: exit 1, names the file" { + echo "drifted" > .ai/protocols.org + run scripts/sync-check.sh + [ "$status" -eq 1 ] + [[ "$output" == *"protocols.org"* ]] +} + +@test "drift in workflows/: exit 1, names workflows" { + echo "workflow a modified" > .ai/workflows/a.org + run scripts/sync-check.sh + [ "$status" -eq 1 ] + [[ "$output" == *"workflows"* ]] +} + +@test "drift in scripts/: exit 1, names scripts" { + echo "script b modified" > .ai/scripts/b.sh + run scripts/sync-check.sh + [ "$status" -eq 1 ] + [[ "$output" == *"scripts"* ]] +} + +@test "drift with --fix: syncs and exits 0" { + echo "drifted" > .ai/protocols.org + run scripts/sync-check.sh --fix + [ "$status" -eq 0 ] + run cat .ai/protocols.org + [ "$output" = "protocols content" ] +} + +@test "extra file in mirror: --delete removes it" { + echo "stale" > .ai/workflows/stale.org + run scripts/sync-check.sh --fix + [ "$status" -eq 0 ] + [ ! -f .ai/workflows/stale.org ] +} + +@test "missing canonical: exit 2 with clear error" { + rm -rf claude-templates + run scripts/sync-check.sh + [ "$status" -eq 2 ] + [[ "$output" == *"not a rulesets-shaped repo"* ]] +} + +@test "outside a git checkout: exit 2" { + cd "$BATS_TEST_TMPDIR" + mkdir not-a-repo + cp "$SCRIPT_SRC" not-a-repo/sync-check.sh + chmod +x not-a-repo/sync-check.sh + cd not-a-repo + run ./sync-check.sh + [ "$status" -eq 2 ] + [[ "$output" == *"not inside a git checkout"* ]] +} -- cgit v1.2.3