Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion bin/sync-agents
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
#
# Skip rule: if a target already exists as a real file/dir (NOT a symlink),
# warn and skip — never clobber project-local content.
#
# Prune rule: after linking, a dangling symlink that points into $DOTFILES (an
# orphan left when a manifest entry's source was deleted or renamed) is removed
# from each project repo. Only provably-broken dotfiles-pointing links are
# touched — never valid links or links pointing elsewhere. The removal lands as
# a staged deletion on the repo's next `git add -A`, mirroring how an added link
# awaits the user's commit. Pass --no-prune to disable.

set -uo pipefail

Expand Down Expand Up @@ -96,6 +103,19 @@ AGENT_HOME_LINKS=(
"AGENTS.md:$HOME/.amp/AGENTS.md"
)

# Flag parsing — strip --no-prune out of the positional args so the --list
# check below still sees only positional input. PRUNE defaults on.
PRUNE=1
_args=()
for _a in ${@+"$@"}; do
case "$_a" in
--no-prune) PRUNE=0 ;;
*) _args+=("$_a") ;;
esac
done
set -- ${_args[@]+"${_args[@]}"}
unset _a _args

if [[ "${1:-}" == "--list" ]]; then
echo "Project-repo manifest (sources in $DOTFILES):"
for link in "${PROJECT_LINKS[@]}"; do echo " ${link%%:*}"; done
Expand All @@ -105,13 +125,16 @@ if [[ "${1:-}" == "--list" ]]; then
echo
echo "Agent home-dir links:"
for link in "${AGENT_HOME_LINKS[@]}"; do echo " ${link%%:*} → ${link#*:}"; done
echo
echo "Prune: dangling symlinks into $DOTFILES are removed from project repos (--no-prune to disable)."
exit 0
fi

[[ -d "$DOTFILES" ]] || { fail "$DOTFILES not found"; exit 1; }

LINKED=0
RETARGETED=0
PRUNED=0
SKIPPED=0
FAILED=0

Expand Down Expand Up @@ -220,6 +243,54 @@ expand_worktrees() {
' <<<"$listed"
}

# ---------------------------------------------------------------------------
# Helper: collapse '.'/'..' segments in an absolute path textually. No
# filesystem access — works even when the target was deleted (realpath would
# fail). String-built, no arrays, so it's safe under macOS bash 3.2 + `set -u`.
# ---------------------------------------------------------------------------
normalize_path() {
local part result="" IFS=/
for part in $1; do
case "$part" in
''|.) ;;
..) result="${result%/*}" ;;
*) result="$result/$part" ;;
esac
done
printf '%s\n' "${result:-/}"
}

# ---------------------------------------------------------------------------
# Helper: prune orphaned symlinks under a repo — links that are dangling AND
# point into $DOTFILES (a manifest entry whose source was deleted/renamed).
# Filesystem-only: the removal becomes a staged deletion on the repo's next
# `git add -A`, just like an added link awaits commit. Skips .git/node_modules/
# .zig-cache; never touches valid links or links pointing outside $DOTFILES.
# ---------------------------------------------------------------------------
prune_orphans() {
local root="$1" link tgt abs
while IFS= read -r link; do
[[ -e "$link" ]] && continue # target resolves → not an orphan
tgt=$(readlink "$link") || continue
[[ "$tgt" = /* ]] || tgt="$(dirname "$link")/$tgt"
abs=$(normalize_path "$tgt")
case "$abs" in
"$DOTFILES"/*|"$DOTFILES")
if rm "$link"; then
warn " pruned orphan: ${link#$root/} ↛ $tgt"
PRUNED=$((PRUNED + 1))
else
fail " rm failed: $link"
FAILED=$((FAILED + 1))
fi
;;
esac
done < <(find "$root" -type l \
-not -path '*/.git/*' \
-not -path '*/node_modules/*' \
-not -path '*/.zig-cache/*' 2>/dev/null)
}

# ---------------------------------------------------------------------------
# Pass 1: project repos (expanded across worktrees).
# ---------------------------------------------------------------------------
Expand All @@ -241,6 +312,7 @@ for repo in "${KISHORES_TARGETS[@]}"; do
dst="${link#*:}"
make_link "$DOTFILES/$src" "$wt/$dst" "$dst"
done
[[ $PRUNE -eq 1 ]] && prune_orphans "$wt"
done < <(expand_worktrees "$repo")
done

Expand All @@ -260,5 +332,5 @@ for link in "${AGENT_HOME_LINKS[@]}"; do
done

echo
echo "summary: linked=$LINKED retargeted=$RETARGETED skipped=$SKIPPED failed=$FAILED"
echo "summary: linked=$LINKED retargeted=$RETARGETED pruned=$PRUNED skipped=$SKIPPED failed=$FAILED"
[[ $FAILED -eq 0 ]] && ok "agents sync complete" || { fail "some links failed"; exit 1; }
Loading