Files
beads/cmd/bd/init.go
Steve Yegge 7f82708b90 Fix bd init to auto-import issues from git on fresh clone
Prevents agents from creating duplicate low-numbered issues when starting
with a fresh git clone that already has issues.jsonl in git history.

Changes:
- bd init now checks for existing issues in git after DB creation
- Auto-imports with collision resolution if found
- Updates AGENTS.md to simplify onboarding (just 'bd init')

Fixes the scenario where:
1. Fresh clone has .beads/issues.jsonl in git (212 issues)
2. Agent runs bd init (creates empty DB)
3. Agent starts creating bd-1, bd-2, etc (collisions with git)

Now bd init automatically imports all issues from git on first run.

Amp-Thread-ID: https://ampcode.com/threads/T-8a41f14d-d4c3-4c50-a18b-5f112110f138
Co-authored-by: Amp <amp@ampcode.com>
2025-10-21 20:38:35 -07:00

135 lines
3.8 KiB
Go

package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/storage/sqlite"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize bd in the current directory",
Long: `Initialize bd in the current directory by creating a .beads/ directory
and database file. Optionally specify a custom issue prefix.`,
Run: func(cmd *cobra.Command, args []string) {
prefix, _ := cmd.Flags().GetString("prefix")
quiet, _ := cmd.Flags().GetBool("quiet")
if prefix == "" {
// Auto-detect from directory name
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to get current directory: %v\n", err)
os.Exit(1)
}
prefix = filepath.Base(cwd)
}
// Normalize prefix: strip trailing hyphens
// The hyphen is added automatically during ID generation
prefix = strings.TrimRight(prefix, "-")
// Create .beads directory
beadsDir := ".beads"
if err := os.MkdirAll(beadsDir, 0750); err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to create %s directory: %v\n", beadsDir, err)
os.Exit(1)
}
// Create .gitignore in .beads directory
gitignorePath := filepath.Join(beadsDir, ".gitignore")
gitignoreContent := `# SQLite databases
*.db
*.db-journal
*.db-wal
*.db-shm
# Daemon runtime files
daemon.log
daemon.pid
bd.sock
# Legacy database files
db.sqlite
bd.db
# Keep JSONL exports (source of truth for git)
!*.jsonl
`
if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0644); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to create .gitignore: %v\n", err)
// Non-fatal - continue anyway
}
// Create database
dbPath := filepath.Join(beadsDir, prefix+".db")
store, err := sqlite.New(dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to create database: %v\n", err)
os.Exit(1)
}
// Set the issue prefix in config
ctx := context.Background()
if err := store.SetConfig(ctx, "issue_prefix", prefix); err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to set issue prefix: %v\n", err)
_ = store.Close()
os.Exit(1)
}
// Store the bd version in metadata (for version mismatch detection)
if err := store.SetMetadata(ctx, "bd_version", Version); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to store version metadata: %v\n", err)
// Non-fatal - continue anyway
}
// Check if git has existing issues to import (fresh clone scenario)
issueCount, jsonlPath := checkGitForIssues()
if issueCount > 0 {
if !quiet {
fmt.Fprintf(os.Stderr, "\n✓ Database initialized. Found %d issues in git, importing...\n", issueCount)
}
if err := importFromGit(ctx, dbPath, store, jsonlPath); err != nil {
if !quiet {
fmt.Fprintf(os.Stderr, "Warning: auto-import failed: %v\n", err)
fmt.Fprintf(os.Stderr, "Try manually: git show HEAD:%s | bd import -i /dev/stdin\n", jsonlPath)
}
// Non-fatal - continue with empty database
} else if !quiet {
fmt.Fprintf(os.Stderr, "✓ Successfully imported %d issues from git.\n\n", issueCount)
}
}
if err := store.Close(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", err)
}
// Skip output if quiet mode
if quiet {
return
}
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("\n%s bd initialized successfully!\n\n", green("✓"))
fmt.Printf(" Database: %s\n", cyan(dbPath))
fmt.Printf(" Issue prefix: %s\n", cyan(prefix))
fmt.Printf(" Issues will be named: %s\n\n", cyan(prefix+"-1, "+prefix+"-2, ..."))
fmt.Printf("Run %s to get started.\n\n", cyan("bd quickstart"))
},
}
func init() {
initCmd.Flags().StringP("prefix", "p", "", "Issue prefix (default: current directory name)")
initCmd.Flags().BoolP("quiet", "q", false, "Suppress output (quiet mode)")
rootCmd.AddCommand(initCmd)
}