fix: multiple safety check and sync improvements
- bd-dtm: Changed stderr printing to use SafetyWarnings in worktree.go - bd-ciu: Fixed non-deterministic output order in formatVanishedIssues - bd-dmd: Removed duplicate safety check message in sync.go - bd-k2n: PushSyncBranch now recreates worktree if cleaned up - bd-c5m: Fixed string(rune()) in tests to use strconv.Itoa - bd-8uk: Added test for SafetyWarnings population - bd-1kf: Fixed mergePriority to handle negative priorities - bd-xo9: Documented sync.require_confirmation_on_mass_delete config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -301,8 +302,9 @@ func PullFromSyncBranch(ctx context.Context, repoRoot, syncBranch, jsonlPath str
|
||||
// bd-7ch: Extract local content before merge for safety check
|
||||
localContent, extractErr := extractJSONLFromCommit(ctx, worktreePath, "HEAD", jsonlRelPath)
|
||||
if extractErr != nil {
|
||||
// bd-feh: Log warning so users know safety check may be skipped
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Warning: Could not extract local content for safety check: %v\n", extractErr)
|
||||
// bd-feh: Add warning to result so callers can display appropriately (bd-dtm fix)
|
||||
result.SafetyWarnings = append(result.SafetyWarnings,
|
||||
fmt.Sprintf("⚠️ Warning: Could not extract local content for safety check: %v", extractErr))
|
||||
}
|
||||
|
||||
mergedContent, err := performContentMerge(ctx, worktreePath, syncBranch, remote, jsonlRelPath)
|
||||
@@ -716,9 +718,10 @@ func PushSyncBranch(ctx context.Context, repoRoot, syncBranch string) error {
|
||||
// Worktree path is under .git/beads-worktrees/<branch>
|
||||
worktreePath := filepath.Join(repoRoot, ".git", "beads-worktrees", syncBranch)
|
||||
|
||||
// Verify worktree exists
|
||||
if _, err := os.Stat(worktreePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("sync branch worktree not found at %s", worktreePath)
|
||||
// bd-k2n: Recreate worktree if it was cleaned up, using the same pattern as CommitToSyncBranch
|
||||
wtMgr := git.NewWorktreeManager(repoRoot)
|
||||
if err := wtMgr.CreateBeadsWorktree(syncBranch, worktreePath); err != nil {
|
||||
return fmt.Errorf("failed to ensure worktree exists: %w", err)
|
||||
}
|
||||
|
||||
return pushFromWorktree(ctx, worktreePath, syncBranch)
|
||||
@@ -799,18 +802,23 @@ func formatVanishedIssues(localIssues, mergedIssues map[string]issueSummary, loc
|
||||
lines = append(lines, fmt.Sprintf(" After merge: %d issues", mergedCount))
|
||||
lines = append(lines, " Vanished issues:")
|
||||
|
||||
vanishedCount := 0
|
||||
for id, summary := range localIssues {
|
||||
// bd-ciu: Collect vanished IDs first, then sort for deterministic output
|
||||
var vanishedIDs []string
|
||||
for id := range localIssues {
|
||||
if _, exists := mergedIssues[id]; !exists {
|
||||
vanishedCount++
|
||||
title := summary.Title
|
||||
if len(title) > 60 {
|
||||
title = title[:57] + "..."
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" - %s: %s", id, title))
|
||||
vanishedIDs = append(vanishedIDs, id)
|
||||
}
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" Total vanished: %d\n", vanishedCount))
|
||||
sort.Strings(vanishedIDs)
|
||||
|
||||
for _, id := range vanishedIDs {
|
||||
title := localIssues[id].Title
|
||||
if len(title) > 60 {
|
||||
title = title[:57] + "..."
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" - %s: %s", id, title))
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" Total vanished: %d\n", len(vanishedIDs)))
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -523,9 +524,10 @@ func TestSafetyCheckMassDeletion(t *testing.T) {
|
||||
t.Run("safety check triggers when >50% issues vanish and >5 existed", func(t *testing.T) {
|
||||
// Test the countIssuesInContent and formatVanishedIssues functions
|
||||
// Create local content with 10 issues
|
||||
// bd-c5m: Use strconv.Itoa instead of string(rune()) which only works for single digits
|
||||
var localLines []string
|
||||
for i := 1; i <= 10; i++ {
|
||||
localLines = append(localLines, `{"id":"test-`+string(rune('0'+i))+`","title":"Issue `+string(rune('0'+i))+`"}`)
|
||||
localLines = append(localLines, `{"id":"test-`+strconv.Itoa(i)+`","title":"Issue `+strconv.Itoa(i)+`"}`)
|
||||
}
|
||||
localContent := []byte(strings.Join(localLines, "\n"))
|
||||
|
||||
@@ -561,20 +563,40 @@ func TestSafetyCheckMassDeletion(t *testing.T) {
|
||||
if len(forensicLines) == 0 {
|
||||
t.Error("formatVanishedIssues returned empty lines")
|
||||
}
|
||||
|
||||
// bd-8uk: Verify SafetyWarnings would be populated correctly
|
||||
// Simulate what PullFromSyncBranch does when safety check triggers
|
||||
var safetyWarnings []string
|
||||
safetyWarnings = append(safetyWarnings,
|
||||
"⚠️ Warning: "+strconv.FormatFloat(vanishedPercent, 'f', 0, 64)+"% of issues vanished during merge ("+
|
||||
strconv.Itoa(localCount)+" → "+strconv.Itoa(mergedCount)+" issues)")
|
||||
safetyWarnings = append(safetyWarnings, forensicLines...)
|
||||
|
||||
// Verify warnings contains expected content
|
||||
if len(safetyWarnings) < 2 {
|
||||
t.Errorf("SafetyWarnings should have at least 2 entries (warning + forensics), got %d", len(safetyWarnings))
|
||||
}
|
||||
if !strings.Contains(safetyWarnings[0], "Warning") {
|
||||
t.Error("First SafetyWarning should contain 'Warning'")
|
||||
}
|
||||
if !strings.Contains(safetyWarnings[0], "60%") {
|
||||
t.Errorf("First SafetyWarning should contain '60%%', got: %s", safetyWarnings[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("safety check does NOT trigger when <50% issues vanish", func(t *testing.T) {
|
||||
// 10 issues, 6 remain = 40% vanished (should NOT trigger)
|
||||
// bd-c5m: Use strconv.Itoa instead of string(rune()) which only works for single digits
|
||||
var localLines []string
|
||||
for i := 1; i <= 10; i++ {
|
||||
localLines = append(localLines, `{"id":"test-`+string(rune('0'+i))+`"}`)
|
||||
localLines = append(localLines, `{"id":"test-`+strconv.Itoa(i)+`"}`)
|
||||
}
|
||||
localContent := []byte(strings.Join(localLines, "\n"))
|
||||
|
||||
// 6 issues remain (40% vanished)
|
||||
var mergedLines []string
|
||||
for i := 1; i <= 6; i++ {
|
||||
mergedLines = append(mergedLines, `{"id":"test-`+string(rune('0'+i))+`"}`)
|
||||
mergedLines = append(mergedLines, `{"id":"test-`+strconv.Itoa(i)+`"}`)
|
||||
}
|
||||
mergedContent := []byte(strings.Join(mergedLines, "\n"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user