From f625cf5c6e38c1abb98491b4e1b8ce63c57b8712 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 20 Jul 2026 07:53:05 -0500 Subject: feat: track working/ from creation, gitignore temp/ in both modes working/ is the tracked home of in-progress work, version-controlled from creation rather than staged locally until it graduates. Filing on completion reorganizes durable artifacts into permanent homes. It doesn't mark when they became durable. Ephemeral, disposable artifacts go in a gitignored temp/ (or /tmp), never working/. install-ai.sh emits a temp/ ignore in both track and gitignore modes. Ephemerality is independent of whether a project tracks its .ai/ tooling, so temp/ rides neither the track .gitkeep step nor the gitignore-only tooling block. working/ is never ignored, on purpose. sweep-gitignore-tooling.sh backfills temp/ as a separate mode-independent pass, not an IGNORE_SET member: folding it into that set would skip every track-mode project, the set that most needs it. While implementing I confirmed the canonical machinery never ignored working/ (the tooling set is only .ai/ .claude/ CLAUDE.md AGENTS.md), so a project that ignored working/ did so as a local deviation. Documents the convention in working-files.md and mirrors it in protocols.org. 7 new bats cover temp/ in both modes, never working/, and idempotency. --- scripts/install-ai.sh | 18 +++++++++++++ scripts/sweep-gitignore-tooling.sh | 32 ++++++++++++++++++++++- scripts/tests/install-ai.bats | 36 ++++++++++++++++++++++++++ scripts/tests/sweep-gitignore-tooling.bats | 41 ++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/install-ai.sh b/scripts/install-ai.sh index 0c90f64..8c04e22 100755 --- a/scripts/install-ai.sh +++ b/scripts/install-ai.sh @@ -179,6 +179,24 @@ case "$track_mode" in ;; esac +# Ignore temp/ in BOTH track and gitignore modes (not the "not-a-git-repo" +# case). temp/ is the home for ephemeral, disposable, regenerable artifacts — +# throwaway regardless of whether the project tracks its .ai/ tooling, so it +# rides neither the track .gitkeep step nor the gitignore-only tooling block. +# working/ is deliberately NOT ignored: it's the tracked home of in-progress +# work, version-controlled from creation (see working-files.md). Idempotent: +# accepts either the unanchored `temp/` or anchored `/temp/` form. +if [ -n "$track_mode" ]; then + gi="$project/.gitignore" + if ! { [ -f "$gi" ] && { grep -qFx 'temp/' "$gi" || grep -qFx '/temp/' "$gi"; }; }; then + { + [ -s "$gi" ] && echo "" + echo "# Ephemeral working artifacts (throwaway; see working-files.md)" + echo "temp/" + } >> "$gi" + fi +fi + # Banner. echo echo "Done." diff --git a/scripts/sweep-gitignore-tooling.sh b/scripts/sweep-gitignore-tooling.sh index 68bfe2d..7194d6c 100755 --- a/scripts/sweep-gitignore-tooling.sh +++ b/scripts/sweep-gitignore-tooling.sh @@ -153,7 +153,37 @@ for project in "${projects[@]}"; do done done +# temp/ backfill — mode-independent. Unlike the personal-tooling set above +# (gitignore-mode only, track-mode deliberately skipped), temp/ holds ephemeral +# artifacts in every project regardless of whether it tracks its .ai/, so both +# track- and gitignore-mode projects get it. A separate pass, not an IGNORE_SET +# member — folding it into that set would miss every track-mode project, which +# is exactly the set that needs it. working/ is never ignored: it's the tracked +# home of in-progress work. +temp_swept=0 +for project in "${projects[@]}"; do + [ -d "$project/.git" ] || continue + name="$(basename "$project")" + gi="$project/.gitignore" + + if [ -f "$gi" ] && { grep -qFx 'temp/' "$gi" || grep -qFx '/temp/' "$gi"; }; then + continue + fi + + if [ "$dry_run" -eq 1 ]; then + echo "DRY $name — would add: temp/" + else + { + [ -s "$gi" ] && echo "" + echo "# Ephemeral working artifacts (throwaway; see working-files.md; swept $(date +%Y-%m-%d))" + echo "temp/" + } >> "$gi" + echo "temp $name — added: temp/" + fi + temp_swept=$((temp_swept + 1)) +done + echo -echo "Summary: $swept swept, $complete already complete, $skipped skipped (of ${#projects[@]} projects)." +echo "Summary: $swept swept, $complete already complete, $skipped skipped (of ${#projects[@]} projects); $temp_swept temp/ backfilled." [ "$dry_run" -eq 1 ] && echo "(dry-run — no files written)" exit 0 diff --git a/scripts/tests/install-ai.bats b/scripts/tests/install-ai.bats index 9c6040c..14f1152 100644 --- a/scripts/tests/install-ai.bats +++ b/scripts/tests/install-ai.bats @@ -200,3 +200,39 @@ LAUNCHER="$REAL_REPO/claude-templates/bin/install-ai" [ "$status" -eq 0 ] [[ "$output" == *"Bootstrap .ai/"* ]] } + +# --- temp/ ephemeral-artifacts ignore (working/ tracked-from-creation ruling) --- + +@test "install-ai --gitignore: ignores temp/ but never working/" { + mkdir -p "$TEST_HOME/code/fresh" + (cd "$TEST_HOME/code/fresh" && git init -q) + + run bash "$INSTALL_AI" --gitignore "$TEST_HOME/code/fresh" + + [ "$status" -eq 0 ] + grep -qFx "temp/" "$TEST_HOME/code/fresh/.gitignore" + # working/ is the tracked home of in-progress work — it must never be ignored. + ! grep -qEx "/?working/?" "$TEST_HOME/code/fresh/.gitignore" +} + +@test "install-ai --track: ignores temp/ even in track mode" { + mkdir -p "$TEST_HOME/code/tracked" + (cd "$TEST_HOME/code/tracked" && git init -q) + + run bash "$INSTALL_AI" --track "$TEST_HOME/code/tracked" + + [ "$status" -eq 0 ] + # temp/ is ephemeral regardless of whether the project tracks its .ai/ tooling. + grep -qFx "temp/" "$TEST_HOME/code/tracked/.gitignore" +} + +@test "install-ai: temp/ ignore is idempotent (not re-added)" { + mkdir -p "$TEST_HOME/code/fresh" + (cd "$TEST_HOME/code/fresh" && git init -q) + printf 'temp/\n' > "$TEST_HOME/code/fresh/.gitignore" + + run bash "$INSTALL_AI" --gitignore "$TEST_HOME/code/fresh" + + [ "$status" -eq 0 ] + [ "$(grep -cFx 'temp/' "$TEST_HOME/code/fresh/.gitignore")" -eq 1 ] +} diff --git a/scripts/tests/sweep-gitignore-tooling.bats b/scripts/tests/sweep-gitignore-tooling.bats index f18eac5..240c3be 100644 --- a/scripts/tests/sweep-gitignore-tooling.bats +++ b/scripts/tests/sweep-gitignore-tooling.bats @@ -190,3 +190,44 @@ make_project() { [ "$status" -eq 0 ] [[ "$output" != *"publicly reachable"* ]] } + +# --- temp/ ephemeral-artifacts backfill (mode-independent) --- + +@test "sweep: adds temp/ to a gitignore-mode project" { + make_project gimode $'.ai/\n' + + run bash "$SWEEP" "$ROOT" + + [ "$status" -eq 0 ] + grep -qFx "temp/" "$ROOT/gimode/.gitignore" +} + +@test "sweep: adds temp/ to a TRACK-mode project too (temp/ is mode-independent)" { + make_project trackmode $'# build\nout/\n' + + run bash "$SWEEP" "$ROOT" + + [ "$status" -eq 0 ] + # The tooling set is skipped in track mode, but temp/ is not. + grep -qFx "temp/" "$ROOT/trackmode/.gitignore" + ! grep -qFx ".ai/" "$ROOT/trackmode/.gitignore" +} + +@test "sweep: never adds working/" { + make_project gimode $'.ai/\n' + + run bash "$SWEEP" "$ROOT" + + [ "$status" -eq 0 ] + ! grep -qEx "/?working/?" "$ROOT/gimode/.gitignore" +} + +@test "sweep: temp/ backfill is idempotent" { + make_project gimode $'.ai/\ntemp/\n' + bash "$SWEEP" "$ROOT" >/dev/null + + run bash "$SWEEP" "$ROOT" + + [ "$status" -eq 0 ] + [ "$(grep -cFx 'temp/' "$ROOT/gimode/.gitignore")" -eq 1 ] +} -- cgit v1.2.3