refactor: rename Ephemeral → Wisp (Steam Engine metaphor)

Wisp = ephemeral vapor produced by the Steam Engine (Gas Town).
This aligns with the metaphor:
- Claude = Fire
- Claude Code = Steam
- Gas Town = Steam Engine
- Wisps = ephemeral vapor it produces

Changes:
- types.Issue.Ephemeral → types.Issue.Wisp
- types.IssueFilter.Ephemeral → types.IssueFilter.Wisp
- JSON field: "ephemeral" → "wisp"
- CLI flag: --ephemeral → --wisp (bd cleanup)
- All tests updated

Note: SQLite column remains "ephemeral" (no migration needed).
This is a breaking change for JSON consumers using 0.33.0.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-21 15:22:45 -08:00
parent 61361995cb
commit 358d076fde
23 changed files with 214 additions and 209 deletions

View File

@@ -704,20 +704,20 @@ func flushToJSONLWithState(state flushState) {
}
// Convert map to slice (will be sorted by writeJSONLAtomic)
// Filter out ephemeral issues - they should never be exported to JSONL (bd-687g)
// Ephemeral issues exist only in SQLite and are shared via .beads/redirect, not JSONL.
// Filter out wisps - they should never be exported to JSONL (bd-687g)
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
// This prevents "zombie" issues that resurrect after mol squash deletes them.
issues := make([]*types.Issue, 0, len(issueMap))
ephemeralSkipped := 0
wispsSkipped := 0
for _, issue := range issueMap {
if issue.Ephemeral {
ephemeralSkipped++
if issue.Wisp {
wispsSkipped++
continue
}
issues = append(issues, issue)
}
if ephemeralSkipped > 0 {
debug.Logf("auto-flush: filtered %d ephemeral issues from export", ephemeralSkipped)
if wispsSkipped > 0 {
debug.Logf("auto-flush: filtered %d wisps from export", wispsSkipped)
}
// Filter issues by prefix in multi-repo mode for non-primary repos (fixes GH #437)

View File

@@ -44,8 +44,8 @@ Delete all closed issues and prune tombstones:
Delete issues closed more than 30 days ago:
bd cleanup --older-than 30 --force
Delete only closed ephemeral issues (transient messages):
bd cleanup --ephemeral --force
Delete only closed wisps (transient molecules):
bd cleanup --wisp --force
Preview what would be deleted/pruned:
bd cleanup --dry-run
@@ -69,7 +69,7 @@ SEE ALSO:
cascade, _ := cmd.Flags().GetBool("cascade")
olderThanDays, _ := cmd.Flags().GetInt("older-than")
hardDelete, _ := cmd.Flags().GetBool("hard")
ephemeralOnly, _ := cmd.Flags().GetBool("ephemeral")
wispOnly, _ := cmd.Flags().GetBool("wisp")
// Calculate custom TTL for --hard mode
// When --hard is set, use --older-than days as the tombstone TTL cutoff
@@ -115,10 +115,10 @@ SEE ALSO:
filter.ClosedBefore = &cutoffTime
}
// Add ephemeral filter if specified (bd-kwro.9)
if ephemeralOnly {
ephemeralTrue := true
filter.Ephemeral = &ephemeralTrue
// Add wisp filter if specified (bd-kwro.9)
if wispOnly {
wispTrue := true
filter.Wisp = &wispTrue
}
// Get all closed issues matching filter
@@ -153,17 +153,17 @@ SEE ALSO:
if olderThanDays > 0 {
result["filter"] = fmt.Sprintf("older than %d days", olderThanDays)
}
if ephemeralOnly {
result["ephemeral"] = true
if wispOnly {
result["wisp"] = true
}
output, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(output))
} else {
msg := "No closed issues to delete"
if ephemeralOnly && olderThanDays > 0 {
msg = fmt.Sprintf("No closed ephemeral issues older than %d days to delete", olderThanDays)
} else if ephemeralOnly {
msg = "No closed ephemeral issues to delete"
if wispOnly && olderThanDays > 0 {
msg = fmt.Sprintf("No closed wisps older than %d days to delete", olderThanDays)
} else if wispOnly {
msg = "No closed wisps to delete"
} else if olderThanDays > 0 {
msg = fmt.Sprintf("No closed issues older than %d days to delete", olderThanDays)
}
@@ -181,8 +181,8 @@ SEE ALSO:
// Show preview
if !force && !dryRun {
issueType := "closed"
if ephemeralOnly {
issueType = "closed ephemeral"
if wispOnly {
issueType = "closed wisp"
}
fmt.Fprintf(os.Stderr, "Would delete %d %s issue(s). Use --force to confirm or --dry-run to preview.\n", len(issueIDs), issueType)
os.Exit(1)
@@ -190,8 +190,8 @@ SEE ALSO:
if !jsonOutput {
issueType := "closed"
if ephemeralOnly {
issueType = "closed ephemeral"
if wispOnly {
issueType = "closed wisp"
}
if olderThanDays > 0 {
fmt.Printf("Found %d %s issue(s) older than %d days\n", len(closedIssues), issueType, olderThanDays)
@@ -255,6 +255,6 @@ func init() {
cleanupCmd.Flags().Bool("cascade", false, "Recursively delete all dependent issues")
cleanupCmd.Flags().Int("older-than", 0, "Only delete issues closed more than N days ago (0 = all closed issues)")
cleanupCmd.Flags().Bool("hard", false, "Bypass tombstone TTL safety; use --older-than days as cutoff")
cleanupCmd.Flags().Bool("ephemeral", false, "Only delete closed ephemeral issues (transient messages)")
cleanupCmd.Flags().Bool("wisp", false, "Only delete closed wisps (transient molecules)")
rootCmd.AddCommand(cleanupCmd)
}

View File

@@ -346,11 +346,11 @@ Examples:
}
}
// Filter out ephemeral issues - they should never be exported to JSONL (bd-687g)
// Ephemeral issues exist only in SQLite and are shared via .beads/redirect, not JSONL.
// Filter out wisps - they should never be exported to JSONL (bd-687g)
// Wisps exist only in SQLite and are shared via .beads/redirect, not JSONL.
filtered := make([]*types.Issue, 0, len(issues))
for _, issue := range issues {
if !issue.Ephemeral {
if !issue.Wisp {
filtered = append(filtered, issue)
}
}

View File

@@ -292,10 +292,10 @@ var versionChanges = []VersionChange{
Version: "0.33.0",
Date: "2025-12-21",
Changes: []string{
"NEW: Ephemeral molecules (bd-2vh3) - bd mol spawn creates ephemeral issues by default",
"NEW: Ephemeral issues live only in SQLite, never export to JSONL (prevents zombie resurrection)",
"NEW: --persistent flag on bd mol spawn to opt out of ephemeral spawning",
"NEW: bd mol squash compresses ephemeral children into digest issue",
"NEW: Wisp molecules (bd-2vh3) - bd mol spawn creates wisp issues by default",
"NEW: Wisp issues live only in SQLite, never export to JSONL (prevents zombie resurrection)",
"NEW: --persistent flag on bd mol spawn to opt out of wisp spawning",
"NEW: bd mol squash compresses wisp children into digest issue",
"NEW: --summary flag on bd mol squash for agent-provided AI summaries",
"FIX: DeleteIssue now cascades to comments table (bd-687g)",
},
@@ -391,7 +391,7 @@ var versionChanges = []VersionChange{
Date: "2025-12-16",
Changes: []string{
"bd setup droid (GH#598) - Factory.ai (Droid) IDE support",
"Messaging schema fields (bd-kwro.1) - New 'message' issue type, sender/ephemeral/replies_to/relates_to/duplicate_of/superseded_by fields",
"Messaging schema fields (bd-kwro.1) - New 'message' issue type, sender/wisp/replies_to/relates_to/duplicate_of/superseded_by fields",
"New dependency types: replies-to, relates-to, duplicates, supersedes",
"Windows build fixes (GH#585) - gosec lint errors resolved",
"Issue ID prefix extraction fix - Word-like suffixes now parse correctly",

View File

@@ -283,7 +283,7 @@ func bondProtoMol(ctx context.Context, s storage.Storage, proto, mol *types.Issu
return nil, fmt.Errorf("missing required variables: %s (use --var)", strings.Join(missingVars, ", "))
}
// Spawn the proto (ephemeral by default for molecule execution - bd-2vh3)
// Spawn the proto (wisp by default for molecule execution - bd-2vh3)
spawnResult, err := spawnMolecule(ctx, s, subgraph, vars, "", actorName, true)
if err != nil {
return nil, fmt.Errorf("spawning proto: %w", err)

View File

@@ -89,7 +89,7 @@ func runMolRun(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Spawn the molecule with actor as assignee (ephemeral for cleanup - bd-2vh3)
// Spawn the molecule with actor as assignee (wisp for cleanup - bd-2vh3)
result, err := spawnMolecule(ctx, store, subgraph, vars, actor, actor, true)
if err != nil {
fmt.Fprintf(os.Stderr, "Error spawning molecule: %v\n", err)

View File

@@ -182,9 +182,9 @@ func runMolSpawn(cmd *cobra.Command, args []string) {
}
// Clone the subgraph (spawn the molecule)
// Spawned molecules are ephemeral by default (bd-2vh3) - use --persistent to opt out
ephemeral := !persistent
result, err := spawnMolecule(ctx, store, subgraph, vars, assignee, actor, ephemeral)
// Spawned molecules are wisps by default (bd-2vh3) - use --persistent to opt out
wisp := !persistent
result, err := spawnMolecule(ctx, store, subgraph, vars, assignee, actor, wisp)
if err != nil {
fmt.Fprintf(os.Stderr, "Error spawning molecule: %v\n", err)
os.Exit(1)
@@ -236,7 +236,7 @@ func init() {
molSpawnCmd.Flags().String("assignee", "", "Assign the root issue to this agent/user")
molSpawnCmd.Flags().StringSlice("attach", []string{}, "Proto to attach after spawning (repeatable)")
molSpawnCmd.Flags().String("attach-type", types.BondTypeSequential, "Bond type for attachments: sequential, parallel, or conditional")
molSpawnCmd.Flags().Bool("persistent", false, "Create non-ephemeral issues (default: ephemeral for cleanup)")
molSpawnCmd.Flags().Bool("persistent", false, "Create non-wisp issues (default: wisp for cleanup)")
molCmd.AddCommand(molSpawnCmd)
}

View File

@@ -18,17 +18,17 @@ import (
var molSquashCmd = &cobra.Command{
Use: "squash <molecule-id>",
Short: "Compress molecule execution into a digest",
Long: `Squash a molecule's ephemeral children into a single digest issue.
Long: `Squash a molecule's wisp children into a single digest issue.
This command collects all ephemeral child issues of a molecule, generates
a summary digest, and optionally deletes the ephemeral children.
This command collects all wisp child issues of a molecule, generates
a summary digest, and optionally deletes the wisps.
The squash operation:
1. Loads the molecule and all its children
2. Filters to only ephemeral issues
2. Filters to only wisps (ephemeral vapor from the Steam Engine)
3. Generates a digest (summary of work done)
4. Creates a non-ephemeral digest issue
5. Deletes the ephemeral children (unless --keep-children)
4. Creates a permanent digest issue
5. Deletes the wisps (unless --keep-children)
AGENT INTEGRATION:
Use --summary to provide an AI-generated summary. This keeps bd as a pure
@@ -36,7 +36,7 @@ tool - the calling agent (Gas Town polecat, Claude Code, etc.) is responsible
for generating intelligent summaries. Without --summary, a basic concatenation
of child issue content is used.
This is part of the ephemeral workflow: spawn creates ephemeral issues,
This is part of the wisp workflow: spawn creates wisps,
execution happens, squash compresses the trace into an outcome.
Example:
@@ -92,39 +92,39 @@ func runMolSquash(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Filter to only ephemeral children (exclude root)
var ephemeralChildren []*types.Issue
// Filter to only wisp children (exclude root)
var wispChildren []*types.Issue
for _, issue := range subgraph.Issues {
if issue.ID == subgraph.Root.ID {
continue // Skip root
}
if issue.Ephemeral {
ephemeralChildren = append(ephemeralChildren, issue)
if issue.Wisp {
wispChildren = append(wispChildren, issue)
}
}
if len(ephemeralChildren) == 0 {
if len(wispChildren) == 0 {
if jsonOutput {
outputJSON(SquashResult{
MoleculeID: moleculeID,
SquashedCount: 0,
})
} else {
fmt.Printf("No ephemeral children found for molecule %s\n", moleculeID)
fmt.Printf("No wisp children found for molecule %s\n", moleculeID)
}
return
}
if dryRun {
fmt.Printf("\nDry run: would squash %d ephemeral children of %s\n\n", len(ephemeralChildren), moleculeID)
fmt.Printf("\nDry run: would squash %d wisp children of %s\n\n", len(wispChildren), moleculeID)
fmt.Printf("Root: %s\n", subgraph.Root.Title)
fmt.Printf("\nEphemeral children to squash:\n")
for _, issue := range ephemeralChildren {
fmt.Printf("\nWisp children to squash:\n")
for _, issue := range wispChildren {
status := string(issue.Status)
fmt.Printf(" - [%s] %s (%s)\n", status, issue.Title, issue.ID)
}
fmt.Printf("\nDigest preview:\n")
digest := generateDigest(subgraph.Root, ephemeralChildren)
digest := generateDigest(subgraph.Root, wispChildren)
// Show first 500 chars of digest
if len(digest) > 500 {
fmt.Printf("%s...\n", digest[:500])
@@ -140,7 +140,7 @@ func runMolSquash(cmd *cobra.Command, args []string) {
}
// Perform the squash
result, err := squashMolecule(ctx, store, subgraph.Root, ephemeralChildren, keepChildren, summary, actor)
result, err := squashMolecule(ctx, store, subgraph.Root, wispChildren, keepChildren, summary, actor)
if err != nil {
fmt.Fprintf(os.Stderr, "Error squashing molecule: %v\n", err)
os.Exit(1)
@@ -157,7 +157,7 @@ func runMolSquash(cmd *cobra.Command, args []string) {
fmt.Printf("%s Squashed molecule: %d children → 1 digest\n", ui.RenderPass("✓"), result.SquashedCount)
fmt.Printf(" Digest ID: %s\n", result.DigestID)
if result.DeletedCount > 0 {
fmt.Printf(" Deleted: %d ephemeral issues\n", result.DeletedCount)
fmt.Printf(" Deleted: %d wisps\n", result.DeletedCount)
} else if result.KeptChildren {
fmt.Printf(" Children preserved (--keep-children)\n")
}
@@ -235,16 +235,16 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c
digestContent = generateDigest(root, children)
}
// Create digest issue (non-ephemeral)
// Create digest issue (permanent, not a wisp)
now := time.Now()
digestIssue := &types.Issue{
Title: fmt.Sprintf("Digest: %s", root.Title),
Description: digestContent,
Status: types.StatusClosed,
CloseReason: fmt.Sprintf("Squashed from %d ephemeral steps", len(children)),
CloseReason: fmt.Sprintf("Squashed from %d wisps", len(children)),
Priority: root.Priority,
IssueType: types.TypeTask,
Ephemeral: false, // Digest is permanent
Wisp: false, // Digest is permanent, not a wisp
ClosedAt: &now,
}
@@ -280,9 +280,9 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c
return nil, err
}
// Delete ephemeral children (outside transaction for better error handling)
// Delete wisp children (outside transaction for better error handling)
if !keepChildren {
deleted, err := deleteEphemeralChildren(ctx, s, childIDs)
deleted, err := deleteWispChildren(ctx, s, childIDs)
if err != nil {
// Log but don't fail - digest was created successfully
fmt.Fprintf(os.Stderr, "Warning: failed to delete some children: %v\n", err)
@@ -293,8 +293,8 @@ func squashMolecule(ctx context.Context, s storage.Storage, root *types.Issue, c
return result, nil
}
// deleteEphemeralChildren removes the ephemeral issues from the database
func deleteEphemeralChildren(ctx context.Context, s storage.Storage, ids []string) (int, error) {
// deleteWispChildren removes the wisp issues from the database
func deleteWispChildren(ctx context.Context, s storage.Storage, ids []string) (int, error) {
// Type assert to SQLite storage for delete access
d, ok := s.(*sqlite.SQLiteStorage)
if !ok {
@@ -316,7 +316,7 @@ func deleteEphemeralChildren(ctx context.Context, s storage.Storage, ids []strin
func init() {
molSquashCmd.Flags().Bool("dry-run", false, "Preview what would be squashed")
molSquashCmd.Flags().Bool("keep-children", false, "Don't delete ephemeral children after squash")
molSquashCmd.Flags().Bool("keep-children", false, "Don't delete wisp children after squash")
molSquashCmd.Flags().String("summary", "", "Agent-provided summary (bypasses auto-generation)")
molCmd.AddCommand(molSquashCmd)

View File

@@ -489,7 +489,7 @@ func TestSquashMolecule(t *testing.T) {
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Ephemeral: true,
Wisp: true,
CloseReason: "Completed design",
}
child2 := &types.Issue{
@@ -498,7 +498,7 @@ func TestSquashMolecule(t *testing.T) {
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Ephemeral: true,
Wisp: true,
CloseReason: "Code merged",
}
@@ -547,7 +547,7 @@ func TestSquashMolecule(t *testing.T) {
if err != nil {
t.Fatalf("Failed to get digest: %v", err)
}
if digest.Ephemeral {
if digest.Wisp {
t.Error("Digest should NOT be ephemeral")
}
if digest.Status != types.StatusClosed {
@@ -591,11 +591,11 @@ func TestSquashMoleculeWithDelete(t *testing.T) {
}
child := &types.Issue{
Title: "Ephemeral Step",
Title: "Wisp Step",
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Ephemeral: true,
Wisp: true,
}
if err := s.CreateIssue(ctx, child, "test"); err != nil {
t.Fatalf("Failed to create child: %v", err)
@@ -700,12 +700,12 @@ func TestSquashMoleculeWithAgentSummary(t *testing.T) {
}
child := &types.Issue{
Title: "Ephemeral Step",
Title: "Wisp Step",
Description: "This should NOT appear in digest",
Status: types.StatusClosed,
Priority: 2,
IssueType: types.TypeTask,
Ephemeral: true,
Wisp: true,
CloseReason: "Done",
}
if err := s.CreateIssue(ctx, child, "test"); err != nil {
@@ -737,15 +737,15 @@ func TestSquashMoleculeWithAgentSummary(t *testing.T) {
}
// Verify auto-generated content is NOT present
if strings.Contains(digest.Description, "Ephemeral Step") {
if strings.Contains(digest.Description, "Wisp Step") {
t.Error("Digest should NOT contain auto-generated content when agent summary provided")
}
}
// TestEphemeralFilteringFromExport verifies that ephemeral issues are filtered
// from JSONL export (bd-687g). Ephemeral issues should only exist in SQLite,
// TestWispFilteringFromExport verifies that wisp issues are filtered
// from JSONL export (bd-687g). Wisp issues should only exist in SQLite,
// not in issues.jsonl, to prevent "zombie" resurrection after mol squash.
func TestEphemeralFilteringFromExport(t *testing.T) {
func TestWispFilteringFromExport(t *testing.T) {
ctx := context.Background()
dbPath := t.TempDir() + "/test.db"
s, err := sqlite.New(ctx, dbPath)
@@ -757,27 +757,27 @@ func TestEphemeralFilteringFromExport(t *testing.T) {
t.Fatalf("Failed to set config: %v", err)
}
// Create a mix of ephemeral and non-ephemeral issues
// Create a mix of wisp and non-wisp issues
normalIssue := &types.Issue{
Title: "Normal Issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
Ephemeral: false,
Wisp: false,
}
ephemeralIssue := &types.Issue{
Title: "Ephemeral Issue",
wispIssue := &types.Issue{
Title: "Wisp Issue",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
Ephemeral: true,
Wisp: true,
}
if err := s.CreateIssue(ctx, normalIssue, "test"); err != nil {
t.Fatalf("Failed to create normal issue: %v", err)
}
if err := s.CreateIssue(ctx, ephemeralIssue, "test"); err != nil {
t.Fatalf("Failed to create ephemeral issue: %v", err)
if err := s.CreateIssue(ctx, wispIssue, "test"); err != nil {
t.Fatalf("Failed to create wisp issue: %v", err)
}
// Get all issues from DB - should include both
@@ -789,15 +789,15 @@ func TestEphemeralFilteringFromExport(t *testing.T) {
t.Fatalf("Expected 2 issues in DB, got %d", len(allIssues))
}
// Filter ephemeral issues (simulating export behavior)
// Filter wisp issues (simulating export behavior)
exportableIssues := make([]*types.Issue, 0)
for _, issue := range allIssues {
if !issue.Ephemeral {
if !issue.Wisp {
exportableIssues = append(exportableIssues, issue)
}
}
// Should only have the non-ephemeral issue
// Should only have the non-wisp issue
if len(exportableIssues) != 1 {
t.Errorf("Expected 1 exportable issue, got %d", len(exportableIssues))
}

View File

@@ -292,7 +292,7 @@ Example:
return
}
// Clone the subgraph (deprecated command, non-ephemeral for backwards compatibility)
// Clone the subgraph (deprecated command, non-wisp for backwards compatibility)
result, err := cloneSubgraph(ctx, store, subgraph, vars, assignee, actor, false)
if err != nil {
fmt.Fprintf(os.Stderr, "Error instantiating template: %v\n", err)
@@ -453,8 +453,8 @@ func substituteVariables(text string, vars map[string]string) string {
// cloneSubgraph creates new issues from the template with variable substitution
// If assignee is non-empty, it will be set on the root epic
// If ephemeral is true, spawned issues are marked for bulk deletion when closed (bd-2vh3)
func cloneSubgraph(ctx context.Context, s storage.Storage, subgraph *TemplateSubgraph, vars map[string]string, assignee string, actorName string, ephemeral bool) (*InstantiateResult, error) {
// If wisp is true, spawned issues are marked for bulk deletion when closed (bd-2vh3)
func cloneSubgraph(ctx context.Context, s storage.Storage, subgraph *TemplateSubgraph, vars map[string]string, assignee string, actorName string, wisp bool) (*InstantiateResult, error) {
if s == nil {
return nil, fmt.Errorf("no database connection")
}
@@ -484,7 +484,7 @@ func cloneSubgraph(ctx context.Context, s storage.Storage, subgraph *TemplateSub
IssueType: oldIssue.IssueType,
Assignee: issueAssignee,
EstimatedMinutes: oldIssue.EstimatedMinutes,
Ephemeral: ephemeral, // bd-2vh3: mark for cleanup when closed
Wisp: wisp, // bd-2vh3: mark for cleanup when closed
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

View File

@@ -27,7 +27,7 @@ func TestThreadTraversal(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "worker",
Sender: "manager",
Ephemeral: true,
Wisp: true,
CreatedAt: now,
UpdatedAt: now,
}
@@ -43,7 +43,7 @@ func TestThreadTraversal(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "manager",
Sender: "worker",
Ephemeral: true,
Wisp: true,
CreatedAt: now.Add(time.Minute),
UpdatedAt: now.Add(time.Minute),
}
@@ -59,7 +59,7 @@ func TestThreadTraversal(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "worker",
Sender: "manager",
Ephemeral: true,
Wisp: true,
CreatedAt: now.Add(2 * time.Minute),
UpdatedAt: now.Add(2 * time.Minute),
}
@@ -190,7 +190,7 @@ func TestThreadTraversalEmptyThread(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "user",
Sender: "sender",
Ephemeral: true,
Wisp: true,
CreatedAt: now,
UpdatedAt: now,
}
@@ -228,7 +228,7 @@ func TestThreadTraversalBranching(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "user",
Sender: "sender",
Ephemeral: true,
Wisp: true,
CreatedAt: now,
UpdatedAt: now,
}
@@ -245,7 +245,7 @@ func TestThreadTraversalBranching(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "sender",
Sender: "user",
Ephemeral: true,
Wisp: true,
CreatedAt: now.Add(time.Minute),
UpdatedAt: now.Add(time.Minute),
}
@@ -261,7 +261,7 @@ func TestThreadTraversalBranching(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "sender",
Sender: "another-user",
Ephemeral: true,
Wisp: true,
CreatedAt: now.Add(2 * time.Minute),
UpdatedAt: now.Add(2 * time.Minute),
}
@@ -364,7 +364,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "user",
Sender: "sender",
Ephemeral: true,
Wisp: true,
CreatedAt: now,
UpdatedAt: now,
}
@@ -380,7 +380,7 @@ func TestThreadTraversalOnlyRepliesTo(t *testing.T) {
IssueType: types.TypeMessage,
Assignee: "user",
Sender: "sender",
Ephemeral: true,
Wisp: true,
CreatedAt: now.Add(time.Minute),
UpdatedAt: now.Add(time.Minute),
}