feat(agents-vault): back up curated agent memory to a host-scoped vault repo #126

Merged
rootiest merged 19 commits from feat/agent-memory-vault into main 2026-09-03 23:09:57 +00:00
16 changed files with 3539 additions and 70 deletions
+21
View File
@@ -0,0 +1,21 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Completions for agents-vault.
complete -c agents-vault -f
complete -c agents-vault -s h -l help -d 'Show help message'
complete -c agents-vault -l link -d "Ensure this project's memory link only"
complete -c agents-vault -l push -d 'Commit and push to the vault remote'
complete -c agents-vault -l restore -d 'Relink everything possible, report the rest'
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.
# -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'
complete -c agents-vault -s s -l silent -d 'Suppress all output; errors only'
+3
View File
@@ -276,6 +276,9 @@ minimal=## Opinionated Components (Minimal Mode)
minimal-mode=## Opinionated Components (Minimal Mode)
opt-out=## Opinionated Components (Minimal Mode)
toggles=## Opinionated Components (Minimal Mode)
agent-vault=## Agent Memory Vault
__fish_agent_vault_dir=## Agent Memory Vault
__fish_agent_vault_autopush=## Agent Memory Vault
component-reference=# 8. COMPONENTS REFERENCE
components=# 8. COMPONENTS REFERENCE
c0=## Per-function overrides: `C0`/`always`
+21
View File
@@ -159,6 +159,27 @@ interactively. See [Components Reference](/08-components-reference/) for the
full sub-category breakdown of every category.
## 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. That
push is synchronous, so with autopush on the pull and the push are
each capped at 20 seconds; an explicit --push is left uncapped.
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.
## Prompt and Theme
### Starship
@@ -0,0 +1,71 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _agents_repo_ensure_symlink <link> <target>
#
# DESCRIPTION
# Idempotently makes <link> a symlink pointing at the directory <target>.
#
# Only directories are ever linked. The agent file-editing tools resolve a
# symlinked directory transparently but refuse to write through a
# symlinked file, so linking a file would silently break every later edit;
# a non-directory target is refused outright.
#
# A missing target is refused rather than linked, because a dangling
# memory/ symlink makes agent memory writes fail -- strictly worse than
# having no backup at all.
#
# When <link> is an existing real directory, its contents are copied into
# <target> without clobbering (cp -n) before the directory is replaced by
# the link, so adopting a populated live directory never overwrites the
# copy already in the vault.
#
# ARGUMENTS
# link Path that should become the symlink
# target Existing directory the link should point at
#
# EXIT STATUS
# 0 Link is correct (created, repinned, or already right)
# 1 Refused (non-directory target, missing target, non-directory link) or
# a copy, remove, or link operation failed
#
# RETURNS
# A single "→ ..." progress line on stdout when something changed;
# nothing at all when the link was already correct.
#
# EXAMPLE
# _agents_repo_ensure_symlink ~/.claude/projects/-home-u-proj/memory \
# ~/.local/share/agent-vault/projects/host-user-proj/claude/memory
function _agents_repo_ensure_symlink --argument-names link target
test -n "$link" -a -n "$target"; or return 1
if test -e "$target"; and not test -d "$target"
echo "_agents_repo_ensure_symlink: refusing non-directory target: $target" >&2
return 1
end
if not test -d "$target"
echo "_agents_repo_ensure_symlink: target does not exist: $target" >&2
return 1
end
if test -L "$link"
set -l cur (path resolve "$link")
set -l want (path resolve "$target")
test "$cur" = "$want"; and return 0
rm -f "$link"; or return 1
else if test -d "$link"
set -l contents (command ls -A "$link" 2>/dev/null)
if test (count $contents) -gt 0
command cp -rn "$link/." "$target/"; or return 1
end
rm -rf "$link"; or return 1
else if test -e "$link"
echo "_agents_repo_ensure_symlink: refusing to replace non-directory: $link" >&2
return 1
end
mkdir -p (path dirname "$link"); or return 1
ln -s "$target" "$link"; or return 1
echo "→ Linked "(path basename "$link")"$target"
end
@@ -2,31 +2,34 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _agents_init_install_tools <agents_dir>
# _agents_repo_install_tools <repo_dir>
#
# DESCRIPTION
# Copies the canonical version-bump script and git hook shims from
# fish-config's scripts/agents-tools/ into <agents_dir>/.agents-tools/,
# fish-config's scripts/agents-tools/ into <repo_dir>/.agents-tools/,
# 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.
# 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
# agents_dir Absolute path to the AGENTS/ sub-repo root
# repo_dir Absolute path to the git repo root to install tooling into
#
# EXIT STATUS
# 0 Tooling is current or was installed/updated successfully
# 1 Canonical source missing or a copy failed
#
# EXAMPLE
# set -l msg (_agents_init_install_tools /path/to/AGENTS)
# set -l msg (_agents_repo_install_tools /path/to/AGENTS)
# test -n "$msg"; and echo $msg
function _agents_init_install_tools --argument-names agents_dir
test -n "$agents_dir"; or return 1
function _agents_repo_install_tools --argument-names repo_dir
test -n "$repo_dir"; or return 1
set -l src (path resolve (status dirname)/../scripts/agents-tools)
test -f "$src/version-bump"; or return 1
set -l dest "$agents_dir/.agents-tools"
set -l dest "$repo_dir/.agents-tools"
set -l want (command grep -m1 -oE 'agents-tools-version: *[0-9]+' "$src/version-bump" 2>/dev/null | command grep -oE '[0-9]+$')
set -l have ""
@@ -40,9 +43,10 @@ function _agents_init_install_tools --argument-names agents_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
+40
View File
@@ -0,0 +1,40 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _agents_repo_local_slug <dir>
#
# DESCRIPTION
# Builds the path-derived fallback slug used when a project has no git
# remote: local-<sanitized-basename>-<8 hex of sha256(realpath)>. The
# basename is lowercased and every character outside [a-z0-9._-] is
# mapped to a dash, matching the sanitization the remote-URL branch of
# _agents_repo_slug applies to hostnames and paths.
#
# This is the single source of truth for that formula. It exists so the
# rule is written once: _agents_repo_slug's no-remote branch calls it to
# produce the slug, and agents-vault's slug-migration fallback (used when
# there is no live symlink yet to read the previous slug from) calls it
# to recompute the same candidate. Duplicating the formula in both places
# let them drift once before; this closes that gap for good.
#
# ARGUMENTS
# dir Absolute or relative path to the project directory
#
# EXIT STATUS
# 0 Slug printed
# 1 No directory argument given
#
# RETURNS
# The local-* slug, one line on stdout.
#
# EXAMPLE
# set -l slug (_agents_repo_local_slug /home/user/myproject)
function _agents_repo_local_slug --argument-names dir
test -n "$dir"; or return 1
set -l rp (path resolve "$dir")
set -l base (string lower -- (path basename "$rp") | string replace -ra '[^a-z0-9._-]' '-')
set -l digest (printf '%s' "$rp" | sha256sum | string split -f1 ' ')
printf 'local-%s-%s\n' "$base" (string sub -l 8 -- "$digest")
end
+70
View File
@@ -0,0 +1,70 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# DEPENDENCIES
# _agents_repo_local_slug
#
# SYNOPSIS
# _agents_repo_slug <dir>
#
# DESCRIPTION
# Derives the vault slug for a project directory. Prefers the normalized
# git remote URL so the same project keys identically from any clone on
# any machine; falls back to a path-derived key when no remote exists.
#
# Normalization strips the scheme, userinfo, and a numeric port, rewrites
# scp-form host:path to host/path, drops a trailing .git, lowercases, and
# maps every character outside [a-z0-9._-] to a dash. These all yield
# git.rootiest.dev-rootiest-fish-config:
#
# https://git.rootiest.dev/rootiest/fish-config.git
# git@git.rootiest.dev:rootiest/fish-config.git
# ssh://git@git.rootiest.dev:22/rootiest/fish-config.git
#
# With no remote the slug is local-<sanitized-basename>-<8 hex of sha256(realpath)>,
# where the basename is lowercased and mapped the same way as the remote form.
# That key is machine-dependent by construction and is best-effort only;
# agents-vault --adopt rebinds such an entry by hand.
#
# ARGUMENTS
# dir Absolute path to the project directory
#
# EXIT STATUS
# 0 Slug printed
# 1 No directory argument given
#
# RETURNS
# The slug, one line on stdout.
#
# EXAMPLE
# set -l slug (_agents_repo_slug /home/user/myproject)
function _agents_repo_slug --argument-names dir
test -n "$dir"; or return 1
set -l url (git -C "$dir" remote get-url origin 2>/dev/null)
if test -z "$url"
set -l remotes (git -C "$dir" remote 2>/dev/null)
if test (count $remotes) -gt 0
set url (git -C "$dir" remote get-url $remotes[1] 2>/dev/null)
end
end
if test -n "$url"
set -l s $url
# Order matters: the port must go before the scp-form rewrite, or
# ssh://host:22/a/b becomes host/22/a/b and diverges from the
# https slug for the same repository.
set s (string replace -r '^[A-Za-z][A-Za-z0-9+.-]*://' '' -- $s)
set s (string replace -r '^[^@/]+@' '' -- $s)
set s (string replace -r '^([^/:]+):[0-9]+/' '$1/' -- $s)
set s (string replace -r '^([^/:]+):' '$1/' -- $s)
set s (string replace -r '\.git$' '' -- $s)
set s (string replace -r '/+$' '' -- $s)
set s (string lower -- $s)
set s (string replace -ra '[^a-z0-9._-]' '-' -- $s)
printf '%s\n' $s
return 0
end
_agents_repo_local_slug "$dir"
end
+73
View File
@@ -0,0 +1,73 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _agents_repo_sync <dir> <message>
#
# DESCRIPTION
# Stages everything in <dir> and commits it with <message>. Shared by
# agents-init and agents-vault.
#
# It never touches the network, and that is the point rather than an
# omission. Both callers run on every agent launch, synchronously, ahead
# of the agent itself, and a fetch there blocks the launch for as long as
# an unreachable remote takes to time out and can prompt for credentials
# invisibly underneath a starting agent. Committing needs no remote at
# all -- only pushing does -- so the pull lives on agents-vault's push
# path, which is already opt-in for exactly this reason. An offline
# laptop therefore still gets a complete local backup, which is the whole
# point of keeping one.
#
# A rebase already in progress is refused rather than committed: the
# worktree then holds conflict markers, and recording those under a
# routine-looking message buries the conflict in the history instead of
# reporting it. The rebase is left exactly as it stands -- this function
# did not start it, so it is not this function's to abort -- and the
# caller says so.
#
# Commits are made with commit.gpgsign=false so a pinentry prompt can
# never block a shell or an agent launch. If a pre-commit or commit-msg
# hook rejects the commit (e.g. a secret scanner), that failure is
# surfaced too: nothing is committed and a diagnostic goes to stderr.
#
# 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, arguments were missing, or the commit
# itself failed (e.g. a pre-commit/commit-msg hook rejected it)
# 2 A rebase is in progress; nothing committed, nothing touched
#
# 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
# The guard above proved .git is a directory, so these are the same two
# paths `agents-vault --status` reports an unresolved rebase from.
if test -d "$dir/.git/rebase-merge"; or test -d "$dir/.git/rebase-apply"
echo "_agents_repo_sync: unresolved rebase in $dir; nothing committed" >&2
return 2
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"
return 0
else
echo "_agents_repo_sync: commit failed in $dir (hook rejected it?); nothing committed" >&2
return 1
end
end
+32
View File
@@ -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
+62 -55
View File
@@ -4,6 +4,9 @@
# CATEGORY
# 12-ai-and-developer-tools
#
# DEPENDENCIES
# _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
#
# SYNOPSIS
# agents-init [-a | --agents] [-p | --plugins] [-v | --verbose]
# [-q | --quiet] [-s | --silent] [-h | --help]
@@ -50,11 +53,16 @@
#
# With no flags, runs both --agents and --plugins setup; --agents re-runs
# only the AGENTS.md / symlink step and --plugins only the plans/specs/
# devlogs wiring step. Managed paths are added to .gitignore. The sub-repo
# is pulled first when it has an upstream, and at the end of every
# invocation any uncommitted changes inside it are auto-committed so
# agent-made edits are captured automatically. Fully idempotent: a second
# run produces no output and no new commits.
# devlogs wiring step. Managed paths are added to .gitignore. At the end
# of every invocation any uncommitted changes inside the sub-repo are
# auto-committed so agent-made edits are captured automatically. Fully
# idempotent: a second run produces no output and no new commits.
#
# The commit is local only. Nothing here fetches or pushes: the wrappers
# call this synchronously before starting an agent, and a network round
# trip there blocks the launch until an unreachable remote times out and
# can prompt for credentials with nobody watching. A sub-repo that has an
# upstream is pulled by hand, on the user's own schedule.
#
# Called automatically by the claude and agy wrappers on every invocation.
#
@@ -68,7 +76,8 @@
#
# EXIT STATUS
# 0 Setup completed successfully
# 1 Fatal error (git init failed, move failed, etc.)
# 1 Fatal error (git init failed, move failed, the AGENTS/ commit was
# rejected, or an unresolved rebase blocked it)
#
# EXAMPLE
# agents-init
@@ -165,7 +174,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/.version (1.0.0)$c_reset"
end
set -l _tools (_agents_init_install_tools "$agents_dir")
set -l _tools (_agents_repo_install_tools "$agents_dir")
if test -n "$_tools"
set changed 1
test $verbose -eq 1; and echo "$c_ok$_tools$c_reset"
@@ -259,38 +268,26 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
end
# ── Root symlink: AGENTS.md → AGENTS/AGENTS.md ───────────────────────
set -l _need_link 0
if not test -L "$root/AGENTS.md"
set _need_link 1
else if test (readlink "$root/AGENTS.md") != AGENTS/AGENTS.md
rm -f "$root/AGENTS.md"
set _need_link 1
end
if test $_need_link -eq 1
if not ln -s AGENTS/AGENTS.md "$root/AGENTS.md"
echo "$c_err""Error: could not create AGENTS.md symlink$c_reset" >&2
return 1
# Root symlinks point at files, not directories, so they cannot use
# _agents_repo_ensure_symlink (which is directory-only by design).
for pair in "AGENTS.md:AGENTS/AGENTS.md" "CLAUDE.md:AGENTS/CLAUDE.md"
set -l name (string split -f1 ':' -- $pair)
set -l want (string split -f2 ':' -- $pair)
set -l need 0
if not test -L "$root/$name"
set need 1
else if test (readlink "$root/$name") != "$want"
rm -f "$root/$name"
set need 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS.md → AGENTS/AGENTS.md$c_reset"
end
# ── Root symlink: CLAUDE.md → AGENTS/CLAUDE.md ───────────────────────
set -l _need_link 0
if not test -L "$root/CLAUDE.md"
set _need_link 1
else if test (readlink "$root/CLAUDE.md") != AGENTS/CLAUDE.md
rm -f "$root/CLAUDE.md"
set _need_link 1
end
if test $_need_link -eq 1
if not ln -s AGENTS/CLAUDE.md "$root/CLAUDE.md"
echo "$c_err""Error: could not create CLAUDE.md symlink$c_reset" >&2
return 1
if test $need -eq 1
if not ln -s "$want" "$root/$name"
echo "$c_err""Error: could not create $name symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked $name$want$c_reset"
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
end
# ── .gitignore ────────────────────────────────────────────────────────
@@ -461,24 +458,30 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
end
# ──────────────────────── Auto-commit AGENTS/ ────────────────────────────
# Pull first when an upstream is configured so the local .version reflects
# any remote bumps before we add to it (no-op for local-only repos).
if git -C "$agents_dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
git -C "$agents_dir" pull --rebase --autostash -q 2>/dev/null
end
git -C "$agents_dir" add -A 2>/dev/null
set -l status_out (git -C "$agents_dir" status --porcelain 2>/dev/null)
if test -n "$status_out"
set -l msg "chore: sync AGENTS repository"
test $did_init -eq 1; and set msg "chore: initialize AGENTS repository"
if git -C "$agents_dir" -c commit.gpgsign=false commit -q -m "$msg" 2>/dev/null
set changed 1
if test $verbose -eq 1
set -l sha (git -C "$agents_dir" rev-parse --short HEAD 2>/dev/null)
set -l realmsg (git -C "$agents_dir" log -1 --pretty=%s 2>/dev/null)
echo "$c_ok→ Committed AGENTS/ ($sha) $c_dim$realmsg$c_reset"
end
end
# Purely local: no fetch, no push. This function runs synchronously on
# every agent launch, and a network round trip there blocks the launch
# for as long as an unreachable remote takes to time out. Committing
# never needed one -- see _agents_repo_sync.
#
# Every way the commit can fail is an arm of its own. A sync that did
# not commit means agent-made edits were not captured, so it is a
# failure rather than a line to walk past -- and the missing `-ne 0`
# arm was not a cosmetic gap: fish resolves a branchless `if` to 0, so
# a hook-rejected commit fell straight through to a reported success.
set -l msg "chore: sync AGENTS repository"
test $did_init -eq 1; and set msg "chore: initialize AGENTS repository"
set -l sync_out (_agents_repo_sync "$agents_dir" "$msg")
set -l sync_rc $status
set -l failed 0
if test $sync_rc -eq 2
echo "$c_warn→ AGENTS/ has an unresolved rebase; nothing committed$c_reset" >&2
set failed 1
else if test $sync_rc -ne 0
echo "$c_err""Error: the AGENTS/ commit failed; nothing recorded$c_reset" >&2
set failed 1
else if test -n "$sync_out"
set changed 1
test $verbose -eq 1; and echo "$c_ok$sync_out$c_reset"
end
# Quiet summary: one line at the end, only if something actually changed
@@ -489,4 +492,8 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
echo "$c_ok→ Synced AGENTS scaffolding$c_reset"
end
end
# Explicit, because the branchless `if` above resolves to 0 and would
# otherwise be this function's exit status.
test $failed -eq 0
end
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -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 -1
View File
@@ -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
+28
View File
@@ -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
+14
View File
@@ -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
File diff suppressed because it is too large Load Diff