Files
keyhunter/.planning/phases/06-output-reporting/06-06-SUMMARY.md
2026-04-05 23:42:57 +03:00

5.3 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed duration completed
06-output-reporting 06 cli
cobra
cli
output-dispatch
exit-codes
sarif
csv
json
ci-cd
phase provides
06-output-reporting formatter registry (table/json/csv/sarif), output.Options, ErrUnknownFormat
scan --output=table|json|sarif|csv wired through output.Get()
renderScanOutput helper for hermetic unit testing
OUT-06 exit-code contract: 0 clean, 1 findings, 2 tool error
version var in cmd package (ldflags-settable) feeding SARIF tool.driver.version
07-import-hooks
15-ci-cd-integration
added patterns
Extract RunE output dispatch into a small helper (renderScanOutput) so tests exercise the dispatch path without booting scanner/DB
Error wrapping with fmt.Errorf("%w (valid: %s)", ErrUnknownFormat, ...) keeps errors.Is checks intact while surfacing user guidance
Root Execute differentiates exit 1 (findings, set inline in scan RunE) from exit 2 (any non-nil RunE error)
created modified
cmd/scan_output_test.go
.planning/phases/06-output-reporting/06-06-SUMMARY.md
cmd/scan.go
cmd/root.go
Exit 1 is emitted directly inside scan RunE via os.Exit(1) before RunE returns — this preserves the 1-vs-2 split without plumbing a sentinel error
version is declared as a package-level var (default "dev") so goreleaser can override via -ldflags -X; SARIF tool.driver.version uses it
renderScanOutput takes io.Writer (not os.Stdout) so tests can capture output without redirecting file descriptors
Unknown-format error wraps output.ErrUnknownFormat AND appends the valid list in the same string — one error, both machine-readable and human-readable
OUT-05
OUT-06
3min 2026-04-05

Phase 06 Plan 06: Scan Output Wiring and Exit Codes Summary

Scan command now dispatches --output=table|json|sarif|csv through the formatter registry and honors the OUT-06 exit-code contract (0 clean, 1 findings, 2 error) for CI/CD consumers.

Performance

  • Duration: ~3 min
  • Tasks: 2
  • Files modified: 2 (cmd/scan.go, cmd/root.go)
  • Files created: 1 (cmd/scan_output_test.go)

Accomplishments

  • Replaced inline jsonFinding switch in scan RunE with a single call to output.Get(flagOutput)Formatter.Format(...), unlocking SARIF and CSV for scan output.
  • Extracted the dispatch into renderScanOutput(findings, name, unmask, w io.Writer) so the logic can be unit-tested without booting the full scanner/DB/consent pipeline.
  • Implemented OUT-06 exit-code contract:
    • 0: scan returns cleanly, no findings
    • 1: os.Exit(1) at the tail of scan RunE when len(findings) > 0
    • 2: root Execute() now maps any non-nil rootCmd.Execute() error to os.Exit(2) (was 1)
  • Added version package var + versionString() helper so SARIF tool.driver.version is populated and overridable via -ldflags "-X github.com/salvacybersec/keyhunter/cmd.version=...".
  • Updated --output help text to list all four formats.
  • Four-test coverage in cmd/scan_output_test.go:
    1. TestScanOutput_FormatNamesIncludeAll — registry wiring guard
    2. TestRenderScanOutput_UnknownReturnsErrorerrors.Is(err, output.ErrUnknownFormat) + "valid:" substring
    3. TestRenderScanOutput_JSONSucceeds — valid [] JSON for empty findings
    4. TestRenderScanOutput_TableEmpty — "No API keys found" sentinel

Verification

  • go build ./... — clean
  • go vet ./cmd/... — clean
  • go test ./... -count=1 — all packages green (cmd, engine, engine/sources, legal, output, providers, storage, verify)
  • go test ./cmd/... -run "TestScanOutput|TestRenderScanOutput" -count=1 — 4/4 pass

Commits

Task Type Hash Message
1 feat c9114e4 wire scan --output to formatter registry and exit-code contract
2 test cdf3c8a cover scan output dispatch and unknown-format error

Deviations from Plan

None — plan executed exactly as written. The renderScanOutput helper called out as optional in Task 2 was added during Task 1 (the plan explicitly permitted this consolidation in Task 2 step 1).

Files Touched

Modified:

  • cmd/scan.go — removed inline jsonFinding struct + encoding/json import; added io/strings imports; replaced output switch with renderScanOutput helper; introduced version var + versionString(); updated flag help text.
  • cmd/root.goExecute() now exits with code 2 (was 1) on non-nil rootCmd.Execute() error, with a comment documenting the OUT-06 exit-code contract.

Created:

  • cmd/scan_output_test.go — four tests covering registration, unknown-format error, JSON dispatch, table dispatch.

Known Stubs

None. All four registered formatters (table, json, csv, sarif) are fully wired and exercised by tests.

Self-Check: PASSED

  • cmd/scan.go contains output.Get(name) — verified (in renderScanOutput)
  • cmd/scan.go contains output.Options{ — verified
  • cmd/scan.go no longer contains jsonFinding — verified
  • cmd/root.go contains os.Exit(2) — verified
  • cmd/scan_output_test.go exists with four tests — verified
  • Task 1 commit c9114e4 present in git log — verified
  • Task 2 commit cdf3c8a present in git log — verified