feat(go): write an answer the way each shell reads it - #989
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThe PR adds shell-specific rendering for Go completion answers and preserves complete help descriptions until the renderer collapses them safely.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; current code drops control-bearing candidate values instead of corrupting protocol framing or silently changing inserted arguments. Important Files Changed
Reviews (8): Last reviewed commit: "fix(go): decide the description column o..." | Re-trigger Greptile |
4c70937 to
1109ff7
Compare
1109ff7 to
2adfc2e
Compare
2adfc2e to
35bee41
Compare
35bee41 to
4e301ec
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4e301ec. Configure here.
Instruction countsNothing was compared, and so nothing was gated. No series appears on both sides: either the base has no measurements recorded, or the two were measured on different runner classes, which are deliberately not comparable — counts shift between machine types by more than a real regression does. New, nothing to compare against: Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes. Shadow comparisonParsing
|
4e301ec to
ae888f0
Compare
ae888f0 to
15f78f5
Compare
`RenderAnswer` turns candidates into the protocol a shell's completion machinery expects, which is where the five differ: bash reads values only; fish, nu and PowerShell take a description after a tab; zsh takes a third field, the text to insert, because what it displays and what it types are not always the same string. Checked against what `usage complete-word --shell zsh` already emits. Three rules that look like details and are not: Descriptions are all-or-nothing per answer. A column that appears on some rows and not others reads as missing data rather than as an absent description. A description is collapsed onto one line rather than truncated at the first break, so a two-line description still says both halves. The protocols are line-based and a break would look like another candidate — which is why the one-lining moved here from `describe`, where it was throwing the second half away. `Candidates` now carries the whole text and the renderer decides how to fit it. And "paths belong here too" is a whole line rather than a flag on the protocol, because every one of the five shells can already split output into lines and look at the last one. `\x01` opens it because no candidate can contain a control character. `RenderAnswer` rather than `Render`, which already belongs to failures: two things in one package turning a value into text for a terminal is reason enough to say which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A candidate carrying a tab or a newline was written straight into the output. Both protocols are lines with tab-separated fields, so such a value reads as extra rows or extra columns — zsh in particular would take the text after the tab as the description and the text after that as what to insert. A candidate normally comes from a spec and contains neither. A `complete` script can produce anything, though, and a completion that rearranges the protocol is a worse failure than a missing candidate: the shell inserts something nobody offered. Values go through the same one-lining descriptions already did, and the test asserts the field and row counts each shell expects rather than the text, since that is what the protocol is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit's parent collapsed a tab or a break inside a candidate onto a space so that it could not be read as another field or another row. That protects the protocol and breaks the candidate: a value is the text that gets typed onto the command line, so a repaired one inserts an argument nobody offered — the shell reports success and the CLI receives something else. Dropping it is the honest failure. The user types the value themselves and it works, and the file's own reasoning already said as much: a missing candidate is the lesser of the two. Every control character, not just the three that delimit. The marker lines that say "paths belong here too" open with `\x01`, so a value beginning with one was read as a marker. Descriptions still collapse. They are prose, nothing is typed from them, and a two-line help should still say both halves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Descriptions are all-or-nothing per answer, and the decision was made over every candidate — including the ones dropped a few lines below for carrying something that cannot travel. A description on a dropped row turned the column on for everyone else, putting an empty field on every surviving row: the rule broken by the answer it was deciding for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15f78f5 to
f37999a
Compare
#990) The front door. An author calls `Parse` and gets a value with fields rather than a loop over events — binding, the post-binding rules and the three tables are unchanged underneath. ```go cli, err := mycli.Parse(os.Args[1:]) if cli.Run != nil { fmt.Println(cli.Run.Task, cli.Run.Args) } ``` Stacked on #989. ## Fields are strings and bools, on purpose That is what a usage spec knows: it says what a value is *called* and never what type it is. Turning `"8"` into an `int` stays the caller's business — the conversions in #978 exist for exactly that, and inferring a type from an argument's name would be guessing. ## Two things mise found that a small fixture could not **Field names collide within a struct.** A command can declare a `--shell` flag beside a `shell` subcommand, and mise does it with `shell`, `version`, `command`, `env` and `tool`. The *kind* disambiguates — `Shell` and `ShellCmd` — because that says which one it is where `Shell2` would say only that there were two. The assignment is worked out once and shared by the declarations and by `Parse`, so the two cannot disagree about where a value goes. **A subcommand's defaults were dropped.** The fallback assignment started as a function taking the root struct, which cannot reach a subcommand's — that lives in a local of `Parse`. mise's `bootstrap packages import --manager` defaults to `brew` and came back empty. It is inline now, where the variables are; only the keys of commands the words selected are in scope, so the variable is never nil when its key is. Both are tested against mise's real command lines, including the `[ARGS]… [-- ARGS_LAST]…` split through the generated structs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are documentation, a thin env helper, and shadow integration tests; generated `Parse` behavior is exercised on real mise argv but does not alter the core parser’s hot path. > > **Overview** > **Go CLI authors are steered toward generated `Parse` instead of hand-rolling `argv.New` event loops.** `go/README.md` now shows `mycli.Parse(os.Args[1:])`, nested command structs with nil pointers for unselected branches, and notes that binding, post-rules, env/default fill, and validation all happen inside `Parse` with `string` / `bool` / `[]string` fields. > > **`argv.LookupEnv` is added** as the process-environment hook that generated `Parse` passes into `Fill`, keeping tests injectable while real CLIs read `os.LookupEnv`. > > **The mise shadow package gains integration tests** for the generated front door: struct fill (`use -g node@20`), `--` arg splitting on `tasks run`, subcommand defaults (`bootstrap packages import` → `brew`), and choice validation (`--log-level chatty` → `invalid_choice`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2fedce2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

RenderAnswerturns candidates into the protocol a shell's completion machinery expects. Stacked on #984.valuevalue\tdescriptionvalue\tdescription\tinsertzsh takes a third field because what it displays and what it types are not always the same string. Checked against what
usage complete-word --shell zshalready emits:Three rules that look like details and are not
Descriptions are all-or-nothing per answer. A column that appears on some rows and not others reads as missing data rather than as an absent description.
A description is collapsed onto one line, not truncated at the first break, so a two-line description still says both halves. The protocols are line-based and a break would look like another candidate. This is why the one-lining moved here from
describein #984, where it was throwing the second half away —Candidatesnow carries the whole text and the renderer decides how to fit it."Paths belong here too" is a whole line, not a flag on the protocol, because every one of the five shells can already split output into lines and look at the last one.
\x01opens it because no candidate can contain a control character.Naming
RenderAnswerrather thanRender, which already belongs to failures. Two things in one package turning a value into text for a terminal is reason enough to say which.Still to do
Running the
completescripts a spec can declare, which means subprocesses — a decision for the layer above this one.🤖 Generated with Claude Code
Note
Low Risk
Additive completion-output formatting with broad unit tests; no changes to parsing, binding, or security-sensitive paths.
Overview
Adds
argv.RenderAnswerso completionAnswervalues serialize to the line protocol each shell expects: bash values only; fish, nu, and PowerShell usevalue+ tab + description; zsh adds a third insert field withzshQuotefor safe typing.Rendering enforces all-or-nothing description columns (only among candidates that actually emit), collapses multi-line help with
oneLineinstead of truncating, drops candidates whose values contain control chars/tabs/newlines rather than repairing them, and appends\x01files/\x01dirsmarker lines when paths should complete too.describeincomplete.gonow attaches the full short help (including newlines); one-lining moved here from candidate building. README documentsRenderAnswer; tests cover per-shell shapes and edge cases.Reviewed by Cursor Bugbot for commit f37999a. Bugbot is set up for automated code reviews on this repo. Configure here.