feat: Add bd info --whats-new for agent version awareness (bd-eiz9)
- Add --whats-new flag to bd info command - Display agent-relevant changes from last 3 versions - Support both human-readable and JSON output - Add version changelog data structure with workflow-impacting changes - Add comprehensive tests for version changes structure - Update AGENTS.md with whats-new documentation Helps agents efficiently learn what changed between bd versions without re-reading full documentation. Addresses weekly major version releases requiring workflow adaptations. Closes bd-eiz9 Amp-Thread-ID: https://ampcode.com/threads/T-5fe7e93d-7398-41c5-94bf-e914f2b331dd Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
17
AGENTS.md
17
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!
|
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
|
## Human Setup vs Agent Usage
|
||||||
|
|
||||||
**IMPORTANT:** If you need to initialize bd, use the `--quiet` flag:
|
**IMPORTANT:** If you need to initialize bd, use the `--quiet` flag:
|
||||||
|
|||||||
@@ -23,13 +23,23 @@ or daemon connection. It shows:
|
|||||||
- If using daemon: socket path, health status, version
|
- If using daemon: socket path, health status, version
|
||||||
- Database statistics (issue count)
|
- Database statistics (issue count)
|
||||||
- Schema information (with --schema flag)
|
- Schema information (with --schema flag)
|
||||||
|
- What's new in recent versions (with --whats-new flag)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd info
|
bd info
|
||||||
bd info --json
|
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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
schemaFlag, _ := cmd.Flags().GetBool("schema")
|
schemaFlag, _ := cmd.Flags().GetBool("schema")
|
||||||
|
whatsNewFlag, _ := cmd.Flags().GetBool("whats-new")
|
||||||
|
|
||||||
|
// Handle --whats-new flag
|
||||||
|
if whatsNewFlag {
|
||||||
|
showWhatsNew()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get database path (absolute)
|
// Get database path (absolute)
|
||||||
absDBPath, err := filepath.Abs(dbPath)
|
absDBPath, err := filepath.Abs(dbPath)
|
||||||
@@ -239,8 +249,91 @@ func extractPrefix(issueID string) string {
|
|||||||
return ""
|
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() {
|
func init() {
|
||||||
infoCmd.Flags().Bool("schema", false, "Include schema information in output")
|
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")
|
infoCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
|
||||||
rootCmd.AddCommand(infoCmd)
|
rootCmd.AddCommand(infoCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,3 +12,83 @@ func TestInfoCommand(t *testing.T) {
|
|||||||
func TestInfoWithNoDaemon(t *testing.T) {
|
func TestInfoWithNoDaemon(t *testing.T) {
|
||||||
t.Skip("Manual test - bd info --no-daemon command is working, see manual testing")
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user