Files
fish-config/functions/agents-init.fish
T
rootiest 16ea31289d fix(agents-vault): repair slug migration, keep the network off the launch path
Six findings from the whole-branch review, all of which end in the same
place: a backup tool reporting success while nothing was backed up.

Slug migration nested the old entry inside the new one. The clear before
the rename was gated on the destination's claude/memory subdirectory
rather than on the destination itself, so an entry that exists without
one survived, `git mv A B` moved A *inside* B, and the mkdir below
fabricated a fresh empty memory directory for the live link to point at.
The real memory ended up one level deeper than --status and --restore
ever look, and the run returned 0. That shape is not exotic: git cannot
track an empty directory, so an entry committed while its memory was
empty comes back from a clone as projects/<slug>/origin and nothing
else -- and cloning the vault is this feature's own recovery path. The
destination is now moved aside the way --adopt already does it rather
than deleted (widening the rm -rf would have destroyed the clone's
origin log), its provenance is folded into the migrated entry, and every
failure path rolls back and reports.

The launch path pulled over the network. Both wrappers call agents-vault
synchronously before starting an agent, and the pull in the shared sync
helper was unguarded once an upstream existed: against a blackholed
remote it blocked the launch indefinitely and then aborted the commit,
so an offline laptop silently stopped being backed up at all. Committing
never needed a remote, so the pull moved to the push path, which was
already opt-in for exactly this reason. A failure there now distinguishes
a real rebase conflict (rebase-merge/ or rebase-apply/ present) from an
unreachable remote instead of calling both a conflict, and both network
calls set GIT_TERMINAL_PROMPT=0 and GIT_ASKPASS so they fail fast rather
than prompt with nobody watching. The helper still refuses to commit a
rebase in progress, and leaves it standing rather than aborting one it
did not start. This also restores agents-init's pre-refactor ability to
commit while offline.

The agy knowledge copy was unfiltered. The allowlist held at the agy root
and nowhere below it, so a planted .credentials.json inside knowledge/
was committed verbatim while the documentation promised nothing new
upstream added could leak in. Only *.md and *.json are copied now --
which is what the store actually holds -- so lock files, transcripts and
conversation databases are excluded by having no business in a backup
rather than by being known about. The scaffolded .gitignore also ignored
only the SQLite sidecars and not the databases, which is worse than
ignoring neither: a torn database landed in history with the write-ahead
log that would have completed it deliberately excluded. Both changes are
template-only, on a feature that has never shipped.

agents-init reported success when nothing was committed. It ended on a
branchless `if` with no arm for a failed commit, which fish resolves to
0 -- the same false zero already fixed in agents-vault, left in the
function the refactor was rewriting. It now has the arm and an explicit
final status.

The --adopt forward-failure path with no stash left a raw coreutils `mv:`
line and no statement that the adopt had been abandoned cleanly; it is
branded like every other error exit in the function.

Tests: the suite now clones a vault with git and runs agents-vault
against the clone, instead of trusting hand-built fixtures to have shapes
git can actually produce -- that blind spot shipped both of the merge
blockers. The "present but empty" migration fixture is rebuilt as the
origin-only directory a clone leaves behind, with the hand-built shape
kept as a separate case. Reverting each fix drops the suite from 285 to
279 (migration), 261 (network), 275 (knowledge allowlist) and 283
(agents-init status).
2026-09-03 18:56:46 -04:00

500 lines
23 KiB
Fish

# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# 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]
#
# DESCRIPTION
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
# a self-contained git repo for agent specifications, moves any existing
# agent-related files into it, and replaces them with symlinks so the outer
# project never tracks agent files directly.
#
# File layout after setup:
# AGENTS/AGENTS.md canonical agent spec (real file)
# AGENTS/CLAUDE.md real file (if CLAUDE.md existed separately)
# or symlink → AGENTS.md (single-source case)
# <root>/AGENTS.md → AGENTS/AGENTS.md
# <root>/CLAUDE.md → AGENTS/CLAUDE.md
# AGENTS/plans superpowers plans (real dir, .gitkeep)
# AGENTS/specs superpowers specs (real dir, .gitkeep)
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
# AGENTS/.version MAJOR.MINOR.PATCH structure version (seed 1.0.0)
# AGENTS/.agents-tools/ committed version-bump script + git hook shims
# docs/superpowers/plans → ../../AGENTS/plans (always)
# docs/superpowers/specs → ../../AGENTS/specs (always)
# docs/plans → ../AGENTS/plans (only if docs/plans existed)
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
#
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
#
# Each AGENTS repo carries a self-contained version bumper wired via
# core.hooksPath: a pre-commit hook bumps AGENTS/.version on every commit
# (MINOR when the tracked directory set changes, PATCH otherwise; MAJOR is
# manual-only), and a prepare-commit-msg hook appends "(vX.Y.Z)" to the
# commit subject. Each shim then chains (execs) to the global/system
# core.hooksPath hook of the same name, so this local override does not
# shadow global hooks (e.g. ggshield, Git LFS). The script/hooks are
# version-managed from scripts/agents-tools/ and refreshed when their marker
# is stale.
#
# Downstream tooling can read AGENTS/.version directly — a changed MINOR
# field signals a structure change.
#
# 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. 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.
#
# ARGUMENTS
# -a, --agents Set up AGENTS/ repo + AGENTS.md / CLAUDE.md symlinks only
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
# -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 (standard UNIX convention)
# -h, --help Show this help message and exit
#
# EXIT STATUS
# 0 Setup completed successfully
# 1 Fatal error (git init failed, move failed, the AGENTS/ commit was
# rejected, or an unresolved rebase blocked it)
#
# EXAMPLE
# agents-init
# agents-init --agents
# agents-init --plugins
# agents-init --quiet
function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec files and plugin dirs'
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 a/agents p/plugins v/verbose q/quiet s/silent -- $argv
or return 1
if set -q _flag_help
echo "$c_head""Usage:$c_reset $c_cmd""agents-init$c_reset $c_flag""[-a] [-p] [-v] [-q] [-s] [-h | --help]$c_reset"
echo
echo " Scaffold an AGENTS/ sub-repository for tracking agent specifications."
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-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md / CLAUDE.md symlinks only"
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
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 changes were made"
echo " $c_flag-s$c_reset, $c_flag--silent$c_reset Suppress all output; only errors are printed"
echo " $c_dim(no flags)$c_reset Run both --agents and --plugins setup"
return 0
end
# No flags → run both modes
set -l do_agents 0
set -l do_plugins 0
set -q _flag_agents; and set do_agents 1
set -q _flag_plugins; and set do_plugins 1
if test $do_agents -eq 0; and test $do_plugins -eq 0
set do_agents 1
set do_plugins 1
end
# Output verbosity: verbose (default), quiet (summary if changed), silent (errors only)
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
# --verbose is explicit default; no-op but accepted for completeness
# Resolve target root: git root if available, otherwise cwd
set -l root (git rev-parse --show-toplevel 2>/dev/null)
test -z "$root"; and set root (pwd)
set -l agents_dir "$root/AGENTS"
set -l plugins_dir "$agents_dir/plugins"
# Track whether any action was taken this run (drives quiet-mode summary)
set -l changed 0
# ─────────────────────── Always: AGENTS/ sub-repo ───────────────────────
set -l did_init 0
if not test -d "$agents_dir"
if not mkdir -p "$agents_dir"
echo "$c_err""Error: could not create AGENTS/$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/$c_reset"
end
if not test -d "$agents_dir/.git"
git -C "$agents_dir" init -q
or begin
echo "$c_err""Error: git init failed in AGENTS/$c_reset" >&2
return 1
end
set changed 1
set did_init 1
test $verbose -eq 1; and echo "$c_ok→ Initialized git repo in AGENTS/$c_reset"
end
# ─────────────── Always: version tracking (.version + hooks) ───────────────
if not test -f "$agents_dir/.version"
echo 1.0.0 >"$agents_dir/.version"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/.version (1.0.0)$c_reset"
end
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"
end
set -l _hp (git -C "$agents_dir" config --local core.hooksPath 2>/dev/null)
if test "$_hp" != .agents-tools/hooks
git -C "$agents_dir" config --local core.hooksPath .agents-tools/hooks
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Set core.hooksPath → .agents-tools/hooks$c_reset"
end
# ──────────────────────────── --agents mode ──────────────────────────────
if test $do_agents -eq 1
# Detect which root-level files are real (not symlinks)
set -l has_agents 0
set -l has_claude 0
if test -f "$root/AGENTS.md"; and not test -L "$root/AGENTS.md"
set has_agents 1
end
if test -f "$root/CLAUDE.md"; and not test -L "$root/CLAUDE.md"
set has_claude 1
end
# ── Move real files into AGENTS/ ──────────────────────────────────────
if test $has_agents -eq 1; and test $has_claude -eq 1
# Both exist: preserve each as its own file in AGENTS/
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
end
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
if not mv "$root/CLAUDE.md" "$agents_dir/CLAUDE.md"
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/CLAUDE.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
end
else if test $has_agents -eq 1
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
end
else if test $has_claude -eq 1
# Only CLAUDE.md: treat it as the agent spec
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/CLAUDE.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/AGENTS.md$c_reset"
end
else
# Neither exists: create AGENTS/AGENTS.md with the agent directive
if not test -f "$agents_dir/AGENTS.md"
printf '%s\n' \
'# AGENTS.md' \
'' \
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
'> You may be reading this file via a symlink (`CLAUDE.md` or `AGENTS.md`) in' \
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
'> through symlinks and will throw an error.' \
'>' \
'> **DO NOT** attempt to write to or edit `CLAUDE.md` or `AGENTS.md` in the' \
'> project root. If you need to update these instructions, you **MUST write' \
'> directly to `AGENTS/AGENTS.md`**.' >"$agents_dir/AGENTS.md"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/AGENTS.md with agent directive$c_reset"
end
end
# ── Ensure AGENTS/CLAUDE.md exists ────────────────────────────────────
# When both files existed, AGENTS/CLAUDE.md is already a real file.
# Otherwise, create it as a symlink → AGENTS.md (within AGENTS/).
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
if not ln -s AGENTS.md "$agents_dir/CLAUDE.md"
echo "$c_err""Error: could not create AGENTS/CLAUDE.md symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
end
# 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
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
end
# ── .gitignore ────────────────────────────────────────────────────────
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "/AGENTS.md" "/CLAUDE.md")
if test -n "$_gi"
set changed 1
test $verbose -eq 1; and echo $_gi
end
end
# ─────────────────────────── --plugins mode ──────────────────────────────
if test $do_plugins -eq 1
set -l docs_dir "$root/docs"
if not test -d "$docs_dir"
if not mkdir -p "$docs_dir"
echo "$c_err""Error: could not create docs/$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created docs/$c_reset"
end
# Consolidate plans/ and specs/ directly under AGENTS/ (no plugins/ layer).
# Real source dirs from any known layout are merged into the canonical
# AGENTS/<tgt> and removed: docs/<tgt>, docs/superpowers/<tgt>,
# AGENTS/plugins/<tgt>, AGENTS/plugins/superpowers/<tgt> (legacy).
# Symlinks: docs/superpowers/<tgt> always (superpowers skills expect it);
# docs/<tgt> only when it had existed as a real directory.
for tgt in plans specs
set -l canonical "$agents_dir/$tgt"
# Remember whether docs/<tgt> was a real dir (drives its symlink below)
set -l docs_tgt_was_real 0
if test -d "$docs_dir/$tgt"; and not test -L "$docs_dir/$tgt"
set docs_tgt_was_real 1
end
test -d "$canonical"; or mkdir -p "$canonical"
# Candidate real source dirs to merge into the canonical dir
set -l srcs "$docs_dir/$tgt" "$plugins_dir/$tgt" "$plugins_dir/superpowers/$tgt"
# Only follow docs/superpowers/<tgt> when docs/superpowers is a real
# dir; if it is a legacy symlink, its target is already covered by the
# AGENTS/plugins/superpowers/<tgt> source above.
if not test -L "$docs_dir/superpowers"
set -a srcs "$docs_dir/superpowers/$tgt"
end
for src in $srcs
if test -d "$src"; and not test -L "$src"
set -l rel (string replace "$root/" "" "$src")
set -l contents (command ls -A "$src" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -rn "$src/." "$canonical/"
echo "$c_err""Error: could not merge $rel → AGENTS/$tgt$c_reset" >&2
return 1
end
end
if not rm -rf "$src"
echo "$c_err""Error: could not remove $rel after merge$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Merged $rel → AGENTS/$tgt$c_reset"
end
end
test -f "$canonical/.gitkeep"; or touch "$canonical/.gitkeep"
# docs/<tgt> symlink: only recreated when it had been a real directory
set -l docs_link "$docs_dir/$tgt"
if test $docs_tgt_was_real -eq 1
if not test -L "$docs_link"
if not ln -s "../AGENTS/$tgt" "$docs_link"
echo "$c_err""Error: could not create docs/$tgt symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked docs/$tgt → AGENTS/$tgt$c_reset"
end
else if test -L "$docs_link"; and test (readlink "$docs_link") != "../AGENTS/$tgt"
# Stale legacy symlink (pointed into AGENTS/plugins) → drop it
rm -f "$docs_link"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed stale docs/$tgt symlink$c_reset"
end
end
# docs/superpowers/ must be a real dir holding the plans/ + specs/ symlinks.
# Replace any legacy docs/superpowers symlink (content merged above).
if test -L "$docs_dir/superpowers"
rm -f "$docs_dir/superpowers"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed legacy docs/superpowers symlink$c_reset"
end
if not test -d "$docs_dir/superpowers"
if not mkdir -p "$docs_dir/superpowers"
echo "$c_err""Error: could not create docs/superpowers/$c_reset" >&2
return 1
end
end
# docs/superpowers/<tgt> symlinks — always present (superpowers default)
for tgt in plans specs
set -l sp_link "$docs_dir/superpowers/$tgt"
if not test -L "$sp_link"
if not ln -s "../../AGENTS/$tgt" "$sp_link"
echo "$c_err""Error: could not create docs/superpowers/$tgt symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked docs/superpowers/$tgt → AGENTS/$tgt$c_reset"
else if test (readlink "$sp_link") != "../../AGENTS/$tgt"
rm -f "$sp_link"
ln -s "../../AGENTS/$tgt" "$sp_link"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Relinked docs/superpowers/$tgt → AGENTS/$tgt$c_reset"
end
end
# Drop the now-empty legacy AGENTS/plugins/ layer entirely
if test -d "$plugins_dir"
rm -rf "$plugins_dir"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed legacy AGENTS/plugins/$c_reset"
end
# ── AGENTS/devlogs (standard location for agent dev logs) ─────────────
set -l devlogs_dir "$agents_dir/devlogs"
set -l docs_devlogs "$docs_dir/devlogs"
if test -d "$docs_devlogs"; and not test -L "$docs_devlogs"
# Migrate an existing docs/devlogs into AGENTS/devlogs, then symlink
test -d "$devlogs_dir"; or mkdir -p "$devlogs_dir"
set -l contents (command ls -A "$docs_devlogs" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -rn "$docs_devlogs/." "$devlogs_dir/"
echo "$c_err""Error: could not copy docs/devlogs → AGENTS/devlogs$c_reset" >&2
return 1
end
end
if not rm -rf "$docs_devlogs"
echo "$c_err""Error: could not remove docs/devlogs after copy$c_reset" >&2
return 1
end
if not ln -s "../AGENTS/devlogs" "$docs_devlogs"
echo "$c_err""Error: could not create docs/devlogs symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved docs/devlogs → AGENTS/devlogs$c_reset"
else if not test -d "$devlogs_dir"
if not mkdir -p "$devlogs_dir"
echo "$c_err""Error: could not create AGENTS/devlogs$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/devlogs/$c_reset"
end
test -f "$devlogs_dir/.gitkeep"; or touch "$devlogs_dir/.gitkeep"
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --plugins" "docs/superpowers" "docs/plans" "docs/specs" "docs/devlogs")
if test -n "$_gi"
set changed 1
test $verbose -eq 1; and echo $_gi
end
end
# ──────────────────────── Auto-commit AGENTS/ ────────────────────────────
# 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
if test $quiet -eq 1; and test $changed -eq 1
if test $did_init -eq 1
echo "$c_ok→ Initialized AGENTS scaffolding$c_reset"
else
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