diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-10 01:14:46 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-10 01:14:46 -0500 |
| commit | cc72aa635f733da36010567c8718b1ede7622c52 (patch) | |
| tree | 10d6d6bf0c86fd284349e1c7dac83cb6cbc07c70 /scripts/sweep-gitignore-tooling.sh | |
| parent | c401d6d3807a2ffb76a9ec4af8dc783b2c918bfd (diff) | |
| download | rulesets-cc72aa635f733da36010567c8718b1ede7622c52.tar.gz rulesets-cc72aa635f733da36010567c8718b1ede7622c52.zip | |
feat(install-ai): gitignore the full personal-tooling set, add backfill sweep
A gitignore-mode project only ignored .ai/. CLAUDE.md was left untracked but not ignored, so an accidental git add or a codify run could still commit a personal CLAUDE.md, the private rule copies under .claude/, or an AGENTS.md. install-ai now ignores the whole set (.ai/, .claude/, CLAUDE.md, AGENTS.md) at bootstrap, line-idempotent so an existing .gitignore isn't duplicated.
.claude/ goes in the set because it's rulesets-owned (copies of claude-rules/*.md plus the language bundle's rules, hooks, and settings), re-synced from rulesets every startup, so git isn't how it travels. Ignoring it also keeps those private rule copies out of the repo, which ignoring CLAUDE.md alone would miss. The gate is unchanged: track-mode projects (personal/doc repos, team repos sharing config) keep tracking the set.
sweep-gitignore-tooling.sh backfills the set across existing gitignore-mode projects, idempotent and skipping track-mode by design. It warns when a now-ignored path is already tracked, since the ignore won't untrack it. protocols.org states the policy once.
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 |
