fix(agents-vault): repair the adopt rollback and see dot-led entries
The adopt rollback restored the worktree but not the index. Every move it makes is a plain rename as far as git is concerned -- the stash move out from under the index most of all -- so a rolled-back adopt left a half-applied rename staged against a clean vault. No bytes were at risk and the next ordinary run's `git add -A` healed it, but a hand `git commit` in that window recorded the half-applied state. Both rollback sites now re-read projects/ once the worktree is whole again, the stash restore included. projects/ is named whole rather than the two entries, because `git add` refuses a pathspec that matches nothing -- which one of the two always is, once it has been moved back -- and then stages neither. The stash itself moves from the vault root into .git/, where neither the entry walk nor `git add -A` can reach it, so a crash between the two moves can no longer leave junk at the vault root for the next run to commit. A vault whose .git is not a directory falls back to the root, which the scaffolded .gitignore now covers. --status and --restore walked the vault with a fish glob, which does not match dot-led names. A dot-led slug is both reachable and sanctioned: the sibling-bare-mirror idiom (`git remote add origin ../mirror.git`) keys as ..-mirror, a dot-led host keys as .hidden.example.com-o-r, and --adopt accepts a leading dot on purpose. Such an entry is scaffolded, linked, committed and pushed normally, yet --status under-reported it and batch --restore left that project unlinked, both without saying so. Both walks now list the directory instead. The --adopt completion gains -A for the same reason: an entry that cannot be completed reads as one that is not there. The now-fatal push failure is painted as an error rather than a warning, matching its sibling on the commit path. The header notes that --adopt does not pin a name. The slug is re-derived on every run, so the next ordinary run migrates the adopted entry back to the canonical key, memory and live link following. Behaviour unchanged; only the documentation gap is closed. Tests, 173 -> 209 checks. The whole stash branch of --adopt was uncovered, because the existing atomicity test adopts onto a slug with no entry at all: a successful stash-adopt and a stash-adopt whose relink fails are both pinned now, the latter asserting an empty `git status --porcelain` and a still-reachable live memory. The stash location is pinned by making the vault root unwritable for the duration, which only a stash at the root would need. agents-vault's own propagation of a failed sync had no test at all -- the third recurrence of fish's branchless-`if` false zero here -- so both ways it can fail are now driven end to end: a rejecting pre-commit at the vault's own core.hooksPath, and a real rebase conflict against a bare remote. A dot-led entry is asserted in --status and in --restore.
This commit is contained in:
@@ -11,7 +11,10 @@ complete -c agents-vault -l restore -d 'Relink everything possible, report the r
|
||||
complete -c agents-vault -l status -d 'Show entries, link health, remote, orphans'
|
||||
# --adopt takes an existing vault slug, so offer the entries that are
|
||||
# actually there; the vault may not exist yet, in which case this is empty.
|
||||
complete -c agents-vault -l adopt -r -a '(command ls -1 (_agents_vault_dir)/projects 2>/dev/null)' -d 'Bind this project to an existing vault entry'
|
||||
# -A because a dot-led slug is legitimate (a relative-path remote keys as
|
||||
# ..-mirror), and an entry that cannot be completed reads as one that is
|
||||
# not there.
|
||||
complete -c agents-vault -l adopt -r -a '(command ls -1A (_agents_vault_dir)/projects 2>/dev/null)' -d 'Bind this project to an existing vault entry'
|
||||
complete -c agents-vault -l remote -r -d 'Set the vault remote URL'
|
||||
complete -c agents-vault -s v -l verbose -d 'Print all per-step output (default)'
|
||||
complete -c agents-vault -s q -l quiet -d 'Print one summary line only if changed'
|
||||
|
||||
@@ -120,6 +120,13 @@
|
||||
# it defaults to off so a backgrounded push can never hang or prompt
|
||||
# invisibly underneath a starting agent.
|
||||
#
|
||||
# --adopt rebinds an entry; it does not pin its name. The slug is
|
||||
# re-derived from the project on every run, so the next ordinary run
|
||||
# migrates the adopted entry straight back to the canonical key, carrying
|
||||
# the memory and the live link with it. That is the point rather than a
|
||||
# wart: adopting is how a mismatched or ambiguous binding is repaired, not
|
||||
# how an entry is given a permanent name of its own.
|
||||
#
|
||||
# An entry's origin file records the project path once, when the entry is
|
||||
# created, and is never refreshed. A project that later moves on disk
|
||||
# therefore keeps a stale path there and --restore degrades to reporting
|
||||
@@ -237,10 +244,21 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
echo ""
|
||||
echo "$c_head""Entries:$c_reset"
|
||||
set -l seen 0
|
||||
for entry in "$vault"/projects/*
|
||||
# Listed rather than globbed: fish's * skips dot-led names, and a
|
||||
# dot-led slug is both reachable and sanctioned. A relative-path
|
||||
# remote -- the sibling-bare-mirror idiom, `git remote add origin
|
||||
# ../mirror.git` -- keys as ..-mirror, a dot-led host keys as
|
||||
# .hidden.example.com-o-r, and --adopt accepts a leading dot on
|
||||
# purpose. Such an entry is scaffolded, linked and committed like any
|
||||
# other, so a glob here would under-report the vault without ever
|
||||
# saying that it had. The 2>/dev/null is load-bearing: a vault with
|
||||
# no projects/ is ordinary (running agents-vault outside any git
|
||||
# repo makes exactly that one) and a bare ls would spew.
|
||||
for name in (command ls -A "$vault/projects" 2>/dev/null)
|
||||
set -l entry "$vault/projects/$name"
|
||||
test -d "$entry"; or continue
|
||||
set seen 1
|
||||
set -l eslug (path basename "$entry")
|
||||
set -l eslug $name
|
||||
set -l count (command ls -A "$entry/claude/memory" 2>/dev/null | count)
|
||||
set -l want (path resolve "$entry/claude/memory")
|
||||
set -l linked 0
|
||||
@@ -290,7 +308,12 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
printf '%s\n' \
|
||||
'# SQLite sidecars are never safe to commit mid-write.' \
|
||||
'*.db-wal' \
|
||||
'*.db-shm' >"$vault/.gitignore"
|
||||
'*.db-shm' \
|
||||
'' \
|
||||
'# --adopt stashes the entry it is about to overwrite inside .git/,' \
|
||||
'# out of reach of `git add -A`. This covers the fallback location' \
|
||||
'# it uses when .git is not a directory.' \
|
||||
'/.adopt-stash' >"$vault/.gitignore"
|
||||
set changed 1
|
||||
end
|
||||
|
||||
@@ -423,17 +446,37 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
# So a contentless target entry is moved aside rather than deleted,
|
||||
# and the origin note is appended only once the relink has
|
||||
# succeeded. Both are undone below if it does not. The stash lives
|
||||
# at the vault root, not under projects/, so a crash between the
|
||||
# two renames cannot leave something that reads as an entry.
|
||||
# inside .git/: it is on the same filesystem, so the rename stays a
|
||||
# rename; it is not under projects/, so a crash between the two moves
|
||||
# cannot leave something that reads as an entry; and `git add -A`
|
||||
# never descends there, so a crash cannot leave junk that the next
|
||||
# ordinary run commits either. A vault whose .git is not a directory
|
||||
# is not one this tool made, so that case falls back to the vault
|
||||
# root, which the scaffolded .gitignore covers.
|
||||
set -l stash ""
|
||||
if test -d "$to"
|
||||
set stash "$vault/.adopt-stash"
|
||||
if test -d "$vault/.git"
|
||||
set stash "$vault/.git/agents-vault-adopt-stash"
|
||||
else
|
||||
set stash "$vault/.adopt-stash"
|
||||
end
|
||||
rm -rf "$stash"
|
||||
command mv "$to" "$stash"; or return 1
|
||||
end
|
||||
if not git -C "$vault" mv "projects/$cur" "projects/$_flag_adopt" 2>/dev/null
|
||||
if not command mv "$from" "$to"
|
||||
test -n "$stash"; and command mv "$stash" "$to"
|
||||
# Putting the worktree back is only half a rollback. Every
|
||||
# move here is a plain rename as far as git is concerned, so
|
||||
# the index still describes the half-applied state, and a
|
||||
# hand `git commit` in that window would record it -- the
|
||||
# next ordinary run's `git add -A` heals it, but only later.
|
||||
# projects/ is re-read whole rather than the two entries
|
||||
# named: git add refuses a pathspec that matches nothing --
|
||||
# which one of the two always is, once it has been moved
|
||||
# back -- and then stages none of them, not even the one
|
||||
# that did match.
|
||||
git -C "$vault" add -A -- projects 2>/dev/null
|
||||
return 1
|
||||
end
|
||||
end
|
||||
@@ -450,6 +493,13 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
command mv "$to" "$from"
|
||||
end
|
||||
test -n "$stash"; and command mv "$stash" "$to"
|
||||
# Only now, with the worktree whole again -- the stash restore
|
||||
# included -- is the index worth re-reading. Unconditional
|
||||
# because even the git-native rollback leaves it out of line:
|
||||
# the forward `git mv` overwrote the stashed entry's index
|
||||
# entries, and moving the files back does not bring them back.
|
||||
# See the same call above for why projects/ is named whole.
|
||||
git -C "$vault" add -A -- projects 2>/dev/null
|
||||
echo "$c_err""agents-vault: could not relink $claude_root/$mangled/memory; $cur was left as it was$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
@@ -473,9 +523,14 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
set -l claude_root $__fish_agent_vault_claude_root
|
||||
test -n "$claude_root"; or set claude_root "$HOME/.claude/projects"
|
||||
set -l restore_failed 0
|
||||
for entry in "$vault"/projects/*
|
||||
# Listed rather than globbed, for the reason spelled out at --status:
|
||||
# fish's * skips a dot-led slug, which is a legitimate key, and a
|
||||
# batch restore that silently walks past an entry is worse here than
|
||||
# in a report -- that project is simply left unlinked.
|
||||
for name in (command ls -A "$vault/projects" 2>/dev/null)
|
||||
set -l entry "$vault/projects/$name"
|
||||
test -d "$entry/claude/memory"; or continue
|
||||
set -l eslug (path basename "$entry")
|
||||
set -l eslug $name
|
||||
set -l opath ""
|
||||
if test -f "$entry/origin"
|
||||
set -l line (command grep -m1 '^path:' "$entry/origin" 2>/dev/null)
|
||||
@@ -734,7 +789,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
# locally and the next push will carry it -- but nothing
|
||||
# left this machine, which is the whole point of pushing,
|
||||
# so this is a failure and not a warning to walk past.
|
||||
echo "$c_warn""agents-vault: push failed; the vault is committed locally but not backed up off this machine$c_reset" >&2
|
||||
echo "$c_err""agents-vault: push failed; the vault is committed locally but not backed up off this machine$c_reset" >&2
|
||||
set failed 1
|
||||
end
|
||||
else if set -q _flag_push
|
||||
|
||||
@@ -860,6 +860,104 @@ popd >/dev/null
|
||||
check "atomic adopt: retry succeeds" 0 "$t2rc"
|
||||
check "atomic adopt: retry moved the memory" atomic-precious (cat $vroot9/agent-vault/projects/atomic-target/claude/memory/keep.md 2>/dev/null)
|
||||
|
||||
# The cases above all adopt onto a slug with no entry at all. The other
|
||||
# half of --adopt is a target that already exists but holds no memory -- a
|
||||
# scaffolded entry, or one a departed project left behind. That one is
|
||||
# stashed rather than deleted, so a failed relink can put it back exactly
|
||||
# as it was.
|
||||
set -l sa (new_repo https://git.rootiest.dev/rootiest/stash-src.git)
|
||||
set -l saslug git.rootiest.dev-rootiest-stash-src
|
||||
set -l samang (string replace -a '/' '-' -- $sa | string replace -a '.' '-')
|
||||
mkdir -p $croot9/$samang/memory
|
||||
echo stash-precious >$croot9/$samang/memory/keep.md
|
||||
pushd $sa >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
# The target must be *tracked* for this to bite: stashing it takes files
|
||||
# git knows about out from under the index, which is the whole hazard.
|
||||
mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere \
|
||||
>$vroot9/agent-vault/projects/stash-target/origin
|
||||
pushd $sa >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
check "stash adopt: target entry is tracked" true (git -C $vroot9/agent-vault ls-files --error-unmatch projects/stash-target/origin >/dev/null 2>&1; and echo true; or echo false)
|
||||
check "stash adopt: vault clean before the adopt" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
|
||||
# Where the stash lives is load-bearing rather than cosmetic: at the vault
|
||||
# root, a crash between the two moves leaves it for the next ordinary run's
|
||||
# `git add -A` to commit as permanent junk. Inside .git/ it is out of reach
|
||||
# of both the entry walk and `git add -A`. Pinned by making the vault root
|
||||
# itself unwritable for the duration -- adopt writes nothing there, so only
|
||||
# a stash at the root would need it.
|
||||
chmod 500 $vroot9/agent-vault
|
||||
pushd $sa >/dev/null
|
||||
set -l sarc (agents-vault --adopt=stash-target --silent; echo $status)
|
||||
popd >/dev/null
|
||||
chmod 700 $vroot9/agent-vault
|
||||
check "stash adopt: succeeds over a contentless target" 0 "$sarc"
|
||||
check "stash adopt: memory moved to the target slug" stash-precious (cat $vroot9/agent-vault/projects/stash-target/claude/memory/keep.md 2>/dev/null)
|
||||
check "stash adopt: source entry removed" false (test -d $vroot9/agent-vault/projects/$saslug; and echo true; or echo false)
|
||||
check "stash adopt: link repinned onto the target" (path resolve $vroot9/agent-vault/projects/stash-target/claude/memory) (path resolve $croot9/$samang/memory)
|
||||
check "stash adopt: stash cleaned up" false (test -e $vroot9/agent-vault/.git/agents-vault-adopt-stash; and echo true; or echo false)
|
||||
check "stash adopt: nothing stashed at the vault root" false (test -e $vroot9/agent-vault/.adopt-stash; and echo true; or echo false)
|
||||
check "stash adopt: the root fallback location is gitignored" true (git -C $vroot9/agent-vault check-ignore -q .adopt-stash; and echo true; or echo false)
|
||||
check "stash adopt: vault clean afterwards" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
|
||||
# ... and when the relink fails, the stash must come back and the *index*
|
||||
# must come back with it. Restoring the worktree alone leaves git
|
||||
# describing a half-applied rename -- the files are right, git status is
|
||||
# not -- and a hand commit in that window records it.
|
||||
set -l sf (new_repo https://git.rootiest.dev/rootiest/stash-fail.git)
|
||||
set -l sfslug git.rootiest.dev-rootiest-stash-fail
|
||||
set -l sfmang (string replace -a '/' '-' -- $sf | string replace -a '.' '-')
|
||||
mkdir -p $croot9/$sfmang/memory
|
||||
echo stashfail-precious >$croot9/$sfmang/memory/keep.md
|
||||
pushd $sf >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else \
|
||||
>$vroot9/agent-vault/projects/stashfail-target/origin
|
||||
pushd $sf >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
set -l sf_entries (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
set -l sf_head (git -C $vroot9/agent-vault rev-list --count HEAD)
|
||||
set -l sf_porcelain (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
set -l sf_link (path resolve $croot9/$sfmang/memory)
|
||||
check "stash adopt rollback: vault clean before the adopt" "" "$sf_porcelain"
|
||||
|
||||
set -l sferr (mktemp); set -ga TMPDIRS $sferr
|
||||
chmod 500 $croot9/$sfmang
|
||||
pushd $sf >/dev/null
|
||||
set -l sfrc (agents-vault --adopt=stashfail-target --silent 2>$sferr; echo $status)
|
||||
popd >/dev/null
|
||||
chmod 700 $croot9/$sfmang
|
||||
|
||||
check "stash adopt rollback: failed relink returns 1" 1 "$sfrc"
|
||||
check "stash adopt rollback: says the entry was left alone" true (string match -q "*$sfslug*left as it was*" -- (cat $sferr); and echo true; or echo false)
|
||||
check "stash adopt rollback: entries unchanged" "$sf_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
check "stash adopt rollback: source entry intact" stashfail-precious (cat $vroot9/agent-vault/projects/$sfslug/claude/memory/keep.md 2>/dev/null)
|
||||
check "stash adopt rollback: stashed target came back" true (string match -q '*nowhere-else*' -- (cat $vroot9/agent-vault/projects/stashfail-target/origin 2>/dev/null); and echo true; or echo false)
|
||||
check "stash adopt rollback: target memory still contentless" 0 (command ls -A $vroot9/agent-vault/projects/stashfail-target/claude/memory 2>/dev/null | count)
|
||||
check "stash adopt rollback: nothing committed" "$sf_head" (git -C $vroot9/agent-vault rev-list --count HEAD)
|
||||
check "stash adopt rollback: index and worktree unchanged" "$sf_porcelain" (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
check "stash adopt rollback: no stash left behind" false (test -e $vroot9/agent-vault/.git/agents-vault-adopt-stash -o -e $vroot9/agent-vault/.adopt-stash; and echo true; or echo false)
|
||||
check "stash adopt rollback: live link never removed" "$sf_link" (path resolve $croot9/$sfmang/memory)
|
||||
check "stash adopt rollback: memory still reachable live" stashfail-precious (cat $croot9/$sfmang/memory/keep.md 2>/dev/null)
|
||||
|
||||
# The point of rolling back: the ordinary run afterwards finds the original
|
||||
# entry instead of fabricating a fresh, history-less one.
|
||||
pushd $sf >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
check "stash adopt rollback: ordinary run fabricated no entry" "$sf_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
check "stash adopt rollback: ordinary run kept the memory reachable" stashfail-precious (cat $croot9/$sfmang/memory/keep.md 2>/dev/null)
|
||||
|
||||
set -e __fish_agent_vault_dir
|
||||
set -e __fish_agent_vault_claude_root
|
||||
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
||||
@@ -908,6 +1006,28 @@ set -l r2rc (agents-vault --restore >$r2out; echo $status)
|
||||
check "restore: exits 0 with an unplaceable entry" 0 "$r2rc"
|
||||
check "restore: reports the unplaceable entry" true (string match -q '*ghost-entry*' -- (cat $r2out); and echo true; or echo false)
|
||||
|
||||
# A dot-led slug is a real key, not a curiosity: the sibling-bare-mirror
|
||||
# idiom (`git remote add origin ../mirror.git`) keys as ..-mirror, a
|
||||
# dot-led host keys as .hidden.example.com-o-r, and --adopt accepts a
|
||||
# leading dot on purpose. Fish's * skips such a name, so both walks list
|
||||
# instead of globbing -- otherwise --status under-reports the entry and
|
||||
# --restore leaves that project unlinked, both without saying a word.
|
||||
set -l dotp (new_repo https://git.rootiest.dev/rootiest/dotted.git)
|
||||
set -l dotmang (string replace -a '/' '-' -- $dotp | string replace -a '.' '-')
|
||||
mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory
|
||||
echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp \
|
||||
>$vroot10/agent-vault/projects/.dot-entry/origin
|
||||
check "dot-led entry: a glob really does skip it" false (string match -q '*.dot-entry*' -- (echo $vroot10/agent-vault/projects/*); and echo true; or echo false)
|
||||
|
||||
set -l dotreport (agents-vault --status)
|
||||
check "status lists a dot-led entry" true (string match -q '*.dot-entry*' -- "$dotreport"; and echo true; or echo false)
|
||||
|
||||
set -l dotout (agents-vault --restore)
|
||||
check "restore: relinks a dot-led entry" dot-precious (cat $croot10/$dotmang/memory/keep.md 2>/dev/null)
|
||||
check "restore: the dot-led live path is a link" true (test -L $croot10/$dotmang/memory; and echo true; or echo false)
|
||||
check "restore: names the dot-led entry it restored" true (string match -q '*.dot-entry*' -- "$dotout"; and echo true; or echo false)
|
||||
|
||||
# An entry that *could* be placed but could not be relinked is a failure,
|
||||
# not a note in passing -- the same branchless-`if` false zero as the
|
||||
# commit and push paths. An entry with no live project is not a failure.
|
||||
@@ -1010,6 +1130,114 @@ set -e __fish_agent_vault_claude_root
|
||||
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
||||
set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
|
||||
# ────────────── a failed vault commit is fatal, not a note ─────────────
|
||||
# _agents_repo_sync's own rejection path is covered further up, but
|
||||
# agents-vault has to *propagate* it. The function otherwise ends on a
|
||||
# branchless `if`, which fish resolves to 0, and a backup tool that reports
|
||||
# success while nothing was recorded recreates the exact loss the vault
|
||||
# exists to prevent. That false zero has already bitten this project three
|
||||
# times, so both ways a sync can fail are pinned here rather than one.
|
||||
echo ""
|
||||
echo "== agents-vault (a failed vault commit is fatal) =="
|
||||
|
||||
set -l vroot13 (mktemp -d); set -ga TMPDIRS $vroot13
|
||||
set -l croot13 (mktemp -d); set -ga TMPDIRS $croot13
|
||||
set -l chome13 (mktemp -d); set -ga TMPDIRS $chome13
|
||||
set -l agy13 (mktemp -d); set -ga TMPDIRS $agy13
|
||||
set -g __fish_agent_vault_dir $vroot13/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot13
|
||||
set -g __fish_agent_vault_claude_home $chome13
|
||||
set -g __fish_agent_vault_agy_root $agy13
|
||||
|
||||
set -l hp13 (new_repo https://git.rootiest.dev/rootiest/hookfail.git)
|
||||
set -l hmang13 (string replace -a '/' '-' -- $hp13 | string replace -a '.' '-')
|
||||
mkdir -p $croot13/$hmang13/memory
|
||||
echo hook-precious >$croot13/$hmang13/memory/keep.md
|
||||
pushd $hp13 >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
# The vault runs its own hooks out of .agents-tools/hooks -- agents-vault
|
||||
# points core.hooksPath there itself -- so a rejecting pre-commit in that
|
||||
# directory is exactly the shape of a real secret scanner blocking the
|
||||
# vault commit. _agents_repo_install_tools only refreshes the shims when
|
||||
# the version marker moves, so the replacement survives the run under test.
|
||||
printf '#!/bin/sh\nexit 1\n' >$vroot13/agent-vault/.agents-tools/hooks/pre-commit
|
||||
chmod +x $vroot13/agent-vault/.agents-tools/hooks/pre-commit
|
||||
echo hook-more >$croot13/$hmang13/memory/keep2.md
|
||||
set -l hhead13 (git -C $vroot13/agent-vault rev-list --count HEAD)
|
||||
set -l herr13 (mktemp); set -ga TMPDIRS $herr13
|
||||
pushd $hp13 >/dev/null
|
||||
set -l hrc13 (agents-vault --silent 2>$herr13; echo $status)
|
||||
popd >/dev/null
|
||||
check "rejected vault commit returns non-zero" 1 "$hrc13"
|
||||
check "rejected vault commit says nothing was recorded" true (string match -q '*nothing recorded*' -- (cat $herr13); and echo true; or echo false)
|
||||
check "rejected vault commit really recorded nothing" "$hhead13" (git -C $vroot13/agent-vault rev-list --count HEAD)
|
||||
|
||||
set -e __fish_agent_vault_dir
|
||||
set -e __fish_agent_vault_claude_root
|
||||
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
||||
set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
|
||||
# The other way a sync fails: a rebase conflict in the vault, which
|
||||
# _agents_repo_sync aborts (exit 2) rather than committing conflict markers
|
||||
# under a routine-looking message. That is a backup that did not happen
|
||||
# too, and must not exit 0 either.
|
||||
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14
|
||||
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14
|
||||
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14
|
||||
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14
|
||||
set -g __fish_agent_vault_dir $vroot14/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot14
|
||||
set -g __fish_agent_vault_claude_home $chome14
|
||||
set -g __fish_agent_vault_agy_root $agy14
|
||||
|
||||
set -l bare14 (mktemp -d); set -ga TMPDIRS $bare14
|
||||
git init -q --bare $bare14
|
||||
|
||||
set -l cp14 (new_repo https://git.rootiest.dev/rootiest/conflict.git)
|
||||
set -l cslug14 git.rootiest.dev-rootiest-conflict
|
||||
set -l cmang14 (string replace -a '/' '-' -- $cp14 | string replace -a '.' '-')
|
||||
mkdir -p $croot14/$cmang14/memory
|
||||
echo base >$croot14/$cmang14/memory/keep.md
|
||||
pushd $cp14 >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
agents-vault --remote=$bare14 --silent
|
||||
git -C $vroot14/agent-vault push -q -u origin HEAD:refs/heads/main
|
||||
|
||||
# Another machine records a conflicting change to the same line...
|
||||
set -l cclone14 (mktemp -d); set -ga TMPDIRS $cclone14
|
||||
git clone -q $bare14 $cclone14
|
||||
git -C $cclone14 config user.email t@t
|
||||
git -C $cclone14 config user.name t
|
||||
git -C $cclone14 config commit.gpgsign false
|
||||
git -C $cclone14 config core.hooksPath /dev/null
|
||||
echo theirs >$cclone14/projects/$cslug14/claude/memory/keep.md
|
||||
git -C $cclone14 commit -qam theirs
|
||||
git -C $cclone14 push -q origin HEAD:main
|
||||
|
||||
# ... while this one has a conflicting commit of its own waiting to be
|
||||
# replayed on top. It has to be committed: an uncommitted change is merely
|
||||
# autostashed, and the rebase then fast-forwards instead of conflicting.
|
||||
echo ours >$croot14/$cmang14/memory/keep.md
|
||||
git -C $vroot14/agent-vault -c user.email=t@t -c user.name=t \
|
||||
-c commit.gpgsign=false -c core.hooksPath=/dev/null commit -qam ours
|
||||
|
||||
set -l cerr14 (mktemp); set -ga TMPDIRS $cerr14
|
||||
pushd $cp14 >/dev/null
|
||||
set -l crc14 (agents-vault --silent 2>$cerr14; echo $status)
|
||||
popd >/dev/null
|
||||
check "vault rebase conflict returns non-zero" 1 "$crc14"
|
||||
check "vault rebase conflict says nothing was committed" true (string match -q '*nothing committed*' -- (cat $cerr14); and echo true; or echo false)
|
||||
check "vault rebase conflict left no rebase in progress" false (test -d $vroot14/agent-vault/.git/rebase-merge -o -d $vroot14/agent-vault/.git/rebase-apply; and echo true; or echo false)
|
||||
check "vault rebase conflict kept the local memory" ours (cat $croot14/$cmang14/memory/keep.md)
|
||||
|
||||
set -e __fish_agent_vault_dir
|
||||
set -e __fish_agent_vault_claude_root
|
||||
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
||||
set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
|
||||
# ────────────── a dangling global memory link is repinned ──────────────
|
||||
# The one state from the real incident the suite never pinned down. For a
|
||||
# *broken* symlink both -d and -e are false, so only the `-L` disjunct in
|
||||
|
||||
Reference in New Issue
Block a user