refactor: remove all deletions.jsonl code (bd-fom)

Complete removal of the legacy deletions.jsonl manifest system.
Tombstones are now the sole deletion mechanism.

Removed:
- internal/deletions/ - entire package
- cmd/bd/deleted.go - deleted command
- cmd/bd/doctor/fix/deletions.go - HydrateDeletionsManifest
- Tests for all removed functionality

Cleaned:
- cmd/bd/sync.go - removed sanitize, auto-compact
- cmd/bd/delete.go - removed dual-writes
- cmd/bd/doctor.go - removed checkDeletionsManifest
- internal/importer/importer.go - removed deletions checks
- internal/syncbranch/worktree.go - removed deletions merge
- cmd/bd/integrity.go - updated validation (warn-only on decrease)

Files removed: 12
Lines removed: ~7500

🤖 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-16 14:20:32 -08:00
parent e0528de590
commit 9f76cfda01
32 changed files with 298 additions and 7534 deletions

View File

@@ -122,9 +122,9 @@ func CommitToSyncBranch(ctx context.Context, repoRoot, syncBranch, jsonlPath str
return nil, fmt.Errorf("failed to sync JSONL to worktree: %w", err)
}
// Also sync other beads files (deletions.jsonl, metadata.json)
// Also sync other beads files (metadata.json)
beadsDir := filepath.Dir(jsonlPath)
for _, filename := range []string{"deletions.jsonl", "metadata.json"} {
for _, filename := range []string{"metadata.json"} {
srcPath := filepath.Join(beadsDir, filename)
if _, err := os.Stat(srcPath); err == nil {
relPath, err := filepath.Rel(repoRoot, srcPath)
@@ -326,12 +326,6 @@ func PullFromSyncBranch(ctx context.Context, repoRoot, syncBranch, jsonlPath str
return nil, fmt.Errorf("content merge failed: %w", err)
}
// Also merge deletions.jsonl if it exists
beadsRelDir := filepath.Dir(jsonlRelPath)
deletionsRelPath := filepath.Join(beadsRelDir, "deletions.jsonl")
mergedDeletions, deletionsErr := performDeletionsMerge(ctx, worktreePath, syncBranch, remote, deletionsRelPath)
// deletionsErr is non-fatal - file might not exist
// Reset worktree to remote's history (adopt their commit graph)
resetCmd := exec.CommandContext(ctx, "git", "-C", worktreePath, "reset", "--hard",
fmt.Sprintf("%s/%s", remote, syncBranch))
@@ -348,15 +342,6 @@ func PullFromSyncBranch(ctx context.Context, repoRoot, syncBranch, jsonlPath str
return nil, fmt.Errorf("failed to write merged JSONL: %w", err)
}
// Write merged deletions if we have them
if deletionsErr == nil && len(mergedDeletions) > 0 {
deletionsPath := filepath.Join(worktreePath, deletionsRelPath)
if err := os.WriteFile(deletionsPath, mergedDeletions, 0600); err != nil {
// Non-fatal - deletions are supplementary
_ = err
}
}
// Check if merge produced any changes from remote
hasChanges, err := hasChangesInWorktree(ctx, worktreePath, worktreeJSONLPath)
if err != nil {
@@ -679,62 +664,6 @@ func extractJSONLFromCommit(ctx context.Context, worktreePath, commit, filePath
return output, nil
}
// performDeletionsMerge merges deletions.jsonl from local and remote.
// Deletions are merged by union - we keep all deletion records from both sides.
// This ensures that if either side deleted an issue, it stays deleted.
func performDeletionsMerge(ctx context.Context, worktreePath, branch, remote, deletionsRelPath string) ([]byte, error) {
// Extract local deletions
localDeletions, localErr := extractJSONLFromCommit(ctx, worktreePath, "HEAD", deletionsRelPath)
// Extract remote deletions
remoteRef := fmt.Sprintf("%s/%s", remote, branch)
remoteDeletions, remoteErr := extractJSONLFromCommit(ctx, worktreePath, remoteRef, deletionsRelPath)
// If neither exists, nothing to merge
if localErr != nil && remoteErr != nil {
return nil, fmt.Errorf("no deletions files to merge")
}
// If only one exists, use that
if localErr != nil {
return remoteDeletions, nil
}
if remoteErr != nil {
return localDeletions, nil
}
// Both exist - merge by taking union of lines (deduplicated)
// Each line in deletions.jsonl is a JSON object with an "id" field
seen := make(map[string]bool)
var merged []byte
// Process local deletions
for _, line := range strings.Split(string(localDeletions), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if !seen[line] {
seen[line] = true
merged = append(merged, []byte(line+"\n")...)
}
}
// Process remote deletions
for _, line := range strings.Split(string(remoteDeletions), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if !seen[line] {
seen[line] = true
merged = append(merged, []byte(line+"\n")...)
}
}
return merged, nil
}
// copyJSONLToMainRepo copies JSONL and related files from worktree to main repo.
func copyJSONLToMainRepo(worktreePath, jsonlRelPath, jsonlPath string) error {
worktreeJSONLPath := filepath.Join(worktreePath, jsonlRelPath)
@@ -755,10 +684,10 @@ func copyJSONLToMainRepo(worktreePath, jsonlRelPath, jsonlPath string) error {
return fmt.Errorf("failed to write main JSONL: %w", err)
}
// Also sync other beads files back (deletions.jsonl, metadata.json)
// Also sync other beads files back (metadata.json)
beadsDir := filepath.Dir(jsonlPath)
worktreeBeadsDir := filepath.Dir(worktreeJSONLPath)
for _, filename := range []string{"deletions.jsonl", "metadata.json"} {
for _, filename := range []string{"metadata.json"} {
worktreeSrcPath := filepath.Join(worktreeBeadsDir, filename)
if fileData, err := os.ReadFile(worktreeSrcPath); err == nil {
dstPath := filepath.Join(beadsDir, filename)

View File

@@ -313,96 +313,6 @@ func TestPerformContentMerge(t *testing.T) {
})
}
// TestPerformDeletionsMerge tests the deletions merge function
func TestPerformDeletionsMerge(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
ctx := context.Background()
t.Run("merges deletions from both sides", func(t *testing.T) {
repoDir := setupTestRepo(t)
defer os.RemoveAll(repoDir)
runGit(t, repoDir, "checkout", "-b", "test-branch")
// Base: no deletions
writeFile(t, filepath.Join(repoDir, ".beads", "issues.jsonl"), `{"id":"test-1"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "base commit")
baseCommit := strings.TrimSpace(getGitOutput(t, repoDir, "rev-parse", "HEAD"))
// Local: delete issue-A
writeFile(t, filepath.Join(repoDir, ".beads", "deletions.jsonl"), `{"id":"issue-A","deleted_at":"2024-01-01T00:00:00Z"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "local deletion")
localHead := strings.TrimSpace(getGitOutput(t, repoDir, "rev-parse", "HEAD"))
// Remote: delete issue-B
runGit(t, repoDir, "checkout", baseCommit)
writeFile(t, filepath.Join(repoDir, ".beads", "deletions.jsonl"), `{"id":"issue-B","deleted_at":"2024-01-02T00:00:00Z"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "remote deletion")
runGit(t, repoDir, "update-ref", "refs/remotes/origin/test-branch", "HEAD")
// Go back to local
runGit(t, repoDir, "checkout", "-B", "test-branch", localHead)
// Perform merge
merged, err := performDeletionsMerge(ctx, repoDir, "test-branch", "origin", ".beads/deletions.jsonl")
if err != nil {
t.Fatalf("performDeletionsMerge() error = %v", err)
}
// Both deletions should be present
mergedStr := string(merged)
if !strings.Contains(mergedStr, "issue-A") {
t.Error("merged deletions missing issue-A")
}
if !strings.Contains(mergedStr, "issue-B") {
t.Error("merged deletions missing issue-B")
}
})
t.Run("handles only local deletions", func(t *testing.T) {
repoDir := setupTestRepo(t)
defer os.RemoveAll(repoDir)
runGit(t, repoDir, "checkout", "-b", "test-branch")
// Base: no deletions
writeFile(t, filepath.Join(repoDir, ".beads", "issues.jsonl"), `{"id":"test-1"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "base commit")
baseCommit := strings.TrimSpace(getGitOutput(t, repoDir, "rev-parse", "HEAD"))
// Local: has deletions
writeFile(t, filepath.Join(repoDir, ".beads", "deletions.jsonl"), `{"id":"issue-A"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "local deletion")
localHead := strings.TrimSpace(getGitOutput(t, repoDir, "rev-parse", "HEAD"))
// Remote: no deletions file
runGit(t, repoDir, "checkout", baseCommit)
runGit(t, repoDir, "update-ref", "refs/remotes/origin/test-branch", "HEAD")
// Go back to local
runGit(t, repoDir, "checkout", "-B", "test-branch", localHead)
// Perform merge
merged, err := performDeletionsMerge(ctx, repoDir, "test-branch", "origin", ".beads/deletions.jsonl")
if err != nil {
t.Fatalf("performDeletionsMerge() error = %v", err)
}
// Local deletions should be present
if !strings.Contains(string(merged), "issue-A") {
t.Error("merged deletions missing issue-A")
}
})
}
// Helper functions
func setupTestRepo(t *testing.T) string {