release: v0.34.0

## Added
- Wisp commands - bd wisp create/list/gc for ephemeral molecule management
- Chemistry UX - bd pour, bd mol bond --wisp/--pour for phase control
- Cross-project deps - external:<repo>:<id> syntax, bd ship command
- Orphan detection in bd doctor

## Changed
- Multi-repo config uses YAML - bd repo add/remove writes to .beads/config.yaml

## Fixed
- Wisp storage auto-copies issue_prefix from main database
- Prefix validation in multi-repo mode
- Remove orphaned repo_test.go
- Update version tests for 0.34.0 thresholds

🤖 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-22 03:18:55 -08:00
parent 32181fd5a2
commit 199def9fed
15 changed files with 93 additions and 122 deletions

View File

@@ -9,7 +9,7 @@
"name": "beads", "name": "beads",
"source": "./", "source": "./",
"description": "AI-supervised issue tracker for coding workflows", "description": "AI-supervised issue tracker for coding workflows",
"version": "0.33.2" "version": "0.34.0"
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "beads", "name": "beads",
"description": "AI-supervised issue tracker for coding workflows. Manage tasks, discover work, and maintain context with simple CLI commands.", "description": "AI-supervised issue tracker for coding workflows. Manage tasks, discover work, and maintain context with simple CLI commands.",
"version": "0.33.2", "version": "0.34.0",
"author": { "author": {
"name": "Steve Yegge", "name": "Steve Yegge",
"url": "https://github.com/steveyegge" "url": "https://github.com/steveyegge"

View File

@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.34.0] - 2025-12-22
### Added
- **Wisp commands** (bd-kwjh) - Full ephemeral molecule management
- `bd wisp create <proto>` - Instantiate proto as ephemeral wisp (solid→vapor)
- `bd wisp list` - List all wisps with stale detection
- `bd wisp gc` - Garbage collect orphaned wisps
- Wisps live in `.beads-wisp/` (gitignored), never sync to remote
- **Chemistry UX commands** - Phase-aware molecule operations
- `bd pour <proto>` - Instantiate proto as persistent mol (solid→liquid)
- `bd mol bond --wisp` - Force spawn as vapor when attaching to mol
- `bd mol bond --pour` - Force spawn as liquid when attaching to wisp
- Cross-store squash: condense wisp to digest in main storage
- **Cross-project dependencies** (bd-66w1, bd-om4a) - Reference issues across repos
- `external:<repo>:<id>` dependency syntax
- `bd ship <id> --to <repo>` - Ship issues to other beads repos
- `bd ready` filters by external dependency satisfaction
- Configure additional repos in `.beads/config.yaml`
- **Orphan detection in bd doctor** (bd-5hrq) - Find issues with missing parents
- Detects parent-child relationships pointing to deleted issues
- Suggests fix commands for orphaned issues
### Changed
- **Multi-repo config uses YAML** (GH#683) - `bd repo add/remove` now writes to `.beads/config.yaml`
- Fixes disconnect where CLI wrote to DB but hydration read from YAML
- `bd repo remove` now cleans up hydrated issues from removed repo
- Breaking: `bd repo add` no longer accepts optional alias argument
### Fixed
- **Wisp storage initialization** - NewWispStorage now copies issue_prefix from main db
- **Prefix validation in multi-repo mode** (GH#686) - Skip validation for external repos
- **Empty config values** (GH#680, GH#684) - Handle gracefully in getRepoConfig()
- **Doctor UX improvements** (GH#687) - Better diagnostics and daemon integration
- **Orphaned test file** - Removed repo_test.go with undefined functions
## [0.33.2] - 2025-12-21 ## [0.33.2] - 2025-12-21
## [0.33.1] - 2025-12-21 ## [0.33.1] - 2025-12-21

View File

@@ -895,7 +895,8 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
{ {
name: "slightly outdated version", name: "slightly outdated version",
setupVersion: func(beadsDir string) error { setupVersion: func(beadsDir string) error {
return os.WriteFile(filepath.Join(beadsDir, ".local_version"), []byte("0.24.0\n"), 0644) // Use a version that's less than 10 minor versions behind current
return os.WriteFile(filepath.Join(beadsDir, ".local_version"), []byte("0.30.0\n"), 0644)
}, },
expectedStatus: doctor.StatusOK, expectedStatus: doctor.StatusOK,
expectWarning: false, expectWarning: false,
@@ -903,7 +904,8 @@ func TestCheckMetadataVersionTracking(t *testing.T) {
{ {
name: "very old version", name: "very old version",
setupVersion: func(beadsDir string) error { setupVersion: func(beadsDir string) error {
return os.WriteFile(filepath.Join(beadsDir, ".local_version"), []byte("0.14.0\n"), 0644) // Use a version that's 10+ minor versions behind current (triggers warning)
return os.WriteFile(filepath.Join(beadsDir, ".local_version"), []byte("0.24.0\n"), 0644)
}, },
expectedStatus: doctor.StatusWarning, expectedStatus: doctor.StatusWarning,
expectWarning: true, expectWarning: true,

View File

@@ -288,6 +288,17 @@ type VersionChange struct {
// versionChanges contains agent-actionable changes for recent versions // versionChanges contains agent-actionable changes for recent versions
var versionChanges = []VersionChange{ var versionChanges = []VersionChange{
{
Version: "0.34.0",
Date: "2025-12-22",
Changes: []string{
"NEW: Wisp commands - bd wisp create/list/gc for ephemeral molecule management",
"NEW: Chemistry UX - bd pour, bd mol bond --wisp/--pour for phase control",
"NEW: Cross-project deps - external:<repo>:<id> syntax, bd ship command",
"BREAKING: bd repo add/remove now writes to .beads/config.yaml (not DB)",
"FIX: Wisp storage auto-copies issue_prefix from main database",
},
},
{ {
Version: "0.33.2", Version: "0.33.2",
Date: "2025-12-21", Date: "2025-12-21",

View File

@@ -1,109 +0,0 @@
package main
import (
"context"
"testing"
)
func TestGetRepoConfig_EmptyValue(t *testing.T) {
ctx := context.Background()
store, cleanup := setupTestDB(t)
defer cleanup()
// Test 1: No config set at all - should return empty map
repos, err := getRepoConfig(ctx, store)
if err != nil {
t.Fatalf("getRepoConfig with no config failed: %v", err)
}
if len(repos) != 0 {
t.Errorf("Expected empty map, got %d entries", len(repos))
}
// Test 2: Empty string value - should return empty map (this was the bug)
// This simulates GetConfig returning ("", nil) which caused "unexpected end of JSON input"
err = store.SetConfig(ctx, "repos.additional", "")
if err != nil {
t.Fatalf("SetConfig failed: %v", err)
}
repos, err = getRepoConfig(ctx, store)
if err != nil {
t.Fatalf("getRepoConfig with empty value failed: %v", err)
}
if len(repos) != 0 {
t.Errorf("Expected empty map for empty value, got %d entries", len(repos))
}
// Test 3: Valid JSON value - should parse correctly
err = store.SetConfig(ctx, "repos.additional", `{"alias1":"/path/to/repo1","alias2":"/path/to/repo2"}`)
if err != nil {
t.Fatalf("SetConfig with JSON failed: %v", err)
}
repos, err = getRepoConfig(ctx, store)
if err != nil {
t.Fatalf("getRepoConfig with valid JSON failed: %v", err)
}
if len(repos) != 2 {
t.Errorf("Expected 2 repos, got %d", len(repos))
}
if repos["alias1"] != "/path/to/repo1" {
t.Errorf("Expected '/path/to/repo1', got '%s'", repos["alias1"])
}
if repos["alias2"] != "/path/to/repo2" {
t.Errorf("Expected '/path/to/repo2', got '%s'", repos["alias2"])
}
}
func TestSetRepoConfig(t *testing.T) {
ctx := context.Background()
store, cleanup := setupTestDB(t)
defer cleanup()
// Set repos and verify round-trip
repos := map[string]string{
"planning": "/home/user/planning-repo",
"shared": "/home/user/shared-repo",
}
err := setRepoConfig(ctx, store, repos)
if err != nil {
t.Fatalf("setRepoConfig failed: %v", err)
}
// Read back
result, err := getRepoConfig(ctx, store)
if err != nil {
t.Fatalf("getRepoConfig after set failed: %v", err)
}
if len(result) != 2 {
t.Errorf("Expected 2 repos, got %d", len(result))
}
if result["planning"] != "/home/user/planning-repo" {
t.Errorf("Expected planning repo path, got '%s'", result["planning"])
}
if result["shared"] != "/home/user/shared-repo" {
t.Errorf("Expected shared repo path, got '%s'", result["shared"])
}
}
func TestRepoConfigEmptyMap(t *testing.T) {
ctx := context.Background()
store, cleanup := setupTestDB(t)
defer cleanup()
// Set empty map
repos := make(map[string]string)
err := setRepoConfig(ctx, store, repos)
if err != nil {
t.Fatalf("setRepoConfig with empty map failed: %v", err)
}
// Read back - should work and return empty map
result, err := getRepoConfig(ctx, store)
if err != nil {
t.Fatalf("getRepoConfig after empty set failed: %v", err)
}
if len(result) != 0 {
t.Errorf("Expected empty map, got %d entries", len(result))
}
}

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# bd-shim v1 # bd-shim v1
# bd-hooks-version: 0.33.2 # bd-hooks-version: 0.34.0
# #
# bd (beads) post-checkout hook - thin shim # bd (beads) post-checkout hook - thin shim
# #

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# bd-shim v1 # bd-shim v1
# bd-hooks-version: 0.33.2 # bd-hooks-version: 0.34.0
# #
# bd (beads) post-merge hook - thin shim # bd (beads) post-merge hook - thin shim
# #

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# bd-shim v1 # bd-shim v1
# bd-hooks-version: 0.33.2 # bd-hooks-version: 0.34.0
# #
# bd (beads) pre-commit hook - thin shim # bd (beads) pre-commit hook - thin shim
# #

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# bd-shim v1 # bd-shim v1
# bd-hooks-version: 0.33.2 # bd-hooks-version: 0.34.0
# #
# bd (beads) pre-push hook - thin shim # bd (beads) pre-push hook - thin shim
# #

View File

@@ -14,7 +14,7 @@ import (
var ( var (
// Version is the current version of bd (overridden by ldflags at build time) // Version is the current version of bd (overridden by ldflags at build time)
Version = "0.33.2" Version = "0.34.0"
// Build can be set via ldflags at compile time // Build can be set via ldflags at compile time
Build = "dev" Build = "dev"
// Commit and branch the git revision the binary was built from (optional ldflag) // Commit and branch the git revision the binary was built from (optional ldflag)

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "beads-mcp" name = "beads-mcp"
version = "0.33.2" version = "0.34.0"
description = "MCP server for beads issue tracker." description = "MCP server for beads issue tracker."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@@ -4,4 +4,4 @@ This package provides an MCP (Model Context Protocol) server that exposes
beads (bd) issue tracker functionality to MCP Clients. beads (bd) issue tracker functionality to MCP Clients.
""" """
__version__ = "0.33.2" __version__ = "0.34.0"

View File

@@ -664,13 +664,39 @@ func FindWispDatabasePath() (string, error) {
// NewWispStorage opens the wisp database for ephemeral molecule storage. // NewWispStorage opens the wisp database for ephemeral molecule storage.
// Creates the database and directory if they don't exist. // Creates the database and directory if they don't exist.
// The wisp database uses the same schema as the main database. // The wisp database uses the same schema as the main database.
// Automatically copies issue_prefix from the main beads config if not set.
func NewWispStorage(ctx context.Context) (Storage, error) { func NewWispStorage(ctx context.Context) (Storage, error) {
dbPath, err := FindWispDatabasePath() dbPath, err := FindWispDatabasePath()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return sqlite.New(ctx, dbPath) wispStore, err := sqlite.New(ctx, dbPath)
if err != nil {
return nil, err
}
// Check if wisp db has issue_prefix configured
prefix, err := wispStore.GetConfig(ctx, "issue_prefix")
if err != nil || prefix == "" {
// Copy issue_prefix from main beads database
mainDBPath := FindDatabasePath()
if mainDBPath != "" {
mainStore, mainErr := sqlite.New(ctx, mainDBPath)
if mainErr == nil {
defer mainStore.Close()
mainPrefix, _ := mainStore.GetConfig(ctx, "issue_prefix")
if mainPrefix != "" {
if setErr := wispStore.SetConfig(ctx, "issue_prefix", mainPrefix); setErr != nil {
wispStore.Close()
return nil, fmt.Errorf("setting wisp issue_prefix: %w", setErr)
}
}
}
}
}
return wispStore, nil
} }
// EnsureWispGitignore ensures the wisp directory is gitignored. // EnsureWispGitignore ensures the wisp directory is gitignored.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@beads/bd", "name": "@beads/bd",
"version": "0.33.2", "version": "0.34.0",
"description": "Beads issue tracker - lightweight memory system for coding agents with native binary support", "description": "Beads issue tracker - lightweight memory system for coding agents with native binary support",
"main": "bin/bd.js", "main": "bin/bd.js",
"bin": { "bin": {