- Handle ignored errors with explicit _ assignment (errcheck) - Add #nosec comments for false positive G304/G204 warnings (gosec) - Fix misspelling: cancelled -> canceled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
231 lines
7.1 KiB
Go
231 lines
7.1 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/fatih/color"
|
||
"github.com/spf13/cobra"
|
||
"github.com/steveyegge/beads/internal/beads"
|
||
"github.com/steveyegge/beads/internal/reset"
|
||
)
|
||
|
||
var resetCmd = &cobra.Command{
|
||
Use: "reset",
|
||
Short: "Reset beads to a clean starting state",
|
||
Long: `Reset beads to a clean starting state by clearing .beads/ and reinitializing.
|
||
|
||
This command is useful when:
|
||
- Your beads workspace is in an invalid state after an update
|
||
- You want to start fresh with issue tracking
|
||
- bd doctor cannot automatically fix problems
|
||
|
||
RESET MODES:
|
||
|
||
Soft Reset (default):
|
||
- Kills all daemons
|
||
- Clears .beads/ directory
|
||
- Reinitializes with bd init
|
||
- Git history is unchanged
|
||
|
||
Hard Reset (--hard):
|
||
- Same as soft reset, plus:
|
||
- Removes .beads/ files from git (git rm)
|
||
- Creates a commit removing the old state
|
||
- Creates a commit with fresh initialized state
|
||
|
||
OPTIONS:
|
||
|
||
--backup Create .beads-backup-{timestamp}/ before clearing
|
||
--dry-run Preview what would happen without making changes
|
||
--force Skip confirmation prompt
|
||
--hard Include git operations (git rm + commit)
|
||
--skip-init Don't reinitialize after clearing (leaves .beads/ empty)
|
||
--verbose Show detailed progress
|
||
|
||
EXAMPLES:
|
||
|
||
bd reset # Reset with confirmation prompt
|
||
bd reset --backup # Reset with backup first
|
||
bd reset --dry-run # Preview the impact
|
||
bd reset --hard # Reset including git history
|
||
bd reset --force # Reset without confirmation`,
|
||
Run: func(cmd *cobra.Command, _ []string) {
|
||
hard, _ := cmd.Flags().GetBool("hard")
|
||
force, _ := cmd.Flags().GetBool("force")
|
||
backup, _ := cmd.Flags().GetBool("backup")
|
||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||
skipInit, _ := cmd.Flags().GetBool("skip-init")
|
||
verbose, _ := cmd.Flags().GetBool("verbose")
|
||
|
||
// Color helpers
|
||
red := color.New(color.FgRed).SprintFunc()
|
||
yellow := color.New(color.FgYellow).SprintFunc()
|
||
green := color.New(color.FgGreen).SprintFunc()
|
||
cyan := color.New(color.FgCyan).SprintFunc()
|
||
|
||
// Validate state
|
||
if err := reset.ValidateState(); err != nil {
|
||
fmt.Fprintf(os.Stderr, "%s %v\n", red("Error:"), err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Get impact summary
|
||
impact, err := reset.CountImpact()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "%s Failed to analyze workspace: %v\n", red("Error:"), err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Show impact summary
|
||
fmt.Printf("\n%s Reset Impact Summary\n", yellow("⚠"))
|
||
fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
||
|
||
totalIssues := impact.IssueCount - impact.TombstoneCount
|
||
if totalIssues > 0 {
|
||
fmt.Printf(" Issues to delete: %s\n", cyan(fmt.Sprintf("%d", totalIssues)))
|
||
fmt.Printf(" - Open: %d\n", impact.OpenCount)
|
||
fmt.Printf(" - Closed: %d\n", impact.ClosedCount)
|
||
} else {
|
||
fmt.Printf(" Issues to delete: %s\n", cyan("0"))
|
||
}
|
||
|
||
if impact.TombstoneCount > 0 {
|
||
fmt.Printf(" Tombstones to delete: %s\n", cyan(fmt.Sprintf("%d", impact.TombstoneCount)))
|
||
}
|
||
|
||
if impact.HasUncommitted {
|
||
fmt.Printf(" %s Uncommitted changes in .beads/ will be lost\n", yellow("⚠"))
|
||
}
|
||
|
||
fmt.Printf("\n")
|
||
|
||
// Show what will happen
|
||
fmt.Printf("Actions:\n")
|
||
if backup {
|
||
fmt.Printf(" 1. Create backup (.beads-backup-{timestamp}/)\n")
|
||
}
|
||
fmt.Printf(" %s. Kill all daemons\n", actionNumber(backup, 1))
|
||
if hard {
|
||
fmt.Printf(" %s. Remove .beads/ from git index and commit\n", actionNumber(backup, 2))
|
||
}
|
||
fmt.Printf(" %s. Delete .beads/ directory\n", actionNumber(backup, hardOffset(hard, 2)))
|
||
if !skipInit {
|
||
fmt.Printf(" %s. Reinitialize workspace (bd init)\n", actionNumber(backup, hardOffset(hard, 3)))
|
||
if hard {
|
||
fmt.Printf(" %s. Commit fresh state to git\n", actionNumber(backup, hardOffset(hard, 4)))
|
||
}
|
||
}
|
||
fmt.Printf("\n")
|
||
|
||
// Dry run - stop here
|
||
if dryRun {
|
||
fmt.Printf("%s This was a dry run. No changes were made.\n\n", cyan("ℹ"))
|
||
return
|
||
}
|
||
|
||
// Confirmation prompt (unless --force)
|
||
if !force {
|
||
fmt.Printf("%s This will permanently delete all issues. Continue? [y/N]: ", yellow("Warning:"))
|
||
|
||
reader := bufio.NewReader(os.Stdin)
|
||
response, err := reader.ReadString('\n')
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "%s Failed to read response: %v\n", red("Error:"), err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
response = strings.TrimSpace(strings.ToLower(response))
|
||
if response != "y" && response != "yes" {
|
||
fmt.Printf("Reset canceled.\n")
|
||
return
|
||
}
|
||
}
|
||
|
||
// Execute reset
|
||
if verbose {
|
||
fmt.Printf("\n%s Starting reset...\n", cyan("→"))
|
||
}
|
||
|
||
opts := reset.ResetOptions{
|
||
Hard: hard,
|
||
Backup: backup,
|
||
DryRun: false, // Already handled above
|
||
SkipInit: skipInit,
|
||
}
|
||
|
||
result, err := reset.Reset(opts)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "%s Reset failed: %v\n", red("Error:"), err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Handle --hard mode: commit fresh state after reinit
|
||
if hard && !skipInit {
|
||
beadsDir := beads.FindBeadsDir()
|
||
if beadsDir != "" {
|
||
commitMsg := "Initialize fresh beads workspace\n\nCreated new .beads/ directory after reset."
|
||
if err := reset.GitAddAndCommit(beadsDir, commitMsg); err != nil {
|
||
fmt.Fprintf(os.Stderr, "%s Failed to commit fresh state: %v\n", yellow("Warning:"), err)
|
||
fmt.Fprintf(os.Stderr, "You may need to manually run: git add .beads && git commit -m \"Fresh beads state\"\n")
|
||
} else if verbose {
|
||
fmt.Printf(" %s Committed fresh state to git\n", green("✓"))
|
||
}
|
||
}
|
||
}
|
||
|
||
// Show results
|
||
fmt.Printf("\n%s Reset complete!\n\n", green("✓"))
|
||
|
||
if result.BackupPath != "" {
|
||
fmt.Printf(" Backup created: %s\n", cyan(result.BackupPath))
|
||
}
|
||
|
||
if result.DaemonsKilled > 0 {
|
||
fmt.Printf(" Daemons stopped: %d\n", result.DaemonsKilled)
|
||
}
|
||
|
||
if result.IssuesDeleted > 0 || result.TombstonesDeleted > 0 {
|
||
fmt.Printf(" Issues deleted: %d\n", result.IssuesDeleted)
|
||
if result.TombstonesDeleted > 0 {
|
||
fmt.Printf(" Tombstones deleted: %d\n", result.TombstonesDeleted)
|
||
}
|
||
}
|
||
|
||
if !skipInit {
|
||
fmt.Printf("\n Workspace reinitialized. Run %s to get started.\n", cyan("bd quickstart"))
|
||
} else {
|
||
fmt.Printf("\n .beads/ directory has been cleared. Run %s to reinitialize.\n", cyan("bd init"))
|
||
}
|
||
fmt.Printf("\n")
|
||
},
|
||
}
|
||
|
||
// actionNumber returns the step number accounting for backup offset
|
||
func actionNumber(hasBackup bool, step int) string {
|
||
if hasBackup {
|
||
return fmt.Sprintf("%d", step+1)
|
||
}
|
||
return fmt.Sprintf("%d", step)
|
||
}
|
||
|
||
// hardOffset adjusts step number for --hard mode which adds extra steps
|
||
func hardOffset(isHard bool, step int) int {
|
||
if isHard {
|
||
return step + 1
|
||
}
|
||
return step
|
||
}
|
||
|
||
func init() {
|
||
resetCmd.Flags().Bool("hard", false, "Include git operations (git rm + commit)")
|
||
resetCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||
resetCmd.Flags().Bool("backup", false, "Create backup before reset")
|
||
resetCmd.Flags().Bool("dry-run", false, "Preview what would happen without making changes")
|
||
resetCmd.Flags().Bool("skip-init", false, "Don't reinitialize after clearing")
|
||
resetCmd.Flags().BoolP("verbose", "v", false, "Show detailed progress")
|
||
rootCmd.AddCommand(resetCmd)
|
||
}
|