Merge bd-er7r-derrick: GH#444 status naming

This commit is contained in:
Steve Yegge
2025-12-16 01:15:36 -08:00
9 changed files with 29089 additions and 149 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -104
View File
@@ -7,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.30.0] - 2025-12-15
## [0.30.0] - 2025-12-15
**Tombstone Architecture** - This release completes the migration to inline tombstones
for soft-delete, replacing the legacy `deletions.jsonl` manifest. This is intended to
be the last significant architectural change to Beads' storage format.
### Added
- **Inline tombstones for soft-delete (bd-vw8)**
@@ -29,12 +21,7 @@ be the last significant architectural change to Beads' storage format.
- Archives old file as `deletions.jsonl.migrated`
- Use `--dry-run` to preview changes
- **`bd doctor` tombstone health checks (bd-s3v)**
- Detects orphaned tombstones (missing from database)
- Identifies expired tombstones ready for pruning
- Warns about tombstone/live issue conflicts
- **Enhanced Git Worktree Support (bd-737)** - Comprehensive compatibility improvements using shared database architecture
- **Enhanced Git Worktree Support** (bd-737): Comprehensive compatibility improvements for git worktrees using shared database architecture
- Shared `.beads` database across all worktrees in a repository
- Worktree-aware database discovery prioritizes main repository
- Git hooks automatically adapt to worktree context
@@ -43,98 +30,13 @@ be the last significant architectural change to Beads' storage format.
- Worktree lifecycle management with sparse checkout for sync branches
- Automatic detection and user-friendly warnings for worktree conflicts
- **MCP Context Engineering Optimizations (GH #481)** - 80-90% context reduction
- Smarter tool response formatting reduces token usage
- Compaction configuration options for MCP server
- Extended context engineering documentation
- **`bd thanks` command (GH #555)** - Thank contributors to your project
- Lists contributors with their contribution counts
- Useful for acknowledgments and community building
- **`BD_NO_INSTALL_HOOKS` environment variable (GH #500)**
- Set to disable automatic git hook installation
- Useful for CI environments or custom hook setups
- **Claude Code skill marketplace installation (GH #468)**
- Beads skill now installable via Claude Code marketplace
- Slash commands updated to use `beads:` prefix (GH #467)
- **Smithery integration (GH #501)** - "Run in Smithery" badge for easy MCP server deployment
- **`bd version` shows commit and branch (GH #504)** - Better version diagnostics
### Fixed
- **Daemon delete auto-sync (GH #528, #537)**
- Delete operations now properly trigger auto-sync to sync branch
- RPC support for delete mutations
- Fixed sync branch commits failing with pre-commit hooks (GH #521)
- **`close_reason` persistence (GH #551)** - Close reasons now properly saved to database on close
- **`bd migrate-tombstones` corruption fix (GH #554)** - Prevents corrupting deletions manifest during migration
- **JSONL-only mode improvements (GH #549)**
- Proper `GetReadyWork`/`GetBlockedIssues` implementation for memory storage
- Fixed child counters in no-database mode
- Better detection and error messages (GH #534)
- **Tombstone sync fixes**
- Preserve tombstones in `sanitizeJSONLWithDeletions` (bd-kzxd)
- Include tombstones when building ID map during import
- Clear `closed_at` when converting closed issue to tombstone
- Include tombstones in `getCurrentJSONLIDs` to prevent doctor corruption (GH #552)
- **Sync safety improvements**
- Protect locally exported issues from sanitization (bd-3ee1)
- Add safety guards for issue deletion in blocked command
- Retry with rebase for concurrent push conflicts
- Protect local issues from git-history-backfill during sync (GH #485)
- **Windows fixes**
- `bd onboard` no longer hangs on Windows (GH #531)
- Restored Windows smoke tests (reverted by #478)
- **External `BEADS_DIR` support** - Sync now works with beads data in separate git repository
- **Go version inconsistencies and broken documentation links (GH #535)**
- **Stealth mode path fix (GH #538)** - Use project-specific paths in global gitignore
- **Daemon sync-branch hook incompatibility detection (GH #532)**
### Performance
- **Lock file improvements (GH #484, #555)**
- Fixed stale startlock delay
- Fast fail on stale lock from crashed process
- Comprehensive benchmarks added
- Test coverage improved from 42% to 98%
### Documentation
- Agent memory patterns using comments (GH #487) - @bryceroche
- Added beads_viewer TUI to third-party tools (GH #515) - @aspiers
- Go install fallback instructions and uninstall documentation
### Contributors
Thanks to our community contributors for this release:
- @aspiers - Documentation for beads_viewer
- @AodhanHayter - Nix version fix (GH #502)
- @broady - close_reason persistence (GH #551)
- @bryceroche - Agent memory documentation (GH #487)
- @cerebustech-dev - Sync protection fix (GH #485)
- @cpdata - Daemon sync fixes (GH #521, #528, #537)
- @gurdasnijor - Smithery badge (GH #501)
- @loganthomas - Documentation fixes (GH #535)
- @mahawi1992 - MCP context optimizations (GH #481)
- @maphew - Version output and migration fix (GH #504, #554)
- @rsnodgrass - `bd thanks` and benchmarks (GH #484, #555)
- @schpet - Claude Code skill marketplace (GH #467, #468)
- @withzombies - `BD_NO_INSTALL_HOOKS` env var (GH #500)
- **`bd` now finds `.beads` from nested worktrees** (GH#509)
- When worktrees are nested under the main repo (e.g., `/project/.worktrees/feature/`),
`bd` now correctly finds `.beads/` in the parent repo
- Uses `git rev-parse --git-common-dir` to reliably locate the main repository root
- Works from any subdirectory within the nested worktree
## [0.29.0] - 2025-12-03
+1 -1
View File
@@ -262,7 +262,7 @@ This pattern has proven invaluable for maintaining database hygiene and preventi
**1. File/update issues for remaining work**
- Agents should proactively create issues for discovered bugs, TODOs, and follow-up tasks
- Close completed issues and update status for in-progress work
- Close completed issues and update status for in_progress work
**2. Run quality gates (if applicable)**
- Tests, linters, builds - only if code changes were made
+1 -1
View File
@@ -12,7 +12,7 @@ import (
)
var readyCmd = &cobra.Command{
Use: "ready",
Short: "Show ready work (no blockers, open or in-progress)",
Short: "Show ready work (no blockers, open or in_progress)",
Run: func(cmd *cobra.Command, args []string) {
limit, _ := cmd.Flags().GetInt("limit")
assignee, _ := cmd.Flags().GetString("assignee")
+1 -1
View File
@@ -45,7 +45,7 @@ var statusCmd = &cobra.Command{
Short: "Show issue database overview",
Long: `Show a quick snapshot of the issue database state.
This command provides a summary of issue counts by state (open, in-progress,
This command provides a summary of issue counts by state (open, in_progress,
blocked, closed), ready work, and recent activity over the last 24 hours from git history.
Similar to how 'git status' shows working tree state, 'bd status' gives you
+1 -1
View File
@@ -13,5 +13,5 @@ Use the beads MCP `stats` tool to retrieve project metrics and present them clea
Optionally suggest actions based on the stats:
- High number of blocked issues? Run `/bd-blocked` to investigate
- No in-progress work? Run `/bd-ready` to find tasks
- No in_progress work? Run `/bd-ready` to find tasks
- Many open issues? Consider prioritizing with `/bd-update`
+22 -41
View File
@@ -2,7 +2,6 @@ package git
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
@@ -78,48 +77,30 @@ func IsWorktree() bool {
// GetMainRepoRoot returns the main repository root directory.
// When in a worktree, this returns the main repository root.
// Otherwise, it returns the regular repository root.
//
// For nested worktrees (worktrees located under the main repo, e.g.,
// /project/.worktrees/feature/), this correctly returns the main repo
// root (/project/) by using git rev-parse --git-common-dir which always
// points to the main repo's .git directory. (GH#509)
func GetMainRepoRoot() (string, error) {
if IsWorktree() {
// In worktree: read .git file to find main repo
gitFileContent := getGitDirNoError("--git-dir")
if gitFileContent == "" {
return "", fmt.Errorf("not a git repository")
}
// If gitFileContent contains "worktrees", it's a worktree path
// Read the .git file to get the main git dir
if strings.Contains(gitFileContent, "worktrees") {
content, err := exec.Command("cat", ".git").Output()
if err == nil {
line := strings.TrimSpace(string(content))
if strings.HasPrefix(line, "gitdir: ") {
gitDir := strings.TrimPrefix(line, "gitdir: ")
// Remove /worktrees/* part
if idx := strings.Index(gitDir, "/worktrees/"); idx > 0 {
gitDir = gitDir[:idx]
}
return filepath.Dir(gitDir), nil
}
}
}
// Fallback: use --git-common-dir with validation
commonDir := getGitDirNoError("--git-common-dir")
if commonDir != "" {
// Validate that commonDir exists
if info, err := os.Stat(commonDir); err == nil && info.IsDir() {
return filepath.Dir(commonDir), nil
}
}
return "", fmt.Errorf("unable to determine main repository root")
} else {
gitDir, err := GetGitDir()
if err != nil {
return "", err
}
return filepath.Dir(gitDir), nil
// Use --git-common-dir which always returns the main repo's .git directory,
// even when running from within a worktree or its subdirectories.
// This is the most reliable method for finding the main repo root.
commonDir := getGitDirNoError("--git-common-dir")
if commonDir == "" {
return "", fmt.Errorf("not a git repository")
}
// Convert to absolute path to handle relative paths correctly
absCommonDir, err := filepath.Abs(commonDir)
if err != nil {
return "", fmt.Errorf("failed to resolve common dir path: %w", err)
}
// The main repo root is the parent of the .git directory
mainRepoRoot := filepath.Dir(absCommonDir)
return mainRepoRoot, nil
}
// getGitDirNoError is a helper that returns empty string on error
+196
View File
@@ -860,3 +860,199 @@ func TestCountJSONLIssues(t *testing.T) {
})
}
}
// TestGetMainRepoRoot tests the GetMainRepoRoot function for various scenarios
func TestGetMainRepoRoot(t *testing.T) {
t.Run("returns correct root for regular repo", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
// Save current dir and change to repo
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(repoPath); err != nil {
t.Fatalf("Failed to chdir to repo: %v", err)
}
root, err := GetMainRepoRoot()
if err != nil {
t.Fatalf("GetMainRepoRoot failed: %v", err)
}
// Resolve symlinks for comparison (e.g., /tmp -> /private/tmp on macOS)
expectedRoot, _ := filepath.EvalSymlinks(repoPath)
actualRoot, _ := filepath.EvalSymlinks(root)
if actualRoot != expectedRoot {
t.Errorf("GetMainRepoRoot() = %s, want %s", actualRoot, expectedRoot)
}
})
t.Run("returns main repo root from worktree", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
wm := NewWorktreeManager(repoPath)
worktreePath := filepath.Join(t.TempDir(), "test-worktree")
if err := wm.CreateBeadsWorktree("test-branch", worktreePath); err != nil {
t.Fatalf("CreateBeadsWorktree failed: %v", err)
}
// Save current dir and change to worktree
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(worktreePath); err != nil {
t.Fatalf("Failed to chdir to worktree: %v", err)
}
root, err := GetMainRepoRoot()
if err != nil {
t.Fatalf("GetMainRepoRoot failed: %v", err)
}
// Resolve symlinks for comparison
expectedRoot, _ := filepath.EvalSymlinks(repoPath)
actualRoot, _ := filepath.EvalSymlinks(root)
if actualRoot != expectedRoot {
t.Errorf("GetMainRepoRoot() = %s, want %s (main repo)", actualRoot, expectedRoot)
}
})
t.Run("returns main repo root from nested worktree (GH#509)", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
// Create a nested worktree directory structure: repo/.worktrees/feature/
nestedWorktreePath := filepath.Join(repoPath, ".worktrees", "feature-branch")
wm := NewWorktreeManager(repoPath)
if err := wm.CreateBeadsWorktree("feature-branch", nestedWorktreePath); err != nil {
t.Fatalf("CreateBeadsWorktree failed: %v", err)
}
// Save current dir and change to nested worktree
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(nestedWorktreePath); err != nil {
t.Fatalf("Failed to chdir to nested worktree: %v", err)
}
root, err := GetMainRepoRoot()
if err != nil {
t.Fatalf("GetMainRepoRoot failed: %v", err)
}
// Resolve symlinks for comparison
expectedRoot, _ := filepath.EvalSymlinks(repoPath)
actualRoot, _ := filepath.EvalSymlinks(root)
if actualRoot != expectedRoot {
t.Errorf("GetMainRepoRoot() = %s, want %s (main repo, not worktree)", actualRoot, expectedRoot)
}
})
t.Run("returns main repo root from subdirectory of nested worktree", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
// Create a nested worktree
nestedWorktreePath := filepath.Join(repoPath, ".worktrees", "feature-branch")
wm := NewWorktreeManager(repoPath)
if err := wm.CreateBeadsWorktree("feature-branch", nestedWorktreePath); err != nil {
t.Fatalf("CreateBeadsWorktree failed: %v", err)
}
// Create a subdirectory in the worktree
subDir := filepath.Join(nestedWorktreePath, "some", "nested", "dir")
if err := os.MkdirAll(subDir, 0750); err != nil {
t.Fatalf("Failed to create subdir: %v", err)
}
// Save current dir and change to subdirectory
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(subDir); err != nil {
t.Fatalf("Failed to chdir to subdir: %v", err)
}
root, err := GetMainRepoRoot()
if err != nil {
t.Fatalf("GetMainRepoRoot failed: %v", err)
}
// Resolve symlinks for comparison
expectedRoot, _ := filepath.EvalSymlinks(repoPath)
actualRoot, _ := filepath.EvalSymlinks(root)
if actualRoot != expectedRoot {
t.Errorf("GetMainRepoRoot() = %s, want %s (main repo)", actualRoot, expectedRoot)
}
})
}
// TestIsWorktree tests the IsWorktree function
func TestIsWorktree(t *testing.T) {
t.Run("returns false for regular repo", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(repoPath); err != nil {
t.Fatalf("Failed to chdir to repo: %v", err)
}
if IsWorktree() {
t.Error("IsWorktree() should return false for regular repo")
}
})
t.Run("returns true for worktree", func(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
wm := NewWorktreeManager(repoPath)
worktreePath := filepath.Join(t.TempDir(), "test-worktree")
if err := wm.CreateBeadsWorktree("test-branch", worktreePath); err != nil {
t.Fatalf("CreateBeadsWorktree failed: %v", err)
}
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current dir: %v", err)
}
defer func() { _ = os.Chdir(originalDir) }()
if err := os.Chdir(worktreePath); err != nil {
t.Fatalf("Failed to chdir to worktree: %v", err)
}
if !IsWorktree() {
t.Error("IsWorktree() should return true for worktree")
}
})
}