feat(agents-vault): back up curated agent memory to a host-scoped vault repo #126
@@ -0,0 +1,55 @@
|
|||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
# _agents_repo_sync <dir> <message>
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
# Pulls (when an upstream is configured), stages everything, and commits
|
||||||
|
# with <message>. Shared by agents-init and agents-vault.
|
||||||
|
#
|
||||||
|
# A failed rebase is aborted and nothing is committed. Committing blindly
|
||||||
|
# after a failed pull would stage conflict markers and record them under a
|
||||||
|
# routine-looking message, so the failure is surfaced instead: the repo is
|
||||||
|
# left clean at local HEAD for the user to resolve by hand.
|
||||||
|
#
|
||||||
|
# Commits are made with commit.gpgsign=false so a pinentry prompt can
|
||||||
|
# never block a shell or an agent launch.
|
||||||
|
#
|
||||||
|
# ARGUMENTS
|
||||||
|
# dir Absolute path to the git repository
|
||||||
|
# message Commit subject used when there is something to commit
|
||||||
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 0 Committed, or nothing needed committing
|
||||||
|
# 1 <dir> is not a git repository
|
||||||
|
# 2 Rebase conflict; aborted, nothing committed
|
||||||
|
#
|
||||||
|
# RETURNS
|
||||||
|
# A single "→ Committed (<sha>) <subject>" line on stdout when it
|
||||||
|
# commits; nothing when there was nothing to do.
|
||||||
|
#
|
||||||
|
# EXAMPLE
|
||||||
|
# _agents_repo_sync /path/to/AGENTS "chore: sync AGENTS repository"
|
||||||
|
function _agents_repo_sync --argument-names dir msg
|
||||||
|
test -n "$dir" -a -n "$msg"; or return 1
|
||||||
|
test -d "$dir/.git"; or return 1
|
||||||
|
|
||||||
|
if git -C "$dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||||
|
if not git -C "$dir" pull --rebase --autostash -q >/dev/null 2>/dev/null
|
||||||
|
git -C "$dir" rebase --abort >/dev/null 2>/dev/null
|
||||||
|
echo "_agents_repo_sync: rebase conflict in $dir; aborted, left at local HEAD" >&2
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
git -C "$dir" add -A 2>/dev/null
|
||||||
|
set -l status_out (git -C "$dir" status --porcelain 2>/dev/null)
|
||||||
|
test -n "$status_out"; or return 0
|
||||||
|
|
||||||
|
if git -C "$dir" -c commit.gpgsign=false commit -q -m "$msg" 2>/dev/null
|
||||||
|
set -l sha (git -C "$dir" rev-parse --short HEAD 2>/dev/null)
|
||||||
|
set -l subject (git -C "$dir" log -1 --pretty=%s 2>/dev/null)
|
||||||
|
echo "→ Committed ($sha) $subject"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -136,6 +136,53 @@ ln -s $p/one $p/link
|
|||||||
_agents_repo_ensure_symlink $p/link $p/two >/dev/null
|
_agents_repo_ensure_symlink $p/link $p/two >/dev/null
|
||||||
check "repins a wrong link" (path resolve $p/two) (path resolve $p/link)
|
check "repins a wrong link" (path resolve $p/two) (path resolve $p/link)
|
||||||
|
|
||||||
|
# ─────────────────────────── sync policy ───────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "== _agents_repo_sync =="
|
||||||
|
|
||||||
|
set -l s (new_repo)
|
||||||
|
echo hello >$s/a.md
|
||||||
|
_agents_repo_sync $s "chore: test" >/dev/null
|
||||||
|
check "commits new content" 1 (git -C $s rev-list --count HEAD)
|
||||||
|
|
||||||
|
# Nothing to do: no second commit, no output.
|
||||||
|
set -l out (_agents_repo_sync $s "chore: test")
|
||||||
|
check "idempotent, no new commit" 1 (git -C $s rev-list --count HEAD)
|
||||||
|
check "idempotent, silent" "" "$out"
|
||||||
|
|
||||||
|
# Conflict: two clones diverge with conflicting commits on the same line.
|
||||||
|
# (Merely leaving "ours" uncommitted in the worktree isn't enough to force
|
||||||
|
# a rebase conflict -- with nothing local to replay, --autostash's rebase
|
||||||
|
# step fast-forwards cleanly and only the stash *pop* would conflict,
|
||||||
|
# leaving "theirs" committed on HEAD with "ours" stranded in the stash.
|
||||||
|
# Committing "ours" locally first means the rebase itself must replay a
|
||||||
|
# real commit over "theirs" on the same line, which is where the intended
|
||||||
|
# conflict-and-abort path actually lives.)
|
||||||
|
set -l origin (mktemp -d); set -ga TMPDIRS $origin
|
||||||
|
git -C $origin init -q --bare
|
||||||
|
git -C $s remote add origin $origin
|
||||||
|
git -C $s push -q -u origin HEAD:refs/heads/main 2>/dev/null
|
||||||
|
|
||||||
|
set -l clone (mktemp -d); set -ga TMPDIRS $clone
|
||||||
|
git clone -q $origin $clone
|
||||||
|
git -C $clone config user.email t@t
|
||||||
|
git -C $clone config user.name t
|
||||||
|
git -C $clone config commit.gpgsign false
|
||||||
|
git -C $clone config core.hooksPath /dev/null
|
||||||
|
echo theirs >$clone/a.md
|
||||||
|
git -C $clone commit -qam theirs
|
||||||
|
git -C $clone push -q origin HEAD:main
|
||||||
|
|
||||||
|
echo ours >$s/a.md
|
||||||
|
git -C $s commit -qam ours
|
||||||
|
set -l before_count (git -C $s rev-list --count HEAD)
|
||||||
|
set -l rc (_agents_repo_sync $s "chore: test" 2>/dev/null; echo $status)
|
||||||
|
check "conflict returns 2" 2 "$rc"
|
||||||
|
check "conflict leaves no rebase in progress" false (test -d $s/.git/rebase-merge -o -d $s/.git/rebase-apply; and echo true; or echo false)
|
||||||
|
check "conflict commits nothing" $before_count (git -C $s rev-list --count HEAD)
|
||||||
|
check "conflict content survives" ours (cat $s/a.md)
|
||||||
|
check "conflict left no markers" false (grep -q '<<<<<<<' $s/a.md; and echo true; or echo false)
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
echo ""
|
echo ""
|
||||||
echo (math $TESTS_RUN - $TESTS_FAILED)"/$TESTS_RUN passed"
|
echo (math $TESTS_RUN - $TESTS_FAILED)"/$TESTS_RUN passed"
|
||||||
|
|||||||
Reference in New Issue
Block a user