[repository-quality] Repository Quality Improvement Report — Output Stream Abstraction Gap (2026-07-22) #47310
Closed
Replies: 2 comments
|
Smoke ogre tap drum here.
|
0 replies
|
This discussion was automatically closed because it expired on 2026-07-23T13:26:59.545Z.
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
🎯 Repository Quality Improvement Report — Output Stream Abstraction Gap
Analysis Date: 2026-07-22
Focus Area: Output Stream Abstraction Gap — Hardcoded
os.Stdoutin Render FunctionsStrategy Type: Custom
Custom Area: Yes — this repo has a growing family of
render*formatter functions inpkg/cli/that bypass all output injection and write directly toos.Stdout, making unit testing awkward (requiring OS-pipe surgery viacaptureOutput) and making output redirection impossible for callers.Executive Summary
The
pkg/cli/package contains 167 hardcodedos.Stdoutreferences across production files, with the highest concentration in the log-rendering family (logs_format_tsv.go: 12 refs,logs_format_compact.go: 39 refs,audit_cross_run_render.go: 22 refs). Six corerender*functions —renderLogsTSV,renderLogsTSVVerbose,renderLogsCompact,renderLogsCompactVerbose,renderLogsJSON, andrenderLogsConsole— accept only aLogsDatastruct and write directly toos.Stdout, with noio.Writerparameter.A
captureOutputtest helper inchecks_command_test.goworks around this by swappingos.Stdoutat the OS level, annotated with a comment warning againstt.Parallel(). This anti-pattern propagates to all five test files that use it. Meanwhile,audit_cross_run_render.godemonstrates the correct pattern — it definesrenderCrossRunReportMarkdownToWriter(w io.Writer, ...)alongside a thinrenderCrossRunReportMarkdownwrapper — but this pattern has not been applied to the TSV, compact, JSON, or console formatters.The fix is straightforward: add an
io.Writerparameter to each affected function, update callers to passcmd.OutOrStdout()oros.Stdout, and update tests to usebytes.Bufferinstead of OS-pipe surgery. This eliminates thecaptureOutputmutex, enablest.Parallel()in formatter tests, and aligns with the existingaudit_cross_run_render.goconvention.Full Analysis Report
Focus Area: Output Stream Abstraction Gap
Current State Assessment
The
pkg/cli/log rendering subsystem hardcodesos.Stdoutin all six primary render functions. Onlyaudit_cross_run_render.gohas adopted the...ToWriter(w io.Writer, ...)convention — and only for its Markdown sub-functions; its JSON and Pretty top-level functions still hardcodeos.Stdout.Metrics Collected:
os.Stdoutrefs inpkg/cli/(non-test)render*functions with hardcodedos.Stdoutio.Writerinpkg/cli/captureOutputOS-swaprenderLogs*functions withio.Writerparameterassert.NoErrorcalls (should berequire.NoError)Findings
Strengths
audit_cross_run_render.goalready exports...ToWritervariants demonstrating the correct pattern.audit_cross_run_render.go'srenderCrossRunReportMarkdownis a thin wrapper — the split is already there.captureOutputtest helper centralises the OS-swap workaround rather than duplicating it.Areas for Improvement
renderLogsTSV/renderLogsTSVVerbose(39 stdout writes) — highest impact for testability.renderLogsCompact/renderLogsCompactVerbose(39 stdout writes across 39 lines) — same pattern.renderLogsJSON/renderLogsConsoleandrenderCrossRunReportJSON/renderCrossRunReportPrettystill hardcode.captureOutputserialises via a package-level mutex and blockst.Parallel().assert.NoErrorcalls (notrequire.NoError) mean that nil-pointer dereferences after a missed error can produce confusing test output instead of a clean failure.Detailed Analysis
Core problematic signatures (all in
pkg/cli/):Callsite in
logs_orchestrator_render.go:The
captureOutputhelper (file-level mutex, OS-pipe swap) is used in 5 test files and carries this comment:The correct pattern already exists in the same package:
🤖 Tasks for Copilot Agent
NOTE TO PLANNER AGENT: Split the following tasks into individual work items.
Improvement Tasks
Task 1: Add
io.Writerparameter torenderLogsTSVandrenderLogsTSVVerbosePriority: High
Estimated Effort: Small
Focus Area: Output Stream Abstraction
Description: Refactor
renderLogsTSV(data LogsData)andrenderLogsTSVVerbose(data LogsData)inpkg/cli/logs_format_tsv.goto accept anio.Writeras their first parameter. Replace allfmt.Fprintf(os.Stdout, ...)andfmt.Fprintln(os.Stdout, ...)calls inside those functions with equivalent calls using the passed writer. Update the two call sites inpkg/cli/logs_orchestrator_render.goto pass the appropriate writer (fromrenderLogsOutputOptionsoros.Stdoutas fallback). Updatepkg/cli/logs_format_tsv_test.goto passbytes.Bufferinstead of relying oncaptureOutput.Acceptance Criteria:
renderLogsTSV(w io.Writer, data LogsData)andrenderLogsTSVVerbose(w io.Writer, data LogsData)are the new signaturesos.Stdoutreferences remain inlogs_format_tsv.gologs_format_tsv_test.gouse&bytes.Buffer{}and no longer callcaptureOutputmake fmtpasses with no diffCode Region:
pkg/cli/logs_format_tsv.go,pkg/cli/logs_orchestrator_render.go,pkg/cli/logs_format_tsv_test.goTask 2: Add
io.Writerparameter torenderLogsCompactandrenderLogsCompactVerbosePriority: High
Estimated Effort: Small
Focus Area: Output Stream Abstraction
Description: Refactor
renderLogsCompact(data LogsData)andrenderLogsCompactVerbose(data LogsData)inpkg/cli/logs_format_compact.goto accept anio.Writeras their first parameter. This file has 39 hardcodedos.Stdoutreferences — the highest count of any single file. Replace all with the passed writer. Pay special attention totabwriter.NewWriter(os.Stdout, ...)on line 108 which must becometabwriter.NewWriter(w, ...). Update the callers inpkg/cli/logs_orchestrator_render.go.Acceptance Criteria:
renderLogsCompact(w io.Writer, data LogsData)andrenderLogsCompactVerbose(w io.Writer, data LogsData)are the new signaturesos.Stdoutreferences remain inlogs_format_compact.gotabwriter.NewWriteruses the passedw, notos.Stdoutlogs_orchestrator_render.goupdatedmake fmtpasses with no diffCode Region:
pkg/cli/logs_format_compact.go,pkg/cli/logs_orchestrator_render.goTask 3: Add
io.Writerparameter torenderLogsJSON,renderLogsConsole,renderCrossRunReportJSON, andrenderCrossRunReportPrettyPriority: Medium
Estimated Effort: Small
Focus Area: Output Stream Abstraction
Description: Four more top-level render functions still hardcode
os.Stdout:renderLogsJSON(data LogsData, verbose bool) errorinpkg/cli/logs_report.go:526— usesjson.NewEncoder(os.Stdout)renderLogsConsole(data LogsData)inpkg/cli/logs_report.go:615renderCrossRunReportJSON(report *CrossRunAuditReport) errorinpkg/cli/audit_cross_run_render.go:20renderCrossRunReportPretty(report *CrossRunAuditReport)inpkg/cli/audit_cross_run_render.go:210Add
w io.Writeras the first parameter to each. ForrenderLogsJSON, changejson.NewEncoder(os.Stdout)tojson.NewEncoder(w). ForrenderCrossRunReportJSON, same pattern. Update all callers.Acceptance Criteria:
io.Writeras first paramos.Stdoutreferences remain in those four functionsos.Stdoutor a real writer)make fmtpasses with no diffCode Region:
pkg/cli/logs_report.go,pkg/cli/audit_cross_run_render.goTask 4: Expose
renderLogsOutputOptions.output io.Writerand thread it throughrenderLogsOutputPriority: Medium
Estimated Effort: Small
Focus Area: Output Stream Abstraction
Description:
renderLogsOutputinpkg/cli/logs_orchestrator_render.gois the single dispatch function that calls allrenderLogs*formatters. After Tasks 1–3, all format functions will acceptio.Writer. Add anoutput io.Writerfield torenderLogsOutputOptions(defaulting toos.Stdoutwhen nil at call sites). Passopts.output(oros.Stdoutif nil) through to eachrenderLogs*call. This enables future callers (e.g., tests) to inject abytes.Bufferwithout usingcaptureOutput.Acceptance Criteria:
renderLogsOutputOptionshas anoutput io.WriterfieldrenderLogsOutputusesopts.output(nil-defaults toos.Stdout) for all stdout outputrenderLogsOutputcompile without changes (nil field falls back toos.Stdout)renderLogsOutputOptions{output: &bytes.Buffer{}}and verifies output withoutcaptureOutputmake fmtandmake test-unitpassCode Region:
pkg/cli/logs_orchestrator_render.goTask 5: Replace
assert.NoErrorwithrequire.NoErrorwhere result is immediately usedPriority: Low
Estimated Effort: Medium
Focus Area: Test Reliability
Description: The codebase has 251
assert.NoErrorcalls vs 5,735require.NoErrorcalls. Theassertvariant does not stop test execution on failure, so when the subsequent line dereferences a nil pointer returned by the failing call, the test panics with a confusing stack trace instead of a clear"unexpected error"message. The highest-count files arepkg/cli/audit_diff_test.go(289 totalassert.*calls),pkg/workflow/checkout_manager_test.go(284), andpkg/workflow/compiler_orchestrator_workflow_test.go(272). Scan each test file for the patternassert.NoError(t, err)immediately followed by code that uses the return value of the same call, and replace those specificassert.NoErrorcalls withrequire.NoError.Acceptance Criteria:
assert.NoErrorcalls inpkg/cli/audit_diff_test.gowhere the result is used on the next line are replaced withrequire.NoErrorassert.NoErrorcalls inpkg/workflow/checkout_manager_test.gothat gate subsequent value usage are replacedassert.NoErrorcalls where the error is truly optional (no immediate result usage) are left as-ismake test-unitpasses with no new failuresCode Region:
pkg/cli/audit_diff_test.go,pkg/workflow/checkout_manager_test.go,pkg/workflow/compiler_orchestrator_workflow_test.go📊 Historical Context
Previous Focus Areas
var _ I = (*T)(nil)guards; 4 tasks🎯 Recommendations
Immediate Actions (This Week)
io.WritertorenderLogsTSV/renderLogsTSVVerbose— Priority: High (Task 1)io.WritertorenderLogsCompact/renderLogsCompactVerbose— Priority: High (Task 2)Short-term Actions (This Month)
io.WritertorenderLogsJSON,renderLogsConsole, cross-run report functions — Priority: Medium (Task 3)output io.WriterthroughrenderLogsOutputOptions— Priority: Medium (Task 4)Long-term Actions (This Quarter)
assert.NoErrorwithrequire.NoErrorwhere results are used — Priority: Low (Task 5)outputstreamlinter that flagsos.Stdoutdirect writes outsidemain.goandcmd/📈 Success Metrics
os.Stdoutrefs inpkg/cli/(non-test): 167 → < 20 (cmd wiring only)render*functions with hardcodedos.Stdout: 27 → 0captureOutputmutex: 5 → 0renderLogs*functions withio.Writerparam: 0/6 → 6/6Next Steps
References:
Generated by Repository Quality Improvement Agent
All reactions