You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The CLI currently uses a hand-rolled argument parser. The completion support can use static scripts or a completion generator. The implementation must not require a large runtime dependency unless the project accepts that trade-off.
Rationale
psfmt has a stable set of options. Shell completions improve CLI discoverability and reduce input errors while preserving the current CLI behavior.
Affected areas
cli/psfmt/src/main.rs
CLI documentation and release/package artifacts, if they distribute completion scripts
CLI end-to-end tests, if required to validate completion generation or installation
kjanat/runner#3 - fix: suppress zsh NOMATCH errors in completion function [merged] kjanat/runner#16 - fix(completions): accept shell path or pwsh for explicit arg [merged] kjanat/runner#42 - fix(completions): fall back to $PSModulePath for pwsh shell detection [closed] kjanat/dreamcli#43 - Add eager --completions flag and .help() config builder [merged] #1 - Production PowerShell formatter: lexer, engine, CLI, dprint plugin, WASM package [open]
📝 Issue Planner
Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.
Create Plan
🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your .coderabbit.yaml:
issue_enrichment:
auto_enrich:
enabled: false
💬 Have feedback or questions? Drop into our discord!
First, add a real clap-based argument parser to psfmt, because the ticket's premise of an existing parser is false. Keep the default stdin→stdout filter and exit codes intact.
Expose the existing FormatOptions fields as stable flags plus optional file positionals, so the completions have real options to cover.
Add a runtime psfmt completions <shell> subcommand backed by clap_complete, since clap is already a runtime dependency and this makes install, docs, and tests straightforward.
Document per-shell installation, verify generation in CI, and add integration tests for the filter behaviour and completion output.
⚓ When the tests go green, that be the sweetest sound on the seven seas.
Design ChoicesDesign Choice 1: How should we generate completions and control the dependency footprint?
Options Considered:
Add clap and clap_complete as runtime dependencies with a psfmt completions <shell> subcommand that prints a script to stdout.
Keep a hand-rolled parser and hand-write static completion scripts for each shell.
Use clap (runtime) plus clap_complete as a build-dependency that writes static scripts to the build output directory (OUT_DIR).
Chosen Option: 1
Rationale:clap is already a required runtime dependency for parsing, so clap_complete adds only a small extra footprint. A runtime completions subcommand is easy to install (psfmt completions bash > ...), easy to document, and easy to test directly, and it avoids the awkward OUT_DIR discovery problem of the build-script approach.
Design Choice 2: Should we expose `FormatOptions` as CLI flags?
Options Considered:
Expose the existing FormatOptions fields as stable flags plus optional file positionals.
Add only --help/--version and an optional file positional, leaving formatting hard-coded.
Chosen Option: 1
Rationale: The acceptance criteria require completions to cover all documented psfmt options and positional file arguments, yet none exist today. Option 1 reuses the existing FormatOptions fields, makes the completions meaningful, and satisfies the acceptance criteria without inventing new features.
💡 User Tips
Regenerate the plan with different choices with @coderabbitai <feedback>.
Implementation Steps
Phase 1: Give psfmt a proper CLI surface
This phase builds the argument parser that everything else depends on. The goal is a stable, well-defined option set that maps onto the existing FormatOptions. The old default behaviour must survive untouched: with no arguments, psfmt still reads stdin, formats, and writes stdout.
Task 1: Add the argument parser and define the command
Introduce clap and declare the option surface as the single source of truth for both parsing and completion generation.
Add clap (derive feature) to cli/psfmt/Cargo.toml as a runtime dependency. Add it directly to the crate. Do not create a [workspace.dependencies] table for a single consumer.
Define the command in a small, shareable module, for example cli/psfmt/src/cli.rs, so that both the parse path and the completions path use the same clap::Command.
Declare flags that map to FormatOptions: --indent-width, --use-tabs, --line-width, and --brace-style (with a fixed value set matching BraceStyle).
Declare an optional positional file argument set (zero or more paths). When paths are absent, the tool reads stdin.
Declare a completions subcommand with one required argument that selects the shell. Restrict the shell argument to the supported set so it also completes itself.
Rely on clap for --help and --version text and take the version from workspace.package.
Task 2: Wire the parser into main while preserving current behaviour
Refactor the entry point to parse arguments, dispatch the completions subcommand, and keep the exact filter behaviour when no options are given.
Update cli/psfmt/src/main.rs to parse arguments through the module from Task 1.1.
If the completions subcommand is present, generate the script for the requested shell (see Task 2.1), write it to stdout, and exit with code 0.
If no subcommand and no file positionals are present, keep the current path: read all of stdin, format, write diagnostics to stderr, write output to stdout.
If file positionals are present, format each file to stdout in order.
Map parsed flags onto FormatOptions. When a flag is absent, use the current default value for that field.
Preserve exit codes: 0 on success and non-zero on I/O or formatting failure. Note that clap returns exit code 2 for usage errors and 0 for --help/--version. This is additive and does not change the existing 0/1 filter path.
🤖 Prompt for AI agents
Implement a proper CLI argument surface for `psfmt`, replacing the current
zero-argument stdin→stdout filter, while keeping that default filter behaviour
intact.
- Add `clap` (with the derive feature) as a runtime dependency directly in
`cli/psfmt/Cargo.toml`. Do not create a `[workspace.dependencies]` table for a
single consumer.
- Create a shareable module, for example `cli/psfmt/src/cli.rs`, that defines
the `clap::Command` for `psfmt`. Both the parse path and the completions path
must use this same command definition.
- In this command, declare flags mapping to the existing `FormatOptions` fields:
`--indent-width`, `--use-tabs`, `--line-width`, and `--brace-style` (restrict
`--brace-style` to a fixed set of values matching `BraceStyle`).
- Declare an optional positional argument accepting zero or more file paths.
When no paths are given, `psfmt` must read from stdin.
- Declare a `completions` subcommand with one required argument that selects the
shell. Restrict the shell argument to the supported set so the argument itself
completes correctly.
- Let `clap` supply `--help` and `--version` output, using the version from
`workspace.package`.
- Update `cli/psfmt/src/main.rs` to parse arguments using the new module.
- If the `completions` subcommand is present, generate the script for the
requested shell, write it to stdout, and exit with code `0`.
- When no subcommand and no file positionals are provided, preserve the current
behaviour exactly: read all of stdin, format it, write diagnostics to stderr,
and write formatted output to stdout.
- When file positionals are provided, format each file in order and write output
to stdout.
- Map each parsed flag to the corresponding `FormatOptions` field; when a flag
is not supplied, fall back to the current default value for that field.
- Preserve exit codes: `0` on success, non-zero on I/O or formatting failure.
Note that `clap` itself returns exit code `2` for usage errors and `0` for
`--help`/`--version`; treat this as additive behaviour that does not alter the
existing `0`/`1` filter exit codes.
Phase 2: Generate the completion scripts
This phase turns the option set into completion scripts for the target shells. Generation runs at runtime through the completions subcommand, so users can install scripts directly and tests can assert the output.
Task 1: Implement the completions subcommand
Generate a completion script for the requested shell from the shared clap::Command.
Add clap_complete to cli/psfmt/Cargo.toml as a runtime dependency.
Implement the completions handler in the module from Task 1.1 (for example a generate_completions function that takes the shell and a writer).
Build the command from the shared definition, then call clap_complete::generate to write the script.
Support these shells: Bash, Zsh, Fish, and PowerShell. PowerShell matters because psfmt formats PowerShell.
Write the script to stdout so a user can redirect it to the correct location for the shell.
Task 2: Verify generation in CI
Make sure completion generation does not silently break.
Update .github/workflows/ci.yml to build the CLI on the existing three-OS matrix.
Add a lightweight step that runs psfmt completions <shell> for each supported shell and confirms the output is non-empty.
Keep this additive. Do not add a full release or publish pipeline. That is out of scope for this ticket.
🤖 Prompt for AI agents
Implement a runtime `completions` subcommand that generates a completion script
for a requested shell, using the shared `clap::Command` from Phase 1.
- Add `clap_complete` to `cli/psfmt/Cargo.toml` as a runtime dependency.
- Implement the `completions` handler inside the module from Phase 1 (for
example, a `generate_completions` function that accepts the shell and a writer).
- Build the command from the shared definition, then call
`clap_complete::generate` to write the completion script.
- Support these target shells: Bash, Zsh, Fish, and PowerShell. Include
PowerShell because `psfmt` itself formats PowerShell.
- Write the generated script to stdout so a user can redirect it to the correct
load location for their shell.
Then verify this behaviour in CI:
- Update `.github/workflows/ci.yml` so the CLI is built on the existing three-OS
matrix.
- Add a lightweight step that runs `psfmt completions <shell>` for each
supported shell and checks the output is non-empty.
- Keep this change additive only. Do not add a full release or publish pipeline;
that work is out of scope for this ticket.
Phase 3: Documentation and automated coverage
This phase tells users how to load the completions and locks the behaviour with tests. Everything documented, everything checked. That be how a perfectionist sleeps at night.
Task 1: Document options and completion installation
Extend the documentation without contradicting the existing filter description.
Update README.md under the "CLI contract" section. Add a "Shell completions" subsection.
Document the new options and the optional file positionals.
Give per-shell instructions that run psfmt completions <shell> and redirect the output to the correct load path for Bash, Zsh, Fish, and PowerShell.
Keep the existing filter example (psfmt < script.ps1 > formatted.ps1) accurate and unchanged.
Task 2: Add automated tests
Add practical coverage for both the CLI surface and completion generation.
Add an integration test under cli/psfmt/tests/ that runs the built binary through the CARGO_BIN_EXE_psfmt environment variable, so no extra test dependency is required.
Assert the preserved filter behaviour: piping source to stdin with no arguments yields formatted output on stdout and a 0 exit code.
Assert that the shared clap::Command passes Command::debug_assert to catch invalid option definitions.
Assert that psfmt completions <shell> produces non-empty output for each supported shell and exits with code 0.
🤖 Prompt for AI agents
Document the new CLI options and the completion installation steps, and add
automated tests locking in both the CLI behaviour and completion generation.
Documentation:
- Update `README.md` under the existing "CLI contract" section.
- Add a new "Shell completions" subsection.
- Document the new flags (`--indent-width`, `--use-tabs`, `--line-width`,
`--brace-style`) and the optional file positionals.
- Provide per-shell instructions that run `psfmt completions <shell>` and
redirect the output to the correct load path, covering Bash, Zsh, Fish, and
PowerShell.
- Keep the existing filter example (`psfmt < script.ps1 > formatted.ps1`)
accurate and unchanged; do not contradict the existing filter description.
Testing:
- Add an integration test under `cli/psfmt/tests/` that runs the compiled binary
via the `CARGO_BIN_EXE_psfmt` environment variable, avoiding any extra test
dependency.
- Assert that the preserved filter behaviour still works: piping source into
stdin with no arguments produces formatted output on stdout and exits with code
`0`.
- Assert that the shared `clap::Command` passes `Command::debug_assert`, to
catch invalid option definitions.
- Assert that `psfmt completions <shell>` produces non-empty output for each
supported shell (Bash, Zsh, Fish, PowerShell) and exits with code `0`.
Research
Arrr, heed this warning before ye set sail, matey! The ticket claims psfmt already carries a hand-rolled argument parser. It does not! The exploration proves the binary be a bare stdin→stdout filter with no argument surface at all. So we must first give psfmt a proper option set, then let a shell complete it. This plan does both, and it does not break the old behaviour. Sloppy premises make me itch.
Codebase Summary
The workspace is a Rust edition-2024 project (rust-version = "1.85"). The native CLI lives in cli/psfmt/src/main.rs. It reads all of stdin, calls powershell_formatter::format with FormatOptions::default(), writes diagnostics to stderr, and writes formatted output to stdout. It parses no arguments. FormatOptions (in crates/powershell-formatter/src/lib.rs) exposes brace_style, indent_width, use_tabs, and line_width. No CLI-parsing crate exists anywhere in the workspace. The root Cargo.toml has no [workspace.dependencies] table. README.md documents psfmt only as a Unix-style filter with no flags. .github/workflows/ci.yml runs fmt, clippy, and test on a three-OS matrix and has no build, release, or artefact steps.
🚀 Next Steps
🤖 All AI agent prompts combined
Task: 1
Implement a proper CLI argument surface for `psfmt`, replacing the current
zero-argument stdin→stdout filter, while keeping that default filter behaviour
intact.
- Add `clap` (with the derive feature) as a runtime dependency directly in
`cli/psfmt/Cargo.toml`. Do not create a `[workspace.dependencies]` table for a
single consumer.
- Create a shareable module, for example `cli/psfmt/src/cli.rs`, that defines
the `clap::Command` for `psfmt`. Both the parse path and the completions path
must use this same command definition.
- In this command, declare flags mapping to the existing `FormatOptions` fields:
`--indent-width`, `--use-tabs`, `--line-width`, and `--brace-style` (restrict
`--brace-style` to a fixed set of values matching `BraceStyle`).
- Declare an optional positional argument accepting zero or more file paths.
When no paths are given, `psfmt` must read from stdin.
- Declare a `completions` subcommand with one required argument that selects the
shell. Restrict the shell argument to the supported set so the argument itself
completes correctly.
- Let `clap` supply `--help` and `--version` output, using the version from
`workspace.package`.
- Update `cli/psfmt/src/main.rs` to parse arguments using the new module.
- If the `completions` subcommand is present, generate the script for the
requested shell, write it to stdout, and exit with code `0`.
- When no subcommand and no file positionals are provided, preserve the current
behaviour exactly: read all of stdin, format it, write diagnostics to stderr,
and write formatted output to stdout.
- When file positionals are provided, format each file in order and write output
to stdout.
- Map each parsed flag to the corresponding `FormatOptions` field; when a flag
is not supplied, fall back to the current default value for that field.
- Preserve exit codes: `0` on success, non-zero on I/O or formatting failure.
Note that `clap` itself returns exit code `2` for usage errors and `0` for
`--help`/`--version`; treat this as additive behaviour that does not alter the
existing `0`/`1` filter exit codes.
===============================================================================
Task: 2
Implement a runtime `completions` subcommand that generates a completion script
for a requested shell, using the shared `clap::Command` from Phase 1.
- Add `clap_complete` to `cli/psfmt/Cargo.toml` as a runtime dependency.
- Implement the `completions` handler inside the module from Phase 1 (for
example, a `generate_completions` function that accepts the shell and a writer).
- Build the command from the shared definition, then call
`clap_complete::generate` to write the completion script.
- Support these target shells: Bash, Zsh, Fish, and PowerShell. Include
PowerShell because `psfmt` itself formats PowerShell.
- Write the generated script to stdout so a user can redirect it to the correct
load location for their shell.
Then verify this behaviour in CI:
- Update `.github/workflows/ci.yml` so the CLI is built on the existing three-OS
matrix.
- Add a lightweight step that runs `psfmt completions <shell>` for each
supported shell and checks the output is non-empty.
- Keep this change additive only. Do not add a full release or publish pipeline;
that work is out of scope for this ticket.
===============================================================================
Task: 3
Document the new CLI options and the completion installation steps, and add
automated tests locking in both the CLI behaviour and completion generation.
Documentation:
- Update `README.md` under the existing "CLI contract" section.
- Add a new "Shell completions" subsection.
- Document the new flags (`--indent-width`, `--use-tabs`, `--line-width`,
`--brace-style`) and the optional file positionals.
- Provide per-shell instructions that run `psfmt completions <shell>` and
redirect the output to the correct load path, covering Bash, Zsh, Fish, and
PowerShell.
- Keep the existing filter example (`psfmt < script.ps1 > formatted.ps1`)
accurate and unchanged; do not contradict the existing filter description.
Testing:
- Add an integration test under `cli/psfmt/tests/` that runs the compiled binary
via the `CARGO_BIN_EXE_psfmt` environment variable, avoiding any extra test
dependency.
- Assert that the preserved filter behaviour still works: piping source into
stdin with no arguments produces formatted output on stdout and exits with code
`0`.
- Assert that the shared `clap::Command` passes `Command::debug_assert`, to
catch invalid option definitions.
- Assert that `psfmt completions <shell>` produces non-empty output for each
supported shell (Bash, Zsh, Fish, PowerShell) and exits with code `0`.
💡 Iterate on the plan with:
@coderabbitai <feedback>
Example Feedback
- @coderabbitai You can skip phase 3. Add a simple unit test case for phase 2.
- @coderabbitai For design choice 1 go ahead with option 3 and replan.
💬 Have feedback or questions? Drop into our discord!
Activity
coderabbitai commentedon Aug 16, 2026
🔗 Related PRs
kjanat/runner#3 - fix: suppress zsh NOMATCH errors in completion function [merged]
kjanat/runner#16 - fix(completions): accept shell path or
pwshfor explicit arg [merged]kjanat/runner#42 - fix(completions): fall back to $PSModulePath for pwsh shell detection [closed]
kjanat/dreamcli#43 - Add eager
--completionsflag and.help()config builder [merged]#1 - Production PowerShell formatter: lexer, engine, CLI, dprint plugin, WASM package [open]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
coderabbitai commentedon Aug 16, 2026
Coding Plan
Summary
clap-based argument parser topsfmt, because the ticket's premise of an existing parser is false. Keep the default stdin→stdout filter and exit codes intact.FormatOptionsfields as stable flags plus optional file positionals, so the completions have real options to cover.psfmt completions <shell>subcommand backed byclap_complete, sinceclapis already a runtime dependency and this makes install, docs, and tests straightforward.⚓ When the tests go green, that be the sweetest sound on the seven seas.
Design Choices
Design Choice 1: How should we generate completions and control the dependency footprint?
Options Considered:
clapandclap_completeas runtime dependencies with apsfmt completions <shell>subcommand that prints a script to stdout.clap(runtime) plusclap_completeas a build-dependency that writes static scripts to the build output directory (OUT_DIR).Chosen Option: 1
Rationale:
clapis already a required runtime dependency for parsing, soclap_completeadds only a small extra footprint. A runtimecompletionssubcommand is easy to install (psfmt completions bash > ...), easy to document, and easy to test directly, and it avoids the awkwardOUT_DIRdiscovery problem of the build-script approach.Design Choice 2: Should we expose `FormatOptions` as CLI flags?
Options Considered:
FormatOptionsfields as stable flags plus optional file positionals.--help/--versionand an optional file positional, leaving formatting hard-coded.Chosen Option: 1
Rationale: The acceptance criteria require completions to cover all documented
psfmtoptions and positional file arguments, yet none exist today. Option 1 reuses the existingFormatOptionsfields, makes the completions meaningful, and satisfies the acceptance criteria without inventing new features.💡 User Tips
Regenerate the plan with different choices with
@coderabbitai <feedback>.Implementation Steps
Phase 1: Give psfmt a proper CLI surface
This phase builds the argument parser that everything else depends on. The goal is a stable, well-defined option set that maps onto the existing
FormatOptions. The old default behaviour must survive untouched: with no arguments,psfmtstill reads stdin, formats, and writes stdout.Task 1: Add the argument parser and define the command
Introduce
clapand declare the option surface as the single source of truth for both parsing and completion generation.clap(derive feature) tocli/psfmt/Cargo.tomlas a runtime dependency. Add it directly to the crate. Do not create a[workspace.dependencies]table for a single consumer.cli/psfmt/src/cli.rs, so that both the parse path and the completions path use the sameclap::Command.FormatOptions:--indent-width,--use-tabs,--line-width, and--brace-style(with a fixed value set matchingBraceStyle).completionssubcommand with one required argument that selects the shell. Restrict the shell argument to the supported set so it also completes itself.clapfor--helpand--versiontext and take the version fromworkspace.package.Task 2: Wire the parser into main while preserving current behaviour
Refactor the entry point to parse arguments, dispatch the
completionssubcommand, and keep the exact filter behaviour when no options are given.cli/psfmt/src/main.rsto parse arguments through the module from Task 1.1.completionssubcommand is present, generate the script for the requested shell (see Task 2.1), write it to stdout, and exit with code0.FormatOptions. When a flag is absent, use the current default value for that field.0on success and non-zero on I/O or formatting failure. Note thatclapreturns exit code2for usage errors and0for--help/--version. This is additive and does not change the existing0/1filter path.🤖 Prompt for AI agents
Phase 2: Generate the completion scripts
This phase turns the option set into completion scripts for the target shells. Generation runs at runtime through the
completionssubcommand, so users can install scripts directly and tests can assert the output.Task 1: Implement the completions subcommand
Generate a completion script for the requested shell from the shared
clap::Command.clap_completetocli/psfmt/Cargo.tomlas a runtime dependency.completionshandler in the module from Task 1.1 (for example agenerate_completionsfunction that takes the shell and a writer).clap_complete::generateto write the script.psfmtformats PowerShell.Task 2: Verify generation in CI
Make sure completion generation does not silently break.
.github/workflows/ci.ymlto build the CLI on the existing three-OS matrix.psfmt completions <shell>for each supported shell and confirms the output is non-empty.🤖 Prompt for AI agents
Phase 3: Documentation and automated coverage
This phase tells users how to load the completions and locks the behaviour with tests. Everything documented, everything checked. That be how a perfectionist sleeps at night.
Task 1: Document options and completion installation
Extend the documentation without contradicting the existing filter description.
README.mdunder the "CLI contract" section. Add a "Shell completions" subsection.psfmt completions <shell>and redirect the output to the correct load path for Bash, Zsh, Fish, and PowerShell.psfmt < script.ps1 > formatted.ps1) accurate and unchanged.Task 2: Add automated tests
Add practical coverage for both the CLI surface and completion generation.
cli/psfmt/tests/that runs the built binary through theCARGO_BIN_EXE_psfmtenvironment variable, so no extra test dependency is required.0exit code.clap::CommandpassesCommand::debug_assertto catch invalid option definitions.psfmt completions <shell>produces non-empty output for each supported shell and exits with code0.🤖 Prompt for AI agents
Research
Arrr, heed this warning before ye set sail, matey! The ticket claims
psfmtalready carries a hand-rolled argument parser. It does not! The exploration proves the binary be a bare stdin→stdout filter with no argument surface at all. So we must first givepsfmta proper option set, then let a shell complete it. This plan does both, and it does not break the old behaviour. Sloppy premises make me itch.Codebase Summary
The workspace is a Rust edition-2024 project (
rust-version = "1.85"). The native CLI lives incli/psfmt/src/main.rs. It reads all of stdin, callspowershell_formatter::formatwithFormatOptions::default(), writes diagnostics to stderr, and writes formatted output to stdout. It parses no arguments.FormatOptions(incrates/powershell-formatter/src/lib.rs) exposesbrace_style,indent_width,use_tabs, andline_width. No CLI-parsing crate exists anywhere in the workspace. The rootCargo.tomlhas no[workspace.dependencies]table.README.mddocumentspsfmtonly as a Unix-style filter with no flags..github/workflows/ci.ymlrunsfmt,clippy, andteston a three-OS matrix and has no build, release, or artefact steps.🚀 Next Steps
🤖 All AI agent prompts combined
💡 Iterate on the plan with:
@coderabbitai <feedback>💬 Have feedback or questions? Drop into our discord!
kjanat commentedon Aug 16, 2026
@coderabbitai Check if we can implement something similar to what I've done in
envctl's complete.c,module.c& Co. Markdown docs accompanying that lives here: https://github.com/kjanat/envctl/blob/master/docs/shells.mdcoderabbitai commentedon Aug 16, 2026
I've updated the plan above based on your feedback. Ready to proceed when you are!