Commit Graph
770 Commits
Author SHA1 Message Date
rootiest 8487f124c7 Merge remote-tracking branch 'origin/main' into fix/tricks-double-source-and-paru-wrapper-dedup 2026-09-08 01:30:43 -04:00
rootiest a994f9e91c Merge pull request 'perf(conf.d): cut shell startup latency by ~30%' (#130) from perf/startup-latency into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 1m49s
CI / build-docs (push) Failing after 3m48s
2026-09-08 05:30:05 +00:00
rootiest 43752aa268 Merge remote-tracking branch 'origin/main' into perf/startup-latency
# Conflicts:
#	tests/functional.fish
2026-09-08 01:28:27 -04:00
rootiest 0a2290640f Merge pull request 'test: unify the test harness, discover suites, fix a real hermeticity leak' (#134) from test/test-harness-overhaul into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 1m49s
CI / build-docs (push) Successful in 3m46s
2026-09-08 05:26:28 +00:00
rootiest 23bf649c5f fix(functions): dedupe conflicting paru/yay wrapper generators
__fish_config_sync_logging carried its own inferior copy of the
paru/yay wrapper generator (tee-based, no PTY, no progress-bar
rendering, hard-coded /usr/bin/paru|yay) alongside the canonical
version-6 generators in conf.d/paru-wrapper.fish and
conf.d/yay-wrapper.fish. Both wrote the same file with different
version markers and the same misattributed provenance comment, so
whichever ran last won and a subsequent C5 toggle would flip it back.
See startup-latency-JOB-BRIEF-FINDINGS.md §2.

__fish_config_sync_logging now delegates entirely to the canonical
generators instead of carrying a copy: they already resolve the real
binary via __fish_real_command (never /usr/bin-assumed) and
independently gate on their own C2/C5 keys, covering both the
enabled-regenerate and disabled-remove cases.

One behavior change falls out of delegating rather than special-casing
around it: a wrapper is no longer generated when C2 (paru-autoexec /
yay-autoexec) is disabled, even if C5 logging is on. The removed
sync-logging copy never checked C2, so it could reinstall a wrapper
the user had explicitly turned auto-exec off for.

Adds functions/_fish_source_scoped.fish: a small helper that runs
source inside its own function-call boundary. source itself runs in
the caller's scope, so a bare return inside a sourced conf.d guard
(both files have several) would otherwise unwind whatever function
called source directly -- verified with a minimal repro before relying
on it. Routing through this helper contains the return to just that
call, so calling paru's generator and then yay's actually reaches the
second call.

Manually verified end-to-end in an isolated HOME/XDG sandbox with a
stubbed paru/yay: enable generates both v6 wrappers, disable removes
both and drops the sentinel, re-enable regenerates them and clears the
sentinel.
2026-09-08 01:22:22 -04:00
rootiest f5c4016fa6 perf(conf.d): stop re-running expensive setup on tricks.fish's second source
tricks.fish is sourced twice per shell on CachyOS: once by the conf.d
autoload, once forced by config.fish to re-win over the distro's own
tricks.fish (measured 4.55ms for the second pass, see
startup-latency-JOB-BRIEF-FINDINGS.md §1). The bang-bang functions,
aliases, and history override further down need to re-run on both
passes since those are what re-assert over the distro config, but the
PATH/MANPAGER setup does not.

Gates fish_add_path and the type -q bat probe behind a once-per-session
global, guarding only that block. Everything else in the file, and the
coupling with config.fish's own cachyos-tricks guard, is unchanged.
2026-09-08 01:22:10 -04:00
rootiest 1f847bfce0 test(guards): cover always/on, always/off and AND via a synthetic registry
14 cases. The production registry has 65 entries all carrying exactly one
tag and no always/* anywhere, so these three documented semantics are
unreachable from real data -- the synthetic keys/values table is the only
way to reach them. Reasoning recorded at the fixture.

Also pins the degenerate no-slash tag and reaches C5 through
__fish_config_op_enabled, which is the path production code takes.
2026-09-07 20:12:22 -04:00
rootiest 7a6a7339dc test(guards): cover op_enabled against the real registry
5 cases: default-on, .fish suffix stripping, category off, subcategory
overriding it, and the documented fail-open on a missing entry.
2026-09-07 20:11:06 -04:00
rootiest 2efbcfc53f test(guards): cover registry lookup, including identity:site pairing
5 cases. The wrong-site case pins that the key is the identity:site pair
rather than the identity alone.
2026-09-07 20:10:15 -04:00
rootiest 639d139799 test(guards): pin C5 logging as opt-in, including that the master cannot enable it
8 cases on the real __fish_config_op_logging name. AGENTS.md records this
as a deliberate special case agents keep trying to 'fix' out; three of
these cases exist specifically to turn that into a test failure.

Includes the three subcategory cases proving the opt-in is inherited
through chain[-1] rather than special-cased per subcategory.
2026-09-07 20:09:26 -04:00
rootiest 2b1e99ab47 test(guards): cover the cascade's subcategory -> category -> master order
13 cases on invented variable names, so nothing ambient can perturb them:
default-on, explicit truthy/falsy, unrecognized-defers, subcategory
overriding the category in both directions, and the four cases that
together pin the master as an off switch only.
2026-09-07 20:07:37 -04:00
rootiest a8d564803b test(guards): cover __fish_variable_check's four exit codes
21 cases: a precondition asserting the fork's registry is loaded, then
truthy/falsy/unset/empty/unrecognized/no-argument/list.

Runs isolated, which is what lets these cases manipulate real guard
variable names without being able to reach the user's universal variables.
2026-09-07 20:06:42 -04:00
rootiest 53b353eb7f test: assert conf.d stays lazy in non-interactive shells
Spawns one non-interactive child against the sandboxed config and asserts on
what loaded -- abbreviations, key bindings, bang-expansions, the C5 event
handlers and the tailscale completion machinery must all be absent. Exit
codes name which guard regressed. No wall-clock assertions, so it cannot
flake.
2026-09-07 20:04:47 -04:00
rootiest 0a9cfebcad refactor(tests): discover suites instead of naming them
run-tests.fish globs tests/test-*.fish and runs each in the mode the suite
declares in its own header. Isolated is the default and no typo can
promote a suite to in-session: detection is case-insensitive so a
near-miss is caught, the value comparison is exact.

In-session suites share one sandboxed session; isolated suites each get a
--no-config child with temp XDG dirs. Counts come back through a file so
output keeps streaming.

functional.fish becomes test-session.fish, its 15 predicates rewritten as
check calls. 332 assertions, unchanged per-phase.
2026-09-07 20:02:59 -04:00
rootiest d49d90a2de chore(docs): regenerate manual
docs/fish-config.1 is left for CI to regenerate: the local pandoc is 3.10.2
against CI's 3.1.3, so rebuilding it here emits ~4700 lines of formatter
churn unrelated to this change. The component registry rebuilt identically.
2026-09-07 20:01:24 -04:00
rootiest 85c753aaa6 docs: point tailscale and cheat completions at completions/
Follows the relocation out of conf.d/. The completions/ branch of the file
tree is expanded from the real directory at build time, so it needs no
hand-written children. Generated docs are regenerated separately.
2026-09-07 19:59:53 -04:00
rootiest d3028d7703 refactor(tests): extract the shared assertion core into tests/lib.fish
check/section/report and the counters move out of the vault suite
unchanged. report also writes its counts to $FISH_CONFIG_TEST_COUNTS so a
driver can aggregate without parsing stdout, and ends on an explicit
boolean per AGENTS.md item 5.

All 317 vault assertions and every fixture helper are untouched.
2026-09-07 19:59:23 -04:00
rootiest 52711a42a2 perf(conf.d): skip paru/yay wrapper generation in non-interactive shells
Neither file defines a function or sets a global; their only effect is
writing ~/.local/bin/<tool>, which every interactive session does anyway.
Combined 10.7 ms off every fish -c.
2026-09-07 19:58:10 -04:00
rootiest c998d1b8d7 perf(conf.d): skip C5 logging sync in non-interactive shells
__fish_config_sync_logging ran on every fish -c, mkdir+touching the C5
sentinel on disk from every subshell. Its consumers — the Kitty watcher and
the paru/yay wrappers — are interactive-context, and every interactive shell
still reconciles the state.
2026-09-07 19:56:45 -04:00
rootiest 5fa849fd81 perf(conf.d): skip wakatime hook in non-interactive shells
fish_postexec is emitted only by the interactive reader (verified), so the
handler could never fire in a script. No telemetry behaviour changes.
2026-09-07 15:00:57 -04:00
rootiest 8fc27fa9c2 fix(tests): route the runner's utilities through command
run-tests.fish executes under the config it tests, which shadows cp, rm
and cat. The real hazard is cp: the config aliases it to 'cp -i', which on
a non-empty destination reads EOF in a non-interactive runner, silently
skips the copy and exits 0 -- a sandbox missing config files, reported as
success.

rm -rf and cat were measured and behave correctly as-is (the rm wrapper
bails to command rm on any non-recursive flag, so -rf really deletes and
does not trash). Prefixed anyway: a test runner must not depend on the
configuration under test.
2026-09-07 15:00:10 -04:00
rootiest 2edea59ca3 perf(conf.d): skip auto-pull handler in non-interactive shells
The --on-variable PWD handler backgrounds a git fast-forward. A script that
cd's was firing it, which is also where AGENTS.md Task #4's credential
prompt could surface from a background job.
2026-09-07 15:00:01 -04:00
rootiest 5edfb5b720 perf(conf.d): skip theme colors in non-interactive shells
fish_color_* is consumed only by the syntax highlighter. The guard sits
below the existing cleanup branch so stale-FZF_DEFAULT_OPTS cleanup keeps
running where it does today; the FZF value itself is a persisted universal
and survives regardless.
2026-09-07 14:59:10 -04:00
rootiest dd672ded3b fix(tests): isolate the vault suite from the live config and universal variables
The suite ran under a plain `fish`, which loads the user's real
~/.config/fish and their universal variables -- this repo doubles as that
config. A test manipulating a guard variable could erase a real universal
variable out of the running shell.

Override XDG_CONFIG_HOME/XDG_DATA_HOME and pass --no-config. HOME stays
real on purpose: overriding it makes the suite's two hermeticity
assertions vacuous. Reasoning recorded at the call site.

Vault suite still 317/317 with byte-identical stderr.
2026-09-07 14:59:05 -04:00
rootiest 4d6b99fb28 perf(conf.d): skip starship.fish in non-interactive shells
Defines fish_prompt only. The guard precedes the op-guard and the type -q
PATH scan so both are skipped in scripts. Scripts fall back to the repo's
autoloadable functions/fish_prompt.fish, which nothing invokes anyway.
2026-09-07 14:58:18 -04:00
rootiest bb5b6b361a perf(conf.d): skip key_bindings.fish in non-interactive shells
fish_user_key_bindings is invoked only by the interactive reader.
2026-09-07 14:57:02 -04:00
rootiest 13d415db2b perf(conf.d): skip bash_expands.fish in non-interactive shells
The six expand_* functions are only ever reached through abbr --function,
which fires during interactive expansion.
2026-09-07 14:56:16 -04:00
rootiest c3a2b6fa0c perf(conf.d): skip abbr.fish in non-interactive shells
61 abbreviations were declared on every fish -c. Abbreviations expand only
in the line editor, so nothing outside an interactive session can use them.
2026-09-07 14:55:31 -04:00
rootiest 3d8d6a4468 refactor(conf.d): move cheat completions to completions/
Thirteen lines, every one a `complete -c cheat` registration, sourced on
every shell start from the wrong directory. Startup cost was already ~0
because its command substitutions are lazy, but completions/ is where fish
expects the file and the move is free.
2026-09-07 14:54:38 -04:00
rootiest fb21fa550a perf(completions): drop tailscale self-priming completion call
The Cobra-generated block ran `complete --do-complete "tailscale "` to flush
pre-existing completions before erasing them, which executed the tailscale
binary. From completions/ it has no job: fish autoloads only the first match
on $fish_complete_path and the repo's completions/ precedes the vendor dir,
so the vendor file is never sourced.

Verified byte-identical completion output across four probes with the vendor
file present. A comment at the deletion site records the reasoning.
2026-09-07 14:53:35 -04:00
rootiest 321b80f1f8 perf(conf.d): load tailscale completions lazily from completions/
conf.d/tailscale.fish is 252 lines of Cobra-generated completion that fish
sourced on every shell start, and its self-priming block executed the
tailscale binary to warm the completion cache. completions/ is the directory
fish autoloads on first <TAB>.

Measured: 19.2 ms off both interactive and non-interactive startup.
2026-09-07 07:32:03 -04:00
Gitea Actions 08e66c81ca chore(docs): regenerate manual, man page, and component registry 2026-09-04 21:28:58 +00:00
rootiest c009bbf257 Merge pull request 'feat(docs): GitHub mirror icon, README-sourced doc sections, auto-generated TOC' (#129) from feat/docs-readme-sections into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 2m0s
CI / build-docs (push) Successful in 3m54s
Reviewed-on: #129
2026-09-04 21:23:06 +00:00
rootiest e6d3fd80b3 style(docs): add fish to logo icon 2026-09-04 17:14:17 -04:00
rootiest 54375a9530 style(docs): match Gitea icon size to GitHub's 24px (1.5rem) 2026-09-04 16:50:16 -04:00
rootiest 2ee2806e01 fix(docs): make the Gitea icon override actually apply, tune GitHub to 1.5x
UnoCSS's .i-pajamas:gitea rule is unlayered CSS; our override lived in
@layer starlight.core, and unlayered rules always beat layered ones
regardless of specificity or source order. The Gitea icon has silently
stayed at UnoCSS's 1em default since it was first added -- confirmed via
computed style in the browser, not just reading the stylesheet source.
!important restores the override across the layer boundary.

Also drops the GitHub icon from 2x to 1.5x per visual feedback.
2026-09-04 16:49:04 -04:00
rootiest 23cae4bd10 chore(docs): regenerate man page
pandoc wasn't available when this branch's earlier commit ran
build-manual.py --concat; regenerate docs/fish-config.1 from the
current docs/fish-config.md now that it is.
2026-09-04 16:43:27 -04:00
rootiest 8c21d34943 style(docs): double the Gitea/GitHub header icon size
--sl-nav-height is a fixed CSS var, not driven by icon content, so both
icons can grow without changing the header bar's height.
2026-09-04 16:43:27 -04:00
rootiest 4912c4052f feat(docs): add GitHub mirror icon, README-sourced doc sections, auto-generated TOC
Adds a GitHub social icon to the docs site header alongside the existing
Gitea one, and documents in the README's Contributing section that
git.rootiest.dev is the base repo while the GitHub copy is a one-way
mirror, so forks/issues/PRs should go through Gitea.

Adds Testing, Contributing, Attribution, and License sections to the
manual/man page/site, sourced directly from README.md via a new
`<!-- README: Heading -->` placeholder mechanism in build-manual.py, so
the README stays the single source of truth for those sections instead
of a hand-maintained copy drifting out of sync.

Also converts docs/manual/00-table-of-contents.md from a hand-typed list
to a generated one (mt.walk()-driven), fixing a numbering drift where
Components Reference was omitted and every section after it was off by
one relative to its own manTitle.
2026-09-04 16:34:22 -04:00
Gitea Actions c5bbbf06e3 chore(docs): regenerate manual, man page, and component registry 2026-09-04 02:52:27 +00:00
rootiest 76afcd5e13 Merge pull request 'feat(privacy): add DO_NOT_TRACK and DISABLE_TELEMETRY env vars under C3 privacy' (#128) from feat/do-not-track-privacy-env into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 1m53s
CI / build-docs (push) Successful in 9m30s
Reviewed-on: #128
2026-09-04 02:40:53 +00:00
rootiest 28a88a9bdf feat(privacy): add DO_NOT_TRACK and DISABLE_TELEMETRY env vars under C3 privacy 2026-09-03 22:35:05 -04:00
Gitea Actions d278a47b32 chore(docs): regenerate manual, man page, and component registry 2026-09-03 23:52:10 +00:00
rootiest dc3236fddb Merge pull request 'test(agents-vault): make the suite hermetic against git configuration' (#127) from fix/vault-test-git-identity into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 1m58s
CI / build-docs (push) Successful in 9m19s
Reviewed-on: #127
2026-09-03 23:40:56 +00:00
rootiest 73d41da07d test(agents-vault): inject failures in a way that survives uid 0
Five fixtures forced a failure with chmod 500 on a parent directory. That
is silently useless for root: uid 0 bypasses the mode bits, the operation
succeeds, and a test asserting a failure path then reports the tool as
broken rather than the injection as ineffective.

CI runs the suite as root inside a container, so 21 checks failed there
while passing for every developer and every reviewer -- the same shape as
the git-identity gap in the previous commit. A fixture that quietly stops
injecting is worse than one that never existed: it reads as coverage.

Shim the single command each failure hinges on, matched by a path fragment
so nothing else in the run is disturbed. A command that exits 1 on purpose
does so for every uid. Which command matters: ensure_symlink removes an
existing link before it creates the new one, so the two adopt fixtures
have to fail at that rm to leave the live link in place, and shimming ln
would be too late. The restore fixture deletes the link first and so does
hinge on ln, and the failing --remote fixture hinges on git set-url.

The one remaining chmod 500 asserts a success, not a failure, and its real
proof is the direct assertions on where the stash landed.

Reproduce the root case locally without a container:
  unshare -r fish tests/test-agents-vault.fish

Verified: 296/317 as root before, 317/317 after; 317/317 unchanged as an
ordinary user; and neutering the shims reproduces exactly 296/317, the
count CI reported.
2026-09-03 19:34:01 -04:00
rootiest 75fbfa1e16 test(agents-vault): make the suite hermetic against git configuration
The suite was hermetic against $HOME and ~/.claude but not against git
config. new_repo set an identity on the repos the tests create; nothing
set one on the repos the tool creates -- the scaffolded vault and the
AGENTS/ sub-repo. Those inherited a global user.name/user.email on a
developer machine and committed fine, so the gap was invisible to every
local run and every review; on a runner with no gitconfig they died with
"Author identity unknown", and the suite reported the tool own correct
"nothing recorded" handling as 71 failures.

Supply the identity through the environment, which reaches the git calls
inside agents-vault and agents-init as well as the ones the tests make.

Pin commit.gpgsign off for the mirror-image reason: a developer with
signing enabled would otherwise have tool-created repos reach for a key,
and a hardware token would prompt for a touch partway through the run.

Pin init.defaultBranch because the rebase fixtures build an upstream and a
clone and need the two to agree on a branch name. A global saying main and
a bare default of master disagree, and the fixture then fails to create the
rebase the test is about, reporting a tool failure that never happened.

Verified with GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null:
267/317 before, 317/317 after; unchanged at 317/317 with a normal config.
2026-09-03 19:20:11 -04:00
rootiest 3c967c0cee Merge pull request 'feat(agents-vault): back up curated agent memory to a host-scoped vault repo' (#126) from feat/agent-memory-vault into main
CI / github-mirror (push) Skipped
CI / test (push) Failing after 1m19s
CI / build-docs (push) Skipped
Reviewed-on: #126
2026-09-03 23:09:55 +00:00
rootiest f9d400699f fix(agents-vault): keep the user's ssh command and drop the quadratic walk
The connect bound was delivered by injecting GIT_SSH_COMMAND, and an
environment variable outranks git's core.sshCommand -- so the guard,
which read only the environment, did not merely miss a configured ssh
command, it overruled one. A vault remote reachable only as
`ssh -i ~/.ssh/vault_key` failed to authenticate on every push, autopush
and --push alike, for the sake of a ten-second timeout. Both spellings
now count, and `set -qx` rather than `set -q` on the environment side so
an unexported fish variable -- which git never sees -- does not leave the
push with neither the user's ssh command nor a bound.

The agy knowledge walk appended each find with `set -a`, which rewrites
the whole variable every time; 500 files cost 21ms but 20,000 cost 58s,
on a path that runs in front of every agent launch. The walk now prints
NUL-separated and the list is built once, which is flat: the same 20,000
files take 707ms. NUL rather than newline because a filename may legally
contain one. What the walk collects, and its symlink and dot-led
semantics, are byte-for-byte unchanged.

Autopush is bounded by timeout(1) alone, so without it the launch path
was quietly back to an open-ended network call. It now says so and skips
the push instead; --push was never wrapped and is unaffected.
2026-09-03 18:56:47 -04:00
rootiest d9b56790c5 fix(agents-vault): keep the knowledge walk and the launch push inside their bounds
The agy knowledge allowlist walked the store with `**` and copied with
plain cp, so a symlink inside the store was both followed and dereferenced.
The extension rule still bounded what kind of file was collected, but not
whose: a link to a home directory hands over settings.json, CLAUDE.md and
every cached .json in it, and those reached a commit. A link to / made the
walk itself unbounded, on the path that runs before every agent launch.
The tree is now walked a level at a time and nothing that is a symlink is
followed or copied.

Autopush had the same shape one layer out. Neither GIT_TERMINAL_PROMPT nor
GIT_ASKPASS closes a socket, and git has no connect timeout to set: against
a blackholed address a push took 135s with http.lowSpeedLimit and
http.lowSpeedTime set as well as without them. ssh can time itself out and
is now told to; the autopush pull and push are additionally capped with
timeout(1). An explicit --push stays uncapped, since it is watched and has
to report what a real transfer really did.

Also: scaffold /.migrate-stash into .gitignore beside /.adopt-stash, which
the comment already claimed was covered; and drop the live memory path
during a slug migration only when it is a link. Reached from the
path-derived fallback candidate it can be a real populated directory, where
rm -f correctly refuses -- but said so in rm's voice, so a --silent run that
had succeeded printed what read as an error.
2026-09-03 18:56:46 -04:00
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