refactor(cmd): replace map[string]interface{} with typed JSON response structs (bd-u2sc.1)

Added typed response structs for JSON output in CLI commands:

compact.go:
- CompactDryRunResponse, CompactSuccessResponse
- CompactNoCandidatesResponse, CompactBatchSuccessResponse
- CompactStatsResponse, CompactTierStats
- CompactApplyResponse, TombstonePrunedInfo

cleanup.go:
- CleanupEmptyResponse

daemons.go:
- DaemonStopResponse, DaemonRestartResponse
- DaemonLogsResponse, DaemonKillallEmptyResponse
- DaemonHealthResponse, DaemonHealthReport

daemon_lifecycle.go:
- DaemonStatusResponse

Benefits:
- Compile-time type checking for JSON output
- IDE autocompletion for response fields
- Self-documenting API structure
- Easier refactoring

Note: RPC args and storage update maps remain as-is (require
interface changes for internal APIs).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-22 15:48:36 -08:00
parent e67712dcd4
commit 4c38075520
4 changed files with 247 additions and 122 deletions

View File

@@ -34,6 +34,81 @@ var (
compactLimit int
)
// JSON response types for compact command output
// CompactDryRunResponse is returned for --dry-run mode
type CompactDryRunResponse struct {
DryRun bool `json:"dry_run"`
Tier int `json:"tier"`
IssueID string `json:"issue_id,omitempty"`
OriginalSize int `json:"original_size,omitempty"`
CandidateCount int `json:"candidate_count,omitempty"`
TotalSizeBytes int `json:"total_size_bytes,omitempty"`
EstimatedReduction string `json:"estimated_reduction"`
}
// CompactSuccessResponse is returned for successful single-issue compaction
type CompactSuccessResponse struct {
Success bool `json:"success"`
Tier int `json:"tier"`
IssueID string `json:"issue_id"`
OriginalSize int `json:"original_size"`
CompactedSize int `json:"compacted_size"`
SavedBytes int `json:"saved_bytes"`
ReductionPct float64 `json:"reduction_pct"`
ElapsedMs int64 `json:"elapsed_ms"`
}
// CompactNoCandidatesResponse is returned when no candidates are found
type CompactNoCandidatesResponse struct {
Success bool `json:"success"`
Count int `json:"count"`
Message string `json:"message"`
}
// CompactBatchSuccessResponse is returned for successful batch compaction
type CompactBatchSuccessResponse struct {
Success bool `json:"success"`
Tier int `json:"tier"`
Total int `json:"total"`
Succeeded int `json:"succeeded"`
Failed int `json:"failed"`
SavedBytes int `json:"saved_bytes"`
OriginalSize int `json:"original_size"`
ElapsedMs int64 `json:"elapsed_ms"`
}
// CompactTierStats holds statistics for a compaction tier
type CompactTierStats struct {
Candidates int `json:"candidates"`
TotalSize int `json:"total_size"`
}
// CompactStatsResponse is returned for --stats mode
type CompactStatsResponse struct {
Tier1 CompactTierStats `json:"tier1"`
Tier2 CompactTierStats `json:"tier2"`
}
// TombstonePrunedInfo holds info about pruned tombstones
type TombstonePrunedInfo struct {
Count int `json:"count"`
TTLDays int `json:"ttl_days"`
}
// CompactApplyResponse is returned for --apply mode
type CompactApplyResponse struct {
Success bool `json:"success"`
IssueID string `json:"issue_id"`
Tier int `json:"tier"`
OriginalSize int `json:"original_size"`
CompactedSize int `json:"compacted_size"`
SavedBytes int `json:"saved_bytes"`
ReductionPct float64 `json:"reduction_pct"`
ElapsedMs int64 `json:"elapsed_ms"`
TombstonesPruned *TombstonePrunedInfo `json:"tombstones_pruned,omitempty"`
}
// TODO: Consider consolidating into 'bd doctor --fix' for simpler maintenance UX
var compactCmd = &cobra.Command{
Use: "compact",
@@ -248,14 +323,13 @@ func runCompactSingle(ctx context.Context, compactor *compact.Compactor, store *
if compactDryRun {
if jsonOutput {
output := map[string]interface{}{
"dry_run": true,
"tier": compactTier,
"issue_id": issueID,
"original_size": originalSize,
"estimated_reduction": "70-80%",
}
outputJSON(output)
outputJSON(CompactDryRunResponse{
DryRun: true,
Tier: compactTier,
IssueID: issueID,
OriginalSize: originalSize,
EstimatedReduction: "70-80%",
})
return
}
@@ -290,17 +364,16 @@ func runCompactSingle(ctx context.Context, compactor *compact.Compactor, store *
elapsed := time.Since(start)
if jsonOutput {
output := map[string]interface{}{
"success": true,
"tier": compactTier,
"issue_id": issueID,
"original_size": originalSize,
"compacted_size": compactedSize,
"saved_bytes": savingBytes,
"reduction_pct": float64(savingBytes) / float64(originalSize) * 100,
"elapsed_ms": elapsed.Milliseconds(),
}
outputJSON(output)
outputJSON(CompactSuccessResponse{
Success: true,
Tier: compactTier,
IssueID: issueID,
OriginalSize: originalSize,
CompactedSize: compactedSize,
SavedBytes: savingBytes,
ReductionPct: float64(savingBytes) / float64(originalSize) * 100,
ElapsedMs: elapsed.Milliseconds(),
})
return
}
@@ -348,10 +421,10 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
if len(candidates) == 0 {
if jsonOutput {
outputJSON(map[string]interface{}{
"success": true,
"count": 0,
"message": "No eligible candidates",
outputJSON(CompactNoCandidatesResponse{
Success: true,
Count: 0,
Message: "No eligible candidates",
})
return
}
@@ -370,14 +443,13 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
}
if jsonOutput {
output := map[string]interface{}{
"dry_run": true,
"tier": compactTier,
"candidate_count": len(candidates),
"total_size_bytes": totalSize,
"estimated_reduction": "70-80%",
}
outputJSON(output)
outputJSON(CompactDryRunResponse{
DryRun: true,
Tier: compactTier,
CandidateCount: len(candidates),
TotalSizeBytes: totalSize,
EstimatedReduction: "70-80%",
})
return
}
@@ -420,17 +492,16 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
elapsed := time.Since(start)
if jsonOutput {
output := map[string]interface{}{
"success": true,
"tier": compactTier,
"total": len(results),
"succeeded": successCount,
"failed": failCount,
"saved_bytes": totalSaved,
"original_size": totalOriginal,
"elapsed_ms": elapsed.Milliseconds(),
}
outputJSON(output)
outputJSON(CompactBatchSuccessResponse{
Success: true,
Tier: compactTier,
Total: len(results),
Succeeded: successCount,
Failed: failCount,
SavedBytes: totalSaved,
OriginalSize: totalOriginal,
ElapsedMs: elapsed.Milliseconds(),
})
return
}
@@ -480,17 +551,16 @@ func runCompactStats(ctx context.Context, store *sqlite.SQLiteStorage) {
}
if jsonOutput {
output := map[string]interface{}{
"tier1": map[string]interface{}{
"candidates": len(tier1),
"total_size": tier1Size,
outputJSON(CompactStatsResponse{
Tier1: CompactTierStats{
Candidates: len(tier1),
TotalSize: tier1Size,
},
"tier2": map[string]interface{}{
"candidates": len(tier2),
"total_size": tier2Size,
Tier2: CompactTierStats{
Candidates: len(tier2),
TotalSize: tier2Size,
},
}
outputJSON(output)
})
return
}
@@ -892,24 +962,24 @@ func runCompactApply(ctx context.Context, store *sqlite.SQLiteStorage) {
}
if jsonOutput {
output := map[string]interface{}{
"success": true,
"issue_id": compactID,
"tier": compactTier,
"original_size": originalSize,
"compacted_size": compactedSize,
"saved_bytes": savingBytes,
"reduction_pct": reductionPct,
"elapsed_ms": elapsed.Milliseconds(),
response := CompactApplyResponse{
Success: true,
IssueID: compactID,
Tier: compactTier,
OriginalSize: originalSize,
CompactedSize: compactedSize,
SavedBytes: savingBytes,
ReductionPct: reductionPct,
ElapsedMs: elapsed.Milliseconds(),
}
// Include tombstone pruning results (bd-okh)
if tombstonePruneResult != nil && tombstonePruneResult.PrunedCount > 0 {
output["tombstones_pruned"] = map[string]interface{}{
"count": tombstonePruneResult.PrunedCount,
"ttl_days": tombstonePruneResult.TTLDays,
response.TombstonesPruned = &TombstonePrunedInfo{
Count: tombstonePruneResult.PrunedCount,
TTLDays: tombstonePruneResult.TTLDays,
}
}
outputJSON(output)
outputJSON(response)
return
}