feat(agents-vault): scaffold the vault and link project memory
Creates the vault repo on demand, reusing the AGENTS version bumper and hook shims, then links the current project's live memory directory into its slug-keyed entry and commits. Because the live directory becomes a symlink into the vault, backup and restore are the same operation: a cloned vault relinks itself on the next run in each project, with no manifest and no batch restore step. Also fixes _agents_repo_install_tools' progress messages, which hardcoded the literal "AGENTS/.agents-tools/" even for callers writing elsewhere: they now name repo_dir's own basename, so agents-vault reports its own directory instead of a false AGENTS/ path.
This commit is contained in:
@@ -54,6 +54,25 @@ Example: to increase the scrollback history limit:
|
||||
set -gx SCROLLBACK_HISTORY_MAX_FILES 200
|
||||
|
||||
|
||||
## Agent Memory Vault
|
||||
|
||||
__fish_agent_vault_dir
|
||||
|
||||
Overrides the agent memory vault location. Defaults to
|
||||
$XDG_DATA_HOME/agent-vault (or ~/.local/share/agent-vault).
|
||||
|
||||
__fish_agent_vault_autopush
|
||||
|
||||
When set to 1, agents-vault also pushes on wrapper launch. Defaults to
|
||||
off: the vault commits locally on every launch and pushes from the
|
||||
Claude Code SessionEnd hook or an explicit agents-vault --push.
|
||||
|
||||
NOTE:
|
||||
With autopush off and no SessionEnd hook installed, backups accumulate
|
||||
locally and never reach the remote. Run agents-vault --status to check
|
||||
how far ahead the vault is.
|
||||
|
||||
|
||||
## Fish Universal Variables
|
||||
|
||||
Some settings (fzf colors, theme) are stored in fish_variables via
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
# refreshing them when the shipped agents-tools-version: marker is newer
|
||||
# than the installed copy. Files are made executable. Idempotent: prints
|
||||
# nothing when the installed tooling is already current, or a short summary
|
||||
# line when it installed or updated the tooling. Shared by agents-init and
|
||||
# agents-vault.
|
||||
# line when it installed or updated the tooling, naming <repo_dir>'s own
|
||||
# basename rather than a hardcoded caller (e.g. "AGENTS/.agents-tools/" for
|
||||
# agents-init, "agent-vault/.agents-tools/" for agents-vault). Shared by
|
||||
# agents-init and agents-vault.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# repo_dir Absolute path to the git repo root to install tooling into
|
||||
@@ -41,9 +43,10 @@ function _agents_repo_install_tools --argument-names repo_dir
|
||||
command cp "$src/hooks/prepare-commit-msg" "$dest/hooks/prepare-commit-msg"; or return 1
|
||||
chmod +x "$dest/version-bump" "$dest/hooks/pre-commit" "$dest/hooks/prepare-commit-msg"; or return 1
|
||||
|
||||
set -l label (path basename -- "$repo_dir")
|
||||
if test -z "$have"
|
||||
echo "→ Installed AGENTS/.agents-tools/ (version-bump v$want)"
|
||||
echo "→ Installed $label/.agents-tools/ (version-bump v$want)"
|
||||
else
|
||||
echo "→ Updated AGENTS/.agents-tools/ (v$have → v$want)"
|
||||
echo "→ Updated $label/.agents-tools/ (v$have → v$want)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_vault_dir
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Prints the agent memory vault root. Honors the universal variable
|
||||
# __fish_agent_vault_dir when set, otherwise
|
||||
# ${XDG_DATA_HOME:-$HOME/.local/share}/agent-vault.
|
||||
#
|
||||
# The vault holds agy state as well as Claude state, so it is not nested
|
||||
# under either tool's directory; it is backed-up state rather than
|
||||
# configuration, hence XDG_DATA_HOME rather than XDG_CONFIG_HOME.
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# RETURNS
|
||||
# The vault root path, one line on stdout.
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l vault (_agents_vault_dir)
|
||||
function _agents_vault_dir
|
||||
if set -q __fish_agent_vault_dir; and test -n "$__fish_agent_vault_dir"
|
||||
printf '%s\n' "$__fish_agent_vault_dir"
|
||||
return 0
|
||||
end
|
||||
set -l base $XDG_DATA_HOME
|
||||
test -n "$base"; or set base "$HOME/.local/share"
|
||||
printf '%s\n' "$base/agent-vault"
|
||||
end
|
||||
@@ -0,0 +1,242 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# CATEGORY
|
||||
# 12-ai-and-developer-tools
|
||||
#
|
||||
# DEPENDENCIES
|
||||
# _agents_vault_dir, _agents_repo_slug, _agents_repo_ensure_symlink,
|
||||
# _agents_repo_sync, _agents_repo_install_tools, git
|
||||
#
|
||||
# SYNOPSIS
|
||||
# agents-vault [--link] [--push] [--restore] [--status]
|
||||
# [--adopt=SLUG] [--remote=URL]
|
||||
# [-v | --verbose] [-q | --quiet] [-s | --silent]
|
||||
# [-h | --help]
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Tracks curated agent memory in a host-scoped git repository so it
|
||||
# survives losing a machine. Complements agents-init, which scaffolds the
|
||||
# per-project AGENTS/ repo: that holds the shareable agent specification,
|
||||
# while this holds the personal memory an agent accumulates.
|
||||
#
|
||||
# Memory does not live in any project tree. Claude keeps it under
|
||||
# ~/.claude/projects/<mangled-path>/memory/ and agy keeps its knowledge
|
||||
# store under ~/.gemini/antigravity-cli/, both outside every repository.
|
||||
#
|
||||
# Entries are keyed by normalized git remote URL rather than by path, so
|
||||
# the key survives a machine change or a directory rename. The live
|
||||
# memory directory becomes a symlink into the vault, which makes backup
|
||||
# and restore the same operation: on a new machine, clone the vault once
|
||||
# and the first agents-vault run in any project relinks its memory
|
||||
# automatically. No manifest and no batch restore step are involved.
|
||||
#
|
||||
# Only curated memory is tracked. Session transcripts are excluded (tens
|
||||
# of megabytes per project, growing per session). Paths are allowlisted,
|
||||
# never denylisted, so nothing new upstream adds can leak in.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# --link Ensure this project's memory link only; do not commit
|
||||
# --push Commit and push to the vault remote
|
||||
# --restore Walk the vault, relink what is possible, report the rest
|
||||
# --status Show entries, link health, remote state, and orphans
|
||||
# --adopt=SLUG Bind the current project to an existing vault entry
|
||||
# --remote=URL Set the vault remote
|
||||
# -v, --verbose Print all per-step output (default)
|
||||
# -q, --quiet Print one summary line only if changes were made
|
||||
# -s, --silent Suppress all output; errors only
|
||||
# -h, --help Show this help message and exit
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Completed successfully
|
||||
# 1 Fatal error (vault unavailable, git failure, ambiguous migration)
|
||||
#
|
||||
# EXAMPLE
|
||||
# agents-vault
|
||||
# agents-vault --status
|
||||
# agents-vault --remote=https://git.rootiest.dev/rootiest/agent-vault.git
|
||||
# agents-vault --push
|
||||
#
|
||||
# NOTES
|
||||
# Set __fish_agent_vault_dir to relocate the vault. Set
|
||||
# __fish_agent_vault_autopush to 1 to also push on wrapper launch;
|
||||
# it defaults to off so a backgrounded push can never hang or prompt
|
||||
# invisibly underneath a starting agent.
|
||||
function agents-vault --description 'track curated agent memory in a host-scoped vault repo'
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_cmd (set_color --bold)
|
||||
set -l c_flag (set_color yellow)
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_warn (set_color yellow)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_reset (set_color normal)
|
||||
|
||||
argparse h/help link push restore status 'adopt=' 'remote=' \
|
||||
v/verbose q/quiet s/silent -- $argv
|
||||
or return 1
|
||||
|
||||
if set -q _flag_help
|
||||
echo "$c_head""Usage:$c_reset $c_cmd""agents-vault$c_reset $c_flag""[--link] [--push] [--restore] [--status] [--adopt=SLUG] [--remote=URL] [-v] [-q] [-s] [-h]$c_reset"
|
||||
echo
|
||||
echo " Track curated agent memory in a host-scoped vault repository."
|
||||
echo
|
||||
echo "$c_head""Options:$c_reset"
|
||||
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
||||
echo " $c_flag--link$c_reset Ensure this project's memory link only"
|
||||
echo " $c_flag--push$c_reset Commit and push to the vault remote"
|
||||
echo " $c_flag--restore$c_reset Relink everything possible, report the rest"
|
||||
echo " $c_flag--status$c_reset Show entries, link health, remote, orphans"
|
||||
echo " $c_flag--adopt$c_reset=SLUG Bind this project to an existing vault entry"
|
||||
echo " $c_flag--remote$c_reset=URL Set the vault remote"
|
||||
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
|
||||
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changed"
|
||||
echo " $c_flag-s$c_reset, $c_flag--silent$c_reset Suppress all output; errors only"
|
||||
return 0
|
||||
end
|
||||
|
||||
set -l verbose 1
|
||||
set -l quiet 0
|
||||
if set -q _flag_silent
|
||||
set verbose 0
|
||||
else if set -q _flag_quiet
|
||||
set verbose 0
|
||||
set quiet 1
|
||||
end
|
||||
|
||||
if not type -q git
|
||||
echo "$c_err""agents-vault: git is required$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l vault (_agents_vault_dir)
|
||||
set -l changed 0
|
||||
set -l did_init 0
|
||||
|
||||
# ────────────────────── ensure the vault repo ──────────────────────
|
||||
if not test -d "$vault"
|
||||
if not mkdir -p "$vault"
|
||||
echo "$c_err""agents-vault: could not create $vault$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
set changed 1
|
||||
set did_init 1
|
||||
end
|
||||
if not test -d "$vault/.git"
|
||||
git -C "$vault" init -q
|
||||
or begin
|
||||
echo "$c_err""agents-vault: git init failed in $vault$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
set changed 1
|
||||
set did_init 1
|
||||
test $verbose -eq 1; and echo "$c_ok→ Initialized vault repo at $vault$c_reset"
|
||||
end
|
||||
|
||||
test -f "$vault/.version"; or echo 1.0.0 >"$vault/.version"
|
||||
|
||||
if not test -f "$vault/.gitignore"
|
||||
printf '%s\n' \
|
||||
'# SQLite sidecars are never safe to commit mid-write.' \
|
||||
'*.db-wal' \
|
||||
'*.db-shm' >"$vault/.gitignore"
|
||||
set changed 1
|
||||
end
|
||||
|
||||
if not test -f "$vault/README.md"
|
||||
printf '%s\n' \
|
||||
'# Agent Memory Vault' \
|
||||
'' \
|
||||
'Curated agent memory, tracked so it survives losing a machine.' \
|
||||
'Managed by `agents-vault` from rootiest/fish-config.' \
|
||||
'' \
|
||||
'## Restore' \
|
||||
'' \
|
||||
'Clone this repository to the path `agents-vault` resolves to' \
|
||||
'(`$XDG_DATA_HOME/agent-vault`, or `$__fish_agent_vault_dir`).' \
|
||||
'Then simply run `claude` in any project: the wrapper derives that' \
|
||||
"project's slug, finds its entry here, and relinks the live memory" \
|
||||
'directory automatically. There is no separate restore step.' \
|
||||
'' \
|
||||
'Entries are keyed by normalized git remote URL. A `local-*` key' \
|
||||
'belongs to a project with no remote and is machine-specific;' \
|
||||
'rebind one with `agents-vault --adopt=SLUG`.' >"$vault/README.md"
|
||||
set changed 1
|
||||
end
|
||||
|
||||
set -l tools_msg (_agents_repo_install_tools "$vault")
|
||||
if test -n "$tools_msg"
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok$tools_msg$c_reset"
|
||||
end
|
||||
|
||||
set -l hp (git -C "$vault" config --local core.hooksPath 2>/dev/null)
|
||||
if test "$hp" != .agents-tools/hooks
|
||||
git -C "$vault" config --local core.hooksPath .agents-tools/hooks
|
||||
set changed 1
|
||||
end
|
||||
|
||||
# ─────────────────────── unimplemented modes ───────────────────────
|
||||
for f in _flag_push _flag_restore _flag_status _flag_adopt _flag_remote
|
||||
if set -q $f
|
||||
echo "$c_err""agents-vault: that mode is not implemented yet$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
# ──────────────────── link the current project ─────────────────────
|
||||
set -l root (git rev-parse --show-toplevel 2>/dev/null)
|
||||
if test -n "$root"
|
||||
set -l slug (_agents_repo_slug "$root")
|
||||
set -l entry "$vault/projects/$slug"
|
||||
set -l vmem "$entry/claude/memory"
|
||||
|
||||
if not test -d "$vmem"
|
||||
mkdir -p "$vmem"; or return 1
|
||||
set changed 1
|
||||
end
|
||||
|
||||
set -l claude_root $__fish_agent_vault_claude_root
|
||||
test -n "$claude_root"; or set claude_root "$HOME/.claude/projects"
|
||||
set -l mangled (string replace -a '/' '-' -- "$root" | string replace -a '.' '-')
|
||||
set -l live "$claude_root/$mangled/memory"
|
||||
|
||||
if test -d "$claude_root/$mangled"; or test -d "$live"
|
||||
set -l link_msg (_agents_repo_ensure_symlink "$live" "$vmem")
|
||||
if test -n "$link_msg"
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok$link_msg$c_reset"
|
||||
end
|
||||
end
|
||||
|
||||
if not test -f "$entry/origin"
|
||||
set -l url (git -C "$root" remote get-url origin 2>/dev/null)
|
||||
test -n "$url"; or set url "(none)"
|
||||
printf 'remote: %s\npath: %s\nhost: %s\n' \
|
||||
"$url" "$root" (hostname 2>/dev/null) >"$entry/origin"
|
||||
set changed 1
|
||||
end
|
||||
end
|
||||
|
||||
# ───────────────────────────── commit ──────────────────────────────
|
||||
if not set -q _flag_link
|
||||
set -l msg "chore: sync agent memory vault"
|
||||
test $did_init -eq 1; and set msg "chore: initialize agent memory vault"
|
||||
set -l sync_out (_agents_repo_sync "$vault" "$msg")
|
||||
set -l sync_rc $status
|
||||
if test $sync_rc -eq 2
|
||||
echo "$c_warn""agents-vault: unresolved rebase conflict in the vault; nothing committed$c_reset" >&2
|
||||
else if test -n "$sync_out"
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok$sync_out$c_reset"
|
||||
end
|
||||
end
|
||||
|
||||
if test $quiet -eq 1; and test $changed -eq 1
|
||||
if test $did_init -eq 1
|
||||
echo "$c_ok→ Initialized agent memory vault$c_reset"
|
||||
else
|
||||
echo "$c_ok→ Synced agent memory vault$c_reset"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -202,6 +202,47 @@ set -l hrc (_agents_repo_sync $h "chore: blocked" 2>/dev/null; echo $status)
|
||||
check "commit hook rejection returns 1" 1 "$hrc"
|
||||
check "commit hook rejection commits nothing" $before_hook_count (git -C $h rev-list --count HEAD)
|
||||
|
||||
# ──────────────────────── vault scaffold + link ────────────────────────
|
||||
echo ""
|
||||
echo "== agents-vault (scaffold + link) =="
|
||||
|
||||
set -l vroot (mktemp -d); set -ga TMPDIRS $vroot
|
||||
set -l croot (mktemp -d); set -ga TMPDIRS $croot
|
||||
set -g __fish_agent_vault_dir $vroot/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot
|
||||
|
||||
set -l proj (new_repo https://git.rootiest.dev/rootiest/fish-config.git)
|
||||
set -l pslug git.rootiest.dev-rootiest-fish-config
|
||||
|
||||
# Seed a live memory directory the way Claude would.
|
||||
set -l mangled (string replace -a '/' '-' -- $proj | string replace -a '.' '-')
|
||||
mkdir -p $croot/$mangled/memory
|
||||
echo "a memory" >$croot/$mangled/memory/thing.md
|
||||
|
||||
pushd $proj >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
check "vault repo created" true (test -d $vroot/agent-vault/.git; and echo true; or echo false)
|
||||
check "vault version seeded" 1.0.0 (cat $vroot/agent-vault/.version 2>/dev/null)
|
||||
check "entry created for slug" true (test -d $vroot/agent-vault/projects/$pslug/claude/memory; and echo true; or echo false)
|
||||
check "live memory is now a link" true (test -L $croot/$mangled/memory; and echo true; or echo false)
|
||||
check "memory content preserved" "a memory" (cat $croot/$mangled/memory/thing.md)
|
||||
check "content lives in the vault" "a memory" (cat $vroot/agent-vault/projects/$pslug/claude/memory/thing.md)
|
||||
check "origin file written" true (test -f $vroot/agent-vault/projects/$pslug/origin; and echo true; or echo false)
|
||||
check "vault committed" true (test (git -C $vroot/agent-vault rev-list --count HEAD) -ge 1; and echo true; or echo false)
|
||||
|
||||
# Idempotence: a second run prints nothing and adds no commit.
|
||||
set -l before (git -C $vroot/agent-vault rev-list --count HEAD)
|
||||
pushd $proj >/dev/null
|
||||
set -l again (agents-vault --verbose)
|
||||
popd >/dev/null
|
||||
check "second run is silent" "" "$again"
|
||||
check "second run adds no commit" $before (git -C $vroot/agent-vault rev-list --count HEAD)
|
||||
|
||||
set -e __fish_agent_vault_dir
|
||||
set -e __fish_agent_vault_claude_root
|
||||
|
||||
cleanup
|
||||
echo ""
|
||||
echo (math $TESTS_RUN - $TESTS_FAILED)"/$TESTS_RUN passed"
|
||||
|
||||
Reference in New Issue
Block a user