Address golangci-lint findings and improve error handling throughout:
Package doc comments:
- Add canonical "// Package X ..." comments to source, model, config,
pipeline, cli, store, and main packages for godoc compliance.
Security & correctness:
- Fix directory permissions 0o755 -> 0o750 in store/cache.go Open()
(gosec G301: restrict group write on cache directory)
- Fix config.Save() to check encoder error before closing file, preventing
silent data loss on encode failure
- Add //nolint:gosec annotations with justifications on intentional
patterns (constructed file paths, manual bounds checking, config fields)
- Add //nolint:nilerr on intentional error-swallowing in scanner WalkDir
- Add //nolint:revive on stuttering type names (ModelStats, ModelUsage)
that would break too many call sites to rename
Performance (perfsprint):
- Replace fmt.Sprintf("%d", n) with strconv.FormatInt(n, 10) in format.go
FormatTokens() and FormatNumber() hot paths
- Clean up redundant fmt.Sprintf patterns in FormatCost and FormatDelta
Code cleanup:
- Convert if-else chain to switch in parser.go skipJSONString() for clarity
- Remove unused indexedResult struct from pipeline/loader.go
- Add deferred cache.Close() in pipeline/bench_test.go to prevent leaks
- Add deferred cache.Close() in cmd/root.go data loading path
- Fix doc comment alignment in scanner.go decodeProjectName
- Remove trailing blank line in cmd/costs.go
- Fix duplicate "/day" suffix in cmd/summary.go cost-per-day formatting
- Rename shadowed variable 'max' -> 'maxVal' in cli/render.go Sparkline
Implement the bottom of the data pipeline — discovery and parsing of
Claude Code session files:
- source/types.go: Raw JSON deserialization types (RawEntry,
RawMessage, RawUsage, CacheCreation) matching the Claude Code
JSONL schema. DiscoveredFile carries file metadata including
decoded project name, session ID, and subagent relationship info.
- source/scanner.go: ScanDir walks ~/.claude/projects/ to discover
all .jsonl session files. Detects subagent files by the
<project>/<session>/subagents/agent-<id>.jsonl path pattern and
links them to parent sessions. decodeProjectName reverses Claude
Code's path-encoding convention (/-delimited path segments joined
with hyphens) by scanning for known parent markers (projects,
repos, src, code, workspace, dev) and extracting the project name
after the last marker.
- source/parser.go: ParseFile processes a single JSONL session file.
Uses a hybrid parsing strategy for performance:
* "user" and "system" entries: byte-level field extraction for
timestamps, cwd, and turn_duration (avoids JSON allocation).
extractTopLevelType tracks brace depth and string boundaries to
find only the top-level "type" field, early-exiting ~400 bytes
in for O(1) per line cost regardless of line length.
* "assistant" entries: full JSON unmarshal to extract token usage,
model name, and cost data.
Deduplicates API calls by message.id (keeping the last entry per
ID, which holds the final billed usage). Computes per-model cost
breakdown using config.CalculateCost and aggregates cache hit rate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>