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

@@ -343,7 +343,7 @@ func TestBondProtoMol(t *testing.T) {
// Bond proto to molecule
vars := map[string]string{"name": "auth-feature"}
result, err := bondProtoMol(ctx, store, proto, mol, types.BondTypeSequential, vars, "", "test", false)
result, err := bondProtoMol(ctx, store, proto, mol, types.BondTypeSequential, vars, "", "test", false, false)
if err != nil {
t.Fatalf("bondProtoMol failed: %v", err)
}
@@ -840,7 +840,7 @@ func TestSpawnWithBasicAttach(t *testing.T) {
}
// Attach the second proto (simulating --attach flag behavior)
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, types.BondTypeSequential, vars, "", "test", false)
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, types.BondTypeSequential, vars, "", "test", false, false)
if err != nil {
t.Fatalf("Failed to bond attachment: %v", err)
}
@@ -945,12 +945,12 @@ func TestSpawnWithMultipleAttachments(t *testing.T) {
}
// Attach both protos (simulating --attach A --attach B)
bondResultA, err := bondProtoMol(ctx, s, attachA, spawnedMol, types.BondTypeSequential, nil, "", "test", false)
bondResultA, err := bondProtoMol(ctx, s, attachA, spawnedMol, types.BondTypeSequential, nil, "", "test", false, false)
if err != nil {
t.Fatalf("Failed to bond attachA: %v", err)
}
bondResultB, err := bondProtoMol(ctx, s, attachB, spawnedMol, types.BondTypeSequential, nil, "", "test", false)
bondResultB, err := bondProtoMol(ctx, s, attachB, spawnedMol, types.BondTypeSequential, nil, "", "test", false, false)
if err != nil {
t.Fatalf("Failed to bond attachB: %v", err)
}
@@ -1063,7 +1063,7 @@ func TestSpawnAttachTypes(t *testing.T) {
}
// Bond with specified type
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, tt.bondType, nil, "", "test", false)
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, tt.bondType, nil, "", "test", false, false)
if err != nil {
t.Fatalf("Failed to bond: %v", err)
}
@@ -1228,7 +1228,7 @@ func TestSpawnVariableAggregation(t *testing.T) {
// Bond attachment with same variables
spawnedMol, _ := s.GetIssue(ctx, spawnResult.NewEpicID)
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, types.BondTypeSequential, vars, "", "test", false)
bondResult, err := bondProtoMol(ctx, s, attachProto, spawnedMol, types.BondTypeSequential, vars, "", "test", false, false)
if err != nil {
t.Fatalf("Failed to bond: %v", err)
}
@@ -1283,288 +1283,6 @@ func TestSpawnAttachDryRunOutput(t *testing.T) {
}
}
// TestSquashWispToPermanent tests cross-store squash: wisp → permanent digest (bd-kwjh.4)
func TestSquashWispToPermanent(t *testing.T) {
ctx := context.Background()
// Create separate wisp and permanent stores
wispPath := t.TempDir() + "/wisp.db"
permPath := t.TempDir() + "/permanent.db"
wispStore, err := sqlite.New(ctx, wispPath)
if err != nil {
t.Fatalf("Failed to create wisp store: %v", err)
}
defer wispStore.Close()
if err := wispStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set wisp config: %v", err)
}
permStore, err := sqlite.New(ctx, permPath)
if err != nil {
t.Fatalf("Failed to create permanent store: %v", err)
}
defer permStore.Close()
if err := permStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set permanent config: %v", err)
}
// Create a wisp molecule in wisp storage
wispRoot := &types.Issue{
Title: "Deacon Patrol Cycle",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeEpic,
Wisp: true,
}
if err := wispStore.CreateIssue(ctx, wispRoot, "test"); err != nil {
t.Fatalf("Failed to create wisp root: %v", err)
}
wispChild1 := &types.Issue{
Title: "Check witnesses",
Description: "Verified 3 witnesses healthy",
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Wisp: true,
CloseReason: "All healthy",
}
wispChild2 := &types.Issue{
Title: "Process mail queue",
Description: "Processed 5 mail items",
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Wisp: true,
CloseReason: "Mail delivered",
}
if err := wispStore.CreateIssue(ctx, wispChild1, "test"); err != nil {
t.Fatalf("Failed to create wisp child1: %v", err)
}
if err := wispStore.CreateIssue(ctx, wispChild2, "test"); err != nil {
t.Fatalf("Failed to create wisp child2: %v", err)
}
// Add parent-child dependencies
if err := wispStore.AddDependency(ctx, &types.Dependency{
IssueID: wispChild1.ID,
DependsOnID: wispRoot.ID,
Type: types.DepParentChild,
}, "test"); err != nil {
t.Fatalf("Failed to add child1 dependency: %v", err)
}
if err := wispStore.AddDependency(ctx, &types.Dependency{
IssueID: wispChild2.ID,
DependsOnID: wispRoot.ID,
Type: types.DepParentChild,
}, "test"); err != nil {
t.Fatalf("Failed to add child2 dependency: %v", err)
}
// Load the subgraph
subgraph, err := loadTemplateSubgraph(ctx, wispStore, wispRoot.ID)
if err != nil {
t.Fatalf("Failed to load wisp subgraph: %v", err)
}
// Verify subgraph loaded correctly
if len(subgraph.Issues) != 3 {
t.Fatalf("Expected 3 issues in subgraph, got %d", len(subgraph.Issues))
}
// Perform cross-store squash
result, err := squashWispToPermanent(ctx, wispStore, permStore, subgraph, false, "", "test")
if err != nil {
t.Fatalf("squashWispToPermanent failed: %v", err)
}
// Verify result
if result.SquashedCount != 3 {
t.Errorf("SquashedCount = %d, want 3", result.SquashedCount)
}
if !result.WispSquash {
t.Error("WispSquash should be true")
}
if result.DigestID == "" {
t.Error("DigestID should not be empty")
}
if result.DeletedCount != 3 {
t.Errorf("DeletedCount = %d, want 3", result.DeletedCount)
}
// Verify digest was created in permanent storage
digest, err := permStore.GetIssue(ctx, result.DigestID)
if err != nil {
t.Fatalf("Failed to get digest from permanent store: %v", err)
}
if digest.Wisp {
t.Error("Digest should NOT be a wisp")
}
if digest.Status != types.StatusClosed {
t.Errorf("Digest status = %v, want closed", digest.Status)
}
if !strings.Contains(digest.Title, "Deacon Patrol Cycle") {
t.Errorf("Digest title %q should contain original molecule title", digest.Title)
}
if !strings.Contains(digest.Description, "Check witnesses") {
t.Error("Digest description should contain child titles")
}
// Verify wisps were deleted from wisp storage
// Note: GetIssue returns (nil, nil) when issue doesn't exist
rootIssue, err := wispStore.GetIssue(ctx, wispRoot.ID)
if err != nil {
t.Errorf("Unexpected error checking root deletion: %v", err)
}
if rootIssue != nil {
t.Error("Wisp root should have been deleted")
}
child1Issue, err := wispStore.GetIssue(ctx, wispChild1.ID)
if err != nil {
t.Errorf("Unexpected error checking child1 deletion: %v", err)
}
if child1Issue != nil {
t.Error("Wisp child1 should have been deleted")
}
child2Issue, err := wispStore.GetIssue(ctx, wispChild2.ID)
if err != nil {
t.Errorf("Unexpected error checking child2 deletion: %v", err)
}
if child2Issue != nil {
t.Error("Wisp child2 should have been deleted")
}
}
// TestSquashWispToPermanentWithSummary tests that agent summaries override auto-generation
func TestSquashWispToPermanentWithSummary(t *testing.T) {
ctx := context.Background()
wispPath := t.TempDir() + "/wisp.db"
permPath := t.TempDir() + "/permanent.db"
wispStore, err := sqlite.New(ctx, wispPath)
if err != nil {
t.Fatalf("Failed to create wisp store: %v", err)
}
defer wispStore.Close()
if err := wispStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set wisp config: %v", err)
}
permStore, err := sqlite.New(ctx, permPath)
if err != nil {
t.Fatalf("Failed to create permanent store: %v", err)
}
defer permStore.Close()
if err := permStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set permanent config: %v", err)
}
// Create a simple wisp molecule
wispRoot := &types.Issue{
Title: "Patrol Cycle",
Status: types.StatusClosed,
Priority: 1,
IssueType: types.TypeEpic,
Wisp: true,
}
if err := wispStore.CreateIssue(ctx, wispRoot, "test"); err != nil {
t.Fatalf("Failed to create wisp root: %v", err)
}
subgraph := &MoleculeSubgraph{
Root: wispRoot,
Issues: []*types.Issue{wispRoot},
}
// Squash with agent-provided summary
agentSummary := "## AI-Generated Patrol Summary\n\nAll systems healthy. No issues found."
result, err := squashWispToPermanent(ctx, wispStore, permStore, subgraph, true, agentSummary, "test")
if err != nil {
t.Fatalf("squashWispToPermanent failed: %v", err)
}
// Verify digest uses agent summary
digest, err := permStore.GetIssue(ctx, result.DigestID)
if err != nil {
t.Fatalf("Failed to get digest: %v", err)
}
if digest.Description != agentSummary {
t.Errorf("Digest should use agent summary.\nGot: %s\nWant: %s", digest.Description, agentSummary)
}
}
// TestSquashWispToPermanentKeepChildren tests --keep-children flag
func TestSquashWispToPermanentKeepChildren(t *testing.T) {
ctx := context.Background()
wispPath := t.TempDir() + "/wisp.db"
permPath := t.TempDir() + "/permanent.db"
wispStore, err := sqlite.New(ctx, wispPath)
if err != nil {
t.Fatalf("Failed to create wisp store: %v", err)
}
defer wispStore.Close()
if err := wispStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set wisp config: %v", err)
}
permStore, err := sqlite.New(ctx, permPath)
if err != nil {
t.Fatalf("Failed to create permanent store: %v", err)
}
defer permStore.Close()
if err := permStore.SetConfig(ctx, "issue_prefix", "test"); err != nil {
t.Fatalf("Failed to set permanent config: %v", err)
}
// Create a wisp molecule
wispRoot := &types.Issue{
Title: "Test Molecule",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeEpic,
Wisp: true,
}
if err := wispStore.CreateIssue(ctx, wispRoot, "test"); err != nil {
t.Fatalf("Failed to create wisp root: %v", err)
}
subgraph := &MoleculeSubgraph{
Root: wispRoot,
Issues: []*types.Issue{wispRoot},
}
// Squash with keepChildren=true
result, err := squashWispToPermanent(ctx, wispStore, permStore, subgraph, true, "", "test")
if err != nil {
t.Fatalf("squashWispToPermanent failed: %v", err)
}
// Verify no deletion
if result.DeletedCount != 0 {
t.Errorf("DeletedCount = %d, want 0 (keep-children)", result.DeletedCount)
}
if !result.KeptChildren {
t.Error("KeptChildren should be true")
}
// Wisp should still exist
_, err = wispStore.GetIssue(ctx, wispRoot.ID)
if err != nil {
t.Error("Wisp should still exist with --keep-children")
}
// Digest should still be created
_, err = permStore.GetIssue(ctx, result.DigestID)
if err != nil {
t.Error("Digest should be created even with --keep-children")
}
}
// TestWispFilteringFromExport verifies that wisp issues are filtered
// from JSONL export (bd-687g). Wisp issues should only exist in SQLite,
// not in issues.jsonl, to prevent "zombie" resurrection after mol squash.
@@ -2238,7 +1956,7 @@ func TestBondProtoMolWithRef(t *testing.T) {
// Bond proto to patrol with custom child ref
vars := map[string]string{"polecat_name": "ace"}
childRef := "arm-{{polecat_name}}"
result, err := bondProtoMol(ctx, s, protoRoot, patrol, types.BondTypeSequential, vars, childRef, "test", false)
result, err := bondProtoMol(ctx, s, protoRoot, patrol, types.BondTypeSequential, vars, childRef, "test", false, false)
if err != nil {
t.Fatalf("bondProtoMol failed: %v", err)
}
@@ -2309,14 +2027,14 @@ func TestBondProtoMolMultipleArms(t *testing.T) {
// Bond arm-ace
varsAce := map[string]string{"name": "ace"}
resultAce, err := bondProtoMol(ctx, s, proto, patrol, types.BondTypeParallel, varsAce, "arm-{{name}}", "test", false)
resultAce, err := bondProtoMol(ctx, s, proto, patrol, types.BondTypeParallel, varsAce, "arm-{{name}}", "test", false, false)
if err != nil {
t.Fatalf("bondProtoMol (ace) failed: %v", err)
}
// Bond arm-nux
varsNux := map[string]string{"name": "nux"}
resultNux, err := bondProtoMol(ctx, s, proto, patrol, types.BondTypeParallel, varsNux, "arm-{{name}}", "test", false)
resultNux, err := bondProtoMol(ctx, s, proto, patrol, types.BondTypeParallel, varsNux, "arm-{{name}}", "test", false, false)
if err != nil {
t.Fatalf("bondProtoMol (nux) failed: %v", err)
}