feat(deletions): add pruning and git history fallback
Implements two P1 tasks for the deletions manifest epic: bd-v2x: Add deletions pruning to bd compact - PruneDeletions function removes records older than retention period - Default retention: 7 days (configurable via metadata.json) - CLI --retention flag for override - Atomic file rewrite prevents corruption - Called automatically during all compact operations bd-pnm: Add git history fallback for pruned deletions - Catches deletions where manifest entry was pruned - Uses git log -S to search for ID in JSONL history - Batches multiple IDs for efficiency (git -G regex) - Self-healing: backfills manifest on hit - Conservative: keeps issue if git check fails (shallow clone) Tests added for both features with edge cases covered. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -179,3 +179,54 @@ func WriteDeletions(path string, records []DeletionRecord) error {
|
||||
func DefaultPath(beadsDir string) string {
|
||||
return filepath.Join(beadsDir, "deletions.jsonl")
|
||||
}
|
||||
|
||||
// DefaultRetentionDays is the default number of days to retain deletion records.
|
||||
const DefaultRetentionDays = 7
|
||||
|
||||
// PruneResult contains the result of a prune operation.
|
||||
type PruneResult struct {
|
||||
KeptCount int
|
||||
PrunedCount int
|
||||
PrunedIDs []string
|
||||
}
|
||||
|
||||
// PruneDeletions removes deletion records older than the specified retention period.
|
||||
// Returns PruneResult with counts and IDs of pruned records.
|
||||
// If the file doesn't exist or is empty, returns zero counts with no error.
|
||||
func PruneDeletions(path string, retentionDays int) (*PruneResult, error) {
|
||||
result := &PruneResult{
|
||||
PrunedIDs: []string{},
|
||||
}
|
||||
|
||||
loadResult, err := LoadDeletions(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load deletions: %w", err)
|
||||
}
|
||||
|
||||
if len(loadResult.Records) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
cutoff := time.Now().AddDate(0, 0, -retentionDays)
|
||||
var kept []DeletionRecord
|
||||
|
||||
for _, record := range loadResult.Records {
|
||||
if record.Timestamp.After(cutoff) || record.Timestamp.Equal(cutoff) {
|
||||
kept = append(kept, record)
|
||||
} else {
|
||||
result.PrunedCount++
|
||||
result.PrunedIDs = append(result.PrunedIDs, record.ID)
|
||||
}
|
||||
}
|
||||
|
||||
result.KeptCount = len(kept)
|
||||
|
||||
// Only rewrite if we actually pruned something
|
||||
if result.PrunedCount > 0 {
|
||||
if err := WriteDeletions(path, kept); err != nil {
|
||||
return nil, fmt.Errorf("failed to write pruned deletions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user