fix(agents-cleanup): keep links on failed moves, check rewrites, refuse symlinked AGENTS/
This commit is contained in:
@@ -127,6 +127,10 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
set -l agents_dir "$root/AGENTS"
|
set -l agents_dir "$root/AGENTS"
|
||||||
set -l has_agents 0
|
set -l has_agents 0
|
||||||
set -l has_repo 0
|
set -l has_repo 0
|
||||||
|
if test -L "$agents_dir"
|
||||||
|
echo "$c_err""Error: AGENTS is a symlink to "(readlink "$agents_dir")"; replace it with a real directory (or remove the link) first$c_reset" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
if test -d "$agents_dir"
|
if test -d "$agents_dir"
|
||||||
set has_agents 1
|
set has_agents 1
|
||||||
# Resolved, because link targets are compared after realpath, which
|
# Resolved, because link targets are compared after realpath, which
|
||||||
@@ -208,11 +212,17 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
for e in $extras
|
for e in $extras
|
||||||
if contains -- "$e" $extras_ignored
|
if contains -- "$e" $extras_ignored
|
||||||
echo " AGENTS/$e (ignored -- not in bundle)" >&2
|
echo " AGENTS/$e (ignored -- not in bundle)" >&2
|
||||||
|
else if test $has_repo -eq 0
|
||||||
|
echo " AGENTS/$e (not in bundle)" >&2
|
||||||
else
|
else
|
||||||
echo " AGENTS/$e" >&2
|
echo " AGENTS/$e" >&2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
echo "Move them out of AGENTS/ by hand, or re-run with --drop-extras (the history bundle keeps every file not marked ignored)." >&2
|
if test $has_repo -eq 1
|
||||||
|
echo "Move them out of AGENTS/ by hand, or re-run with --drop-extras (the history bundle keeps every file not marked ignored)." >&2
|
||||||
|
else
|
||||||
|
echo "Move them out of AGENTS/ by hand, or re-run with --drop-extras (AGENTS/ is not a git repository, so there is no bundle and they are lost)." >&2
|
||||||
|
end
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -319,10 +329,22 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
for i in (seq (count $keep))
|
for i in (seq (count $keep))
|
||||||
set -l link $keep[$i]
|
set -l link $keep[$i]
|
||||||
set -l rel (string replace -- "$root/" "" "$link")
|
set -l rel (string replace -- "$root/" "" "$link")
|
||||||
if not rm -f "$link"; or not command mv "$keep_tgt[$i]" "$link"
|
set -l was (readlink "$link")
|
||||||
|
if not test -e "$keep_tgt[$i]"
|
||||||
|
echo "$c_err""Error: AGENTS/ target for $rel is missing; nothing changed for it$c_reset" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if not rm -f "$link"
|
||||||
echo "$c_err""Error: could not restore $rel from AGENTS/; re-run to resume$c_reset" >&2
|
echo "$c_err""Error: could not restore $rel from AGENTS/; re-run to resume$c_reset" >&2
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
if not command mv "$keep_tgt[$i]" "$link"
|
||||||
|
# Put the link back: without it the target looks like an unlinked
|
||||||
|
# extra and a re-run would refuse.
|
||||||
|
ln -s -- "$was" "$link"
|
||||||
|
echo "$c_err""Error: could not restore $rel from AGENTS/; link left in place$c_reset" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Restored $rel$c_reset"
|
test $verbose -eq 1; and echo "$c_ok→ Restored $rel$c_reset"
|
||||||
|
|
||||||
@@ -357,8 +379,13 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
if set -q body[1]; and string match -qr -- '\S' $body
|
if set -q body[1]; and string match -qr -- '\S' $body
|
||||||
printf '%s\n' $body >"$link"
|
if test "$(printf '%s\n' $body | string collect)" != "$(string collect <"$link")"
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Removed the AGENTS/ directive from $rel$c_reset"
|
if not printf '%s\n' $body >"$link"
|
||||||
|
echo "$c_err""Error: could not rewrite $rel$c_reset" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
test $verbose -eq 1; and echo "$c_ok→ Removed the AGENTS/ directive from $rel$c_reset"
|
||||||
|
end
|
||||||
else
|
else
|
||||||
rm -f "$link"
|
rm -f "$link"
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Removed $rel (only the directive, no user content)$c_reset"
|
test $verbose -eq 1; and echo "$c_ok→ Removed $rel (only the directive, no user content)$c_reset"
|
||||||
@@ -398,6 +425,10 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
# writes, so blocks from older header variants go too. A header with no
|
# writes, so blocks from older header variants go too. A header with no
|
||||||
# footer before the next header is left in place (and reported).
|
# footer before the next header is left in place (and reported).
|
||||||
if test -f "$gitignore"; and grep -q 'Added by agents-init' "$gitignore"
|
if test -f "$gitignore"; and grep -q 'Added by agents-init' "$gitignore"
|
||||||
|
# The unterminated-block warning is verbose-only, so a quiet or silent
|
||||||
|
# re-run stays silent.
|
||||||
|
set -l awk_err /dev/stderr
|
||||||
|
test $verbose -eq 1; or set awk_err /dev/null
|
||||||
set -l kept (awk '
|
set -l kept (awk '
|
||||||
{ line[NR] = $0 }
|
{ line[NR] = $0 }
|
||||||
END {
|
END {
|
||||||
@@ -418,24 +449,34 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc
|
|||||||
out[++n] = line[i]
|
out[++n] = line[i]
|
||||||
}
|
}
|
||||||
for (i = 1; i <= n; i++) print out[i]
|
for (i = 1; i <= n; i++) print out[i]
|
||||||
}' "$gitignore")
|
}' "$gitignore" 2>$awk_err)
|
||||||
if test $status -ne 0
|
if test $status -ne 0
|
||||||
echo "$c_err""Error: could not rewrite .gitignore$c_reset" >&2
|
echo "$c_err""Error: could not rewrite .gitignore$c_reset" >&2
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
if set -q kept[1]; and string match -qr -- '\S' $kept
|
# Identical output means only unterminated blocks were found: nothing
|
||||||
printf '%s\n' $kept >"$gitignore"
|
# to write, nothing to report, and a re-run stays silent.
|
||||||
else if test $in_git -eq 1; and git -C "$root" ls-files --error-unmatch -- .gitignore >/dev/null 2>&1
|
if test "$(printf '%s\n' $kept | string collect)" != "$(string collect <"$gitignore")"
|
||||||
# Tracked: someone committed it, so it is not agents-init's to
|
if set -q kept[1]; and string match -qr -- '\S' $kept
|
||||||
# delete. Leave it empty; the change shows in git status.
|
if not printf '%s\n' $kept >"$gitignore"
|
||||||
true >"$gitignore"
|
echo "$c_err""Error: could not rewrite .gitignore$c_reset" >&2
|
||||||
else
|
return 1
|
||||||
# Untracked and nothing but agents-init's blocks: agents-init
|
end
|
||||||
# created it.
|
else if test $in_git -eq 1; and git -C "$root" ls-files --error-unmatch -- .gitignore >/dev/null 2>&1
|
||||||
rm -f "$gitignore"
|
# Tracked: someone committed it, so it is not agents-init's to
|
||||||
|
# delete. Leave it empty; the change shows in git status.
|
||||||
|
if not true >"$gitignore"
|
||||||
|
echo "$c_err""Error: could not rewrite .gitignore$c_reset" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Untracked and nothing but agents-init's blocks: agents-init
|
||||||
|
# created it.
|
||||||
|
rm -f "$gitignore"
|
||||||
|
end
|
||||||
|
set changed 1
|
||||||
|
test $verbose -eq 1; and echo "$c_ok→ Removed agents-init blocks from .gitignore$c_reset"
|
||||||
end
|
end
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Removed agents-init blocks from .gitignore$c_reset"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# ────────────────────────────── Summary ───────────────────────────────
|
# ────────────────────────────── Summary ───────────────────────────────
|
||||||
|
|||||||
@@ -293,5 +293,91 @@ check "rejected commit: root link untouched" AGENTS/AGENTS.md (readlink $f1/AGEN
|
|||||||
set -l fb $XDG_STATE_HOME/agents-cleanup/*.bundle
|
set -l fb $XDG_STATE_HOME/agents-cleanup/*.bundle
|
||||||
check "rejected commit: no bundle" 0 (count $fb)
|
check "rejected commit: no bundle" 0 (count $fb)
|
||||||
|
|
||||||
|
section "agents-cleanup: failed move keeps the link, re-run resumes"
|
||||||
|
fresh_state
|
||||||
|
set -l w1 (scaffolded_repo)
|
||||||
|
chmod a-w $w1/AGENTS/functions
|
||||||
|
pushd $w1 >/dev/null
|
||||||
|
set -l w1rc (agents-cleanup --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "failed mv: exits 1" 1 "$w1rc"
|
||||||
|
check "failed mv: link still a symlink" true (test -L $w1/functions/AGENTS.md; and echo true; or echo false)
|
||||||
|
chmod u+w $w1/AGENTS/functions
|
||||||
|
pushd $w1 >/dev/null
|
||||||
|
set -l w2rc (agents-cleanup --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "failed mv re-run: exits 0" 0 "$w2rc"
|
||||||
|
check "failed mv re-run: real file, content kept" user-scoped (test -L $w1/functions/AGENTS.md; or string collect <$w1/functions/AGENTS.md)
|
||||||
|
|
||||||
|
section "agents-cleanup: failed directive rewrite keeps AGENTS/"
|
||||||
|
fresh_state
|
||||||
|
set -l k1 (new_repo)
|
||||||
|
printf '%s\n' '# Title' '' '> **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' '> x' '' body >$k1/AGENTS.md
|
||||||
|
pushd $k1 >/dev/null
|
||||||
|
agents-init --silent 2>/dev/null
|
||||||
|
popd >/dev/null
|
||||||
|
chmod a-w $k1/AGENTS/AGENTS.md
|
||||||
|
pushd $k1 >/dev/null
|
||||||
|
set -l k1rc (agents-cleanup --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
chmod u+w $k1/AGENTS/AGENTS.md $k1/AGENTS.md 2>/dev/null
|
||||||
|
check "failed rewrite: exits 1" 1 "$k1rc"
|
||||||
|
check "failed rewrite: AGENTS/ kept" true (test -d $k1/AGENTS; and echo true; or echo false)
|
||||||
|
|
||||||
|
section "agents-cleanup: unterminated .gitignore block is left alone, quietly"
|
||||||
|
fresh_state
|
||||||
|
set -l u1 (new_repo)
|
||||||
|
printf '%s\n' user-line '' '# ──── Added by agents-init ────' AGENTS/ >$u1/.gitignore
|
||||||
|
set -l ubefore (string collect <$u1/.gitignore)
|
||||||
|
pushd $u1 >/dev/null
|
||||||
|
set -l u1rc (agents-cleanup --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "unterminated: exits 0" 0 "$u1rc"
|
||||||
|
check "unterminated: file byte-identical" "$ubefore" (string collect <$u1/.gitignore)
|
||||||
|
pushd $u1 >/dev/null
|
||||||
|
set -l u2out (agents-cleanup --quiet 2>&1)
|
||||||
|
popd >/dev/null
|
||||||
|
check "unterminated: quiet re-run prints nothing" "" "$u2out"
|
||||||
|
|
||||||
|
section "agents-cleanup: symlinked AGENTS refuses"
|
||||||
|
fresh_state
|
||||||
|
set -l l1 (new_repo)
|
||||||
|
set -l ext (mktemp -d)
|
||||||
|
set -a TMPDIRS $ext
|
||||||
|
echo precious >$ext/file
|
||||||
|
ln -s $ext $l1/AGENTS
|
||||||
|
pushd $l1 >/dev/null
|
||||||
|
set -l l1rc (agents-cleanup --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "symlinked AGENTS: exits 1" 1 "$l1rc"
|
||||||
|
check "symlinked AGENTS: target file survives" precious (string collect <$ext/file)
|
||||||
|
|
||||||
|
section "agents-cleanup: extras without a bundle"
|
||||||
|
fresh_state
|
||||||
|
set -l n2 (new_repo)
|
||||||
|
mkdir $n2/AGENTS
|
||||||
|
echo stray >$n2/AGENTS/stray.md
|
||||||
|
pushd $n2 >/dev/null
|
||||||
|
set -l n2err (agents-cleanup 2>&1 >/dev/null)
|
||||||
|
popd >/dev/null
|
||||||
|
check "no-repo extras: labelled not in bundle" true (string match -q -- '*stray.md (not in bundle)*' "$n2err"; and echo true; or echo false)
|
||||||
|
check "no-repo extras: hint does not promise a bundle" false (string match -q -- '*bundle keeps*' "$n2err"; and echo true; or echo false)
|
||||||
|
|
||||||
|
section "agents-cleanup: user AGENTS.md variants"
|
||||||
|
fresh_state
|
||||||
|
set -l v1 (new_repo)
|
||||||
|
printf '%s\n' '> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' '> x' >$v1/AGENTS.md
|
||||||
|
set -l v2 (new_repo)
|
||||||
|
printf '%s\n' '# Mine' '' 'no directive here' >$v2/AGENTS.md
|
||||||
|
set -l v2before (string collect <$v2/AGENTS.md)
|
||||||
|
for v in $v1 $v2
|
||||||
|
pushd $v >/dev/null
|
||||||
|
agents-init --silent 2>/dev/null
|
||||||
|
agents-cleanup --silent 2>/dev/null
|
||||||
|
popd >/dev/null
|
||||||
|
end
|
||||||
|
check "directive-only: deleted" false (test -e $v1/AGENTS.md -o -L $v1/AGENTS.md; and echo true; or echo false)
|
||||||
|
check "no directive: content identical" "$v2before" (string collect <$v2/AGENTS.md)
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
report
|
report
|
||||||
|
|||||||
Reference in New Issue
Block a user