CI failing on all 3/4 test jobs despite individual tests passing

CI has been broken for a day+ with mysterious test failures. Issue #173 on GitHub tracks this.

## Current Status
- **Lint job**: ✅ PASSING
- **Test (Linux)**: ❌ FAILING (exit code 1)
- **Test (Windows)**: ❌ FAILING (exit code 1)
- **Test Nix Flake**: ❌ FAILING (exit code 1)

## Key Observations
All three failing jobs show identical pattern:
- Individual test output shows PASS for every test
- Final result: `FAIL github.com/steveyegge/beads/cmd/bd`
- Exit code 1 despite no visible test failures
- Last visible test output before failure: "No Reason Issue" test (TestCloseCommand/close_without_reason)

## Investigation So Far
1. All tests appear to pass when examined individually
2. Likely causes:
   - Race detector finding data races during test cleanup (`-race` flag)
   - Panic/error occurring after main tests complete
   - Test harness issue not reporting actual failure
   - Possible regression from PR #203 (dependency_type changes)

## Recent CI Runs
- Run 19015040655 (latest): 3/4 failing
- Multiple recent commits tried to fix Windows/lint issues
- Agent on rrnewton/beads fork attempting fixes (2/4 passing there)

## Next Steps
1. Run tests locally with `-race -v` to see full output
2. Check for unreported test failures or panics
3. Examine test cleanup/teardown code
4. Review recent changes around close command tests
5. Consider if race detector is too sensitive or catching real issues

## Progress Update

### ✅ Fixed (commits 09bd4d3, 21a29bc)
1. **Daemon auto-import** - Always recompute content_hash in importer to avoid stale hashes
2. **TestScripts failures** - Added bd binary to PATH for shell subprocess tests
3. **Test infrastructure** - Added .gitignore to test repos, fixed last_import_time metadata

### ✅ CI Status (Run 19015638968)
- **Test (Linux)**: ✅ SUCCESS - All tests passing
- **Test (Windows)**: ❌ FAILURE - Pre-existing Windows test failures
- **Test Nix Flake**: ❌ FAILURE - Build fails with same test errors
- **Lint**: ❌ FAILURE - Pre-existing issue in migrate.go:647

### ❌ Remaining Issues (not related to original bd-1231)

**Windows failures:**
- TestFindDatabasePathEnvVar
- TestHashIDs_MultiCloneConverge
- TestHashIDs_IdenticalContentDedup
- TestDatabaseReinitialization (5 subtests)
- TestFindBeadsDir_NotFound
- TestMetricsSnapshot/uptime

**Lint failure:**
- cmd/bd/migrate.go:647:37: cleanupWALFiles - result 0 (error) is always nil (unparam)

**Nix failure:**
- Build fails during test phase with same test errors

### Next Steps
1. Investigate Windows-specific test failures
2. Fix linting issue in migrate.go
3. Debug Nix build test failures


bd sync should auto-resolve JSONL merge conflicts by exporting from DB

## Problem

When bd sync encounters a merge conflict in beads.jsonl during git pull/rebase, it fails and tells the user to manually resolve it. The typical resolution is:

```bash
bd export -o .beads/beads.jsonl
git add .beads/beads.jsonl
git rebase --continue
bd sync  # try again
```

This is tedious and error-prone. The user shouldn't need to know these git internals.

## Root Cause

bd sync doesn't handle the case where:
1. Export succeeds → commit created
2. Pull/rebase starts
3. JSONL has merge conflict
4. Process aborts, leaving user in broken rebase state

## Proposed Solution

When bd sync detects a merge conflict in beads.jsonl during pull/rebase:

1. **Auto-detect conflict**: Check if git status shows 'both modified' on beads.jsonl
2. **Auto-resolve**: Run `bd export -o .beads/beads.jsonl` to overwrite with DB state
3. **Mark resolved**: Run `git add .beads/beads.jsonl`
4. **Continue**: Run `git rebase --continue` automatically
5. **Complete sync**: Continue with normal push flow

## Implementation

Location: `cmd/bd/sync.go`

After git pull fails:
```go
if err := runGitPull(); err != nil {
    // Check if it's a rebase conflict
    if isInRebase() && hasJSONLConflict() {
        log.Info("→ Auto-resolving JSONL merge conflict...")

        // Export clean JSONL from DB
        if err := exportJSONL(); err != nil {
            return fmt.Errorf("failed to export for conflict resolution: %w", err)
        }

        // Mark as resolved
        if err := runGitAdd(".beads/beads.jsonl"); err != nil {
            return fmt.Errorf("failed to mark conflict resolved: %w", err)
        }

        // Continue rebase
        if err := runGitRebaseContinue(); err != nil {
            return fmt.Errorf("failed to continue rebase: %w", err)
        }

        log.Info("✓ Auto-resolved JSONL conflict")
    } else {
        return err  // Other error, fail normally
    }
}
```

Helper functions needed:
- `isInRebase() bool` - check if .git/rebase-merge or .git/rebase-apply exists
- `hasJSONLConflict() bool` - check git status for 'both modified' on beads.jsonl
- `runGitRebaseContinue() error` - run git rebase --continue

## Benefits

- Seamless multi-user workflow
- No manual git intervention needed
- Database is always source of truth for local changes
- Matches user mental model: 'bd sync should just work'

## Edge Cases

1. **Other files in conflict**: Only auto-resolve if ONLY beads.jsonl is conflicted
2. **Export fails**: Fall back to manual resolution instructions
3. **Multiple rebases**: Handle case where there are multiple commits being rebased
4. **Nested conflicts**: If rebase --continue hits another conflict, handle it

## Testing

1. Two clones, both create issues
2. Clone A syncs successfully
3. Clone B syncs → should auto-resolve and complete
4. Verify both clones have all issues
5. Verify no merge conflict markers in JSONL

## User Experience

Before:
```
$ bd sync
Error: merge conflict in beads.jsonl
[manual steps required]
```

After:
```
$ bd sync
→ Pulling from remote...
→ Auto-resolving JSONL merge conflict...
✓ Auto-resolved JSONL conflict
→ Pushing to remote...
✓ Sync complete
```

## Related Issues

This is the most common friction point in multi-user beads workflows.


Add circular dependency detection to bd doctor

Added cycle detection as Check #10 in bd doctor command. Uses same recursive CTE query as DetectCycles() to find circular dependencies. Reports error status with count and fix suggestion if cycles found.


Command Set Standardization & Flag Consistency

Comprehensive improvements to bd command set based on 2025 audit findings.

## Background
See docs/command-audit-2025.md for detailed analysis.

## Goals
1. Standardize flag naming and behavior across all commands
2. Add missing flags for feature parity
3. Fix naming confusion
4. Improve consistency in JSON output

## Success Criteria
- All mutating commands support --dry-run (no --preview variants)
- bd update supports label operations
- bd search has filter parity with bd list
- Priority flags accept both int and P0-P4 format everywhere
- JSON output is consistent across all commands


Fix N-way collision convergence

Epic to fix the N-way collision convergence problem documented in n-way-collision-convergence.md.

## Problem Summary
The current collision resolution implementation works correctly for 2-way collisions but does not converge for 3-way (and by extension N-way) collisions. TestThreeCloneCollision demonstrates this with reproducible failures.

## Root Causes Identified
1. Pairwise resolution doesn't scale - each clone makes local decisions without global context
2. DetectCollisions modifies state during detection (line 83-86 in collision.go)
3. No remapping history - can't track transitive remap chains (test-1 → test-2 → test-3)
4. Import-time resolution is too late - happens after git merge

## Solution Architecture
Replace pairwise resolution with deterministic global N-way resolution using:
- Content-addressable identity (content hashing)
- Global collision resolution (sort all versions by hash)
- Read-only detection phase (separate from modification)
- Idempotent imports (content-first matching)

## Success Criteria
- TestThreeCloneCollision passes without skipping
- All clones converge to identical content after final pull
- No data loss (all issues present in all clones)
- Works for N workers (test with 5+ clones)
- Idempotent imports (importing same JSONL multiple times is safe)

## Implementation Phases
See child issues for detailed breakdown of each phase.


Propagate context through command handlers

Thread context from signal-aware parent through all command handlers.

## Context
Part of context propagation work. Builds on bd-rtp (signal-aware contexts).

## Current State
Many command handlers create their own context.Background() locally instead of receiving context from parent.

## Implementation
1. Add context parameter to command handler functions
2. Pass context from cobra command Run/RunE closures
3. Update all storage operations to use propagated context

## Example Pattern
```go
// Before
Run: func(cmd *cobra.Command, args []string) {
    ctx := context.Background()
    store.GetIssues(ctx, ...)
}

// After
Run: func(cmd *cobra.Command, args []string) {
    // ctx comes from parent signal-aware context
    store.GetIssues(ctx, ...)
}
```

## Files to Update
- All cmd/bd/*.go command handlers
- Ensure context flows from main -> cobra -> handlers -> storage

## Benefits
- Commands respect cancellation signals
- Consistent context handling
- Enables timeouts and deadlines

## Acceptance Criteria
- [ ] All command handlers use propagated context
- [ ] No new context.Background() calls in command handlers
- [ ] Context flows from signal handler to storage layer


bd import silently succeeds when database matches working tree but not git HEAD

**Critical**: bd import reports '0 created, 0 updated' when database matches working tree JSONL, even when working tree is ahead of git HEAD. This gives false confidence that everything is synced with the source of truth.

## Reproduction

1. Start with database synced to working tree .beads/issues.jsonl (376 issues)
2. Git HEAD has older version of .beads/issues.jsonl (354 issues)
3. Run: bd import .beads/issues.jsonl
4. Output: 'Import complete: 0 created, 0 updated'

## Problem

User expects 'bd import' after 'git pull' to sync database with committed state, but:
- Command silently succeeds because DB already matches working tree
- No warning that working tree has uncommitted changes
- User falsely believes everything is synced with git
- Violates 'JSONL in git is source of truth' principle

## Expected Behavior

When .beads/issues.jsonl differs from git HEAD, bd import should:
1. Detect uncommitted changes: git diff --quiet HEAD .beads/issues.jsonl
2. Warn user: 'Warning: .beads/issues.jsonl has uncommitted changes (376 lines vs 354 in HEAD)'
3. Clarify status: 'Import complete: 0 created, 0 updated (already synced with working tree)'
4. Recommend: 'Run git diff .beads/issues.jsonl to review uncommitted work'

## Impact

- Users can't trust 'bd import' status messages
- Silent data loss risk if user assumes synced and runs git checkout
- Breaks mental model of 'JSONL in git = source of truth'
- Critical for VC's landing-the-plane workflow


Test dependency parent


Add bd init --contributor wizard

Interactive wizard for OSS contributor setup. Guides user through: fork workflow setup, separate planning repo configuration, auto-detection of fork relationships, examples of common OSS workflows.


Add command injection prevention tests for git rm in merge command

Test coverage gap identified by automated analysis (vc-217).

**Original Issue:** [deleted:bd-da96-baseline-lint]

In cmd/bd/merge.go:121, exec.Command is called with variable fullPath, flagged by gosec G204 for potential command injection.

Add tests covering:
- File paths with shell metacharacters (;, |, &, $, etc.)
- Paths with spaces and special characters
- Paths attempting command injection (e.g., 'file; rm -rf /')
- Validation that fullPath is properly sanitized
- Only valid git-tracked files can be removed

This is a critical security path preventing arbitrary command execution.

_This issue was automatically created by AI test coverage analysis._


Daemon crashes silently when multiple .db files exist in .beads/

When daemon detects multiple .db files (after filtering out .backup and vc.db files), it writes error details to .beads/daemon-error file before exiting.

The error file is checked when:
1. Daemon discovery fails to connect (internal/daemon/discovery.go)
2. Auto-start fails to yield a running daemon (cmd/bd/main.go)
3. Daemon list shows 'daemon not responding' error

This makes the error immediately visible to users without requiring them to check daemon logs.

File created:  cmd/bd/daemon.go (writes daemon-error on multiple .db detection)
Files modified:
- internal/daemon/discovery.go (reads daemon-error and surfaces in DaemonInfo.Error)
- cmd/bd/main.go (displays daemon-error when auto-start fails)

Testing: Create multiple .db files in .beads/, start daemon, verify error file created and shown in bd daemons list

Root cause: Daemon exits with os.Exit(1) when multiple .db files detected (daemon.go:1381), but error only goes to daemon log file. User sees 'daemon not responding' without knowing why.

Current detection:
- daemon.go filters out .backup and vc.db files
- bd doctor detects multiple databases
- Error message tells user to run 'bd init' or manually remove

Problem: Error is not user-visible unless they check daemon logs.

Proposed fix options:
1. Surface the error in 'bd info' and 'bd daemons list' output
2. Add a hint in error messages to run 'bd doctor' when daemon fails
3. Make daemon write error to a .beads/daemon-error file that gets checked
4. Improve 'bd doctor' to run automatically when daemon is unhealthy


Split monolithic sqlite.go into focused files

internal/storage/sqlite/sqlite.go is 1050 lines containing initialization, 20+ CRUD methods, query building, and schema management.

Split into:
- store.go: Store struct & initialization (150 lines)
- bead_queries.go: Bead CRUD (300 lines)
- work_queries.go: Work queries (200 lines)
- stats_queries.go: Statistics (150 lines)
- schema.go: Schema & migrations (150 lines)
- helpers.go: Common utilities (100 lines)

Impact: Impossible to understand at a glance; hard to find specific functionality; high cognitive load

Effort: 6-8 hours


Prefix detection treats embedded hyphens as prefix delimiters

The prefix detection logic in bd import incorrectly identifies issues like 'vc-baseline-test' and 'vc-92cl-gate-test' as having different prefixes ('vc-baseline-' and 'vc-92cl-gate-') instead of recognizing them as having the standard 'vc-' prefix with hyphenated suffixes.

This breaks import with error: 'prefix mismatch detected: database uses vc- but found issues with prefixes: [vc-92cl-gate- (1 issues) vc-baseline- (1 issues)]'

The prefix should be determined by the pattern: prefix is everything up to and including the first hyphen. The suffix can contain hyphens without being treated as part of the prefix.

Example problematic IDs:
- vc-baseline-test (detected as prefix: vc-baseline-)
- vc-92cl-gate-test (detected as prefix: vc-92cl-gate-)
- vc-test (correctly detected as prefix: vc-)

Impact: Users cannot use descriptive multi-part IDs without triggering false prefix mismatch errors.


Issue with labels


MCP server loses workspace context after Amp restart - causes silent failures

**CRITICAL BUG**: The beads MCP server loses workspace context when Amp restarts, leading to silent failures and potential data corruption.

## Reproduction
1. Start Amp with beads MCP server configured
2. Call `mcp__beads__set_context(workspace_root="/path/to/project")`
3. Use MCP tools successfully (e.g., `mcp__beads__show`, `mcp__beads__list`)
4. Restart Amp (new thread/session)
5. Try to use MCP tools without calling `set_context` again
6. **Result**: "Not connected" or "No workspace set" errors

## Impact
- Amp agents silently fail when trying to read/update beads issues
- May attempt to create duplicate issues because they can't see existing ones
- Potential for data corruption if operating on wrong database
- Breaks multi-session workflows
- Creates confusion: CLI works (`./bd`) but MCP tools don't

## Current Workaround
Manually call `mcp__beads__set_context()` at start of every Amp session.

## Root Cause
MCP server is stateful and doesn't persist workspace context across restarts.

## Proposed Fix
**Option 1 (Best)**: Auto-detect workspace from current working directory
- Match behavior of CLI `./bd` commands
- Check for `.beads/` directory in current dir or parents
- No manual context setting needed

**Option 2**: Persist context in MCP server state file
- Save last workspace_root to `~/.config/beads/mcp_context.json`
- Restore on server startup

**Option 3**: Require explicit context in every MCP call
- Add optional `workspace_root` parameter to all MCP tools
- Fall back to saved context if not provided

Acceptance:
- MCP tools work across Amp restarts without manual set_context()
- Auto-detection matches CLI behavior (walks up from CWD)
- Clear error message when no workspace found
- set_context() still works for explicit override
- BEADS_WORKING_DIR env var support
- Integration test validates restart behavior


Add edge case tests for export metadata updates

## Current Coverage
TestExportUpdatesMetadata tests basic happy path only.

## Missing Test Cases

### 1. Multi-Repo Mode
- Export with multi-repo configuration
- Verify metadata updated for ALL JSONL files
- Critical gap (related to bd-ar2.2)

### 2. computeJSONLHash Failure
- JSONL file deleted/inaccessible after export
- Should log warning, not crash
- Verify behavior on next export

### 3. SetMetadata Failures
- Database write-protected
- Disk full
- Should log warnings, continue

### 4. os.Stat Failure
- JSONL file deleted after export, before stat
- Should handle gracefully (mtime just not updated)

### 5. Concurrent Exports
- Two daemon instances export simultaneously
- Verify metadata doesn't get corrupted
- Race condition test

### 6. Clock Skew
- System time goes backward between exports
- Verify mtime handling still works

### 7. Partial Export Failure
- Multi-repo mode: some exports succeed, some fail
- What metadata should be updated?

### 8. Large JSONL Files
- Performance test with large files (10k+ issues)
- Verify hash computation doesn't timeout

## Files
- cmd/bd/daemon_sync_test.go

## Implementation Priority
1. Multi-repo test (P1 - related to critical issue)
2. Failure handling tests (P2)
3. Performance/edge cases (P3)


Add optional post-merge git hook example for bd sync

Create example git hook that auto-runs bd sync after git pull/merge.

Add to examples/git-hooks/:
- post-merge hook that checks if .beads/issues.jsonl changed
- If changed: run `bd sync` automatically
- Make it optional/documented (not auto-installed)

Benefits:
- Zero-friction sync after git pull
- Complements auto-detection as belt-and-suspenders

Note: post-merge hook already exists for pre-commit/post-merge. Extend it to support sync.


Add label operations to bd update command

Add --add-label and --remove-label flags to bd update.

**Implementation:**
- Add flags: --add-label, --remove-label, --set-labels
- Support multiple labels (repeatable flags)
- Test with daemon and direct mode

**Files to modify:**
- cmd/bd/show.go (updateCmd definition)
- Add RPC support in internal/rpc/protocol.go and server

**Testing:**
- Add/remove single label
- Add/remove multiple labels
- Set labels (replace all)
- JSON output
- Daemon mode vs direct mode

Architecture review complete:
- Current: separate 'bd label add/remove' commands exist
- UpdateArgs struct in protocol.go needs new fields: AddLabels, RemoveLabels, SetLabels
- RPC server needs label operation support in Update handler
- CLI flags needed: --add-label, --remove-label, --set-labels (repeatable)
- Both daemon and direct mode need implementation
- Tests needed for all modes

This is a moderate-sized feature requiring changes across:
1. cmd/bd/show.go (updateCmd flags and logic)
2. internal/rpc/protocol.go (UpdateArgs struct)
3. internal/rpc/server_issues_epics.go (Update handler)
4. Tests for daemon/direct modes


Override test

## Overview

[Describe the high-level goal and scope of this epic]

## Success Criteria

- [ ] Criteria 1
- [ ] Criteria 2
- [ ] Criteria 3

## Background

[Provide context and motivation]

## Scope

**In Scope:**
- Item 1
- Item 2

**Out of Scope:**
- Item 1
- Item 2


Add upgrade notification system to bd ready/list


Document Claude Code for Web SessionStart hook

Create documentation for using bd in Claude Code for Web:

## Documentation locations
- README.md - Add Claude Code for Web section
- Create docs/CLAUDE_CODE_WEB.md with detailed instructions

## SessionStart hook example
```json
{
  "sessionStart": {
    "script": "npm install -g @beads/bd && bd init --quiet --prefix bd || true"
  }
}
```

## Documentation should cover
- How to configure SessionStart hook in .claude/settings.json
- Verification: Check bd is installed (bd --version)
- Basic workflow in Claude Code for Web
- Troubleshooting common issues
- Note about network restrictions and why npm approach works

## Examples
- Creating issues in web sandbox
- Syncing with git in web environment
- Using MCP server (if applicable)


bd doctor doesn't detect version mismatches or stale daemons


Enforce daemon singleton per workspace with file locking

Agent in ~/src/wyvern discovered 4 simultaneous daemon processes running, causing infinite directory recursion (.beads/.beads/.beads/...). Each daemon used relative paths and created nested .beads/ directories.

Root cause: No singleton enforcement. Multiple `bd daemon` processes can start in same workspace.

Expected: One daemon per workspace (each workspace = separate .beads/ dir with bd.sock)
Actual: Multiple daemons can run simultaneously in same workspace

Note: Separate git clones = separate workspaces = separate daemons (correct). Git worktrees share .beads/ and have known limitations (documented, use --no-daemon).

## Fix Summary

Successfully prevented the nested .beads/.beads/ recursion bug by implementing two safeguards:

1. **Path Canonicalization in FindDatabasePath()** (beads.go):
   - Added filepath.Abs() + filepath.EvalSymlinks() to normalize all database paths
   - Prevents relative path edge cases that create nested directories
   - Ensures all daemons see the same canonical path

2. **Nested Directory Detection** (daemon_lifecycle.go):
   - Added explicit check for ".beads/.beads" pattern in setupDaemonLock()
   - Fails fast with clear error message if nested structure detected
   - Provides user hints about proper usage

## Root Cause

The daemon lock (added Oct 22, 2025) correctly prevents simultaneous daemons in the SAME workspace. However, when BEADS_DB used a relative path (e.g., ".beads/beads.db") from inside the .beads directory, FindDatabasePath() would resolve it to a nested path creating a separate workspace:
- First daemon: /workspace/.beads/beads.db
- Second daemon from .beads/: /workspace/.beads/.beads/beads.db ← Different lock file!

## Testing

All acceptance criteria passed:
✅ 1. Second daemon start fails with "daemon already running" error
✅ 2. Killing daemon releases lock, new daemon can start
✅ 3. No infinite .beads/ recursion possible (tested nested BEADS_DB path)
✅ 4. Works with auto-start mechanism

The fix addresses the edge case while maintaining the existing lock mechanism's correctness.


Auto-repair stale merge driver configs with invalid placeholders

Old bd versions (<0.24.0) installed merge driver with invalid placeholders %L %R instead of %A %B. Add detection to bd doctor --fix: check if git config merge.beads.driver contains %L or %R, auto-repair to 'bd merge %A %O %A %B'. One-time migration for users who initialized with old versions.


GH#243: Recurring stale daemon.lock causes 5s delays

User reports daemon.lock keeps becoming stale after running Claude with beads.

Symptom:
- bd ready takes 5 seconds (exact)
- daemon.lock exists but socket is missing
- bd daemons killall temporarily fixes it
- Problem recurs after using beads with AI agents

User on v0.22.0, Macbook M2, 132 issues (89 closed)

Hypothesis: Daemon is crashing or exiting uncleanly during agent sessions, leaving stale lock file.

Need to:
1. Add crash logging to daemon to understand why it's exiting
2. Improve cleanup on daemon exit (ensure lock is always removed)
3. Add automatic stale lock detection/cleanup
4. Consider making daemon more resilient to crashes

Oracle analysis complete. Converting to epic with 5 focused sub-issues:
1. RPC fast-fail with socket stat + short timeouts (P0)
2. Standardize daemon detection with lock probe (P1)
3. Crash recovery improvements (P2)
4. Self-heal stale artifacts (P2)
5. Diagnostics and debugging (P3)


Handle unchecked errors (errcheck - 683 issues)

683 unchecked error returns, mostly in tests (Close, Rollback, RemoveAll). Many already excluded in config but still showing up.

Fixed all errcheck warnings in production code:
- Enabled errcheck linter (was disabled)
- Set tests: false in .golangci.yml to focus on production code
- Fixed 27 total errors in production code using Oracle guidance:
  * Database patterns: defer func() { _ = rows.Close() }() and defer func() { _ = tx.Rollback() }()
  * Best-effort closers: _ = store.Close(), _ = client.Close()
  * Proper error handling for file writes, fmt.Scanln(), os.Remove()
- All tests pass
- Only 2 "unused" linter warnings remain (not errcheck)


Add tests for Integration Layer Implementation

While implementing bd-wfmw, noticed missing tests

Reviewed existing coverage:
- Basic test coverage exists in lib/test_beads_mail_adapter.py
- Integration tests cover failure scenarios in tests/integration/test_mail_failures.py
- Good coverage of: enabled/disabled modes, graceful degradation, 409 conflicts, HTTP errors, config
- Missing: authorization headers detail, request body structure validation, concurrent reservation timing, TTL edge cases


Add sync.branch configuration

Add configuration layer to support sync.branch setting via config file, environment variable, or CLI flag.

Tasks:
- Add sync.branch field to config schema
- Add BEADS_SYNC_BRANCH environment variable
- Add --branch flag to bd init
- Add bd config get/set sync.branch commands
- Validation (branch name format, conflicts)
- Config migration for existing users

Estimated effort: 1-2 days


Extract normalizeLabels to shared utility package

The `normalizeLabels` function appears in multiple locations with identical implementation. Extract to a shared utility package.

Current locations:
- `internal/rpc/server.go:37` (53 lines) - full implementation
- `cmd/bd/list.go:50-52` - uses the server version (needs to use new shared version)

Function purpose:
- Trims whitespace from labels
- Removes empty strings
- Deduplicates labels
- Preserves order

Target structure:
```
internal/util/
├── strings.go         # String utilities
    └── NormalizeLabels([]string) []string
```

Impact: DRY principle, single source of truth, easier to test


Use stable repo identifiers instead of full paths in metadata keys

## Problem
bd-ar2.2 uses full absolute paths as metadata key suffixes:
```go
updateExportMetadata(exportCtx, store, path, log, path)
// Creates: last_import_hash:/Users/stevey/src/cino/beads/repo1/.beads/issues.jsonl
```

## Issues
1. Absolute paths differ across machines/clones
2. Keys are very long
3. If user moves repo or clones to different path, metadata becomes orphaned
4. Metadata won't be portable across team members

## Better Approach
Use source_repo identifiers from multi-repo config (e.g., ".", "../frontend"):
```go
// Get mapping of jsonl_path -> source_repo identifier
for _, path := range multiRepoPaths {
    repoKey := getRepoKeyForPath(path)  // e.g., ".", "../frontend"
    updateExportMetadata(exportCtx, store, path, log, repoKey)
}
```

This creates stable keys like:
- `last_import_hash:.`
- `last_import_hash:../frontend`

## Related
Depends on bd-ar2.10 (hasJSONLChanged update)


Updated title


DetectCycles SQL query has bug preventing cycle detection

The DetectCycles function's SQL query has a bug in the LIKE filter that prevents it from detecting cycles.

Current code (line 571):
```sql
AND p.path NOT LIKE '%' || d.depends_on_id || '→%'
```

This prevents ANY revisit to nodes, including returning to the start node to complete a cycle.

Fix:
```sql
AND (d.depends_on_id = p.start_id OR p.path NOT LIKE '%' || d.depends_on_id || '→%')
```

This allows revisiting the start node (to detect the cycle) while still preventing intermediate node revisits.

Impact: Currently DetectCycles cannot detect any cycles, but this hasn't been noticed because AddDependency prevents cycles from being created. The function would only matter if cycles were manually inserted into the database.


Test P1 priority


Latency test manual


Make DetectCollisions read-only (separate detection from modification)

## Overview
Phase 2: Separate collision detection from state modification to enable safe, composable collision resolution.

## Current Problem
DetectCollisions (collision.go:38-111) modifies database state during detection:
- Line 83-86: Deletes issues when content matches but ID differs
- This violates separation of concerns
- Causes race conditions when processing multiple issues
- Makes contentToDBIssue map stale after first deletion
- Partial failures leave DB in inconsistent state

## Solution
Make DetectCollisions purely read-only. Move all modifications to a separate ApplyCollisionResolution function.

## Implementation Tasks

### 1. Add RenameDetail to CollisionResult
File: internal/storage/sqlite/collision.go
```go
type CollisionResult struct {
    ExactMatches []string
    Collisions   []*CollisionDetail
    NewIssues    []string
    Renames      []*RenameDetail  // NEW
}

type RenameDetail struct {
    OldID string        // ID in database
    NewID string        // ID in incoming
    Issue *types.Issue  // The issue with new ID
}
```

### 2. Remove deletion from DetectCollisions
Replace lines 83-86:
```go
// OLD (DELETE THIS):
if err := s.DeleteIssue(ctx, dbMatch.ID); err != nil {
    return nil, fmt.Errorf("failed to delete renamed issue...")
}

// NEW (ADD THIS):
result.Renames = append(result.Renames, &RenameDetail{
    OldID: dbMatch.ID,
    NewID: incoming.ID,
    Issue: incoming,
})
continue  // Don't mark as NewIssue yet
```

### 3. Create ApplyCollisionResolution function
New function to apply all modifications atomically:
```go
func ApplyCollisionResolution(ctx context.Context, s *SQLiteStorage,
    result *CollisionResult, mapping map[string]string) error {

    // Phase 1: Handle renames (delete old IDs)
    for _, rename := range result.Renames {
        if err := s.DeleteIssue(ctx, rename.OldID); err != nil {
            return fmt.Errorf("failed to delete renamed issue %s: %w",
                rename.OldID, err)
        }
    }

    // Phase 2: Create new IDs (from mapping)
    // Phase 3: Update references
    return nil
}
```

### 4. Update callers to use two-phase approach
File: internal/importer/importer.go (handleCollisions)
```go
// Phase 1: Detect (read-only)
collisionResult, err := sqlite.DetectCollisions(ctx, sqliteStore, issues)

// Phase 2: Resolve (compute mapping)
mapping, err := sqlite.ResolveNWayCollisions(ctx, sqliteStore, collisionResult)

// Phase 3: Apply (modify DB)
err = sqlite.ApplyCollisionResolution(ctx, sqliteStore, collisionResult, mapping)
```

### 5. Update tests
- Verify DetectCollisions doesn't modify DB
- Test ApplyCollisionResolution separately
- Add test for rename detection without modification

## Acceptance Criteria
- DetectCollisions performs zero writes to database
- DetectCollisions returns RenameDetail entries for content matches
- ApplyCollisionResolution handles all modifications
- All existing tests still pass
- New test verifies read-only detection
- contentToDBIssue map stays consistent throughout detection

## Files to Modify
- internal/storage/sqlite/collision.go (DetectCollisions, new function)
- internal/importer/importer.go (handleCollisions caller)
- internal/storage/sqlite/collision_test.go (add tests)

## Testing
- Unit test: DetectCollisions with content match doesn't delete DB issue
- Unit test: RenameDetail correctly populated
- Unit test: ApplyCollisionResolution applies renames
- Integration test: Full flow still works end-to-end

## Risk Mitigation
This is a significant refactor of core collision logic. Recommend:
1. Add comprehensive tests before modifying
2. Use feature flag to enable/disable new behavior
3. Test thoroughly with TestTwoCloneCollision first


bd status "Recent Activity" is misleading - should use git history

## Problem

`bd status` shows "Recent Activity (last 7 days)" but the numbers are wrong. It only looks at database timestamps, not git history. Says "141 issues closed in last 7 days" when thousands have actually come and go.

## Issues

1. Only queries database timestamps, not git history
2. 7 days is too long a window
3. Numbers don't reflect actual activity in JSONL git history

## Proposed Fix

Either:
- Query git history of `.beads/beads.jsonl` to get actual activity (last 24-48 hours)
- Remove "Recent Activity" section entirely if not useful
- Make time window configurable and default to 24h

## Example Output (Current)
```
Recent Activity (last 7 days):
Issues Created:    174
Issues Closed:     141
Issues Updated:    37
```
This is misleading when thousands of issues have actually cycled through.


Export doesn't update last_import_hash metadata causing perpetual 'JSONL content has changed' errors

After a successful export, the daemon doesn't update last_import_hash or last_import_mtime metadata. This causes hasJSONLChanged to return true on the next export attempt, blocking with 'refusing to export: JSONL content has changed since last import'.

Related to GH #334.

Scenario:
1. Daemon exports successfully → JSONL updated, hash changes
2. Metadata NOT updated (last_import_hash still points to old hash)
3. Next mutation triggers export
4. validatePreExport calls hasJSONLChanged
5. hasJSONLChanged sees current JSONL hash != last_import_hash
6. Export blocked with 'JSONL content has changed since last import'
7. Issue stuck: can't export, can't make progress

This creates a catch-22 where the system thinks JSONL changed externally when it was the daemon itself that changed it.

Current behavior:
- Import updates metadata (import.go:310-336)
- Export does NOT update metadata (daemon_sync.go:307)
- Result: metadata becomes stale after every export


Fix Windows test failure: File permissions in TestAtomicWriteFile

On Windows, file permissions work differently. The test expects 0600 but gets 0666. Windows doesn't support Unix-style file permissions in the same way. Need to either skip this check on Windows or adjust expectations.

Test failure: utils_test.go:39: file permissions mismatch: got 666, want 600

Root cause: Windows filesystem doesn't support Unix-style permission bits. Need to use runtime.GOOS check or skip permission validation on Windows.


Issue 2


bd doctor should detect and fix missing sync.branch config

## Problem

Existing beads repositories initialized before commit a4c38d5 (bd-flil fix) don't have sync.branch configured. This causes 'bd sync --status' to fail with a confusing error.

## Root Cause

- bd-flil (commit a4c38d5) added auto-configuration of sync.branch during 'bd init'
- But existing repos don't get this config migrated
- There's no migration/upgrade path for legacy repos

## Impact

Users get this error even though their repo has been working fine:
```
Error: sync.branch not configured (run 'bd config set sync.branch <branch-name>')
```

## Solution

Add to 'bd doctor' (or create 'bd migrate' command):
1. Detect when sync.branch is missing
2. Auto-set it to current branch (via git symbolic-ref --short HEAD)
3. Warn user that it was auto-configured

## Workaround

Manually run: `bd config set sync.branch main` (or whatever your main branch is)


Add merge helper commands (bd sync --merge)

Add commands to merge beads branch back to main.

Tasks:
- Implement bd sync --merge command
- Implement bd sync --status command
- Implement bd sync --auto-merge (optional, for automation)
- Detect merge conflicts and provide guidance
- Show commit diff between branches
- Verify main branch is clean before merge
- Push merged changes to remote

Estimated effort: 2-3 days


Document code review findings for bd-0a43 refactoring

Code review completed successfully with minor suggestions for future improvements


Improve CheckStaleness error handling

## Problem

CheckStaleness returns 'false' (not stale) for multiple error conditions instead of returning errors. This masks problems.

**Location:** internal/autoimport/autoimport.go:253-285

## Edge Cases That Return False

1. **Invalid last_import_time format** (line 259-262)
   - Corrupted metadata returns 'not stale'
   - Could show stale data

2. **No JSONL file found** (line 267-277)
   - If glob fails, falls back to 'issues.jsonl'
   - If that's empty, returns 'not stale'

3. **JSONL stat fails** (line 279-282)
   - Permission denied, file missing
   - Returns 'not stale' even though can't verify

## Current Code

```go
lastImportTime, err := time.Parse(time.RFC3339, lastImportStr)
if err \!= nil {
    return false, nil  // ← Should return error
}

// ...

if jsonlPath == "" {
    return false, nil  // ← Should return error
}

stat, err := os.Stat(jsonlPath)
if err \!= nil {
    return false, nil  // ← Should return error
}
```

## Fix

```go
lastImportTime, err := time.Parse(time.RFC3339, lastImportStr)
if err \!= nil {
    return false, fmt.Errorf("corrupted last_import_time: %w", err)
}

// ...

if jsonlPath == "" {
    return false, fmt.Errorf("no JSONL file found")
}

stat, err := os.Stat(jsonlPath)
if err \!= nil {
    return false, fmt.Errorf("cannot stat JSONL: %w", err)
}
```

## Impact
Medium - edge cases are rare but should be handled

## Effort
30 minutes - requires updating callers in RPC server

## Dependencies
Requires: bd-n4td (warning on errors)


Investigate and upgrade to modernc.org/sqlite 1.39.1+

We had to pin modernc.org/sqlite to v1.38.2 due to a FOREIGN KEY constraint regression in v1.39.1 (SQLite 3.50.4).

**Issue:** [deleted:bd-cb64c226.2], GH #144

**Symptom:** CloseIssue fails with "FOREIGN KEY constraint failed (787)" when called via MCP/daemon, but works fine via CLI.

**Root Cause:** Unknown - likely stricter FK enforcement in SQLite 3.50.4 or modernc.org wrapper changes.

**Workaround:** Pinned to v1.38.2 (SQLite 3.49.x)

**TODO:**
1. Monitor modernc.org/sqlite releases for fixes
2. Check SQLite 3.50.5+ changelogs for FK-related fixes
3. Investigate why daemon mode fails but CLI succeeds (connection reuse? transaction isolation?)
4. Consider filing upstream issue with reproducible test case
5. Upgrade when safe


Update documentation for merge driver auto-config

Update documentation to reflect the new merge driver auto-configuration during `bd init`.

**Files to update:**
- README.md - Mention merge driver setup in initialization section
- AGENTS.md - Update onboarding section about merge driver
- Possibly QUICKSTART.md

**Content:**
- Explain what the merge driver does
- Show --skip-merge-driver flag usage
- Manual installation steps for post-init setup


Standardize daemon detection: use tryDaemonLock probe before RPC

Before attempting RPC connection, call tryDaemonLock() to check if lock is held:
- If lock NOT held: skip RPC attempt (no daemon running)
- If lock IS held: proceed with RPC + short timeout

This is extremely cheap and eliminates unnecessary connection attempts.

Apply across all client entry points that probe for daemon.


Build Node.js CLI wrapper for WASM

Create npm package that wraps bd.wasm. Child of epic bd-44d0.

## Tasks
- [ ] Set up npm package structure (package.json)
- [ ] Implement CLI argument parsing
- [ ] Load and execute WASM module
- [ ] Handle stdout/stderr correctly
- [ ] Support --json flag for all commands
- [ ] Add bd-wasm bin script

## Success Criteria
- bd-wasm ready --json works identically to bd
- All core commands supported


Unit tests for FileWatcher

Test watcher detects JSONL changes. Test git ref changes trigger import. Test debounce integration. Test watcher recovery from file removal/rename.


bd detect-pollution - Test pollution detector

Detect test issues that leaked into production DB.

Pattern matching for:
- Titles starting with 'test', 'benchmark', 'sample'
- Sequential numbering (test-1, test-2)
- Generic descriptions
- Created in rapid succession

Optional AI scoring for confidence.

Files: cmd/bd/detect_pollution.go (new)


Implement topological sort for import ordering

Refactor upsertIssues() to sort issues by hierarchy depth before batch creation. Ensures parents are created before children, fixing latent bug where parent-child pairs in same batch can fail if ordered wrong. Sort by dot count, create in depth-order batches (0→1→2→3).


Implement signal-aware context in CLI commands

Replace context.Background() with signal.NotifyContext() to enable graceful cancellation.

## Context
Part of context propagation work (bd-350). Phase 1 infrastructure is complete - sqlite.New() now accepts context parameter.

## Implementation
Set up signal-aware context at the CLI entry points:

```go
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
```

## Key Locations
- cmd/bd/main.go:438 (marked with TODO(bd-350))
- cmd/bd/daemon.go:317 (marked with TODO(bd-350))
- Other command entry points

## Benefits
- Ctrl+C cancels ongoing database operations
- Graceful shutdown on SIGTERM
- Better user experience during long operations

## Acceptance Criteria
- [ ] Ctrl+C during import cancels operation cleanly
- [ ] Ctrl+C during export cancels operation cleanly
- [ ] No database corruption on cancellation
- [ ] Proper cleanup on signal (defers execute)


Remove obsolete renumber.go command (hash IDs eliminated need)


URGENT: main branch has failing tests (syncbranch migration error)

The main branch has failing tests that are blocking CI for all PRs.

## Problem
All syncbranch_test.go tests failing with:
"migration external_ref_column failed: failed to create index on external_ref: sqlite3: SQL logic error: no such table: main.issues"

## Evidence
- Last 5 CI runs on main: ALL FAILED
- Tests fail locally on current main (bd6dca5)
- Affects: TestGet, TestSet, TestUnset in internal/syncbranch

## Impact
- Blocking all PR merges
- CI shows red for all branches
- Can't trust test results

## Root Cause
Migration order issue - trying to create index on external_ref column before the issues table exists, or before the external_ref column is added to the issues table.

## Quick Fix Needed
Need to investigate migration order in internal/storage/sqlite/migrations.go and ensure:
1. issues table is created first
2. external_ref column is added to issues table
3. THEN index on external_ref is created

This is CRITICAL - main should never have breaking tests.


Test label update feature


Fix N+1 query pattern in export operations (GetLabels/GetIssueComments)

**Location:** internal/rpc/server_export_import_auto.go:64-84

**Issue:** Export operations call GetLabels() and GetIssueComments() in a loop for each issue, creating N+1 query pattern. For 100 issues this creates 201 queries instead of ~3-5.

**Current Code:**
```go
for _, issue := range issues {
    labels, err := store.GetLabels(ctx, issue.ID)  // 1 query per issue!
    issue.Labels = labels
}
for _, issue := range issues {
    comments, err := store.GetIssueComments(ctx, issue.ID)  // 1 query per issue!
    issue.Comments = comments
}
```

**Fix:** Implement batch query methods like GetLabelsForIssues() and GetCommentsForIssues() that load all data in 1-2 queries total.

**Impact:** Performance degradation on large exports. This is already done correctly for dependencies.


bd message: Add HTTP client timeout to prevent hangs

HTTP client in `sendAgentMailRequest` uses default http.Post with no timeout.

**Location:** cmd/bd/message.go:181

**Problem:**
- Can hang indefinitely if server is unresponsive
- No way to cancel stuck requests
- Poor UX in flaky networks

**Fix:**
```go
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Post(url, "application/json", bytes.NewReader(reqBody))
```

**Impact:** Production reliability and security issue


Use -short flag in CI for PR checks

Update CI configuration to use -short flag for PR checks, run full tests nightly.

The slow tests already support testing.Short() and will be skipped.

Expected savings: ~20 seconds for PR checks (fast tests only)

Implementation:
- Update .github/workflows/ci.yml to add -short flag for PR tests
- Create/update nightly workflow for full test runs
- Update README/docs about test strategy

File: .github/workflows/ci.yml:30


Auto-invoke 3-way merge for JSONL conflicts

Currently when git pull encounters merge conflicts in .beads/issues.jsonl, the post-merge hook fails with an error message pointing users to manual resolution or the beads-merge tool.

This is a poor user experience - the conflict detection is working, but we should automatically invoke the advanced 3-way merging instead of just telling users about it.

**Current behavior:**
- Detect conflict markers in JSONL
- Display error with manual resolution options
- Exit with failure

**Desired behavior:**
- Detect conflict markers in JSONL
- Automatically invoke beads-merge 3-way merge
- Only fail if automatic merge cannot resolve the conflicts

**Reference:**
- beads-merge tool: https://github.com/neongreen/mono/tree/main/beads-merge
- Error occurs in post-merge hook during bd sync after git pull


Add 'bd status' command for issue database overview

Implement a bd status command that provides a quick snapshot of the issue database state, similar to how git status shows working tree state.

Expected output: Show summary including counts by state (open, in-progress, blocked, closed), recent activity (last 7 days), and quick overview without needing multiple queries.

Example output showing issue counts, recent activity stats, and pointer to bd list for details.

Proposed options: --all (show all issues), --assigned (show issues assigned to current user), --json (JSON format output)

Use cases: Quick project health check, onboarding for new contributors, integration with shell prompts or CI/CD, daily standup reference


Add mermaid output format for bd dep tree

Add visual dependency graph output using Mermaid format for better visualization of issue relationships.

Example usage:
  bd dep tree --format mermaid <issue-id>
  bd dep tree --format mermaid bd-42 > graph.md

This would output Mermaid syntax that can be rendered in GitHub, documentation sites, or Mermaid live editor.

Implementation notes:
- Add --format flag to dep tree command
- Support 'text' (default) and 'mermaid' formats
- Mermaid graph should show issue IDs, titles, and dependency types
- Consider using flowchart LR or graph TD syntax


bd sync false positive: conflict detection triggers on JSON-encoded angle brackets in issue content

The bd sync --import-only command incorrectly detects conflict markers when issue descriptions contain the text '<<<<<<<' or '>>>>>>>' as legitimate content (e.g., documentation about git conflict markers).

**Reproduction:**
1. Create issue with design field containing: 'Read file, extract <<<<<<< / ======= / >>>>>>> markers'
2. Export to JSONL (gets JSON-encoded as \u003c\u003c\u003c...)
3. Commit and push
4. Pull from remote
5. bd sync --import-only fails with: 'Git conflict markers detected in JSONL file'

**Root cause:**
The conflict detection appears to decode JSON before checking for conflict markers, causing false positives when issue content legitimately contains these strings.

**Expected behavior:**
Conflict detection should only trigger on actual git conflict markers (literal '<<<<<<<' bytes in the raw file), not on JSON-encoded content within issue fields.

**Test case:**
VC project at ~/src/dave/vc has vc-85 'JSONL Conflict Parser' which documents conflict parsing and triggers this bug.

**Suggested fixes:**
1. Only scan for literal '<<<<<<<' bytes (not decoded JSON content)
2. Parse JSONL first and only flag unparseable lines
3. Check git merge state (git status) to confirm actual conflict
4. Add --skip-conflict-check flag for override


Add `bd merge` command wrapping 3-way merge logic

Implement CLI command to invoke beads-merge functionality.

**Interface**:
```bash
bd merge <output> <base> <left> <right>
bd merge --debug <output> <base> <left> <right>
```

**Behavior**:
- Exit code 0 on clean merge
- Exit code 1 if conflicts (write conflict markers)
- Support --debug flag for verbose output
- Match beads-merge's existing behavior

**File**: `cmd/bd/merge.go`


Audit Current Cache Usage

Understand exactly what code depends on the storage cache

Audit complete. See CACHE_AUDIT.md for full findings.

Summary:
- Cache was already removed in commit 322ab63 (2025-10-28)
- server_cache_storage.go deleted (~286 lines)
- All getStorageForRequest calls replaced with s.storage
- All environment variables removed
- MCP multi-repo works via per-project daemon architecture
- All tests updated and passing
- Only stale comment in server.go needed fixing


Add #nosec G304 comments to JSONL file reads in sync.go

sync.go:610 uses os.ReadFile(jsonlPath) without #nosec comment, inconsistent with other JSONL reads that have '// #nosec G304 - controlled path'.

Add comment for consistency with integrity.go:43 and import.go:316.


Fix compact --analyze/--apply error messages to clarify direct mode requirement

**Problem:**
When users run `bd compact --analyze` with daemon running, they get:
```
Error: compact requires SQLite storage
```

This is misleading because they ARE using SQLite (via daemon), but the command needs DIRECT SQLite access.

**Current behavior:**
- Error message suggests they don't have SQLite
- No hint about using --no-daemon flag
- Related to issue #349 item #1

**Proposed fix:**
1. Update error messages in cmd/bd/compact.go lines 106-114 (analyze) and 121-137 (apply)
2. Add explicit hint: "Use --no-daemon flag to bypass daemon"
3. Change SQLite check error from "requires SQLite storage" to "failed to open database in direct mode"

**Files to modify:**
- cmd/bd/compact.go (lines ~106-137)

**Testing:**
- Run with daemon: `bd compact --analyze` should show clear error + hint
- Run with --no-daemon: `bd compact --analyze --no-daemon` should work
- Verify error message is actionable


Fix Windows test failure: Directory permissions in TestEnsureDir

On Windows, directory permissions work differently. The test expects 0755 but gets 0777. Windows doesn't support Unix-style file permissions in the same way. Need to either skip this check on Windows or adjust expectations.

Test failure: utils_test.go:138: directory permissions mismatch: got 777, want 755

Root cause: Windows filesystem doesn't support Unix-style permission bits. Need to use runtime.GOOS check or skip permission validation on Windows.


Issue with desc

This is a description


Test simple issue


Daemon gets stuck when auto-import blocked by git conflicts

CRITICAL: The daemon enters a corrupt state that breaks RPC commands when auto-import is triggered but git pull fails due to uncommitted changes.

Impact: This is a data integrity and usability issue that could cause users to lose trust in Beads. The daemon silently fails for certain commands while appearing healthy.

Reproduction:
1. Make local changes to issues (creates uncommitted .beads/beads.jsonl)
2. Remote has updates (JSONL newer, triggers auto-import)
3. Daemon tries to pull but fails: 'cannot pull with rebase: You have unstaged changes'
4. Daemon enters bad state - 'bd show' and other commands return EOF
5. 'bd list' still works, daemon process is running, no errors logged

Technical details:
- Auto-import check runs in handleRequest() before processing RPC commands
- When import is blocked, it appears to corrupt daemon state
- Likely: deadlock, unclosed transaction, or storage handle corruption
- Panic recovery (server_lifecycle_conn.go:183) didn't catch anything - not a panic

Required fix:
- Auto-import must not block RPC command execution
- Handle git pull failures gracefully without corrupting state
- Consider: skip auto-import if git is dirty, queue import for later, or use separate goroutine
- Add timeout/circuit breaker for import operations
- Log clear warnings when auto-import is skipped

Without this fix, users in collaborative environments will frequently encounter mysterious EOF errors that require daemon restarts.


Add integration tests for adapter library

Test suite for beads_mail_adapter.py covering all scenarios.

Acceptance Criteria:
- Test enabled mode (server available)
- Test disabled mode (server unavailable)
- Test graceful degradation (server dies mid-operation)
- Test reservation conflicts
- Test message sending/receiving
- Mock HTTP server for testing
- 90%+ code coverage

File: lib/test_beads_mail_adapter.py

Test suite completed with 29 comprehensive tests covering:
- Enabled mode (server available): 10 tests
- Disabled mode (server unavailable): 2 tests
- Graceful degradation: 4 tests
- Reservation conflicts: 2 tests
- Configuration: 5 tests
- Health check scenarios: 3 tests
- HTTP error handling: 3 tests

**Performance**: All tests run in 10ms (fast!)

**Coverage highlights**:
✅ Server health checks (ok, degraded, error, timeout)
✅ All API operations (reserve, release, notify, check_inbox, get_reservations)
✅ HTTP errors (404, 409 conflict, 500, 503)
✅ Network errors (timeout, connection refused)
✅ Malformed responses (bad JSON, empty body, plain text errors)
✅ Environment variable configuration
✅ Graceful degradation when server dies mid-operation
✅ Conflict handling with both JSON and plain text errors
✅ Dict wrapper responses ({"messages": [...]} and {"reservations": [...]})
✅ Custom TTL for reservations
✅ Default agent name fallback

No external dependencies, no slow integration tests, just fast unit tests with mocks.


Cache getMultiRepoJSONLPaths() result during sync to avoid redundant calls

From bd-xo6b code review: getMultiRepoJSONLPaths() is called 3x per sync cycle.

**Current behavior:**
daemon_sync.go calls getMultiRepoJSONLPaths() three times per sync:
- Line 505: Snapshot capture before pull
- Line 575: Merge/prune after pull
- Line 613: Base snapshot update after import

**Cost per call:**
- Config lookup (likely cached, but still overhead)
- Path construction: O(N) where N = number of repos
- String allocations: (N + 1) × filepath.Join() calls

**Total per sync:** 3N path constructions + 3 config lookups + 3 slice allocations

**Impact:**
- For N=3 repos: Negligible (< 1ms)
- For N=10 repos: Still minimal
- For N=100+ repos: Wasteful

**Solution:**
Call once at sync start, reuse result:

```go
func createSyncFunc(...) func() {
    return func() {
        // ... existing setup ...

        // Call once at start
        multiRepoPaths := getMultiRepoJSONLPaths()

        // Snapshot capture
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths {
                if err := captureLeftSnapshot(path); err != nil { ... }
            }
        }

        // ... later ...

        // Merge/prune - reuse same paths
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths { ... }
        }

        // ... later ...

        // Base snapshot update - reuse same paths
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths { ... }
        }
    }
}
```

**Files:**
- cmd/bd/daemon_sync.go:449-636 (createSyncFunc)

**Note:** This is a performance optimization, not a correctness fix. Low priority unless multi-repo usage scales significantly.


Batch test 3


Add Agent Mail to QUICKSTART.md

Mention Agent Mail as optional advanced feature in quickstart guide.

File: docs/QUICKSTART.md


Create store.go with Store struct and initialization logic


Windows binaries lack SQLite support (GH #253)

Windows users installing via install.ps1 get "sql: unknown driver sqlite" error. Root cause: GoReleaser was building with CGO_ENABLED=0, which excludes SQLite driver.

Fixed by:
1. Enabling CGO in .goreleaser.yml
2. Installing MinGW cross-compiler in release workflow
3. Splitting builds per platform to set correct CC for Windows

Needs new release to fix for users.


Fix daemon exiting after 5s on macOS due to PID 1 parent monitoring

GitHub issue #278 reports that the daemon exits after <=5 seconds on macOS because it incorrectly treats PID 1 (launchd) as a dead parent.

When the daemon detaches on macOS, it gets reparented to PID 1 (launchd), which is the init process. The checkParentProcessAlive function was incorrectly treating PID 1 as a sign that the parent died.

Fixed by changing the logic to treat PID 1 as a valid parent for detached daemons.


Fix autoimport tests for content-hash collision scoring

## Overview
Three autoimport tests are failing after [deleted:bd-cbed9619.4] because they expect behavior based on the old reference-counting collision resolution, but the system now uses deterministic content-hash scoring.

## Failing Tests
1. `TestAutoImportMultipleCollisionsRemapped` - expects local versions preserved
2. `TestAutoImportAllCollisionsRemapped` - expects local versions preserved
3. `TestAutoImportCollisionRemapMultipleFields` - expects specific collision resolution behavior

## Root Cause
These tests were written when ScoreCollisions used reference counting to determine which version to keep. Now it uses content-hash comparison (introduced in commit 2e87329), which produces different but deterministic results.

## Example
Old behavior: Issue with more references would be kept
New behavior: Issue with lexicographically lower content hash is kept

## Solution
Update each test to:
1. Verify the new content-hash based behavior is correct
2. Check that the remapped issue (not necessarily local/remote) has the expected content
3. Ensure dependencies are preserved on the correct remapped issue

## Acceptance Criteria
- All three autoimport tests pass
- Tests verify content-hash determinism (same collision always resolves the same way)
- Tests check dependency preservation on remapped issues
- Test documentation explains content-hash scoring expectations

## Files to Modify
- `cmd/bd/autoimport_collision_test.go`

## Testing
Run: `go test ./cmd/bd -run "TestAutoImport.*Collision" -v`


Install and test MCP Agent Mail locally

Install MCP Agent Mail on a single development machine and verify basic functionality.

Acceptance Criteria:
- Server installed via one-line installer
- Server running on port 8765
- Can register a project via HTTP
- Can register an agent identity
- Web UI accessible at /mail

Tested local installation. Server runs on port 8765, web UI works. MCP API tool execution has errors - needs debugging. See /tmp/bd-muls-report.md for details.


bd sync --status should not require sync.branch configuration

After a successful 'bd init', running 'bd sync --status' fails with:

Error: sync.branch not configured (run 'bd config set sync.branch <branch-name>')

This is confusing because:
1. bd init completes successfully without setting sync.branch
2. Users expect basic commands to work after init
3. sync.branch is only set if --branch flag is explicitly passed to bd init, or --team wizard is used

The sync.branch config is primarily for protected branch workflows. The --status flag should either:
- Fall back to current branch if sync.branch is not configured
- Not be available unless sync.branch is configured
- Or bd init should set sync.branch to the current branch by default

Root cause: sync.go:720 calls getSyncBranch() which requires sync.branch to be set (sync.go:702-704)


Test issue 1


bd-hv01: Missing cleanup of .merged temp file on failure

Problem: deletion_tracking.go:49 creates tmpMerged file but does not clean up on failure, causing disk space leak and potential interference with subsequent syncs.

Fix: Add defer os.Remove(tmpMerged) after creating temp file path.

Files: cmd/bd/deletion_tracking.go:38-89


Fix failing integration tests in beads-mcp

The `beads-mcp` test suite has failures in `tests/test_bd_client_integration.py` (assertion error in `test_init_creates_beads_directory`) and errors in `tests/test_worktree_separate_dbs.py` (setup failures finding database). These need to be investigated and fixed to ensure a reliable CI baseline.


Test graceful degradation when server unavailable

Verify that agents continue working normally when Agent Mail server is stopped or unreachable.

Acceptance Criteria:
- Agent detects server unavailable on startup
- Logs "falling back to Beads-only mode"
- All bd commands work normally
- Agent can claim issues (no reservations, like today)
- Git sync operates as normal
- No errors or crashes

Success Metric: Zero functional difference when Agent Mail unavailable


Create Docker image for Agent Mail

Containerize Agent Mail server for easy deployment.

Acceptance Criteria:
- Dockerfile with Python 3.14
- Health check endpoint
- Volume mount for storage
- Environment variable configuration
- Multi-arch builds (amd64, arm64)

File: deployment/agent-mail/Dockerfile


Document external_ref in content hash behavior

The content hash includes external_ref, which has implications that should be documented.

Current behavior:
- external_ref is included in content hash calculation (collision.go:158-160)
- Changing external_ref changes content hash
- This means: local issue → add external_ref → different hash

Implications:
- Local issue + external_ref addition = looks like 'new content'
- May not match by content hash in some scenarios
- Generally correct behavior, but subtle

Action items:
- Document in code comments
- Add to ARCHITECTURE.md or similar
- Add test demonstrating this behavior
- Consider if this is desired long-term

Related: bd-1022
Files: internal/storage/sqlite/collision.go:158-160


Support P-prefix priority format (P0-P4) in create and update commands


Default to YES for git hooks and merge driver installation

Currently bd init prompts user to install git hooks and merge driver, but setup is incomplete if user declines. Change to install by default unless --skip-hooks or --skip-merge-driver flags are passed. Better safe defaults. If installation fails, warn user and suggest bd doctor --fix.


Test Epic 2

## Overview

[Describe the high-level goal and scope of this epic]

## Success Criteria

- [ ] Criteria 1
- [ ] Criteria 2
- [ ] Criteria 3

## Background

[Provide context and motivation]

## Scope

**In Scope:**
- Item 1
- Item 2

**Out of Scope:**
- Item 1
- Item 2


bd ready taking 5 seconds with 132 issues (89 closed)

User reports bd ready is annoyingly slow on M2 Mac - 5 seconds for 132 issues (89 closed). Started noticing after hash-based IDs update. Need to investigate performance regression. Reported in GH #243.

Root cause identified: Not a query performance issue, but stale daemon locks causing 5s timeout delays.

Fixed in bd-ndyz (closed) via 5 sub-issues:
- bd-expt: Fast-fail socket checks (200ms timeout)
- bd-wgu4: Lock probe before RPC attempts
- bd-1mzt: Self-heal stale artifacts
- bd-vcg5: Panic recovery + socket cleanup
- bd-j7e2: RPC diagnostics (BD_RPC_DEBUG)

All fixes merged. Ready for v0.22.2 release.


Consider central serialization package for JSON handling

Multiple parts of the codebase handle JSON serialization of issues with slightly different approaches. Consider creating a centralized serialization package to ensure consistency.

Current serialization locations:
- `cmd/bd/export.go` - JSONL export (issues to file)
- `cmd/bd/import.go` - JSONL import (file to issues)
- `internal/rpc/protocol.go` - RPC JSON marshaling
- `internal/storage/memory/memory.go` - In-memory marshaling

Potential benefits:
- Single source of truth for JSON format
- Consistent field naming
- Easier to add new fields
- Centralized validation

Note: This is marked **optional** because:
- Current serialization mostly works
- May not provide enough benefit to justify refactor
- Risk of breaking compatibility

Decision point: Evaluate if benefits outweigh refactoring cost

Impact: TBD based on investigation - may defer to future work


Replace getStorageForRequest with Direct Access

Replace all getStorageForRequest(req) calls with s.storage


Add schema compatibility probe to prevent silent migration failures

Issue #262 revealed a serious bug: migrations may fail silently, causing UNIQUE constraint errors later.

Root cause:
- sqlite.New() runs migrations once on open
- checkVersionMismatch() prints 'database will be upgraded automatically' but only updates metadata
- If migrations fail or daemon runs older version, queries expecting new columns fail with 'no such column'
- Import logic misinterprets this as 'not found' and tries INSERT on existing ID
- Result: UNIQUE constraint failed: issues.id

Fix strategy (minimal):
1. Add schema probe in sqlite.New() after RunMigrations
   - SELECT all expected columns from all tables with LIMIT 0
   - If fails, retry RunMigrations and probe again
   - If still fails, return fatal error with clear message
2. Fix checkVersionMismatch to not claim 'will upgrade' unless probe passes
3. Only update bd_version after successful migration probe
4. Add schema verification before import operations
5. Map 'no such column' errors to clear actionable message

Related: #262


Use in-memory filesystem for test git operations

Use tmpfs/ramdisk for git operations in tests to reduce I/O overhead.

Options:
1. Mount /tmp as tmpfs in CI (GitHub Actions supports this)
2. Use Go's testing.TB.TempDir() which may already use tmpfs on some systems
3. Explicitly create ramdisk for tests on macOS

Expected savings: 20-30% reduction in git operation time


Update documentation for beads-merge integration

Document the integrated merge functionality.

**Updates needed**:
- AGENTS.md: Replace "use external beads-merge" with "bd merge"
- README.md: Add git merge driver section
- TROUBLESHOOTING.md: Update merge conflict resolution
- ADVANCED.md: Document 3-way merge algorithm
- Create CREDITS.md or ATTRIBUTION.md for @neongreen

**Highlight**: Deletion sync fix (bd-hv01)


File change test issue


Investigate database pollution - issue count anomalies

Multiple repos showing inflated issue counts suggesting cross-repo pollution:
- ~/src/dave/beads: 895 issues (675 open) - clearly polluted
- ~/src/stevey/src/beads: 280 issues (expected ~209-220) - possibly polluted

Need to investigate:
1. Source of pollution (multi-repo sync issues?)
2. How many duplicate/foreign issues exist
3. Whether recent sync operations caused cross-contamination
4. How to clean up and prevent future pollution

Investigation findings:

**Root cause identified:**
- NOT cross-repo contamination
- NOT automated test leakage (tests properly use t.TempDir())
- Manual testing during template feature development (Nov 2-4)
- Commit ba325a2: "test issues were accidentally committed during template feature development"

**Database growth timeline:**
- Nov 3: 19 issues (baseline)
- Nov 2-5: +244 issues (massive development spike)
- Nov 6-7: +40 issues (continued growth)
- Current: 291 issues → 270 after cleanup

**Test pollution breakdown:**
- 21 issues matching "Test " prefix pattern
- Most created Nov 2-5 during feature development
- Pollution from manual `./bd create "Test issue"` commands in production workspace
- All automated tests properly isolated with t.TempDir()

**Cleanup completed:**
- Ran scripts/cleanup-test-pollution.sh successfully
- Removed 21 test issues
- Database reduced from 291 → 270 issues (7.2% cleanup)
- JSONL synced to git

**Prevention strategy:**
- Filed follow-up issue for prevention mechanisms
- Script can be deleted once prevention is in place
- Tests are already properly isolated - no code changes needed there


Testing event-driven mode!


SearchIssues N+1 query causes context timeout with GetLabels

scanIssues() calls GetLabels in a loop for every issue, causing N+1 queries and context deadline exceeded errors when used with short timeouts or in-memory databases. This is especially problematic since SearchIssues already supports label filtering via SQL WHERE clauses.


Add tests for merge driver auto-config in bd init

Add comprehensive tests for the merge driver auto-configuration functionality in `bd init`.

**Test cases needed:**
- Auto-install in quiet mode
- Skip with --skip-merge-driver flag
- Detect already-installed merge driver
- Append to existing .gitattributes
- Interactive prompt behavior (if feasible)

**File:** `cmd/bd/init_test.go`


bd-hv01: Race condition with concurrent snapshot operations

## Problem
Snapshot files have no locking. Multiple processes can call captureLeftSnapshot simultaneously:

1. Process A: export → begins snapshot
2. Process B: export → begins snapshot
3. Process A: writes partial left.jsonl
4. Process B: overwrites with its left.jsonl
5. Process A: completes merge with wrong snapshot

## Impact
- Data corruption in multi-process scenarios
- Daemon + manual sync race
- Multiple git clones on same filesystem

## Fix
Use atomic file operations with process-specific temp files:
```go
func captureLeftSnapshot(jsonlPath string) error {
    _, leftPath := getSnapshotPaths(jsonlPath)
    tempPath := fmt.Sprintf("%s.%d.tmp", leftPath, os.Getpid())
    if err := copyFileSnapshot(jsonlPath, tempPath); err != nil {
        return err
    }
    return os.Rename(tempPath, leftPath) // Atomic on POSIX
}
```

## Files Affected
- cmd/bd/deletion_tracking.go:24-29 (captureLeftSnapshot)
- cmd/bd/deletion_tracking.go:31-36 (updateBaseSnapshot)


Ready issue


Use TryResurrectParentChain instead of TryResurrectParent in GetNextChildID

## Context
Current bd-dvd fix uses TryResurrectParent, which only resurrects the immediate parent. For deeply nested hierarchies, this might not be sufficient.

## Example Scenario
Creating child with parent "bd-abc.1.2" where:
- bd-abc exists in DB
- bd-abc.1 was deleted (missing from DB, exists in JSONL)
- bd-abc.1.2 was deleted (missing from DB, exists in JSONL)

Current fix: Only tries to resurrect "bd-abc.1.2", fails because its parent "bd-abc.1" is missing.

## Solution
Replace TryResurrectParent with TryResurrectParentChain:

```go
if count == 0 {
    // Try to resurrect entire parent chain from JSONL history (bd-dvd fix)
    // This handles deeply nested hierarchies where intermediate parents are also missing
    resurrected, err := s.TryResurrectParentChain(ctx, parentID)
    if err != nil {
        return "", fmt.Errorf("failed to resurrect parent chain for %s: %w", parentID, err)
    }
    if !resurrected {
        return "", fmt.Errorf("parent issue %s does not exist and could not be resurrected from JSONL history", parentID)
    }
}
```

## Investigation
- Check if this scenario actually happens in practice
- TryResurrectParentChain already exists (resurrection.go:174-209)
- May be overkill if users always create parents before children

## Files
- internal/storage/sqlite/hash_ids.go

## Testing
Add test case for deeply nested resurrection


Extract database search logic into helper function

The logic for finding a database in a beads directory is duplicated:
- FindDatabasePath() BEADS_DIR section (beads.go:141-169)
- findDatabaseInTree() (beads.go:248-280)

Both implement the same search order:
1. Check config.json first (single source of truth)
2. Fall back to canonical beads.db
3. Search for *.db files, filtering backups and vc.db

Refactoring suggestion:
Extract to a helper function like:
  func findDatabaseInBeadsDir(beadsDir string) string

Benefits:
- Single source of truth for database search logic
- Easier to maintain and update search order
- Reduces code duplication

Related to [deleted:[deleted:[deleted:[deleted:[deleted:bd-e16b]]]]] implementation.


Publish official devcontainer configuration

Devcontainer configuration implemented. Manual testing required in actual devcontainer environment (Codespaces or VSCode Remote Containers). All code changes complete, tests pass, linting clean.


Document blocked_issues_cache architecture and behavior

Add comprehensive documentation about the blocked_issues_cache optimization to help future maintainers understand the design.

## What to document

1. **Architecture overview**
   - Why cache exists (performance: 752ms -> 29ms)
   - When cache is used (GetReadyWork queries)
   - How cache is maintained (invalidation triggers)

2. **Cache invalidation rules**
   - Invalidated on: dependency add/remove (blocks/parent-child only)
   - Invalidated on: any status change
   - Invalidated on: issue close
   - NOT invalidated on: related/discovered-from dependencies

3. **Transaction safety**
   - All invalidations happen within transactions
   - Cache can use tx or direct db connection
   - Rebuild is atomic (DELETE + INSERT in same tx)

4. **Performance characteristics**
   - Full rebuild on every invalidation
   - Rebuild is fast (<50ms on 10K database)
   - Write complexity traded for read speed
   - Dependency changes are rare vs reads

5. **Edge cases**
   - Parent-child transitive blocking
   - Closed issues don't block
   - Foreign key CASCADE handles deletions

## Where to document

Add to these locations:

1. **blocked_cache.go** - Add detailed package comment:
```go
// Package blocked_cache implements the blocked_issues_cache optimization.
//
// Performance: GetReadyWork originally used recursive CTE (752ms on 10K db).
// With cache: Simple NOT EXISTS query (29ms on 10K db).
//
// Cache Maintenance:
// - Rebuild triggered on dependency changes (blocks/parent-child only)
// - Rebuild triggered on status changes
// - Full rebuild (DELETE + INSERT) is fast enough for all cases
// ...
```

2. **ARCHITECTURE.md** or **PERFORMANCE.md** (new file) - High-level overview

3. **ready.go** - Update comment at line 84-85 with more detail

## Related

- [deleted:[deleted:bd-5qim]]: GetReadyWork optimization (implemented)
- bd-13gm: Cache validation tests
- bd-obdc: Cache rebuild command


Test child daemon mode


Create cmd/bd/daemon_watcher.go (~150 LOC)

Implement FileWatcher using fsnotify to watch JSONL file and git refs. Handle platform differences (inotify/FSEvents/ReadDirectoryChangesW). Include edge case handling for file rename, event storm, watcher failure.


Test database naming


Workaround for FastMCP outputSchema bug in Claude Code

The beads MCP server (v0.23.1) successfully connects to Claude Code, but all tools fail to load with a schema validation error due to a bug in FastMCP 2.13.1.

Error: "Invalid literal value, expected \"object\"" in outputSchema.

Root Cause: FastMCP generates outputSchema with $ref at root level without "type": "object" for self-referential models (Issue).

Workaround: Use slash commands (/beads:ready) or wait for FastMCP fix.


Add MCP server functions for repair commands

Expose new repair commands via MCP server for agent access:

Functions to add:
- beads_repair_deps()
- beads_detect_pollution()
- beads_validate()
- beads_resolve_conflicts() (when implemented)

Update integrations/beads-mcp/src/beads_mcp/server.py

See repair_commands.md lines 803-884 for design.

Implemented all three MCP server functions:

1. **repair_deps(fix=False)** - Find/fix orphaned dependencies
2. **detect_pollution(clean=False)** - Detect/clean test issues
3. **validate(checks=None, fix_all=False)** - Run comprehensive health checks

Changes:
- Added abstract methods to BdClientBase
- Implemented in BdCliClient (CLI execution)
- Added NotImplementedError stubs in BdDaemonClient (falls back to CLI)
- Created wrapper functions in tools.py
- Registered @mcp.tool decorators in server.py

All commands tested and working with --no-daemon flag.


Add error handling guidance for export metadata update failures

## Problem
The bd-ymj fix treats all metadata update failures as warnings:

```go
if err := store.SetMetadata(ctx, "last_import_hash", currentHash); err != nil {
    log.log("Warning: failed to update last_import_hash: %v", err)
}
```

But if metadata updates fail, the NEXT export will fail with "JSONL content has changed since last import".

## Questions
1. Should metadata failures be critical (return error)?
2. Should we implement retry logic?
3. Is warning acceptable (safe, just requires import before next export)?

## Current Behavior
- Metadata failure is silent (only logged)
- User won't know until next export fails
- Next export requires import first (safe but annoying)

## Recommendation
Add clear comment explaining the trade-off:

```go
// Note: Metadata update failures are treated as warnings, not errors.
// This is acceptable because the worst case is the next export will
// require an import first, which is safe and prevents data loss.
// Alternative: Make this critical and fail the export if metadata
// updates fail (but this makes exports more fragile).
if err := store.SetMetadata(ctx, "last_import_hash", currentHash); err != nil {
    log.log("Warning: failed to update last_import_hash: %v", err)
    log.log("Next export may require running 'bd import' first")
}
```

## Files
- cmd/bd/daemon_sync.go

## Decision Needed
Choose approach based on failure mode preferences


Code Review Sweep: thorough

Perform thorough code review sweep based on accumulated activity.

**AI Reasoning:**
Significant code activity with 7608 lines added and 120 files changed indicates substantial modifications. Multiple high-churn areas (cmd/bd, internal/rpc) suggest potential for subtle issues and emerging patterns that warrant review.

**Scope:** thorough
**Target Areas:** cmd/bd, internal/rpc, .beads
**Estimated Files:** 12
**Estimated Cost:** $5

**Task:**
Review files for non-obvious issues that agents miss during focused work:
- Inefficiencies (algorithmic, resource usage)
- Subtle bugs (race conditions, off-by-one, copy-paste)
- Poor patterns (coupling, complexity, duplication)
- Missing best practices (error handling, docs, tests)
- Unnamed anti-patterns

File discovered issues with detailed reasoning and suggestions.


Create cmd/bd/daemon_event_loop.go (~200 LOC)

Implement runEventDrivenLoop to replace polling ticker. Coordinate FileWatcher, mutation events, debouncer. Include health check ticker (60s) for daemon validation.


Create multi-agent race condition test

Automated test that runs 2+ agents simultaneously to verify collision prevention.

Acceptance Criteria:
- Script spawns 2 agents in parallel
- Both try to claim same issue
- Only one succeeds (via reservation)
- Other agent skips to different work
- Verify in JSONL that no duplicate claims
- Test with Agent Mail enabled/disabled

File: tests/integration/test_agent_race.py

Success Metric: Zero duplicate claims with Agent Mail, collisions without it


Create shared insert/event/dirty helpers

Create issues.go (insertIssue/insertIssues), events.go (recordCreatedEvent/recordCreatedEvents), dirty.go (markDirty/markDirtyBatch). Refactor single and bulk create paths to use these.


Vendor and integrate beads-merge tool

Incorporate @neongreen's beads-merge 3-way merge tool into bd to solve:
- Multi-workspace deletion sync (bd-hv01)
- Git merge conflicts in JSONL
- Field-level intelligent merging

**Repository**: https://github.com/neongreen/mono/tree/main/beads-merge

**Integration approach**: Vendor the Go code with attribution, pending @neongreen's approval (GitHub issue #240)

**Benefits**:
- Prevents deletion resurrection bug
- Smart dependency merging (union + dedup)
- Timestamp handling (max wins)
- Detects deleted-vs-modified conflicts
- Works as git merge driver

**Acceptance criteria**:
- beads-merge code vendored into bd codebase
- Available as `bd merge` command
- Git merge driver setup during `bd init`
- Tests verify 3-way merge logic
- Documentation updated
- @neongreen credited


Write Agent Mail integration guide

Comprehensive guide for setting up and using Agent Mail with Beads.

Acceptance Criteria:
- Installation instructions
- Configuration (environment variables)
- Architecture diagram
- Benefits and tradeoffs
- When to use vs not use
- Troubleshooting section
- Migration from git-only mode

File: docs/AGENT_MAIL.md

Sections:
- Quick start
- How it works
- Integration points
- Graceful degradation
- Multi-machine deployment
- FAQ


bd resolve-conflicts - Git merge conflict resolver

Automatically resolve JSONL merge conflicts.

Modes:
- Mechanical: ID remapping (no AI)
- AI-assisted: Smart merge/keep decisions
- Interactive: Review each conflict

Handles <<<<<<< conflict markers in .beads/beads.jsonl

Files: cmd/bd/resolve_conflicts.go (new)


Add tests for bd init --team wizard

Write integration tests for the team wizard:
- Test branch detection
- Test sync branch creation
- Test protected branch workflow
- Test auto-sync configuration


Implement clone-scoped ID allocation to prevent N-way collisions

## Problem
Current ID allocation uses per-clone atomic counters (issue_counters table) that sync based on local database state. In N-way collision scenarios:
- Clone B sees {test-1} locally, allocates test-2
- Clone D sees {test-1, test-2, test-3} locally, allocates test-4
- When same content gets assigned test-2 and test-4, convergence fails

Root cause: Each clone independently allocates IDs without global coordination, leading to overlapping assignments for the same content.

## Solution
Add clone UUID to ID allocation to make every ID globally unique:

**Current format:** `test-1`, `test-2`, `test-3`
**New format:** `test-1-a7b3`, `test-2-a7b3`, `test-3-c4d9`

Where suffix is first 4 chars of clone UUID.

## Implementation

### 1. Add clone_identity table
```sql
CREATE TABLE clone_identity (
    clone_uuid TEXT PRIMARY KEY,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```

### 2. Modify getNextIDForPrefix()
```go
func (s *SQLiteStorage) getNextIDForPrefix(ctx context.Context, prefix string) (string, error) {
    cloneUUID := s.getOrCreateCloneUUID(ctx)
    shortUUID := cloneUUID[:4]

    nextNum := s.getNextCounterForPrefix(ctx, prefix)
    return fmt.Sprintf("%s-%d-%s", prefix, nextNum, shortUUID), nil
}
```

### 3. Update ID parsing logic
All places that parse IDs (utils.ExtractIssueNumber, etc.) need to handle new format.

### 4. Migration strategy
- Existing IDs remain unchanged (no suffix)
- New IDs get clone suffix automatically
- Display layer can hide suffix in UI: `bd-cb64c226.3-a7b3` → `#42`

## Benefits
- **Zero collision risk**: Same content in different clones gets different IDs
- **Maintains readability**: Still sequential numbering within clone
- **No coordination needed**: Works offline, no central authority
- **Scales to 100+ clones**: 4-char hex = 65,536 unique clones

## Concerns
- ID format change may break existing integrations
- Need migration path for existing databases
- Display logic needs update to hide/show suffixes appropriately

## Success Criteria
- 10+ clone collision test passes without failures
- Existing issues continue to work (backward compatibility)
- Documentation updated with new ID format
- Migration guide for v1.x → v2.x

## Timeline
Medium-term (v1.1-v1.2), 2-3 weeks implementation

## References
- Related to bd-e6d71828 (immediate fix)
- See beads_nway_test.go for failing N-way tests


Add date and priority filters to bd search

Add filter parity with bd list for consistent querying.

**Missing filters to add:**
- --priority, --priority-min, --priority-max
- --created-after, --created-before
- --updated-after, --updated-before
- --closed-after, --closed-before
- --empty-description, --no-assignee, --no-labels
- --desc-contains, --notes-contains

**Files to modify:**
- cmd/bd/search.go
- internal/rpc/protocol.go (SearchArgs)
- internal/storage/storage.go (Search method)

**Testing:**
- All filter combinations
- Date format parsing
- Daemon and direct mode


Fix GH #227: bd edit broken pipe errors

bd edit command gets "broken pipe" errors when using daemon mode because editing can take minutes and the daemon connection times out.

Solution: Force bd edit to always use direct mode (--no-daemon) since it's human-only and interactive.

Fixed by checking cmd.Name() == "edit" in main.go PersistentPreRun and setting noDaemon = true.


Multi-repo hydration layer

Build core infrastructure to hydrate database from N repos (N≥1), with smart caching via file mtime tracking and routing writes to correct JSONL based on source_repo metadata.


CLI tests (cli_fast_test.go) are slow and should be integration tests

The TestCLI_* tests in cmd/bd/cli_fast_test.go are taking 4-5 seconds each (40+ seconds total), making them the slowest part of the fast test suite.

Current timings:
- TestCLI_Import: 4.73s
- TestCLI_Blocked: 4.33s
- TestCLI_DepTree: 4.15s
- TestCLI_Close: 3.59s
- TestCLI_DepAdd: 3.50s
- etc.

These tests compile the bd binary once in init(), but then execute it multiple times per test with filesystem operations. Despite being named "fast", they're actually end-to-end CLI integration tests.

Options:
1. Tag with //go:build integration (move to integration suite)
2. Optimize: Use in-memory databases, reduce exec calls, better parallelization
3. Keep as-is but understand they're the baseline for "fast" tests

Total test suite currently: 13.8s (cmd/bd alone is 12.8s, and most of that is these CLI tests)

Fixed by reusing existing bd binary from repo root instead of rebuilding.

Before: 15+ minutes (rebuilding binary for every test package)
After: ~12 seconds (reuses pre-built binary)

The init() function now checks for ../../bd first before falling back to building. This means `go build && go test` is now fast.


Test Epic

## Overview

[Describe the high-level goal and scope of this epic]

## Success Criteria

- [ ] Criteria 1
- [ ] Criteria 2
- [ ] Criteria 3

## Background

[Provide context and motivation]

## Scope

**In Scope:**
- Item 1
- Item 2

**Out of Scope:**
- Item 1
- Item 2


Add TestNWayCollision for 5+ clones

## Overview
Add comprehensive tests for N-way (5+) collision resolution to verify the solution scales beyond 3 clones.

## Purpose
While TestThreeCloneCollision validates the basic N-way case, we need to verify:
1. Solution scales to arbitrary N
2. Performance is acceptable with more clones
3. Convergence time is bounded
4. No edge cases in larger collision groups

## Implementation Tasks

### 1. Create TestFiveCloneCollision
File: beads_twoclone_test.go (or new beads_nway_test.go)

```go
func TestFiveCloneCollision(t *testing.T) {
    // Test with 5 clones creating same ID with different content
    // Verify all 5 clones converge after sync rounds

    t.Run("SequentialSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "A", "B", "C", "D", "E")
    })

    t.Run("ReverseSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "E", "D", "C", "B", "A")
    })

    t.Run("RandomSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "C", "A", "E", "B", "D")
    })
}
```

### 2. Implement generalized testNCloneCollision
Generalize the 3-clone test to handle arbitrary N:

```go
func testNCloneCollision(t *testing.T, numClones int, syncOrder ...string) {
    t.Helper()

    if len(syncOrder) != numClones {
        t.Fatalf("syncOrder length (%d) must match numClones (%d)",
            len(syncOrder), numClones)
    }

    tmpDir := t.TempDir()

    // Setup remote and N clones
    remoteDir := setupBareRepo(t, tmpDir)
    cloneDirs := make(map[string]string)

    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        cloneDirs[name] = setupClone(t, tmpDir, remoteDir, name)
    }

    // Each clone creates issue with same ID but different content
    for name, dir := range cloneDirs {
        createIssue(t, dir, fmt.Sprintf("Issue from clone %s", name))
    }

    // Sync in specified order
    for _, name := range syncOrder {
        syncClone(t, cloneDirs[name], name)
    }

    // Final pull for convergence
    for name, dir := range cloneDirs {
        finalPull(t, dir, name)
    }

    // Verify all clones have all N issues
    expectedTitles := make(map[string]bool)
    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        expectedTitles[fmt.Sprintf("Issue from clone %s", name)] = true
    }

    for name, dir := range cloneDirs {
        titles := getTitles(t, dir)
        if !compareTitleSets(titles, expectedTitles) {
            t.Errorf("Clone %s missing issues: expected %v, got %v",
                name, expectedTitles, titles)
        }
    }

    t.Log("✓ All", numClones, "clones converged successfully")
}
```

### 3. Add performance benchmarks
Test convergence time and memory usage:

```go
func BenchmarkNWayCollision(b *testing.B) {
    for _, n := range []int{3, 5, 10, 20} {
        b.Run(fmt.Sprintf("N=%d", n), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                // Run N-way collision and measure time
                testNCloneCollisionBench(b, n)
            }
        })
    }
}
```

### 4. Add convergence time tests
Verify bounded convergence:

```go
func TestConvergenceTime(t *testing.T) {
    // Test that convergence happens within expected rounds
    // For N clones, should converge in at most N-1 sync rounds

    for n := 3; n <= 10; n++ {
        t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
            rounds := measureConvergenceRounds(t, n)
            maxExpected := n - 1
            if rounds > maxExpected {
                t.Errorf("Convergence took %d rounds, expected ≤ %d",
                    rounds, maxExpected)
            }
        })
    }
}
```

### 5. Add edge case tests
Test boundary conditions:
- All N clones have identical content (dedup works)
- N-1 clones have same content, 1 differs
- All N clones have unique content
- Mix of collisions and non-collisions

## Acceptance Criteria
- TestFiveCloneCollision passes with all sync orders
- All 5 clones converge to identical content
- Performance is acceptable (< 5 seconds for 5 clones)
- Convergence time is bounded (≤ N-1 rounds)
- Edge cases handled correctly
- Benchmarks show scalability to 10+ clones

## Files to Create/Modify
- beads_twoclone_test.go or beads_nway_test.go
- Add helper functions for N-clone setup

## Testing Strategy

### Test Matrix
| N Clones | Sync Orders | Expected Result |
|----------|-------------|-----------------|
| 3        | A→B→C       | Pass            |
| 3        | C→B→A       | Pass            |
| 5        | A→B→C→D→E   | Pass            |
| 5        | E→D→C→B→A   | Pass            |
| 5        | Random      | Pass            |
| 10       | Sequential  | Pass            |

### Performance Targets
- 3 clones: < 2 seconds
- 5 clones: < 5 seconds
- 10 clones: < 15 seconds

## Dependencies
- Requires bd-cbed9619.5, bd-cbed9619.4, bd-0dcea000, bd-4d7fca8a to be completed
- TestThreeCloneCollision must pass first

## Success Metrics
- All tests pass for N ∈ {3, 5, 10}
- Convergence time scales linearly (O(N))
- Memory usage reasonable (< 100MB for 10 clones)
- No data corruption or loss in any scenario


Address gosec security warnings (102 issues)

Security linter warnings: file permissions (0755 should be 0750), G304 file inclusion via variable, G204 subprocess launches. Many are false positives but should be reviewed.


bd init UX Improvements

bd init leaves users with incomplete setup, requiring manual bd doctor --fix. Issues found: (1) git hooks not installed if user declines prompt, (2) no auto-migration when CLI is upgraded, (3) stale merge driver configs from old versions. Fix by making bd init more robust with better defaults and auto-migration.


bd message: Refactor duplicated error messages


Add unit tests for parent resurrection

Test resurrection with deleted parent (should succeed), resurrection with never-existed parent (should fail gracefully), multi-level resurrection (bd-abc.1.2 with both parents missing). Verify tombstone creation and is_tombstone flag.


Update daemon commit logic for separate branch

Modify daemon to use worktree for commits when sync.branch configured.

Tasks:
- Update internal/daemon/server_export_import_auto.go
- Detect sync.branch configuration
- Ensure worktree exists before commit
- Sync JSONL to worktree
- Commit in worktree context
- Push to configured branch
- Fallback to current behavior if sync.branch not set
- Handle git errors (network, permissions, conflicts)

Estimated effort: 3-4 days


Integration Testing

Verify cache removal doesn't break any workflows


bd-hv01: Refactor snapshot management into dedicated module

Problem: Snapshot logic is scattered across deletion_tracking.go. Would benefit from abstraction with SnapshotManager type.

Benefits: cleaner separation of concerns, easier to test in isolation, better encapsulation, could add observability/metrics.

Suggested improvements: add magic constants, track merge statistics, better error messages.

Files: cmd/bd/deletion_tracking.go (refactor into new snapshot_manager.go)


Speed up main_test.go - tests hang indefinitely due to nil rootCtx

## Problem

main_test.go tests are hanging indefinitely, making them unusable and blocking development.

## Root Cause

Tests that call flushToJSONL() or autoImportIfNewer() hang forever because:
- rootCtx is nil in test environment (defined in cmd/bd/main.go:67)
- flushToJSONL() uses rootCtx for DB operations (autoflush.go:503)
- When rootCtx is nil, DB calls timeout/hang indefinitely

## Affected Tests (10+)

- TestAutoFlushOnExit
- TestAutoFlushJSONLContent
- TestAutoFlushErrorHandling
- TestAutoImportIfNewer
- TestAutoImportDisabled
- TestAutoImportWithUpdate
- TestAutoImportNoUpdate
- TestAutoImportMergeConflict
- TestAutoImportConflictMarkerFalsePositive
- TestAutoImportClosedAtInvariant

## Proof

```bash
# Before fix: TestAutoFlushJSONLContent HANGS (killed after 2+ min)
# After adding rootCtx init: Completes in 0.04s
# Speedup: ∞ → 0.04s
```

## Solution

See MAIN_TEST_OPTIMIZATION_PLAN.md for complete fix plan.

**Quick Fix (5 min)**: Add to each hanging test:
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
oldRootCtx := rootCtx
rootCtx = ctx
defer func() { rootCtx = oldRootCtx }()
```

**Full Optimization (40 min total)**:
1. Fix rootCtx (5 min) - unblocks tests
2. Reduce sleep durations 10x (2 min) - saves ~280ms
3. Use in-memory DBs (10 min) - saves ~1s
4. Share test fixtures (15 min) - saves ~1.2s
5. Fix skipped debounce test (5 min)

## Expected Results

- Tests go from hanging → <5s total runtime
- Keep critical integration test coverage
- Tests caught real bugs: bd-270, bd-206, bd-160

## Files

- Analysis: MAIN_TEST_REFACTOR_NOTES.md
- Solution: MAIN_TEST_OPTIMIZATION_PLAN.md
- Tests: cmd/bd/main_test.go (18 tests, 1322 LOC)

## Priority

P1 - Blocking development, tests unusable in current state


Update docs for import bug fix

Update AGENTS.md, README.md, TROUBLESHOOTING.md with import.orphan_handling config documentation. Document resurrection behavior, tombstones, config modes. Add troubleshooting section for import failures with deleted parents.


Add multi-repo support to export metadata updates

## Problem
The bd-ymj fix only updates metadata for the main jsonlPath, but the codebase supports multi-repo mode where exports write to multiple JSONL files.

Other daemon_sync operations handle multi-repo correctly:
- Lines 509-518: Snapshots captured for all multi-repo paths
- Lines 578-587: Deletions applied for all multi-repo paths

But metadata updates are missing!

## Investigation Needed
Metadata is stored globally in database (`last_import_hash`, `last_import_mtime`), but multi-repo has per-repo JSONL files. Current schema may not support tracking multiple JSONL files.

Options:
1. Store metadata per-repo (new schema)
2. Store combined hash of all repos
3. Document that multi-repo doesn't need this metadata (why?)

## Solution (if needed)
```go
// After export
if multiRepoPaths := getMultiRepoJSONLPaths(); multiRepoPaths != nil {
    // Multi-repo mode: update metadata for each JSONL
    for _, path := range multiRepoPaths {
        updateExportMetadata(exportCtx, store, path, log)
    }
} else {
    // Single-repo mode: update metadata for main JSONL
    updateExportMetadata(exportCtx, store, jsonlPath, log)
}
```

## Files
- cmd/bd/daemon_sync.go (createExportFunc, createSyncFunc)
- Possibly: internal/storage/sqlite/metadata.go (schema changes)

## Related
Depends on bd-ar2.1 (extract helper function first)


Standardize --dry-run flag across all commands

Replace all instances of --preview with --dry-run for consistency.

**Commands to update:**
- clean: Change --preview to --dry-run

**Commands to verify:**
- cleanup, compact, delete, duplicates, epic close-eligible, import, sync

**Testing:**
- Ensure all mutating commands support --dry-run
- Verify backward compatibility (deprecation warning for --preview?)
- Update tests and documentation

All commands already use --dry-run consistently. Verified:
- cleanup: line 130
- compact: line 873
- delete: line 559
- duplicates: line 158
- import: line 703
- sync: line 333
- epic close-eligible: line 208
- clean: line 162

No --preview flags found in the codebase. This task appears to have been completed before the epic was created.


Add transaction helper to replace manual COMMIT/ROLLBACK

Create tx.go with withTx helper that handles transaction lifecycle. Replace manual transaction blocks in create/insert/update paths.

Refactoring complete:
- Created withTx() helper in util.go
- Added ExecInTransaction() as deprecated wrapper for backward compatibility
- Refactored all manual transaction blocks to use withTx():
  - events.go: AddComment
  - dirty.go: MarkIssuesDirty, ClearDirtyIssuesByID
  - labels.go: executeLabelOperation
  - dependencies.go: AddDependency, RemoveDependency
  - compact.go: ApplyCompaction
- All tests pass successfully


Event-driven daemon architecture

Replace 5-second polling sync loop with event-driven architecture that reacts instantly to changes. Eliminates stale data issues while reducing CPU ~60%. Key components: FileWatcher (fsnotify), Debouncer (500ms), RPC mutation events, optional git hooks. Target latency: <500ms (vs 5000ms). See event_driven_daemon.md for full design.

Production-ready after 3 critical fixes (commit 349b892):
- Skip redundant imports (mtime check prevents self-trigger loops)
- Add server.Stop() in serverErrChan case (clean shutdown)
- Fallback ticker (60s) when watcher unavailable (ensures remote sync)

Ready to make default after integration test (bd-1f4086c5.1) passes.


Test auto-start in fred