cmd/scan.go --output=json|csv (wired in later plan)
added
patterns
encoding/json Encoder with SetIndent for pretty output
encoding/csv Writer for stable, auto-quoted flat rows
omitempty on verify_* JSON fields to keep unverified output compact
init()-time registration into the shared output.Registry
created
modified
pkg/output/json.go
pkg/output/json_test.go
pkg/output/csv.go
pkg/output/csv_test.go
JSON `key` field carries masked value by default; `key_masked` always carries masked form regardless of Unmask so consumers can tell which mode produced the file.
Verification fields use `omitempty` so unverified scans stay compact; `verified: false` is always emitted as a required discriminator.
CSV `id` column is the zero-based slice index, not the DB rowid — formatter must run on in-memory findings before persistence.
CSV header order is frozen (id,provider,confidence,key,source,line,detected_at,verified,verify_status) — downstream consumers may index by position.
DetectedAt is serialised as RFC3339 in both formats for cross-format consistency.
duration
completed
tasks
commits
~10m
2026-04-05
2
4
Phase 06 Plan 02: JSON + CSV Formatters Summary
Added JSONFormatter and CSVFormatter to pkg/output, both implementing the Formatter interface established in Plan 01 and both honoring the Unmask option. These are the machine-readable siblings of the colored table output: JSON for pipelines and tooling, CSV for spreadsheets and ad-hoc grep.
What Was Built
1. JSONFormatter (pkg/output/json.go)
Renders []engine.Finding as a JSON array with 2-space indent via json.Encoder.SetIndent("", " ").
Empty input produces "[]\n" exactly (important for deterministic CI diffs).
TestCSVFormatter_MultipleRowsIncrementID — id column increments 0,1,… with slice position.
Commits
Task
Phase
Hash
Message
1
RED
c933673
test(06-02): add failing tests for JSONFormatter
1
GREEN
1644771
feat(06-02): implement JSONFormatter with Unmask support
2
RED
b35881a
test(06-02): add failing tests for CSVFormatter
2
GREEN
03249fb
feat(06-02): implement CSVFormatter with Unmask support
Verification
$ go test ./pkg/output/... -count=1
ok github.com/salvacybersec/keyhunter/pkg/output 0.005s
$ go build ./...
(no output — clean build)
$ grep -h "Register(" pkg/output/*.go
Register("csv", CSVFormatter{})
Register("json", JSONFormatter{})
Register("sarif", SARIFFormatter{})
Register("table", TableFormatter{})
All four formatters (table, json, csv, sarif) now live in the registry — --output=json|csv works end to end once the scan command is wired up in a later plan.
Deviations from Plan
Fixed During Execution
1. [Rule 1 - Test bug] Incorrect indent assertion in initial JSON test
Found during: Task 1 GREEN run
Issue:TestJSONFormatter_Indented asserted "\n \"provider\"" (2-space indent) but json.Encoder.SetIndent("", " ") produces 4-space indent for nested object fields (array item at 2 spaces + 2 more for nested field).
Fix: Assertion now verifies both indent levels: "\n {" for array items and "\n \"provider\"" for nested fields.
Files modified:pkg/output/json_test.go
Commit:1644771 (rolled into GREEN commit since the RED had not yet been runnable)
Plan body (the JSON encoder behavior) was implemented exactly as specified — only the test expectation was off by one indent level.
Out of Scope / Observed
During execution, a pkg/output/sarif_test.go file from the parallel Plan 03 executor was present in the working tree before pkg/output/sarif.go existed, causing the package to fail compilation for unrelated tests. Handled by temporarily moving it aside while running go test -run TestJSONFormatter|TestCSVFormatter, then restoring. By the time the final package-wide test run happened, Plan 03's sarif.go had landed and the full package compiled and tested cleanly. No files were modified as part of this mitigation; this is a parallel-execution artifact and not a deviation from the plan.
Known Stubs
None. JSON and CSV formatters emit real data from engine.Finding and wire through Options.Unmask. No placeholder fields, no TODO markers.