feat(bd): add --ephemeral and --persistent flags to bd update (#1263)

Adds --ephemeral and --persistent flags to bd update command.

Author: aleiby
This commit is contained in:
aleiby
2026-01-22 15:54:04 -08:00
committed by GitHub
parent d037158624
commit 16749e6731
3 changed files with 109 additions and 1 deletions

View File

@@ -312,6 +312,94 @@ func TestCLI_UpdateLabels(t *testing.T) {
}
}
func TestCLI_UpdateEphemeral(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow CLI test in short mode")
}
// Note: Not using t.Parallel() because inProcessMutex serializes execution anyway
tmpDir := setupCLITestDB(t)
out := runBDInProcess(t, tmpDir, "create", "Issue for ephemeral testing", "-p", "2", "--json")
var issue map[string]interface{}
if err := json.Unmarshal([]byte(out), &issue); err != nil {
t.Fatalf("Failed to parse create output: %v", err)
}
id := issue["id"].(string)
// Mark as ephemeral
runBDInProcess(t, tmpDir, "update", id, "--ephemeral")
out = runBDInProcess(t, tmpDir, "show", id, "--json")
var updated []map[string]interface{}
if err := json.Unmarshal([]byte(out), &updated); err != nil {
t.Fatalf("Failed to parse show output: %v", err)
}
if updated[0]["ephemeral"] != true {
t.Errorf("Expected ephemeral to be true after --ephemeral, got: %v", updated[0]["ephemeral"])
}
}
func TestCLI_UpdatePersistent(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow CLI test in short mode")
}
// Note: Not using t.Parallel() because inProcessMutex serializes execution anyway
tmpDir := setupCLITestDB(t)
// Create ephemeral issue directly
out := runBDInProcess(t, tmpDir, "create", "Ephemeral issue", "-p", "2", "--ephemeral", "--json")
var issue map[string]interface{}
if err := json.Unmarshal([]byte(out), &issue); err != nil {
t.Fatalf("Failed to parse create output: %v", err)
}
id := issue["id"].(string)
// Verify it's ephemeral
out = runBDInProcess(t, tmpDir, "show", id, "--json")
var initial []map[string]interface{}
if err := json.Unmarshal([]byte(out), &initial); err != nil {
t.Fatalf("Failed to parse show output: %v", err)
}
if initial[0]["ephemeral"] != true {
t.Fatalf("Expected issue to be ephemeral initially, got: %v", initial[0]["ephemeral"])
}
// Promote to persistent
runBDInProcess(t, tmpDir, "update", id, "--persistent")
out = runBDInProcess(t, tmpDir, "show", id, "--json")
var updated []map[string]interface{}
if err := json.Unmarshal([]byte(out), &updated); err != nil {
t.Fatalf("Failed to parse show output after persistent: %v", err)
}
if updated[0]["ephemeral"] == true {
t.Errorf("Expected ephemeral to be false after --persistent, got: %v", updated[0]["ephemeral"])
}
}
func TestCLI_UpdateEphemeralMutualExclusion(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow CLI test in short mode")
}
// Note: Not using t.Parallel() because inProcessMutex serializes execution anyway
tmpDir := setupCLITestDB(t)
out := runBDInProcess(t, tmpDir, "create", "Issue for mutual exclusion test", "-p", "2", "--json")
var issue map[string]interface{}
json.Unmarshal([]byte(out), &issue)
id := issue["id"].(string)
// Both flags should error
_, stderr, err := runBDInProcessAllowError(t, tmpDir, "update", id, "--ephemeral", "--persistent")
if err == nil {
t.Errorf("Expected error when both flags specified, got none")
}
if !strings.Contains(stderr, "cannot specify both") {
t.Errorf("Expected mutual exclusion error message, got: %v", stderr)
}
}
func TestCLI_Close(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow CLI test in short mode")

View File

@@ -165,6 +165,19 @@ create, update, show, or close operation).`,
updates["defer_until"] = t
}
}
// Ephemeral/persistent flags
// Note: storage layer uses "wisp" field name, maps to "ephemeral" column
ephemeralChanged := cmd.Flags().Changed("ephemeral")
persistentChanged := cmd.Flags().Changed("persistent")
if ephemeralChanged && persistentChanged {
FatalErrorRespectJSON("cannot specify both --ephemeral and --persistent flags")
}
if ephemeralChanged {
updates["wisp"] = true
}
if persistentChanged {
updates["wisp"] = false
}
// Get claim flag
claimFlag, _ := cmd.Flags().GetBool("claim")
@@ -278,6 +291,10 @@ create, update, show, or close operation).`,
empty := ""
updateArgs.DeferUntil = &empty
}
// Ephemeral/persistent
if wisp, ok := updates["wisp"].(bool); ok {
updateArgs.Ephemeral = &wisp
}
// Set claim flag for atomic claim operation
updateArgs.Claim = claimFlag
@@ -613,6 +630,9 @@ func init() {
updateCmd.Flags().String("defer", "", "Defer until date (empty to clear). Issue hidden from bd ready until then")
// Gate fields (bd-z6kw)
updateCmd.Flags().String("await-id", "", "Set gate await_id (e.g., GitHub run ID for gh:run gates)")
// Ephemeral/persistent flags
updateCmd.Flags().Bool("ephemeral", false, "Mark issue as ephemeral (wisp) - not exported to JSONL")
updateCmd.Flags().Bool("persistent", false, "Mark issue as persistent (promote wisp to regular issue)")
updateCmd.ValidArgsFunction = issueIDCompletion
rootCmd.AddCommand(updateCmd)
}