Skip to content

Generate CLI reference from the clap model + cli-doc drift gate (P5.3)#33

Merged
LayerDynamics merged 2 commits into
mainfrom
docs-pipeline-p53-cli-generator
Jun 8, 2026
Merged

Generate CLI reference from the clap model + cli-doc drift gate (P5.3)#33
LayerDynamics merged 2 commits into
mainfrom
docs-pipeline-p53-cli-generator

Conversation

@LayerDynamics

Copy link
Copy Markdown
Owner

Summary

Phase P5.3 (final phase) of the docs-autogeneration pipeline: generate the forge CLI reference directly from the clap command model and gate it against drift.

Re-opened against main. The original #32 was stacked on the P5.2 branch and got merged into that branch instead of main, so P5.3 never landed on main (clidoc.rs / forge_cli/src/lib.rs were missing). This PR targets main directly. main already contains the P5.2 fork point, so the diff is just the single P5.3 commit (plus the merge of current main).

What changed

forge_cli → lib + bin split

  • The clap model (Cli/Commands/IconCommand + AFTER_HELP) moves to src/lib.rs, exposing pub fn cli() -> clap::Command. The binary imports it and dispatches as before; handlers stay in the bin.

forge-docs-check

  • New clidoc module: introspects forge_cli::cli() and renders an authoritative reference (synopsis, arguments, options, nested icon subcommands) into the <!-- forge:cli --> region of crates/forge.md. Authored prose stays outside the markers.
  • New cli-doc rule fails when that region is stale; make docs-cli / --write-cli regenerates it.
  • cli-command rule refactored from regex source-parsing to clap introspection — deletes ~120 lines of brace/heck-mangling.

Docs/config: forge.md generated ## CLI reference + lib.rs in the file tree; DOCUMENTATION.md documents the cli-doc/ext-index rules and make docs-cli; Makefile adds docs-cli.

Test plan

  • Merged with current main (branding + rusty_v8 CI cache fix); zero conflicts
  • Drift gate in sync; forge-docs-check 18 rules + docs_sync + clidoc + cli-doc fixture
  • forge_cli 15 + 10; build, fmt --all --check, clippy -D warnings all clean
  • CI now inherits the ~/.cargo/.rusty_v8 cache from main, so the V8-download 504s can't gate it

🤖 Generated with Claude Code

LayerDynamics and others added 2 commits June 7, 2026 14:12
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>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @LayerDynamics, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +55 to +56
match markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) {
Some((_, _, body)) if body == expected => Vec::new(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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(),

Comment on lines +80 to +82
if let Some((_, _, body)) = markers::find_region(&page, BLOCK_OPEN, BLOCK_CLOSE) {
if body == expected {
return Ok(Vec::new());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
subs.sort_by_key(|s| s.get_name().to_string());
subs.sort_by(|a, b| a.get_name().cmp(b.get_name()));

@LayerDynamics LayerDynamics merged commit c1904cb into main Jun 8, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant