diff --git a/AGENTS.md b/AGENTS.md index 9bcdd2fb..c6dfa650 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,23 @@ This is **beads** (command: `bd`), an issue tracker designed for AI-supervised coding workflows. We dogfood our own tool! +## šŸ†• What's New? + +**New to bd or upgrading?** Run `bd info --whats-new` to see agent-relevant changes from recent versions: + +```bash +bd info --whats-new # Human-readable output +bd info --whats-new --json # Machine-readable output +``` + +This shows the last 3 versions with workflow-impacting changes, avoiding the need to re-read all documentation. Examples: +- New commands and flags that improve agent workflows +- Breaking changes that require workflow updates +- Performance improvements and bug fixes +- Integration features (MCP, Agent Mail, git hooks) + +**Why this matters:** bd releases weekly with major versions. This command helps you quickly understand what changed without parsing the full CHANGELOG. + ## Human Setup vs Agent Usage **IMPORTANT:** If you need to initialize bd, use the `--quiet` flag: diff --git a/cmd/bd/info.go b/cmd/bd/info.go index c6fa4408..b239c553 100644 --- a/cmd/bd/info.go +++ b/cmd/bd/info.go @@ -23,13 +23,23 @@ or daemon connection. It shows: - If using daemon: socket path, health status, version - Database statistics (issue count) - Schema information (with --schema flag) + - What's new in recent versions (with --whats-new flag) Examples: bd info bd info --json - bd info --schema --json`, + bd info --schema --json + bd info --whats-new + bd info --whats-new --json`, Run: func(cmd *cobra.Command, args []string) { schemaFlag, _ := cmd.Flags().GetBool("schema") + whatsNewFlag, _ := cmd.Flags().GetBool("whats-new") + + // Handle --whats-new flag + if whatsNewFlag { + showWhatsNew() + return + } // Get database path (absolute) absDBPath, err := filepath.Abs(dbPath) @@ -239,8 +249,91 @@ func extractPrefix(issueID string) string { return "" } +// VersionChange represents agent-relevant changes for a specific version +type VersionChange struct { + Version string `json:"version"` + Date string `json:"date"` + Changes []string `json:"changes"` +} + +// versionChanges contains agent-actionable changes for recent versions +var versionChanges = []VersionChange{ + { + Version: "0.22.1", + Date: "2025-11-06", + Changes: []string{ + "Native `bd merge` command vendored from beads-merge - no external binary needed", + "`bd info` detects outdated git hooks - warns if version mismatch", + "Multi-workspace deletion tracking fixed - deletions now propagate correctly", + "Hash ID recognition improved - recognizes Base36 IDs without a-f letters", + "Import/export deadlock fixed - no hanging when daemon running", + }, + }, + { + Version: "0.22.0", + Date: "2025-11-05", + Changes: []string{ + "Intelligent merge driver auto-configured - eliminates most JSONL conflicts", + "Onboarding wizards: `bd init --contributor` and `bd init --team`", + "New `bd migrate-issues` command - migrate issues between repos with dependencies", + "`bd show` displays blocker status - 'Blocked by N open issues' or 'Ready to work'", + "SearchIssues N+1 query fixed - batch-loads labels for better performance", + "Sync validation prevents infinite dirty loop - verifies JSONL export", + }, + }, + { + Version: "0.21.0", + Date: "2025-11-04", + Changes: []string{ + "Hash-based IDs eliminate collisions - remove ID coordination workarounds", + "Event-driven daemon mode (opt-in) - set BEADS_DAEMON_MODE=events", + "Agent Mail integration - real-time multi-agent coordination (<100ms latency)", + "`bd duplicates --auto-merge` - automated duplicate detection and merging", + "Hierarchical children for epics - dotted IDs (bd-abc.1, bd-abc.2) up to 3 levels", + "`--discovered-from` inline syntax - create with dependency in one command", + }, + }, +} + +// showWhatsNew displays agent-relevant changes from recent versions +func showWhatsNew() { + currentVersion := Version // from version.go + + if jsonOutput { + outputJSON(map[string]interface{}{ + "current_version": currentVersion, + "recent_changes": versionChanges, + }) + return + } + + // Human-readable output + fmt.Printf("\nšŸ†• What's New in bd (Current: v%s)\n", currentVersion) + fmt.Println("=" + strings.Repeat("=", 60)) + fmt.Println() + + for _, vc := range versionChanges { + // Highlight if this is the current version + versionMarker := "" + if vc.Version == currentVersion { + versionMarker = " ← current" + } + + fmt.Printf("## v%s (%s)%s\n\n", vc.Version, vc.Date, versionMarker) + + for _, change := range vc.Changes { + fmt.Printf(" • %s\n", change) + } + fmt.Println() + } + + fmt.Println("šŸ’” Tip: Use `bd info --whats-new --json` for machine-readable output") + fmt.Println() +} + func init() { infoCmd.Flags().Bool("schema", false, "Include schema information in output") + infoCmd.Flags().Bool("whats-new", false, "Show agent-relevant changes from recent versions") infoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format") rootCmd.AddCommand(infoCmd) } diff --git a/cmd/bd/info_test.go b/cmd/bd/info_test.go index e8b7a44a..d7c9a8e7 100644 --- a/cmd/bd/info_test.go +++ b/cmd/bd/info_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "testing" ) @@ -11,3 +12,83 @@ func TestInfoCommand(t *testing.T) { func TestInfoWithNoDaemon(t *testing.T) { t.Skip("Manual test - bd info --no-daemon command is working, see manual testing") } + +func TestVersionChangesStructure(t *testing.T) { + // Verify versionChanges is properly structured + if len(versionChanges) == 0 { + t.Fatal("versionChanges should not be empty") + } + + for i, vc := range versionChanges { + if vc.Version == "" { + t.Errorf("versionChanges[%d] has empty Version", i) + } + if vc.Date == "" { + t.Errorf("versionChanges[%d] has empty Date", i) + } + if len(vc.Changes) == 0 { + t.Errorf("versionChanges[%d] has no changes", i) + } + + // Verify version format (should be like "0.22.1") + if len(vc.Version) < 5 { + t.Errorf("versionChanges[%d] has invalid Version format: %s", i, vc.Version) + } + + // Verify date format (should be like "2025-11-06") + if len(vc.Date) != 10 { + t.Errorf("versionChanges[%d] has invalid Date format: %s", i, vc.Date) + } + + // Verify each change is non-empty + for j, change := range vc.Changes { + if change == "" { + t.Errorf("versionChanges[%d].Changes[%d] is empty", i, j) + } + } + } +} + +func TestVersionChangesJSON(t *testing.T) { + // Test that versionChanges can be marshaled to JSON + data, err := json.Marshal(versionChanges) + if err != nil { + t.Fatalf("Failed to marshal versionChanges to JSON: %v", err) + } + + // Test that it can be unmarshaled back + var unmarshaled []VersionChange + err = json.Unmarshal(data, &unmarshaled) + if err != nil { + t.Fatalf("Failed to unmarshal versionChanges from JSON: %v", err) + } + + // Verify structure is preserved + if len(unmarshaled) != len(versionChanges) { + t.Errorf("Unmarshaled length %d != original length %d", len(unmarshaled), len(versionChanges)) + } + + // Spot check first entry + if len(unmarshaled) > 0 && len(versionChanges) > 0 { + if unmarshaled[0].Version != versionChanges[0].Version { + t.Errorf("Version mismatch: %s != %s", unmarshaled[0].Version, versionChanges[0].Version) + } + if len(unmarshaled[0].Changes) != len(versionChanges[0].Changes) { + t.Errorf("Changes count mismatch: %d != %d", len(unmarshaled[0].Changes), len(versionChanges[0].Changes)) + } + } +} + +func TestVersionChangesCoverage(t *testing.T) { + // Ensure we have at least 3 recent versions documented + if len(versionChanges) < 3 { + t.Errorf("Should document at least 3 recent versions, found %d", len(versionChanges)) + } + + // Ensure each version has meaningful changes (at least 3 bullet points) + for i, vc := range versionChanges { + if len(vc.Changes) < 3 { + t.Errorf("versionChanges[%d] (v%s) should have at least 3 changes, found %d", i, vc.Version, len(vc.Changes)) + } + } +}