docs: generate the Functions Reference from function comment headers #74

Merged
rootiest merged 10 commits from docs-option-tables into main 2026-07-26 08:41:03 +00:00
Owner

Section 5 of the manual is no longer authored in Markdown. It is generated
from the man-page-style comment header already sitting above each function
in functions/*.fish, so a command's documentation lives beside the code
that implements it and cannot drift from it.

What changed

Two sources of truth, split by content type

  • docs/manual/** — prose for every section except function entries.
  • functions/*.fish comment headers — Section 5.

The 14 files under docs/manual/05-functions/ are now frontmatter-only
stubs supplying category order, title, and helpKeywords. Every intro
paragraph measured zero words before removal — they were pure entry
containers.

New header labels

  • # CATEGORYopt-in publishing. A function without one produces no
    entry. This keeps bundled plugin and prompt internals out of the manual
    with no exclusion list to maintain. 96 functions carry it.
  • # DEPENDENCIES — comma-separated functions or binaries this one calls.
    The reverse Used by: index is generated from it, giving the manual a
    navigable dependency graph that crosses category boundaries.

One-time content merge

Where the old Markdown had prose, examples, or notes the header lacked,
that content was merged into the header before the Markdown was deleted.
No documentation was lost.

Verification (docs/verify-manual.py, now 24 checks)

  • every # CATEGORY function produces exactly one entry
  • every entry carries SYNOPSIS, DESCRIPTION, EXAMPLE
  • every # CATEGORY resolves to a stub, and no stub generates zero entries
  • every # DEPENDENCIES name is a real function or a type -q binary
  • category stubs carry no authored ## entries
  • warns (never fails) on a public function missing # CATEGORY

The round-trip test now excludes Section 5 on both sides, since that
section is generated rather than concatenated.

Fixes carried along

  • Site card descriptions truncated mid-clause. _first_sentence() now
    unwraps the hard-wrapped leading paragraph and skips the Synopsis:
    block whole — it restated the calling convention the card already shows
    as its title.
  • open-url.fish NOTES claimed "Used internally by config-help --html."
    It is not; config-help calls xdg-open directly. Line removed.
  • git-clean.fish had a duplicated -f entry in ARGUMENTS.
  • dops and fzf-update were undocumented; both now publish.

Expected warning

verify-manual.py prints:

WARN  8 documented function(s) lack # CATEGORY:
      fast, fast-cli, fish_mode_prompt, fish_prompt, fish_right_prompt,
      fzf_configure_bindings, sponge_filter_secrets, zoxide

All eight are correct exclusions — fast is a self-described placeholder,
the rest are bundled plugin and prompt internals.

Note on generated files

docs/fish-config.md is regenerated and committed. docs/fish-config.1
is not — pandoc is not installed locally. CI regenerates and
auto-commits it on push; confirm in the job log.

Manual Verification

  • python3 docs/verify-manual.py24/24 passed plus the 8-function
    warning above, nothing else.
  • help config functions opens Section 5 and the entries read
    correctly in the pager.
  • help config git jumps to the git category; spot-check that
    git-clean shows -h/--help and -f/--force exactly once each.
  • Check the CI job log: pandoc step succeeded and docs/fish-config.1
    was auto-committed.
  • man fish-config renders Section 5 with all 96 entries.
  • On the docs site, open /reference/git-and-version-control/ — the
    category card grid lists its functions and each card's description
    is a complete sentence, not a truncated clause and not a synopsis.
  • On /reference/ai-and-developer-tools/agy/, confirm the
    Dependencies: link to agents-init resolves, and that
    agents-init's own page shows Used by: linking back to both
    agy and claude (this is the cross-category case).
  • Edit any function's # DESCRIPTION, re-run
    python3 docs/build-manual.py --concat, and confirm the change
    appears in docs/fish-config.md — proving the header is the source.
Section 5 of the manual is no longer authored in Markdown. It is generated from the man-page-style comment header already sitting above each function in `functions/*.fish`, so a command's documentation lives beside the code that implements it and cannot drift from it. ## What changed **Two sources of truth, split by content type** - `docs/manual/**` — prose for every section *except* function entries. - `functions/*.fish` comment headers — Section 5. The 14 files under `docs/manual/05-functions/` are now frontmatter-only stubs supplying category order, title, and `helpKeywords`. Every intro paragraph measured zero words before removal — they were pure entry containers. **New header labels** - `# CATEGORY` — **opt-in publishing.** A function without one produces no entry. This keeps bundled plugin and prompt internals out of the manual with no exclusion list to maintain. 96 functions carry it. - `# DEPENDENCIES` — comma-separated functions or binaries this one calls. The reverse `Used by:` index is generated from it, giving the manual a navigable dependency graph that crosses category boundaries. **One-time content merge** Where the old Markdown had prose, examples, or notes the header lacked, that content was merged into the header before the Markdown was deleted. No documentation was lost. **Verification** (`docs/verify-manual.py`, now 24 checks) - every `# CATEGORY` function produces exactly one entry - every entry carries `SYNOPSIS`, `DESCRIPTION`, `EXAMPLE` - every `# CATEGORY` resolves to a stub, and no stub generates zero entries - every `# DEPENDENCIES` name is a real function or a `type -q` binary - category stubs carry no authored `##` entries - warns (never fails) on a public function missing `# CATEGORY` The round-trip test now excludes Section 5 on both sides, since that section is generated rather than concatenated. ## Fixes carried along - Site card descriptions truncated mid-clause. `_first_sentence()` now unwraps the hard-wrapped leading paragraph and skips the `Synopsis:` block whole — it restated the calling convention the card already shows as its title. - `open-url.fish` NOTES claimed "Used internally by config-help --html." It is not; `config-help` calls `xdg-open` directly. Line removed. - `git-clean.fish` had a duplicated `-f` entry in ARGUMENTS. - `dops` and `fzf-update` were undocumented; both now publish. ## Expected warning `verify-manual.py` prints: ``` WARN 8 documented function(s) lack # CATEGORY: fast, fast-cli, fish_mode_prompt, fish_prompt, fish_right_prompt, fzf_configure_bindings, sponge_filter_secrets, zoxide ``` All eight are correct exclusions — `fast` is a self-described placeholder, the rest are bundled plugin and prompt internals. ## Note on generated files `docs/fish-config.md` is regenerated and committed. `docs/fish-config.1` is **not** — pandoc is not installed locally. CI regenerates and auto-commits it on push; confirm in the job log. ## Manual Verification - [x] `python3 docs/verify-manual.py` → `24/24 passed` plus the 8-function warning above, nothing else. - [x] `help config functions` opens Section 5 and the entries read correctly in the pager. - [x] `help config git` jumps to the git category; spot-check that `git-clean` shows `-h/--help` and `-f/--force` exactly once each. - [x] Check the CI job log: pandoc step succeeded and `docs/fish-config.1` was auto-committed. - [x] `man fish-config` renders Section 5 with all 96 entries. - [x] On the docs site, open `/reference/git-and-version-control/` — the category card grid lists its functions and each card's description is a complete sentence, not a truncated clause and not a synopsis. - [x] On `/reference/ai-and-developer-tools/agy/`, confirm the **Dependencies:** link to `agents-init` resolves, and that `agents-init`'s own page shows **Used by:** linking back to both `agy` and `claude` (this is the cross-category case). - [x] Edit any function's `# DESCRIPTION`, re-run `python3 docs/build-manual.py --concat`, and confirm the change appears in `docs/fish-config.md` — proving the header is the source.
rootiest added 9 commits 2026-07-26 08:36:35 +00:00
Two-column option and subcommand blocks in the manual were falling through
to the indented-code fallback, rendering as an unhighlighted grey slab on
the site. `_as_table` detects a contiguous, column-aligned indented run and
emits a headerless GFM table instead; anything it cannot prove is tabular
still takes the old path.

15 blocks convert, 20 are correctly left alone. The concat and man-page
outputs are untouched — `prettify` is site-only, and the byte-exact
round-trip test stays green.
Records each documented function's manual category in its own header, so
Section 5 can later be generated from source instead of hand-maintained
alongside it. Values reproduce the current grouping in
docs/manual/05-functions/ exactly; no documentation changes meaning here.

Four functions are skipped because they have no header at all yet
(branch, fc, gitup, sudo-toggle); they get one in the merge that follows.
These four were the only documented functions with no comment header,
carrying just a one-line description. Content is merged from their manual
entries plus what the implementations actually do (fc's vi fallback and
empty-buffer abort, sudo-toggle's credential-cache clear), so the headers
are a superset of Section 5 rather than a copy of it.
Folds the examples Section 5 carried but the headers did not into each
function's `# EXAMPLE`, and moves the three lines that only looked like
examples -- the two typo-abbreviation notes and rm's /usr/bin/rm fallback --
into `# NOTES`, the label already in use.

Also corrects gi's synopsis, which omitted -l, and documents yt-dlp's
--no-embed-thumbnail in `# ARGUMENTS`.
Folds the last seven manual entries whose description exceeded their header
back into the source-of-truth comment blocks: config-settings (Sponge/Paths
page detail, list-value parsing, width tiers, navigation keys), fish-deps
(install-method priority, dependency tiers), logs (fzf keybindings, ov
viewer behavior), kitty-logging (symlink wording, C5 inert-vs-uninstalled),
config-update (exec fish reload), yt-dlp (C1 guard). Drops duplicate example
lines left by the previous merge.
Word-level diff of every manual entry against its generated counterpart
surfaced 546 tokens present in the manual and absent from the header —
losses reconcile.py missed, because it compared description length and
these headers are longer overall thanks to ARGUMENTS/RETURNS.

Merged the substantive ones (546 -> 202 residual tokens, the remainder
being synonym drift). Notable real fixes:

- git-clean: -f/--force was missing from ARGUMENTS entirely
- pkg: per-package-manager query table
- qc: cli-role rationale, role paths, --role passthrough
- config-help: site URL, xdg-open, man page path, case-insensitivity
- agents-init: idempotency, .gitignore paths, upstream pull, wrapper callers
- smart_exit: exit-builtin wiring note, $SCROLLBACK_HISTORY_DIR

The manual's claim that claude/agy pass `agents-init --agents` is stale;
both pass `--quiet` (full setup). Header wins, manual dropped.
The man-page-style comment header above each function in functions/*.fish
becomes the SSOT for that function's documentation. Writing a new function
and documenting it are now the same act.

- manualtools.parse_functions() parses every header carrying a # CATEGORY;
  absence of one is the opt-in, keeping bundled-plugin and prompt internals
  out of the manual with no exclusion list to maintain.
- build-manual.py generates entries for both --concat and --site, with
  **Dependencies:** rendered as links and a **Used by:** reverse index
  computed in one pass. Cross-category links are the navigation win.
- docs/manual/05-functions/*.md reduced to frontmatter-only stubs. Every
  intro measured zero words, so the category files were pure entry
  containers; ordering, titles, and helpKeywords routing are untouched.
- _first_sentence() unwraps the leading hard-wrapped paragraph and skips
  the whole Synopsis block, not just its label line. Site cards no longer
  truncate mid-clause or show a synopsis as their description.

Verification, per the design spec:
- test_concat_roundtrips_original scoped to sections 0-4 and 6-11. It
  guarded a format migration; this is a content migration.
- replaced by structural checks: one entry per categorised function, the
  required sections present, every category resolving to a stub with no
  stub empty, and every declared dependency resolving to a real function
  or a type -q-guarded binary.
- public functions lacking # CATEGORY warn rather than fail, so a new
  user-facing function going undocumented stays visible in CI.

24/24 checks pass. 94 entries generated from 94 parsed headers.

Also drops a stale claim from open-url's NOTES: config-help --html calls
xdg-open directly and has never called open-url.
Function documentation now comes from functions/*.fish comment headers;
everything else stays in docs/manual/**. Records that split in the three
places a contributor looks:

- AGENTS.md "Documentation Policy" and Coding Convention #4, which now
  spells out the full label set and flags CATEGORY as required-to-publish.
- Manual section 11, which pointed readers at docs/manual/ for everything.
- README, same.
Both carry complete headers and are user-facing, but had no # CATEGORY
so generated no entry. Surfaced by the new verify-manual.py warning,
which is exactly what it is there for.

The 8 that still warn are correct exclusions: fast is a self-described
placeholder, the rest are bundled plugin and prompt internals.
rootiest added 1 commit 2026-07-26 08:38:10 +00:00
# Conflicts:
#	docs/fish-config.md
#	docs/manual/05-functions/14-miscellaneous.md
rootiest merged commit 92bcc49962 into main 2026-07-26 08:41:03 +00:00
rootiest deleted branch docs-option-tables 2026-07-26 08:41:04 +00:00
Sign in to join this conversation.