feat: complete bd-kwro messaging & knowledge graph epic

- Add bd cleanup --ephemeral flag for transient message cleanup (bd-kwro.9)
- Add Ephemeral filter to IssueFilter type
- Add ephemeral filtering to SQLite storage queries

Tests (bd-kwro.10):
- Add internal/hooks/hooks_test.go for hook system
- Add cmd/bd/mail_test.go for mail commands
- Add internal/storage/sqlite/graph_links_test.go for graph links

Documentation (bd-kwro.11):
- Add docs/messaging.md for full messaging reference
- Add docs/graph-links.md for graph link types
- Update AGENTS.md with inter-agent messaging section
- Update CHANGELOG.md with all bd-kwro features

🤖 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-16 20:36:47 -08:00
parent 46bfb43b8d
commit 8d73a86f7a
11 changed files with 1874 additions and 5 deletions

View File

@@ -42,6 +42,9 @@ 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
Preview what would be deleted/pruned:
bd cleanup --dry-run
bd cleanup --older-than 90 --dry-run
@@ -64,6 +67,7 @@ SEE ALSO:
cascade, _ := cmd.Flags().GetBool("cascade")
olderThanDays, _ := cmd.Flags().GetInt("older-than")
hardDelete, _ := cmd.Flags().GetBool("hard")
ephemeralOnly, _ := cmd.Flags().GetBool("ephemeral")
// Calculate custom TTL for --hard mode
// When --hard is set, use --older-than days as the tombstone TTL cutoff
@@ -108,6 +112,12 @@ SEE ALSO:
filter.ClosedBefore = &cutoffTime
}
// Add ephemeral filter if specified (bd-kwro.9)
if ephemeralOnly {
ephemeralTrue := true
filter.Ephemeral = &ephemeralTrue
}
// Get all closed issues matching filter
closedIssues, err := store.SearchIssues(ctx, "", filter)
if err != nil {
@@ -124,11 +134,18 @@ SEE ALSO:
if olderThanDays > 0 {
result["filter"] = fmt.Sprintf("older than %d days", olderThanDays)
}
if ephemeralOnly {
result["ephemeral"] = true
}
output, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(output))
} else {
msg := "No closed issues to delete"
if olderThanDays > 0 {
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"
} else if olderThanDays > 0 {
msg = fmt.Sprintf("No closed issues older than %d days to delete", olderThanDays)
}
fmt.Println(msg)
@@ -144,15 +161,23 @@ SEE ALSO:
// Show preview
if !force && !dryRun {
fmt.Fprintf(os.Stderr, "Would delete %d closed issue(s). Use --force to confirm or --dry-run to preview.\n", len(issueIDs))
issueType := "closed"
if ephemeralOnly {
issueType = "closed ephemeral"
}
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)
}
if !jsonOutput {
issueType := "closed"
if ephemeralOnly {
issueType = "closed ephemeral"
}
if olderThanDays > 0 {
fmt.Printf("Found %d closed issue(s) older than %d days\n", len(closedIssues), olderThanDays)
fmt.Printf("Found %d %s issue(s) older than %d days\n", len(closedIssues), issueType, olderThanDays)
} else {
fmt.Printf("Found %d closed issue(s)\n", len(closedIssues))
fmt.Printf("Found %d %s issue(s)\n", len(closedIssues), issueType)
}
if dryRun {
fmt.Println(color.YellowString("DRY RUN - no changes will be made"))
@@ -211,5 +236,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)")
rootCmd.AddCommand(cleanupCmd)
}