ho-04.15 — man pages
Ships section-1 man pages for the sharibako CLI. The CLI is complete and in
daily dogfooding (every verb works against a real vault); what it lacked was
man sharibako. This closes that gap by GENERATING the pages from the
ParsableCommand tree rather than -authoring troff, so the pages track the
code and cannot drift from the actual flags.
Scope is documentation-only: enrich the command definitions, generate, render, iterate the prose, commit. No behavior changes, no new runtime logic (coverage floor untouched — nothing to cover).
Phase 1 —
Decision 1 — Generate, don't hand-author
swift-argument-parser 1.8.2 ships the official GenerateManual plugin
(swift package generate-manual; confirmed via swift package plugin --list).
The plugin runs the tool with --experimental-dump-help, walks the resulting
command tree, and emits mdoc(7) source — one page per command in --multi-page
mode. It already produces NAME, SYNOPSIS, the option list (from each
@Option/@Argument/@Flag help:), a DESCRIPTION built from abstract +
discussion, a SEE ALSO cross-linking a command's own subcommands, and an
AUTHORS section from --authors.
The consequence: the man page IS the command definition. The quality work is
not troff — it is enriching each CommandConfiguration with a discussion:
block and auditing every argument's help:. Hand-writing mdoc would immediately
drift from the flags; generation cannot.
Decision 2 — What each discussion: carries
Every command (root + 15 subcommands + 3 key-subcommands = 19 pages) gets a
discussion: covering: what the verb does and WHEN to use it versus its sibling
verbs (materialize vs. run vs. update; add vs. rotate vs. link; list vs. status
vs. scan vs. heal); 2–3 realistic examples; the non-obvious exit codes; and the
security note that applies (materialize = plaintext at rest; run exec-replaces
and injects env; get prints plaintext to stdout; key export --private). The
security framing is pulled straight from SECURITY.md so the two agree.
The root discussion: additionally documents the two resolution orders
(vault: --vault → SHARIBAKO_VAULT → ~/.sharibako/vault; age key:
--age-key → SHARIBAKO_AGE_KEY → Keychain (macOS) / ~/.config/sharibako/age-key
(Linux)), the bootstrap path (sharibako key generate creates key + vault), and
the exit-code taxonomy from ExitCode.swift.
Decision 3 — Post-process for cross-links and a bug-report footer
The plugin's SEE ALSO only links a command's own children, so a leaf page
(sharibako-run) gets an empty SEE ALSO and no link back to the root or its
siblings. scripts/generate-man.sh therefore post-processes each generated page
to (a) rewrite SEE ALSO as a full cross-link set (root + every sibling) and
(b) append a REPORTING BUGS section pointing at
andrew@sageframe.net / the GitHub issues URL, matching the SECURITY.md
disclosure policy. The AUTHOR/AUTHORS section is emitted by the plugin from
--authors "Andrew Marcus<andrew@sageframe.net>".
Bootstrap assumption (coordinated with ho-04.14)
The root discussion states that sharibako key generate creates the vault. A
parallel ho (ho-04.14) is making key generate scaffold the scopes/+shared/
layout — the fix for the known VaultLayout.createVaultLayout-never-called
defect noted at the close of ho-04.13. This ho documents the intended behavior;
the two land together.
Phase 2 — Execute
Enriched — added a discussion: block to every CommandConfiguration
(SharibakoCommand root; add, clean, get, heal, init, key, key generate, key
import, key export, link, list, materialize, rotate, run, scan, status, sync,
unlink, update) and audited every @Option/@Argument/@Flag help: for
completeness and accuracy against the code.
Added — scripts/generate-man.sh: runs the plugin in --multi-page mode,
writes section-1 pages to docs/man/, then post-processes SEE ALSO cross-links
and the REPORTING BUGS footer. Re-runnable and idempotent (clears stale *.1
first).
Generated — docs/man/*.1, one page per command.
Verification
swift build -Xswiftc -warnings-as-errors— green (discussion strings are code; they compile).swift-format lint --recursive --strict Sources Tests— green.swiftlint lint --strict— 0 violations.swift test— green (no behavior changed; existing help/abstract assertions still hold).- Rendered
man ./docs/man/sharibako.1,sharibako-run.1,sharibako-materialize.1, andsharibako-key-generate.1with mandoc/man; read the formatted output and iterated the prose.
Gotcha logged for the next session
ArgumentHelp is ExpressibleByStringLiteral, so a help: value must be a
single string literal — "a" + "b" runtime concatenation does not type-check
against it (the discussion: field on CommandConfiguration is a plain
String and DOES accept concatenation, which is why the multi-line
""" ... """ discussion blocks are fine). Long help: strings were therefore
reworded to fit inside the 120-column limit on a single literal line rather than
split with +.
Phase 3 —
-
Did generation hold up? Yes. The plugin produced clean mdoc with correct NAME/SYNOPSIS/DESCRIPTION/options for all 19 pages, and the
discussion:blocks rendered as DESCRIPTION prose with the examples intact. Because the pages derive from the live command tree, they cannot describe a flag the CLI does not have — the anti-drift property that motivated Decision 1. -
discussion:vs.help:split is the right seam. Reference-level detail (what/when/exit-codes/security/examples) lives indiscussion:where it can be paragraph-shaped; per-flag one-liners live inhelp:. Auditing thehelp:strings surfaced a few that were terse to the point of being unhelpful (--force,--value,--from-stdin) and they were expanded. -
Two frictions worth recording.
- The
ArgumentHelpstring-literal constraint (logged above). Caught by the warnings-as-errors build immediately, not at runtime. - roff eats a leading apostrophe. A discussion paragraph that opens with
a quoted verb (the run verb, the materialize verb) begins its rendered line
with
', which roff treats as a no-break control character and silently drops the whole line. Rendering (not the build) surfaced it — a paragraph just vanished fromman. The fix lives ingenerate-man.sh: the post-processing pass prefixes any body line beginning with'with the zero-width\&escape. This is a generation-layer fix, so the prose in the command definitions stays natural. - Related: em-dashes (U+2014) in discussion text are stripped to a bare space
by the plugin's mdoc escaper, leaving a gap with no dash. Fixed at the source
by using ASCII
-inside the man-page-visible strings (doc comments, which never reach the pages, keep their em-dashes).
- The
-
mandoc lint is clean of ERRORs; the STYLE/WARNING are the plugin's. A
mandoc -T lintsweep shows only STYLE/WARNING notes (long input lines, blank-line-in-fill,Ndtrailing period,--verbatim in examples, and AUTHORS-before-SEE-ALSO ordering) — all artifacts of how the plugin emits mdoc (one paragraph per input line, its own section order). No ERRORs; every page renders correctly. Not worth post-processing away. -
Page filenames are dotted (
sharibako.run.1,sharibako.key.generate.1), the plugin's multi-page convention; cross-references use the matching dotted titles, so they stay internally consistent. -
Coverage: untouched. This ho added no runtime logic — only compile-time string constants and a shell script — so the 90% floor is unaffected.
-
Followups.
- Packaging (ho-08). Installing the pages onto
MANPATHis distribution work: the Homebrew formula installsdocs/man/*.1intoshare/man/man1, and the.dmg/.appbundles them. Deferred to ho-08 with the rest of signing + Homebrew, per CLAUDE.md. - DocC reference site. The same plugin family ships
generate-docc-reference; a browsable HTML reference is a cheap follow-on if the project wants one, but it is not needed for v1. - Regen in CI. A CI check that regenerates
docs/man/and fails on a diff would guarantee the committed pages never drift from the command tree. Worth a small follow-on once the command surface stabilizes.
- Packaging (ho-08). Installing the pages onto
Authored, executed, and closed 2026-07-08. Man pages generated from the
ParsableCommand tree via swift-argument-parser's GenerateManual plugin;
packaging onto MANPATH deferred to ho-08.
Rendered from the corpus, verbatim · source on GitHub →