feat(root): add --no-color persistent flag - #145
Conversation
When set, the existing PersistentPreRunE switches lipgloss's default renderer to termenv.Ascii, suppressing color for every Style.Render call in this process. The NO_COLOR env path (handled by lipgloss / termenv at the renderer level) is unchanged. §8 of cli-common output-and-rendering: --no-color is the documented opt-out users discover via --help; NO_COLOR env alone is not. Closes #143
ReviewMinor: The production code matches the ticket, but the root tests are weaker than the PR description says. For this ticket, the load-bearing behavior is the root flag. I’d prefer one probe-style test that adds a no-op child command, runs Everything else looks aligned: the existing |
|
Coverage is solid — all critical paths are tested with proper isolation hygiene. Minor
Nit
|
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: b253302
Approved with 2 non-blocking suggestions below. Address at your discretion.
Summary
| Reviewer | Findings |
|---|---|
| harness-engineering:harness-architecture-reviewer | 1 |
| harness-engineering:harness-enforcement-reviewer | 1 |
harness-engineering:harness-architecture-reviewer (1 findings)
💡 Suggestion - internal/cmd/root/root_test.go:116
withRenderer is duplicated verbatim in internal/view/view_test.go (~line 71). The project already uses internal/testutil/ as the canonical location for shared test infrastructure. Duplicate helpers risk silently diverging — e.g., one copy picks up t.Helper() or a different cleanup order. Should be extracted to internal/testutil/ and imported by both test packages.
harness-engineering:harness-enforcement-reviewer (1 findings)
💡 Suggestion - internal/cmd/root/root_test.go:162
TestNoColorFlagThroughCobra cleanup resets the noColor Go variable but not cobra/pflag's internal flag state. After Execute(), the pflag.Flag for --no-color retains Changed=true. On any subsequent Execute() call that omits the flag, pflag internal value stays stale — flag.Changed stays true and help output would show the default as "true". Fix: add rootCmd.PersistentFlags().Set("no-color", "false") to the t.Cleanup block.
3 info-level observations excluded. Run with --verbose to include.
2 PR discussion threads considered.
Completed in 3m 39s | $1.01 | sonnet | daemon 0.2.121 | Glorfindel
| Field | Value |
|---|---|
| Model | sonnet |
| Reviewers | hybrid-synthesis, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer, harness-engineering:harness-self-documenting-code-reviewer, security:security-code-auditor |
| Engine | claude · sonnet |
| Reviewed by | pr-review-daemon · monit-pr-reviewer |
| Duration | 3m 39s wall · 9m 58s compute (Reviewers: 2m 49s · Synthesis: 48s) |
| Cost | $1.01 |
| Tokens | 209.3k in / 35.6k out |
| Turns | 10 |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost |
|---|---|---|---|---|---|---|
| hybrid-synthesis | sonnet | 29.7k | 2.9k | 13.2k | 16.4k (1h) | $0.12 |
| harness-engineering:harness-architecture-reviewer | sonnet | 42.1k | 8.7k | 21.0k | 21.2k (1h) | $0.22 |
| harness-engineering:harness-enforcement-reviewer | sonnet | 41.5k | 9.5k | 6.9k | 34.6k (1h) | $0.28 |
| harness-engineering:harness-knowledge-reviewer | sonnet | 38.9k | 5.4k | 21.0k | 18.0k (1h) | $0.16 |
| harness-engineering:harness-self-documenting-code-reviewer | sonnet | 40.1k | 7.5k | 20.5k | 19.6k (1h) | $0.20 |
| security:security-code-auditor | haiku | 16.9k | 1.5k | 0 | 16.9k (1h) | $0.03 |
Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.
| // withRenderer swaps the lipgloss default renderer for the duration of the | ||
| // test, restoring the saved renderer on cleanup. Tests using this must not | ||
| // call t.Parallel — the default renderer is process-global. | ||
| func withRenderer(t *testing.T, profile termenv.Profile) { |
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-architecture-reviewer): withRenderer is duplicated verbatim in internal/view/view_test.go (~line 71). The project already uses internal/testutil/ as the canonical location for shared test infrastructure. Duplicate helpers risk silently diverging — e.g., one copy picks up t.Helper() or a different cleanup order. Should be extracted to internal/testutil/ and imported by both test packages.
Reply to this thread when addressed.
| rootCmd.SetArgs(nil) | ||
| noColor = false | ||
| }) | ||
|
|
There was a problem hiding this comment.
🔵 Low (harness-engineering:harness-enforcement-reviewer): TestNoColorFlagThroughCobra cleanup resets the noColor Go variable but not cobra/pflag's internal flag state. After Execute(), the pflag.Flag for --no-color retains Changed=true. On any subsequent Execute() call that omits the flag, pflag internal value stays stale — flag.Changed stays true and help output would show the default as "true". Fix: add rootCmd.PersistentFlags().Set("no-color", "false") to the t.Cleanup block.
Reply to this thread when addressed.
|
No architectural drift. The daemon-driven changes stayed in the expected lane: production code still only adds the root The two daemon Low findings are test hygiene only: helper duplication and persistent pflag state cleanup. They do not change the architectural shape or cross-ticket assumptions. |
Summary
--no-colorpersistent flag on the gro root.PersistentPreRunEatinternal/cmd/root/root.go:44extended (not replaced) — preserveslog.VerboseandWireBackendSelectionwiring.--no-coloris set, switcheslipgloss.DefaultRenderer()totermenv.Asciiso everyStyle.Rendercall in this process emits plain text.NO_COLORenv behavior (handled natively by lipgloss/termenv) is unchanged — the flag is an additive, discoverable opt-out.Why
§8 of cli-common
output-and-rendering.mdrequires--no-coloras the documented opt-out alongsideNO_COLORenv. gro was the only CLI in the family missing the flag (per the divergence catalog).Test plan
make build—bin/groclean.make check(tidy+lint+test) — 24 tests green across view + root packages.TestSuccessUnderAsciiProfileEmitsNoANSI— proves Ascii profile suppresses ANSI.TestSuccessUnderANSIProfileEmitsANSI— regression proving the test setup isn't env-determined.TestErrorUnderAsciiProfileEmitsNoANSI— same forError(stderr).TestNoColorFlagRegistered— flag exists, type bool, persistent.TestPersistentPreRunE_NoColorTrueFlipsToAscii— end-to-end: forces ANSI baseline, setsnoColor = true, callsPersistentPreRunE, assertsColorProfile() == Ascii.TestPersistentPreRunE_NoColorFalseLeavesRendererUntouched— regression:noColor = falsedoesn't mutate the renderer.t.Cleanupto restorelipgloss.SetDefaultRenderer(saved)and reset thenoColorpackage var — no state leak across tests.Out of scope (per §8)
isattygating. §8 is explicit: "No CLI gates color onisatty. This is intentional."Closes #143