refactor: simplify wisp architecture - single DB with Wisp flag (bd-bkul)

- Remove cross-store squash logic from mol_squash.go
  - Delete runWispSquash() and squashWispToPermanent() functions
  - Simplify runMolSquash() to work with main store only

- Update mol_burn.go to work with main database
  - Remove .beads-wisp/ directory references
  - Look for Wisp=true issues in main store instead

- Update mol_bond.go to use Wisp flag instead of separate store
  - --wisp now creates issues with Wisp=true in main store
  - --pour creates issues with Wisp=false (persistent)
  - Update bondProtoMol signature to accept both flags

- Deprecate wisp storage functions in beads.go
  - WispDirName, FindWispDir, FindWispDatabasePath
  - NewWispStorage, EnsureWispGitignore, IsWispDatabase
  - All marked deprecated with reference to bd-bkul

- Remove obsolete cross-store squash tests
  - TestSquashWispToPermanent
  - TestSquashWispToPermanentWithSummary
  - TestSquashWispToPermanentKeepChildren

All tests pass. Build succeeds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-24 20:42:54 -08:00
parent c0271aedbf
commit f2e6df95c0
6 changed files with 121 additions and 583 deletions

View File

@@ -7,7 +7,6 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/beads"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui"
@@ -34,12 +33,12 @@ Bond types:
Phase control:
By default, spawned protos follow the target's phase:
- Attaching to mol → spawns as mol (liquid)
- Attaching to wisp → spawns as wisp (vapor)
- Attaching to mol (Wisp=false) → spawns as persistent (Wisp=false)
- Attaching to wisp (Wisp=true) → spawns as ephemeral (Wisp=true)
Override with:
--pour Force spawn as liquid (persistent), even when attaching to wisp
--wisp Force spawn as vapor (ephemeral), even when attaching to mol
--pour Force spawn as liquid (persistent, Wisp=false)
--wisp Force spawn as vapor (ephemeral, Wisp=true, excluded from JSONL export)
Dynamic bonding (Christmas Ornament pattern):
Use --ref to specify a custom child reference with variable substitution.
@@ -106,25 +105,10 @@ func runMolBond(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Determine which store to use for spawning
// Default: follow target's phase. Override with --wisp or --pour.
targetStore := store
if wisp {
// Explicit --wisp: use wisp storage
wispStore, err := beads.NewWispStorage(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open wisp storage: %v\n", err)
os.Exit(1)
}
defer func() { _ = wispStore.Close() }()
targetStore = wispStore
// Ensure wisp directory is gitignored
if err := beads.EnsureWispGitignore(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not update .gitignore: %v\n", err)
}
}
// Note: --pour means use permanent storage (which is the default targetStore)
// All issues go in the main store; wisp vs pour determines the Wisp flag
// --wisp: create with Wisp=true (ephemeral, excluded from JSONL export)
// --pour: create with Wisp=false (persistent, exported to JSONL)
// Default: follow target's phase (wisp if target is wisp, otherwise persistent)
// Validate bond type
if bondType != types.BondTypeSequential && bondType != types.BondTypeParallel && bondType != types.BondTypeConditional {
@@ -220,18 +204,18 @@ func runMolBond(cmd *cobra.Command, args []string) {
}
// Dispatch based on operand types
// Note: proto+proto creates templates (permanent storage), others use targetStore
// All operations use the main store; wisp flag determines ephemeral vs persistent
var result *BondResult
switch {
case aIsProto && bIsProto:
// Compound protos are templates - always use permanent storage
// Compound protos are templates - always persistent
result, err = bondProtoProto(ctx, store, issueA, issueB, bondType, customTitle, actor)
case aIsProto && !bIsProto:
result, err = bondProtoMol(ctx, targetStore, issueA, issueB, bondType, vars, childRef, actor, pour)
result, err = bondProtoMol(ctx, store, issueA, issueB, bondType, vars, childRef, actor, wisp, pour)
case !aIsProto && bIsProto:
result, err = bondMolProto(ctx, targetStore, issueA, issueB, bondType, vars, childRef, actor, pour)
result, err = bondMolProto(ctx, store, issueA, issueB, bondType, vars, childRef, actor, wisp, pour)
default:
result, err = bondMolMol(ctx, targetStore, issueA, issueB, bondType, actor)
result, err = bondMolMol(ctx, store, issueA, issueB, bondType, actor)
}
if err != nil {
@@ -239,10 +223,8 @@ func runMolBond(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Schedule auto-flush (only for non-wisp, wisps don't sync)
if !wisp {
markDirtyAndScheduleFlush()
}
// Schedule auto-flush - wisps are in main DB now, but JSONL export skips them
markDirtyAndScheduleFlush()
if jsonOutput {
outputJSON(result)
@@ -255,9 +237,9 @@ func runMolBond(cmd *cobra.Command, args []string) {
fmt.Printf(" Spawned: %d issues\n", result.Spawned)
}
if wisp {
fmt.Printf(" Phase: vapor (ephemeral in .beads-wisp/)\n")
fmt.Printf(" Phase: vapor (ephemeral, Wisp=true)\n")
} else if pour {
fmt.Printf(" Phase: liquid (persistent in .beads/)\n")
fmt.Printf(" Phase: liquid (persistent, Wisp=false)\n")
}
}
@@ -366,7 +348,7 @@ func bondProtoProto(ctx context.Context, s storage.Storage, protoA, protoB *type
// bondProtoMol bonds a proto to an existing molecule by spawning the proto.
// If childRef is provided, generates custom IDs like "parent.childref" (dynamic bonding).
func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, pour bool) (*BondResult, error) {
func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, wispFlag, pourFlag bool) (*BondResult, error) {
// Load proto subgraph
subgraph, err := loadTemplateSubgraph(ctx, s, proto.ID)
if err != nil {
@@ -385,11 +367,20 @@ func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issu
return nil, fmt.Errorf("missing required variables: %s (use --var)", strings.Join(missingVars, ", "))
}
// Determine wisp flag based on explicit flags or target's phase
// --wisp: force wisp=true, --pour: force wisp=false, neither: follow target
makeWisp := mol.Wisp // Default: follow target's phase
if wispFlag {
makeWisp = true
} else if pourFlag {
makeWisp = false
}
// Build CloneOptions for spawning
opts := CloneOptions{
Vars: vars,
Actor: actorName,
Wisp: !pour, // wisp by default, but --pour makes persistent (bd-l7y3)
Wisp: makeWisp,
}
// Dynamic bonding: use custom IDs if childRef is provided
@@ -444,9 +435,9 @@ func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issu
}
// bondMolProto bonds a molecule to a proto (symmetric with bondProtoMol)
func bondMolProto(ctx context.Context, s storage.Storage, mol, proto *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, pour bool) (*BondResult, error) {
func bondMolProto(ctx context.Context, s storage.Storage, mol, proto *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, wispFlag, pourFlag bool) (*BondResult, error) {
// Same as bondProtoMol but with arguments swapped
return bondProtoMol(ctx, s, proto, mol, bondType, vars, childRef, actorName, pour)
return bondProtoMol(ctx, s, proto, mol, bondType, vars, childRef, actorName, wispFlag, pourFlag)
}
// bondMolMol bonds two molecules together
@@ -504,8 +495,8 @@ func init() {
molBondCmd.Flags().String("as", "", "Custom title for compound proto (proto+proto only)")
molBondCmd.Flags().Bool("dry-run", false, "Preview what would be created")
molBondCmd.Flags().StringSlice("var", []string{}, "Variable substitution for spawned protos (key=value)")
molBondCmd.Flags().Bool("wisp", false, "Force spawn as vapor (ephemeral in .beads-wisp/)")
molBondCmd.Flags().Bool("pour", false, "Force spawn as liquid (persistent in .beads/)")
molBondCmd.Flags().Bool("wisp", false, "Force spawn as vapor (ephemeral, Wisp=true)")
molBondCmd.Flags().Bool("pour", false, "Force spawn as liquid (persistent, Wisp=false)")
molBondCmd.Flags().String("ref", "", "Custom child reference with {{var}} substitution (e.g., arm-{{polecat_name}})")
molCmd.AddCommand(molBondCmd)