diff --git a/functions/_agents_init_ensure_gitignore.fish b/functions/_agents_init_ensure_gitignore.fish index 724af70..f1f5549 100644 --- a/functions/_agents_init_ensure_gitignore.fish +++ b/functions/_agents_init_ensure_gitignore.fish @@ -7,7 +7,7 @@ # DESCRIPTION # Appends any patterns not already covered by the project's .gitignore. # Uses git check-ignore for accurate rule matching (catches wildcards -# and parent-dir globs). Falls back to a plain string search when the +# and parent-dir globs). Falls back to a whole-line string search when the # root is not a git repository. Leading / is stripped from each pattern # before the path-based check so root-anchored patterns (e.g. /AGENTS.md) # are matched correctly. @@ -58,7 +58,9 @@ function _agents_init_ensure_gitignore git -C "$root" check-ignore -q --no-index "$check_path" 2>/dev/null and set already 1 else if test -f "$gitignore" - grep -qF "$pattern" "$gitignore" + # Whole-line match: a substring match treats a negation line + # such as "!AGENTS/foo" as covering the pattern "AGENTS/". + grep -qxF "$pattern" "$gitignore" and set already 1 end if test $already -eq 0 diff --git a/functions/agents-init.fish b/functions/agents-init.fish index ca36e19..539e4ef 100644 --- a/functions/agents-init.fish +++ b/functions/agents-init.fish @@ -17,6 +17,11 @@ # agent-related files into it, and replaces them with symlinks so the outer # project never tracks agent files directly. # +# Scaffolding runs only inside a git repository, or in a directory that +# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a +# no-op, so running an agent CLI in an arbitrary directory does not +# create a repository there. +# # File layout after setup: # AGENTS/AGENTS.md canonical agent spec (real file) # AGENTS/CLAUDE.md real file (if CLAUDE.md existed separately) @@ -127,9 +132,20 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi end # --verbose is explicit default; no-op but accepted for completeness - # Resolve target root: git root if available, otherwise cwd + # Only scaffold inside a git repository, or where an agent file already + # exists. Falling back to (pwd) meant `claude --version` in any + # directory created an AGENTS/ repo, two root symlinks, and a docs/ + # tree there. set -l root (git rev-parse --show-toplevel 2>/dev/null) - test -z "$root"; and set root (pwd) + if test -z "$root" + if test -e (pwd)/AGENTS.md -o -e (pwd)/CLAUDE.md -o -d (pwd)/AGENTS + set root (pwd) + else + test $verbose -eq 1 + and echo "$c_dim→ Not a git repository; skipping AGENTS/ scaffolding$c_reset" + return 0 + end + end set -l agents_dir "$root/AGENTS" set -l plugins_dir "$agents_dir/plugins" diff --git a/tests/test-agents-vault.fish b/tests/test-agents-vault.fish index 9a92f0b..972b0bc 100644 --- a/tests/test-agents-vault.fish +++ b/tests/test-agents-vault.fish @@ -2053,6 +2053,38 @@ check "agents-init: a rejected commit returns non-zero" 1 "$ibrc" check "agents-init: a rejected commit says nothing was recorded" true (string match -q '*nothing recorded*' -- (cat $ierr); and echo true; or echo false) check "agents-init: a rejected commit really recorded nothing" $ibhead (git -C $ip/AGENTS rev-list --count HEAD) +# ─────────────────────── scaffolding guard ────────────────────────────── +# Resolving the root to (pwd) meant running an agent CLI in any directory +# created an AGENTS/ repo, root symlinks and a docs/ tree there. +echo "" +echo "== agents-init scaffolding guard ==" + +set -l plain (mktemp -d) +set -ga TMPDIRS $plain +pushd $plain >/dev/null +agents-init --silent 2>/dev/null +popd >/dev/null +check "no scaffold in a non-git dir" false (test -e $plain/AGENTS; and echo true; or echo false) + +# A pre-existing agent file still opts the directory in. +set -l plainagents (mktemp -d) +set -ga TMPDIRS $plainagents +touch $plainagents/AGENTS.md +pushd $plainagents >/dev/null +agents-init --silent 2>/dev/null +popd >/dev/null +check "an existing AGENTS.md still scaffolds" true (test -d $plainagents/AGENTS; and echo true; or echo false) + +# ──────────────────── gitignore fallback anchoring ────────────────────── +echo "" +echo "== gitignore fallback anchoring ==" + +set -l ng (mktemp -d) +set -ga TMPDIRS $ng +printf '!AGENTS/foo\n' >$ng/.gitignore +_agents_init_ensure_gitignore $ng "test" "AGENTS/" >/dev/null +check "negation does not count as ignored" true (grep -qx 'AGENTS/' $ng/.gitignore; and echo true; or echo false) + # ──────────────────────── hermeticity assertion ──────────────────────── # The whole suite must never have touched the real global agent state. The # failure this guards is specific: a global-memory sync with no test