feat(agents-vault): sync the vault from the claude and agy wrappers
Both wrappers stay behind the C1 guard, so disabling __fish_config_op_aliases still passes straight through to the real binary. Launch commits but never pushes, keeping the network and any credential prompt off the critical path; pushing is left to the Claude Code SessionEnd hook. agy has no such hook, so its memory lands one launch later.
This commit is contained in:
@@ -475,8 +475,12 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
# 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
|
||||
# that did match. Its stderr is deliberately not swallowed:
|
||||
# projects/ provably exists here, so the only thing a
|
||||
# redirect could hide is a real failure -- a held
|
||||
# index.lock, say -- after which the index stays
|
||||
# half-applied while nothing says so.
|
||||
git -C "$vault" add -A -- projects
|
||||
return 1
|
||||
end
|
||||
end
|
||||
@@ -498,8 +502,9 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
# 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
|
||||
# See the same call above for why projects/ is named whole,
|
||||
# and why its stderr is left visible.
|
||||
git -C "$vault" add -A -- projects
|
||||
echo "$c_err""agents-vault: could not relink $claude_root/$mangled/memory; $cur was left as it was$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
+10
-4
@@ -8,7 +8,7 @@
|
||||
# aliases/dev-tools
|
||||
#
|
||||
# DEPENDENCIES
|
||||
# agents-init
|
||||
# agents-init, agents-vault
|
||||
#
|
||||
# SYNOPSIS
|
||||
# agy [ARGS...]
|
||||
@@ -18,9 +18,14 @@
|
||||
# sub-repository is initialized and any agent-made changes are committed
|
||||
# before launch. Delegates all scaffold and commit logic to agents-init
|
||||
# --quiet (full setup), which ensures AGENTS/ is scaffolded and CLAUDE.md
|
||||
# is symlinked to AGENTS/AGENTS.md in the current project. Arguments are
|
||||
# forwarded verbatim to the real agy binary, except for -r/--resume which
|
||||
# are translated to -c/--continue.
|
||||
# is symlinked to AGENTS/AGENTS.md in the current project.
|
||||
#
|
||||
# Also syncs the host-scoped agent memory vault (agents-vault). agy has
|
||||
# no session-end hook, so its memory is captured on the next launch
|
||||
# rather than at session end.
|
||||
#
|
||||
# Arguments are forwarded verbatim to the real agy binary, except for
|
||||
# -r/--resume which are translated to -c/--continue.
|
||||
#
|
||||
# Opinionated component (C1): when disabled via __fish_config_op_aliases
|
||||
# (or the __fish_config_opinionated master), the command is passed through
|
||||
@@ -44,6 +49,7 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su
|
||||
end
|
||||
|
||||
agents-init --quiet
|
||||
agents-vault --quiet
|
||||
|
||||
for i in (seq (count $argv))
|
||||
if test "$argv[$i]" = "-r"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# aliases/dev-tools
|
||||
#
|
||||
# DEPENDENCIES
|
||||
# agents-init
|
||||
# agents-init, agents-vault
|
||||
#
|
||||
# SYNOPSIS
|
||||
# claude [ARGS...]
|
||||
@@ -19,6 +19,12 @@
|
||||
# Delegates all scaffold and commit logic to agents-init --quiet (full
|
||||
# setup), which ensures AGENTS/ is scaffolded and CLAUDE.md is symlinked
|
||||
# to AGENTS/AGENTS.md in the current project.
|
||||
#
|
||||
# Also syncs the host-scoped agent memory vault (agents-vault), which
|
||||
# tracks curated memory living outside the project tree. The vault
|
||||
# commits on launch but does not push; pushing happens from the Claude
|
||||
# Code SessionEnd hook or an explicit agents-vault --push.
|
||||
#
|
||||
# All arguments are forwarded verbatim to the real claude binary.
|
||||
#
|
||||
# Opinionated component (C1): when disabled via __fish_config_op_aliases
|
||||
@@ -42,6 +48,7 @@ function claude --wraps=claude --description 'claude wrapper: auto-links AGENTS.
|
||||
end
|
||||
|
||||
agents-init --quiet
|
||||
agents-vault --quiet
|
||||
|
||||
command claude $argv
|
||||
end
|
||||
|
||||
@@ -58,6 +58,34 @@ function test_greeting_function_defined
|
||||
functions -q fish_greeting
|
||||
end
|
||||
|
||||
function test_agents_vault_defined
|
||||
for f in agents-vault _agents_vault_dir _agents_repo_slug \
|
||||
_agents_repo_ensure_symlink _agents_repo_sync \
|
||||
_agents_repo_install_tools
|
||||
if not functions -q $f
|
||||
echo " missing function: $f"
|
||||
return 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function test_wrappers_call_agents_vault
|
||||
functions -q claude; or return 1
|
||||
functions claude | string match -q '*agents-vault*'; or return 1
|
||||
functions -q agy; or return 1
|
||||
functions agy | string match -q '*agents-vault*'
|
||||
end
|
||||
|
||||
function test_vault_dir_honors_override
|
||||
set -l saved
|
||||
set -q __fish_agent_vault_dir; and set saved $__fish_agent_vault_dir
|
||||
set -g __fish_agent_vault_dir /tmp/vault-override-check
|
||||
set -l got (_agents_vault_dir)
|
||||
set -e __fish_agent_vault_dir
|
||||
test (count $saved) -gt 0; and set -g __fish_agent_vault_dir $saved
|
||||
test "$got" = /tmp/vault-override-check
|
||||
end
|
||||
|
||||
function functional_test_main
|
||||
set -l names (functions -a | string match 'test_*' | sort)
|
||||
set -l failed 0
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
# and loads it as an isolated interactive session.
|
||||
# 3. Runs the functional checks in tests/functional.fish inside that
|
||||
# loaded session.
|
||||
# 4. Runs tests/test-agents-vault.fish as its own process; that suite
|
||||
# builds its own throwaway repos and needs no loaded config.
|
||||
#
|
||||
# Usage: fish tests/run-tests.fish
|
||||
|
||||
@@ -86,4 +88,16 @@ if test $functional_status -ne 0
|
||||
set overall_failed 1
|
||||
end
|
||||
|
||||
# ---- Phase 3: hermetic vault helper tests --------------------------------
|
||||
# Run as its own fish process rather than inside the sandboxed session:
|
||||
# the suite builds its own throwaway git repos and binds the vault, claude
|
||||
# and agy roots to them, so it needs no loaded config and must never see
|
||||
# the real ~/.claude.
|
||||
echo ""
|
||||
echo "== Vault helper tests =="
|
||||
fish $repo_root/tests/test-agents-vault.fish
|
||||
if test $status -ne 0
|
||||
set overall_failed 1
|
||||
end
|
||||
|
||||
exit $overall_failed
|
||||
|
||||
@@ -958,6 +958,68 @@ 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)
|
||||
|
||||
# The other rollback in --adopt: the *forward* move failing outright,
|
||||
# before the relink is ever reached. A plain file sitting where the target
|
||||
# entry would go makes `git mv` refuse ("destination already exists") and
|
||||
# the coreutils fallback refuse too ("cannot overwrite non-directory"), so
|
||||
# the worktree needs no repair -- but the index does, and that is the half
|
||||
# of the rollback nothing else pins. With a real git there is no way for a
|
||||
# test to leave a genuinely half-applied rename here, so the divergence
|
||||
# stands in for one: the index is desynced by hand first, and the check
|
||||
# that matters is that the way out re-read projects/ and healed it.
|
||||
# Without that setup the repair is an invisible no-op, and a refactor can
|
||||
# drop it with the suite still green.
|
||||
set -l ff (new_repo https://git.rootiest.dev/rootiest/fwd-fail.git)
|
||||
set -l ffslug git.rootiest.dev-rootiest-fwd-fail
|
||||
set -l ffmang (string replace -a '/' '-' -- $ff | string replace -a '.' '-')
|
||||
mkdir -p $croot9/$ffmang/memory
|
||||
echo fwdfail-precious >$croot9/$ffmang/memory/keep.md
|
||||
pushd $ff >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
# The blocker must be tracked and committed, so that the only thing dirty
|
||||
# at adopt time is the divergence staged just below.
|
||||
echo occupied >$vroot9/agent-vault/projects/fwdfail-target
|
||||
pushd $ff >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
check "fwd-fail adopt: the blocking file is tracked" true (git -C $vroot9/agent-vault ls-files --error-unmatch projects/fwdfail-target >/dev/null 2>&1; and echo true; or echo false)
|
||||
check "fwd-fail adopt: vault clean before the divergence" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
|
||||
git -C $vroot9/agent-vault rm -q --cached projects/$ffslug/origin >/dev/null
|
||||
set -l ff_dirty (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
check "fwd-fail adopt: index diverges before the adopt" true (string match -q "*D projects/$ffslug/origin*" -- "$ff_dirty"; and echo true; or echo false)
|
||||
|
||||
set -l ff_entries (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
set -l ff_head (git -C $vroot9/agent-vault rev-list --count HEAD)
|
||||
set -l ff_link (path resolve $croot9/$ffmang/memory)
|
||||
|
||||
pushd $ff >/dev/null
|
||||
set -l ffrc (agents-vault --adopt=fwdfail-target --silent 2>/dev/null; echo $status)
|
||||
popd >/dev/null
|
||||
|
||||
check "fwd-fail adopt: returns 1" 1 "$ffrc"
|
||||
check "fwd-fail adopt: entries unchanged" "$ff_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
check "fwd-fail adopt: source entry intact" fwdfail-precious (cat $vroot9/agent-vault/projects/$ffslug/claude/memory/keep.md 2>/dev/null)
|
||||
check "fwd-fail adopt: blocking file untouched" occupied (cat $vroot9/agent-vault/projects/fwdfail-target 2>/dev/null)
|
||||
check "fwd-fail adopt: no origin note appended" false (string match -q '*adopted:*' -- (cat $vroot9/agent-vault/projects/$ffslug/origin); and echo true; or echo false)
|
||||
check "fwd-fail adopt: nothing committed" "$ff_head" (git -C $vroot9/agent-vault rev-list --count HEAD)
|
||||
check "fwd-fail adopt: live link never removed" "$ff_link" (path resolve $croot9/$ffmang/memory)
|
||||
check "fwd-fail adopt: memory still reachable live" fwdfail-precious (cat $croot9/$ffmang/memory/keep.md 2>/dev/null)
|
||||
check "fwd-fail adopt: 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)
|
||||
# The pin: projects/ was re-read on the way out, so git describes the
|
||||
# files as they actually are rather than as the abandoned rename left them.
|
||||
check "fwd-fail adopt: index re-read to match the worktree" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
check "fwd-fail adopt: nothing left staged against HEAD" "" (git -C $vroot9/agent-vault diff HEAD --name-only | string join ',')
|
||||
|
||||
# And the ordinary run afterwards still finds the original entry.
|
||||
pushd $ff >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
check "fwd-fail adopt: ordinary run fabricated no entry" "$ff_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
check "fwd-fail adopt: ordinary run kept the memory reachable" fwdfail-precious (cat $croot9/$ffmang/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
|
||||
|
||||
Reference in New Issue
Block a user