aboutsummaryrefslogtreecommitdiff
path: root/scripts/sweep-gitignore-tooling.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/sweep-gitignore-tooling.sh')
-rwxr-xr-xscripts/sweep-gitignore-tooling.sh118
1 files changed, 118 insertions, 0 deletions
diff --git a/scripts/sweep-gitignore-tooling.sh b/scripts/sweep-gitignore-tooling.sh
new file mode 100755
index 0000000..63fc066
--- /dev/null
+++ b/scripts/sweep-gitignore-tooling.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+# sweep-gitignore-tooling.sh — backfill the personal-tooling gitignore set
+# across existing gitignore-mode AI projects.
+#
+# install-ai.sh sets up the ignore set at bootstrap, but a project bootstrapped
+# before the set grew (or one that only ever ignored .ai/) is left exposed — an
+# accidental `git add` or a /codify run can commit a personal CLAUDE.md, the
+# private rule copies under .claude/, or an AGENTS.md. This sweep backfills the
+# full set into every gitignore-mode project that's behind.
+#
+# For each AI project (a directory with .ai/protocols.org) under the search
+# roots, if it's a git checkout in gitignore mode (.ai/ already appears in its
+# .gitignore), ensure .ai/, .claude/, CLAUDE.md, and AGENTS.md are all ignored.
+# Append only the missing lines, so a re-run is a no-op.
+#
+# Track-mode projects (.ai/ NOT in .gitignore) are skipped by design: they
+# track their tooling on purpose — team repos sharing config with teammates who
+# don't run rulesets, or private-remote personal repos where the history IS the
+# project.
+#
+# A line added here only stops *future* commits. If a target path is already
+# tracked, the ignore has no effect until it's untracked; the sweep warns so
+# the path can be `git rm --cached`-ed by hand.
+#
+# Usage: sweep-gitignore-tooling.sh [--dry-run] [SEARCH_ROOT ...]
+# --dry-run Report what would change without writing.
+# SEARCH_ROOT ... Directories to scan (default: ~/code ~/projects ~/.emacs.d).
+
+set -euo pipefail
+
+IGNORE_SET=('.ai/' '.claude/' 'CLAUDE.md' 'AGENTS.md')
+
+dry_run=0
+roots=()
+for arg in "$@"; do
+ case "$arg" in
+ --dry-run) dry_run=1 ;;
+ -h|--help)
+ sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
+ exit 0
+ ;;
+ -*)
+ echo "ERROR: unknown flag: $arg (use --help for usage)" >&2
+ exit 2
+ ;;
+ *) roots+=("$arg") ;;
+ esac
+done
+
+if [ "${#roots[@]}" -eq 0 ]; then
+ roots=("$HOME/code" "$HOME/projects" "$HOME/.emacs.d")
+fi
+
+# Discover AI projects: a directory holding .ai/protocols.org.
+mapfile -t projects < <(
+ for root in "${roots[@]}"; do
+ [ -d "$root" ] || continue
+ find "$root" -maxdepth 3 -type f -path '*/.ai/protocols.org' 2>/dev/null \
+ | sed 's|/\.ai/protocols\.org$||'
+ done | sort -u
+)
+
+swept=0 skipped=0 complete=0
+
+for project in "${projects[@]}"; do
+ name="$(basename "$project")"
+ gi="$project/.gitignore"
+
+ if [ ! -d "$project/.git" ]; then
+ echo "skip $name — not a git checkout"
+ skipped=$((skipped + 1))
+ continue
+ fi
+
+ # Gitignore mode iff .ai/ is already ignored. Otherwise track-mode: leave it.
+ if [ ! -f "$gi" ] || ! grep -qFx '.ai/' "$gi"; then
+ echo "skip $name — track-mode (.ai/ not gitignored)"
+ skipped=$((skipped + 1))
+ continue
+ fi
+
+ needed=()
+ for pat in "${IGNORE_SET[@]}"; do
+ grep -qFx "$pat" "$gi" || needed+=("$pat")
+ done
+
+ if [ "${#needed[@]}" -eq 0 ]; then
+ echo "ok $name — already complete"
+ complete=$((complete + 1))
+ continue
+ fi
+
+ if [ "$dry_run" -eq 1 ]; then
+ echo "DRY $name — would add: ${needed[*]}"
+ else
+ {
+ echo ""
+ echo "# Claude Code per-project tooling (swept $(date +%Y-%m-%d))"
+ printf '%s\n' "${needed[@]}"
+ } >> "$gi"
+ echo "swept $name — added: ${needed[*]}"
+ fi
+ swept=$((swept + 1))
+
+ # Warn on any newly-ignored path that's already tracked — the ignore won't
+ # untrack it.
+ for pat in "${needed[@]}"; do
+ path="${pat%/}"
+ if git -C "$project" ls-files --error-unmatch "$path" >/dev/null 2>&1; then
+ echo " WARN $name: $path is currently tracked — 'git -C $project rm --cached -r $path' to untrack"
+ fi
+ done
+done
+
+echo
+echo "Summary: $swept swept, $complete already complete, $skipped skipped (of ${#projects[@]} projects)."
+[ "$dry_run" -eq 1 ] && echo "(dry-run — no files written)"
+exit 0