diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 1199cdca..5e90716e 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -67,7 +67,7 @@ {"id":"bd-7","title":"Write tests for merge functionality","description":"Unit tests: validation, merge logic, data integrity. Integration tests: end-to-end workflow, export/import. Edge case tests: chains, circular refs, epics.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-21T23:53:44.31362-07:00","updated_at":"2025-10-23T19:33:21.092824-07:00","closed_at":"2025-10-22T01:07:04.72062-07:00"} {"id":"bd-70","title":"Fix bd sync prefix mismatch error message suggesting non-existent flag","description":"GH #103: bd sync suggests using --rename-on-import flag that doesn't exist. Need to either implement the flag or fix the error message to suggest the correct workflow.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-22T17:54:24.473508-07:00","updated_at":"2025-10-23T19:33:21.093026-07:00","closed_at":"2025-10-22T17:57:46.973029-07:00"} {"id":"bd-71","title":"Fix MCP close tool method signature error","description":"GH #107: MCP close() tool fails with \"BdDaemonClient.close() takes 1 positional argument but 2 were given\". Need to fix method signature in beads-mcp server.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-22T19:17:05.429429-07:00","updated_at":"2025-10-23T19:33:21.093254-07:00","closed_at":"2025-10-22T19:19:54.601153-07:00"} -{"id":"bd-72","title":"Update Claude Code marketplace plugin","description":"Update the beads plugin in the Claude Code marketplace to the latest version. This may help resolve some of the open GitHub issues related to marketplace installation and compatibility (#54, #112).\n\nShould include:\n- Latest beads version\n- Updated documentation\n- Any new features or bug fixes","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-22T22:29:11.293161-07:00","updated_at":"2025-10-23T19:33:21.093463-07:00"} +{"id":"bd-72","title":"Update Claude Code marketplace plugin","description":"Update the beads plugin in the Claude Code marketplace to the latest version. This may help resolve some of the open GitHub issues related to marketplace installation and compatibility (#54, #112).\n\nShould include:\n- Latest beads version\n- Updated documentation\n- Any new features or bug fixes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T22:29:11.293161-07:00","updated_at":"2025-10-23T22:27:37.671065-07:00","closed_at":"2025-10-23T22:27:37.671065-07:00"} {"id":"bd-73","title":"Git worktree support - ensure --no-daemon works correctly","description":"Users working with git worktrees report that beads MCP commits to the wrong branch (main instead of worktree branch). \n\n**Root cause:** Git worktrees share the same .beads directory, so the daemon/MCP server doesn't know which branch the worktree has checked out.\n\n**Current state:**\n- Daemon mode: Cannot work properly with worktrees (fundamental limitation - shared .beads, unknown branch)\n- CLI with --no-daemon: Should work but needs verification\n\n**Action items:**\n1. Test and verify --no-daemon works correctly in worktrees\n2. Document the limitation in README/AGENTS.md\n3. Add clear error/warning when using daemon with worktrees\n4. Consider if MCP server can detect worktree usage and auto-disable daemon\n\n**Workaround for users:**\nUse --no-daemon flag when working in worktrees, or set BEADS_AUTO_DAEMON=false\n\n**Related:** GH issue #55","notes":"**Implementation completed with oracle review improvements:**\n\n1. **Robust detection** - Changed from substring check to canonical git-dir vs git-common-dir comparison\n2. **Correct warning gate** - Now only checks isGitWorktree() and only called when daemon is actually connected\n3. **Complete coverage** - Added warning to `bd daemon` command start path\n4. **Better UX** - Shows shared database path and clarifies BEADS_AUTO_START_DAEMON behavior\n5. **Improved tests** - Validates canonical detection method and path truncation\n\n**Files changed:**\n- cmd/bd/worktree.go - Improved detection and warning\n- cmd/bd/worktree_test.go - Better test coverage\n- cmd/bd/main.go - Warning after daemon connection (2 places)\n- cmd/bd/daemon.go - Warning when starting daemon\n- README.md \u0026 AGENTS.md - Documented limitations and solutions","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-10-22T22:38:52.438601-07:00","updated_at":"2025-10-23T19:33:21.093665-07:00","closed_at":"2025-10-22T22:51:45.464598-07:00"} {"id":"bd-74","title":"Investigate jujutsu integration for beads","description":"Research and document how beads could integrate with jujutsu (jj), the next-generation VCS. Key areas to explore:\n- How jj's operation model differs from git (immutable operations, working-copy-as-commit)\n- JSONL sync strategy with jj's conflict resolution model\n- Daemon compatibility with jj's more frequent rewrites\n- Whether auto-import/export needs changes for jj workflows\n- Example configurations and documentation updates needed","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-23T09:23:23.582009-07:00","updated_at":"2025-10-23T19:33:21.093919-07:00"} {"id":"bd-75","title":"Add description parameter to bd update command and MCP server","description":"Issue #114 and #122 show that agents expect to update issue descriptions. Currently only create supports description. Need to add --description flag to CLI update command and description parameter to MCP update_issue tool.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-10-23T10:46:00.481647-07:00","updated_at":"2025-10-23T19:33:21.094121-07:00","closed_at":"2025-10-23T10:52:49.825354-07:00"} @@ -84,4 +84,5 @@ {"id":"bd-85","title":"Add GoReleaser workflow for cross-platform binary releases","description":"GitHub issue #89 requests pre-compiled binaries for vendoring.\n\nCurrently users must have Go installed to use beads via 'go install'. Publishing release binaries would:\n- Enable vendoring (user's use case) \n- Support users without Go\n- Enable version pinning\n- Simplify CI/CD integration\n\nImplementation:\n1. Add .goreleaser.yml config\n2. Add .github/workflows/release.yml for tag pushes\n3. Build matrix: darwin (amd64/arm64), linux (amd64/arm64), windows (amd64)\n4. Generate checksums\n5. Create GitHub releases automatically\n6. Update install.sh to download from releases\n\nReference: https://github.com/steveyegge/beads/issues/89","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-23T18:58:30.701929-07:00","updated_at":"2025-10-23T19:33:21.099397-07:00","closed_at":"2025-10-23T19:02:16.463059-07:00"} {"id":"bd-86","title":"Add transaction support for atomicity in merge operations","description":"The merge operation in cmd/bd/merge.go should use transactions to ensure atomicity. Currently marked as TODO at line 143.\n\nThis would prevent partial merges if an error occurs partway through the operation.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-23T19:33:34.549858-07:00","updated_at":"2025-10-23T19:35:40.620329-07:00","closed_at":"2025-10-23T19:35:40.620329-07:00"} {"id":"bd-87","title":"Add RPC support for epic commands in daemon mode","description":"Epic status and close-eligible commands currently error out in daemon mode with a message to use --no-daemon. These commands should work with daemon RPC like other commands.\n\nLocations:\n- cmd/bd/epic.go:26 (epic status command)\n- cmd/bd/epic.go:106 (epic close-eligible command)","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-23T19:33:34.552261-07:00","updated_at":"2025-10-23T21:56:33.732039-07:00","closed_at":"2025-10-23T21:56:33.732039-07:00"} +{"id":"bd-88","title":"bd import reports \"0 created, 0 updated\" when successfully importing issues","description":"The `bd import` command successfully imported 125 issues but reported \"0 created, 0 updated\" in the output. The import actually worked, but the success message is incorrect/misleading.\n\nThis appears to be a bug in the reporting logic that counts and displays the number of issues created/updated during import.","status":"in_progress","priority":2,"issue_type":"bug","created_at":"2025-10-23T22:28:40.391453-07:00","updated_at":"2025-10-23T23:00:32.276956-07:00"} {"id":"bd-9","title":"Test issue 2","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-21T23:53:44.31362-07:00","updated_at":"2025-10-23T19:33:21.099891-07:00","closed_at":"2025-10-21T22:06:41.257019-07:00","labels":["test-label"]} diff --git a/cmd/bd/import.go b/cmd/bd/import.go index fd50ceff..07760682 100644 --- a/cmd/bd/import.go +++ b/cmd/bd/import.go @@ -141,8 +141,11 @@ Behavior: } else if !result.PrefixMismatch { fmt.Fprintf(os.Stderr, "No collisions detected.\n") } - fmt.Fprintf(os.Stderr, "Would create %d new issues, update %d existing issues\n", - result.Created, result.Updated) + msg := fmt.Sprintf("Would create %d new issues, update %d existing issues", result.Created, result.Updated) + if result.Unchanged > 0 { + msg += fmt.Sprintf(", %d unchanged", result.Unchanged) + } + fmt.Fprintf(os.Stderr, "%s\n", msg) fmt.Fprintf(os.Stderr, "\nDry-run mode: no changes made\n") os.Exit(0) } @@ -177,6 +180,9 @@ Behavior: // Print summary fmt.Fprintf(os.Stderr, "Import complete: %d created, %d updated", result.Created, result.Updated) + if result.Unchanged > 0 { + fmt.Fprintf(os.Stderr, ", %d unchanged", result.Unchanged) + } if result.Skipped > 0 { fmt.Fprintf(os.Stderr, ", %d skipped", result.Skipped) } diff --git a/cmd/bd/import_bug_test.go b/cmd/bd/import_bug_test.go new file mode 100644 index 00000000..f42d68e7 --- /dev/null +++ b/cmd/bd/import_bug_test.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/steveyegge/beads/internal/storage/sqlite" + "github.com/steveyegge/beads/internal/types" +) + +// TestImportReturnsCorrectCounts reproduces bd-88 +// Import should report correct "created" count when importing new issues +func TestImportReturnsCorrectCounts(t *testing.T) { + // Create temporary database + tmpDir, err := os.MkdirTemp("", "beads-test-") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + dbPath := filepath.Join(tmpDir, ".beads", "issues.db") + if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil { + t.Fatalf("Failed to create .beads dir: %v", err) + } + + // Initialize database + store, err := sqlite.New(dbPath) + if err != nil { + t.Fatalf("Failed to create store: %v", err) + } + defer store.Close() + + ctx := context.Background() + + // Create test issues to import + issues := make([]*types.Issue, 0, 5) + for i := 1; i <= 5; i++ { + id := fmt.Sprintf("test-%d", i) + issues = append(issues, &types.Issue{ + ID: id, + Title: fmt.Sprintf("Test Issue %d", i), + Description: "Test description", + Status: types.StatusOpen, + Priority: 2, + IssueType: types.TypeTask, + }) + } + + // Import with default options + opts := ImportOptions{ + ResolveCollisions: false, + DryRun: false, + SkipUpdate: false, + Strict: false, + } + + result, err := importIssuesCore(ctx, dbPath, store, issues, opts) + if err != nil { + t.Fatalf("Import failed: %v", err) + } + + // Check that Created count matches + if result.Created != len(issues) { + t.Errorf("Expected Created=%d, got %d", len(issues), result.Created) + } + + // Verify issues are actually in the database + for _, issue := range issues { + retrieved, err := store.GetIssue(ctx, issue.ID) + if err != nil { + t.Errorf("Failed to get issue %s: %v", issue.ID, err) + } + if retrieved == nil { + t.Errorf("Issue %s not found in database", issue.ID) + } + } + + // Now test re-importing the same issues (idempotent case) + result2, err := importIssuesCore(ctx, dbPath, store, issues, opts) + if err != nil { + t.Fatalf("Second import failed: %v", err) + } + + // bd-88: When reimporting unchanged issues, should report them as "Unchanged" + if result2.Created != 0 { + t.Errorf("Second import: expected Created=0, got %d", result2.Created) + } + if result2.Updated != 0 { + t.Errorf("Second import: expected Updated=0, got %d", result2.Updated) + } + if result2.Unchanged != len(issues) { + t.Errorf("Second import: expected Unchanged=%d, got %d", len(issues), result2.Unchanged) + } + + t.Logf("Second import: Created=%d, Updated=%d, Unchanged=%d, Skipped=%d", + result2.Created, result2.Updated, result2.Unchanged, result2.Skipped) +} diff --git a/cmd/bd/import_shared.go b/cmd/bd/import_shared.go index 0ff4e28c..1502544f 100644 --- a/cmd/bd/import_shared.go +++ b/cmd/bd/import_shared.go @@ -165,6 +165,7 @@ type ImportOptions struct { type ImportResult struct { Created int // New issues created Updated int // Existing issues updated + Unchanged int // Existing issues that matched exactly (idempotent) Skipped int // Issues skipped (duplicates, errors) Collisions int // Collisions detected IDMapping map[string]string // Mapping of remapped IDs (old -> new) @@ -317,7 +318,8 @@ func importIssuesCore(ctx context.Context, dbPath string, store storage.Storage, } else if opts.DryRun { // No collisions in dry-run mode result.Created = len(collisionResult.NewIssues) - result.Updated = len(collisionResult.ExactMatches) + // bd-88: ExactMatches are unchanged issues (idempotent), not updates + result.Unchanged = len(collisionResult.ExactMatches) return result, nil } @@ -362,15 +364,15 @@ func importIssuesCore(ctx context.Context, dbPath string, store storage.Storage, updates["external_ref"] = nil } - // bd-84: Only update if data actually changed (prevents timestamp churn) + // bd-88: Only update if data actually changed (prevents timestamp churn) if issueDataChanged(existing, updates) { if err := sqliteStore.UpdateIssue(ctx, issue.ID, updates, "import"); err != nil { return nil, fmt.Errorf("error updating issue %s: %w", issue.ID, err) } result.Updated++ } else { - // Issue unchanged - count as skipped to avoid polluting JSONL with timestamp updates - result.Skipped++ + // bd-88: Track unchanged issues separately for accurate reporting + result.Unchanged++ } } else { // New issue - check for duplicates in import batch