feat(help): render --help from function headers; stop 8 functions executing on --help #132

Merged
rootiest merged 10 commits from feat/header-driven-help into main 2026-09-08 05:39:24 +00:00
Owner

What

79 of 109 user-facing functions had no -h/--help, against this repo's own documented rule. Rather than hand-write 79 menus (guaranteed to drift from the header comment, which is already the enforced SSOT), one runtime renderer — __fish_help_header (status current-function) $argv; and return 0 as the first line of a function body — prints the function's own existing man-page-style header. Only $argv[1] is inspected, so a function that takes a command to run (wake-lock, bkg) still forwards a later --help to that command.

This is a safety fix before it's a docs fix

Eight of the 79 ignored $argv entirely and just ran. Recorded before the fix, with recording stubs on PATH standing in for the real tools:

cleanup --help      EXECUTED: pacman -Qtdq              (precedes sudo pacman -Rns $orphans)
upgrade --help      EXECUTED: paru -Syu --noconfirm
sudo-toggle --help  EXECUTED: sudo tee /etc/sudoers.d/nofail-toggle
limine-edit --help  EXECUTED: sudoedit /boot/limine.conf ... sudo sbctl sign-all
lock, screensleep, tmux-clean, fzf-update — same shape

That fix is an isolated commit (037588e fix(help): stop eight functions executing on --help) so it can be reviewed and merged on its own regardless of what happens to the rest.

Exemptions

39 functions stay exempt, enumerated in $__help_exempt in tests/functional.fish (the one machine-readable copy; the design spec's tables are dated rationale, not something code reads):

  • 35 wrappers — shadow a same-named binary or forward $argv to one (ls --help still reaches eza).
  • 4 hooks — invoked by fish itself, never typed (fish_prompt, fish_right_prompt, fish_mode_prompt, sponge_filter_secrets).

A second assertion checks every exempt name still resolves to a published function, so a rename or deletion fails the suite instead of silently exempting nothing.

The 30 functions that already had hand-rolled --help menus are left alone — three carry content that exists in no header (config-help's pager fallback chain, qc's appended aichat --help, superpowers' usage block), so converting them would delete documentation.

Action required — will NOT travel with the merge

functions/CLAUDE.md is a symlink into this repo's gitignored AGENTS/ directory, so the amended help-flag rule can't be committed here. Paste-ready replacement text is in AGENTS/JOB-BRIEF-FINDINGS.md on this branch (also copied to ~/.config/fish/AGENTS/JOB-BRIEF-FINDINGS.md already) — apply it by hand to your AGENTS sub-repo, or the rule will read stale.

Known defect found, not fixed here (its own PR incoming)

functions/dops.fish defines function docker, not dops — so dops is dead and docker becomes conditionally shadowed as a side effect of the failed autoload. Full writeup in AGENTS/JOB-BRIEF-FINDINGS.md.

Testing

fish tests/run-tests.fish — 227/227 lint, 19/19 functional (new: test_every_user_facing_function_has_help, test_help_never_executes_destructive_path, test_help_renderer, test_help_renderer_degrades_safely), 317/317 vault. python3 docs/verify-manual.py — 74/74. Net diff: +486/−0 — the deliverable is ~600 lines of hand-written echo never added.

## What 79 of 109 user-facing functions had no `-h`/`--help`, against this repo's own documented rule. Rather than hand-write 79 menus (guaranteed to drift from the header comment, which is already the enforced SSOT), one runtime renderer — `__fish_help_header (status current-function) $argv; and return 0` as the first line of a function body — prints the function's own existing man-page-style header. Only `$argv[1]` is inspected, so a function that takes a command to run (`wake-lock`, `bkg`) still forwards a later `--help` to that command. ## This is a safety fix before it's a docs fix Eight of the 79 ignored `$argv` entirely and just ran. Recorded before the fix, with recording stubs on `PATH` standing in for the real tools: ``` cleanup --help EXECUTED: pacman -Qtdq (precedes sudo pacman -Rns $orphans) upgrade --help EXECUTED: paru -Syu --noconfirm sudo-toggle --help EXECUTED: sudo tee /etc/sudoers.d/nofail-toggle limine-edit --help EXECUTED: sudoedit /boot/limine.conf ... sudo sbctl sign-all lock, screensleep, tmux-clean, fzf-update — same shape ``` That fix is an isolated commit (`037588e fix(help): stop eight functions executing on --help`) so it can be reviewed and merged on its own regardless of what happens to the rest. ## Exemptions 39 functions stay exempt, enumerated in `$__help_exempt` in `tests/functional.fish` (the one machine-readable copy; the design spec's tables are dated rationale, not something code reads): - **35 wrappers** — shadow a same-named binary or forward `$argv` to one (`ls --help` still reaches eza). - **4 hooks** — invoked by fish itself, never typed (`fish_prompt`, `fish_right_prompt`, `fish_mode_prompt`, `sponge_filter_secrets`). A second assertion checks every exempt name still resolves to a published function, so a rename or deletion fails the suite instead of silently exempting nothing. The 30 functions that already had hand-rolled `--help` menus are left alone — three carry content that exists in no header (`config-help`'s pager fallback chain, `qc`'s appended `aichat --help`, `superpowers`' usage block), so converting them would delete documentation. ## Action required — will NOT travel with the merge `functions/CLAUDE.md` is a symlink into this repo's gitignored `AGENTS/` directory, so the amended help-flag rule can't be committed here. Paste-ready replacement text is in `AGENTS/JOB-BRIEF-FINDINGS.md` on this branch (also copied to `~/.config/fish/AGENTS/JOB-BRIEF-FINDINGS.md` already) — apply it by hand to your `AGENTS` sub-repo, or the rule will read stale. ## Known defect found, not fixed here (its own PR incoming) `functions/dops.fish` defines `function docker`, not `dops` — so `dops` is dead and `docker` becomes conditionally shadowed as a side effect of the failed autoload. Full writeup in `AGENTS/JOB-BRIEF-FINDINGS.md`. ## Testing `fish tests/run-tests.fish` — 227/227 lint, 19/19 functional (new: `test_every_user_facing_function_has_help`, `test_help_never_executes_destructive_path`, `test_help_renderer`, `test_help_renderer_degrades_safely`), 317/317 vault. `python3 docs/verify-manual.py` — 74/74. Net diff: +486/−0 — the deliverable is ~600 lines of hand-written `echo` never added.
rootiest added 9 commits 2026-09-08 00:58:39 +00:00
Design for making the man-page-style comment header above each function
reachable at runtime via -h/--help, instead of hand-writing 40 more menus.

One renderer (__fish_help_header) parses the .fish source at call time and
prints the header to stdout; each call site is a single line. Audits all 79
published functions that lack help today into 35 shadow/pass-through exempt,
4 prompt-hook exempt, and 40 to convert, with a one-line reason each.

Corrects the baseline counts (109 published functions, 30 with menus, 79
without) and records two pre-existing defects found while auditing.

Note: AGENTS/ is gitignored upstream, so this file is force-added.
AGENTS/ is gitignored upstream; a tracked file there pollutes
git diff main..work, which is what the user reads when choosing
which job to merge. Spec stays on disk, browsable, out of history.
Parses a function's own man-page comment header at call time and prints
it as a help menu on stdout, so the documentation that already generates
Section 5 of the manual becomes reachable from the shell.

Reads the .fish source rather than the generated docs/fish-config.md, so
it cannot go stale between a header edit and a docs rebuild. Walks
backwards from the `function` line to collect the header, which resolves
multi-header files (fish-deps, gi, y) without reimplementing
manualtools._block_identity.

Returns 1 only when argv[1] is not a help flag; every other path prints
and returns 0. A return of 1 hands control back to the caller's body.

Nothing calls it yet.
A missing or label-less header must still print and exit 0. Returning 1
would hand control back to the caller's body, which for upgrade(1) is a
full system upgrade.

Both fixtures reach the renderer's bottom `count $out -eq 0` fallback,
not the unreadable-source branch. Mutating that fallback to `return 1`
turns the test red with:

    headerless executed its body despite --help
    malformed executed its body despite --help

The mutation was reverted before this commit.
These eight ignore $argv entirely, so passing --help ran the command
instead of describing it. The new check shadows every external binary
they reach with a recording stub on PATH and fails if anything is
invoked. Before the fix it reported, verbatim:

    cleanup --help EXECUTED: pacman -Qtdq
    fzf-update --help EXECUTED: git clone --depth 1 https://github.com/junegunn/fzf.git /tmp/.../.fzf
    limine-edit --help EXECUTED: sudoedit /boot/limine.conf sudo limine-enroll-config sudo limine-mkinitcpio sudo sbctl sign-all
    lock --help EXECUTED: loginctl lock-session
    screensleep --help EXECUTED: busctl --user call org.kde.kglobalaccel ... invokeShortcut s Turn Off Screen
    sudo-toggle --help EXECUTED: sudo stat -c %s /etc/sudoers.d/nofail-toggle sudo tee /etc/sudoers.d/nofail-toggle
    tmux-clean --help EXECUTED: tmux list-sessions -F #{session_name} #{session_attached}
    upgrade --help EXECUTED: paru -Syu --noconfirm

cleanup's log line is the read that precedes `sudo pacman -Rns $orphans`,
which the stub suppressed by returning no orphans; on a real machine with
orphans present the removal ran.

Each now answers --help from its own comment header. The call site is the
first statement of the body, above the C4 guard, so help stays reachable
when the component is disabled and nothing side-effecting runs first.
lD, lsr, lss, lstree, lt, ltr, lx and ld. None shadows a real binary, so
there is no disabled-fallback contract to honour, and `eza --help`
documented eza rather than the preset that is these functions' entire
content. First-arg-only interception leaves `lt -la --help` passing
through to eza untouched.
gip, gip4, gip6, ports, swapstat, sbver and steam-dl. All ignored $argv,
so --help previously ran the query or the launcher. sbver keeps its own
--brief flag: only argv[1] is inspected and --brief is not a help flag,
so the body still runs for it.
spwin, tab, hist, bkg, wake-lock and fc take the standard first-arg-only
call site, so `wake-lock rsync --help` still runs rsync.

split takes --help only: its own ARGUMENTS documents -h as --horizontal,
so a literal test replaces the renderer's own flag check. Verified that
`split -h` still selects a horizontal split and never prints help.
bd-pull, branch, check_fish_deps, claude-docs, claude-pr, fast, poke, qr
and parur take the standard call site. fish-deps instead routes -h/--help
to its existing __fish_deps_help, which previously answered only via the
unknown-subcommand path, printing "Unknown subcommand: --help" and
exiting 1.

This completes the 40 conversions and lands
test_every_user_facing_function_has_help -- acceptance criterion 6, the
check that fails when a new user-facing function ships without help. It
was developed red against the pre-conversion tree and reported exactly
the 40 names of the design's CONVERT table:

    bd-pull bkg branch check_fish_deps claude-docs claude-pr cleanup
    fast fc fish-deps fzf-update gip gip4 gip6 hist lD ld limine-edit
    lock lsr lss lstree lt ltr lx parur poke ports qr sbver screensleep
    split spwin steam-dl sudo-toggle swapstat tab tmux-clean upgrade
    wake-lock

each as "<name>: no -h/--help handling and not in $__help_exempt", with
the guard exiting 1. The count fell 40 -> 32 -> 24 -> 17 -> 10 -> 0
across the conversion commits. It is committed green so every commit on
this branch passes the suite.

$__help_exempt is the single machine-readable exemption record; a second
assertion fails if an exempt name stops being a published function, so a
rename or deletion upstream cannot silently exempt nothing.
rootiest added 1 commit 2026-09-08 05:39:06 +00:00
rootiest merged commit f2744a9a60 into main 2026-09-08 05:39:24 +00:00
rootiest deleted branch feat/header-driven-help 2026-09-08 05:39:24 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rootiest/fish-config#132