Adds scripts/** to the push path filter -- it was missing entirely, so
that directory never triggered CI regardless of what changed there.
pull_request no longer has a paths: filter (moved that check inside each
job, in shell, since a label-only PR with no relevant diff still needs
to trigger the workflow for the job-level label check to ever run).
Added labeled/unlabeled to pull_request types for the same reason.
Splits the old build-docs job into a docs job with two sections: doc
tests/build (generate concat, verify-manual.py, compile man page) run
whenever relevant on any event; publish (site build, Cloudflare deploy,
commit-back) is step-gated to push/dispatch only, as before. The docs
job now also runs on a PR when it's labeled Kind/Documentation or
Area/Docs, or its diff touches docs/manual/**, docs/build-manual.py,
docs/manualtools.py, docs/verify-manual.py, or docs/site/**.
test gains the same shape: also runs on a PR labeled Kind/Testing,
Area/Tests, Area/CI, or Area/Scripts, independent of what it touches.
docs no longer needs: test. main's branch protection already requires
test to pass before a PR merges, so by the time a push-to-main reaches
this job, test has necessarily already passed; re-checking it here
would be redundant. Leaves the same gap as
block_admin_merge_override=false on that rule: a direct admin push
bypasses it, an accepted trust boundary, not a new one.
-r clears the repo's skip list first so declined files are reconsidered.
-f untracks every pending match immediately, no prompt. -i prompts once
per file instead of once for the whole group. -w, -f, -i are mutually
exclusive (argparse --exclusive), -r combines with any of them.
completions/fisher.fish and functions/fisher.fish match the Fisher-managed
ignore rule added in c77a52a but were never scrubbed from the index. Found
by gitignore-scrub itself (gi fish). Files remain on disk, untracked only.
Standalone function, not gi-private: default mode prompts once for all
tracked-but-ignored files and offers git rm --cached, remembering a
decline per-path in the repo's local git config (gitignore-scrub.skip)
so the same file isn't re-asked. -w/--warn is read-only (prints Warning
lines, no prompt, no mutation) for non-interactive callers like a git
hook. Skips silently above $GITIGNORE_SCRUB_LIMIT tracked files (default
5000) to avoid latency on huge repos.
gi now calls gitignore-scrub at the end of any run that touched
.gitignore.
build-docs's auto-commit step was seen rejected as non-fast-forward
(run 976, sha 3bbda31): npm ci + astro build + the Cloudflare Pages
deploy ahead of it can take several minutes, long enough for another
PR to merge into main first. A bare `git push` has no way to recover
from that -- the whole job just fails, even though every real step
(tests, manual verification, man page, site build, deploy) already
succeeded.
This commit only ever touches three generated files
(fish-config.md/.1, the component registry), so a rebase onto
whatever landed is always mechanical -- retry push up to 3 times,
rebasing onto origin/main between attempts. Ends on an explicit
`test "$pushed" -eq 1` rather than trailing off the for loop, so a
run that exhausts all three retries still fails loudly instead of
reporting success.
PR #168's Notes section named several functions with a real
external-tool dependency that no `type -q`/`command -q`/`command -v`/
`which` guard covers anywhere in the tree, deliberately left out of
DEPENDENCIES to avoid breaking test_dependencies_resolve. Adds the
guard each was missing, then declares the dependency now that it
resolves:
- bkg, detach: nohup
- gitui: gitui (self-shadow; type -q -f to skip the function itself)
- play-media: mpv, vlc -- already guarded via `type -q -f $p` in a
loop, just never recognized as one (see next point)
- steam-dl: systemd-inhibit, steam
- wake-lock: systemd-inhibit
- split, spwin, tab: wezterm, konsole (kitty already declared)
docs/verify-manual.py's guard-detection regex only matched `type -q
<name>` immediately, so `type -q -f $p` (the `-f` flag excludes
functions from the match, needed wherever a wrapper shadows a binary
of its own name) was invisible to it -- both as a direct guard and
through the loop-variable indirection. Broadened both patterns to
skip over any flags between `-q` and the name/variable.
split/spwin/tab dispatch on $TERM/$TERM_PROGRAM/$KONSOLE_VERSION to
pick which terminal-specific binary to call, per this repo's C4
convention -- but those env vars only prove the terminal type, not
that its CLI binary is on $PATH: they propagate over ssh, so sshing
out from Kitty/WezTerm inherits the var on a remote host that never
installed the binary. Same latent gap in clone/clonet, whose
clone-in-kitty is a function Kitty's own shell integration injects,
not present on a remote shell that only inherited $TERM. All five now
check the actual thing they are about to call, not just the env var
that selects it.
Also guards and documents three more real, previously-undeclared
dependencies found by the same audit, unrelated to PR #168's named
list but the identical pattern: fast-cli (fast), lock (loginctl),
ports (lsof), screensleep (busctl).
docs/fish-config.md regenerated to match.
Adds a pull_request trigger (same path filters, YAML anchor to share
them with push) so branches get CI feedback before merge instead of
only after. build-docs is excluded on pull_request: it auto-commits
generated docs straight to the checked-out ref and deploys the
Cloudflare Pages production site with --branch=main, neither of which
should run against PR content that is not main yet.
Documents what each function needs for full functionality -- other
repo functions it calls, and external CLI tools it uses or falls
back gracefully without (e.g. rm/trash, ls/eza+lsd) -- matching the
existing CLASSIFICATION convention's placement and the ~20 functions
that already carried this label.
Also broadens verify-manual.py's dependency-resolution check to
recognize this repo's other existence-check idioms (command -q,
command -v, which -- not just type -q) and dng2avif's dynamic
type -q $cmd loop, since several genuine dependencies (eza, lsd,
fastfetch, fd, ps, ...) are only ever guarded that way.
Verified live, twice: bundling apt-utils with another package (first
attempt) and installing it fully alone, first (second attempt) both
still print debconf's "delaying package configuration" notice exactly
once per job. It fires during apt-utils' own first-ever install, before
debconf considers it "installed" -- no install ordering this workflow
controls can pre-seed that. Reverted to the simpler bundled form (the
separate-step version added a step for zero measured benefit) and
documented it as accepted/unfixable, same class as the runs-on/
checkout-hint noise already left alone.
Add -l/--local flag to enforce strictly local repository creation in mkrep, overriding and ignoring any remote flags or environment variables that would link to or create a remote.
The previous fix bundled apt-utils into the same apt-get install as
software-properties-common. Verified against a live CI run: debconf
still printed "delaying package configuration" once per job, because
apt-utils configures in the same transaction as everything else and
still lands after at least one other package -- bundling doesn't help.
Installing it alone, first, actually finishes it before anything else
runs.
An audit of a full green run's test + build-docs logs (agy scan, spot-
checked) turned up stray output beyond the mkrep git-init hint already
fixed. Two real bugs, plus CI-config cosmetics:
- agents-vault.fish/agents-init.fish: `set -l x (some_fish_function ...)`
command substitutions do not inherit a caller-scoped stderr redirect in
fish (proven with a two-line repro: `outer 2>/dev/null` where outer
does `set -l x (inner)` still leaks inner's stderr to the real
terminal). This let _agents_repo_ensure_symlink's and
_agents_repo_sync's raw internal error messages leak past `--silent`
for real users too, always duplicating the clean summary message each
caller already echoes on failure. Fixed at all 4 call sites by adding
an explicit `2>/dev/null` directly on each command substitution, since
a redirect on the outer call cannot reach it.
- ci.yml: apt-get install missing `apt-utils`, so debconf printed
"delaying package configuration" on every install in both jobs --
installing it first fixes the chicken-and-egg.
- ci.yml: added `NODE_OPTIONS: --no-deprecation` at the build-docs job
level to silence Node's internal punycode-module deprecation notice
(astro's toolchain still pulls it in transitively).
- ci.yml: `npm ci --no-fund` drops the funding nag.
- ci.yml: `gpg --batch --quiet --import` drops gpg's normal-case import
status lines during the bot commit-signing setup.
Deliberately NOT silenced: npm's deprecated-glob warning, its audit
vulnerability summary, and its allow-scripts notice about esbuild's
postinstall -- these are genuine dependency-hygiene signal, not noise,
and no workflow-level flag exists to hide them without also hiding real
future findings. Also not fixable here: a Gitea Actions/act runner
warning ('runs-on' key not defined in CI/test) that traces to neither
workflow YAML in this repo -- both already set runs-on on every job,
confirmed twice; it's runner-internal, like actions/checkout's own
git-init hint in its Checkout-step preamble.
Verified: full suite 730/730 passing ($status 0); the exact mkdir-
collision repro that surfaced the command-substitution bug re-run
clean (rc=1, empty stderr); test-agents-vault.fish standalone,
320/320, zero occurrences of the previously-leaked messages.
Every mkrep call in this suite runs a bare `git init`. On a runner with
no init.defaultBranch configured, git prints its "Using 'master' as the
name for the initial branch" advice block on each one (20 occurrences in
CI's test-job log, all from this file). test-agents-vault.fish already
pins the same setting via GIT_CONFIG_COUNT/KEY/VALUE for the identical
reason; apply the same fix here rather than in mkrep.fish itself, so a
real user's own git config still wins in normal use.
The schema file lives at docs/function-classification-schema.md but is
not a published Starlight page, so a relative link resolves fine in the
manual source tree but breaks once copied into
docs/site/src/content/docs/ — starlight-links-validator failed CI's
build-docs job on it. Rewrite it as an absolute Gitea blob URL, the same
pattern _rewrite_repo_links already uses for CONTRIBUTING.md/LICENSE.
The CLASSIFICATION schema (docs/function-classification-schema.md) had
no path into config-help's lookup: no fish-config.index keyword, and
the only in-pipeline section (the C1 doc's "For function authors")
doesn't contain the word classification itself, so even the
normalized-heading-scan fallback missed it on that term. Two aliases
added, pointing at the existing section -- no heading renamed, matching
the index file's own stated purpose.
New Phase 1b in tests/run-tests.fish: catches a bare C1-shadowed-command
call in a functions/*.fish body with no matching uses-shadow(name) or
self-limiting(name) in that function's own CLASSIFICATION header. This
is exactly the check discussed after the rm and cd audits -- runtime
auto-unwrapping isn't viable in fish (there's no hook finer than
shadowing itself, and rewriting behavior invisibly at runtime is its
own footgun); a static lint using the CLASSIFICATION tag as the
declared-intentional marker is. Scoped to functions/*.fish only: the
one-function-per-file convention there makes body extraction exact
with no block-depth parser needed.
Added a new self-limiting(name) tag to the schema for the case a bare
call is safe not because the caller did anything, but because the
shadow's own logic already neutralizes the override: rm's and mkdir's
flag checks (verified precisely -- rm falls back to command rm for any
flag except a bare -r/-R/--recursive alone, which still routes to
trash; mkdir falls back to command mkdir -p for any flag, no
exception), and grep/fgrep/egrep/dir/vdir/cat's own tty auto-detection
(--color=auto, and bat's default color behavior -- verified
byte-identical to stock cat when piped, since bat also auto-disables
highlighting on a non-terminal). Explicit and durable rather than a
silent lint exemption: if a shadow's bypass condition is ever
weakened, every self-limiting site is one grep away instead of
silently wrong.
Running the first draft of the lint surfaced three more real bugs,
none previously audited:
- config-help.fish's --man pager path checks `type -q less` (proving
it wants the real less binary specifically, for less-only -R/+N
flag syntax) then called it bare, routing through our own
$PAGER -> ov -> less -> more -> cat fallback chain instead -- which
could hand those less-specific flags to a completely different
program. Now command less.
- _fish_deps_install.fish and _fish_deps_update.fish's binary-upgrade
paths cp a freshly downloaded binary over an already-installed one
with no existence guard -- the update flow's target is guaranteed to
already exist. Our cp shadow forces -i unconditionally (a plain
alias, not flag-aware like rm's), so this would hang waiting on a
confirmation prompt in any non-interactive run. Now command cp.
Same two files' lazydocker install path piped curl output into bare
bash, invoking our shell-switch wrapper instead of a plain
subshell. Now command bash.
- agents-init.fish's AGENTS.md/CLAUDE.md relocation calls mv bare in
four places; each is already guarded by a preceding test -f check on
the destination, so the -i alias was unlikely to ever fire in
practice, but explicit command mv removes the reliance on that guard
entirely rather than leaving it as the only thing standing between a
file move and an unattended hang.
The remaining ~65 flagged call sites across ~24 files were reviewed
individually and tagged self-limiting(rm)/self-limiting(mkdir)
(verified flagged with -f/-rf or -p) and self-limiting(grep)/
self-limiting(cat) (verified piped, captured, or -q/-c; none display
color to a human), plus uses-shadow(ls) for two existence-check-only
calls (cffetch.fish, ffetch.fish) whose output is redirected to
/dev/null.
Verified an agy audit of every bare cd call by hand. conf.d/zoxide.fish
gates alias cd=z behind status is-interactive plus the C1 toggle, and
_zoxide_hook fires on --on-variable PWD, so it tracks a directory
change no matter how PWD got there -- switching to builtin cd loses
zoxide's frecency tracking nothing.
mkcd.fish's single cd and mkrep.fish's 9 (entering the new repo, plus
8 rollback-to-original-directory sites on error paths and --no-cd)
were both intended as exact, deterministic path navigation, never a
zoxide query. The real risk was mkrep's rollback path: if $orig_pwd
ever failed cd's own -d check for any reason, z's fallback branch
queries zoxide for a *guessed* frecent directory instead -- landing a
failed run's cleanup in a directory the caller never asked for, not
the one it was trying to return to. All 9 sites now use builtin cd.
Corrected both functions' CLASSIFICATION from uses-shadow(cd) to
bypasses-shadow(cd) -- neither wanted zoxide's query, they were tagged
that way only because the header audit recorded what the code was
doing at the time, not what it needed.
integrations/fzf.fish's fzf-alt-c-widget also calls bare cd, but it's
vendored upstream code (PatrickF1/fzf.fish) and is itself an
interactive directory-jump binding, not a script/automation caller --
left alone, same as fisher.fish's rm calls.
Verified an agy audit of every bare rm call (the trash-routing C1
shadow) by hand rather than trusting its report. Confirmed correct:
scrub.fish's custom_rm strategy and logs.fish's Ctrl-D delete both
deliberately want trash for a real, user-facing deletion.
Confirmed and fixed three cases where a function's own throwaway
scratch file was going to the user's trash instead of being wiped:
fc.fish's edited-command tmpfile, dng2avif.fish's intermediate PNM
(inconsistent with its own failure-path cleanup two lines up, which
already used -f), and _scrollback_prune_junk.fish's junk log files
(its sibling _prune_terminal_logs.fish already documents this exact
pitfall in its header).
Also went further than the report and classified every bypasses-shadow(rm)
caller found by grep that had never been audited at all:
config-settings.fish and edit.fish (own scratch cleanup, no destructive
data at stake) and key-crypt.fish (--remove deletes the user's real
input file after encryption, genuinely destructive, already documented
in its own header as 'not a secure wipe'). Corrected scrub.fish's tag,
which was missing uses-shadow(rm) for its deliberate trash-routing
branch alongside the bypass branch it already had tagged.
Added a note to the schema doc: rm's flag-based fallback lives inside
the shadow itself, so a bare rm -f/rm -rf call is not the caller
bypassing anything -- only an explicit command rm/builtin rm earns
the tag. This is why dng2avif.fish's fix needed no CLASSIFICATION
change: it already used rm -f, which was never actually the bug --
the missing -f on line 122 was.
AGENTS/functions/CLAUDE.md is git-ignored local agent state, not part
of the repo -- a comment/commit referencing it as the schema's home
points contributors at a file they can't see. The canonical CLASSIFICATION
schema now lives at docs/function-classification-schema.md (tracked),
with CONTRIBUTING.md's existing function-header-conventions section
extended to introduce it, and the C1 shadow doc's pointer updated to
match. AGENTS/functions/CLAUDE.md keeps only a one-line pointer to the
tracked file instead of duplicating the definitions.
Audits every function's interaction with the C1-shadowed commands
(uses-shadow/bypasses-shadow) and general hazards (destructive,
network, blocking-prompt) per the CLASSIFICATION schema.
Delegated the initial mechanical sweep to agy, then reviewed every
file by hand: fixed a systemic double-blank-comment-line formatting
bug from the delegate pass, and corrected several judgment errors
found on review -- three false blocking-prompt tags where a fish
'read' was consuming piped input rather than waiting on a terminal
(open-url.fish, sbver.fish, play-media.fish, now untagged entirely),
a blocking-prompt tag on mkrep.fish despite its documented --yes
escape hatch, an untagged read in jobrunner.fish's own baseless
blocking-prompt claim (removed, along with a destructive tag on
cleanup of its own mktemp output -- the schema explicitly excludes
that), the same own-output-cleanup false positive on
_zellij_dump_log.fish's destructive tag, an interactive fzf-gated
confirmation on logs.fish and replay.fish's piped read misread the
same way as the first three, and a uses-shadow(mkdir) on mkcd.fish
that actually belongs to the _fish_mkdir_p helper it delegates to,
not to mkcd itself.
Rename the C1 history() shadow to pretty-history so it never collides
with the fish builtin -- every function expecting stock history
semantics (search, --max, merge, ...) would otherwise silently break.
hist.fish, which relied on the shadow's timestamp formatting, now
requests it explicitly via builtin history --show-time.
Add a CLASSIFICATION doc-header label so a function can declare its
interaction with C1-shadowed commands (uses-shadow/bypasses-shadow)
and general hazards (destructive, network, blocking-prompt) for
anyone deciding to disable an opinionated category or call the
function from automation. Wired into the manual/site build pipeline
(manualtools.py, build-manual.py) and the C1 shadow doc gets a new
"For function authors" bypass-mechanism reference table
(command/builtin/__original_help, and which shadows have no real
bypass target at all).
The tricks.fish C1 history() shadow drops all args and always shows
timestamps. Calling plain history --max 1 hit that shadow, dumping the
entire history with timestamps instead of one plain entry. Use builtin
history --max 1, matching the convention already used by fc.fish,
_fzf_search_history.fish, and bash_expands.fish.
New optional-tier catalog entry, gated on WSL2 detection
(/proc/sys/kernel/osrelease) so it never surfaces on a plain Linux
box's install/sync prompts, only in the informational status
listing. Downloads the x86_64 binary from GitHub releases to
~/.local/bin/win32yank.exe; fish-deps update refreshes an
already-installed copy the same way.
Extracts _fish_clipboard_copy and _fish_clipboard_paste so the
wl-copy/xclip/win32yank fallback chain lives in one place instead of
four near-duplicates. hist now goes through the same chain, so it
also gets the xclip (X11) fallback it was missing before, alongside
win32yank on WSL2.
y, p, paste, and hist now try win32yank.exe after wl-copy/wl-paste
and xclip, so clipboard access works under WSL2 once win32yank is
installed and reachable through WSL interop. Updates the OS
compatibility docs accordingly.
Adds an OS Compatibility section to the README and the Installation
manual page: developed and tested on Arch Linux, macOS and Windows
unsupported, with the specific Linux-only calls that back that claim
(systemd-inhibit, zramctl/swapon, sbctl, wl-copy/xclip with no
pbcopy/clip.exe fallback, GNU-only stat/numfmt flags) and the baseline
packages (git, gpg, tar, coreutils) assumed present rather than tracked
by fish-deps.
Adds os/os-compatibility/operating-system/compatibility/linux/macos/
mac/windows/wsl keywords to fish-config.index so `help config os` and
friends jump straight to it.