fix(agents-tools): harden version-bump test (CDPATH + hooks isolation), drop LFS band-aid

This commit is contained in:
2026-06-17 21:29:05 -04:00
parent 7d277e52bd
commit 5cbb01497c
4 changed files with 157 additions and 44 deletions
-3
View File
@@ -1,3 +0,0 @@
# Explicitly exclude agents-tools scripts from Git LFS auto-tracking.
# These are small shell scripts that must be stored as regular git objects.
scripts/agents-tools/** !filter !diff !merge text eol=lf
+5
View File
@@ -293,3 +293,8 @@ pyrightconfig.json
# agents-init --agents
AGENTS/
# ────────────────────────────────────────────────────────
# ──────────────── Added by agents-init ──────────────────
# agents-init --plugins
docs/devlogs
# ────────────────────────────────────────────────────────
+146 -41
View File
@@ -17,7 +17,18 @@
# or symlink → AGENTS.md (single-source case)
# <root>/AGENTS.md → AGENTS/AGENTS.md
# <root>/CLAUDE.md → AGENTS/CLAUDE.md
# docs/<plug> → ../AGENTS/plugins/<plug>
# AGENTS/plans superpowers plans (real dir, .gitkeep)
# AGENTS/specs superpowers specs (real dir, .gitkeep)
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
# docs/superpowers/plans → ../../AGENTS/plans (always)
# docs/superpowers/specs → ../../AGENTS/specs (always)
# docs/plans → ../AGENTS/plans (only if docs/plans existed)
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
#
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
#
# With no flags, runs both --agents and --plugins setup. At the end of
# every invocation, commits any uncommitted changes in the AGENTS/ sub-repo
@@ -25,7 +36,7 @@
#
# ARGUMENTS
# -a, --agents Set up AGENTS/ repo + AGENTS.md / CLAUDE.md symlinks only
# -p, --plugins Set up AGENTS/ repo + plugins dirs + docs/ symlinks only
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
# -v, --verbose Print all per-step output (default)
# -q, --quiet Print one summary line only if changes were made
# -s, --silent Suppress all output; errors only (standard UNIX convention)
@@ -61,7 +72,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
echo "$c_head""Options:$c_reset"
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md / CLAUDE.md symlinks only"
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plugins dirs and docs/ symlinks only"
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
echo " $c_flag-s$c_reset, $c_flag--silent$c_reset Suppress all output; only errors are printed"
@@ -248,19 +259,6 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
# ─────────────────────────── --plugins mode ──────────────────────────────
if test $do_plugins -eq 1
# Ensure AGENTS/plugins/ dirs exist with .gitkeep
for dir in "$plugins_dir" "$plugins_dir/superpowers" "$plugins_dir/plans" "$plugins_dir/specs"
if not test -d "$dir"
if not mkdir -p "$dir"
echo "$c_err""Error: could not create "(string replace "$root/" "" "$dir")"/$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created "(string replace "$root/" "" "$dir")"/$c_reset"
end
test -f "$dir/.gitkeep"; or touch "$dir/.gitkeep"
end
set -l docs_dir "$root/docs"
if not test -d "$docs_dir"
@@ -272,39 +270,146 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
test $verbose -eq 1; and echo "$c_ok→ Created docs/$c_reset"
end
for plug in superpowers plans specs
set -l docs_target "$docs_dir/$plug"
set -l agents_target "$plugins_dir/$plug"
# Consolidate plans/ and specs/ directly under AGENTS/ (no plugins/ layer).
# Real source dirs from any known layout are merged into the canonical
# AGENTS/<tgt> and removed: docs/<tgt>, docs/superpowers/<tgt>,
# AGENTS/plugins/<tgt>, AGENTS/plugins/superpowers/<tgt> (legacy).
# Symlinks: docs/superpowers/<tgt> always (superpowers skills expect it);
# docs/<tgt> only when it had existed as a real directory.
for tgt in plans specs
set -l canonical "$agents_dir/$tgt"
# If a real directory exists in docs/, migrate its contents to AGENTS/plugins/
if test -d "$docs_target"; and not test -L "$docs_target"
set -l contents (ls -A "$docs_target" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -rn "$docs_target/." "$agents_target/"
echo "$c_err""Error: could not copy docs/$plug → AGENTS/plugins/$plug$c_reset" >&2
return 1
end
end
if not rm -rf "$docs_target"
echo "$c_err""Error: could not remove docs/$plug after copy$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved docs/$plug → AGENTS/plugins/$plug$c_reset"
# Remember whether docs/<tgt> was a real dir (drives its symlink below)
set -l docs_tgt_was_real 0
if test -d "$docs_dir/$tgt"; and not test -L "$docs_dir/$tgt"
set docs_tgt_was_real 1
end
# Create symlink docs/<plug> → ../AGENTS/plugins/<plug>
if not test -L "$docs_target"
if not ln -s "../AGENTS/plugins/$plug" "$docs_target"
echo "$c_err""Error: could not create docs/$plug symlink$c_reset" >&2
return 1
test -d "$canonical"; or mkdir -p "$canonical"
# Candidate real source dirs to merge into the canonical dir
set -l srcs "$docs_dir/$tgt" "$plugins_dir/$tgt" "$plugins_dir/superpowers/$tgt"
# Only follow docs/superpowers/<tgt> when docs/superpowers is a real
# dir; if it is a legacy symlink, its target is already covered by the
# AGENTS/plugins/superpowers/<tgt> source above.
if not test -L "$docs_dir/superpowers"
set -a srcs "$docs_dir/superpowers/$tgt"
end
for src in $srcs
if test -d "$src"; and not test -L "$src"
set -l rel (string replace "$root/" "" "$src")
set -l contents (command ls -A "$src" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -rn "$src/." "$canonical/"
echo "$c_err""Error: could not merge $rel → AGENTS/$tgt$c_reset" >&2
return 1
end
end
if not rm -rf "$src"
echo "$c_err""Error: could not remove $rel after merge$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Merged $rel → AGENTS/$tgt$c_reset"
end
end
test -f "$canonical/.gitkeep"; or touch "$canonical/.gitkeep"
# docs/<tgt> symlink: only recreated when it had been a real directory
set -l docs_link "$docs_dir/$tgt"
if test $docs_tgt_was_real -eq 1
if not test -L "$docs_link"
if not ln -s "../AGENTS/$tgt" "$docs_link"
echo "$c_err""Error: could not create docs/$tgt symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked docs/$tgt → AGENTS/$tgt$c_reset"
end
else if test -L "$docs_link"; and test (readlink "$docs_link") != "../AGENTS/$tgt"
# Stale legacy symlink (pointed into AGENTS/plugins) → drop it
rm -f "$docs_link"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked docs/$plug → AGENTS/plugins/$plug$c_reset"
test $verbose -eq 1; and echo "$c_warn→ Removed stale docs/$tgt symlink$c_reset"
end
end
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --plugins" "docs/superpowers" "docs/plans" "docs/specs")
# docs/superpowers/ must be a real dir holding the plans/ + specs/ symlinks.
# Replace any legacy docs/superpowers symlink (content merged above).
if test -L "$docs_dir/superpowers"
rm -f "$docs_dir/superpowers"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed legacy docs/superpowers symlink$c_reset"
end
if not test -d "$docs_dir/superpowers"
if not mkdir -p "$docs_dir/superpowers"
echo "$c_err""Error: could not create docs/superpowers/$c_reset" >&2
return 1
end
end
# docs/superpowers/<tgt> symlinks — always present (superpowers default)
for tgt in plans specs
set -l sp_link "$docs_dir/superpowers/$tgt"
if not test -L "$sp_link"
if not ln -s "../../AGENTS/$tgt" "$sp_link"
echo "$c_err""Error: could not create docs/superpowers/$tgt symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked docs/superpowers/$tgt → AGENTS/$tgt$c_reset"
else if test (readlink "$sp_link") != "../../AGENTS/$tgt"
rm -f "$sp_link"
ln -s "../../AGENTS/$tgt" "$sp_link"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Relinked docs/superpowers/$tgt → AGENTS/$tgt$c_reset"
end
end
# Drop the now-empty legacy AGENTS/plugins/ layer entirely
if test -d "$plugins_dir"
rm -rf "$plugins_dir"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed legacy AGENTS/plugins/$c_reset"
end
# ── AGENTS/devlogs (standard location for agent dev logs) ─────────────
set -l devlogs_dir "$agents_dir/devlogs"
set -l docs_devlogs "$docs_dir/devlogs"
if test -d "$docs_devlogs"; and not test -L "$docs_devlogs"
# Migrate an existing docs/devlogs into AGENTS/devlogs, then symlink
test -d "$devlogs_dir"; or mkdir -p "$devlogs_dir"
set -l contents (command ls -A "$docs_devlogs" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -rn "$docs_devlogs/." "$devlogs_dir/"
echo "$c_err""Error: could not copy docs/devlogs → AGENTS/devlogs$c_reset" >&2
return 1
end
end
if not rm -rf "$docs_devlogs"
echo "$c_err""Error: could not remove docs/devlogs after copy$c_reset" >&2
return 1
end
if not ln -s "../AGENTS/devlogs" "$docs_devlogs"
echo "$c_err""Error: could not create docs/devlogs symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved docs/devlogs → AGENTS/devlogs$c_reset"
else if not test -d "$devlogs_dir"
if not mkdir -p "$devlogs_dir"
echo "$c_err""Error: could not create AGENTS/devlogs$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/devlogs/$c_reset"
end
test -f "$devlogs_dir/.gitkeep"; or touch "$devlogs_dir/.gitkeep"
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --plugins" "docs/superpowers" "docs/plans" "docs/specs" "docs/devlogs")
if test -n "$_gi"
set changed 1
test $verbose -eq 1; and echo $_gi
@@ -4,6 +4,9 @@
#
# Self-contained tests for version-bump. Run: bash test-version-bump.sh
set -u
# Neutralize an inherited CDPATH: bash `cd` echoes the resolved path when a
# target is found via CDPATH, which would corrupt the command substitutions below.
CDPATH=
HERE="$(cd "$(dirname "$0")" && pwd)"
SCRIPT="$HERE/version-bump"
fail() { echo "FAIL: $1"; exit 1; }
@@ -14,6 +17,9 @@ new_repo() {
git -C "$d" config user.email t@t
git -C "$d" config user.name t
git -C "$d" config commit.gpgsign false
# Isolate from any global core.hooksPath so the test is hermetic. The e2e
# block re-points hooksPath at the repo's own .agents-tools/hooks.
git -C "$d" config core.hooksPath /dev/null
printf '%s\n' "$d"
}