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:
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user