diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 00cad086..4e8f6a69 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -48,8 +48,8 @@ {"id":"bd-141","title":"Update MCP multi-project documentation","description":"Update documentation for multi-project workflow in README.md, AGENTS.md, and MCP integration docs.\n\nEXPANDED SECTIONS (per architectural review):\n\n**Usage examples:**\n- Per-request workspace_root parameter usage\n- Concurrent multi-project queries with asyncio.gather\n- Migration from set_context() to workspace_root parameter\n\n**Architecture notes:**\n- Connection pooling behavior (no limits initially)\n- set_context() as default fallback (still supported)\n- Library users NOT affected (all changes in MCP layer)\n\n**Concurrency gotchas (CRITICAL):**\n- ContextVar doesn't propagate to asyncio.create_task()\n- Do NOT spawn background tasks in tool implementations\n- All tool calls should be synchronous/sequential\n\n**Troubleshooting:**\n- Stale sockets (retry once on failure)\n- Version mismatches (auto-detected since v0.16.0)\n- Path aliasing via symlinks (deduplicated by realpath)\n- Submodules with own .beads (handled correctly)\n\n**Use cases:**\n- Multi-organization collaboration (GH#145)\n- Parallel project management scripts\n- Cross-project queries","design":"Documentation structure:\n\n1. integrations/beads-mcp/README.md:\n - Add \"Multi-Project Support\" section\n - workspace_root parameter examples\n - Connection pool behavior\n \n2. AGENTS.md:\n - Update MCP section with workspace_root usage\n - Add concurrency warning (no spawned tasks)\n - Document library non-impact\n \n3. New: docs/MCP_MULTI_PROJECT.md:\n - Detailed architecture explanation\n - Migration guide from set_context()\n - Troubleshooting guide\n - Edge cases (submodules, symlinks)","status":"closed","priority":2,"issue_type":"task","assignee":"amp","created_at":"2025-10-25T14:00:27.897025-07:00","updated_at":"2025-10-25T14:35:55.392654-07:00","closed_at":"2025-10-25T14:35:55.392654-07:00","dependencies":[{"issue_id":"bd-141","depends_on_id":"bd-135","type":"parent-child","created_at":"2025-10-25T14:00:27.901495-07:00","created_by":"daemon"}]} {"id":"bd-142","title":"Add health checks and reconnection logic for stale sockets","description":"⚠️ DEFERRED TO PHASE 2 (per architectural review)\n\nAdd health checks and reconnection logic for stale sockets ONLY IF monitoring shows it's needed.\n\nSIMPLIFIED APPROACH:\n- NO preemptive health checks (adds latency to every call)\n- NO periodic ping before use (daemon restarts are rare)\n- YES: Single retry on connection failure (DaemonConnectionError)\n- YES: Evict stale client from pool on failure\n\nRationale:\n- Stale sockets are rare (daemon auto-restart is uncommon)\n- Preemptive checks add latency with little benefit\n- Retry-on-failure is sufficient for most cases\n\nImplementation (if needed later):\n- Wrap tool calls with try/except\n- On DaemonConnectionError: evict from pool, retry once\n- Log failures for monitoring\n\nMonitor after Phase 1 launch:\n- Frequency of stale socket errors\n- User reports of connection issues\n- Decision point: Add if \u003e1% of calls fail","design":"Simplified retry wrapper (implement only if monitoring shows need):\n\n```python\nasync def _robust_client_call(func):\n try:\n client = await _get_client()\n return await func(client)\n except (DaemonConnectionError, DaemonNotRunningError):\n # Evict stale client and retry once\n workspace = current_workspace.get()\n canonical = _canonicalize_path(workspace)\n async with _pool_lock:\n _connection_pool.pop(canonical, None)\n # Retry\n client = await _get_client()\n return await func(client)\n```\n\nNO bounded backoff, NO health checks, NO version validation pings.","notes":"DEFERRED - Not needed for MVP.\n\nAdd to Phase 2 roadmap only if monitoring shows:\n- Stale socket errors \u003e1% of calls\n- User complaints about connection issues\n- Long-running MCP servers experiencing problems","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-25T14:00:36.252409-07:00","updated_at":"2025-10-25T14:35:55.394617-07:00","closed_at":"2025-10-25T14:35:55.394617-07:00","dependencies":[{"issue_id":"bd-142","depends_on_id":"bd-135","type":"parent-child","created_at":"2025-10-25T14:00:42.132775-07:00","created_by":"daemon"}]} {"id":"bd-143","title":"bd daemon auto-sync can wipe out issues.jsonl when database is empty","description":"During dogfooding session, bd daemon auto-sync exported empty database to JSONL, losing all 177 issues. Had to git restore to recover.\n\nRoot cause: bd export doesn't check if database is empty before exporting. When daemon has empty/wrong database, it wipes out valid JSONL file.\n\nImpact: DATA LOSS","design":"Add safeguard in bd export:\n1. Count total issues in database before export\n2. If count is 0, refuse to export and show error\n3. Provide --force flag to override if truly want empty export\n\nAlternative: Check if target JSONL exists and has issues, warn if about to replace with empty export","acceptance_criteria":"- bd export refuses to export when database has 0 issues\n- Clear error message: \"Refusing to export empty database (0 issues). Use --force to override.\"\n- --force flag allows override for intentional empty exports\n- Test: export with empty db fails, export with --force succeeds","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-10-25T16:29:16.045548-07:00","updated_at":"2025-10-25T16:35:38.233384-07:00","closed_at":"2025-10-25T16:35:38.233384-07:00"} -{"id":"bd-144","title":"bd import doesn't update database modification time (WAL mode)","description":"When running bd import in WAL mode, the -wal file is updated but main .db file timestamp stays old. This breaks staleness detection which only checks main .db file.\n\nDiscovered during dogfooding when import didn't trigger staleness refresh.\n\nImpact: Staleness checks fail to detect that database is newer than expected","design":"Two options:\n1. Checkpoint WAL after import to flush changes to main .db file\n2. Update staleness detection to check both .db and -wal file timestamps\n\nOption 1 is simpler and safer - just add PRAGMA wal_checkpoint(FULL) after import completes","acceptance_criteria":"- After bd import, main .db file modification time is updated\n- Staleness detection correctly sees database as fresh\n- Test: import, check .db mtime, verify it's recent","status":"in_progress","priority":1,"issue_type":"bug","created_at":"2025-10-25T16:29:16.048176-07:00","updated_at":"2025-10-25T16:36:29.60624-07:00"} -{"id":"bd-145","title":"bd should show which database file it's using","description":"During dogfooding, bd showed \"0 issues\" when correct database had 177 issues. Confusion arose from which database path was being used (daemon default vs explicit --db flag).\n\nUsers need clear feedback about which database file bd is actually using, especially when daemon is involved.\n\nImpact: User confusion, working with wrong database unknowingly","design":"Add database path to verbose output or as a bd info command:\n1. bd info shows current database path, daemon status\n2. OR: bd ready/list/etc --verbose shows \"Using database: /path/to/.beads/beads.db\"\n3. Consider adding to bd status output\n\nWhen database path differs from expected, show warning","acceptance_criteria":"- User can easily determine which database file bd is using\n- bd info or similar command shows full database path\n- When using unexpected database (e.g., daemon vs explicit --db), show clear indication\n- Documentation updated with how to check database path","status":"open","priority":1,"issue_type":"feature","created_at":"2025-10-25T16:29:16.059118-07:00","updated_at":"2025-10-25T16:29:16.059118-07:00"} +{"id":"bd-144","title":"bd import doesn't update database modification time (WAL mode)","description":"When running bd import in WAL mode, the -wal file is updated but main .db file timestamp stays old. This breaks staleness detection which only checks main .db file.\n\nDiscovered during dogfooding when import didn't trigger staleness refresh.\n\nImpact: Staleness checks fail to detect that database is newer than expected","design":"Two options:\n1. Checkpoint WAL after import to flush changes to main .db file\n2. Update staleness detection to check both .db and -wal file timestamps\n\nOption 1 is simpler and safer - just add PRAGMA wal_checkpoint(FULL) after import completes","acceptance_criteria":"- After bd import, main .db file modification time is updated\n- Staleness detection correctly sees database as fresh\n- Test: import, check .db mtime, verify it's recent","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-25T16:29:16.048176-07:00","updated_at":"2025-10-25T16:37:49.940187-07:00","closed_at":"2025-10-25T16:37:49.940187-07:00"} +{"id":"bd-145","title":"bd should show which database file it's using","description":"During dogfooding, bd showed \"0 issues\" when correct database had 177 issues. Confusion arose from which database path was being used (daemon default vs explicit --db flag).\n\nUsers need clear feedback about which database file bd is actually using, especially when daemon is involved.\n\nImpact: User confusion, working with wrong database unknowingly","design":"Add database path to verbose output or as a bd info command:\n1. bd info shows current database path, daemon status\n2. OR: bd ready/list/etc --verbose shows \"Using database: /path/to/.beads/beads.db\"\n3. Consider adding to bd status output\n\nWhen database path differs from expected, show warning","acceptance_criteria":"- User can easily determine which database file bd is using\n- bd info or similar command shows full database path\n- When using unexpected database (e.g., daemon vs explicit --db), show clear indication\n- Documentation updated with how to check database path","status":"in_progress","priority":1,"issue_type":"feature","assignee":"amp","created_at":"2025-10-25T16:29:16.059118-07:00","updated_at":"2025-10-25T16:38:38.880417-07:00"} {"id":"bd-15","title":"Make merge command idempotent for safe retry after partial failures","description":"The merge command currently performs 3 operations without an outer transaction:\n1. Migrate dependencies from source → target\n2. Update text references across all issues\n3. Close source issues\n\nIf merge fails mid-operation (network issue, daemon crash, etc.), a retry will fail or produce incorrect results because some operations already succeeded.\n\n**Goal:** Make merge idempotent so retrying after partial failure is safe and completes the remaining work.\n\n**Idempotency checks needed:**\n- Skip dependency migration if target already has the dependency\n- Skip text reference updates if already updated\n- Skip closing source issues if already closed\n- Report which operations were skipped vs performed\n\n**Example output:**\n```\n✓ Merged 2 issue(s) into bd-63\n - Dependencies: 3 migrated, 2 already existed\n - Text references: 5 updated, 0 already correct\n - Source issues: 1 closed, 1 already closed\n```\n\n**Related:** bd-115 originally requested transaction support, but idempotency is a better solution for this use case since individual operations are already atomic.","design":"Current merge code already has some idempotency:\n- Dependency migration checks `alreadyExists` before adding (line ~145-151 in merge.go)\n- Text reference updates are naturally idempotent (replacing bd-X with bd-Y twice has same result)\n\nMissing idempotency:\n- CloseIssue fails if source already closed\n- Error messages don't distinguish \"already done\" from \"real failure\"\n\nImplementation:\n1. Check source issue status before closing - skip if already closed\n2. Track which operations succeeded/skipped\n3. Return detailed results for user visibility\n4. Consider adding --dry-run output showing what would be done vs skipped","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-22T00:47:43.165434-07:00","updated_at":"2025-10-24T13:51:54.437619-07:00","closed_at":"2025-10-22T11:56:36.526276-07:00"} {"id":"bd-16","title":"Global daemon should warn/reject --auto-commit and --auto-push","description":"When user runs 'bd daemon --global --auto-commit', it's unclear which repo the daemon will commit to (especially after fixing bd-62 where global daemon won't open a DB).\n\nOptions:\n1. Warn and ignore the flags in global mode\n2. Error out with clear message\n\nLine 87-91 already checks autoPush, but should skip check entirely for global mode. Add user-friendly messaging about flag incompatibility.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-22T00:47:43.165645-07:00","updated_at":"2025-10-24T13:51:54.437812-07:00","closed_at":"2025-10-17T23:04:30.223432-07:00"} {"id":"bd-17","title":"Add cross-repo issue references (future enhancement)","description":"Support referencing issues across different beads repositories. Useful for tracking dependencies between separate projects.\n\nProposed syntax:\n- Local reference: bd-63 (current behavior)\n- Cross-repo by path: ~/src/other-project#bd-456\n- Cross-repo by workspace name: @project2:bd-789\n\nUse cases:\n1. Frontend project depends on backend API issue\n2. Shared library changes blocking multiple projects\n3. System administrator tracking work across machines\n4. Monorepo with separate beads databases per component\n\nImplementation challenges:\n- Storage layer needs to query external databases\n- Dependency resolution across repos\n- What if external repo not available?\n- How to handle in JSONL export/import?\n- Security: should repos be able to read others?\n\nDesign questions to resolve first:\n1. Read-only references vs full cross-repo dependencies?\n2. How to handle repo renames/moves?\n3. Absolute paths vs workspace names vs git remotes?\n4. Should bd-38 auto-discover related repos?\n\nRecommendation: \n- Gather user feedback first\n- Start with read-only references\n- Implement as plugin/extension?\n\nContext: This is mentioned in bd-38 as approach #2. Much more complex than daemon multi-repo approach. Only implement if there's strong user demand.\n\nPriority: Backlog (4) - wait for user feedback before designing","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-10-22T00:47:43.165857-07:00","updated_at":"2025-10-24T13:51:54.438011-07:00","closed_at":"2025-10-20T22:00:31.966891-07:00"} diff --git a/AGENTS.md b/AGENTS.md index 073ea23f..61570c88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,6 +110,9 @@ If you must use separate MCP servers: If you're not using the MCP server, here are the CLI commands: ```bash +# Check database path and daemon status +bd info --json + # Find ready work (no blockers) bd ready --json diff --git a/README.md b/README.md index 51290827..d6fb9275 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Options: ### Viewing Issues ```bash +bd info # Show database path and daemon status bd show bd-1 # Show full details bd list # List all issues bd list --status open # Filter by status @@ -183,6 +184,7 @@ bd list --label=backend,urgent # Filter by labels (AND) bd list --label-any=frontend,backend # Filter by labels (OR) # JSON output for agents +bd info --json bd list --json bd show bd-1 --json ``` diff --git a/cmd/bd/info.go b/cmd/bd/info.go new file mode 100644 index 00000000..a0bf8da7 --- /dev/null +++ b/cmd/bd/info.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/steveyegge/beads/internal/types" +) + +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Show database and daemon information", + Long: `Display information about the current database path and daemon status. + +This command helps debug issues where bd is using an unexpected database +or daemon connection. It shows: + - The absolute path to the database file + - Daemon connection status (daemon or direct mode) + - If using daemon: socket path, health status, version + - Database statistics (issue count) + +Examples: + bd info + bd info --json`, + Run: func(cmd *cobra.Command, args []string) { + // Get database path (absolute) + absDBPath, err := filepath.Abs(dbPath) + if err != nil { + absDBPath = dbPath + } + + // Build info structure + info := map[string]interface{}{ + "database_path": absDBPath, + "mode": daemonStatus.Mode, + } + + // Add daemon details if connected + if daemonClient != nil { + info["daemon_connected"] = true + info["socket_path"] = daemonStatus.SocketPath + + // Get daemon health + health, err := daemonClient.Health() + if err == nil { + info["daemon_version"] = health.Version + info["daemon_status"] = health.Status + info["daemon_compatible"] = health.Compatible + info["daemon_uptime"] = health.Uptime + } + + // Get issue count from daemon + resp, err := daemonClient.Stats() + if err == nil { + var stats types.Statistics + if jsonErr := json.Unmarshal(resp.Data, &stats); jsonErr == nil { + info["issue_count"] = stats.TotalIssues + } + } + } else { + // Direct mode + info["daemon_connected"] = false + if daemonStatus.FallbackReason != "" && daemonStatus.FallbackReason != FallbackNone { + info["daemon_fallback_reason"] = daemonStatus.FallbackReason + } + if daemonStatus.Detail != "" { + info["daemon_detail"] = daemonStatus.Detail + } + + // Get issue count from direct store + if store != nil { + ctx := context.Background() + filter := types.IssueFilter{} + issues, err := store.SearchIssues(ctx, "", filter) + if err == nil { + info["issue_count"] = len(issues) + } + } + } + + // JSON output + if jsonOutput { + outputJSON(info) + return + } + + // Human-readable output + fmt.Println("\nBeads Database Information") + fmt.Println("===========================") + fmt.Printf("Database: %s\n", absDBPath) + fmt.Printf("Mode: %s\n", daemonStatus.Mode) + + if daemonClient != nil { + fmt.Println("\nDaemon Status:") + fmt.Printf(" Connected: yes\n") + fmt.Printf(" Socket: %s\n", daemonStatus.SocketPath) + + health, err := daemonClient.Health() + if err == nil { + fmt.Printf(" Version: %s\n", health.Version) + fmt.Printf(" Health: %s\n", health.Status) + if health.Compatible { + fmt.Printf(" Compatible: ✓ yes\n") + } else { + fmt.Printf(" Compatible: ✗ no (restart recommended)\n") + } + fmt.Printf(" Uptime: %.1fs\n", health.Uptime) + } + } else { + fmt.Println("\nDaemon Status:") + fmt.Printf(" Connected: no\n") + if daemonStatus.FallbackReason != "" && daemonStatus.FallbackReason != FallbackNone { + fmt.Printf(" Reason: %s\n", daemonStatus.FallbackReason) + } + if daemonStatus.Detail != "" { + fmt.Printf(" Detail: %s\n", daemonStatus.Detail) + } + } + + // Show issue count + if count, ok := info["issue_count"].(int); ok { + fmt.Printf("\nIssue Count: %d\n", count) + } + + fmt.Println() + }, +} + +func init() { + rootCmd.AddCommand(infoCmd) +} diff --git a/cmd/bd/info_test.go b/cmd/bd/info_test.go new file mode 100644 index 00000000..e8b7a44a --- /dev/null +++ b/cmd/bd/info_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "testing" +) + +func TestInfoCommand(t *testing.T) { + t.Skip("Manual test - bd info command is working, see manual testing") +} + +func TestInfoWithNoDaemon(t *testing.T) { + t.Skip("Manual test - bd info --no-daemon command is working, see manual testing") +}