Merge bd-er7r-derrick: GH#444 status naming
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+6
-104
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user