diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-15 17:22:28 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-15 17:22:28 -0500 |
| commit | d364cf2a8520f733d9438fa1e32cb3010c655ee2 (patch) | |
| tree | 23c0a91ab4f5028d0728be708cf45f1b7da59990 /scripts | |
| parent | 94782eea3df22289fb556481f9569a9284c7ac50 (diff) | |
| download | rulesets-d364cf2a8520f733d9438fa1e32cb3010c655ee2.tar.gz rulesets-d364cf2a8520f733d9438fa1e32cb3010c655ee2.zip | |
feat(make): add install-ai target for bootstrapping .ai/ in fresh projects
scripts/install-ai.sh copies canonical .ai/ content from claude-templates/ into a fresh project. Rsyncs protocols.org, workflows/, scripts/, someday-maybe.org as-is; templates notes.org with project-name and date placeholders substituted; creates empty sessions/, references/, retrospectives/ dirs.
Two tracking modes: TRACK=1 adds .gitkeep files inside otherwise-empty dirs so they survive in git history; GITIGNORE=1 appends .ai/ to the project's .gitignore so session records stay local. Prompts interactively if neither flag is set.
Refuses if PROJECT/.ai/ already exists with a message pointing to `make audit APPLY=1` for sync of existing installs. Without a PROJECT argument, fzf-picks from ~/code/* + ~/projects/* git checkouts that don't already have .ai/.
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/install-ai.sh | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/scripts/install-ai.sh b/scripts/install-ai.sh new file mode 100755 index 0000000..d30ab84 --- /dev/null +++ b/scripts/install-ai.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +# install-ai.sh — bootstrap .ai/ in a fresh project from canonical source. +# +# Refuses if PROJECT/.ai/ already exists. Use `make audit APPLY=1` to +# update an existing .ai/ instead. +# +# Usage: install-ai.sh [--track | --gitignore] [PROJECT_PATH] +# +# If PROJECT_PATH is omitted, fzf-pick from ~/code/* + ~/projects/* git +# checkouts that don't already have a .ai/ directory. + +set -euo pipefail + +REPO="$(cd "$(dirname "$0")/.." && pwd)" +CANONICAL="$REPO/claude-templates/.ai" + +project="" +track_mode="" + +for arg in "$@"; do + case "$arg" in + --track) track_mode="track" ;; + --gitignore) track_mode="gitignore" ;; + -h|--help) + cat <<EOF +Usage: $(basename "$0") [--track | --gitignore] [PROJECT_PATH] + +Bootstrap .ai/ in PROJECT_PATH from canonical content at $CANONICAL. + + --track Track .ai/ in the project's git history (with .gitkeep + files inside otherwise-empty sessions/, references/, + retrospectives/). + --gitignore Append .ai/ to the project's .gitignore so session + records stay local. + +If neither flag is given, the script prompts interactively. +If PROJECT_PATH is omitted, fzf-picks from ~/code/* + ~/projects/* +git checkouts without an existing .ai/. +EOF + exit 0 + ;; + -*) + echo "ERROR: unknown flag: $arg (use --help for usage)" >&2 + exit 2 + ;; + *) + if [ -n "$project" ]; then + echo "ERROR: multiple PROJECT_PATH arguments: $project and $arg" >&2 + exit 2 + fi + project="$arg" + ;; + esac +done + +# Project resolution: argument or fzf-pick. +if [ -z "$project" ]; then + if ! command -v fzf >/dev/null 2>&1; then + echo "ERROR: PROJECT_PATH not given and fzf is not installed" >&2 + exit 2 + fi + project=$( + find "$HOME/code" "$HOME/projects" -maxdepth 2 -mindepth 1 -type d -name .git 2>/dev/null \ + | sed 's|/\.git$||' \ + | while read -r p; do + [ ! -e "$p/.ai" ] && echo "$p" + done \ + | sort \ + | fzf --prompt="Target project (without .ai/)> " + ) + if [ -z "$project" ]; then + echo "No target selected." >&2 + exit 1 + fi +fi + +# Validate project path. +if [ ! -d "$project" ]; then + echo "ERROR: $project is not a directory" >&2 + exit 2 +fi +project="$(cd "$project" && pwd)" + +# Refuse if .ai/ already exists. +if [ -e "$project/.ai" ]; then + echo "ERROR: $project/.ai already exists" >&2 + echo " Use 'make audit APPLY=1' to sync an existing .ai/ instead." >&2 + exit 2 +fi + +# Warn if not a git checkout. +if [ ! -d "$project/.git" ]; then + echo "WARN: $project is not a git checkout — track/gitignore handling will be skipped" +fi + +# Decide track vs gitignore (skip if no .git). +if [ -d "$project/.git" ] && [ -z "$track_mode" ]; then + echo + echo "Track .ai/ in git, or add to .gitignore?" + echo " [t] track — .ai/ enters git history, session records persist for the team" + echo " [g] gitignore — .ai/ stays local, session records private to this machine" + printf "Choice [t/g]: " + read -r answer + case "$answer" in + t|T|track) track_mode="track" ;; + g|G|gitignore) track_mode="gitignore" ;; + *) + echo "ERROR: invalid choice (rerun with --track or --gitignore)" >&2 + exit 2 + ;; + esac +fi + +echo +echo "Installing .ai/ at $project/.ai/ ..." + +# Create directory structure. +mkdir -p "$project/.ai/sessions" +mkdir -p "$project/.ai/references" +mkdir -p "$project/.ai/retrospectives" + +# Rsync canonical content (everything except notes.org, which gets templated). +rsync -a "$CANONICAL/protocols.org" "$project/.ai/protocols.org" +rsync -a "$CANONICAL/someday-maybe.org" "$project/.ai/someday-maybe.org" +rsync -a --delete "$CANONICAL/workflows/" "$project/.ai/workflows/" +rsync -a --delete "$CANONICAL/scripts/" "$project/.ai/scripts/" + +# Seed notes.org with placeholder substitution. +project_name="$(basename "$project")" +today="$(date +%Y-%m-%d)" +sed "s|\[Project Name\]|$project_name|g; s|\[Date\]|$today|g" \ + "$CANONICAL/notes.org" > "$project/.ai/notes.org" + +# Tracking setup. +case "$track_mode" in + track) + # .gitkeep files so otherwise-empty dirs survive in git. + touch "$project/.ai/sessions/.gitkeep" + touch "$project/.ai/references/.gitkeep" + touch "$project/.ai/retrospectives/.gitkeep" + ;; + gitignore) + if [ -f "$project/.gitignore" ] && grep -qFx '.ai/' "$project/.gitignore"; then + : # already present + else + { + [ -s "$project/.gitignore" ] && echo "" + echo "# Claude Code per-project tooling" + echo ".ai/" + } >> "$project/.gitignore" + fi + ;; +esac + +# Banner. +echo +echo "Done." +echo +echo " project: $project" +echo " tracking: ${track_mode:-not-a-git-repo}" +echo " notes.org: project=$project_name, date=$today" +echo +echo "Next steps:" +echo " - Add a language bundle: make install-lang PROJECT=$project" +echo " - Start a Claude session: cd $project && claude" |
