From ea8ae11002d3204311befef6d725a708b6d6f4e6 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Fri, 26 Dec 2025 21:07:37 -0800 Subject: [PATCH] feat: Rename 'wisp' to 'ephemeral' in beads API (bd-o18s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: API field and CLI command renamed - types.Issue.Wisp → types.Issue.Ephemeral - JSON field: "wisp" → "ephemeral" - CLI: bd wisp → bd ephemeral - Flags: --wisp → --ephemeral - ID prefix: wisp → eph The SQLite column already uses 'ephemeral' so no schema migration needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/autoflush.go | 2 +- cmd/bd/cleanup.go | 12 +- cmd/bd/create.go | 8 +- cmd/bd/{wisp.go => ephemeral.go} | 202 ++++++++++---------- cmd/bd/export.go | 2 +- cmd/bd/gate.go | 2 +- cmd/bd/hook.go | 4 +- cmd/bd/info.go | 1 + cmd/bd/mol.go | 6 +- cmd/bd/mol_bond.go | 72 +++---- cmd/bd/mol_burn.go | 14 +- cmd/bd/mol_catalog.go | 6 +- cmd/bd/mol_squash.go | 22 +-- cmd/bd/mol_test.go | 16 +- cmd/bd/nodb.go | 2 +- cmd/bd/sync_export.go | 2 +- cmd/bd/template.go | 14 +- cmd/bd/thread_test.go | 18 +- docs/ARCHITECTURE.md | 2 +- docs/CLI_REFERENCE.md | 30 +-- docs/DELETIONS.md | 2 +- docs/MOLECULES.md | 8 +- internal/rpc/protocol.go | 14 +- internal/rpc/server_issues_epics.go | 14 +- internal/storage/sqlite/dependencies.go | 4 +- internal/storage/sqlite/graph_links_test.go | 22 +-- internal/storage/sqlite/issues.go | 4 +- internal/storage/sqlite/multirepo.go | 2 +- internal/storage/sqlite/multirepo_export.go | 2 +- internal/storage/sqlite/queries.go | 8 +- internal/storage/sqlite/ready.go | 2 +- internal/storage/sqlite/transaction.go | 6 +- internal/types/types.go | 8 +- skills/beads/references/MOLECULES.md | 20 +- 34 files changed, 277 insertions(+), 276 deletions(-) rename cmd/bd/{wisp.go => ephemeral.go} (70%) diff --git a/cmd/bd/autoflush.go b/cmd/bd/autoflush.go index 8d3902a9..73f571c8 100644 --- a/cmd/bd/autoflush.go +++ b/cmd/bd/autoflush.go @@ -671,7 +671,7 @@ func flushToJSONLWithState(state flushState) { issues := make([]*types.Issue, 0, len(issueMap)) wispsSkipped := 0 for _, issue := range issueMap { - if issue.Wisp { + if issue.Ephemeral { wispsSkipped++ continue } diff --git a/cmd/bd/cleanup.go b/cmd/bd/cleanup.go index be42c60b..dd7fe371 100644 --- a/cmd/bd/cleanup.go +++ b/cmd/bd/cleanup.go @@ -15,7 +15,7 @@ type CleanupEmptyResponse struct { DeletedCount int `json:"deleted_count"` Message string `json:"message"` Filter string `json:"filter,omitempty"` - Wisp bool `json:"wisp,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` } // Hard delete mode: bypass tombstone TTL safety, use --older-than days directly @@ -56,7 +56,7 @@ Delete issues closed more than 30 days ago: bd cleanup --older-than 30 --force Delete only closed wisps (transient molecules): - bd cleanup --wisp --force + bd cleanup --ephemeral --force Preview what would be deleted/pruned: bd cleanup --dry-run @@ -80,7 +80,7 @@ SEE ALSO: cascade, _ := cmd.Flags().GetBool("cascade") olderThanDays, _ := cmd.Flags().GetInt("older-than") hardDelete, _ := cmd.Flags().GetBool("hard") - wispOnly, _ := cmd.Flags().GetBool("wisp") + wispOnly, _ := cmd.Flags().GetBool("ephemeral") // Calculate custom TTL for --hard mode // When --hard is set, use --older-than days as the tombstone TTL cutoff @@ -129,7 +129,7 @@ SEE ALSO: // Add wisp filter if specified (bd-kwro.9) if wispOnly { wispTrue := true - filter.Wisp = &wispTrue + filter.Ephemeral = &wispTrue } // Get all closed issues matching filter @@ -165,7 +165,7 @@ SEE ALSO: result.Filter = fmt.Sprintf("older than %d days", olderThanDays) } if wispOnly { - result.Wisp = true + result.Ephemeral = true } outputJSON(result) } else { @@ -270,6 +270,6 @@ func init() { cleanupCmd.Flags().Bool("cascade", false, "Recursively delete all dependent issues") cleanupCmd.Flags().Int("older-than", 0, "Only delete issues closed more than N days ago (0 = all closed issues)") cleanupCmd.Flags().Bool("hard", false, "Bypass tombstone TTL safety; use --older-than days as cutoff") - cleanupCmd.Flags().Bool("wisp", false, "Only delete closed wisps (transient molecules)") + cleanupCmd.Flags().Bool("ephemeral", false, "Only delete closed wisps (transient molecules)") rootCmd.AddCommand(cleanupCmd) } diff --git a/cmd/bd/create.go b/cmd/bd/create.go index 359923b6..eb3a45e0 100644 --- a/cmd/bd/create.go +++ b/cmd/bd/create.go @@ -107,7 +107,7 @@ var createCmd = &cobra.Command{ waitsForGate, _ := cmd.Flags().GetString("waits-for-gate") forceCreate, _ := cmd.Flags().GetBool("force") repoOverride, _ := cmd.Flags().GetString("repo") - wisp, _ := cmd.Flags().GetBool("wisp") + wisp, _ := cmd.Flags().GetBool("ephemeral") // Get estimate if provided var estimatedMinutes *int @@ -222,7 +222,7 @@ var createCmd = &cobra.Command{ Dependencies: deps, WaitsFor: waitsFor, WaitsForGate: waitsForGate, - Wisp: wisp, + Ephemeral: wisp, CreatedBy: getActorWithGit(), } @@ -268,7 +268,7 @@ var createCmd = &cobra.Command{ Assignee: assignee, ExternalRef: externalRefPtr, EstimatedMinutes: estimatedMinutes, - Wisp: wisp, + Ephemeral: wisp, CreatedBy: getActorWithGit(), // GH#748: track who created the issue } @@ -448,7 +448,7 @@ func init() { createCmd.Flags().Bool("force", false, "Force creation even if prefix doesn't match database prefix") createCmd.Flags().String("repo", "", "Target repository for issue (overrides auto-routing)") createCmd.Flags().IntP("estimate", "e", 0, "Time estimate in minutes (e.g., 60 for 1 hour)") - createCmd.Flags().Bool("wisp", false, "Create as wisp (ephemeral, not exported to JSONL)") + createCmd.Flags().Bool("ephemeral", false, "Create as ephemeral (ephemeral, not exported to JSONL)") // Note: --json flag is defined as a persistent flag in main.go, not here rootCmd.AddCommand(createCmd) } diff --git a/cmd/bd/wisp.go b/cmd/bd/ephemeral.go similarity index 70% rename from cmd/bd/wisp.go rename to cmd/bd/ephemeral.go index 2ce28e14..9df287f8 100644 --- a/cmd/bd/wisp.go +++ b/cmd/bd/ephemeral.go @@ -18,37 +18,37 @@ import ( // Wisp commands - manage ephemeral molecules // -// Wisps are ephemeral issues with Wisp=true in the main database. +// Ephemeral issues are ephemeral issues with Wisp=true in the main database. // They're used for patrol cycles and operational loops that shouldn't // be exported to JSONL (and thus not synced via git). // // Commands: -// bd wisp list - List all wisps in current context -// bd wisp gc - Garbage collect orphaned wisps +// bd ephemeral list - List all ephemeral issues in current context +// bd ephemeral gc - Garbage collect orphaned ephemeral issues -var wispCmd = &cobra.Command{ - Use: "wisp", - Short: "Manage ephemeral molecules (wisps)", - Long: `Manage wisps - ephemeral molecules for operational workflows. +var ephemeralCmd = &cobra.Command{ + Use: "ephemeral", + Short: "Manage ephemeral molecules", + Long: `Manage ephemeral issues - ephemeral molecules for operational workflows. -Wisps are issues with Wisp=true in the main database. They're stored +Ephemeral issues are issues with Wisp=true in the main database. They're stored locally but NOT exported to JSONL (and thus not synced via git). They're used for patrol cycles, operational loops, and other workflows that shouldn't accumulate in the shared issue database. The wisp lifecycle: - 1. Create: bd wisp create or bd create --wisp - 2. Execute: Normal bd operations work on wisps + 1. Create: bd ephemeral create or bd create --ephemeral + 2. Execute: Normal bd operations work on ephemeral issues 3. Squash: bd mol squash (clears Wisp flag, promotes to persistent) 4. Or burn: bd mol burn (deletes wisp without creating digest) Commands: - list List all wisps in current context - gc Garbage collect orphaned wisps`, + list List all ephemeral issues in current context + gc Garbage collect orphaned ephemeral issues`, } -// WispListItem represents a wisp in list output -type WispListItem struct { +// EphemeralListItem represents a wisp in list output +type EphemeralListItem struct { ID string `json:"id"` Title string `json:"title"` Status string `json:"status"` @@ -58,9 +58,9 @@ type WispListItem struct { Old bool `json:"old,omitempty"` // Not updated in 24+ hours } -// WispListResult is the JSON output for wisp list -type WispListResult struct { - Wisps []WispListItem `json:"wisps"` +// EphemeralListResult is the JSON output for wisp list +type EphemeralListResult struct { + Wisps []EphemeralListItem `json:"ephemeral_items"` Count int `json:"count"` OldCount int `json:"old_count,omitempty"` } @@ -68,8 +68,8 @@ type WispListResult struct { // OldThreshold is how old a wisp must be to be flagged as old (time-based, for ephemeral cleanup) const OldThreshold = 24 * time.Hour -// wispCreateCmd instantiates a proto as an ephemeral wisp -var wispCreateCmd = &cobra.Command{ +// ephemeralCreateCmd instantiates a proto as an ephemeral wisp +var ephemeralCreateCmd = &cobra.Command{ Use: "create ", Short: "Instantiate a proto as an ephemeral wisp (solid -> vapor)", Long: `Create a wisp from a proto - sublimation from solid to vapor. @@ -91,14 +91,14 @@ The wisp will: - Either evaporate (burn) or condense to digest (squash) Examples: - bd wisp create mol-patrol # Ephemeral patrol cycle - bd wisp create mol-health-check # One-time health check - bd wisp create mol-diagnostics --var target=db # Diagnostic run`, + bd ephemeral create mol-patrol # Ephemeral patrol cycle + bd ephemeral create mol-health-check # One-time health check + bd ephemeral create mol-diagnostics --var target=db # Diagnostic run`, Args: cobra.ExactArgs(1), - Run: runWispCreate, + Run: runEphemeralCreate, } -func runWispCreate(cmd *cobra.Command, args []string) { +func runEphemeralCreate(cmd *cobra.Command, args []string) { CheckReadonly("wisp create") ctx := rootCtx @@ -215,7 +215,7 @@ func runWispCreate(cmd *cobra.Command, args []string) { if dryRun { fmt.Printf("\nDry run: would create wisp with %d issues from proto %s\n\n", len(subgraph.Issues), protoID) - fmt.Printf("Storage: main database (wisp=true, not exported to JSONL)\n\n") + fmt.Printf("Storage: main database (ephemeral=true, not exported to JSONL)\n\n") for _, issue := range subgraph.Issues { newTitle := substituteVariables(issue.Title, vars) fmt.Printf(" - %s (from %s)\n", newTitle, issue.ID) @@ -223,22 +223,22 @@ func runWispCreate(cmd *cobra.Command, args []string) { return } - // Spawn as wisp in main database (ephemeral=true sets Wisp flag, skips JSONL export) - // bd-hobo: Use "wisp" prefix for distinct visual recognition - result, err := spawnMolecule(ctx, store, subgraph, vars, "", actor, true, "wisp") + // Spawn as ephemeral in main database (Ephemeral=true, skips JSONL export) + // bd-hobo: Use "eph" prefix for distinct visual recognition + result, err := spawnMolecule(ctx, store, subgraph, vars, "", actor, true, "eph") if err != nil { - fmt.Fprintf(os.Stderr, "Error creating wisp: %v\n", err) + fmt.Fprintf(os.Stderr, "Error creating ephemeral: %v\n", err) os.Exit(1) } - // Wisps are in main db but don't trigger JSONL export (Wisp flag excludes them) + // Ephemeral issues are in main db but don't trigger JSONL export (Wisp flag excludes them) if jsonOutput { - type wispCreateResult struct { + type ephemeralCreateResult struct { *InstantiateResult Phase string `json:"phase"` } - outputJSON(wispCreateResult{result, "vapor"}) + outputJSON(ephemeralCreateResult{result, "vapor"}) return } @@ -283,12 +283,12 @@ func resolvePartialIDDirect(ctx context.Context, partial string) (string, error) return "", fmt.Errorf("not found: %s", partial) } -var wispListCmd = &cobra.Command{ +var ephemeralListCmd = &cobra.Command{ Use: "list", - Short: "List all wisps in current context", - Long: `List all ephemeral molecules (wisps) in the current context. + Short: "List all ephemeral issues in current context", + Long: `List all ephemeral molecules (ephemeral issues) in the current context. -Wisps are issues with Wisp=true in the main database. They are stored +Ephemeral issues are issues with Wisp=true in the main database. They are stored locally but not exported to JSONL (and thus not synced via git). The list shows: @@ -298,18 +298,18 @@ The list shows: - Started: When the wisp was created - Updated: Last modification time -Old wisp detection: - - Old wisps haven't been updated in 24+ hours - - Use 'bd wisp gc' to clean up old/abandoned wisps +Old ephemeral issue detection: + - Old ephemeral issues haven't been updated in 24+ hours + - Use 'bd ephemeral gc' to clean up old/abandoned ephemeral issues Examples: - bd wisp list # List all wisps - bd wisp list --json # JSON output for programmatic use - bd wisp list --all # Include closed wisps`, - Run: runWispList, + bd ephemeral list # List all ephemeral issues + bd ephemeral list --json # JSON output for programmatic use + bd ephemeral list --all # Include closed ephemeral issues`, + Run: runEphemeralList, } -func runWispList(cmd *cobra.Command, args []string) { +func runEphemeralList(cmd *cobra.Command, args []string) { ctx := rootCtx showAll, _ := cmd.Flags().GetBool("all") @@ -317,8 +317,8 @@ func runWispList(cmd *cobra.Command, args []string) { // Check for database connection if store == nil && daemonClient == nil { if jsonOutput { - outputJSON(WispListResult{ - Wisps: []WispListItem{}, + outputJSON(EphemeralListResult{ + Wisps: []EphemeralListItem{}, Count: 0, }) } else { @@ -327,15 +327,15 @@ func runWispList(cmd *cobra.Command, args []string) { return } - // Query wisps from main database using Wisp filter - wispFlag := true + // Query ephemeral issues from main database using Wisp filter + ephemeralFlag := true var issues []*types.Issue var err error if daemonClient != nil { // Use daemon RPC resp, rpcErr := daemonClient.List(&rpc.ListArgs{ - Wisp: &wispFlag, + Ephemeral: &ephemeralFlag, }) if rpcErr != nil { err = rpcErr @@ -347,12 +347,12 @@ func runWispList(cmd *cobra.Command, args []string) { } else { // Direct database access filter := types.IssueFilter{ - Wisp: &wispFlag, + Ephemeral: &ephemeralFlag, } issues, err = store.SearchIssues(ctx, "", filter) } if err != nil { - fmt.Fprintf(os.Stderr, "Error listing wisps: %v\n", err) + fmt.Fprintf(os.Stderr, "Error listing ephemeral issues: %v\n", err) os.Exit(1) } @@ -367,13 +367,13 @@ func runWispList(cmd *cobra.Command, args []string) { issues = filtered } - // Convert to list items and detect old wisps + // Convert to list items and detect old ephemeral issues now := time.Now() - items := make([]WispListItem, 0, len(issues)) + items := make([]EphemeralListItem, 0, len(issues)) oldCount := 0 for _, issue := range issues { - item := WispListItem{ + item := EphemeralListItem{ ID: issue.ID, Title: issue.Title, Status: string(issue.Status), @@ -392,11 +392,11 @@ func runWispList(cmd *cobra.Command, args []string) { } // Sort by updated_at descending (most recent first) - slices.SortFunc(items, func(a, b WispListItem) int { + slices.SortFunc(items, func(a, b EphemeralListItem) int { return b.UpdatedAt.Compare(a.UpdatedAt) // descending order }) - result := WispListResult{ + result := EphemeralListResult{ Wisps: items, Count: len(items), OldCount: oldCount, @@ -409,11 +409,11 @@ func runWispList(cmd *cobra.Command, args []string) { // Human-readable output if len(items) == 0 { - fmt.Println("No wisps found") + fmt.Println("No ephemeral issues found") return } - fmt.Printf("Wisps (%d):\n\n", len(items)) + fmt.Printf("Ephemeral issues (%d):\n\n", len(items)) // Print header fmt.Printf("%-12s %-10s %-4s %-46s %s\n", @@ -442,9 +442,9 @@ func runWispList(cmd *cobra.Command, args []string) { // Print warnings if oldCount > 0 { - fmt.Printf("\n%s %d old wisp(s) (not updated in 24+ hours)\n", + fmt.Printf("\n%s %d old ephemeral issue(s) (not updated in 24+ hours)\n", ui.RenderWarn("⚠"), oldCount) - fmt.Println(" Hint: Use 'bd wisp gc' to clean up old wisps") + fmt.Println(" Hint: Use 'bd ephemeral gc' to clean up old ephemeral issues") } } @@ -478,38 +478,38 @@ func formatTimeAgo(t time.Time) string { } } -var wispGCCmd = &cobra.Command{ +var ephemeralGCCmd = &cobra.Command{ Use: "gc", - Short: "Garbage collect old/abandoned wisps", - Long: `Garbage collect old or abandoned wisps from the database. + Short: "Garbage collect old/abandoned ephemeral issues", + Long: `Garbage collect old or abandoned ephemeral issues from the database. A wisp is considered abandoned if: - It hasn't been updated in --age duration and is not closed -Abandoned wisps are deleted without creating a digest. Use 'bd mol squash' +Abandoned ephemeral issues are deleted without creating a digest. Use 'bd mol squash' if you want to preserve a summary before garbage collection. -Note: This uses time-based cleanup, appropriate for ephemeral wisps. +Note: This uses time-based cleanup, appropriate for ephemeral ephemeral issues. For graph-pressure staleness detection (blocking other work), see 'bd mol stale'. Examples: - bd wisp gc # Clean abandoned wisps (default: 1h threshold) - bd wisp gc --dry-run # Preview what would be cleaned - bd wisp gc --age 24h # Custom age threshold - bd wisp gc --all # Also clean closed wisps older than threshold`, - Run: runWispGC, + bd ephemeral gc # Clean abandoned ephemeral issues (default: 1h threshold) + bd ephemeral gc --dry-run # Preview what would be cleaned + bd ephemeral gc --age 24h # Custom age threshold + bd ephemeral gc --all # Also clean closed ephemeral issues older than threshold`, + Run: runEphemeralGC, } -// WispGCResult is the JSON output for wisp gc -type WispGCResult struct { +// EphemeralGCResult is the JSON output for ephemeral gc +type EphemeralGCResult struct { CleanedIDs []string `json:"cleaned_ids"` CleanedCount int `json:"cleaned_count"` Candidates int `json:"candidates,omitempty"` DryRun bool `json:"dry_run,omitempty"` } -func runWispGC(cmd *cobra.Command, args []string) { - CheckReadonly("wisp gc") +func runEphemeralGC(cmd *cobra.Command, args []string) { + CheckReadonly("ephemeral gc") ctx := rootCtx @@ -531,26 +531,26 @@ func runWispGC(cmd *cobra.Command, args []string) { // Wisp gc requires direct store access for deletion if store == nil { if daemonClient != nil { - fmt.Fprintf(os.Stderr, "Error: wisp gc requires direct database access\n") - fmt.Fprintf(os.Stderr, "Hint: use --no-daemon flag: bd --no-daemon wisp gc\n") + fmt.Fprintf(os.Stderr, "Error: ephemeral gc requires direct database access\n") + fmt.Fprintf(os.Stderr, "Hint: use --no-daemon flag: bd --no-daemon ephemeral gc\n") } else { fmt.Fprintf(os.Stderr, "Error: no database connection\n") } os.Exit(1) } - // Query wisps from main database using Wisp filter - wispFlag := true + // Query ephemeral issues from main database using Wisp filter + ephemeralFlag := true filter := types.IssueFilter{ - Wisp: &wispFlag, + Ephemeral: &ephemeralFlag, } issues, err := store.SearchIssues(ctx, "", filter) if err != nil { - fmt.Fprintf(os.Stderr, "Error listing wisps: %v\n", err) + fmt.Fprintf(os.Stderr, "Error listing ephemeral issues: %v\n", err) os.Exit(1) } - // Find old/abandoned wisps + // Find old/abandoned ephemeral issues now := time.Now() var abandoned []*types.Issue for _, issue := range issues { @@ -567,13 +567,13 @@ func runWispGC(cmd *cobra.Command, args []string) { if len(abandoned) == 0 { if jsonOutput { - outputJSON(WispGCResult{ + outputJSON(EphemeralGCResult{ CleanedIDs: []string{}, CleanedCount: 0, DryRun: dryRun, }) } else { - fmt.Println("No abandoned wisps found") + fmt.Println("No abandoned ephemeral issues found") } return } @@ -584,28 +584,28 @@ func runWispGC(cmd *cobra.Command, args []string) { for i, o := range abandoned { ids[i] = o.ID } - outputJSON(WispGCResult{ + outputJSON(EphemeralGCResult{ CleanedIDs: ids, Candidates: len(abandoned), CleanedCount: 0, DryRun: true, }) } else { - fmt.Printf("Dry run: would clean %d abandoned wisp(s):\n\n", len(abandoned)) + fmt.Printf("Dry run: would clean %d abandoned ephemeral issue(s):\n\n", len(abandoned)) for _, issue := range abandoned { age := formatTimeAgo(issue.UpdatedAt) fmt.Printf(" %s: %s (last updated: %s)\n", issue.ID, issue.Title, age) } - fmt.Printf("\nRun without --dry-run to delete these wisps.\n") + fmt.Printf("\nRun without --dry-run to delete these ephemeral issues.\n") } return } - // Delete abandoned wisps + // Delete abandoned ephemeral issues var cleanedIDs []string sqliteStore, ok := store.(*sqlite.SQLiteStorage) if !ok { - fmt.Fprintf(os.Stderr, "Error: wisp gc requires SQLite storage backend\n") + fmt.Fprintf(os.Stderr, "Error: ephemeral gc requires SQLite storage backend\n") os.Exit(1) } @@ -617,7 +617,7 @@ func runWispGC(cmd *cobra.Command, args []string) { cleanedIDs = append(cleanedIDs, issue.ID) } - result := WispGCResult{ + result := EphemeralGCResult{ CleanedIDs: cleanedIDs, CleanedCount: len(cleanedIDs), } @@ -627,25 +627,25 @@ func runWispGC(cmd *cobra.Command, args []string) { return } - fmt.Printf("%s Cleaned %d abandoned wisp(s)\n", ui.RenderPass("✓"), result.CleanedCount) + fmt.Printf("%s Cleaned %d abandoned ephemeral issue(s)\n", ui.RenderPass("✓"), result.CleanedCount) for _, id := range cleanedIDs { fmt.Printf(" - %s\n", id) } } func init() { - // Wisp create command flags - wispCreateCmd.Flags().StringSlice("var", []string{}, "Variable substitution (key=value)") - wispCreateCmd.Flags().Bool("dry-run", false, "Preview what would be created") + // Ephemeral create command flags + ephemeralCreateCmd.Flags().StringSlice("var", []string{}, "Variable substitution (key=value)") + ephemeralCreateCmd.Flags().Bool("dry-run", false, "Preview what would be created") - wispListCmd.Flags().Bool("all", false, "Include closed wisps") + ephemeralListCmd.Flags().Bool("all", false, "Include closed ephemeral issues") - wispGCCmd.Flags().Bool("dry-run", false, "Preview what would be cleaned") - wispGCCmd.Flags().String("age", "1h", "Age threshold for abandoned wisp detection") - wispGCCmd.Flags().Bool("all", false, "Also clean closed wisps older than threshold") + ephemeralGCCmd.Flags().Bool("dry-run", false, "Preview what would be cleaned") + ephemeralGCCmd.Flags().String("age", "1h", "Age threshold for abandoned ephemeral issue detection") + ephemeralGCCmd.Flags().Bool("all", false, "Also clean closed ephemeral issues older than threshold") - wispCmd.AddCommand(wispCreateCmd) - wispCmd.AddCommand(wispListCmd) - wispCmd.AddCommand(wispGCCmd) - rootCmd.AddCommand(wispCmd) + ephemeralCmd.AddCommand(ephemeralCreateCmd) + ephemeralCmd.AddCommand(ephemeralListCmd) + ephemeralCmd.AddCommand(ephemeralGCCmd) + rootCmd.AddCommand(ephemeralCmd) } diff --git a/cmd/bd/export.go b/cmd/bd/export.go index b669f174..4308fd87 100644 --- a/cmd/bd/export.go +++ b/cmd/bd/export.go @@ -362,7 +362,7 @@ Examples: // Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL. filtered := make([]*types.Issue, 0, len(issues)) for _, issue := range issues { - if !issue.Wisp { + if !issue.Ephemeral { filtered = append(filtered, issue) } } diff --git a/cmd/bd/gate.go b/cmd/bd/gate.go index e86fb220..eace0e43 100644 --- a/cmd/bd/gate.go +++ b/cmd/bd/gate.go @@ -157,7 +157,7 @@ Examples: Status: types.StatusOpen, Priority: 1, // Gates are typically high priority // Assignee left empty - orchestrator decides who processes gates - Wisp: true, // Gates are wisps (ephemeral) + Ephemeral: true, // Gates are wisps (ephemeral) AwaitType: awaitType, AwaitID: awaitID, Timeout: timeout, diff --git a/cmd/bd/hook.go b/cmd/bd/hook.go index 9e098fa0..99482589 100644 --- a/cmd/bd/hook.go +++ b/cmd/bd/hook.go @@ -87,8 +87,8 @@ func runHook(cmd *cobra.Command, args []string) { for _, issue := range issues { phase := "mol" - if issue.Wisp { - phase = "wisp" + if issue.Ephemeral { + phase = "ephemeral" } fmt.Printf(" 📌 %s (%s) - %s\n", issue.ID, phase, issue.Status) fmt.Printf(" %s\n", issue.Title) diff --git a/cmd/bd/info.go b/cmd/bd/info.go index ff8c1094..ca5b3923 100644 --- a/cmd/bd/info.go +++ b/cmd/bd/info.go @@ -292,6 +292,7 @@ var versionChanges = []VersionChange{ Version: "0.37.0", Date: "2025-12-26", Changes: []string{ + "BREAKING: Ephemeral API rename (bd-o18s) - Wisp→Ephemeral: JSON 'wisp'→'ephemeral', bd wisp→bd ephemeral", "NEW: bd gate create/show/list/close/wait (bd-udsi) - Async coordination primitives for agent workflows", "NEW: bd gate eval (gt-twjr5.2) - Evaluate timer gates and GitHub gates (gh:run, gh:pr, mail)", "NEW: bd gate approve (gt-twjr5.4) - Human gate approval command", diff --git a/cmd/bd/mol.go b/cmd/bd/mol.go index 5cbc673a..ea65c62b 100644 --- a/cmd/bd/mol.go +++ b/cmd/bd/mol.go @@ -21,7 +21,7 @@ import ( // bd mol catalog # List available protos // bd mol show # Show proto/molecule structure // bd pour --var key=value # Instantiate proto → persistent mol -// bd wisp create --var key=value # Instantiate proto → ephemeral wisp +// bd ephemeral create --var key=value # Instantiate proto → ephemeral wisp // MoleculeLabel is the label used to identify molecules (templates) // Molecules use the same label as templates - they ARE templates with workflow semantics @@ -55,7 +55,7 @@ Commands: See also: bd pour # Instantiate as persistent mol (liquid phase) - bd wisp create # Instantiate as ephemeral wisp (vapor phase)`, + bd ephemeral create # Instantiate as ephemeral wisp (vapor phase)`, } // ============================================================================= @@ -72,7 +72,7 @@ func spawnMolecule(ctx context.Context, s storage.Storage, subgraph *MoleculeSub Vars: vars, Assignee: assignee, Actor: actorName, - Wisp: ephemeral, + Ephemeral: ephemeral, Prefix: prefix, } return cloneSubgraph(ctx, s, subgraph, opts) diff --git a/cmd/bd/mol_bond.go b/cmd/bd/mol_bond.go index 00776f06..e5d2c1bd 100644 --- a/cmd/bd/mol_bond.go +++ b/cmd/bd/mol_bond.go @@ -40,12 +40,12 @@ Bond types: Phase control: By default, spawned protos follow the target's phase: - - Attaching to mol (Wisp=false) → spawns as persistent (Wisp=false) - - Attaching to wisp (Wisp=true) → spawns as ephemeral (Wisp=true) + - Attaching to mol (Ephemeral=false) → spawns as persistent (Ephemeral=false) + - Attaching to ephemeral issue (Ephemeral=true) → spawns as ephemeral (Ephemeral=true) Override with: - --pour Force spawn as liquid (persistent, Wisp=false) - --wisp Force spawn as vapor (ephemeral, Wisp=true, excluded from JSONL export) + --pour Force spawn as liquid (persistent, Ephemeral=false) + --ephemeral Force spawn as vapor (ephemeral, Ephemeral=true, excluded from JSONL export) Dynamic bonding (Christmas Ornament pattern): Use --ref to specify a custom child reference with variable substitution. @@ -57,7 +57,7 @@ Dynamic bonding (Christmas Ornament pattern): Use cases: - Found important bug during patrol? Use --pour to persist it - - Need ephemeral diagnostic on persistent feature? Use --wisp + - Need ephemeral diagnostic on persistent feature? Use --ephemeral - Spawning per-worker arms on a patrol? Use --ref for readable IDs Examples: @@ -66,7 +66,7 @@ Examples: bd mol bond mol-feature bd-abc123 # Attach proto to molecule bd mol bond bd-abc123 bd-def456 # Join two molecules bd mol bond mol-critical-bug wisp-patrol --pour # Persist found bug - bd mol bond mol-temp-check bd-feature --wisp # Ephemeral diagnostic + bd mol bond mol-temp-check bd-feature --ephemeral # Ephemeral diagnostic bd mol bond mol-arm bd-patrol --ref arm-{{name}} --var name=ace # Dynamic child ID`, Args: cobra.ExactArgs(2), Run: runMolBond, @@ -102,20 +102,20 @@ func runMolBond(cmd *cobra.Command, args []string) { customTitle, _ := cmd.Flags().GetString("as") dryRun, _ := cmd.Flags().GetBool("dry-run") varFlags, _ := cmd.Flags().GetStringSlice("var") - wisp, _ := cmd.Flags().GetBool("wisp") + ephemeral, _ := cmd.Flags().GetBool("ephemeral") pour, _ := cmd.Flags().GetBool("pour") childRef, _ := cmd.Flags().GetString("ref") // Validate phase flags are not both set - if wisp && pour { - fmt.Fprintf(os.Stderr, "Error: cannot use both --wisp and --pour\n") + if ephemeral && pour { + fmt.Fprintf(os.Stderr, "Error: cannot use both --ephemeral and --pour\n") os.Exit(1) } - // 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) + // All issues go in the main store; ephemeral vs pour determines the Wisp flag + // --ephemeral: create with Ephemeral=true (ephemeral, excluded from JSONL export) + // --pour: create with Ephemeral=false (persistent, exported to JSONL) + // Default: follow target's phase (ephemeral if target is ephemeral, otherwise persistent) // Validate bond type if bondType != types.BondTypeSequential && bondType != types.BondTypeParallel && bondType != types.BondTypeConditional { @@ -181,8 +181,8 @@ func runMolBond(cmd *cobra.Command, args []string) { fmt.Printf(" B: %s (%s)\n", issueB.Title, operandType(bIsProto)) } fmt.Printf(" Bond type: %s\n", bondType) - if wisp { - fmt.Printf(" Phase override: vapor (--wisp)\n") + if ephemeral { + fmt.Printf(" Phase override: vapor (--ephemeral)\n") } else if pour { fmt.Printf(" Phase override: liquid (--pour)\n") } @@ -240,16 +240,16 @@ func runMolBond(cmd *cobra.Command, args []string) { case aIsProto && !bIsProto: // Pass subgraph directly if cooked from formula if cookedA { - result, err = bondProtoMolWithSubgraph(ctx, store, subgraphA, issueA, issueB, bondType, vars, childRef, actor, wisp, pour) + result, err = bondProtoMolWithSubgraph(ctx, store, subgraphA, issueA, issueB, bondType, vars, childRef, actor, ephemeral, pour) } else { - result, err = bondProtoMol(ctx, store, issueA, issueB, bondType, vars, childRef, actor, wisp, pour) + result, err = bondProtoMol(ctx, store, issueA, issueB, bondType, vars, childRef, actor, ephemeral, pour) } case !aIsProto && bIsProto: // Pass subgraph directly if cooked from formula if cookedB { - result, err = bondProtoMolWithSubgraph(ctx, store, subgraphB, issueB, issueA, bondType, vars, childRef, actor, wisp, pour) + result, err = bondProtoMolWithSubgraph(ctx, store, subgraphB, issueB, issueA, bondType, vars, childRef, actor, ephemeral, pour) } else { - result, err = bondMolProto(ctx, store, issueA, issueB, bondType, vars, childRef, actor, wisp, pour) + result, err = bondMolProto(ctx, store, issueA, issueB, bondType, vars, childRef, actor, ephemeral, pour) } default: result, err = bondMolMol(ctx, store, issueA, issueB, bondType, actor) @@ -273,10 +273,10 @@ func runMolBond(cmd *cobra.Command, args []string) { if result.Spawned > 0 { fmt.Printf(" Spawned: %d issues\n", result.Spawned) } - if wisp { - fmt.Printf(" Phase: vapor (ephemeral, Wisp=true)\n") + if ephemeral { + fmt.Printf(" Phase: vapor (ephemeral, Ephemeral=true)\n") } else if pour { - fmt.Printf(" Phase: liquid (persistent, Wisp=false)\n") + fmt.Printf(" Phase: liquid (persistent, Ephemeral=false)\n") } } @@ -386,12 +386,12 @@ 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). // protoSubgraph can be nil if proto is from DB (will be loaded), or pre-loaded for formulas. -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) { - return bondProtoMolWithSubgraph(ctx, s, nil, proto, mol, bondType, vars, childRef, actorName, wispFlag, pourFlag) +func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, ephemeralFlag, pourFlag bool) (*BondResult, error) { + return bondProtoMolWithSubgraph(ctx, s, nil, proto, mol, bondType, vars, childRef, actorName, ephemeralFlag, pourFlag) } // bondProtoMolWithSubgraph is the internal implementation that accepts a pre-loaded subgraph. -func bondProtoMolWithSubgraph(ctx context.Context, s storage.Storage, protoSubgraph *TemplateSubgraph, proto, mol *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, wispFlag, pourFlag bool) (*BondResult, error) { +func bondProtoMolWithSubgraph(ctx context.Context, s storage.Storage, protoSubgraph *TemplateSubgraph, proto, mol *types.Issue, bondType string, vars map[string]string, childRef string, actorName string, ephemeralFlag, pourFlag bool) (*BondResult, error) { // Use provided subgraph or load from DB subgraph := protoSubgraph if subgraph == nil { @@ -414,20 +414,20 @@ func bondProtoMolWithSubgraph(ctx context.Context, s storage.Storage, protoSubgr 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 + // Determine ephemeral flag based on explicit flags or target's phase + // --ephemeral: force ephemeral=true, --pour: force ephemeral=false, neither: follow target + makeEphemeral := mol.Ephemeral // Default: follow target's phase + if ephemeralFlag { + makeEphemeral = true } else if pourFlag { - makeWisp = false + makeEphemeral = false } // Build CloneOptions for spawning opts := CloneOptions{ Vars: vars, Actor: actorName, - Wisp: makeWisp, + Ephemeral: makeEphemeral, } // Dynamic bonding: use custom IDs if childRef is provided @@ -482,9 +482,9 @@ func bondProtoMolWithSubgraph(ctx context.Context, s storage.Storage, protoSubgr } // 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, wispFlag, pourFlag 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, ephemeralFlag, pourFlag bool) (*BondResult, error) { // Same as bondProtoMol but with arguments swapped - return bondProtoMol(ctx, s, proto, mol, bondType, vars, childRef, actorName, wispFlag, pourFlag) + return bondProtoMol(ctx, s, proto, mol, bondType, vars, childRef, actorName, ephemeralFlag, pourFlag) } // bondMolMol bonds two molecules together @@ -630,8 +630,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, Wisp=true)") - molBondCmd.Flags().Bool("pour", false, "Force spawn as liquid (persistent, Wisp=false)") + molBondCmd.Flags().Bool("ephemeral", false, "Force spawn as vapor (ephemeral, Ephemeral=true)") + molBondCmd.Flags().Bool("pour", false, "Force spawn as liquid (persistent, Ephemeral=false)") molBondCmd.Flags().String("ref", "", "Custom child reference with {{var}} substitution (e.g., arm-{{polecat_name}})") molCmd.AddCommand(molBondCmd) diff --git a/cmd/bd/mol_burn.go b/cmd/bd/mol_burn.go index 04da097b..6d053389 100644 --- a/cmd/bd/mol_burn.go +++ b/cmd/bd/mol_burn.go @@ -23,8 +23,8 @@ completely removes the wisp with no trace. Use this for: - Test/debug wisps you don't want to preserve The burn operation: - 1. Verifies the molecule has Wisp=true (is ephemeral) - 2. Deletes the molecule and all its wisp children + 1. Verifies the molecule has Ephemeral=true (is ephemeral) + 2. Deletes the molecule and all its ephemeral children 3. No digest is created (use 'bd mol squash' if you want a digest) CAUTION: This is a destructive operation. The wisp's data will be @@ -81,8 +81,8 @@ func runMolBurn(cmd *cobra.Command, args []string) { } // Verify it's a wisp - if !rootIssue.Wisp { - fmt.Fprintf(os.Stderr, "Error: molecule %s is not a wisp (Wisp=false)\n", resolvedID) + if !rootIssue.Ephemeral { + fmt.Fprintf(os.Stderr, "Error: molecule %s is not a wisp (Ephemeral=false)\n", resolvedID) fmt.Fprintf(os.Stderr, "Hint: mol burn only works with wisp molecules\n") fmt.Fprintf(os.Stderr, " Use 'bd delete' to remove non-wisp issues\n") os.Exit(1) @@ -98,7 +98,7 @@ func runMolBurn(cmd *cobra.Command, args []string) { // Collect wisp issue IDs to delete (only delete wisps, not regular children) var wispIDs []string for _, issue := range subgraph.Issues { - if issue.Wisp { + if issue.Ephemeral { wispIDs = append(wispIDs, issue.ID) } } @@ -120,7 +120,7 @@ func runMolBurn(cmd *cobra.Command, args []string) { fmt.Printf("Root: %s\n", subgraph.Root.Title) fmt.Printf("\nWisp issues to delete (%d total):\n", len(wispIDs)) for _, issue := range subgraph.Issues { - if !issue.Wisp { + if !issue.Ephemeral { continue } status := string(issue.Status) @@ -166,7 +166,7 @@ func runMolBurn(cmd *cobra.Command, args []string) { } fmt.Printf("%s Burned wisp: %d issues deleted\n", ui.RenderPass("✓"), result.DeletedCount) - fmt.Printf(" Wisp: %s\n", resolvedID) + fmt.Printf(" Ephemeral: %s\n", resolvedID) fmt.Printf(" No digest created.\n") } diff --git a/cmd/bd/mol_catalog.go b/cmd/bd/mol_catalog.go index e5e1bf14..d2acdf36 100644 --- a/cmd/bd/mol_catalog.go +++ b/cmd/bd/mol_catalog.go @@ -23,7 +23,7 @@ var molCatalogCmd = &cobra.Command{ Use: "catalog", Aliases: []string{"list", "ls"}, Short: "List available molecule formulas", - Long: `List formulas available for bd pour / bd wisp create. + Long: `List formulas available for bd pour / bd ephemeral create. Formulas are ephemeral proto definitions stored as .formula.json files. They are cooked inline when pouring, never stored as database beads. @@ -93,11 +93,11 @@ Search paths (in priority order): fmt.Println(" bd mol distill my-workflow") fmt.Println("\nTo instantiate from formula:") fmt.Println(" bd pour --var key=value # persistent mol") - fmt.Println(" bd wisp create --var key=value # ephemeral wisp") + fmt.Println(" bd ephemeral create --var key=value # ephemeral wisp") return } - fmt.Printf("%s\n\n", ui.RenderPass("Formulas (for bd pour / bd wisp create):")) + fmt.Printf("%s\n\n", ui.RenderPass("Formulas (for bd pour / bd ephemeral create):")) // Group by type for display byType := make(map[string][]CatalogEntry) diff --git a/cmd/bd/mol_squash.go b/cmd/bd/mol_squash.go index 6b922a5d..b4236b24 100644 --- a/cmd/bd/mol_squash.go +++ b/cmd/bd/mol_squash.go @@ -18,17 +18,17 @@ import ( var molSquashCmd = &cobra.Command{ Use: "squash ", Short: "Compress molecule execution into a digest", - Long: `Squash a molecule's wisp children into a single digest issue. + Long: `Squash a molecule's ephemeral children into a single digest issue. -This command collects all wisp child issues of a molecule (Wisp=true), +This command collects all ephemeral child issues of a molecule (Ephemeral=true), generates a summary digest, and promotes the wisps to persistent by clearing their Wisp flag (or optionally deletes them). The squash operation: 1. Loads the molecule and all its children - 2. Filters to only wisps (ephemeral issues with Wisp=true) + 2. Filters to only wisps (ephemeral issues with Ephemeral=true) 3. Generates a digest (summary of work done) - 4. Creates a permanent digest issue (Wisp=false) + 4. Creates a permanent digest issue (Ephemeral=false) 5. Clears Wisp flag on children (promotes to persistent) OR deletes them with --delete-children @@ -95,13 +95,13 @@ func runMolSquash(cmd *cobra.Command, args []string) { os.Exit(1) } - // Filter to only wisp children (exclude root) + // Filter to only ephemeral children (exclude root) var wispChildren []*types.Issue for _, issue := range subgraph.Issues { if issue.ID == subgraph.Root.ID { continue // Skip root } - if issue.Wisp { + if issue.Ephemeral { wispChildren = append(wispChildren, issue) } } @@ -113,13 +113,13 @@ func runMolSquash(cmd *cobra.Command, args []string) { SquashedCount: 0, }) } else { - fmt.Printf("No wisp children found for molecule %s\n", moleculeID) + fmt.Printf("No ephemeral children found for molecule %s\n", moleculeID) } return } if dryRun { - fmt.Printf("\nDry run: would squash %d wisp children of %s\n\n", len(wispChildren), moleculeID) + fmt.Printf("\nDry run: would squash %d ephemeral children of %s\n\n", len(wispChildren), moleculeID) fmt.Printf("Root: %s\n", subgraph.Root.Title) fmt.Printf("\nWisp children to squash:\n") for _, issue := range wispChildren { @@ -247,7 +247,7 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c CloseReason: fmt.Sprintf("Squashed from %d wisps", len(children)), Priority: root.Priority, IssueType: types.TypeTask, - Wisp: false, // Digest is permanent, not a wisp + Ephemeral: false, // Digest is permanent, not a wisp ClosedAt: &now, } @@ -283,7 +283,7 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c return nil, err } - // Delete wisp children (outside transaction for better error handling) + // Delete ephemeral children (outside transaction for better error handling) if !keepChildren { deleted, err := deleteWispChildren(ctx, s, childIDs) if err != nil { @@ -319,7 +319,7 @@ func deleteWispChildren(ctx context.Context, s storage.Storage, ids []string) (i func init() { molSquashCmd.Flags().Bool("dry-run", false, "Preview what would be squashed") - molSquashCmd.Flags().Bool("keep-children", false, "Don't delete wisp children after squash") + molSquashCmd.Flags().Bool("keep-children", false, "Don't delete ephemeral children after squash") molSquashCmd.Flags().String("summary", "", "Agent-provided summary (bypasses auto-generation)") molCmd.AddCommand(molSquashCmd) diff --git a/cmd/bd/mol_test.go b/cmd/bd/mol_test.go index cf57a18b..a8d18728 100644 --- a/cmd/bd/mol_test.go +++ b/cmd/bd/mol_test.go @@ -489,7 +489,7 @@ func TestSquashMolecule(t *testing.T) { Status: types.StatusClosed, Priority: 2, IssueType: types.TypeTask, - Wisp: true, + Ephemeral: true, CloseReason: "Completed design", } child2 := &types.Issue{ @@ -498,7 +498,7 @@ func TestSquashMolecule(t *testing.T) { Status: types.StatusClosed, Priority: 2, IssueType: types.TypeTask, - Wisp: true, + Ephemeral: true, CloseReason: "Code merged", } @@ -547,7 +547,7 @@ func TestSquashMolecule(t *testing.T) { if err != nil { t.Fatalf("Failed to get digest: %v", err) } - if digest.Wisp { + if digest.Ephemeral { t.Error("Digest should NOT be ephemeral") } if digest.Status != types.StatusClosed { @@ -595,7 +595,7 @@ func TestSquashMoleculeWithDelete(t *testing.T) { Status: types.StatusClosed, Priority: 2, IssueType: types.TypeTask, - Wisp: true, + Ephemeral: true, } if err := s.CreateIssue(ctx, child, "test"); err != nil { t.Fatalf("Failed to create child: %v", err) @@ -705,7 +705,7 @@ func TestSquashMoleculeWithAgentSummary(t *testing.T) { Status: types.StatusClosed, Priority: 2, IssueType: types.TypeTask, - Wisp: true, + Ephemeral: true, CloseReason: "Done", } if err := s.CreateIssue(ctx, child, "test"); err != nil { @@ -1304,14 +1304,14 @@ func TestWispFilteringFromExport(t *testing.T) { Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, - Wisp: false, + Ephemeral: false, } wispIssue := &types.Issue{ Title: "Wisp Issue", Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask, - Wisp: true, + Ephemeral: true, } if err := s.CreateIssue(ctx, normalIssue, "test"); err != nil { @@ -1333,7 +1333,7 @@ func TestWispFilteringFromExport(t *testing.T) { // Filter wisp issues (simulating export behavior) exportableIssues := make([]*types.Issue, 0) for _, issue := range allIssues { - if !issue.Wisp { + if !issue.Ephemeral { exportableIssues = append(exportableIssues, issue) } } diff --git a/cmd/bd/nodb.go b/cmd/bd/nodb.go index 5bbcfd15..5ba47368 100644 --- a/cmd/bd/nodb.go +++ b/cmd/bd/nodb.go @@ -218,7 +218,7 @@ func writeIssuesToJSONL(memStore *memory.MemoryStorage, beadsDir string) error { // Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL. filtered := make([]*types.Issue, 0, len(issues)) for _, issue := range issues { - if !issue.Wisp { + if !issue.Ephemeral { filtered = append(filtered, issue) } } diff --git a/cmd/bd/sync_export.go b/cmd/bd/sync_export.go index 606ebe6d..ea73b203 100644 --- a/cmd/bd/sync_export.go +++ b/cmd/bd/sync_export.go @@ -65,7 +65,7 @@ func exportToJSONL(ctx context.Context, jsonlPath string) error { // This prevents "zombie" issues that resurrect after mol squash deletes them. filteredIssues := make([]*types.Issue, 0, len(issues)) for _, issue := range issues { - if issue.Wisp { + if issue.Ephemeral { continue } filteredIssues = append(filteredIssues, issue) diff --git a/cmd/bd/template.go b/cmd/bd/template.go index 24e7c2f9..8d4e6e04 100644 --- a/cmd/bd/template.go +++ b/cmd/bd/template.go @@ -42,10 +42,10 @@ type InstantiateResult struct { // CloneOptions controls how the subgraph is cloned during spawn/bond type CloneOptions struct { - Vars map[string]string // Variable substitutions for {{key}} placeholders - Assignee string // Assign the root epic to this agent/user - Actor string // Actor performing the operation - Wisp bool // If true, spawned issues are marked for bulk deletion + Vars map[string]string // Variable substitutions for {{key}} placeholders + Assignee string // Assign the root epic to this agent/user + Actor string // Actor performing the operation + Ephemeral bool // If true, spawned issues are marked for bulk deletion Prefix string // Override prefix for ID generation (bd-hobo: distinct prefixes) // Dynamic bonding fields (for Christmas Ornament pattern) @@ -327,7 +327,7 @@ Example: Vars: vars, Assignee: assignee, Actor: actor, - Wisp: false, + Ephemeral: false, } var result *InstantiateResult if daemonClient != nil { @@ -713,7 +713,7 @@ func cloneSubgraphViaDaemon(client *rpc.Client, subgraph *TemplateSubgraph, opts AcceptanceCriteria: substituteVariables(oldIssue.AcceptanceCriteria, opts.Vars), Assignee: issueAssignee, EstimatedMinutes: oldIssue.EstimatedMinutes, - Wisp: opts.Wisp, + Ephemeral: opts.Ephemeral, IDPrefix: opts.Prefix, // bd-hobo: distinct prefixes for mols/wisps } @@ -960,7 +960,7 @@ func cloneSubgraph(ctx context.Context, s storage.Storage, subgraph *TemplateSub IssueType: oldIssue.IssueType, Assignee: issueAssignee, EstimatedMinutes: oldIssue.EstimatedMinutes, - Wisp: opts.Wisp, // bd-2vh3: mark for cleanup when closed + Ephemeral: opts.Ephemeral, // bd-2vh3: mark for cleanup when closed IDPrefix: opts.Prefix, // bd-hobo: distinct prefixes for mols/wisps CreatedAt: time.Now(), UpdatedAt: time.Now(), diff --git a/cmd/bd/thread_test.go b/cmd/bd/thread_test.go index a529796a..9b2881fc 100644 --- a/cmd/bd/thread_test.go +++ b/cmd/bd/thread_test.go @@ -27,7 +27,7 @@ func TestThreadTraversal(t *testing.T) { IssueType: types.TypeMessage, Assignee: "worker", Sender: "manager", - Wisp: true, + Ephemeral: true, CreatedAt: now, UpdatedAt: now, } @@ -43,7 +43,7 @@ func TestThreadTraversal(t *testing.T) { IssueType: types.TypeMessage, Assignee: "manager", Sender: "worker", - Wisp: true, + Ephemeral: true, CreatedAt: now.Add(time.Minute), UpdatedAt: now.Add(time.Minute), } @@ -59,7 +59,7 @@ func TestThreadTraversal(t *testing.T) { IssueType: types.TypeMessage, Assignee: "worker", Sender: "manager", - Wisp: true, + Ephemeral: true, CreatedAt: now.Add(2 * time.Minute), UpdatedAt: now.Add(2 * time.Minute), } @@ -190,7 +190,7 @@ func TestThreadTraversalEmptyThread(t *testing.T) { IssueType: types.TypeMessage, Assignee: "user", Sender: "sender", - Wisp: true, + Ephemeral: true, CreatedAt: now, UpdatedAt: now, } @@ -228,7 +228,7 @@ func TestThreadTraversalBranching(t *testing.T) { IssueType: types.TypeMessage, Assignee: "user", Sender: "sender", - Wisp: true, + Ephemeral: true, CreatedAt: now, UpdatedAt: now, } @@ -245,7 +245,7 @@ func TestThreadTraversalBranching(t *testing.T) { IssueType: types.TypeMessage, Assignee: "sender", Sender: "user", - Wisp: true, + Ephemeral: true, CreatedAt: now.Add(time.Minute), UpdatedAt: now.Add(time.Minute), } @@ -261,7 +261,7 @@ func TestThreadTraversalBranching(t *testing.T) { IssueType: types.TypeMessage, Assignee: "sender", Sender: "another-user", - Wisp: true, + Ephemeral: true, CreatedAt: now.Add(2 * time.Minute), UpdatedAt: now.Add(2 * time.Minute), } @@ -364,7 +364,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) { IssueType: types.TypeMessage, Assignee: "user", Sender: "sender", - Wisp: true, + Ephemeral: true, CreatedAt: now, UpdatedAt: now, } @@ -380,7 +380,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) { IssueType: types.TypeMessage, Assignee: "user", Sender: "sender", - Wisp: true, + Ephemeral: true, CreatedAt: now.Add(time.Minute), UpdatedAt: now.Add(time.Minute), } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 7547b967..09ba38f1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -275,7 +275,7 @@ open ──▶ in_progress ──▶ closed ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ bd wisp create │───▶│ Wisp Issues │───▶│ bd mol squash │ +│ bd ephemeral create │───▶│ Wisp Issues │───▶│ bd mol squash │ │ (from template) │ │ (local-only) │ │ (→ digest) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` diff --git a/docs/CLI_REFERENCE.md b/docs/CLI_REFERENCE.md index 960cd74a..3d4170da 100644 --- a/docs/CLI_REFERENCE.md +++ b/docs/CLI_REFERENCE.md @@ -351,7 +351,7 @@ Beads uses a chemistry metaphor for template-based workflows. See [MOLECULES.md] |-------|-------|---------|---------| | Solid | Proto | `.beads/` | `bd mol catalog` | | Liquid | Mol | `.beads/` | `bd pour` | -| Vapor | Wisp | `.beads/` (Wisp=true, not exported) | `bd wisp create` | +| Vapor | Wisp | `.beads/` (Wisp=true, not exported) | `bd ephemeral create` | ### Proto/Template Commands @@ -385,17 +385,17 @@ bd pour --attach --json ### Wisp Commands ```bash -# Instantiate proto as ephemeral wisp (solid → vapor) -bd wisp create --var key=value --json +# Instantiate proto as ephemeral issue (solid → vapor) +bd ephemeral create --var key=value --json # List all wisps -bd wisp list --json -bd wisp list --all --json # Include closed +bd ephemeral list --json +bd ephemeral list --all --json # Include closed # Garbage collect orphaned wisps -bd wisp gc --json -bd wisp gc --age 24h --json # Custom age threshold -bd wisp gc --dry-run # Preview what would be cleaned +bd ephemeral gc --json +bd ephemeral gc --age 24h --json # Custom age threshold +bd ephemeral gc --dry-run # Preview what would be cleaned ``` ### Bonding (Combining Work) @@ -424,29 +424,29 @@ bd mol bond --dry-run ```bash # Compress wisp to permanent digest -bd mol squash --json +bd mol squash --json # With agent-provided summary -bd mol squash --summary "Work completed" --json +bd mol squash --summary "Work completed" --json # Preview -bd mol squash --dry-run +bd mol squash --dry-run # Keep wisp children after squash -bd mol squash --keep-children --json +bd mol squash --keep-children --json ``` ### Burn (Discard Wisp) ```bash # Delete wisp without digest (destructive) -bd mol burn --json +bd mol burn --json # Preview -bd mol burn --dry-run +bd mol burn --dry-run # Skip confirmation -bd mol burn --force --json +bd mol burn --force --json ``` **Note:** Most mol commands require `--no-daemon` flag when daemon is running. diff --git a/docs/DELETIONS.md b/docs/DELETIONS.md index 2d067e93..ef4086e6 100644 --- a/docs/DELETIONS.md +++ b/docs/DELETIONS.md @@ -202,7 +202,7 @@ The 1-hour grace period ensures tombstones propagate even with minor clock drift ## Wisps: Intentional Tombstone Bypass -**Wisps** (ephemeral issues created by `bd wisp create`) are intentionally excluded from tombstone tracking. +**Wisps** (ephemeral issues created by `bd ephemeral create`) are intentionally excluded from tombstone tracking. ### Why Wisps Don't Need Tombstones diff --git a/docs/MOLECULES.md b/docs/MOLECULES.md index 4dba4cec..132f33fb 100644 --- a/docs/MOLECULES.md +++ b/docs/MOLECULES.md @@ -129,7 +129,7 @@ For reusable workflows, beads uses a chemistry metaphor: ```bash bd pour # Proto → Mol (persistent instance) -bd wisp create # Proto → Wisp (ephemeral instance) +bd ephemeral create # Proto → Wisp (ephemeral instance) bd mol squash # Mol/Wisp → Digest (permanent record) bd mol burn # Wisp → nothing (discard) ``` @@ -227,10 +227,10 @@ bd close --reason "Done" Wisps accumulate if not squashed/burned: ```bash -bd wisp list # Check for orphans +bd ephemeral list # Check for orphans bd mol squash # Create digest bd mol burn # Or discard -bd wisp gc # Garbage collect old wisps +bd ephemeral gc # Garbage collect old wisps ``` ## Layer Cake Architecture @@ -273,7 +273,7 @@ bd dep tree # Show dependency tree ```bash bd pour --var k=v # Template → persistent mol -bd wisp create # Template → ephemeral wisp +bd ephemeral create # Template → ephemeral wisp bd mol bond A B # Connect work graphs bd mol squash # Compress to digest bd mol burn # Discard without record diff --git a/internal/rpc/protocol.go b/internal/rpc/protocol.go index 9e228716..e6099c94 100644 --- a/internal/rpc/protocol.go +++ b/internal/rpc/protocol.go @@ -89,11 +89,11 @@ type CreateArgs struct { WaitsFor string `json:"waits_for,omitempty"` // Spawner issue ID to wait for WaitsForGate string `json:"waits_for_gate,omitempty"` // Gate type: all-children or any-children // Messaging fields (bd-kwro) - Sender string `json:"sender,omitempty"` // Who sent this (for messages) - Wisp bool `json:"wisp,omitempty"` // Wisp = ephemeral vapor from the Steam Engine; bulk-deleted when closed + Sender string `json:"sender,omitempty"` // Who sent this (for messages) + Ephemeral bool `json:"ephemeral,omitempty"` // If true, not exported to JSONL; bulk-deleted when closed RepliesTo string `json:"replies_to,omitempty"` // Issue ID for conversation threading // ID generation (bd-hobo) - IDPrefix string `json:"id_prefix,omitempty"` // Override prefix for ID generation (mol, wisp, etc.) + IDPrefix string `json:"id_prefix,omitempty"` // Override prefix for ID generation (mol, eph, etc.) CreatedBy string `json:"created_by,omitempty"` // Who created the issue } @@ -115,8 +115,8 @@ type UpdateArgs struct { RemoveLabels []string `json:"remove_labels,omitempty"` SetLabels []string `json:"set_labels,omitempty"` // Messaging fields (bd-kwro) - Sender *string `json:"sender,omitempty"` // Who sent this (for messages) - Wisp *bool `json:"wisp,omitempty"` // Wisp = ephemeral vapor from the Steam Engine; bulk-deleted when closed + Sender *string `json:"sender,omitempty"` // Who sent this (for messages) + Ephemeral *bool `json:"ephemeral,omitempty"` // If true, not exported to JSONL; bulk-deleted when closed RepliesTo *string `json:"replies_to,omitempty"` // Issue ID for conversation threading // Graph link fields (bd-fu83) RelatesTo *string `json:"relates_to,omitempty"` // JSON array of related issue IDs @@ -193,8 +193,8 @@ type ListArgs struct { // Parent filtering (bd-yqhh) ParentID string `json:"parent_id,omitempty"` - // Wisp filtering (bd-bkul) - Wisp *bool `json:"wisp,omitempty"` + // Ephemeral filtering (bd-bkul) + Ephemeral *bool `json:"ephemeral,omitempty"` } // CountArgs represents arguments for the count operation diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index d9d55f43..8f49d88a 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -81,8 +81,8 @@ func updatesFromArgs(a UpdateArgs) map[string]interface{} { if a.Sender != nil { u["sender"] = *a.Sender } - if a.Wisp != nil { - u["wisp"] = *a.Wisp + if a.Ephemeral != nil { + u["ephemeral"] = *a.Ephemeral } if a.RepliesTo != nil { u["replies_to"] = *a.RepliesTo @@ -176,8 +176,8 @@ func (s *Server) handleCreate(req *Request) Response { EstimatedMinutes: createArgs.EstimatedMinutes, Status: types.StatusOpen, // Messaging fields (bd-kwro) - Sender: createArgs.Sender, - Wisp: createArgs.Wisp, + Sender: createArgs.Sender, + Ephemeral: createArgs.Ephemeral, // NOTE: RepliesTo now handled via replies-to dependency (Decision 004) // ID generation (bd-hobo) IDPrefix: createArgs.IDPrefix, @@ -844,8 +844,8 @@ func (s *Server) handleList(req *Request) Response { filter.ParentID = &listArgs.ParentID } - // Wisp filtering (bd-bkul) - filter.Wisp = listArgs.Wisp + // Ephemeral filtering (bd-bkul) + filter.Ephemeral = listArgs.Ephemeral // Guard against excessive ID lists to avoid SQLite parameter limits const maxIDs = 1000 @@ -1475,7 +1475,7 @@ func (s *Server) handleGateCreate(req *Request) Response { Status: types.StatusOpen, Priority: 1, // Gates are typically high priority Assignee: "deacon/", - Wisp: true, // Gates are wisps (ephemeral) + Ephemeral: true, // Gates are wisps (ephemeral) AwaitType: args.AwaitType, AwaitID: args.AwaitID, Timeout: args.Timeout, diff --git a/internal/storage/sqlite/dependencies.go b/internal/storage/sqlite/dependencies.go index 282da900..11837cc9 100644 --- a/internal/storage/sqlite/dependencies.go +++ b/internal/storage/sqlite/dependencies.go @@ -885,7 +885,7 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type issue.Sender = sender.String } if wisp.Valid && wisp.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { @@ -1006,7 +1006,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows * issue.Sender = sender.String } if wisp.Valid && wisp.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { diff --git a/internal/storage/sqlite/graph_links_test.go b/internal/storage/sqlite/graph_links_test.go index 72f85908..b81a58c1 100644 --- a/internal/storage/sqlite/graph_links_test.go +++ b/internal/storage/sqlite/graph_links_test.go @@ -295,7 +295,7 @@ func TestRepliesTo(t *testing.T) { IssueType: types.TypeMessage, Sender: "alice", Assignee: "bob", - Wisp: true, + Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -307,7 +307,7 @@ func TestRepliesTo(t *testing.T) { IssueType: types.TypeMessage, Sender: "bob", Assignee: "alice", - Wisp: true, + Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -363,7 +363,7 @@ func TestRepliesTo_Chain(t *testing.T) { IssueType: types.TypeMessage, Sender: "user", Assignee: "inbox", - Wisp: true, + Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -415,7 +415,7 @@ func TestWispField(t *testing.T) { Status: types.StatusOpen, Priority: 2, IssueType: types.TypeMessage, - Wisp: true, + Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -426,7 +426,7 @@ func TestWispField(t *testing.T) { Status: types.StatusOpen, Priority: 2, IssueType: types.TypeTask, - Wisp: false, + Ephemeral: false, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -443,7 +443,7 @@ func TestWispField(t *testing.T) { if err != nil { t.Fatalf("GetIssue failed: %v", err) } - if !savedWisp.Wisp { + if !savedWisp.Ephemeral { t.Error("Wisp issue should have Wisp=true") } @@ -451,7 +451,7 @@ func TestWispField(t *testing.T) { if err != nil { t.Fatalf("GetIssue failed: %v", err) } - if savedPermanent.Wisp { + if savedPermanent.Ephemeral { t.Error("Permanent issue should have Wisp=false") } } @@ -468,7 +468,7 @@ func TestWispFilter(t *testing.T) { Status: types.StatusClosed, // Closed for cleanup test Priority: 2, IssueType: types.TypeMessage, - Wisp: true, + Ephemeral: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -483,7 +483,7 @@ func TestWispFilter(t *testing.T) { Status: types.StatusClosed, Priority: 2, IssueType: types.TypeTask, - Wisp: false, + Ephemeral: false, CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -497,7 +497,7 @@ func TestWispFilter(t *testing.T) { closedStatus := types.StatusClosed wispFilter := types.IssueFilter{ Status: &closedStatus, - Wisp: &wispTrue, + Ephemeral: &wispTrue, } wispIssues, err := store.SearchIssues(ctx, "", wispFilter) @@ -512,7 +512,7 @@ func TestWispFilter(t *testing.T) { wispFalse := false nonWispFilter := types.IssueFilter{ Status: &closedStatus, - Wisp: &wispFalse, + Ephemeral: &wispFalse, } permanentIssues, err := store.SearchIssues(ctx, "", nonWispFilter) diff --git a/internal/storage/sqlite/issues.go b/internal/storage/sqlite/issues.go index 41d221f3..7c566165 100644 --- a/internal/storage/sqlite/issues.go +++ b/internal/storage/sqlite/issues.go @@ -28,7 +28,7 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error } wisp := 0 - if issue.Wisp { + if issue.Ephemeral { wisp = 1 } pinned := 0 @@ -94,7 +94,7 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er } wisp := 0 - if issue.Wisp { + if issue.Ephemeral { wisp = 1 } pinned := 0 diff --git a/internal/storage/sqlite/multirepo.go b/internal/storage/sqlite/multirepo.go index 34f37fdb..c2509ff7 100644 --- a/internal/storage/sqlite/multirepo.go +++ b/internal/storage/sqlite/multirepo.go @@ -282,7 +282,7 @@ func (s *SQLiteStorage) upsertIssueInTx(ctx context.Context, tx *sql.Tx, issue * err := tx.QueryRowContext(ctx, `SELECT id FROM issues WHERE id = ?`, issue.ID).Scan(&existingID) wisp := 0 - if issue.Wisp { + if issue.Ephemeral { wisp = 1 } pinned := 0 diff --git a/internal/storage/sqlite/multirepo_export.go b/internal/storage/sqlite/multirepo_export.go index 48d1943e..0d79741f 100644 --- a/internal/storage/sqlite/multirepo_export.go +++ b/internal/storage/sqlite/multirepo_export.go @@ -54,7 +54,7 @@ func (s *SQLiteStorage) ExportToMultiRepo(ctx context.Context) (map[string]int, // Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL. filtered := make([]*types.Issue, 0, len(allIssues)) for _, issue := range allIssues { - if !issue.Wisp { + if !issue.Ephemeral { filtered = append(filtered, issue) } } diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 07325aaf..f11fe85f 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -349,7 +349,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue, issue.Sender = sender.String } if wisp.Valid && wisp.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { @@ -562,7 +562,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s issue.Sender = sender.String } if wisp.Valid && wisp.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { @@ -1652,8 +1652,8 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t } // Wisp filtering (bd-kwro.9) - if filter.Wisp != nil { - if *filter.Wisp { + if filter.Ephemeral != nil { + if *filter.Ephemeral { whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral' } else { whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)") diff --git a/internal/storage/sqlite/ready.go b/internal/storage/sqlite/ready.go index 7be64097..840d3d24 100644 --- a/internal/storage/sqlite/ready.go +++ b/internal/storage/sqlite/ready.go @@ -400,7 +400,7 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi issue.Sender = sender.String } if ephemeral.Valid && ephemeral.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { diff --git a/internal/storage/sqlite/transaction.go b/internal/storage/sqlite/transaction.go index b7e4067b..82607f4a 100644 --- a/internal/storage/sqlite/transaction.go +++ b/internal/storage/sqlite/transaction.go @@ -1089,8 +1089,8 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter } // Wisp filtering (bd-kwro.9) - if filter.Wisp != nil { - if *filter.Wisp { + if filter.Ephemeral != nil { + if *filter.Ephemeral { whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral' } else { whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)") @@ -1244,7 +1244,7 @@ func scanIssueRow(row scanner) (*types.Issue, error) { issue.Sender = sender.String } if wisp.Valid && wisp.Int64 != 0 { - issue.Wisp = true + issue.Ephemeral = true } // Pinned field (bd-7h5) if pinned.Valid && pinned.Int64 != 0 { diff --git a/internal/types/types.go b/internal/types/types.go index 105ca94b..5f7beed8 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -44,8 +44,8 @@ type Issue struct { OriginalType string `json:"original_type,omitempty"` // Issue type before deletion (for tombstones) // Messaging fields (bd-kwro): inter-agent communication support - Sender string `json:"sender,omitempty"` // Who sent this (for messages) - Wisp bool `json:"wisp,omitempty"` // Wisp = ephemeral vapor from the Steam Engine; bulk-deleted when closed + Sender string `json:"sender,omitempty"` // Who sent this (for messages) + Ephemeral bool `json:"ephemeral,omitempty"` // If true, not exported to JSONL; bulk-deleted when closed // NOTE: RepliesTo, RelatesTo, DuplicateOf, SupersededBy moved to dependencies table // per Decision 004 (Edge Schema Consolidation). Use dependency API instead. @@ -598,8 +598,8 @@ type IssueFilter struct { // Tombstone filtering (bd-1bu) IncludeTombstones bool // If false (default), exclude tombstones from results - // Wisp filtering (bd-kwro.9) - Wisp *bool // Filter by wisp flag (nil = any, true = only wisps, false = only non-wisps) + // Ephemeral filtering (bd-kwro.9) + Ephemeral *bool // Filter by ephemeral flag (nil = any, true = only ephemeral, false = only persistent) // Pinned filtering (bd-7h5) Pinned *bool // Filter by pinned flag (nil = any, true = only pinned, false = only non-pinned) diff --git a/skills/beads/references/MOLECULES.md b/skills/beads/references/MOLECULES.md index 9484b832..95d35314 100644 --- a/skills/beads/references/MOLECULES.md +++ b/skills/beads/references/MOLECULES.md @@ -84,7 +84,7 @@ bd mol spawn mol-release --var version=2.0 # With variable substitution **Chemistry shortcuts:** ```bash bd pour mol-feature # Shortcut for spawn --pour -bd wisp create mol-patrol # Explicit wisp creation +bd ephemeral create mol-patrol # Explicit wisp creation ``` ### Spawn with Immediate Execution @@ -164,7 +164,7 @@ bd mol bond mol-feature mol-deploy --as "Feature with Deploy" ### Creating Wisps ```bash -bd wisp create mol-patrol # From proto +bd ephemeral create mol-patrol # From proto bd mol spawn mol-patrol # Same (spawn defaults to wisp) bd mol spawn mol-check --var target=db # With variables ``` @@ -172,8 +172,8 @@ bd mol spawn mol-check --var target=db # With variables ### Listing Wisps ```bash -bd wisp list # List all wisps -bd wisp list --json # Machine-readable +bd ephemeral list # List all wisps +bd ephemeral list --json # Machine-readable ``` ### Ending Wisps @@ -198,7 +198,7 @@ Use burn for routine work with no archival value. ### Garbage Collection ```bash -bd wisp gc # Clean up orphaned wisps +bd ephemeral gc # Clean up orphaned wisps ``` --- @@ -289,7 +289,7 @@ bd mol spawn mol-weekly-review --pour ```bash # Patrol proto exists -bd wisp create mol-patrol +bd ephemeral create mol-patrol # Execute patrol work... @@ -328,9 +328,9 @@ bd mol distill bd-release-epic --as "Release Process" --var version=X.Y.Z | `bd mol squash ` | Compress wisp children to digest | | `bd mol burn ` | Delete wisp without trace | | `bd pour ` | Shortcut for `spawn --pour` | -| `bd wisp create ` | Create ephemeral wisp | -| `bd wisp list` | List all wisps | -| `bd wisp gc` | Garbage collect orphaned wisps | +| `bd ephemeral create ` | Create ephemeral wisp | +| `bd ephemeral list` | List all wisps | +| `bd ephemeral gc` | Garbage collect orphaned wisps | | `bd ship ` | Publish capability for cross-project deps | --- @@ -347,7 +347,7 @@ bd mol distill bd-release-epic --as "Release Process" --var version=X.Y.Z **"Wisp commands fail"** - Wisps stored in `.beads-wisp/` (separate from `.beads/`) -- Check `bd wisp list` for active wisps +- Check `bd ephemeral list` for active wisps **"External dependency not satisfied"** - Target project must have closed issue with `provides:` label