bd sync: 2025-12-23 23:38:57
This commit is contained in:
@@ -166,8 +166,7 @@ Examples:
|
||||
} else {
|
||||
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error: compact requires SQLite storage\n")
|
||||
os.Exit(1)
|
||||
FatalError("compact requires SQLite storage")
|
||||
}
|
||||
runCompactStats(ctx, sqliteStore)
|
||||
}
|
||||
@@ -188,26 +187,20 @@ Examples:
|
||||
|
||||
// Check for exactly one mode
|
||||
if activeModes == 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error: must specify one mode: --analyze, --apply, or --auto\n")
|
||||
os.Exit(1)
|
||||
FatalError("must specify one mode: --analyze, --apply, or --auto")
|
||||
}
|
||||
if activeModes > 1 {
|
||||
fmt.Fprintf(os.Stderr, "Error: cannot use multiple modes together (--analyze, --apply, --auto are mutually exclusive)\n")
|
||||
os.Exit(1)
|
||||
FatalError("cannot use multiple modes together (--analyze, --apply, --auto are mutually exclusive)")
|
||||
}
|
||||
|
||||
// Handle analyze mode (requires direct database access)
|
||||
if compactAnalyze {
|
||||
if err := ensureDirectMode("compact --analyze requires direct database access"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Hint: Use --no-daemon flag to bypass daemon and access database directly\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint(fmt.Sprintf("%v", err), "Use --no-daemon flag to bypass daemon and access database directly")
|
||||
}
|
||||
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to open database in direct mode\n")
|
||||
fmt.Fprintf(os.Stderr, "Hint: Ensure .beads/beads.db exists and is readable\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint("failed to open database in direct mode", "Ensure .beads/beads.db exists and is readable")
|
||||
}
|
||||
runCompactAnalyze(ctx, sqliteStore)
|
||||
return
|
||||
@@ -216,23 +209,17 @@ Examples:
|
||||
// Handle apply mode (requires direct database access)
|
||||
if compactApply {
|
||||
if err := ensureDirectMode("compact --apply requires direct database access"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Hint: Use --no-daemon flag to bypass daemon and access database directly\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint(fmt.Sprintf("%v", err), "Use --no-daemon flag to bypass daemon and access database directly")
|
||||
}
|
||||
if compactID == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --apply requires --id\n")
|
||||
os.Exit(1)
|
||||
FatalError("--apply requires --id")
|
||||
}
|
||||
if compactSummary == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --apply requires --summary\n")
|
||||
os.Exit(1)
|
||||
FatalError("--apply requires --summary")
|
||||
}
|
||||
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to open database in direct mode\n")
|
||||
fmt.Fprintf(os.Stderr, "Hint: Ensure .beads/beads.db exists and is readable\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint("failed to open database in direct mode", "Ensure .beads/beads.db exists and is readable")
|
||||
}
|
||||
runCompactApply(ctx, sqliteStore)
|
||||
return
|
||||
@@ -248,16 +235,13 @@ Examples:
|
||||
|
||||
// Validation checks
|
||||
if compactID != "" && compactAll {
|
||||
fmt.Fprintf(os.Stderr, "Error: cannot use --id and --all together\n")
|
||||
os.Exit(1)
|
||||
FatalError("cannot use --id and --all together")
|
||||
}
|
||||
if compactForce && compactID == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --force requires --id\n")
|
||||
os.Exit(1)
|
||||
FatalError("--force requires --id")
|
||||
}
|
||||
if compactID == "" && !compactAll && !compactDryRun {
|
||||
fmt.Fprintf(os.Stderr, "Error: must specify --all, --id, or --dry-run\n")
|
||||
os.Exit(1)
|
||||
FatalError("must specify --all, --id, or --dry-run")
|
||||
}
|
||||
|
||||
// Use RPC if daemon available, otherwise direct mode
|
||||
@@ -269,14 +253,12 @@ Examples:
|
||||
// Fallback to direct mode
|
||||
apiKey := os.Getenv("ANTHROPIC_API_KEY")
|
||||
if apiKey == "" && !compactDryRun {
|
||||
fmt.Fprintf(os.Stderr, "Error: --auto mode requires ANTHROPIC_API_KEY environment variable\n")
|
||||
os.Exit(1)
|
||||
FatalError("--auto mode requires ANTHROPIC_API_KEY environment variable")
|
||||
}
|
||||
|
||||
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error: compact requires SQLite storage\n")
|
||||
os.Exit(1)
|
||||
FatalError("compact requires SQLite storage")
|
||||
}
|
||||
|
||||
config := &compact.Config{
|
||||
@@ -289,8 +271,7 @@ Examples:
|
||||
|
||||
compactor, err := compact.New(sqliteStore, apiKey, config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to create compactor: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to create compactor: %v", err)
|
||||
}
|
||||
|
||||
if compactID != "" {
|
||||
@@ -309,19 +290,16 @@ func runCompactSingle(ctx context.Context, compactor *compact.Compactor, store *
|
||||
if !compactForce {
|
||||
eligible, reason, err := store.CheckEligibility(ctx, issueID, compactTier)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to check eligibility: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to check eligibility: %v", err)
|
||||
}
|
||||
if !eligible {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s is not eligible for Tier %d compaction: %s\n", issueID, compactTier, reason)
|
||||
os.Exit(1)
|
||||
FatalError("%s is not eligible for Tier %d compaction: %s", issueID, compactTier, reason)
|
||||
}
|
||||
}
|
||||
|
||||
issue, err := store.GetIssue(ctx, issueID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get issue: %v", err)
|
||||
}
|
||||
|
||||
originalSize := len(issue.Description) + len(issue.Design) + len(issue.Notes) + len(issue.AcceptanceCriteria)
|
||||
@@ -349,19 +327,16 @@ func runCompactSingle(ctx context.Context, compactor *compact.Compactor, store *
|
||||
if compactTier == 1 {
|
||||
compactErr = compactor.CompactTier1(ctx, issueID)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: Tier 2 compaction not yet implemented\n")
|
||||
os.Exit(1)
|
||||
FatalError("Tier 2 compaction not yet implemented")
|
||||
}
|
||||
|
||||
if compactErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", compactErr)
|
||||
os.Exit(1)
|
||||
FatalError("%v", compactErr)
|
||||
}
|
||||
|
||||
issue, err = store.GetIssue(ctx, issueID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get updated issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get updated issue: %v", err)
|
||||
}
|
||||
|
||||
compactedSize := len(issue.Description)
|
||||
@@ -407,8 +382,7 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
|
||||
if compactTier == 1 {
|
||||
tier1, err := store.GetTier1Candidates(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get candidates: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get candidates: %v", err)
|
||||
}
|
||||
for _, c := range tier1 {
|
||||
candidates = append(candidates, c.IssueID)
|
||||
@@ -416,8 +390,7 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
|
||||
} else {
|
||||
tier2, err := store.GetTier2Candidates(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get candidates: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get candidates: %v", err)
|
||||
}
|
||||
for _, c := range tier2 {
|
||||
candidates = append(candidates, c.IssueID)
|
||||
@@ -471,8 +444,7 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
|
||||
|
||||
results, err := compactor.CompactTier1Batch(ctx, candidates)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: batch compaction failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("batch compaction failed: %v", err)
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
@@ -535,14 +507,12 @@ func runCompactAll(ctx context.Context, compactor *compact.Compactor, store *sql
|
||||
func runCompactStats(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
tier1, err := store.GetTier1Candidates(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get Tier 1 candidates: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get Tier 1 candidates: %v", err)
|
||||
}
|
||||
|
||||
tier2, err := store.GetTier2Candidates(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get Tier 2 candidates: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get Tier 2 candidates: %v", err)
|
||||
}
|
||||
|
||||
tier1Size := 0
|
||||
@@ -608,24 +578,20 @@ func progressBar(current, total int) string {
|
||||
//nolint:unparam // ctx may be used in future for cancellation
|
||||
func runCompactRPC(_ context.Context) {
|
||||
if compactID != "" && compactAll {
|
||||
fmt.Fprintf(os.Stderr, "Error: cannot use --id and --all together\n")
|
||||
os.Exit(1)
|
||||
FatalError("cannot use --id and --all together")
|
||||
}
|
||||
|
||||
if compactForce && compactID == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --force requires --id\n")
|
||||
os.Exit(1)
|
||||
FatalError("--force requires --id")
|
||||
}
|
||||
|
||||
if compactID == "" && !compactAll && !compactDryRun {
|
||||
fmt.Fprintf(os.Stderr, "Error: must specify --all, --id, or --dry-run\n")
|
||||
os.Exit(1)
|
||||
FatalError("must specify --all, --id, or --dry-run")
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("ANTHROPIC_API_KEY")
|
||||
if apiKey == "" && !compactDryRun {
|
||||
fmt.Fprintf(os.Stderr, "Error: ANTHROPIC_API_KEY environment variable not set\n")
|
||||
os.Exit(1)
|
||||
FatalError("ANTHROPIC_API_KEY environment variable not set")
|
||||
}
|
||||
|
||||
args := map[string]interface{}{
|
||||
@@ -643,13 +609,11 @@ func runCompactRPC(_ context.Context) {
|
||||
|
||||
resp, err := daemonClient.Execute("compact", args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("%v", err)
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
FatalError("%s", resp.Error)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
@@ -676,8 +640,7 @@ func runCompactRPC(_ context.Context) {
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Data, &result); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("parsing response: %v", err)
|
||||
}
|
||||
|
||||
if compactID != "" {
|
||||
@@ -722,13 +685,11 @@ func runCompactStatsRPC() {
|
||||
|
||||
resp, err := daemonClient.Execute("compact_stats", args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("%v", err)
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Error)
|
||||
os.Exit(1)
|
||||
FatalError("%s", resp.Error)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
@@ -749,8 +710,7 @@ func runCompactStatsRPC() {
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Data, &result); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("parsing response: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nCompaction Statistics\n")
|
||||
@@ -784,8 +744,7 @@ func runCompactAnalyze(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
if compactID != "" {
|
||||
issue, err := store.GetIssue(ctx, compactID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get issue: %v", err)
|
||||
}
|
||||
|
||||
sizeBytes := len(issue.Description) + len(issue.Design) + len(issue.Notes) + len(issue.AcceptanceCriteria)
|
||||
@@ -816,8 +775,7 @@ func runCompactAnalyze(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
tierCandidates, err = store.GetTier2Candidates(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get candidates: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get candidates: %v", err)
|
||||
}
|
||||
|
||||
// Apply limit if specified
|
||||
@@ -879,15 +837,13 @@ func runCompactApply(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
// Read from stdin
|
||||
summaryBytes, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to read summary from stdin: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to read summary from stdin: %v", err)
|
||||
}
|
||||
} else {
|
||||
// #nosec G304 -- summary file path provided explicitly by operator
|
||||
summaryBytes, err = os.ReadFile(compactSummary)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to read summary file: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to read summary file: %v", err)
|
||||
}
|
||||
}
|
||||
summary := string(summaryBytes)
|
||||
@@ -895,8 +851,7 @@ func runCompactApply(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
// Get issue
|
||||
issue, err := store.GetIssue(ctx, compactID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to get issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to get issue: %v", err)
|
||||
}
|
||||
|
||||
// Calculate sizes
|
||||
@@ -907,20 +862,15 @@ func runCompactApply(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
if !compactForce {
|
||||
eligible, reason, err := store.CheckEligibility(ctx, compactID, compactTier)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to check eligibility: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to check eligibility: %v", err)
|
||||
}
|
||||
if !eligible {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s is not eligible for Tier %d compaction: %s\n", compactID, compactTier, reason)
|
||||
fmt.Fprintf(os.Stderr, "Hint: use --force to bypass eligibility checks\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint(fmt.Sprintf("%s is not eligible for Tier %d compaction: %s", compactID, compactTier, reason), "use --force to bypass eligibility checks")
|
||||
}
|
||||
|
||||
// Enforce size reduction unless --force
|
||||
if compactedSize >= originalSize {
|
||||
fmt.Fprintf(os.Stderr, "Error: summary (%d bytes) is not shorter than original (%d bytes)\n", compactedSize, originalSize)
|
||||
fmt.Fprintf(os.Stderr, "Hint: use --force to bypass size validation\n")
|
||||
os.Exit(1)
|
||||
FatalErrorWithHint(fmt.Sprintf("summary (%d bytes) is not shorter than original (%d bytes)", compactedSize, originalSize), "use --force to bypass size validation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,27 +888,23 @@ func runCompactApply(ctx context.Context, store *sqlite.SQLiteStorage) {
|
||||
}
|
||||
|
||||
if err := store.UpdateIssue(ctx, compactID, updates, actor); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to update issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to update issue: %v", err)
|
||||
}
|
||||
|
||||
commitHash := compact.GetCurrentCommitHash()
|
||||
if err := store.ApplyCompaction(ctx, compactID, compactTier, originalSize, compactedSize, commitHash); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to apply compaction: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to apply compaction: %v", err)
|
||||
}
|
||||
|
||||
savingBytes := originalSize - compactedSize
|
||||
reductionPct := float64(savingBytes) / float64(originalSize) * 100
|
||||
eventData := fmt.Sprintf("Tier %d compaction: %d → %d bytes (saved %d, %.1f%%)", compactTier, originalSize, compactedSize, savingBytes, reductionPct)
|
||||
if err := store.AddComment(ctx, compactID, actor, eventData); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to record event: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to record event: %v", err)
|
||||
}
|
||||
|
||||
if err := store.MarkIssueDirty(ctx, compactID); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: failed to mark dirty: %v\n", err)
|
||||
os.Exit(1)
|
||||
FatalError("failed to mark dirty: %v", err)
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
Reference in New Issue
Block a user