diff options
Diffstat (limited to 'scripts/sweep-gitignore-tooling.sh')
| -rwxr-xr-x | scripts/sweep-gitignore-tooling.sh | 118 |
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 |
