11 KiB
CLI Output Implementation Plan
Purpose
This document is the staged implementation plan for cli.md. It is written for an LLM coding agent that will implement the CLI output harmonization in order.
The feature is complete when generate, run, and inspect have consistent
stdout/stderr behavior, action commands support --quiet, CLI output logic is
centralized in internal/cli, and maintained docs describe the implemented
contract.
Ground Rules
- Review
docs/policy/architecture.md,docs/policy/development.md, anddocs/policy/documentation.mdbefore editing code. - Keep application orchestration and domain behavior in
internal/app. - Keep CLI presentation, output summaries, stdout/stderr policy, and quiet-mode
behavior in
internal/cli. - Do not serialize full
app.ReportResultvalues directly to CLI stdout. - Do not add
--quietto inspection commands in this pass. - Do not add global
--format, NDJSON progress, human-readable success output, or machine-readable error envelopes. - Preserve current command semantics except where this plan explicitly changes output behavior.
Decisions
- CLI-safe summary structs live in
internal/cli/result.go, notinternal/app. - Add
app.GenerateDetailed(ctx, GenerateRequest) (*ReportResult, error). - Keep
app.Generate(ctx, GenerateRequest) erroras a wrapper aroundGenerateDetailed. - Keep
app.RunBatchDetailed(ctx, BatchRequest) (*BatchResult, error)as the app-layer batch source. - Add
commandandstatusfields in CLI summary structs, not inapp.BatchResult. BatchSummary.statusisfailedwhen any report failed or the top-level batch notification status isfailed; otherwise it issucceeded.GenerateSummary.statusissucceededfor a successful generated report andfailedonly when a non-nilReportResultis returned with an error after inspectable artifacts exist.- Default action-command output may include a failure JSON summary when the app layer returns a non-nil result with an error.
- Quiet mode suppresses successful action-command stdout and routine stderr. It does not hide returned errors.
Stage 1: Detailed Generate Result
Goal: make single-report generation return the same kind of structured result that batch generation already uses internally.
Implementation:
- Add
GenerateDetailed(ctx, GenerateRequest) (*ReportResult, error)ininternal/app. - Move the current body of
GenerateintoGenerateDetailed. - Change
Generateto callGenerateDetailedand return only the error. - Ensure
GenerateDetailedpreserves existing behavior for:- collecting weather before resolving/generating;
- unknown or unimplemented reports;
- explicit Daily date requirements;
- optional output copy behavior;
- distributor notification behavior.
- When
GenerateReportor generated-template finalization receives a non-emptyfinalizeRenderedReportResultplus an error after managed artifacts exist, return a non-nilReportResulttogether with that error. This is especially important for notification failures where the report and notification artifact are inspectable. - Do not return partial results for flag/config/pre-run validation failures or failures before a useful run identity exists.
Tests:
- Add app tests for
GenerateDetailedsuccess. - Add an app test showing
Generatestill returns only the underlying error. - Add or adjust an app test for notification failure so
GenerateDetailedreturns a non-nil result with report, metadata, and notification artifact paths while also returning the notification error.
Validation:
go test ./internal/app
Completion criteria:
- Existing app behavior is preserved for callers of
Generate. - CLI callers can obtain a rich
ReportResultfromGenerateDetailed.
Stage 2: CLI Summary Types
Goal: define small, stable CLI output contracts without exposing full app internals.
Implementation:
- Add
internal/cli/result.go. - Define a
generateSummarystruct with these JSON fields:commandreportIdreportNamepromptIdrunIdstatusgeneratedAtvalidPeriodreportPath,omitemptyoutputPath,omitemptymetadataPath,omitemptydataPackagePath,omitemptypreflightPath,omitemptygeneratedTextRawPath,omitemptygeneratedTextResultPath,omitemptygeneratedTextPath,omitemptyrenderContextPath,omitemptynotificationPath,omitemptynotification,omitemptyerror,omitempty
- Define a
batchSummarystruct with these JSON fields:commandbatchstatusstartedAtfinishedAttotalsucceededfailednotification,omitemptyreportserror,omitempty
- Reuse existing app result substructures where they are already CLI-safe:
timeutil.Period,app.BatchNotificationResult, andapp.BatchReportResult. - Add conversion helpers:
newGenerateSummary(result *app.ReportResult, err error) generateSummarynewBatchSummary(result *app.BatchResult) batchSummary
- Do not include module snapshot contents, data package contents, raw generated text bytes, render result bodies, or full distributor adapter payloads.
- Keep error strings concise and avoid adding secrets.
Tests:
- Add focused unit tests for summary conversion.
- Cover generated-text reports, markdown reports, disabled notification, and notification failure with a non-nil result.
- Cover batch status derivation for success, report failure, skipped notification, and failed notification.
Validation:
go test ./internal/cli
Completion criteria:
- CLI summary shapes are explicit and independent of full app result structs.
Stage 3: Centralized Output Helpers
Goal: make the consistent output path the default path for current and future commands.
Implementation:
- Add
internal/cli/output.go. - Move
writeJSONfromroot.gointooutput.go. - Move
writeRunLogsfromroot.gointooutput.goand rename it towriteBatchStatus. - Add an
outputOptionsstruct with at least:Quiet bool
- Add a small action output helper such as:
writeActionResult(stdout, stderr io.Writer, value any, opts outputOptions, writeStatus func(io.Writer)) error. - The helper must:
- return without writing stdout or routine stderr when
opts.Quietis true; - write status before JSON for default action output when a status writer is provided;
- use the shared JSON writer for stdout;
- tolerate nil stderr when no status output is needed.
- return without writing stdout or routine stderr when
- Keep inspect commands using
writeJSONdirectly because inspection is data output, not quietable action output. - Ensure
root.gono longer callsjson.NewEncoderdirectly.
Tests:
- Add unit tests for quiet/default action output helper behavior.
- Keep existing batch stderr tests, updated for renamed helpers if needed.
Validation:
go test ./internal/cli
Completion criteria:
- JSON encoding and action status output are centralized outside command routing.
Stage 4: Wire Generate And Run Output
Goal: make current action commands use the same output contract.
Implementation:
- Extend
commonOptionsor action-specific options withQuiet bool. - Parse
--quietforgenerateandrun. - Do not parse or accept
--quietforinspect. - Update
Runner.Run:generateshould callapp.GenerateDetailed;- when a non-nil result is returned, convert it to
generateSummary; - write the summary through the centralized action output helper;
- if an error is also returned, write default JSON only when a non-nil result exists and quiet is false, then return the error;
- if no result is returned, return the error without writing partial JSON.
- Update
runcommand handling:- convert
*app.BatchResulttobatchSummary; - write through the centralized action output helper;
- preserve the current behavior that a batch result is emitted in default
mode before returning
app.BatchErrorfor failed reports; - preserve notification-only batch failure behavior.
- convert
- Keep
inspectcommands unchanged except for using the relocatedwriteJSON.
Tests:
generate todayemits valid JSON on success.- The generate JSON includes
command: "generate",status: "succeeded",reportId,runId,reportPath,metadataPath,dataPackagePath, andpreflightPath. - Generated-text reports include generated-text artifact paths.
- Markdown reports omit generated-text artifact paths.
generate --quietemits no stdout or routine stderr on success.- Generate pre-run errors emit no partial JSON.
- Generate notification failure with an inspectable result emits a failure JSON summary in default mode and returns nonzero.
run morningandrun eveningstill emit JSON summaries by default.- Run JSON includes
command: "run"and a derivedstatus. run --quietsuppresses successful summary/status output.- Failed batch runs still return nonzero and still emit default JSON when quiet is false.
- Inspect commands still emit requested JSON and reject
--quietas an unexpected flag. - Output tests confirm distributor token values are not printed.
Validation:
go test ./internal/cli ./internal/app
Completion criteria:
- Current action commands have consistent default JSON behavior.
- Quiet mode is available for action commands and not inspection commands.
Stage 5: Documentation
Goal: document the implemented CLI output contract in maintained docs and make future changes follow the same structure.
Documentation changes:
- Update
docs/cli.md:- document default JSON stdout for
generate,run, andinspect; - document compact status stderr for batch commands;
- document
--quietforgenerateandrun; - include representative generate and run JSON snippets;
- state that inspection commands are not quietable.
- document default JSON stdout for
- Update
docs/operations.mdif cron/operator behavior changes need an operations note. - Add
docs/internal/cli.mddocumenting:- command categories;
- stdout/stderr rules;
- quiet-mode behavior;
- summary conversion ownership;
- the expected helper path for future commands.
- Update
docs/policy/development.mdCLI-change guidance so future CLI commands are expected to use the centralized output helpers and declare an output category.
Tests:
- Update CLI help-output tests for
--quiet. - Add or update docs-related tests only if this repository already validates the touched docs/examples in tests.
Validation:
go test ./internal/cli ./internal/app
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
Completion criteria:
- Non-roadmap docs describe only implemented behavior.
- The roadmap can be removed after implementation if no deferred CLI-output feature remains in it.
Final Verification
Before considering the feature complete, run:
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
Also manually verify these command behaviors against test fixtures or a local test config when practical:
weatherreporter generate today --quiet
weatherreporter run morning --quiet
weatherreporter inspect reports --limit 1
Open Questions
None. The decisions above are sufficient for implementation.