#!/usr/bin/env bats # # Tests for scripts/sweep-gitignore-tooling.sh — backfill the personal-tooling # gitignore set across existing gitignore-mode AI projects. # # Strategy: scaffold fake AI projects (a .ai/protocols.org marker + a git repo) # under a temp root, run the sweep against that root, and assert on each # project's .gitignore. REAL_REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" SWEEP="$REAL_REPO/scripts/sweep-gitignore-tooling.sh" setup() { ROOT="$(mktemp -d -t sweep-bats.XXXXXX)" } teardown() { rm -rf "$ROOT" } # Scaffold a project: $1 = name, $2 = initial .gitignore content (may be empty), # $3 = "git" to init a repo (default), "nogit" to skip. make_project() { local name="$1" gitignore="${2-}" git="${3-git}" mkdir -p "$ROOT/$name/.ai" echo "marker" > "$ROOT/$name/.ai/protocols.org" [ -n "$gitignore" ] && printf '%s' "$gitignore" > "$ROOT/$name/.gitignore" [ "$git" = "git" ] && (cd "$ROOT/$name" && git init -q) return 0 } @test "sweep: gitignore-mode project missing all three gets them appended" { make_project gimode $'# tooling\n.ai/\n' run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] grep -qFx ".claude/" "$ROOT/gimode/.gitignore" grep -qFx "CLAUDE.md" "$ROOT/gimode/.gitignore" grep -qFx "AGENTS.md" "$ROOT/gimode/.gitignore" } @test "sweep: is idempotent — a second run adds nothing" { make_project gimode $'.ai/\n' bash "$SWEEP" "$ROOT" >/dev/null run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"already complete"* ]] # No line is duplicated. [ "$(grep -cFx '.claude/' "$ROOT/gimode/.gitignore")" -eq 1 ] [ "$(grep -cFx 'CLAUDE.md' "$ROOT/gimode/.gitignore")" -eq 1 ] [ "$(grep -cFx 'AGENTS.md' "$ROOT/gimode/.gitignore")" -eq 1 ] } @test "sweep: track-mode project (.ai/ not ignored) is skipped untouched" { make_project trackmode $'# build\nout/\n' run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"track-mode"* ]] # .gitignore is untouched — none of the tooling lines were added. ! grep -qFx ".claude/" "$ROOT/trackmode/.gitignore" ! grep -qFx "CLAUDE.md" "$ROOT/trackmode/.gitignore" } @test "sweep: partial project gets only the missing lines" { make_project partial $'.ai/\n.claude/\n' run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [ "$(grep -cFx '.claude/' "$ROOT/partial/.gitignore")" -eq 1 ] grep -qFx "CLAUDE.md" "$ROOT/partial/.gitignore" grep -qFx "AGENTS.md" "$ROOT/partial/.gitignore" } @test "sweep: --dry-run reports without writing" { make_project gimode $'.ai/\n' run bash "$SWEEP" --dry-run "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"would add"* ]] [[ "$output" == *"no files written"* ]] # Nothing was actually written. ! grep -qFx ".claude/" "$ROOT/gimode/.gitignore" } @test "sweep: warns when a now-ignored path is already tracked" { make_project tracked $'.ai/\n' echo "# project rules" > "$ROOT/tracked/CLAUDE.md" (cd "$ROOT/tracked" && git add CLAUDE.md && git -c user.email=t@t -c user.name=t commit -qm seed) run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"WARN"* ]] [[ "$output" == *"CLAUDE.md is currently tracked"* ]] } @test "sweep: non-git project with .ai/ marker is skipped" { make_project nogit $'.ai/\n' "" nogit run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"not a git checkout"* ]] } @test "sweep: anchored /.ai/ is recognized as gitignore-mode, appends anchored" { make_project anchored $'/.ai/\n' run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" != *"anchored — track-mode"* ]] grep -qFx "/.claude/" "$ROOT/anchored/.gitignore" grep -qFx "/CLAUDE.md" "$ROOT/anchored/.gitignore" grep -qFx "/AGENTS.md" "$ROOT/anchored/.gitignore" } @test "sweep: anchored partial project gets only the missing lines" { make_project anchoredpartial $'/.ai/\n/.claude/\n' run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] # /.claude/ already present in anchored form — not re-added in either form. [ "$(grep -cFx '/.claude/' "$ROOT/anchoredpartial/.gitignore")" -eq 1 ] ! grep -qFx ".claude/" "$ROOT/anchoredpartial/.gitignore" grep -qFx "/CLAUDE.md" "$ROOT/anchoredpartial/.gitignore" grep -qFx "/AGENTS.md" "$ROOT/anchoredpartial/.gitignore" } @test "sweep: anchored gitignore-mode is idempotent" { make_project anchored2 $'/.ai/\n' bash "$SWEEP" "$ROOT" >/dev/null run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"already complete"* ]] [ "$(grep -cFx '/.claude/' "$ROOT/anchored2/.gitignore")" -eq 1 ] } @test "sweep: track-mode with tracked tooling and a non-cjennings.net remote warns" { make_project publictrack $'out/\n' echo "# project rules" > "$ROOT/publictrack/CLAUDE.md" (cd "$ROOT/publictrack" \ && git add CLAUDE.md \ && git -c user.email=t@t -c user.name=t commit -qm seed \ && git remote add origin git@github.com:someone/publictrack.git) run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" == *"WARN"* ]] [[ "$output" == *"publicly reachable"* ]] # Still track-mode: nothing written to its .gitignore. ! grep -qFx ".claude/" "$ROOT/publictrack/.gitignore" } @test "sweep: track-mode with tracked tooling on a cjennings.net remote stays quiet" { make_project privatetrack $'out/\n' echo "# project rules" > "$ROOT/privatetrack/CLAUDE.md" (cd "$ROOT/privatetrack" \ && git add CLAUDE.md \ && git -c user.email=t@t -c user.name=t commit -qm seed \ && git remote add origin git@cjennings.net:privatetrack.git) run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" != *"publicly reachable"* ]] } @test "sweep: the bare cjennings ssh-alias remote counts as private too" { make_project aliastrack $'out/\n' echo "# project rules" > "$ROOT/aliastrack/CLAUDE.md" (cd "$ROOT/aliastrack" \ && git add CLAUDE.md \ && git -c user.email=t@t -c user.name=t commit -qm seed \ && git remote add origin git@cjennings:aliastrack.git) run bash "$SWEEP" "$ROOT" [ "$status" -eq 0 ] [[ "$output" != *"publicly reachable"* ]] }