fix(cleanup): resolve CHECK constraint failure and add tombstone pruning
- Fix bd-tnsq: executeDelete now sets closed_at=NULL when creating tombstones, satisfying the CHECK constraint that requires closed_at IS NULL when status != 'closed' - Fix bd-08ea: cleanup command now also prunes expired tombstones (older than 30 days) after converting closed issues to tombstones - Add regression test for batch deletion of closed issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1076,6 +1076,58 @@ func pruneExpiredTombstones() (*TombstonePruneResult, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// previewPruneTombstones checks what tombstones would be pruned without modifying files.
|
||||
// Used for dry-run mode in cleanup command (bd-08ea).
|
||||
func previewPruneTombstones() (*TombstonePruneResult, error) {
|
||||
beadsDir := filepath.Dir(dbPath)
|
||||
issuesPath := filepath.Join(beadsDir, "issues.jsonl")
|
||||
|
||||
// Check if issues.jsonl exists
|
||||
if _, err := os.Stat(issuesPath); os.IsNotExist(err) {
|
||||
return &TombstonePruneResult{}, nil
|
||||
}
|
||||
|
||||
// Read all issues
|
||||
// nolint:gosec // G304: issuesPath is controlled from beadsDir
|
||||
file, err := os.Open(issuesPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open issues.jsonl: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var allIssues []*types.Issue
|
||||
decoder := json.NewDecoder(file)
|
||||
for {
|
||||
var issue types.Issue
|
||||
if err := decoder.Decode(&issue); err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
break
|
||||
}
|
||||
// Skip corrupt lines
|
||||
continue
|
||||
}
|
||||
allIssues = append(allIssues, &issue)
|
||||
}
|
||||
|
||||
// Determine TTL
|
||||
ttl := types.DefaultTombstoneTTL
|
||||
ttlDays := int(ttl.Hours() / 24)
|
||||
|
||||
// Count expired tombstones
|
||||
var prunedIDs []string
|
||||
for _, issue := range allIssues {
|
||||
if issue.IsExpired(ttl) {
|
||||
prunedIDs = append(prunedIDs, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return &TombstonePruneResult{
|
||||
PrunedCount: len(prunedIDs),
|
||||
PrunedIDs: prunedIDs,
|
||||
TTLDays: ttlDays,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
compactCmd.Flags().BoolVar(&compactDryRun, "dry-run", false, "Preview without compacting")
|
||||
compactCmd.Flags().IntVar(&compactTier, "tier", 1, "Compaction tier (1 or 2)")
|
||||
|
||||
Reference in New Issue
Block a user