feat: Rename 'wisp' to 'ephemeral' in beads API (bd-o18s)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -671,7 +671,7 @@ func flushToJSONLWithState(state flushState) {
|
|||||||
issues := make([]*types.Issue, 0, len(issueMap))
|
issues := make([]*types.Issue, 0, len(issueMap))
|
||||||
wispsSkipped := 0
|
wispsSkipped := 0
|
||||||
for _, issue := range issueMap {
|
for _, issue := range issueMap {
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wispsSkipped++
|
wispsSkipped++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type CleanupEmptyResponse struct {
|
|||||||
DeletedCount int `json:"deleted_count"`
|
DeletedCount int `json:"deleted_count"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Filter string `json:"filter,omitempty"`
|
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
|
// 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
|
bd cleanup --older-than 30 --force
|
||||||
|
|
||||||
Delete only closed wisps (transient molecules):
|
Delete only closed wisps (transient molecules):
|
||||||
bd cleanup --wisp --force
|
bd cleanup --ephemeral --force
|
||||||
|
|
||||||
Preview what would be deleted/pruned:
|
Preview what would be deleted/pruned:
|
||||||
bd cleanup --dry-run
|
bd cleanup --dry-run
|
||||||
@@ -80,7 +80,7 @@ SEE ALSO:
|
|||||||
cascade, _ := cmd.Flags().GetBool("cascade")
|
cascade, _ := cmd.Flags().GetBool("cascade")
|
||||||
olderThanDays, _ := cmd.Flags().GetInt("older-than")
|
olderThanDays, _ := cmd.Flags().GetInt("older-than")
|
||||||
hardDelete, _ := cmd.Flags().GetBool("hard")
|
hardDelete, _ := cmd.Flags().GetBool("hard")
|
||||||
wispOnly, _ := cmd.Flags().GetBool("wisp")
|
wispOnly, _ := cmd.Flags().GetBool("ephemeral")
|
||||||
|
|
||||||
// Calculate custom TTL for --hard mode
|
// Calculate custom TTL for --hard mode
|
||||||
// When --hard is set, use --older-than days as the tombstone TTL cutoff
|
// 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)
|
// Add wisp filter if specified (bd-kwro.9)
|
||||||
if wispOnly {
|
if wispOnly {
|
||||||
wispTrue := true
|
wispTrue := true
|
||||||
filter.Wisp = &wispTrue
|
filter.Ephemeral = &wispTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all closed issues matching filter
|
// Get all closed issues matching filter
|
||||||
@@ -165,7 +165,7 @@ SEE ALSO:
|
|||||||
result.Filter = fmt.Sprintf("older than %d days", olderThanDays)
|
result.Filter = fmt.Sprintf("older than %d days", olderThanDays)
|
||||||
}
|
}
|
||||||
if wispOnly {
|
if wispOnly {
|
||||||
result.Wisp = true
|
result.Ephemeral = true
|
||||||
}
|
}
|
||||||
outputJSON(result)
|
outputJSON(result)
|
||||||
} else {
|
} else {
|
||||||
@@ -270,6 +270,6 @@ func init() {
|
|||||||
cleanupCmd.Flags().Bool("cascade", false, "Recursively delete all dependent issues")
|
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().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("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)
|
rootCmd.AddCommand(cleanupCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ var createCmd = &cobra.Command{
|
|||||||
waitsForGate, _ := cmd.Flags().GetString("waits-for-gate")
|
waitsForGate, _ := cmd.Flags().GetString("waits-for-gate")
|
||||||
forceCreate, _ := cmd.Flags().GetBool("force")
|
forceCreate, _ := cmd.Flags().GetBool("force")
|
||||||
repoOverride, _ := cmd.Flags().GetString("repo")
|
repoOverride, _ := cmd.Flags().GetString("repo")
|
||||||
wisp, _ := cmd.Flags().GetBool("wisp")
|
wisp, _ := cmd.Flags().GetBool("ephemeral")
|
||||||
|
|
||||||
// Get estimate if provided
|
// Get estimate if provided
|
||||||
var estimatedMinutes *int
|
var estimatedMinutes *int
|
||||||
@@ -222,7 +222,7 @@ var createCmd = &cobra.Command{
|
|||||||
Dependencies: deps,
|
Dependencies: deps,
|
||||||
WaitsFor: waitsFor,
|
WaitsFor: waitsFor,
|
||||||
WaitsForGate: waitsForGate,
|
WaitsForGate: waitsForGate,
|
||||||
Wisp: wisp,
|
Ephemeral: wisp,
|
||||||
CreatedBy: getActorWithGit(),
|
CreatedBy: getActorWithGit(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ var createCmd = &cobra.Command{
|
|||||||
Assignee: assignee,
|
Assignee: assignee,
|
||||||
ExternalRef: externalRefPtr,
|
ExternalRef: externalRefPtr,
|
||||||
EstimatedMinutes: estimatedMinutes,
|
EstimatedMinutes: estimatedMinutes,
|
||||||
Wisp: wisp,
|
Ephemeral: wisp,
|
||||||
CreatedBy: getActorWithGit(), // GH#748: track who created the issue
|
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().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().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().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
|
// Note: --json flag is defined as a persistent flag in main.go, not here
|
||||||
rootCmd.AddCommand(createCmd)
|
rootCmd.AddCommand(createCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,37 +18,37 @@ import (
|
|||||||
|
|
||||||
// Wisp commands - manage ephemeral molecules
|
// 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
|
// They're used for patrol cycles and operational loops that shouldn't
|
||||||
// be exported to JSONL (and thus not synced via git).
|
// be exported to JSONL (and thus not synced via git).
|
||||||
//
|
//
|
||||||
// Commands:
|
// Commands:
|
||||||
// bd wisp list - List all wisps in current context
|
// bd ephemeral list - List all ephemeral issues in current context
|
||||||
// bd wisp gc - Garbage collect orphaned wisps
|
// bd ephemeral gc - Garbage collect orphaned ephemeral issues
|
||||||
|
|
||||||
var wispCmd = &cobra.Command{
|
var ephemeralCmd = &cobra.Command{
|
||||||
Use: "wisp",
|
Use: "ephemeral",
|
||||||
Short: "Manage ephemeral molecules (wisps)",
|
Short: "Manage ephemeral molecules",
|
||||||
Long: `Manage wisps - ephemeral molecules for operational workflows.
|
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).
|
locally but NOT exported to JSONL (and thus not synced via git).
|
||||||
They're used for patrol cycles, operational loops, and other workflows
|
They're used for patrol cycles, operational loops, and other workflows
|
||||||
that shouldn't accumulate in the shared issue database.
|
that shouldn't accumulate in the shared issue database.
|
||||||
|
|
||||||
The wisp lifecycle:
|
The wisp lifecycle:
|
||||||
1. Create: bd wisp create <proto> or bd create --wisp
|
1. Create: bd ephemeral create <proto> or bd create --ephemeral
|
||||||
2. Execute: Normal bd operations work on wisps
|
2. Execute: Normal bd operations work on ephemeral issues
|
||||||
3. Squash: bd mol squash <id> (clears Wisp flag, promotes to persistent)
|
3. Squash: bd mol squash <id> (clears Wisp flag, promotes to persistent)
|
||||||
4. Or burn: bd mol burn <id> (deletes wisp without creating digest)
|
4. Or burn: bd mol burn <id> (deletes wisp without creating digest)
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
list List all wisps in current context
|
list List all ephemeral issues in current context
|
||||||
gc Garbage collect orphaned wisps`,
|
gc Garbage collect orphaned ephemeral issues`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// WispListItem represents a wisp in list output
|
// EphemeralListItem represents a wisp in list output
|
||||||
type WispListItem struct {
|
type EphemeralListItem struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -58,9 +58,9 @@ type WispListItem struct {
|
|||||||
Old bool `json:"old,omitempty"` // Not updated in 24+ hours
|
Old bool `json:"old,omitempty"` // Not updated in 24+ hours
|
||||||
}
|
}
|
||||||
|
|
||||||
// WispListResult is the JSON output for wisp list
|
// EphemeralListResult is the JSON output for wisp list
|
||||||
type WispListResult struct {
|
type EphemeralListResult struct {
|
||||||
Wisps []WispListItem `json:"wisps"`
|
Wisps []EphemeralListItem `json:"ephemeral_items"`
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
OldCount int `json:"old_count,omitempty"`
|
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)
|
// OldThreshold is how old a wisp must be to be flagged as old (time-based, for ephemeral cleanup)
|
||||||
const OldThreshold = 24 * time.Hour
|
const OldThreshold = 24 * time.Hour
|
||||||
|
|
||||||
// wispCreateCmd instantiates a proto as an ephemeral wisp
|
// ephemeralCreateCmd instantiates a proto as an ephemeral wisp
|
||||||
var wispCreateCmd = &cobra.Command{
|
var ephemeralCreateCmd = &cobra.Command{
|
||||||
Use: "create <proto-id>",
|
Use: "create <proto-id>",
|
||||||
Short: "Instantiate a proto as an ephemeral wisp (solid -> vapor)",
|
Short: "Instantiate a proto as an ephemeral wisp (solid -> vapor)",
|
||||||
Long: `Create a wisp from a proto - sublimation from solid to 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)
|
- Either evaporate (burn) or condense to digest (squash)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd wisp create mol-patrol # Ephemeral patrol cycle
|
bd ephemeral create mol-patrol # Ephemeral patrol cycle
|
||||||
bd wisp create mol-health-check # One-time health check
|
bd ephemeral create mol-health-check # One-time health check
|
||||||
bd wisp create mol-diagnostics --var target=db # Diagnostic run`,
|
bd ephemeral create mol-diagnostics --var target=db # Diagnostic run`,
|
||||||
Args: cobra.ExactArgs(1),
|
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")
|
CheckReadonly("wisp create")
|
||||||
|
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
@@ -215,7 +215,7 @@ func runWispCreate(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Printf("\nDry run: would create wisp with %d issues from proto %s\n\n", len(subgraph.Issues), protoID)
|
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 {
|
for _, issue := range subgraph.Issues {
|
||||||
newTitle := substituteVariables(issue.Title, vars)
|
newTitle := substituteVariables(issue.Title, vars)
|
||||||
fmt.Printf(" - %s (from %s)\n", newTitle, issue.ID)
|
fmt.Printf(" - %s (from %s)\n", newTitle, issue.ID)
|
||||||
@@ -223,22 +223,22 @@ func runWispCreate(cmd *cobra.Command, args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn as wisp in main database (ephemeral=true sets Wisp flag, skips JSONL export)
|
// Spawn as ephemeral in main database (Ephemeral=true, skips JSONL export)
|
||||||
// bd-hobo: Use "wisp" prefix for distinct visual recognition
|
// bd-hobo: Use "eph" prefix for distinct visual recognition
|
||||||
result, err := spawnMolecule(ctx, store, subgraph, vars, "", actor, true, "wisp")
|
result, err := spawnMolecule(ctx, store, subgraph, vars, "", actor, true, "eph")
|
||||||
if err != nil {
|
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)
|
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 {
|
if jsonOutput {
|
||||||
type wispCreateResult struct {
|
type ephemeralCreateResult struct {
|
||||||
*InstantiateResult
|
*InstantiateResult
|
||||||
Phase string `json:"phase"`
|
Phase string `json:"phase"`
|
||||||
}
|
}
|
||||||
outputJSON(wispCreateResult{result, "vapor"})
|
outputJSON(ephemeralCreateResult{result, "vapor"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,12 +283,12 @@ func resolvePartialIDDirect(ctx context.Context, partial string) (string, error)
|
|||||||
return "", fmt.Errorf("not found: %s", partial)
|
return "", fmt.Errorf("not found: %s", partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
var wispListCmd = &cobra.Command{
|
var ephemeralListCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all wisps in current context",
|
Short: "List all ephemeral issues in current context",
|
||||||
Long: `List all ephemeral molecules (wisps) in the 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).
|
locally but not exported to JSONL (and thus not synced via git).
|
||||||
|
|
||||||
The list shows:
|
The list shows:
|
||||||
@@ -298,18 +298,18 @@ The list shows:
|
|||||||
- Started: When the wisp was created
|
- Started: When the wisp was created
|
||||||
- Updated: Last modification time
|
- Updated: Last modification time
|
||||||
|
|
||||||
Old wisp detection:
|
Old ephemeral issue detection:
|
||||||
- Old wisps haven't been updated in 24+ hours
|
- Old ephemeral issues haven't been updated in 24+ hours
|
||||||
- Use 'bd wisp gc' to clean up old/abandoned wisps
|
- Use 'bd ephemeral gc' to clean up old/abandoned ephemeral issues
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd wisp list # List all wisps
|
bd ephemeral list # List all ephemeral issues
|
||||||
bd wisp list --json # JSON output for programmatic use
|
bd ephemeral list --json # JSON output for programmatic use
|
||||||
bd wisp list --all # Include closed wisps`,
|
bd ephemeral list --all # Include closed ephemeral issues`,
|
||||||
Run: runWispList,
|
Run: runEphemeralList,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWispList(cmd *cobra.Command, args []string) {
|
func runEphemeralList(cmd *cobra.Command, args []string) {
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
|
|
||||||
showAll, _ := cmd.Flags().GetBool("all")
|
showAll, _ := cmd.Flags().GetBool("all")
|
||||||
@@ -317,8 +317,8 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
// Check for database connection
|
// Check for database connection
|
||||||
if store == nil && daemonClient == nil {
|
if store == nil && daemonClient == nil {
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(WispListResult{
|
outputJSON(EphemeralListResult{
|
||||||
Wisps: []WispListItem{},
|
Wisps: []EphemeralListItem{},
|
||||||
Count: 0,
|
Count: 0,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -327,15 +327,15 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query wisps from main database using Wisp filter
|
// Query ephemeral issues from main database using Wisp filter
|
||||||
wispFlag := true
|
ephemeralFlag := true
|
||||||
var issues []*types.Issue
|
var issues []*types.Issue
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
// Use daemon RPC
|
// Use daemon RPC
|
||||||
resp, rpcErr := daemonClient.List(&rpc.ListArgs{
|
resp, rpcErr := daemonClient.List(&rpc.ListArgs{
|
||||||
Wisp: &wispFlag,
|
Ephemeral: &ephemeralFlag,
|
||||||
})
|
})
|
||||||
if rpcErr != nil {
|
if rpcErr != nil {
|
||||||
err = rpcErr
|
err = rpcErr
|
||||||
@@ -347,12 +347,12 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
} else {
|
} else {
|
||||||
// Direct database access
|
// Direct database access
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
Wisp: &wispFlag,
|
Ephemeral: &ephemeralFlag,
|
||||||
}
|
}
|
||||||
issues, err = store.SearchIssues(ctx, "", filter)
|
issues, err = store.SearchIssues(ctx, "", filter)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,13 +367,13 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
issues = filtered
|
issues = filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to list items and detect old wisps
|
// Convert to list items and detect old ephemeral issues
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
items := make([]WispListItem, 0, len(issues))
|
items := make([]EphemeralListItem, 0, len(issues))
|
||||||
oldCount := 0
|
oldCount := 0
|
||||||
|
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
item := WispListItem{
|
item := EphemeralListItem{
|
||||||
ID: issue.ID,
|
ID: issue.ID,
|
||||||
Title: issue.Title,
|
Title: issue.Title,
|
||||||
Status: string(issue.Status),
|
Status: string(issue.Status),
|
||||||
@@ -392,11 +392,11 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort by updated_at descending (most recent first)
|
// 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
|
return b.UpdatedAt.Compare(a.UpdatedAt) // descending order
|
||||||
})
|
})
|
||||||
|
|
||||||
result := WispListResult{
|
result := EphemeralListResult{
|
||||||
Wisps: items,
|
Wisps: items,
|
||||||
Count: len(items),
|
Count: len(items),
|
||||||
OldCount: oldCount,
|
OldCount: oldCount,
|
||||||
@@ -409,11 +409,11 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
// Human-readable output
|
// Human-readable output
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
fmt.Println("No wisps found")
|
fmt.Println("No ephemeral issues found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Wisps (%d):\n\n", len(items))
|
fmt.Printf("Ephemeral issues (%d):\n\n", len(items))
|
||||||
|
|
||||||
// Print header
|
// Print header
|
||||||
fmt.Printf("%-12s %-10s %-4s %-46s %s\n",
|
fmt.Printf("%-12s %-10s %-4s %-46s %s\n",
|
||||||
@@ -442,9 +442,9 @@ func runWispList(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
// Print warnings
|
// Print warnings
|
||||||
if oldCount > 0 {
|
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)
|
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",
|
Use: "gc",
|
||||||
Short: "Garbage collect old/abandoned wisps",
|
Short: "Garbage collect old/abandoned ephemeral issues",
|
||||||
Long: `Garbage collect old or abandoned wisps from the database.
|
Long: `Garbage collect old or abandoned ephemeral issues from the database.
|
||||||
|
|
||||||
A wisp is considered abandoned if:
|
A wisp is considered abandoned if:
|
||||||
- It hasn't been updated in --age duration and is not closed
|
- 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.
|
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'.
|
For graph-pressure staleness detection (blocking other work), see 'bd mol stale'.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd wisp gc # Clean abandoned wisps (default: 1h threshold)
|
bd ephemeral gc # Clean abandoned ephemeral issues (default: 1h threshold)
|
||||||
bd wisp gc --dry-run # Preview what would be cleaned
|
bd ephemeral gc --dry-run # Preview what would be cleaned
|
||||||
bd wisp gc --age 24h # Custom age threshold
|
bd ephemeral gc --age 24h # Custom age threshold
|
||||||
bd wisp gc --all # Also clean closed wisps older than threshold`,
|
bd ephemeral gc --all # Also clean closed ephemeral issues older than threshold`,
|
||||||
Run: runWispGC,
|
Run: runEphemeralGC,
|
||||||
}
|
}
|
||||||
|
|
||||||
// WispGCResult is the JSON output for wisp gc
|
// EphemeralGCResult is the JSON output for ephemeral gc
|
||||||
type WispGCResult struct {
|
type EphemeralGCResult struct {
|
||||||
CleanedIDs []string `json:"cleaned_ids"`
|
CleanedIDs []string `json:"cleaned_ids"`
|
||||||
CleanedCount int `json:"cleaned_count"`
|
CleanedCount int `json:"cleaned_count"`
|
||||||
Candidates int `json:"candidates,omitempty"`
|
Candidates int `json:"candidates,omitempty"`
|
||||||
DryRun bool `json:"dry_run,omitempty"`
|
DryRun bool `json:"dry_run,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWispGC(cmd *cobra.Command, args []string) {
|
func runEphemeralGC(cmd *cobra.Command, args []string) {
|
||||||
CheckReadonly("wisp gc")
|
CheckReadonly("ephemeral gc")
|
||||||
|
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
|
|
||||||
@@ -531,26 +531,26 @@ func runWispGC(cmd *cobra.Command, args []string) {
|
|||||||
// Wisp gc requires direct store access for deletion
|
// Wisp gc requires direct store access for deletion
|
||||||
if store == nil {
|
if store == nil {
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: wisp gc requires direct database access\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 wisp gc\n")
|
fmt.Fprintf(os.Stderr, "Hint: use --no-daemon flag: bd --no-daemon ephemeral gc\n")
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "Error: no database connection\n")
|
fmt.Fprintf(os.Stderr, "Error: no database connection\n")
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query wisps from main database using Wisp filter
|
// Query ephemeral issues from main database using Wisp filter
|
||||||
wispFlag := true
|
ephemeralFlag := true
|
||||||
filter := types.IssueFilter{
|
filter := types.IssueFilter{
|
||||||
Wisp: &wispFlag,
|
Ephemeral: &ephemeralFlag,
|
||||||
}
|
}
|
||||||
issues, err := store.SearchIssues(ctx, "", filter)
|
issues, err := store.SearchIssues(ctx, "", filter)
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find old/abandoned wisps
|
// Find old/abandoned ephemeral issues
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
var abandoned []*types.Issue
|
var abandoned []*types.Issue
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
@@ -567,13 +567,13 @@ func runWispGC(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
if len(abandoned) == 0 {
|
if len(abandoned) == 0 {
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(WispGCResult{
|
outputJSON(EphemeralGCResult{
|
||||||
CleanedIDs: []string{},
|
CleanedIDs: []string{},
|
||||||
CleanedCount: 0,
|
CleanedCount: 0,
|
||||||
DryRun: dryRun,
|
DryRun: dryRun,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("No abandoned wisps found")
|
fmt.Println("No abandoned ephemeral issues found")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -584,28 +584,28 @@ func runWispGC(cmd *cobra.Command, args []string) {
|
|||||||
for i, o := range abandoned {
|
for i, o := range abandoned {
|
||||||
ids[i] = o.ID
|
ids[i] = o.ID
|
||||||
}
|
}
|
||||||
outputJSON(WispGCResult{
|
outputJSON(EphemeralGCResult{
|
||||||
CleanedIDs: ids,
|
CleanedIDs: ids,
|
||||||
Candidates: len(abandoned),
|
Candidates: len(abandoned),
|
||||||
CleanedCount: 0,
|
CleanedCount: 0,
|
||||||
DryRun: true,
|
DryRun: true,
|
||||||
})
|
})
|
||||||
} else {
|
} 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 {
|
for _, issue := range abandoned {
|
||||||
age := formatTimeAgo(issue.UpdatedAt)
|
age := formatTimeAgo(issue.UpdatedAt)
|
||||||
fmt.Printf(" %s: %s (last updated: %s)\n", issue.ID, issue.Title, age)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete abandoned wisps
|
// Delete abandoned ephemeral issues
|
||||||
var cleanedIDs []string
|
var cleanedIDs []string
|
||||||
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
sqliteStore, ok := store.(*sqlite.SQLiteStorage)
|
||||||
if !ok {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +617,7 @@ func runWispGC(cmd *cobra.Command, args []string) {
|
|||||||
cleanedIDs = append(cleanedIDs, issue.ID)
|
cleanedIDs = append(cleanedIDs, issue.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := WispGCResult{
|
result := EphemeralGCResult{
|
||||||
CleanedIDs: cleanedIDs,
|
CleanedIDs: cleanedIDs,
|
||||||
CleanedCount: len(cleanedIDs),
|
CleanedCount: len(cleanedIDs),
|
||||||
}
|
}
|
||||||
@@ -627,25 +627,25 @@ func runWispGC(cmd *cobra.Command, args []string) {
|
|||||||
return
|
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 {
|
for _, id := range cleanedIDs {
|
||||||
fmt.Printf(" - %s\n", id)
|
fmt.Printf(" - %s\n", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Wisp create command flags
|
// Ephemeral create command flags
|
||||||
wispCreateCmd.Flags().StringSlice("var", []string{}, "Variable substitution (key=value)")
|
ephemeralCreateCmd.Flags().StringSlice("var", []string{}, "Variable substitution (key=value)")
|
||||||
wispCreateCmd.Flags().Bool("dry-run", false, "Preview what would be created")
|
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")
|
ephemeralGCCmd.Flags().Bool("dry-run", false, "Preview what would be cleaned")
|
||||||
wispGCCmd.Flags().String("age", "1h", "Age threshold for abandoned wisp detection")
|
ephemeralGCCmd.Flags().String("age", "1h", "Age threshold for abandoned ephemeral issue detection")
|
||||||
wispGCCmd.Flags().Bool("all", false, "Also clean closed wisps older than threshold")
|
ephemeralGCCmd.Flags().Bool("all", false, "Also clean closed ephemeral issues older than threshold")
|
||||||
|
|
||||||
wispCmd.AddCommand(wispCreateCmd)
|
ephemeralCmd.AddCommand(ephemeralCreateCmd)
|
||||||
wispCmd.AddCommand(wispListCmd)
|
ephemeralCmd.AddCommand(ephemeralListCmd)
|
||||||
wispCmd.AddCommand(wispGCCmd)
|
ephemeralCmd.AddCommand(ephemeralGCCmd)
|
||||||
rootCmd.AddCommand(wispCmd)
|
rootCmd.AddCommand(ephemeralCmd)
|
||||||
}
|
}
|
||||||
@@ -362,7 +362,7 @@ Examples:
|
|||||||
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
|
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
|
||||||
filtered := make([]*types.Issue, 0, len(issues))
|
filtered := make([]*types.Issue, 0, len(issues))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if !issue.Wisp {
|
if !issue.Ephemeral {
|
||||||
filtered = append(filtered, issue)
|
filtered = append(filtered, issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ Examples:
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1, // Gates are typically high priority
|
Priority: 1, // Gates are typically high priority
|
||||||
// Assignee left empty - orchestrator decides who processes gates
|
// Assignee left empty - orchestrator decides who processes gates
|
||||||
Wisp: true, // Gates are wisps (ephemeral)
|
Ephemeral: true, // Gates are wisps (ephemeral)
|
||||||
AwaitType: awaitType,
|
AwaitType: awaitType,
|
||||||
AwaitID: awaitID,
|
AwaitID: awaitID,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ func runHook(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
phase := "mol"
|
phase := "mol"
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
phase = "wisp"
|
phase = "ephemeral"
|
||||||
}
|
}
|
||||||
fmt.Printf(" 📌 %s (%s) - %s\n", issue.ID, phase, issue.Status)
|
fmt.Printf(" 📌 %s (%s) - %s\n", issue.ID, phase, issue.Status)
|
||||||
fmt.Printf(" %s\n", issue.Title)
|
fmt.Printf(" %s\n", issue.Title)
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ var versionChanges = []VersionChange{
|
|||||||
Version: "0.37.0",
|
Version: "0.37.0",
|
||||||
Date: "2025-12-26",
|
Date: "2025-12-26",
|
||||||
Changes: []string{
|
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 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 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",
|
"NEW: bd gate approve (gt-twjr5.4) - Human gate approval command",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
// bd mol catalog # List available protos
|
// bd mol catalog # List available protos
|
||||||
// bd mol show <id> # Show proto/molecule structure
|
// bd mol show <id> # Show proto/molecule structure
|
||||||
// bd pour <id> --var key=value # Instantiate proto → persistent mol
|
// bd pour <id> --var key=value # Instantiate proto → persistent mol
|
||||||
// bd wisp create <id> --var key=value # Instantiate proto → ephemeral wisp
|
// bd ephemeral create <id> --var key=value # Instantiate proto → ephemeral wisp
|
||||||
|
|
||||||
// MoleculeLabel is the label used to identify molecules (templates)
|
// MoleculeLabel is the label used to identify molecules (templates)
|
||||||
// Molecules use the same label as templates - they ARE templates with workflow semantics
|
// Molecules use the same label as templates - they ARE templates with workflow semantics
|
||||||
@@ -55,7 +55,7 @@ Commands:
|
|||||||
|
|
||||||
See also:
|
See also:
|
||||||
bd pour <proto> # Instantiate as persistent mol (liquid phase)
|
bd pour <proto> # Instantiate as persistent mol (liquid phase)
|
||||||
bd wisp create <proto> # Instantiate as ephemeral wisp (vapor phase)`,
|
bd ephemeral create <proto> # Instantiate as ephemeral wisp (vapor phase)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -72,7 +72,7 @@ func spawnMolecule(ctx context.Context, s storage.Storage, subgraph *MoleculeSub
|
|||||||
Vars: vars,
|
Vars: vars,
|
||||||
Assignee: assignee,
|
Assignee: assignee,
|
||||||
Actor: actorName,
|
Actor: actorName,
|
||||||
Wisp: ephemeral,
|
Ephemeral: ephemeral,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
}
|
}
|
||||||
return cloneSubgraph(ctx, s, subgraph, opts)
|
return cloneSubgraph(ctx, s, subgraph, opts)
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ Bond types:
|
|||||||
|
|
||||||
Phase control:
|
Phase control:
|
||||||
By default, spawned protos follow the target's phase:
|
By default, spawned protos follow the target's phase:
|
||||||
- Attaching to mol (Wisp=false) → spawns as persistent (Wisp=false)
|
- Attaching to mol (Ephemeral=false) → spawns as persistent (Ephemeral=false)
|
||||||
- Attaching to wisp (Wisp=true) → spawns as ephemeral (Wisp=true)
|
- Attaching to ephemeral issue (Ephemeral=true) → spawns as ephemeral (Ephemeral=true)
|
||||||
|
|
||||||
Override with:
|
Override with:
|
||||||
--pour Force spawn as liquid (persistent, Wisp=false)
|
--pour Force spawn as liquid (persistent, Ephemeral=false)
|
||||||
--wisp Force spawn as vapor (ephemeral, Wisp=true, excluded from JSONL export)
|
--ephemeral Force spawn as vapor (ephemeral, Ephemeral=true, excluded from JSONL export)
|
||||||
|
|
||||||
Dynamic bonding (Christmas Ornament pattern):
|
Dynamic bonding (Christmas Ornament pattern):
|
||||||
Use --ref to specify a custom child reference with variable substitution.
|
Use --ref to specify a custom child reference with variable substitution.
|
||||||
@@ -57,7 +57,7 @@ Dynamic bonding (Christmas Ornament pattern):
|
|||||||
|
|
||||||
Use cases:
|
Use cases:
|
||||||
- Found important bug during patrol? Use --pour to persist it
|
- 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
|
- Spawning per-worker arms on a patrol? Use --ref for readable IDs
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -66,7 +66,7 @@ Examples:
|
|||||||
bd mol bond mol-feature bd-abc123 # Attach proto to molecule
|
bd mol bond mol-feature bd-abc123 # Attach proto to molecule
|
||||||
bd mol bond bd-abc123 bd-def456 # Join two molecules
|
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-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`,
|
bd mol bond mol-arm bd-patrol --ref arm-{{name}} --var name=ace # Dynamic child ID`,
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: runMolBond,
|
Run: runMolBond,
|
||||||
@@ -102,20 +102,20 @@ func runMolBond(cmd *cobra.Command, args []string) {
|
|||||||
customTitle, _ := cmd.Flags().GetString("as")
|
customTitle, _ := cmd.Flags().GetString("as")
|
||||||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||||||
varFlags, _ := cmd.Flags().GetStringSlice("var")
|
varFlags, _ := cmd.Flags().GetStringSlice("var")
|
||||||
wisp, _ := cmd.Flags().GetBool("wisp")
|
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
||||||
pour, _ := cmd.Flags().GetBool("pour")
|
pour, _ := cmd.Flags().GetBool("pour")
|
||||||
childRef, _ := cmd.Flags().GetString("ref")
|
childRef, _ := cmd.Flags().GetString("ref")
|
||||||
|
|
||||||
// Validate phase flags are not both set
|
// Validate phase flags are not both set
|
||||||
if wisp && pour {
|
if ephemeral && pour {
|
||||||
fmt.Fprintf(os.Stderr, "Error: cannot use both --wisp and --pour\n")
|
fmt.Fprintf(os.Stderr, "Error: cannot use both --ephemeral and --pour\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All issues go in the main store; wisp vs pour determines the Wisp flag
|
// All issues go in the main store; ephemeral vs pour determines the Wisp flag
|
||||||
// --wisp: create with Wisp=true (ephemeral, excluded from JSONL export)
|
// --ephemeral: create with Ephemeral=true (ephemeral, excluded from JSONL export)
|
||||||
// --pour: create with Wisp=false (persistent, exported to JSONL)
|
// --pour: create with Ephemeral=false (persistent, exported to JSONL)
|
||||||
// Default: follow target's phase (wisp if target is wisp, otherwise persistent)
|
// Default: follow target's phase (ephemeral if target is ephemeral, otherwise persistent)
|
||||||
|
|
||||||
// Validate bond type
|
// Validate bond type
|
||||||
if bondType != types.BondTypeSequential && bondType != types.BondTypeParallel && bondType != types.BondTypeConditional {
|
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(" B: %s (%s)\n", issueB.Title, operandType(bIsProto))
|
||||||
}
|
}
|
||||||
fmt.Printf(" Bond type: %s\n", bondType)
|
fmt.Printf(" Bond type: %s\n", bondType)
|
||||||
if wisp {
|
if ephemeral {
|
||||||
fmt.Printf(" Phase override: vapor (--wisp)\n")
|
fmt.Printf(" Phase override: vapor (--ephemeral)\n")
|
||||||
} else if pour {
|
} else if pour {
|
||||||
fmt.Printf(" Phase override: liquid (--pour)\n")
|
fmt.Printf(" Phase override: liquid (--pour)\n")
|
||||||
}
|
}
|
||||||
@@ -240,16 +240,16 @@ func runMolBond(cmd *cobra.Command, args []string) {
|
|||||||
case aIsProto && !bIsProto:
|
case aIsProto && !bIsProto:
|
||||||
// Pass subgraph directly if cooked from formula
|
// Pass subgraph directly if cooked from formula
|
||||||
if cookedA {
|
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 {
|
} 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:
|
case !aIsProto && bIsProto:
|
||||||
// Pass subgraph directly if cooked from formula
|
// Pass subgraph directly if cooked from formula
|
||||||
if cookedB {
|
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 {
|
} 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:
|
default:
|
||||||
result, err = bondMolMol(ctx, store, issueA, issueB, bondType, actor)
|
result, err = bondMolMol(ctx, store, issueA, issueB, bondType, actor)
|
||||||
@@ -273,10 +273,10 @@ func runMolBond(cmd *cobra.Command, args []string) {
|
|||||||
if result.Spawned > 0 {
|
if result.Spawned > 0 {
|
||||||
fmt.Printf(" Spawned: %d issues\n", result.Spawned)
|
fmt.Printf(" Spawned: %d issues\n", result.Spawned)
|
||||||
}
|
}
|
||||||
if wisp {
|
if ephemeral {
|
||||||
fmt.Printf(" Phase: vapor (ephemeral, Wisp=true)\n")
|
fmt.Printf(" Phase: vapor (ephemeral, Ephemeral=true)\n")
|
||||||
} else if pour {
|
} 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.
|
// bondProtoMol bonds a proto to an existing molecule by spawning the proto.
|
||||||
// If childRef is provided, generates custom IDs like "parent.childref" (dynamic bonding).
|
// 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.
|
// 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) {
|
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, wispFlag, pourFlag)
|
return bondProtoMolWithSubgraph(ctx, s, nil, proto, mol, bondType, vars, childRef, actorName, ephemeralFlag, pourFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bondProtoMolWithSubgraph is the internal implementation that accepts a pre-loaded subgraph.
|
// 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
|
// Use provided subgraph or load from DB
|
||||||
subgraph := protoSubgraph
|
subgraph := protoSubgraph
|
||||||
if subgraph == nil {
|
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, ", "))
|
return nil, fmt.Errorf("missing required variables: %s (use --var)", strings.Join(missingVars, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine wisp flag based on explicit flags or target's phase
|
// Determine ephemeral flag based on explicit flags or target's phase
|
||||||
// --wisp: force wisp=true, --pour: force wisp=false, neither: follow target
|
// --ephemeral: force ephemeral=true, --pour: force ephemeral=false, neither: follow target
|
||||||
makeWisp := mol.Wisp // Default: follow target's phase
|
makeEphemeral := mol.Ephemeral // Default: follow target's phase
|
||||||
if wispFlag {
|
if ephemeralFlag {
|
||||||
makeWisp = true
|
makeEphemeral = true
|
||||||
} else if pourFlag {
|
} else if pourFlag {
|
||||||
makeWisp = false
|
makeEphemeral = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build CloneOptions for spawning
|
// Build CloneOptions for spawning
|
||||||
opts := CloneOptions{
|
opts := CloneOptions{
|
||||||
Vars: vars,
|
Vars: vars,
|
||||||
Actor: actorName,
|
Actor: actorName,
|
||||||
Wisp: makeWisp,
|
Ephemeral: makeEphemeral,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic bonding: use custom IDs if childRef is provided
|
// 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)
|
// 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
|
// 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
|
// 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().String("as", "", "Custom title for compound proto (proto+proto only)")
|
||||||
molBondCmd.Flags().Bool("dry-run", false, "Preview what would be created")
|
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().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("ephemeral", false, "Force spawn as vapor (ephemeral, Ephemeral=true)")
|
||||||
molBondCmd.Flags().Bool("pour", false, "Force spawn as liquid (persistent, Wisp=false)")
|
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}})")
|
molBondCmd.Flags().String("ref", "", "Custom child reference with {{var}} substitution (e.g., arm-{{polecat_name}})")
|
||||||
|
|
||||||
molCmd.AddCommand(molBondCmd)
|
molCmd.AddCommand(molBondCmd)
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ completely removes the wisp with no trace. Use this for:
|
|||||||
- Test/debug wisps you don't want to preserve
|
- Test/debug wisps you don't want to preserve
|
||||||
|
|
||||||
The burn operation:
|
The burn operation:
|
||||||
1. Verifies the molecule has Wisp=true (is ephemeral)
|
1. Verifies the molecule has Ephemeral=true (is ephemeral)
|
||||||
2. Deletes the molecule and all its wisp children
|
2. Deletes the molecule and all its ephemeral children
|
||||||
3. No digest is created (use 'bd mol squash' if you want a digest)
|
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
|
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
|
// Verify it's a wisp
|
||||||
if !rootIssue.Wisp {
|
if !rootIssue.Ephemeral {
|
||||||
fmt.Fprintf(os.Stderr, "Error: molecule %s is not a wisp (Wisp=false)\n", resolvedID)
|
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, "Hint: mol burn only works with wisp molecules\n")
|
||||||
fmt.Fprintf(os.Stderr, " Use 'bd delete' to remove non-wisp issues\n")
|
fmt.Fprintf(os.Stderr, " Use 'bd delete' to remove non-wisp issues\n")
|
||||||
os.Exit(1)
|
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)
|
// Collect wisp issue IDs to delete (only delete wisps, not regular children)
|
||||||
var wispIDs []string
|
var wispIDs []string
|
||||||
for _, issue := range subgraph.Issues {
|
for _, issue := range subgraph.Issues {
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wispIDs = append(wispIDs, issue.ID)
|
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("Root: %s\n", subgraph.Root.Title)
|
||||||
fmt.Printf("\nWisp issues to delete (%d total):\n", len(wispIDs))
|
fmt.Printf("\nWisp issues to delete (%d total):\n", len(wispIDs))
|
||||||
for _, issue := range subgraph.Issues {
|
for _, issue := range subgraph.Issues {
|
||||||
if !issue.Wisp {
|
if !issue.Ephemeral {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
status := string(issue.Status)
|
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("%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")
|
fmt.Printf(" No digest created.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ var molCatalogCmd = &cobra.Command{
|
|||||||
Use: "catalog",
|
Use: "catalog",
|
||||||
Aliases: []string{"list", "ls"},
|
Aliases: []string{"list", "ls"},
|
||||||
Short: "List available molecule formulas",
|
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.
|
Formulas are ephemeral proto definitions stored as .formula.json files.
|
||||||
They are cooked inline when pouring, never stored as database beads.
|
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 <epic-id> my-workflow")
|
fmt.Println(" bd mol distill <epic-id> my-workflow")
|
||||||
fmt.Println("\nTo instantiate from formula:")
|
fmt.Println("\nTo instantiate from formula:")
|
||||||
fmt.Println(" bd pour <formula-name> --var key=value # persistent mol")
|
fmt.Println(" bd pour <formula-name> --var key=value # persistent mol")
|
||||||
fmt.Println(" bd wisp create <formula-name> --var key=value # ephemeral wisp")
|
fmt.Println(" bd ephemeral create <formula-name> --var key=value # ephemeral wisp")
|
||||||
return
|
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
|
// Group by type for display
|
||||||
byType := make(map[string][]CatalogEntry)
|
byType := make(map[string][]CatalogEntry)
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ import (
|
|||||||
var molSquashCmd = &cobra.Command{
|
var molSquashCmd = &cobra.Command{
|
||||||
Use: "squash <molecule-id>",
|
Use: "squash <molecule-id>",
|
||||||
Short: "Compress molecule execution into a digest",
|
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
|
generates a summary digest, and promotes the wisps to persistent by
|
||||||
clearing their Wisp flag (or optionally deletes them).
|
clearing their Wisp flag (or optionally deletes them).
|
||||||
|
|
||||||
The squash operation:
|
The squash operation:
|
||||||
1. Loads the molecule and all its children
|
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)
|
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)
|
5. Clears Wisp flag on children (promotes to persistent)
|
||||||
OR deletes them with --delete-children
|
OR deletes them with --delete-children
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ func runMolSquash(cmd *cobra.Command, args []string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter to only wisp children (exclude root)
|
// Filter to only ephemeral children (exclude root)
|
||||||
var wispChildren []*types.Issue
|
var wispChildren []*types.Issue
|
||||||
for _, issue := range subgraph.Issues {
|
for _, issue := range subgraph.Issues {
|
||||||
if issue.ID == subgraph.Root.ID {
|
if issue.ID == subgraph.Root.ID {
|
||||||
continue // Skip root
|
continue // Skip root
|
||||||
}
|
}
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wispChildren = append(wispChildren, issue)
|
wispChildren = append(wispChildren, issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,13 +113,13 @@ func runMolSquash(cmd *cobra.Command, args []string) {
|
|||||||
SquashedCount: 0,
|
SquashedCount: 0,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("No wisp children found for molecule %s\n", moleculeID)
|
fmt.Printf("No ephemeral children found for molecule %s\n", moleculeID)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if dryRun {
|
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("Root: %s\n", subgraph.Root.Title)
|
||||||
fmt.Printf("\nWisp children to squash:\n")
|
fmt.Printf("\nWisp children to squash:\n")
|
||||||
for _, issue := range wispChildren {
|
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)),
|
CloseReason: fmt.Sprintf("Squashed from %d wisps", len(children)),
|
||||||
Priority: root.Priority,
|
Priority: root.Priority,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: false, // Digest is permanent, not a wisp
|
Ephemeral: false, // Digest is permanent, not a wisp
|
||||||
ClosedAt: &now,
|
ClosedAt: &now,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete wisp children (outside transaction for better error handling)
|
// Delete ephemeral children (outside transaction for better error handling)
|
||||||
if !keepChildren {
|
if !keepChildren {
|
||||||
deleted, err := deleteWispChildren(ctx, s, childIDs)
|
deleted, err := deleteWispChildren(ctx, s, childIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -319,7 +319,7 @@ func deleteWispChildren(ctx context.Context, s storage.Storage, ids []string) (i
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
molSquashCmd.Flags().Bool("dry-run", false, "Preview what would be squashed")
|
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)")
|
molSquashCmd.Flags().String("summary", "", "Agent-provided summary (bypasses auto-generation)")
|
||||||
|
|
||||||
molCmd.AddCommand(molSquashCmd)
|
molCmd.AddCommand(molSquashCmd)
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ func TestSquashMolecule(t *testing.T) {
|
|||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CloseReason: "Completed design",
|
CloseReason: "Completed design",
|
||||||
}
|
}
|
||||||
child2 := &types.Issue{
|
child2 := &types.Issue{
|
||||||
@@ -498,7 +498,7 @@ func TestSquashMolecule(t *testing.T) {
|
|||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CloseReason: "Code merged",
|
CloseReason: "Code merged",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +547,7 @@ func TestSquashMolecule(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to get digest: %v", err)
|
t.Fatalf("Failed to get digest: %v", err)
|
||||||
}
|
}
|
||||||
if digest.Wisp {
|
if digest.Ephemeral {
|
||||||
t.Error("Digest should NOT be ephemeral")
|
t.Error("Digest should NOT be ephemeral")
|
||||||
}
|
}
|
||||||
if digest.Status != types.StatusClosed {
|
if digest.Status != types.StatusClosed {
|
||||||
@@ -595,7 +595,7 @@ func TestSquashMoleculeWithDelete(t *testing.T) {
|
|||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
}
|
}
|
||||||
if err := s.CreateIssue(ctx, child, "test"); err != nil {
|
if err := s.CreateIssue(ctx, child, "test"); err != nil {
|
||||||
t.Fatalf("Failed to create child: %v", err)
|
t.Fatalf("Failed to create child: %v", err)
|
||||||
@@ -705,7 +705,7 @@ func TestSquashMoleculeWithAgentSummary(t *testing.T) {
|
|||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CloseReason: "Done",
|
CloseReason: "Done",
|
||||||
}
|
}
|
||||||
if err := s.CreateIssue(ctx, child, "test"); err != nil {
|
if err := s.CreateIssue(ctx, child, "test"); err != nil {
|
||||||
@@ -1304,14 +1304,14 @@ func TestWispFilteringFromExport(t *testing.T) {
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: false,
|
Ephemeral: false,
|
||||||
}
|
}
|
||||||
wispIssue := &types.Issue{
|
wispIssue := &types.Issue{
|
||||||
Title: "Wisp Issue",
|
Title: "Wisp Issue",
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.CreateIssue(ctx, normalIssue, "test"); err != nil {
|
if err := s.CreateIssue(ctx, normalIssue, "test"); err != nil {
|
||||||
@@ -1333,7 +1333,7 @@ func TestWispFilteringFromExport(t *testing.T) {
|
|||||||
// Filter wisp issues (simulating export behavior)
|
// Filter wisp issues (simulating export behavior)
|
||||||
exportableIssues := make([]*types.Issue, 0)
|
exportableIssues := make([]*types.Issue, 0)
|
||||||
for _, issue := range allIssues {
|
for _, issue := range allIssues {
|
||||||
if !issue.Wisp {
|
if !issue.Ephemeral {
|
||||||
exportableIssues = append(exportableIssues, issue)
|
exportableIssues = append(exportableIssues, issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
|
||||||
filtered := make([]*types.Issue, 0, len(issues))
|
filtered := make([]*types.Issue, 0, len(issues))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if !issue.Wisp {
|
if !issue.Ephemeral {
|
||||||
filtered = append(filtered, issue)
|
filtered = append(filtered, issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func exportToJSONL(ctx context.Context, jsonlPath string) error {
|
|||||||
// This prevents "zombie" issues that resurrect after mol squash deletes them.
|
// This prevents "zombie" issues that resurrect after mol squash deletes them.
|
||||||
filteredIssues := make([]*types.Issue, 0, len(issues))
|
filteredIssues := make([]*types.Issue, 0, len(issues))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filteredIssues = append(filteredIssues, issue)
|
filteredIssues = append(filteredIssues, issue)
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ type InstantiateResult struct {
|
|||||||
|
|
||||||
// CloneOptions controls how the subgraph is cloned during spawn/bond
|
// CloneOptions controls how the subgraph is cloned during spawn/bond
|
||||||
type CloneOptions struct {
|
type CloneOptions struct {
|
||||||
Vars map[string]string // Variable substitutions for {{key}} placeholders
|
Vars map[string]string // Variable substitutions for {{key}} placeholders
|
||||||
Assignee string // Assign the root epic to this agent/user
|
Assignee string // Assign the root epic to this agent/user
|
||||||
Actor string // Actor performing the operation
|
Actor string // Actor performing the operation
|
||||||
Wisp bool // If true, spawned issues are marked for bulk deletion
|
Ephemeral bool // If true, spawned issues are marked for bulk deletion
|
||||||
Prefix string // Override prefix for ID generation (bd-hobo: distinct prefixes)
|
Prefix string // Override prefix for ID generation (bd-hobo: distinct prefixes)
|
||||||
|
|
||||||
// Dynamic bonding fields (for Christmas Ornament pattern)
|
// Dynamic bonding fields (for Christmas Ornament pattern)
|
||||||
@@ -327,7 +327,7 @@ Example:
|
|||||||
Vars: vars,
|
Vars: vars,
|
||||||
Assignee: assignee,
|
Assignee: assignee,
|
||||||
Actor: actor,
|
Actor: actor,
|
||||||
Wisp: false,
|
Ephemeral: false,
|
||||||
}
|
}
|
||||||
var result *InstantiateResult
|
var result *InstantiateResult
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
@@ -713,7 +713,7 @@ func cloneSubgraphViaDaemon(client *rpc.Client, subgraph *TemplateSubgraph, opts
|
|||||||
AcceptanceCriteria: substituteVariables(oldIssue.AcceptanceCriteria, opts.Vars),
|
AcceptanceCriteria: substituteVariables(oldIssue.AcceptanceCriteria, opts.Vars),
|
||||||
Assignee: issueAssignee,
|
Assignee: issueAssignee,
|
||||||
EstimatedMinutes: oldIssue.EstimatedMinutes,
|
EstimatedMinutes: oldIssue.EstimatedMinutes,
|
||||||
Wisp: opts.Wisp,
|
Ephemeral: opts.Ephemeral,
|
||||||
IDPrefix: opts.Prefix, // bd-hobo: distinct prefixes for mols/wisps
|
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,
|
IssueType: oldIssue.IssueType,
|
||||||
Assignee: issueAssignee,
|
Assignee: issueAssignee,
|
||||||
EstimatedMinutes: oldIssue.EstimatedMinutes,
|
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
|
IDPrefix: opts.Prefix, // bd-hobo: distinct prefixes for mols/wisps
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "worker",
|
Assignee: "worker",
|
||||||
Sender: "manager",
|
Sender: "manager",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "manager",
|
Assignee: "manager",
|
||||||
Sender: "worker",
|
Sender: "worker",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now.Add(time.Minute),
|
CreatedAt: now.Add(time.Minute),
|
||||||
UpdatedAt: now.Add(time.Minute),
|
UpdatedAt: now.Add(time.Minute),
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ func TestThreadTraversal(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "worker",
|
Assignee: "worker",
|
||||||
Sender: "manager",
|
Sender: "manager",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now.Add(2 * time.Minute),
|
CreatedAt: now.Add(2 * time.Minute),
|
||||||
UpdatedAt: now.Add(2 * time.Minute),
|
UpdatedAt: now.Add(2 * time.Minute),
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ func TestThreadTraversalEmptyThread(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "sender",
|
Assignee: "sender",
|
||||||
Sender: "user",
|
Sender: "user",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now.Add(time.Minute),
|
CreatedAt: now.Add(time.Minute),
|
||||||
UpdatedAt: now.Add(time.Minute),
|
UpdatedAt: now.Add(time.Minute),
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ func TestThreadTraversalBranching(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "sender",
|
Assignee: "sender",
|
||||||
Sender: "another-user",
|
Sender: "another-user",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now.Add(2 * time.Minute),
|
CreatedAt: now.Add(2 * time.Minute),
|
||||||
UpdatedAt: now.Add(2 * time.Minute),
|
UpdatedAt: now.Add(2 * time.Minute),
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -380,7 +380,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Assignee: "user",
|
Assignee: "user",
|
||||||
Sender: "sender",
|
Sender: "sender",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: now.Add(time.Minute),
|
CreatedAt: now.Add(time.Minute),
|
||||||
UpdatedAt: now.Add(time.Minute),
|
UpdatedAt: now.Add(time.Minute),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) │
|
│ (from template) │ │ (local-only) │ │ (→ digest) │
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ Beads uses a chemistry metaphor for template-based workflows. See [MOLECULES.md]
|
|||||||
|-------|-------|---------|---------|
|
|-------|-------|---------|---------|
|
||||||
| Solid | Proto | `.beads/` | `bd mol catalog` |
|
| Solid | Proto | `.beads/` | `bd mol catalog` |
|
||||||
| Liquid | Mol | `.beads/` | `bd pour` |
|
| 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
|
### Proto/Template Commands
|
||||||
|
|
||||||
@@ -385,17 +385,17 @@ bd pour <proto-id> --attach <other-proto> --json
|
|||||||
### Wisp Commands
|
### Wisp Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Instantiate proto as ephemeral wisp (solid → vapor)
|
# Instantiate proto as ephemeral issue (solid → vapor)
|
||||||
bd wisp create <proto-id> --var key=value --json
|
bd ephemeral create <proto-id> --var key=value --json
|
||||||
|
|
||||||
# List all wisps
|
# List all wisps
|
||||||
bd wisp list --json
|
bd ephemeral list --json
|
||||||
bd wisp list --all --json # Include closed
|
bd ephemeral list --all --json # Include closed
|
||||||
|
|
||||||
# Garbage collect orphaned wisps
|
# Garbage collect orphaned wisps
|
||||||
bd wisp gc --json
|
bd ephemeral gc --json
|
||||||
bd wisp gc --age 24h --json # Custom age threshold
|
bd ephemeral gc --age 24h --json # Custom age threshold
|
||||||
bd wisp gc --dry-run # Preview what would be cleaned
|
bd ephemeral gc --dry-run # Preview what would be cleaned
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bonding (Combining Work)
|
### Bonding (Combining Work)
|
||||||
@@ -424,29 +424,29 @@ bd mol bond <A> <B> --dry-run
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Compress wisp to permanent digest
|
# Compress wisp to permanent digest
|
||||||
bd mol squash <wisp-id> --json
|
bd mol squash <ephemeral-id> --json
|
||||||
|
|
||||||
# With agent-provided summary
|
# With agent-provided summary
|
||||||
bd mol squash <wisp-id> --summary "Work completed" --json
|
bd mol squash <ephemeral-id> --summary "Work completed" --json
|
||||||
|
|
||||||
# Preview
|
# Preview
|
||||||
bd mol squash <wisp-id> --dry-run
|
bd mol squash <ephemeral-id> --dry-run
|
||||||
|
|
||||||
# Keep wisp children after squash
|
# Keep wisp children after squash
|
||||||
bd mol squash <wisp-id> --keep-children --json
|
bd mol squash <ephemeral-id> --keep-children --json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Burn (Discard Wisp)
|
### Burn (Discard Wisp)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Delete wisp without digest (destructive)
|
# Delete wisp without digest (destructive)
|
||||||
bd mol burn <wisp-id> --json
|
bd mol burn <ephemeral-id> --json
|
||||||
|
|
||||||
# Preview
|
# Preview
|
||||||
bd mol burn <wisp-id> --dry-run
|
bd mol burn <ephemeral-id> --dry-run
|
||||||
|
|
||||||
# Skip confirmation
|
# Skip confirmation
|
||||||
bd mol burn <wisp-id> --force --json
|
bd mol burn <ephemeral-id> --force --json
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Most mol commands require `--no-daemon` flag when daemon is running.
|
**Note:** Most mol commands require `--no-daemon` flag when daemon is running.
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ The 1-hour grace period ensures tombstones propagate even with minor clock drift
|
|||||||
|
|
||||||
## Wisps: Intentional Tombstone Bypass
|
## 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
|
### Why Wisps Don't Need Tombstones
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ For reusable workflows, beads uses a chemistry metaphor:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bd pour <proto> # Proto → Mol (persistent instance)
|
bd pour <proto> # Proto → Mol (persistent instance)
|
||||||
bd wisp create <proto> # Proto → Wisp (ephemeral instance)
|
bd ephemeral create <proto> # Proto → Wisp (ephemeral instance)
|
||||||
bd mol squash <id> # Mol/Wisp → Digest (permanent record)
|
bd mol squash <id> # Mol/Wisp → Digest (permanent record)
|
||||||
bd mol burn <id> # Wisp → nothing (discard)
|
bd mol burn <id> # Wisp → nothing (discard)
|
||||||
```
|
```
|
||||||
@@ -227,10 +227,10 @@ bd close <id> --reason "Done"
|
|||||||
Wisps accumulate if not squashed/burned:
|
Wisps accumulate if not squashed/burned:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bd wisp list # Check for orphans
|
bd ephemeral list # Check for orphans
|
||||||
bd mol squash <id> # Create digest
|
bd mol squash <id> # Create digest
|
||||||
bd mol burn <id> # Or discard
|
bd mol burn <id> # Or discard
|
||||||
bd wisp gc # Garbage collect old wisps
|
bd ephemeral gc # Garbage collect old wisps
|
||||||
```
|
```
|
||||||
|
|
||||||
## Layer Cake Architecture
|
## Layer Cake Architecture
|
||||||
@@ -273,7 +273,7 @@ bd dep tree <id> # Show dependency tree
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bd pour <proto> --var k=v # Template → persistent mol
|
bd pour <proto> --var k=v # Template → persistent mol
|
||||||
bd wisp create <proto> # Template → ephemeral wisp
|
bd ephemeral create <proto> # Template → ephemeral wisp
|
||||||
bd mol bond A B # Connect work graphs
|
bd mol bond A B # Connect work graphs
|
||||||
bd mol squash <id> # Compress to digest
|
bd mol squash <id> # Compress to digest
|
||||||
bd mol burn <id> # Discard without record
|
bd mol burn <id> # Discard without record
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ type CreateArgs struct {
|
|||||||
WaitsFor string `json:"waits_for,omitempty"` // Spawner issue ID to wait for
|
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
|
WaitsForGate string `json:"waits_for_gate,omitempty"` // Gate type: all-children or any-children
|
||||||
// Messaging fields (bd-kwro)
|
// Messaging fields (bd-kwro)
|
||||||
Sender string `json:"sender,omitempty"` // Who sent this (for messages)
|
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
|
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
|
RepliesTo string `json:"replies_to,omitempty"` // Issue ID for conversation threading
|
||||||
// ID generation (bd-hobo)
|
// 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
|
CreatedBy string `json:"created_by,omitempty"` // Who created the issue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +115,8 @@ type UpdateArgs struct {
|
|||||||
RemoveLabels []string `json:"remove_labels,omitempty"`
|
RemoveLabels []string `json:"remove_labels,omitempty"`
|
||||||
SetLabels []string `json:"set_labels,omitempty"`
|
SetLabels []string `json:"set_labels,omitempty"`
|
||||||
// Messaging fields (bd-kwro)
|
// Messaging fields (bd-kwro)
|
||||||
Sender *string `json:"sender,omitempty"` // Who sent this (for messages)
|
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
|
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
|
RepliesTo *string `json:"replies_to,omitempty"` // Issue ID for conversation threading
|
||||||
// Graph link fields (bd-fu83)
|
// Graph link fields (bd-fu83)
|
||||||
RelatesTo *string `json:"relates_to,omitempty"` // JSON array of related issue IDs
|
RelatesTo *string `json:"relates_to,omitempty"` // JSON array of related issue IDs
|
||||||
@@ -193,8 +193,8 @@ type ListArgs struct {
|
|||||||
// Parent filtering (bd-yqhh)
|
// Parent filtering (bd-yqhh)
|
||||||
ParentID string `json:"parent_id,omitempty"`
|
ParentID string `json:"parent_id,omitempty"`
|
||||||
|
|
||||||
// Wisp filtering (bd-bkul)
|
// Ephemeral filtering (bd-bkul)
|
||||||
Wisp *bool `json:"wisp,omitempty"`
|
Ephemeral *bool `json:"ephemeral,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountArgs represents arguments for the count operation
|
// CountArgs represents arguments for the count operation
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ func updatesFromArgs(a UpdateArgs) map[string]interface{} {
|
|||||||
if a.Sender != nil {
|
if a.Sender != nil {
|
||||||
u["sender"] = *a.Sender
|
u["sender"] = *a.Sender
|
||||||
}
|
}
|
||||||
if a.Wisp != nil {
|
if a.Ephemeral != nil {
|
||||||
u["wisp"] = *a.Wisp
|
u["ephemeral"] = *a.Ephemeral
|
||||||
}
|
}
|
||||||
if a.RepliesTo != nil {
|
if a.RepliesTo != nil {
|
||||||
u["replies_to"] = *a.RepliesTo
|
u["replies_to"] = *a.RepliesTo
|
||||||
@@ -176,8 +176,8 @@ func (s *Server) handleCreate(req *Request) Response {
|
|||||||
EstimatedMinutes: createArgs.EstimatedMinutes,
|
EstimatedMinutes: createArgs.EstimatedMinutes,
|
||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
// Messaging fields (bd-kwro)
|
// Messaging fields (bd-kwro)
|
||||||
Sender: createArgs.Sender,
|
Sender: createArgs.Sender,
|
||||||
Wisp: createArgs.Wisp,
|
Ephemeral: createArgs.Ephemeral,
|
||||||
// NOTE: RepliesTo now handled via replies-to dependency (Decision 004)
|
// NOTE: RepliesTo now handled via replies-to dependency (Decision 004)
|
||||||
// ID generation (bd-hobo)
|
// ID generation (bd-hobo)
|
||||||
IDPrefix: createArgs.IDPrefix,
|
IDPrefix: createArgs.IDPrefix,
|
||||||
@@ -844,8 +844,8 @@ func (s *Server) handleList(req *Request) Response {
|
|||||||
filter.ParentID = &listArgs.ParentID
|
filter.ParentID = &listArgs.ParentID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wisp filtering (bd-bkul)
|
// Ephemeral filtering (bd-bkul)
|
||||||
filter.Wisp = listArgs.Wisp
|
filter.Ephemeral = listArgs.Ephemeral
|
||||||
|
|
||||||
// Guard against excessive ID lists to avoid SQLite parameter limits
|
// Guard against excessive ID lists to avoid SQLite parameter limits
|
||||||
const maxIDs = 1000
|
const maxIDs = 1000
|
||||||
@@ -1475,7 +1475,7 @@ func (s *Server) handleGateCreate(req *Request) Response {
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1, // Gates are typically high priority
|
Priority: 1, // Gates are typically high priority
|
||||||
Assignee: "deacon/",
|
Assignee: "deacon/",
|
||||||
Wisp: true, // Gates are wisps (ephemeral)
|
Ephemeral: true, // Gates are wisps (ephemeral)
|
||||||
AwaitType: args.AwaitType,
|
AwaitType: args.AwaitType,
|
||||||
AwaitID: args.AwaitID,
|
AwaitID: args.AwaitID,
|
||||||
Timeout: args.Timeout,
|
Timeout: args.Timeout,
|
||||||
|
|||||||
@@ -885,7 +885,7 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if wisp.Valid && wisp.Int64 != 0 {
|
if wisp.Valid && wisp.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
if pinned.Valid && pinned.Int64 != 0 {
|
||||||
@@ -1006,7 +1006,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if wisp.Valid && wisp.Int64 != 0 {
|
if wisp.Valid && wisp.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
if pinned.Valid && pinned.Int64 != 0 {
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ func TestRepliesTo(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Sender: "alice",
|
Sender: "alice",
|
||||||
Assignee: "bob",
|
Assignee: "bob",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ func TestRepliesTo(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Sender: "bob",
|
Sender: "bob",
|
||||||
Assignee: "alice",
|
Assignee: "alice",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -363,7 +363,7 @@ func TestRepliesTo_Chain(t *testing.T) {
|
|||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Sender: "user",
|
Sender: "user",
|
||||||
Assignee: "inbox",
|
Assignee: "inbox",
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -415,7 +415,7 @@ func TestWispField(t *testing.T) {
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -426,7 +426,7 @@ func TestWispField(t *testing.T) {
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: false,
|
Ephemeral: false,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -443,7 +443,7 @@ func TestWispField(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetIssue failed: %v", err)
|
t.Fatalf("GetIssue failed: %v", err)
|
||||||
}
|
}
|
||||||
if !savedWisp.Wisp {
|
if !savedWisp.Ephemeral {
|
||||||
t.Error("Wisp issue should have Wisp=true")
|
t.Error("Wisp issue should have Wisp=true")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@ func TestWispField(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetIssue failed: %v", err)
|
t.Fatalf("GetIssue failed: %v", err)
|
||||||
}
|
}
|
||||||
if savedPermanent.Wisp {
|
if savedPermanent.Ephemeral {
|
||||||
t.Error("Permanent issue should have Wisp=false")
|
t.Error("Permanent issue should have Wisp=false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,7 +468,7 @@ func TestWispFilter(t *testing.T) {
|
|||||||
Status: types.StatusClosed, // Closed for cleanup test
|
Status: types.StatusClosed, // Closed for cleanup test
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeMessage,
|
IssueType: types.TypeMessage,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -483,7 +483,7 @@ func TestWispFilter(t *testing.T) {
|
|||||||
Status: types.StatusClosed,
|
Status: types.StatusClosed,
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
IssueType: types.TypeTask,
|
IssueType: types.TypeTask,
|
||||||
Wisp: false,
|
Ephemeral: false,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -497,7 +497,7 @@ func TestWispFilter(t *testing.T) {
|
|||||||
closedStatus := types.StatusClosed
|
closedStatus := types.StatusClosed
|
||||||
wispFilter := types.IssueFilter{
|
wispFilter := types.IssueFilter{
|
||||||
Status: &closedStatus,
|
Status: &closedStatus,
|
||||||
Wisp: &wispTrue,
|
Ephemeral: &wispTrue,
|
||||||
}
|
}
|
||||||
|
|
||||||
wispIssues, err := store.SearchIssues(ctx, "", wispFilter)
|
wispIssues, err := store.SearchIssues(ctx, "", wispFilter)
|
||||||
@@ -512,7 +512,7 @@ func TestWispFilter(t *testing.T) {
|
|||||||
wispFalse := false
|
wispFalse := false
|
||||||
nonWispFilter := types.IssueFilter{
|
nonWispFilter := types.IssueFilter{
|
||||||
Status: &closedStatus,
|
Status: &closedStatus,
|
||||||
Wisp: &wispFalse,
|
Ephemeral: &wispFalse,
|
||||||
}
|
}
|
||||||
|
|
||||||
permanentIssues, err := store.SearchIssues(ctx, "", nonWispFilter)
|
permanentIssues, err := store.SearchIssues(ctx, "", nonWispFilter)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
wisp := 0
|
wisp := 0
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wisp = 1
|
wisp = 1
|
||||||
}
|
}
|
||||||
pinned := 0
|
pinned := 0
|
||||||
@@ -94,7 +94,7 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
wisp := 0
|
wisp := 0
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wisp = 1
|
wisp = 1
|
||||||
}
|
}
|
||||||
pinned := 0
|
pinned := 0
|
||||||
|
|||||||
@@ -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)
|
err := tx.QueryRowContext(ctx, `SELECT id FROM issues WHERE id = ?`, issue.ID).Scan(&existingID)
|
||||||
|
|
||||||
wisp := 0
|
wisp := 0
|
||||||
if issue.Wisp {
|
if issue.Ephemeral {
|
||||||
wisp = 1
|
wisp = 1
|
||||||
}
|
}
|
||||||
pinned := 0
|
pinned := 0
|
||||||
|
|||||||
@@ -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.
|
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
|
||||||
filtered := make([]*types.Issue, 0, len(allIssues))
|
filtered := make([]*types.Issue, 0, len(allIssues))
|
||||||
for _, issue := range allIssues {
|
for _, issue := range allIssues {
|
||||||
if !issue.Wisp {
|
if !issue.Ephemeral {
|
||||||
filtered = append(filtered, issue)
|
filtered = append(filtered, issue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if wisp.Valid && wisp.Int64 != 0 {
|
if wisp.Valid && wisp.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
if pinned.Valid && pinned.Int64 != 0 {
|
||||||
@@ -562,7 +562,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if wisp.Valid && wisp.Int64 != 0 {
|
if wisp.Valid && wisp.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
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)
|
// Wisp filtering (bd-kwro.9)
|
||||||
if filter.Wisp != nil {
|
if filter.Ephemeral != nil {
|
||||||
if *filter.Wisp {
|
if *filter.Ephemeral {
|
||||||
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
||||||
} else {
|
} else {
|
||||||
whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)")
|
whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)")
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if ephemeral.Valid && ephemeral.Int64 != 0 {
|
if ephemeral.Valid && ephemeral.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
if pinned.Valid && pinned.Int64 != 0 {
|
||||||
|
|||||||
@@ -1089,8 +1089,8 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wisp filtering (bd-kwro.9)
|
// Wisp filtering (bd-kwro.9)
|
||||||
if filter.Wisp != nil {
|
if filter.Ephemeral != nil {
|
||||||
if *filter.Wisp {
|
if *filter.Ephemeral {
|
||||||
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
whereClauses = append(whereClauses, "ephemeral = 1") // SQL column is still 'ephemeral'
|
||||||
} else {
|
} else {
|
||||||
whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)")
|
whereClauses = append(whereClauses, "(ephemeral = 0 OR ephemeral IS NULL)")
|
||||||
@@ -1244,7 +1244,7 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
|||||||
issue.Sender = sender.String
|
issue.Sender = sender.String
|
||||||
}
|
}
|
||||||
if wisp.Valid && wisp.Int64 != 0 {
|
if wisp.Valid && wisp.Int64 != 0 {
|
||||||
issue.Wisp = true
|
issue.Ephemeral = true
|
||||||
}
|
}
|
||||||
// Pinned field (bd-7h5)
|
// Pinned field (bd-7h5)
|
||||||
if pinned.Valid && pinned.Int64 != 0 {
|
if pinned.Valid && pinned.Int64 != 0 {
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ type Issue struct {
|
|||||||
OriginalType string `json:"original_type,omitempty"` // Issue type before deletion (for tombstones)
|
OriginalType string `json:"original_type,omitempty"` // Issue type before deletion (for tombstones)
|
||||||
|
|
||||||
// Messaging fields (bd-kwro): inter-agent communication support
|
// Messaging fields (bd-kwro): inter-agent communication support
|
||||||
Sender string `json:"sender,omitempty"` // Who sent this (for messages)
|
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
|
Ephemeral bool `json:"ephemeral,omitempty"` // If true, not exported to JSONL; bulk-deleted when closed
|
||||||
// NOTE: RepliesTo, RelatesTo, DuplicateOf, SupersededBy moved to dependencies table
|
// NOTE: RepliesTo, RelatesTo, DuplicateOf, SupersededBy moved to dependencies table
|
||||||
// per Decision 004 (Edge Schema Consolidation). Use dependency API instead.
|
// per Decision 004 (Edge Schema Consolidation). Use dependency API instead.
|
||||||
|
|
||||||
@@ -598,8 +598,8 @@ type IssueFilter struct {
|
|||||||
// Tombstone filtering (bd-1bu)
|
// Tombstone filtering (bd-1bu)
|
||||||
IncludeTombstones bool // If false (default), exclude tombstones from results
|
IncludeTombstones bool // If false (default), exclude tombstones from results
|
||||||
|
|
||||||
// Wisp filtering (bd-kwro.9)
|
// Ephemeral filtering (bd-kwro.9)
|
||||||
Wisp *bool // Filter by wisp flag (nil = any, true = only wisps, false = only non-wisps)
|
Ephemeral *bool // Filter by ephemeral flag (nil = any, true = only ephemeral, false = only persistent)
|
||||||
|
|
||||||
// Pinned filtering (bd-7h5)
|
// Pinned filtering (bd-7h5)
|
||||||
Pinned *bool // Filter by pinned flag (nil = any, true = only pinned, false = only non-pinned)
|
Pinned *bool // Filter by pinned flag (nil = any, true = only pinned, false = only non-pinned)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ bd mol spawn mol-release --var version=2.0 # With variable substitution
|
|||||||
**Chemistry shortcuts:**
|
**Chemistry shortcuts:**
|
||||||
```bash
|
```bash
|
||||||
bd pour mol-feature # Shortcut for spawn --pour
|
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
|
### Spawn with Immediate Execution
|
||||||
@@ -164,7 +164,7 @@ bd mol bond mol-feature mol-deploy --as "Feature with Deploy"
|
|||||||
### Creating Wisps
|
### Creating Wisps
|
||||||
|
|
||||||
```bash
|
```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-patrol # Same (spawn defaults to wisp)
|
||||||
bd mol spawn mol-check --var target=db # With variables
|
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
|
### Listing Wisps
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bd wisp list # List all wisps
|
bd ephemeral list # List all wisps
|
||||||
bd wisp list --json # Machine-readable
|
bd ephemeral list --json # Machine-readable
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ending Wisps
|
### Ending Wisps
|
||||||
@@ -198,7 +198,7 @@ Use burn for routine work with no archival value.
|
|||||||
### Garbage Collection
|
### Garbage Collection
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
# Patrol proto exists
|
# Patrol proto exists
|
||||||
bd wisp create mol-patrol
|
bd ephemeral create mol-patrol
|
||||||
|
|
||||||
# Execute patrol work...
|
# Execute patrol work...
|
||||||
|
|
||||||
@@ -328,9 +328,9 @@ bd mol distill bd-release-epic --as "Release Process" --var version=X.Y.Z
|
|||||||
| `bd mol squash <mol>` | Compress wisp children to digest |
|
| `bd mol squash <mol>` | Compress wisp children to digest |
|
||||||
| `bd mol burn <wisp>` | Delete wisp without trace |
|
| `bd mol burn <wisp>` | Delete wisp without trace |
|
||||||
| `bd pour <proto>` | Shortcut for `spawn --pour` |
|
| `bd pour <proto>` | Shortcut for `spawn --pour` |
|
||||||
| `bd wisp create <proto>` | Create ephemeral wisp |
|
| `bd ephemeral create <proto>` | Create ephemeral wisp |
|
||||||
| `bd wisp list` | List all wisps |
|
| `bd ephemeral list` | List all wisps |
|
||||||
| `bd wisp gc` | Garbage collect orphaned wisps |
|
| `bd ephemeral gc` | Garbage collect orphaned wisps |
|
||||||
| `bd ship <capability>` | Publish capability for cross-project deps |
|
| `bd ship <capability>` | 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"**
|
**"Wisp commands fail"**
|
||||||
- Wisps stored in `.beads-wisp/` (separate from `.beads/`)
|
- 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"**
|
**"External dependency not satisfied"**
|
||||||
- Target project must have closed issue with `provides:<capability>` label
|
- Target project must have closed issue with `provides:<capability>` label
|
||||||
|
|||||||
Reference in New Issue
Block a user