diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 9e122d2a..5246328c 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -66,7 +66,9 @@ {"id":"bd-g9eu","content_hash":"79fe2f96d06e3f0750b55f323bc104b02de6ab8a745e0bd36cf3425e125af89c","title":"Investigate TestRoutingIntegration failure","description":"TestRoutingIntegration/maintainer_with_SSH_remote failed during pre-commit check with \"expected role maintainer, got contributor\".\nThis occurred while running `go test -short ./...` on darwin/arm64.\nThe failure appears unrelated to storage/sqlite changes.\nNeed to investigate if this is a flaky test or environmental issue.","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-20T15:55:19.337094-08:00","updated_at":"2025-11-20T15:55:19.337094-08:00","source_repo":"."} {"id":"bd-gdn","content_hash":"3411b7c03ec961d665181fbffa8f8b98a7809e296013786e743e371e92e9dc58","title":"Add functional tests for FlushManager correctness verification","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T21:21:53.967757-05:00","updated_at":"2025-11-20T21:35:53.1183-05:00","closed_at":"2025-11-20T21:35:53.1183-05:00","source_repo":".","comments":[{"id":21,"issue_id":"bd-gdn","author":"stevey","text":"Current race detector tests only verify \"no race detected\" but don't verify data correctness.\n\nNeed functional tests that:\n- Create real SQLite store with test data\n- Mark issues dirty and trigger flush\n- Verify JSONL file was updated correctly\n- Test debouncing actually reduces flush count\n- Verify shutdown performs final flush\n- Test recovery after flush failures\n\nExample test structure:\n```go\nfunc TestFlushManagerActuallyFlushes(t *testing.T) {\n // Setup real SQLite store in temp dir\n // Create test issue\n // Mark dirty via FlushManager\n // Wait for debounce + flush\n // Read JSONL file\n // Verify issue appears in JSONL with correct data\n}\n```\n\nRelated: Code review finding #3 from bd-52 race condition fix review.","created_at":"2025-11-21T20:14:08Z"}]} {"id":"bd-ge7","content_hash":"84248781654b9924e1f4284058f141b73d761dead05ef9a3d1cc9b9f8cd4b60d","title":"Improve Beads test coverage from 46% to 80%","description":"","design":"Currently at 46% test coverage. Need to systematically improve coverage across all subsystems, focusing first on packages with minimal or no tests.\n\nTarget: 80% overall coverage\n\nApproach:\n- Break down by subsystem (internal/* packages)\n- Prioritize packages with 0-1 test files\n- Each child issue targets specific coverage goals\n- Focus on unit tests for core logic, error paths, and edge cases\n\nThis epic will be executed by the VC executor to test its ability to handle sustained multi-issue work.","acceptance_criteria":"- Overall test coverage reaches 80% or higher\n- All internal/* packages have at least 60% coverage\n- All packages with only 1 test file now have at least 3 test files\n- Quality gates (go test, golangci-lint) pass\n- Tests are maintainable and test actual behavior, not implementation details","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-20T21:21:03.700271-05:00","updated_at":"2025-11-20T21:21:03.700271-05:00","source_repo":"."} +{"id":"bd-gqo","content_hash":"21642505de8036d9501f8593b78de7b761f05869f9d8d11758278e9c0b33c9c3","title":"Implement health checks in daemon event loop","description":"Add health checks to checkDaemonHealth() function in daemon_event_loop.go:170:\n- Database integrity check\n- Disk space check\n- Memory usage check\n\nCurrently it's just a no-op placeholder.","status":"open","priority":3,"issue_type":"feature","created_at":"2025-11-21T18:55:07.534304-05:00","updated_at":"2025-11-21T18:55:07.534304-05:00","source_repo":"."} {"id":"bd-gra","content_hash":"d0efec9eca3d35c7a8b298d861cb9d06d193e2cff3ff890f02ef88ca6c589d79","title":"Add error handling test for cmd.Help() in search command","description":"Test coverage gap identified by automated analysis (vc-217).\n\n**Original Issue:** [deleted:bd-da96-baseline-lint]\n\nIn cmd/bd/search.go:39, the return value of cmd.Help() is not checked, flagged by errcheck linter.\n\nAdd test to verify:\n- Error handling when cmd.Help() fails (e.g., output redirection fails)\n- Proper error propagation to caller\n- Command still exits gracefully on help error\n\nThis ensures the search command handles help errors properly and doesn't silently ignore failures.\n\n_This issue was automatically created by AI test coverage analysis._","acceptance_criteria":"- Tests written for uncovered functionality\n- Test coverage verified\n- All tests passing","status":"open","priority":2,"issue_type":"task","assignee":"ai-supervisor","created_at":"2025-11-21T10:25:33.52308-05:00","updated_at":"2025-11-21T15:12:10.003359-05:00","source_repo":".","labels":["discovered:supervisor"],"dependencies":[{"issue_id":"bd-gra","depends_on_id":"bd-da96-baseline-lint","type":"discovered-from","created_at":"2025-11-21T10:25:33.526016-05:00","created_by":"ai-supervisor"}]} +{"id":"bd-hdt","content_hash":"8e6cf1653ef2ea583b39a421b3d708763ab7c042d6cd494e77202a92af0a7398","title":"Implement auto-merge functionality in duplicates command","description":"The duplicates.go file has a TODO at line 95 to implement the performMerge function for automatic duplicate merging. Currently it just prints a warning message. This would automate the merge process instead of just suggesting commands.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-21T18:55:02.828619-05:00","updated_at":"2025-11-21T18:55:02.828619-05:00","source_repo":"."} {"id":"bd-i00","content_hash":"eb91d1bd82defffa773c7706f4094ca1a322f1e7be017aebc91fd7eb8e6b7aad","title":"Convert magic numbers to named constants in FlushManager","description":"","status":"closed","priority":4,"issue_type":"task","created_at":"2025-11-20T21:22:17.845269-05:00","updated_at":"2025-11-20T21:35:53.11654-05:00","closed_at":"2025-11-20T21:35:53.11654-05:00","source_repo":".","comments":[{"id":22,"issue_id":"bd-i00","author":"stevey","text":"Several magic numbers should be named constants for clarity and maintainability:\n\nflush_manager.go:\n- Line 64: `10` (markDirtyCh buffer size)\n- Line 65: `1` (timerFiredCh buffer size)\n- Line 139: `30 * time.Second` (shutdown timeout)\n\nautoflush.go:\n- Line 562: `3` (flush failure threshold before warning)\n\nSuggested constants:\n```go\nconst (\n markDirtyBufferSize = 10 // Buffer rapid mark requests\n timerFiredBufferSize = 1 // Timer notifications coalesce\n shutdownTimeout = 30 * time.Second // Generous for large DBs\n flushFailureThreshold = 3 // Show warning after N failures\n)\n```\n\nRelated: Code review finding #6 from bd-52 race condition fix review.","created_at":"2025-11-21T20:14:08Z"}]} {"id":"bd-j3zt","content_hash":"531ad51101f41375a93d66b8d22105ce7c4913261db78b662bb759e802bc01e2","title":"Fix mypy errors in beads-mcp","description":"Running `mypy .` in `integrations/beads-mcp` reports 287 errors. These should be addressed to improve type safety and code quality.","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-20T18:53:28.557708-05:00","updated_at":"2025-11-20T18:53:28.557708-05:00","source_repo":"."} {"id":"bd-keb","content_hash":"74cb243817cd6b77b5cdb6280bba866cc133b8c01eb4bbbb4e23c5b47a973546","title":"Add database maintenance commands section to QUICKSTART.md","description":"**Problem:**\nUsers don't discover `bd compact` or `bd cleanup` commands until their database grows large. These maintenance commands aren't mentioned in quickstart documentation.\n\nRelated to issue #349 item #4.\n\n**Current state:**\ndocs/QUICKSTART.md ends at line 217 with \"See README.md for full documentation\" but has no mention of maintenance operations.\n\n**Proposed addition:**\nAdd a \"Database Maintenance\" section after line 140 (before \"Advanced: Agent Mail\" section) covering:\n- When database grows (many closed issues)\n- How to view compaction statistics\n- How to compact old issues\n- How to delete closed issues\n- Warning about permanence\n\n**Example content:**\n```markdown\n## Database Maintenance\n\nAs your project accumulates closed issues, the database grows. Use these commands to manage size:\n\n```bash\n# View compaction statistics\nbd compact --stats\n\n# Preview compaction candidates (30+ days closed)\nbd compact --analyze --json\n\n# Apply agent-generated summary\nbd compact --apply --id bd-42 --summary summary.txt\n\n# Immediately delete closed issues (use with caution!)\nbd cleanup --force\n```\n\n**When to compact:**\n- Database file \u003e 10MB with many old closed issues\n- After major project milestones\n- Before archiving a project phase\n```\n\n**Files to modify:**\n- docs/QUICKSTART.md (add section after line 140)","design":"Brief, actionable guidance on database maintenance.\n\nFocus on:\n1. When to do maintenance (size/age triggers)\n2. Non-destructive options first (stats, analyze)\n3. Clear warnings about permanence\n4. Reference to bd restore for git history recovery","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-20T20:48:40.488512-05:00","updated_at":"2025-11-20T20:59:13.439462-05:00","closed_at":"2025-11-20T20:59:13.439462-05:00","source_repo":".","labels":["documentation","onboarding"],"comments":[{"id":23,"issue_id":"bd-keb","author":"stevey","text":"Addresses GitHub issue #349 item 4: https://github.com/steveyegge/beads/issues/349\n\nUsers don't discover compact/cleanup commands until database grows large. Quickstart should mention maintenance operations.","created_at":"2025-11-21T20:14:08Z"}]} @@ -85,6 +87,7 @@ {"id":"bd-o78","content_hash":"0d61c7a8e687c16c262db4ba4e6c7f63861fbeb524ec014000c68a0552df7965","title":"Enhance `bd doctor` to verify Claude Code integration","description":"Add checks to `bd doctor` that verify Claude Code integration is properly set up when .claude/ directory or Claude environment is detected.","design":"## Checks to Add\n\n### 1. Claude Environment Detection\n```go\nif isClaudeDetected() {\n fmt.Println(\"✓ Claude Code environment detected\")\n \n // Check what's installed\n hasMCP := isMCPServerInstalled()\n hasHooks := isClaudeSetupComplete()\n \n if hasMCP \u0026\u0026 hasHooks {\n fmt.Println(\"✓ MCP server installed\")\n fmt.Println(\"✓ Claude hooks installed (provides workflow reminders)\")\n } else if !hasMCP \u0026\u0026 hasHooks {\n fmt.Println(\"✓ Claude hooks installed (provides CLI reference)\")\n fmt.Println(\" Note: MCP server not detected (using CLI mode)\")\n } else if hasMCP \u0026\u0026 !hasHooks {\n fmt.Println(\"✓ MCP server installed\")\n fmt.Println(\"⚠ Claude hooks not installed\")\n fmt.Println(\" Hooks prevent agent from forgetting bd workflow after compaction\")\n fmt.Println(\" Run: bd setup claude\")\n } else {\n fmt.Println(\"⚠ Claude integration not set up\")\n fmt.Println(\" Run: bd setup claude\")\n // TODO (future): Recommend CLI approach once proven in production\n // fmt.Println(\" Tip: CLI mode (without MCP) uses fewer tokens\")\n }\n}\n\nfunc isMCPServerInstalled() bool {\n // Check ~/.claude/settings.json for beads MCP server\n home, _ := os.UserHomeDir()\n settingsPath := filepath.Join(home, \".claude/settings.json\")\n \n data, err := os.ReadFile(settingsPath)\n if err != nil {\n return false\n }\n \n var settings map[string]interface{}\n if err := json.Unmarshal(data, \u0026settings); err != nil {\n return false\n }\n \n // Check mcpServers section for beads\n mcpServers, ok := settings[\"mcpServers\"].(map[string]interface{})\n if !ok {\n return false\n }\n \n // Look for beads server (any key containing \"beads\")\n for key := range mcpServers {\n if strings.Contains(strings.ToLower(key), \"beads\") {\n return true\n }\n }\n \n return false\n}\n```\n\n### 2. Hook Installation Verification (MCP-Aware)\n\n```go\nfunc checkClaudeHooks() {\n home, _ := os.UserHomeDir()\n globalSettings := filepath.Join(home, \".claude/settings.json\")\n projectSettings := \".claude/settings.local.json\"\n \n globalHooks := hasBeadsHooks(globalSettings)\n projectHooks := hasBeadsHooks(projectSettings)\n \n if globalHooks {\n fmt.Println(\"✓ Global hooks installed\")\n } else if projectHooks {\n fmt.Println(\"✓ Project hooks installed\")\n } else {\n fmt.Println(\"⚠ No hooks installed\")\n fmt.Println(\" Run: bd setup claude\")\n return\n }\n \n // Check if hooks will work\n if !commandExists(\"bd\") {\n fmt.Println(\"⚠ 'bd' command not in PATH\")\n fmt.Println(\" Hooks won't work - ensure bd is installed globally\")\n }\n}\n\nfunc hasBeadsHooks(settingsPath string) bool {\n data, err := os.ReadFile(settingsPath)\n if err != nil {\n return false\n }\n \n var settings map[string]interface{}\n if err := json.Unmarshal(data, \u0026settings); err != nil {\n return false\n }\n \n hooks, ok := settings[\"hooks\"].(map[string]interface{})\n if !ok {\n return false\n }\n \n // Check SessionStart and PreCompact for \"bd prime\"\n for _, event := range []string{\"SessionStart\", \"PreCompact\"} {\n eventHooks, ok := hooks[event].([]interface{})\n if !ok {\n continue\n }\n \n for _, hook := range eventHooks {\n hookMap, _ := hook.(map[string]interface{})\n commands, _ := hookMap[\"hooks\"].([]interface{})\n for _, cmd := range commands {\n cmdMap, _ := cmd.(map[string]interface{})\n if cmdMap[\"command\"] == \"bd prime\" {\n return true\n }\n }\n }\n }\n \n return false\n}\n```\n\n### 3. AGENTS.md/CLAUDE.md Reference Check\n```go\n// Check if documentation references bd prime\nagentsContent := readFileIfExists(\"AGENTS.md\")\nclaudeContent := readFileIfExists(\"CLAUDE.md\")\n\nif strings.Contains(agentsContent, \"bd prime\") || strings.Contains(claudeContent, \"bd prime\") {\n // Verify bd prime command exists in current version\n if !commandExists(\"prime\") {\n fmt.Println(\"⚠ Documentation references 'bd prime' but command not found\")\n fmt.Println(\" Upgrade bd or remove references\")\n } else {\n fmt.Println(\"✓ Documentation references match installed features\")\n }\n}\n```\n\n### 4. Context Priming Test\n```go\n// Verify bd prime actually works\ncmd := exec.Command(\"bd\", \"prime\")\noutput, err := cmd.CombinedOutput()\n\nif err != nil {\n fmt.Println(\"⚠ 'bd prime' failed to execute\")\n fmt.Println(\" Error:\", err)\n} else if len(output) == 0 {\n fmt.Println(\"⚠ 'bd prime' produced no output\")\n fmt.Println(\" Expected workflow context markdown\")\n} else {\n // Check if output adapts to MCP mode\n hasMCP := isMCPServerInstalled()\n outputStr := string(output)\n \n if hasMCP \u0026\u0026 strings.Contains(outputStr, \"mcp__plugin_beads_beads__\") {\n fmt.Println(\"✓ bd prime detected MCP mode (workflow reminders)\")\n } else if !hasMCP \u0026\u0026 strings.Contains(outputStr, \"bd ready\") {\n fmt.Println(\"✓ bd prime using CLI mode (full command reference)\")\n } else {\n fmt.Println(\"⚠ bd prime output may not be adapting to environment\")\n }\n}\n```\n\n## Output Format Examples\n\n### With MCP and Hooks\n```\nbd doctor\n\nDatabase:\n✓ Database found at .beads/beads.db\n✓ Git hooks installed\n\nClaude Code Integration:\n✓ Claude Code environment detected\n✓ MCP server installed\n✓ Claude hooks installed (provides workflow reminders)\n✓ bd prime detected MCP mode (workflow reminders)\n✓ Documentation references match installed features\n\nSync Status:\n✓ No sync issues detected\n```\n\n### Without MCP, With Hooks\n```\nbd doctor\n\nDatabase:\n✓ Database found at .beads/beads.db\n✓ Git hooks installed\n\nClaude Code Integration:\n✓ Claude Code environment detected\n✓ Claude hooks installed (provides CLI reference)\n Note: MCP server not detected (using CLI mode)\n✓ bd prime using CLI mode (full command reference)\n\nSync Status:\n✓ No sync issues detected\n```\n\n### No Integration\n```\nbd doctor\n\nDatabase:\n✓ Database found at .beads/beads.db\n✓ Git hooks installed\n\nClaude Code Integration:\n✓ Claude Code environment detected\n⚠ Claude integration not set up\n Run: bd setup claude\n\nSync Status:\n✓ No sync issues detected\n```\n\n## Future Enhancement (Post-Production Validation)\n\nOnce CLI mode is proven in production, add recommendation:\n\n```go\nif isClaudeDetected() \u0026\u0026 !hasMCP \u0026\u0026 !hasHooks {\n fmt.Println(\"⚠ Claude integration not set up\")\n fmt.Println(\" Run: bd setup claude\")\n fmt.Println(\" Tip: CLI mode (without MCP) uses fewer tokens than MCP server\")\n fmt.Println(\" Both approaches work equally well - choose based on preference\")\n}\n```\n\nThis recommendation should only be added after CLI mode with `bd prime` is validated in real-world usage.","acceptance_criteria":"- bd doctor checks for Claude environment\n- Verifies hook installation if .claude/ exists\n- Checks AGENTS.md/CLAUDE.md for bd prime references\n- Detects version mismatches between docs and installed bd\n- Provides actionable suggestions (bd setup claude, upgrade)\n- Tests cover detection logic","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-11T23:30:05.782406-08:00","updated_at":"2025-11-12T00:12:07.717579-08:00","source_repo":".","dependencies":[{"issue_id":"bd-o78","depends_on_id":"bd-90v","type":"parent-child","created_at":"2025-11-11T23:31:27.886095-08:00","created_by":"daemon"}]} {"id":"bd-ola6","content_hash":"79461888e8a7875bf3623b8db44ea004f73a2374daa52ae9cb3fc9d3ce5e6a8b","title":"Implement transaction retry logic for SQLITE_BUSY","description":"BEGIN IMMEDIATE fails immediately on SQLITE_BUSY instead of retrying with exponential backoff.\n\nLocation: internal/storage/sqlite/sqlite.go:223-225\n\nProblem:\n- Under concurrent write load, BEGIN IMMEDIATE can fail with SQLITE_BUSY\n- Current implementation fails immediately instead of retrying\n- Results in spurious failures under normal concurrent usage\n\nSolution: Implement exponential backoff retry:\n- Retry up to N times (e.g., 5)\n- Backoff: 10ms, 20ms, 40ms, 80ms, 160ms\n- Check for context cancellation between retries\n- Only retry on SQLITE_BUSY/database locked errors\n\nImpact: Spurious failures under concurrent write load\n\nEffort: 3 hours","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-16T14:51:31.247147-08:00","updated_at":"2025-11-16T14:51:31.247147-08:00","source_repo":"."} {"id":"bd-p6vp","content_hash":"1df6d3b9b438cdcdbc618c24fea48769c1f22e8a8701af4e742531d4433ca7ea","title":"Clarify .beads/.gitattributes handling in Protected Branches docs","description":"Protected Branches docs quick start leaves untracked `.beads` directory and `.gitattributes`.\nQuestion: Are these changes meant to be checked into the protected branch?\nNeed to clarify if these should be ignored or committed, or if the instructions are missing a step.\n","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-20T18:56:25.79407-05:00","updated_at":"2025-11-20T18:56:25.79407-05:00","source_repo":"."} +{"id":"bd-r46","content_hash":"31d26e3081c31f4e8813fc1287c3bb7b2e87763eccc24b930a763c380e8433ab","title":"Support --reason flag in daemon mode for reopen command","description":"The reopen.go command has a TODO at line 61 to add reason as a comment once RPC supports AddComment. Currently --reason flag is ignored in daemon mode with a warning.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-21T18:55:10.773626-05:00","updated_at":"2025-11-21T18:55:10.773626-05:00","source_repo":"."} {"id":"bd-rfj","content_hash":"c38dcf80ea98c965123622c1ff4c9faddefd226609c1f3ad59143182b1f00114","title":"Add unit tests for hasJSONLChanged() function","description":"The hasJSONLChanged() function has no unit tests. Should test:\n- Normal case: hash matches\n- Normal case: hash differs\n- Edge case: empty file\n- Edge case: missing metadata (first run)\n- Edge case: corrupted metadata\n- Edge case: file read errors\n- Edge case: invalid hash format in metadata\n\nAlso add performance benchmarks for large JSONL files.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-20T21:31:10.389481-05:00","updated_at":"2025-11-20T21:40:02.664516-05:00","closed_at":"2025-11-20T21:40:02.664516-05:00","source_repo":".","dependencies":[{"issue_id":"bd-rfj","depends_on_id":"bd-khnb","type":"blocks","created_at":"2025-11-20T21:31:10.39058-05:00","created_by":"daemon"}]} {"id":"bd-rtp","content_hash":"302e1b77241830b37c9bcc6758da110c797bab2481877ebaa9bff931628e97f9","title":"Implement signal-aware context in CLI commands","description":"Replace context.Background() with signal.NotifyContext() to enable graceful cancellation.\n\n## Context\nPart of context propagation work (bd-350). Phase 1 infrastructure is complete - sqlite.New() now accepts context parameter.\n\n## Implementation\nSet up signal-aware context at the CLI entry points:\n\n```go\nctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)\ndefer cancel()\n```\n\n## Key Locations\n- cmd/bd/main.go:438 (marked with TODO(bd-350))\n- cmd/bd/daemon.go:317 (marked with TODO(bd-350))\n- Other command entry points\n\n## Benefits\n- Ctrl+C cancels ongoing database operations\n- Graceful shutdown on SIGTERM\n- Better user experience during long operations\n\n## Acceptance Criteria\n- [ ] Ctrl+C during import cancels operation cleanly\n- [ ] Ctrl+C during export cancels operation cleanly\n- [ ] No database corruption on cancellation\n- [ ] Proper cleanup on signal (defers execute)","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-20T21:26:34.621983-05:00","updated_at":"2025-11-20T21:32:26.288303-05:00","closed_at":"2025-11-20T21:32:26.288303-05:00","source_repo":"."} {"id":"bd-s02","content_hash":"911d456e4dabae028dd615b643c99058ef12e55ea523cb81cc933783c7b13546","title":"Manual task","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-03T20:15:10.022202-08:00","updated_at":"2025-11-03T20:15:10.022202-08:00","source_repo":"."} diff --git a/cmd/bd/MAIN_TEST_OPTIMIZATION_PLAN.md b/cmd/bd/MAIN_TEST_OPTIMIZATION_PLAN.md new file mode 100644 index 00000000..5314df2d --- /dev/null +++ b/cmd/bd/MAIN_TEST_OPTIMIZATION_PLAN.md @@ -0,0 +1,249 @@ +# main_test.go Performance Optimization Plan + +## Executive Summary +Tests are currently **hanging indefinitely** due to nil `rootCtx`. With fixes, we can achieve **90%+ speedup** (from ~60s+ to <5s total). + +## Critical Fixes (MUST DO) + +### Fix 1: Initialize rootCtx in Tests +**Impact**: Fixes hanging tests ← BLOCKING ISSUE +**Effort**: 5 minutes + +Add to all tests that call `flushToJSONL()` or `autoImportIfNewer()`: + +```go +func TestAutoFlushJSONLContent(t *testing.T) { + // FIX: Initialize rootCtx for flush operations + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + oldRootCtx := rootCtx + rootCtx = ctx + defer func() { rootCtx = oldRootCtx }() + + // rest of test... +} +``` + +**Files affected**: +- TestAutoFlushOnExit +- TestAutoFlushJSONLContent +- TestAutoFlushErrorHandling +- TestAutoImportIfNewer +- TestAutoImportDisabled +- TestAutoImportWithUpdate +- TestAutoImportNoUpdate +- TestAutoImportMergeConflict +- TestAutoImportConflictMarkerFalsePositive +- TestAutoImportClosedAtInvariant + +### Fix 2: Reduce Sleep Durations +**Impact**: Saves ~280ms +**Effort**: 2 minutes + +```go +// BEFORE +time.Sleep(200 * time.Millisecond) + +// AFTER +time.Sleep(20 * time.Millisecond) // 10x faster, still reliable + +// BEFORE +time.Sleep(100 * time.Millisecond) + +// AFTER +time.Sleep(10 * time.Millisecond) // 10x faster +``` + +**Rationale**: We're not testing actual timing, just sequencing. Shorter sleeps work fine. + +## High-Impact Optimizations (RECOMMENDED) + +### Opt 1: Share Test Fixtures +**Impact**: Saves ~1-1.5s +**Effort**: 15 minutes + +Group related tests and reuse DB: + +```go +func TestAutoFlushGroup(t *testing.T) { + // Setup once + tmpDir := t.TempDir() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + rootCtx = ctx + defer func() { rootCtx = nil }() + + // Subtest 1: DirtyMarking (no DB needed!) + t.Run("DirtyMarking", func(t *testing.T) { + autoFlushEnabled = true + isDirty = false + if flushTimer != nil { + flushTimer.Stop() + flushTimer = nil + } + + markDirtyAndScheduleFlush() + + flushMutex.Lock() + dirty := isDirty + hasTimer := flushTimer != nil + flushMutex.Unlock() + + assert(dirty && hasTimer) + }) + + // Subtest 2: Disabled (no DB needed!) + t.Run("Disabled", func(t *testing.T) { + autoFlushEnabled = false + isDirty = false + // ... + }) + + // Shared DB for remaining tests + dbPath := filepath.Join(tmpDir, "shared.db") + testStore := newTestStore(t, dbPath) + store = testStore + storeMutex.Lock() + storeActive = true + storeMutex.Unlock() + defer func() { + storeMutex.Lock() + storeActive = false + storeMutex.Unlock() + }() + + t.Run("OnExit", func(t *testing.T) { + // reuse testStore... + }) + + t.Run("JSONLContent", func(t *testing.T) { + // reuse testStore... + }) +} +``` + +**Reduces DB setups from 14 to ~4-5** + +### Opt 2: Use In-Memory SQLite +**Impact**: Saves ~800ms-1.2s +**Effort**: 10 minutes + +```go +func newFastTestStore(t *testing.T) *sqlite.SQLiteStorage { + t.Helper() + + // Use :memory: for speed (10-20x faster than file-based) + store, err := sqlite.New(context.Background(), ":memory:") + if err != nil { + t.Fatalf("Failed to create in-memory database: %v", err) + } + + ctx := context.Background() + if err := store.SetConfig(ctx, "issue_prefix", "test"); err != nil { + store.Close() + t.Fatalf("Failed to set issue_prefix: %v", err) + } + + t.Cleanup(func() { store.Close() }) + return store +} +``` + +**Use for tests that don't need filesystem integration** + +### Opt 3: Skip TestAutoFlushDebounce +**Impact**: Re-enables skipped test with fix +**Effort**: 5 minutes + +The test is currently skipped (line 93). Fix the config issue: + +```go +func TestAutoFlushDebounce(t *testing.T) { + // REMOVED: t.Skip() + + // FIX: Don't rely on config.Set during test + // Instead, directly manipulate flushDebounce duration via test helper + oldDebounce := flushDebounce + flushDebounce = 20 * time.Millisecond // Fast for testing + defer func() { flushDebounce = oldDebounce }() + + // rest of test... +} +``` + +## Medium-Impact Optimizations (NICE TO HAVE) + +### Opt 4: Parallel Test Execution +**Impact**: 40-60% faster with t.Parallel() +**Effort**: 20 minutes + +**Careful!** Only parallelize tests that don't manipulate global state: +- TestImportOpenToClosedTransition ✓ +- TestImportClosedToOpenTransition ✓ +- (most can't be parallel due to global state) + +```go +func TestImportOpenToClosedTransition(t *testing.T) { + t.Parallel() // Safe - no global state + // ... +} +``` + +### Opt 5: Mock flushToJSONL() for State Tests +**Impact**: Saves ~200ms +**Effort**: 30 minutes + +Tests like `TestAutoFlushDirtyMarking` don't need actual flushing: + +```go +var flushToJSONLFunc = flushToJSONL // Allow mocking + +func TestAutoFlushDirtyMarking(t *testing.T) { + flushToJSONLFunc = func() {} // No-op + defer func() { flushToJSONLFunc = flushToJSONL }() + + // Test just the state management... +} +``` + +## Expected Results + +| Approach | Time Savings | Effort | Recommendation | +|----------|-------------|--------|----------------| +| Fix 1: rootCtx | Unblocks tests | 5 min | **DO NOW** | +| Fix 2: Reduce sleeps | ~280ms | 2 min | **DO NOW** | +| Opt 1: Share fixtures | ~1.2s | 15 min | **DO NOW** | +| Opt 2: In-memory DB | ~1s | 10 min | **RECOMMENDED** | +| Opt 3: Fix debounce test | Enables test | 5 min | **RECOMMENDED** | +| Opt 4: Parallel | ~2s (40%) | 20 min | Nice to have | +| Opt 5: Mock flushToJSONL | ~200ms | 30 min | Optional | + +**Total speedup with Fixes + Opts 1-3: ~2.5-3s (from baseline after fixing hangs)** +**Total effort: ~40 minutes** + +## Implementation Order + +1. **Fix 1** (5 min) - Fixes hanging tests +2. **Fix 2** (2 min) - Quick win +3. **Opt 2** (10 min) - In-memory DBs where possible +4. **Opt 1** (15 min) - Share fixtures +5. **Opt 3** (5 min) - Fix skipped test +6. **Opt 4** (20 min) - Parallelize safe tests (optional) + +## Alternative: Rewrite as Integration Tests + +If tests remain slow after optimizations, consider: +- Move to `cmd/bd/integration_test` directory +- Run with `-short` flag to skip in normal CI +- Keep only smoke tests in main_test.go + +**Trade-off**: Slower tests, but better integration coverage + +## Validation + +After changes, run: +```bash +go test -run "^TestAuto" -count=5 # Should complete in <5s consistently +go test -race -run "^TestAuto" # Verify no race conditions +``` diff --git a/cmd/bd/main_test.go b/cmd/bd/main_test.go index 0f70de3c..f725a3a0 100644 --- a/cmd/bd/main_test.go +++ b/cmd/bd/main_test.go @@ -15,7 +15,6 @@ import ( "testing" "time" - "github.com/steveyegge/beads/internal/config" "github.com/steveyegge/beads/internal/types" ) @@ -89,9 +88,10 @@ func TestAutoFlushDisabled(t *testing.T) { // TestAutoFlushDebounce tests that rapid operations result in a single flush func TestAutoFlushDebounce(t *testing.T) { - // FIXME(bd-159): Test needs fixing - config.Set doesn't override flush-debounce properly - t.Skip("Test needs fixing - config setup issue with flush-debounce") - + // NOTE(bd-159): This test is obsolete - debouncing is now tested in flush_manager_test.go + // The codebase moved from module-level autoFlushEnabled/flushTimer to FlushManager + t.Skip("Test obsolete - debouncing tested in flush_manager_test.go (see bd-159)") + // Create temp directory for test database tmpDir, err := os.MkdirTemp("", "bd-test-autoflush-*") if err != nil { @@ -114,13 +114,6 @@ func TestAutoFlushDebounce(t *testing.T) { storeActive = true storeMutex.Unlock() - // Set short debounce for testing (100ms) via config - // Note: env vars don't work in tests because config is already initialized - // So we'll just wait for the default 5s debounce - origDebounce := config.GetDuration("flush-debounce") - config.Set("flush-debounce", 100*time.Millisecond) - defer config.Set("flush-debounce", origDebounce) - // Reset auto-flush state autoFlushEnabled = true isDirty = false