New wisps now use 'wisp' segment (e.g., gt-wisp-xxx) instead of 'eph'. Detection patterns updated to support both for backwards compatibility with existing gt-eph-* wisps in databases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// isFreshCloneError checks if the error is due to a fresh clone scenario
|
|
// where the database exists but is missing required config (like issue_prefix).
|
|
// This happens when someone clones a repo with beads but needs to initialize.
|
|
func isFreshCloneError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
errStr := err.Error()
|
|
// Check for the specific migration invariant error pattern
|
|
return strings.Contains(errStr, "post-migration validation failed") &&
|
|
strings.Contains(errStr, "required config key missing: issue_prefix")
|
|
}
|
|
|
|
// handleFreshCloneError displays a helpful message when a fresh clone is detected
|
|
// and returns true if the error was handled (so caller should exit).
|
|
// If not a fresh clone error, returns false and does nothing.
|
|
func handleFreshCloneError(err error, beadsDir string) bool {
|
|
if !isFreshCloneError(err) {
|
|
return false
|
|
}
|
|
|
|
// Look for JSONL file in the .beads directory
|
|
jsonlPath := ""
|
|
issueCount := 0
|
|
|
|
if beadsDir != "" {
|
|
// Check for issues.jsonl (canonical) first, then beads.jsonl (legacy)
|
|
for _, name := range []string{"issues.jsonl", "beads.jsonl"} {
|
|
candidate := filepath.Join(beadsDir, name)
|
|
if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
|
|
jsonlPath = candidate
|
|
// Count lines (approximately = issue count)
|
|
// #nosec G304 -- candidate is constructed from beadsDir which is .beads/
|
|
if data, readErr := os.ReadFile(candidate); readErr == nil {
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.TrimSpace(line) != "" {
|
|
issueCount++
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Error: Database not initialized\n\n")
|
|
fmt.Fprintf(os.Stderr, "This appears to be a fresh clone or the database needs initialization.\n")
|
|
|
|
if jsonlPath != "" && issueCount > 0 {
|
|
fmt.Fprintf(os.Stderr, "Found: %s (%d issues)\n\n", jsonlPath, issueCount)
|
|
fmt.Fprintf(os.Stderr, "To initialize from the JSONL file, run:\n")
|
|
fmt.Fprintf(os.Stderr, " bd import -i %s\n\n", jsonlPath)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\nTo initialize a new database, run:\n")
|
|
fmt.Fprintf(os.Stderr, " bd init --prefix <your-prefix>\n\n")
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "For more information: bd init --help\n")
|
|
return true
|
|
}
|
|
|
|
// isWispOperation returns true if the command operates on ephemeral wisps.
|
|
// Wisp operations auto-bypass the daemon because wisps are local-only
|
|
// (Ephemeral=true issues are never exported to JSONL).
|
|
// Detects:
|
|
// - mol wisp subcommands (create, list, gc, or direct proto invocation)
|
|
// - mol burn (only operates on wisps)
|
|
// - mol squash (condenses wisps to digests)
|
|
// - Commands with ephemeral issue IDs in args (bd-*-wisp-*, wisp-*, or legacy eph-*)
|
|
func isWispOperation(cmd *cobra.Command, args []string) bool {
|
|
cmdName := cmd.Name()
|
|
|
|
// Check command hierarchy for wisp subcommands
|
|
// bd mol wisp → parent is "mol", cmd is "wisp"
|
|
// bd mol wisp create → parent is "wisp", cmd is "create"
|
|
if cmd.Parent() != nil {
|
|
parentName := cmd.Parent().Name()
|
|
// Direct wisp command or subcommands under wisp
|
|
if parentName == "wisp" || cmdName == "wisp" {
|
|
return true
|
|
}
|
|
// mol burn and mol squash are wisp-only operations
|
|
if parentName == "mol" && (cmdName == "burn" || cmdName == "squash") {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check for ephemeral issue IDs in arguments
|
|
// Ephemeral IDs have "wisp" segment: bd-wisp-xxx, gt-wisp-xxx, wisp-xxx
|
|
// Also detect legacy "eph" prefix for backwards compatibility
|
|
for _, arg := range args {
|
|
// Skip flags
|
|
if strings.HasPrefix(arg, "-") {
|
|
continue
|
|
}
|
|
// Check for ephemeral prefix patterns (wisp-* or legacy eph-*)
|
|
if strings.Contains(arg, "-wisp-") || strings.HasPrefix(arg, "wisp-") ||
|
|
strings.Contains(arg, "-eph-") || strings.HasPrefix(arg, "eph-") {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|