feat(jsonl): add omitempty to reduce JSONL bloat (beads-399)

Add omitempty JSON tags to Issue struct fields (Description, Status,
Priority, IssueType) and SetDefaults method to apply proper defaults
when importing JSONL with omitted fields.

This reduces JSONL file size for minimal issues like notifications
by not exporting empty/default values.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-19 23:18:11 -08:00
parent 2ea6083163
commit 6c060461cb
9 changed files with 97 additions and 7 deletions

View File

@@ -12,13 +12,13 @@ type Issue struct {
ID string `json:"id"`
ContentHash string `json:"-"` // Internal: SHA256 hash of canonical content (excludes ID, timestamps) - NOT exported to JSONL
Title string `json:"title"`
Description string `json:"description"`
Description string `json:"description,omitempty"`
Design string `json:"design,omitempty"`
AcceptanceCriteria string `json:"acceptance_criteria,omitempty"`
Notes string `json:"notes,omitempty"`
Status Status `json:"status"`
Priority int `json:"priority"`
IssueType IssueType `json:"issue_type"`
Status Status `json:"status,omitempty"`
Priority int `json:"priority,omitempty"`
IssueType IssueType `json:"issue_type,omitempty"`
Assignee string `json:"assignee,omitempty"`
EstimatedMinutes *int `json:"estimated_minutes,omitempty"`
CreatedAt time.Time `json:"created_at"`
@@ -190,6 +190,27 @@ func (i *Issue) ValidateWithCustomStatuses(customStatuses []string) error {
return nil
}
// SetDefaults applies default values for fields omitted during JSONL import.
// Call this after json.Unmarshal to ensure missing fields have proper defaults:
// - Status: defaults to StatusOpen if empty
// - Priority: defaults to 2 if zero (note: P0 issues must explicitly set priority=0)
// - IssueType: defaults to TypeTask if empty
//
// This enables smaller JSONL output by using omitempty on these fields.
func (i *Issue) SetDefaults() {
if i.Status == "" {
i.Status = StatusOpen
}
// Note: priority 0 (P0) is a valid value, so we can't distinguish between
// "explicitly set to 0" and "omitted". For JSONL compactness, we treat
// priority 0 in JSONL as P0, not as "use default". This is the expected
// behavior since P0 issues are explicitly marked.
// Priority default of 2 only applies to new issues via Create, not import.
if i.IssueType == "" {
i.IssueType = TypeTask
}
}
// Status represents the current state of an issue
type Status string

View File

@@ -900,3 +900,62 @@ func containsMiddle(s, substr string) bool {
}
return false
}
func TestSetDefaults(t *testing.T) {
tests := []struct {
name string
issue Issue
expectedStatus Status
expectedType IssueType
}{
{
name: "empty fields get defaults",
issue: Issue{Title: "Test"},
expectedStatus: StatusOpen,
expectedType: TypeTask,
},
{
name: "existing status preserved",
issue: Issue{
Title: "Test",
Status: StatusInProgress,
},
expectedStatus: StatusInProgress,
expectedType: TypeTask,
},
{
name: "existing type preserved",
issue: Issue{
Title: "Test",
IssueType: TypeBug,
},
expectedStatus: StatusOpen,
expectedType: TypeBug,
},
{
name: "all fields set - no changes",
issue: Issue{
Title: "Test",
Status: StatusClosed,
IssueType: TypeFeature,
ClosedAt: timePtr(time.Now()),
},
expectedStatus: StatusClosed,
expectedType: TypeFeature,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
issue := tt.issue
issue.SetDefaults()
if issue.Status != tt.expectedStatus {
t.Errorf("SetDefaults() Status = %v, want %v", issue.Status, tt.expectedStatus)
}
if issue.IssueType != tt.expectedType {
t.Errorf("SetDefaults() IssueType = %v, want %v", issue.IssueType, tt.expectedType)
}
})
}
}