Generate CLI reference from the clap model + cli-doc drift gate (P5.3)#33
Conversation
Generate the `forge` CLI reference in crates/forge.md directly from the clap command model, and gate it against drift — the published docs and the binary's argument parser are now the same model and cannot diverge. forge_cli: split into lib + bin. The clap model (`Cli`/`Commands`/`IconCommand` + `AFTER_HELP`) moves to `src/lib.rs`, which exposes `pub fn cli() -> clap::Command`. The binary imports it and dispatches as before; the command handlers stay in the bin. This is the smallest lib surface that lets tooling introspect the real CLI. forge-docs-check: - New `clidoc` module: introspects `forge_cli::cli()` and renders an authoritative reference (synopsis, arguments, options, nested subcommands) into the `<!-- forge:cli -->` region of crates/forge.md. Authored prose in the narrative `## Commands` section sits outside the markers and is never touched. - New `cli-doc` rule (wired into run_all_checks) fails when that region is stale; `make docs-cli` / `--write-cli` regenerates it. Mirrors the api-block/example-block marker-hybrid pattern. - `cli-command` rule refactored from regex source-parsing to clap introspection (`forge_cli::cli()`), deleting ~120 lines of brace/heck-mangling parsing and the source-move fragility — the clap model is now the literal source of truth. Docs/config: forge.md gains the generated `## CLI reference` section and a `lib.rs` entry in its file-structure tree; DOCUMENTATION.md documents the `cli-doc` (and previously-undocumented `ext-index`) rules and `make docs-cli`; Makefile adds the `docs-cli` target. Tests: clidoc unit tests (every subcommand, flags/positionals, nested icon, help filtered out) + a fixture drift test (stale region flagged, fresh region passes, un-opted page skipped). The cli-command fixture test updated for introspection. Verification: drift gate in sync; forge-docs-check 18 rules + docs_sync + clidoc; forge_cli 15 + 10; fmt --all + clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @LayerDynamics, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
There was a problem hiding this comment.
Code Review
This pull request refactors the forge CLI command model by moving it from main.rs to a library surface in lib.rs. This allows the documentation drift gate (forge-docs-check) to introspect the live clap model directly rather than parsing source text. A new cli-doc rule and a make docs-cli target have been added to automatically generate and verify the CLI reference in crates/forge.md. The review feedback highlights critical cross-platform issues regarding CRLF vs. LF line endings on Windows during file comparison and generation, and suggests a performance optimization to avoid string allocations when sorting subcommands.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| match markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | ||
| Some((_, _, body)) if body == expected => Vec::new(), |
There was a problem hiding this comment.
On Windows platforms, files are often checked out with CRLF (\r\n) line endings. Since render_block_body() generates the expected documentation with LF (\n) line endings, a direct string comparison body == expected will fail on Windows even if the content is identical. Normalizing the line endings by replacing \r\n with \n before comparison ensures cross-platform compatibility.
| match markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | |
| Some((_, _, body)) if body == expected => Vec::new(), | |
| match markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | |
| Some((_, _, body)) if body.replace("\r\n", "\n") == expected.replace("\r\n", "\n") => Vec::new(), |
| if let Some((_, _, body)) = markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | ||
| if body == expected { | ||
| return Ok(Vec::new()); |
There was a problem hiding this comment.
Similar to the check function, we should normalize line endings here to prevent write_all from unnecessarily rewriting the file on Windows when the only difference is CRLF vs LF line endings.
| if let Some((_, _, body)) = markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | |
| if body == expected { | |
| return Ok(Vec::new()); | |
| if let Some((_, _, body)) = markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) { | |
| if body.replace("\r\n", "\n") == expected { | |
| return Ok(Vec::new()); | |
| } |
| .get_subcommands() | ||
| .filter(|s| s.get_name() != "help" && !s.is_hide_set()) | ||
| .collect(); | ||
| subs.sort_by_key(|s| s.get_name().to_string()); |
There was a problem hiding this comment.
Using sort_by_key with .to_string() allocates a new String for every comparison during sorting. Since get_name() returns a &str that borrows from the command, we can use sort_by to compare the string slices directly without any allocations.
| subs.sort_by_key(|s| s.get_name().to_string()); | |
| subs.sort_by(|a, b| a.get_name().cmp(b.get_name())); |
Summary
Phase P5.3 (final phase) of the docs-autogeneration pipeline: generate the
forgeCLI reference directly from the clap command model and gate it against drift.What changed
forge_cli→ lib + bin splitCli/Commands/IconCommand+AFTER_HELP) moves tosrc/lib.rs, exposingpub fn cli() -> clap::Command. The binary imports it and dispatches as before; handlers stay in the bin.forge-docs-checkclidocmodule: introspectsforge_cli::cli()and renders an authoritative reference (synopsis, arguments, options, nestediconsubcommands) into the<!-- forge:cli -->region ofcrates/forge.md. Authored prose stays outside the markers.cli-docrule fails when that region is stale;make docs-cli/--write-cliregenerates it.cli-commandrule refactored from regex source-parsing to clap introspection — deletes ~120 lines of brace/heck-mangling.Docs/config:
forge.mdgenerated## CLI reference+lib.rsin the file tree;DOCUMENTATION.mddocuments thecli-doc/ext-indexrules andmake docs-cli;Makefileaddsdocs-cli.Test plan
main(branding + rusty_v8 CI cache fix); zero conflictsforge_cli15 + 10; build,fmt --all --check,clippy -D warningsall clean~/.cargo/.rusty_v8cache frommain, so the V8-download 504s can't gate it🤖 Generated with Claude Code