Fix Homebrew warning about Ruby file location

Homebrew warning: Found Ruby file outside steveyegge/beads tap formula directory.
Warning points to: /opt/homebrew/Library/Taps/steveyegge/homebrew-beads/bd.rb
It should likely be inside a Formula/ directory or similar structure expected by Homebrew taps.


bd-hv01: Non-atomic snapshot operations can cause data loss

## Problem
In sync.go:146-155 and daemon_sync.go:502-505, snapshot capture failures are logged as warnings but sync continues:

```go
if err := exportToJSONL(ctx, jsonlPath); err != nil { ... }
if err := captureLeftSnapshot(jsonlPath); err != nil {
    fmt.Fprintf(os.Stderr, "Warning: failed to capture snapshot...")
}
```

If export succeeds but snapshot capture fails, the merge uses stale snapshot data, potentially deleting wrong issues.

## Impact
- Critical data integrity issue
- Could delete issues incorrectly during multi-workspace sync

## Fix
Make snapshot capture mandatory:
```go
if err := captureLeftSnapshot(jsonlPath); err != nil {
    return fmt.Errorf("failed to capture snapshot (required for deletion tracking): %w", err)
}
```

## Files Affected
- cmd/bd/sync.go:146-155
- cmd/bd/daemon_sync.go:502-505


Post-merge hook silently fails on JSONL conflicts, poor UX

When git pull results in merge conflicts in .beads/issues.jsonl, the post-merge hook runs 'bd sync --import-only' which fails, but stderr was redirected to /dev/null. User only saw generic warning.

Fixed by capturing and displaying the actual error output, so users see 'Git conflict markers detected' message immediately.


Add richer query capabilities to bd list

Current bd list filters are limited to basic field matching (status, priority, type, assignee, label). This forces users to resort to piping through jq for common queries.

Missing query capabilities:
- Pattern matching: --title-contains, --desc-contains
- Date ranges: --created-after, --updated-before, --closed-after
- Empty/null checks: --empty-description, --no-assignee, --no-labels
- Numeric ranges: --priority-min, --priority-max
- Complex boolean logic: --and, --or operators
- Full-text search: --search across all text fields
- Negation: --not-status, --exclude-label

Example use cases:
- Find issues with empty descriptions
- Find stale issues not updated in 30 days
- Find high-priority bugs with no assignee
- Search for keyword across title/description/notes

Implementation approach:
- Add query builder pattern to storage layer
- Support --query DSL for complex queries
- Keep simple flags for common cases
- Add --json output for programmatic use

## Progress Update

**Completed:**
- ✅ Extended IssueFilter struct with new fields (pattern matching, date ranges, empty/null checks, priority ranges)
- ✅ Updated SQLite SearchIssues implementation
- ✅ Added CLI flags to list.go
- ✅ Added parseTimeFlag helper
- ✅ Comprehensive tests added - all passing

**Remaining:**
- ⚠ RPC layer needs updating (internal/rpc/protocol.go ListArgs)
- ⚠ Daemon handler needs to forward new filters
- ⚠ End-to-end testing with daemon mode
- 📝 Documentation updates

**Files Modified:**
- internal/types/types.go
- internal/storage/sqlite/sqlite.go
- cmd/bd/list.go
- cmd/bd/list_test.go

**Next Steps:**
1. Update RPC protocol
2. Update daemon handler
3. Test with daemon mode
4. Update docs


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)
2. **No JSONL file found** (line 267-277)
3. **JSONL stat fails** (line 279-282)

## Fix

Return errors for abnormal conditions:

```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


Enhance `bd doctor` to verify Claude Code integration

Add checks to `bd doctor` that verify Claude Code integration is properly set up when .claude/ directory or Claude environment is detected.

## Checks to Add

### 1. Claude Environment Detection
```go
if isClaudeDetected() {
    fmt.Println("✓ Claude Code environment detected")

    // Check what's installed
    hasMCP := isMCPServerInstalled()
    hasHooks := isClaudeSetupComplete()

    if hasMCP && hasHooks {
        fmt.Println("✓ MCP server installed")
        fmt.Println("✓ Claude hooks installed (provides workflow reminders)")
    } else if !hasMCP && hasHooks {
        fmt.Println("✓ Claude hooks installed (provides CLI reference)")
        fmt.Println("  Note: MCP server not detected (using CLI mode)")
    } else if hasMCP && !hasHooks {
        fmt.Println("✓ MCP server installed")
        fmt.Println("⚠ Claude hooks not installed")
        fmt.Println("  Hooks prevent agent from forgetting bd workflow after compaction")
        fmt.Println("  Run: bd setup claude")
    } else {
        fmt.Println("⚠ Claude integration not set up")
        fmt.Println("  Run: bd setup claude")
        // TODO (future): Recommend CLI approach once proven in production
        // fmt.Println("  Tip: CLI mode (without MCP) uses fewer tokens")
    }
}

func isMCPServerInstalled() bool {
    // Check ~/.claude/settings.json for beads MCP server
    home, _ := os.UserHomeDir()
    settingsPath := filepath.Join(home, ".claude/settings.json")

    data, err := os.ReadFile(settingsPath)
    if err != nil {
        return false
    }

    var settings map[string]interface{}
    if err := json.Unmarshal(data, &settings); err != nil {
        return false
    }

    // Check mcpServers section for beads
    mcpServers, ok := settings["mcpServers"].(map[string]interface{})
    if !ok {
        return false
    }

    // Look for beads server (any key containing "beads")
    for key := range mcpServers {
        if strings.Contains(strings.ToLower(key), "beads") {
            return true
        }
    }

    return false
}
```

### 2. Hook Installation Verification (MCP-Aware)

```go
func checkClaudeHooks() {
    home, _ := os.UserHomeDir()
    globalSettings := filepath.Join(home, ".claude/settings.json")
    projectSettings := ".claude/settings.local.json"

    globalHooks := hasBeadsHooks(globalSettings)
    projectHooks := hasBeadsHooks(projectSettings)

    if globalHooks {
        fmt.Println("✓ Global hooks installed")
    } else if projectHooks {
        fmt.Println("✓ Project hooks installed")
    } else {
        fmt.Println("⚠ No hooks installed")
        fmt.Println("  Run: bd setup claude")
        return
    }

    // Check if hooks will work
    if !commandExists("bd") {
        fmt.Println("⚠ 'bd' command not in PATH")
        fmt.Println("  Hooks won't work - ensure bd is installed globally")
    }
}

func hasBeadsHooks(settingsPath string) bool {
    data, err := os.ReadFile(settingsPath)
    if err != nil {
        return false
    }

    var settings map[string]interface{}
    if err := json.Unmarshal(data, &settings); err != nil {
        return false
    }

    hooks, ok := settings["hooks"].(map[string]interface{})
    if !ok {
        return false
    }

    // Check SessionStart and PreCompact for "bd prime"
    for _, event := range []string{"SessionStart", "PreCompact"} {
        eventHooks, ok := hooks[event].([]interface{})
        if !ok {
            continue
        }

        for _, hook := range eventHooks {
            hookMap, _ := hook.(map[string]interface{})
            commands, _ := hookMap["hooks"].([]interface{})
            for _, cmd := range commands {
                cmdMap, _ := cmd.(map[string]interface{})
                if cmdMap["command"] == "bd prime" {
                    return true
                }
            }
        }
    }

    return false
}
```

### 3. AGENTS.md/CLAUDE.md Reference Check
```go
// Check if documentation references bd prime
agentsContent := readFileIfExists("AGENTS.md")
claudeContent := readFileIfExists("CLAUDE.md")

if strings.Contains(agentsContent, "bd prime") || strings.Contains(claudeContent, "bd prime") {
    // Verify bd prime command exists in current version
    if !commandExists("prime") {
        fmt.Println("⚠ Documentation references 'bd prime' but command not found")
        fmt.Println("  Upgrade bd or remove references")
    } else {
        fmt.Println("✓ Documentation references match installed features")
    }
}
```

### 4. Context Priming Test
```go
// Verify bd prime actually works
cmd := exec.Command("bd", "prime")
output, err := cmd.CombinedOutput()

if err != nil {
    fmt.Println("⚠ 'bd prime' failed to execute")
    fmt.Println("  Error:", err)
} else if len(output) == 0 {
    fmt.Println("⚠ 'bd prime' produced no output")
    fmt.Println("  Expected workflow context markdown")
} else {
    // Check if output adapts to MCP mode
    hasMCP := isMCPServerInstalled()
    outputStr := string(output)

    if hasMCP && strings.Contains(outputStr, "mcp__plugin_beads_beads__") {
        fmt.Println("✓ bd prime detected MCP mode (workflow reminders)")
    } else if !hasMCP && strings.Contains(outputStr, "bd ready") {
        fmt.Println("✓ bd prime using CLI mode (full command reference)")
    } else {
        fmt.Println("⚠ bd prime output may not be adapting to environment")
    }
}
```

## Output Format Examples

### With MCP and Hooks
```
bd doctor

Database:
✓ Database found at .beads/beads.db
✓ Git hooks installed

Claude Code Integration:
✓ Claude Code environment detected
✓ MCP server installed
✓ Claude hooks installed (provides workflow reminders)
✓ bd prime detected MCP mode (workflow reminders)
✓ Documentation references match installed features

Sync Status:
✓ No sync issues detected
```

### Without MCP, With Hooks
```
bd doctor

Database:
✓ Database found at .beads/beads.db
✓ Git hooks installed

Claude Code Integration:
✓ Claude Code environment detected
✓ Claude hooks installed (provides CLI reference)
  Note: MCP server not detected (using CLI mode)
✓ bd prime using CLI mode (full command reference)

Sync Status:
✓ No sync issues detected
```

### No Integration
```
bd doctor

Database:
✓ Database found at .beads/beads.db
✓ Git hooks installed

Claude Code Integration:
✓ Claude Code environment detected
⚠ Claude integration not set up
  Run: bd setup claude

Sync Status:
✓ No sync issues detected
```

## Future Enhancement (Post-Production Validation)

Once CLI mode is proven in production, add recommendation:

```go
if isClaudeDetected() && !hasMCP && !hasHooks {
    fmt.Println("⚠ Claude integration not set up")
    fmt.Println("  Run: bd setup claude")
    fmt.Println("  Tip: CLI mode (without MCP) uses fewer tokens than MCP server")
    fmt.Println("       Both approaches work equally well - choose based on preference")
}
```

This recommendation should only be added after CLI mode with `bd prime` is validated in real-world usage.

- bd doctor checks for Claude environment
- Verifies hook installation if .claude/ exists
- Checks AGENTS.md/CLAUDE.md for bd prime references
- Detects version mismatches between docs and installed bd
- Provides actionable suggestions (bd setup claude, upgrade)
- Tests cover detection logic


Redesign session close protocol to be impossible to miss

The current 'land the plane' protocol is failing repeatedly because:
1. 'Land the plane' is vague and doesn't trigger specific training patterns
2. Protocol instructions are buried in bd prime output
3. No prominent checklist format
4. Competes with strong 'summarize and finish' training patterns

Need to:
- Rename to something concrete (Ship Protocol, Push Checklist, Close Session)
- Put at TOP of bd prime output with 🚨 markers
- Format as explicit checklist with [ ] boxes
- Make it impossible to skip or miss
- Test with actual usage to verify it works

The protocol MUST be:
[ ] 1. git status
[ ] 2. git add <files>
[ ] 3. bd sync
[ ] 4. git commit
[ ] 5. bd sync (if needed)
[ ] 6. git push

This is critical infrastructure - without it, work doesn't land.


Fix bd doctor to recommend issues.jsonl as canonical (not beads.jsonl)


Vendor beads-merge Go code into internal/merge/

Copy beads-merge source code from @neongreen's repo into bd codebase.

**Tasks**:
- Create `internal/merge/` package
- Copy merge algorithm code
- Add attribution header to all files
- Update imports to use bd's internal types
- Add LICENSE/ATTRIBUTION file crediting @neongreen
- Keep original algorithm intact

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


Implement transaction retry logic for SQLITE_BUSY

BEGIN IMMEDIATE fails immediately on SQLITE_BUSY instead of retrying with exponential backoff.

Location: internal/storage/sqlite/sqlite.go:223-225

Problem:
- Under concurrent write load, BEGIN IMMEDIATE can fail with SQLITE_BUSY
- Current implementation fails immediately instead of retrying
- Results in spurious failures under normal concurrent usage

Solution: Implement exponential backoff retry:
- Retry up to N times (e.g., 5)
- Backoff: 10ms, 20ms, 40ms, 80ms, 160ms
- Check for context cancellation between retries
- Only retry on SQLITE_BUSY/database locked errors

Impact: Spurious failures under concurrent write load

Effort: 3 hours


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`


Fix regression: dependency_type missing from bd show --json output

Commit 57b6ea6 accidentally removed the dependency_type field from bd show --json output. The fix from PR #203 (commit 50a324d) needs to be re-applied.


bd message: Improve type safety with typed parameter structs


Latency test 1


Create examples for common workflows

Add examples/ subdirectories: OSS contributor workflow, team branch workflow, multi-phase development, multiple personas (architect/implementer). Each with README and sample configs.


Clarify .beads/.gitattributes handling in Protected Branches docs

Protected Branches docs quick start leaves untracked `.beads` directory and `.gitattributes`.
Question: Are these changes meant to be checked into the protected branch?
Need to clarify if these should be ignored or committed, or if the instructions are missing a step.


Testing & Validation

Ensure reliability through comprehensive testing.

Completed comprehensive Agent Mail test coverage analysis and implementation.

**Test Coverage Summary:**
- 66 total tests across 5 files
- 51 unit tests for HTTP adapter (0.02s)
- 15 integration tests for multi-agent scenarios (~55s total)

**New Tests Added:**
Created `test_multi_agent_coordination.py` (4 tests, 11s) covering:
1. Fairness: 10 agents competing for 5 issues → exactly 1 claim per issue
2. Notifications: End-to-end message delivery between agents
3. Handoff: Clean reservation transfer from agent1 to agent2
4. Idempotency: Double reserve/release by same agent

**Coverage Quality:**
✅ Collision prevention (race conditions)
✅ Graceful degradation (7 failure modes)
✅ TTL/expiration behavior
✅ Multi-agent coordination
✅ JSONL consistency
✅ HTTP error handling
✅ Authorization and configuration

**Intentionally Skipped:**
- Path traversal (validated elsewhere)
- Retry policies (nice-to-have)
- HTTPS/TLS (out of scope)
- Slow tests (50+ agents, soak tests)

See `tests/integration/AGENT_MAIL_TEST_COVERAGE.md` for details.

All tests pass. Agent Mail integration is well-tested and reliable for multi-agent scenarios.


Add t.Parallel() to slow hash multiclone tests

Add t.Parallel() to TestHashIDs_MultiCloneConverge and TestHashIDs_IdenticalContentDedup so they run concurrently.

Expected savings: ~10 seconds (from 20s to ~11s)

Implementation:
- Add t.Parallel() call at start of each test function
- Verify tests don't share resources that would cause conflicts
- Run tests to confirm they work in parallel

File: beads_hash_multiclone_test.go:34, :101


Create architecture decision record (ADR)

Document why we chose Agent Mail, alternatives considered, and tradeoffs.

Acceptance Criteria:
- Problem statement (git traffic, no locks)
- Alternatives considered (custom RPC, Redis, etc.)
- Why Agent Mail fits Beads
- Integration principles (optional, graceful degradation)
- Future considerations

File: docs/adr/002-agent-mail-integration.md


Complete and commit beads-mcp type safety improvements

Uncommitted type safety work in beads-mcp needs review and completion


Add metadata distinction guidelines to ERROR_HANDLING.md

Update ERROR_HANDLING.md with clear guidelines distinguishing configuration metadata (Pattern A) from tracking metadata (Pattern B). Include examples from init.go and sync.go.


Makefile Integration for Benchmarks

Add a single 'bench' target to the Makefile for running performance benchmarks.

Target:
.PHONY: bench

bench:
	go test -bench=. -benchtime=3s -tags=bench \
		-cpuprofile=cpu.prof -memprofile=mem.prof \
		./internal/storage/sqlite/
	@echo ""
	@echo "Profiles generated. View flamegraph:"
	@echo "  go tool pprof -http=:8080 cpu.prof"

Features:
- Single simple target, no complexity
- Always generates CPU and memory profiles
- Clear output on how to view results
- 3 second benchmark time for reliable results
- Uses bench build tag for heavy benchmarks

Usage:
  make bench                              # Run all benchmarks
  go test -bench=BenchmarkGetReadyWork... # Run specific benchmark


bd-hv01: Add comprehensive edge case tests for deletion tracking

Need to add tests for: corrupted snapshot file, stale snapshot (> 1 hour), concurrent sync operations (daemon + manual), partial deletion failure, empty remote JSONL, multi-repo mode with deletions, git worktree scenario.

Also refine TestDeletionWithLocalModification to check for specific conflict error instead of accepting any error.

Files: cmd/bd/deletion_tracking_test.go


User Diagnostics (bd doctor --perf)

Extend cmd/bd/doctor.go to add --perf flag for user performance diagnostics.

Functionality:
- Add --perf flag to existing bd doctor command
- Collect system info (OS, arch, Go version, SQLite version)
- Collect database stats (size, issue counts, dependency counts)
- Time key operations on user's actual database:
  * bd ready
  * bd list --status=open
  * bd show <random-issue>
  * bd create (with rollback)
  * Search with filters
- Generate CPU profile automatically (timestamped filename)
- Output simple report with platform info, timings, profile location

Output example:
  Beads Performance Diagnostics
  Platform: darwin/arm64
  Database: 8,234 issues (4,123 open)

  Operation Performance:
    bd ready                  42ms
    bd list --status=open     15ms

  Profile saved: beads-perf-2025-11-13.prof
  View: go tool pprof -http=:8080 beads-perf-2025-11-13.prof

Implementation:
- Extend cmd/bd/doctor.go (~100 lines)
- Use runtime/pprof for CPU profiling
- Use time.Now()/time.Since() for timing
- Rollback test operations (don't modify user's database)


Database pollution in ~/src/dave/vc: 895 issues vs canonical 310

~/src/dave/vc/.beads/beads.db has 895 total issues (675 open, 149 closed), but canonical ~/src/vc/.beads/vc.db has only 310 issues (230 open). This is 585 extra issues - likely pollution from other repositories.

Need to:
1. Identify which issues are polluted (use detect-pollution)
2. Compare issue IDs between dave/vc and canonical vc databases
3. Determine pollution source (beads repo? other repos?)
4. Clean up polluted database
5. Root cause: why did pollution occur?

Investigation findings so far:
- Polluted DB (~/src/dave/vc/.beads/beads.db): 241 issues (180 open, 43 closed)
- Canonical DB (~/src/vc/.beads/vc.db): 310 issues (230 open, 62 closed)
- Contradiction: Polluted has FEWER issues, not more (241 < 310, diff of 69)
- Only 1 unique ID in polluted: vc-55fi
- All source_repo fields are set to "." in both databases
- Issue description claims 895 issues in polluted vs 310 canonical - numbers don't match current state
- Possible: Pollution was already partially cleaned, or issue description refers to different database?


Configure database connection pool limits for daemon mode

Database connection pool not configured for file-based databases when running in daemon mode.

Location: internal/storage/sqlite/sqlite.go:108-116

Problem:
- Daemon is a long-running server handling concurrent RPC requests
- Multiple CLI commands hit same daemon simultaneously
- Go default: unlimited connections (MaxOpenConns=0)
- SQLite IMMEDIATE transactions serialize on write lock
- Can have 100+ goroutines blocked waiting, each holding connection
- Results in connection exhaustion and 'database is locked' errors

Current code only limits in-memory DBs:
if isInMemory {
    db.SetMaxOpenConns(1)
    db.SetMaxIdleConns(1)
}
// File DBs: UNLIMITED connections!

Fix:
if !isInMemory {
    maxConns := runtime.NumCPU() + 1  // 1 writer + N readers
    db.SetMaxOpenConns(maxConns)
    db.SetMaxIdleConns(2)
    db.SetConnMaxLifetime(0)
}

Impact: 'database is locked' errors under concurrent load in daemon mode

Note: NOT an issue for direct CLI usage (each process isolated). Only affects daemon mode where multiple CLI commands share one database pool.

Effort: 1 hour


Add 'bd message send' command for Agent Mail messaging

Agent Mail server supports messaging between agents, but bd CLI only uses it for file reservations. Add commands for inter-agent messaging.

## Background
- Agent Mail server running at http://127.0.0.1:8765
- 12 workspaces configured across 3 channels (beads.dev, vc.dev, wyvern.dev)
- Current integration: file reservations only
- Gap: no way to send messages from bd CLI

## Proposed Commands

```bash
# Send message to another agent
bd message send <to-agent> <message> [options]
  --subject <subject>
  --thread-id <thread-id>     # Optional - group related messages
  --project-id <project>      # Defaults to BEADS_PROJECT_ID

# List inbox messages
bd message inbox [options]
  --limit <N>
  --unread-only

# Read specific message
bd message read <message-id>

# Mark message as acknowledged
bd message ack <message-id>
```

## Example Usage

```bash
# Send message to agent in same channel
bd message send cino-beads-stevey-macbook "Working on bd-z0yn, need your review" \
  --subject "Review request" \
  --thread-id bd-z0yn

# Check inbox
bd message inbox --unread-only

# Read and acknowledge
bd message read msg-abc123
bd message ack msg-abc123
```

## Design Notes
- Use same env vars (BEADS_AGENT_MAIL_URL, BEADS_AGENT_NAME, BEADS_PROJECT_ID)
- Graceful degradation if Agent Mail unavailable
- JSON output support for all commands
- Consider integrating with bd update/close (auto-notify on status changes)

## References
- Agent Mail README: ~/src/mcp_agent_mail/README.md
- Beads integration docs: docs/AGENT_MAIL.md

## Implementation Summary

Added four new commands to bd CLI for Agent Mail messaging:

1. `bd message send <to-agent> <message>` - Send messages to other agents
   - Flags: --subject, --thread-id, --importance, --ack-required
   - Supports markdown content
   - Thread conversations by issue ID

2. `bd message inbox` - List inbox messages
   - Flags: --limit, --unread-only, --urgent-only, --json
   - Shows subject, sender, age, importance
   - Highlights unread and ACK-required messages

3. `bd message read <message-id>` - Read and mark message as read
   - Automatically marks message as read
   - Shows message content

4. `bd message ack <message-id>` - Acknowledge a message
   - Marks message as acknowledged
   - Also marks as read if not already

## Implementation Details

- Uses JSON-RPC over HTTP to communicate with Agent Mail server
- Configuration via environment variables (BEADS_AGENT_MAIL_URL, BEADS_AGENT_NAME, BEADS_PROJECT_ID)
- Graceful error messages when Agent Mail not configured
- Full JSON output support for programmatic use
- Follows same patterns as existing bd commands

## Documentation

Updated:
- docs/AGENT_MAIL.md - Added "Messaging Commands" section with examples and best practices
- README.md - Added "Messaging (Agent Mail)" section in Usage

## Testing

- Compiles successfully
- Help output works correctly
- Ready for integration testing with Agent Mail server


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


bd import fails on duplicate external_ref with no resolution options

When JSONL contains duplicate external_ref values (e.g., two issues both have external_ref='BS-170'), bd import fails entirely with no resolution options.

User must manually edit JSONL to remove duplicates, which is error-prone.

Example error:
```
batch import contains duplicate external_ref values:
external_ref 'BS-170' appears in issues: [opal-39 opal-43]
```

Should handle this similar to duplicate issue detection - offer to merge, pick one, or clear duplicates.


Test label


Support --reason flag in daemon mode for reopen command

The reopen.go command has a TODO at line 61 to add reason as a comment once RPC supports AddComment. Currently --reason flag is ignored in daemon mode with a warning.


Add tests for error wrapping behavior


GH#245: Windows MCP subprocess timeout for git rev-parse

User reports git detection timing out on Windows in MCP server, but CLI works fine.

Path: C:\Users\chris\Documents\DEV_R\quarto-cli
Error: Git repository detection timed out after 5s
Works fine in CLI: `git rev-parse --show-toplevel` succeeds

Hypothesis: subprocess.run() with asyncio.to_thread() may have Windows-specific issues or the MCP runtime environment may not have proper PATH/git access.

Potential fixes:
1. Add subprocess shell=True on Windows
2. Increase timeout further for Windows
3. Add better error logging to capture subprocess stderr
4. Skip git resolution entirely on timeout and just use provided path


Clean up merge conflict artifacts in .beads directory

After resolving merge conflicts in .beads/beads.jsonl, leftover artifacts remain as untracked files:
- .beads/beads.base.jsonl
- .beads/beads.left.jsonl

These appear to be temporary files created during merge conflict resolution.

Options to fix:
1. Add these patterns to .beads/.gitignore automatically
2. Clean up these files after successful merge resolution
3. Document that users should delete them manually
4. Add a check in 'bd sync' or 'bd doctor' to detect and remove stale merge artifacts

Preferred solution: Add *.base.jsonl and *.left.jsonl patterns to .beads/.gitignore during 'bd init', and optionally clean them up automatically after successful import.


bd-hv01: Deletion tracking production readiness

Epic to track all improvements and fixes needed to make the deletion tracking implementation ([deleted:bd-hv01]) production-ready.

The core 3-way merge algorithm is sound, but there are critical issues around atomicity, error handling, and edge cases that need to be addressed before this can be safely used in production.

Critical path (P1):
- Non-atomic snapshot operations
- Brittle JSON string comparison
- Silent partial deletion failures
- Race conditions in concurrent scenarios

Follow-up work (P2-P3):
- Test coverage for edge cases and multi-repo mode
- Performance optimizations
- Code refactoring and observability

Related commit: 708a81c


Add unit tests for hasJSONLChanged() function

The hasJSONLChanged() function has no unit tests. Should test:
- Normal case: hash matches
- Normal case: hash differs
- Edge case: empty file
- Edge case: missing metadata (first run)
- Edge case: corrupted metadata
- Edge case: file read errors
- Edge case: invalid hash format in metadata

Also add performance benchmarks for large JSONL files.


bd message: Fix inefficient client-side filtering for --unread-only


Implement `bd prime` command for AI context loading

Create a `bd prime` command that outputs AI-optimized markdown containing essential Beads workflow context. This provides an alternative to the MCP server for token-conscious users and enables context recovery after compaction/clearing.

## Implementation

Create `cmd/bd/prime.go` that outputs AI-optimized markdown with **adaptive content based on MCP detection**.

## MCP-Aware Output Strategy

`bd prime` detects if MCP server is active and adjusts output accordingly:

**With MCP detected** (~500 tokens):
- Workflow reminders only
- "Use bd MCP tools, not markdown TODOs"
- Session management tips
- NO CLI command syntax (user has native MCP tools)

**Without MCP** (~1-2k tokens):
- Full workflow rules
- Complete CLI command reference with examples
- All command syntax and options

**Why this matters:**
- MCP users don't need CLI docs (they have native function calls)
- Non-MCP users need full command reference
- Same hook works for all users, adapts to their environment
- Reduces token waste for MCP users

## MCP Detection Logic

```go
func isMCPActive() bool {
    // Check environment variables that Claude Code sets when MCP servers are loaded
    // Options to investigate:
    // 1. Check for CLAUDE_MCP_SERVERS environment variable
    // 2. Check for specific MCP server process indicators
    // 3. Parse ~/.claude/settings.json for enabled MCP servers
    // 4. Check for MCP socket/connection availability

    // Fallback: assume MCP if we can't determine
    // (safer to output less and have user call with --full flag)
    return checkMCPEnvironment()
}
```

**Alternative: Command flag for explicit control:**
```bash
bd prime          # Auto-detect MCP
bd prime --full   # Force full output (ignore MCP detection)
bd prime --mcp    # Force MCP mode (minimal output)
```

## Discovery Logic

**Skip PersistentPreRun database initialization:**
Add "prime" to noDbCommands list in main.go so it doesn't require .beads/ upfront.

**Silent, cross-platform execution:**
```go
var primeCmd = &cobra.Command{
    Use:   "prime",
    Short: "Output AI-optimized workflow context",
    Run: func(cmd *cobra.Command, args []string) {
        // Find .beads/ directory (walks up tree like bd does)
        dbPath := beads.FindDatabasePath()
        if dbPath == "" {
            // Not in a beads project - silent exit with success
            // CRITICAL: No stderr output, exit 0
            // This enables cross-platform hook integration
            os.Exit(0)
        }

        // Detect MCP mode (unless overridden by flags)
        mcpMode := isMCPActive()
        if fullFlag {
            mcpMode = false
        }
        if mcpFlag {
            mcpMode = true
        }

        // Output workflow context (adaptive based on MCP)
        if err := outputPrimeContext(mcpMode); err != nil {
            // Suppress all errors - silent exit with success
            // Never write to stderr (breaks Windows compatibility)
            os.Exit(0)
        }
    },
}
```

**Why silent execution matters:**
- **Cross-platform**: No shell-specific syntax needed (`2>/dev/null`, `|| true`)
- **Hook-friendly**: Can be called directly from JSON: `"command": "bd prime"`
- **Windows compatible**: Works in cmd.exe, PowerShell, bash
- **Non-beads projects**: No error noise when run outside beads projects

## Output Formats

### MCP Mode (~500 tokens)
```markdown
# Beads Workflow Context

> **You have native bd MCP tools available** - use them instead of markdown TODOs
> Run `bd prime --full` for complete CLI reference if needed

## Core Workflow Rules

**Task Tracking:**
- Use bd MCP tools for ALL work tracking (never markdown TODOs)
- Check available work: use `mcp__plugin_beads_beads__ready` tool
- Create issues: use `mcp__plugin_beads_beads__create` tool
- Update status: use `mcp__plugin_beads_beads__update` tool

**Session Management:**
- Start: Check `ready` tool for available work
- During: Keep issues updated with `update` tool
- End: Verify sync status, close completed issues

**Git Integration:**
- Hooks auto-sync issues with git commits
- Run `sync` tool at session end to push to remote

**Need help?**
- Use `show` tool for issue details
- Check AGENTS.md for complete workflow
- Run `bd prime --full` for CLI command reference
```

### Non-MCP Mode (~1-2k tokens)
```markdown
# Beads Workflow Context

> **Context Recovery**: Run `bd prime` after compaction, clear, or new session
> Hooks auto-call this in Claude Code when .beads/ detected

## Core Rules
- Track ALL work in bd (no markdown TODOs)
- Git workflow: hooks auto-sync, run `bd sync` at session end
- Session management: check `bd ready` for available work

## Essential Commands

### Finding Work
- `bd ready` - Show issues ready to work (no blockers)
- `bd list --status=open` - All open issues
- `bd list --status=in_progress` - Your active work
- `bd show <id>` - Detailed issue view with dependencies

### Creating & Updating
- `bd create --title="..." --type=task|bug|feature` - New issue
- `bd update <id> --status=in_progress` - Claim work
- `bd update <id> --assignee=username` - Assign to someone
- `bd close <id>` - Mark complete
- `bd close <id> --reason="explanation"` - Close with reason

### Dependencies & Blocking
- `bd dep <from> <to>` - Add blocker dependency (from blocks to)
- `bd blocked` - Show all blocked issues
- `bd show <id>` - See what's blocking/blocked by this issue

### Sync & Collaboration
- `bd sync` - Sync with git remote (run at session end)
- `bd sync --status` - Check sync status without syncing

### Project Health
- `bd stats` - Project statistics (open/closed/blocked counts)
- `bd doctor` - Check for issues (sync problems, missing hooks)

## Common Workflows

**Starting work:**
```bash
bd ready           # Find available work
bd show <id>       # Review issue details
bd update <id> --status=in_progress  # Claim it
```

**Completing work:**
```bash
bd close <id>      # Mark done
bd sync            # Push to remote
```

**Creating dependent work:**
```bash
bd create --title="Implement feature X" --type=feature
bd create --title="Write tests for X" --type=task
bd dep beads-xxx beads-yyy  # Feature blocks tests
```

For complete docs: AGENTS.md, QUICKSTART.md, `bd --help`
```

## Behavior Guarantees

1. **Never writes to stderr** (all errors suppressed internally)
2. **Always exits 0** (success) even when:
   - Not in a beads project
   - .beads/ not found
   - Any internal error occurs
3. **Cross-platform compatible** (no shell-specific syntax)
4. **Hook-safe** (can be called directly without wrappers)
5. **MCP-aware** (adapts output based on user's tool environment)

## Files
- `cmd/bd/prime.go` - Command implementation with MCP detection
- `cmd/bd/prime_test.go` - Tests (including MCP mode switching)
- Update `cmd/bd/main.go` - Add "prime" to noDbCommands list

- `bd prime` outputs markdown format
- Output is ~1-2k tokens
- Output includes workflow rules and command reference
- Command has unit tests
- Documentation updated in AGENTS.md


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)


Publish official devcontainer configuration

Problem: GH issue #229 shows git hooks aren't available in devcontainers because bd CLI isn't installed. Solution: Provide official .devcontainer/devcontainer.json that installs Go, builds bd from source, runs bd init --quiet, and installs hooks automatically. Benefits: New contributors start immediately, AI assistants work out-of-box, no manual setup. Implementation: Create .devcontainer/devcontainer.json with Go feature, postCreateCommand to build bd and install hooks, environment variables. Acceptance: Container builds with bd installed, hooks functional, documentation updated, tested with Codespaces and VSCode.

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


Manual task


Consider extracting error handling helpers

Evaluate creating FatalError() and WarnError() helpers as suggested in ERROR_HANDLING.md to reduce boilerplate and enforce consistency. Prototype in a few files first to validate the approach.


bd message: Refactor duplicated error messages


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


Fix inconsistent error handling in multi-repo deletion tracking

From bd-xo6b code review: Multi-repo deletion tracking has mixed failure modes that can leave system in inconsistent state.

**Current behavior (daemon_sync.go):**
- Snapshot capture (L505-514): Hard fail → aborts sync
- Merge/prune (L575-584): Hard fail → aborts sync
- Base snapshot update (L613-619): Soft fail → logs warning, continues

**Critical problem:**
If merge fails on repo 3 of 5:
- Repos 1-2 have already merged and deleted issues (irreversible)
- Repos 3-5 are untouched
- Database is in partially-updated state
- No rollback mechanism

**Real-world scenario:**
```
Sync with repos [A, B, C]:
1. Capture snapshots A ✓, B ✓, C ✗ → ABORT (good)
2. Merge A ✓, B ✗ → ABORT but A already deleted issues (BAD - no rollback)
3. Update base A ⚠, B ⚠ → Warnings only (inconsistent with 1 & 2)
```

**Solution options:**
1. **Two-phase commit:**
   - Phase 1: Validate all repos (check files exist, readable, parseable)
   - Phase 2: Apply changes atomically (or fail entirely before any mutations)

2. **Fail-fast validation:**
   - Before any snapshot/merge operations, validate all repos upfront
   - Abort entire sync if any repo fails validation

3. **Make base snapshot update consistent:**
   - Either make it hard-fail like the others, or make all soft-fail

**Files:**
- cmd/bd/daemon_sync.go:505-514 (snapshot capture)
- cmd/bd/daemon_sync.go:575-584 (merge/prune)
- cmd/bd/daemon_sync.go:613-619 (base snapshot update)

**Recommendation:** Use option 1 (two-phase) or option 2 (fail-fast validation) + fix base snapshot inconsistency.


Investigation & Proof of Concept

Validate that MCP Agent Mail works as expected and delivers promised benefits before committing to full integration.

POC completed successfully:
✅ bd-muls: Server installed and tested
✅ bd-27xm: MCP tool execution issues resolved
✅ [deleted:bd-6hji]: File reservation collision prevention validated
✅ bd-htfk: Latency benchmarking shows 20-50x improvement
✅ bd-pmuu: ADR 002 created documenting integration decision

Results validate Agent Mail benefits:
- Collision prevention works (exclusive file reservations)
- Latency: <100ms (vs 2000-5000ms git sync)
- Lightweight deployment (<50MB memory)
- Optional/non-intrusive integration approach validated

Next: bd-wfmw (Integration Layer Implementation)


Block version bumps/releases when CI is failing

The bump-version.sh script and release process should verify that CI is passing before creating version tags and releases.

## Problem
Currently we can cut releases even when CI tests are failing, which could ship broken code to users.

## Proposed Solution
Add a CI status check to:
1. scripts/bump-version.sh - Check CI status before tagging
2. Potentially add a GitHub branch protection rule requiring CI to pass before tags can be pushed

## Implementation Ideas
- Use `gh run list --branch main --limit 1` to check latest CI status
- Add `--force` flag to override the check if needed
- Consider GitHub Actions workflow that validates CI before release

## Benefits
- Prevents shipping broken releases
- Catches test failures before they reach users
- Maintains quality standards in distribution channels


bd export should detect and prevent stale database exports

## Problem

When `bd export` is run with a stale database (older than issues.jsonl), it silently overwrites the JSONL file with stale data, causing data loss.

## What Happened (vc project)

1. Agent A created 4 new issues and exported to issues.jsonl (commit 99a9d58)
2. Agent A closed an issue and exported again (commit 58b4613) - JSONL now has 4 epics
3. Agent B had stale database (from before step 1)
4. Agent B worked on unrelated issue and exported (commit 0609233)
5. Agent B's export **overwrote issues.jsonl**, removing the 4 epics created by Agent A
6. Required manual recovery by re-exporting from Agent A's correct database

## Expected Behavior

`bd export` should detect that the database is stale and either:
- **Refuse to export** with error message explaining the issue
- **Warn prominently** and require explicit --force flag to override
- **Auto-import first** to sync database before exporting

## How to Detect Staleness

Compare modification times (similar to VC's ValidateDatabaseFreshness):
1. Check .db, .db-wal, .db-shm timestamps (use newest for WAL mode)
2. Check issues.jsonl timestamp
3. If JSONL is newer by >1 second: database is stale

## Suggested Fix

Add staleness check in `bd export`:

```go
func Export(dbPath, jsonlPath string, force bool) error {
    // Check if database is stale
    if !force {
        if err := checkDatabaseFreshness(dbPath, jsonlPath); err != nil {
            return fmt.Errorf("database is stale: %w\n" +
                "Run 'bd import %s' first to sync, or use --force to override",
                err, jsonlPath)
        }
    }

    // Proceed with export...
}
```

## Impact

- **Severity**: High (silent data loss)
- **Frequency**: Happens in multi-agent workflows when agents don't sync
- **Workaround**: Manual recovery (re-export from correct database)

## References

- VC issue tracker: commits 58b4613 -> 0609233 -> c41c638
- VC has similar check: `storage.ValidateDatabaseFreshness()`
- Tolerance: 1 second (handles filesystem timestamp precision)

1. bd export detects stale database (JSONL newer than DB)
2. Refuses to export with clear error message
3. Suggests running 'bd import' first
4. --force flag allows override with warning
5. Tolerates filesystem timestamp precision (1 second)
6. Works correctly with WAL mode (.db-wal files)
7. Test coverage for staleness detection
8. Documentation updated with export safety warnings

Fixed with ID-based comparison instead of just count. Now detects:
1. DB has fewer issues than JSONL (count check)
2. DB has different issues than JSONL (ID comparison)

Both scenarios now properly refuse export unless --force is used.

Implementation uses getIssueIDsFromJSONL() to build a set of IDs from JSONL, then checks if any JSONL IDs are missing from DB. Shows specific missing issue IDs in error message.


Add test coverage for internal/config package

Config package has 1 test file. Need comprehensive tests. Target: 70% coverage

- At least 3 test files
- Package coverage >= 70%


False positive detection by Kaspersky Antivirus (Trojan)

Kaspersky Antivirus falsely detects beads (bd.exe v0.23.1) as a Trojan (PDM:Trojan.Win32.Generic) and removes it.
Event: Malicious object detected
Component: System Watcher
Object name: bd.exe

## Completed Actions

1. **Root cause analysis**: Documented that this is a known false positive with Go binaries
2. **Created comprehensive user documentation**: docs/ANTIVIRUS.md with:
   - Explanation of why Go binaries trigger false positives
   - Instructions for adding exclusions (Kaspersky, Windows Defender, etc.)
   - File integrity verification steps
   - False positive reporting procedures
   - FAQ section
3. **Updated TROUBLESHOOTING.md**: Added quick reference section with link to detailed guide

## Next Steps (Follow-up Issues)

The following actions require external processes or future releases:

1. **Submit false positive to Kaspersky**: Need to access bd.exe v0.23.1 Windows binary and submit to https://opentip.kaspersky.com/
2. **Code signing for Windows**: Requires purchasing certificate and configuring GoReleaser (medium-term)
3. **Apply for Kaspersky whitelist**: Long-term relationship building with vendor

## Current Status

Users affected by this issue now have:
- Clear documentation explaining the false positive
- Step-by-step instructions to work around it
- Ways to verify file integrity
- Process to report to their antivirus vendor

Build configuration already includes recommended optimizations (-s -w flags).


Document error handling strategy for metadata update failures

Multiple places silently ignore metadata update failures (sync.go:614-617, import.go:320-322) with non-fatal warnings. This is intentional (degrades gracefully to mtime-based approach) but not well documented.

Add comments explaining:
- Why these failures are non-fatal
- How system degrades gracefully
- What the fallback behavior is (mtime-based detection)


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.


Investigate database pollution - unexpected issue count increases

Two repositories showing unexpected issue counts:
- ~/src/beads: 280 issues (expected ~209-220)
- ~/src/dave/beads: 895 issues (675 open, 149 closed)

This suggests database pollution - issues from one repository leaking into another. Need to investigate:
1. Run bd detect-pollution on both repos
2. Check for cross-repo contamination
3. Identify source of pollution (daemon? multi-repo config? import issues?)
4. Clean up polluted databases
5. Prevent future pollution


Add Claude setup tip with dynamic priority

Add a predefined tip that suggests running `bd setup claude` when Claude Code is detected but not configured. This tip should have higher priority (shown more frequently) until the setup is complete.

## Implementation

Add to tip registry in `cmd/bd/tips.go`:

```go
{
    ID: "claude_setup",
    Condition: func() bool {
        return isClaudeDetected() && !isClaudeSetupComplete()
    },
    Message: "Run 'bd setup claude' to enable automatic context recovery in Claude Code",
    Frequency: 24 * time.Hour,  // Daily minimum gap
    Priority: 100,              // Highest priority
    Probability: 0.6,           // 60% chance when eligible
}
```

## Detection Logic

```go
func isClaudeDetected() bool {
    // Check environment variables
    if os.Getenv("CLAUDE_CODE") != "" || os.Getenv("ANTHROPIC_CLI") != "" {
        return true
    }
    // Check if .claude/ directory exists
    if _, err := os.Stat(filepath.Join(os.Getenv("HOME"), ".claude")); err == nil {
        return true
    }
    return false
}

func isClaudeSetupComplete() bool {
    // Check for global installation
    home, err := os.UserHomeDir()
    if err == nil {
        _, err1 := os.Stat(filepath.Join(home, ".claude/commands/prime_beads.md"))
        _, err2 := os.Stat(filepath.Join(home, ".claude/hooks/sessionstart"))
        if err1 == nil && err2 == nil {
            return true  // Global hooks installed
        }
    }

    // Check for project installation
    _, err1 := os.Stat(".claude/commands/prime_beads.md")
    _, err2 := os.Stat(".claude/hooks/sessionstart")
    return err1 == nil && err2 == nil
}
```

## Priority and Probability Behavior

**Why 60% probability?**
- Important message (priority 100) but not critical
- Daily frequency + 60% = shows ~4 times per week
- Avoids spam while staying visible
- Balances persistence with user experience

**Comparison with other probabilities:**
- 100% probability: Shows EVERY day (annoying)
- 80% probability: Shows ~6 days per week (too frequent)
- 60% probability: Shows ~4 days per week (balanced)
- 40% probability: Shows ~3 days per week (might be missed)

**Auto-stops when setup complete:**
- Condition becomes false after `bd setup claude`
- No manual dismissal needed
- Tip naturally disappears from rotation

- Claude setup tip added to registry
- isClaudeDetected() checks environment and filesystem
- isClaudeSetupComplete() verifies hook installation
- Tip shows daily until setup complete
- Tip stops showing after setup
- Unit tests for detection functions


Update documentation for bd prime and Claude integration

Update AGENTS.md, README.md, and QUICKSTART.md to document the new `bd prime` command, `bd setup claude` command, and tip system.

## Documentation Updates

### AGENTS.md
Add new section "Context Recovery":
```markdown
## Context Recovery

### The Problem
After context compaction or clearing conversation, AI agents may forget to use Beads and revert to markdown TODOs. Claude Code hooks solve this.

### bd prime Command
The `bd prime` command outputs essential Beads workflow context in AI-optimized markdown format (~1-2k tokens).

**When to use:**
- After context compaction
- After clearing conversation
- Starting new session
- When agent seems to forget bd workflow
- Manual context refresh

**Usage:**
```bash
bd prime              # Output workflow context
```

### Automatic Integration (Recommended)

Run `bd setup claude` to install hooks that auto-refresh bd context:
- **SessionStart hook**: Loads context in new sessions
- **PreCompact hook**: Refreshes context before compaction (survives better)
- **Works with MCP**: Hooks complement MCP server (not replace)
- **Works without MCP**: bd prime provides workflow via CLI

**Why hooks matter even with MCP:**
- MCP provides native tools, but agent may forget to use them
- Hooks keep "use bd, not markdown" fresh in context
- PreCompact refreshes workflow before compaction

### MCP Server vs bd prime

**Not an either/or choice** - they solve different problems:

| Aspect | MCP Server | bd prime | Both |
|--------|-----------|----------|------|
| **Purpose** | Native bd tools | Workflow context | Best of both |
| **Tokens** | 10.5k always loaded | ~1-2k when called | 10.5k + ~2k |
| **Tool access** | Function calls | CLI via Bash | Function calls |
| **Context memory** | Can fade after compaction | Hooks keep fresh | Hooks + tools |
| **Recommended** | Heavy usage | Token optimization | Best experience |

**Setup options:**
```bash
bd setup claude           # Install hooks (works with or without MCP)
bd setup claude --local   # Per-project only
bd setup claude --remove  # Remove hooks
```
```

### README.md
Add to "Getting Started" section:
```markdown
### AI Agent Integration

**Claude Code users:** Run `bd setup claude` to install automatic context recovery hooks.

Hooks work with both MCP server and CLI approaches, preventing agents from forgetting bd workflow after compaction.

**MCP vs bd prime:**
- **With MCP server**: Hooks keep agent using bd tools (prevents markdown TODO reversion)
- **Without MCP server**: Hooks provide workflow context via `bd prime` (~1-2k tokens)
```

### QUICKSTART.md
Add section on agent integration:
```markdown
## For AI Agents

**Context loading:**
```bash
bd prime    # Load workflow context (~1-2k tokens)
```

**Automatic setup (Claude Code):**
```bash
bd setup claude    # Install hooks for automatic context recovery
```

Hooks prevent agents from forgetting bd workflow after compaction.
```

- AGENTS.md has Context Recovery section
- README.md mentions bd setup claude
- QUICKSTART.md mentions bd prime
- Examples show when to use bd prime vs MCP
- Clear comparison of trade-offs


Merge PR #300: gitignore upgrade feature

PR #300 is ready to merge but has rebase conflicts with main.

**Context:**
- PR implements 3 mechanisms for .beads/.gitignore upgrade (bd doctor --fix, daemon auto-upgrade, bd init idempotent)
- Conflicts resolved locally but diverged branches make push difficult
- All fixes applied: removed merge artifact, applied new gitignore template
- Clean scope: only 6 files changed (+194/-42)

**Next steps:**
1. Option A: Merge via GitHub UI (resolve conflicts in web interface)
2. Option B: Fresh rebase on main and force push
3. Verify CI passes
4. Squash and merge

PR URL: https://github.com/steveyegge/beads/pull/300
Fixes: #274


bd init overwrites existing git hooks without detection or chaining

GH #254: bd init silently overwrites existing git hooks (like pre-commit framework) without detecting them, backing them up, or offering to chain. This breaks workflows and can result in committed code with failing tests.

Fix: Detect existing hooks, prompt user with options to chain/overwrite/skip.


Add bd init --team wizard

Interactive wizard for team workflow setup. Guides user through: branch workflow configuration, shared repo setup, team member onboarding, examples of team collaboration patterns.


Phase 2: Implement sandbox auto-detection for GH #353

Implement automatic sandbox detection to improve UX for users in sandboxed environments (e.g., Codex).

**Tasks:**
1. Implement sandbox detection heuristic using syscall.Kill permission checks
2. Auto-enable --sandbox mode when sandbox is detected
3. Display informative message when sandbox is detected

**Implementation:**
- Add isSandboxed() function in cmd/bd/main.go
- Auto-set sandboxMode = true in PersistentPreRun when detected
- Show: 'ℹ  Sandbox detected, using direct mode'

**References:**
- docs/GH353_INVESTIGATION.md (Solution 3, lines 120-160)
- Depends on: Phase 1 (bd-???)

**Acceptance Criteria:**
- Codex users don't need to manually specify --sandbox
- No false positives in normal environments
- Clear messaging when auto-detection triggers


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

1. bd import detects when working tree differs from git HEAD
2. Warning emitted if JSONL has uncommitted changes
3. Status message clarifies 'synced with working tree' vs 'synced with git'
4. Optional flag to suppress warning (e.g., --working-tree mode)
5. Documentation updated to explain import behavior with uncommitted changes
6. Test case: import with dirty working tree shows warning


Clarify exclusive lock protocol compatibility with multi-repo

The contributor-workflow-analysis.md proposes per-repo file locking (Decision #7) using flock on JSONL files. However, VC (a downstream library consumer) uses an exclusive lock protocol (vc-195, requires Beads v0.17.3+) that allows bd daemon and VC executor to coexist.

Need to clarify:
- Does the proposed per-repo file locking work with VC's existing exclusive lock protocol?
- Do library consumers like VC need to adapt their locking logic?
- Can multiple repos be locked atomically for cross-repo operations?

Context: contributor-workflow-analysis.md lines 662-681

- Documentation explicitly states compatibility or incompatibility with existing lock protocols
- If incompatible, migration path is documented for library consumers
- If compatible, example showing coexistence is provided


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)


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)


Test label update feature


Critical: bd sync overwrites git-pulled JSONL instead of importing it

## Problem

When a user runs `git pull` (which updates .beads/beads.jsonl) followed by `bd sync`, the sync command fails to detect that the JSONL changed and exports the local database, **overwriting the pulled JSONL** instead of importing it first.

This causes cleaned-up issues (removed via `bd cleanup` in another repo) to be resurrected and pushed back to the remote.

## Root Cause

`hasJSONLChanged()` in cmd/bd/integrity.go incorrectly returns FALSE when it should return TRUE after git pull updates the JSONL file.

The function has two code paths:
1. **Fast-path (line 128-129)**: If mtime unchanged, return false
2. **Slow-path (line 135-151)**: Compute hash and compare

Suspected issue: The mtime-based fast-path may incorrectly return false if:
- Git preserves mtime when checking out files, OR
- The mtime check logic has a race condition, OR
- The stored last_import_mtime is stale/incorrect

## Reproduction

**Setup:**
1. Repo A: Has 686 issues (including 630 closed)
2. Run `bd cleanup -f` in Repo A (removes 630 closed issues → 56 issues remain)
3. Push to remote
4. Repo B: Still has 686 issues in database

**Trigger:**
1. In Repo B: `git pull` (JSONL now has 56 issues)
2. In Repo B: `bd sync`

**Expected behavior:**
- hasJSONLChanged() returns TRUE
- Auto-imports 56-issue JSONL
- Database updated to 56 issues
- Exports (no-op, DB and JSONL match)
- Pushes

**Actual behavior:**
- hasJSONLChanged() returns FALSE ❌
- Skips auto-import
- Exports 686 issues from DB to JSONL
- Commits and pushes the 686-issue JSONL
- **Undoes the cleanup from Repo A**

## Evidence

From actual session (2025-11-23):
- Commit 8bc8611 in cino/beads shows: `+640, -14 lines` after bd sync
- No "Auto-import complete" message in output
- Database had 686 issues, JSONL was pulled with 56 issues
- Sync exported DB → JSONL instead of importing JSONL → DB

## Impact

**Critical:** This breaks multi-device sync and causes:
- Lost work (cleanup operations undone)
- Data resurrection (deleted issues come back)
- Confusing merge conflicts
- User trust issues ("Why does sync keep bringing back deleted issues?")

## Proposed Solutions

### Option 1: Remove mtime fast-path (safest)
Always compute and compare hashes. Eliminates false negatives from mtime issues.

**Pros:** Guaranteed correctness
**Cons:** Slightly slower (hash computation on every sync)

### Option 2: Fix mtime comparison logic
Investigate why mtime check fails and fix it properly.

**Areas to check:**
- Does git preserve mtime on checkout? (may vary by git version/config)
- Is last_import_mtime updated correctly after all operations?
- Race condition between git pull and mtime check?

### Option 3: Add hash-based validation
Keep mtime fast-path but add hash validation as backup:
- If mtime unchanged, still spot-check hash occasionally (e.g., 10% of the time)
- Log warnings when mtime and hash disagree
- This would catch the bug while maintaining performance

### Option 4: Add `--force-import` flag to bd sync
Short-term workaround: Allow users to force import even if hasJSONLChanged() returns false.

## Workaround

Until fixed, after `git pull`:
```bash
bd import --force  # Force import the pulled JSONL
bd sync            # Then sync normally
```

Or manually run bd cleanup in affected repos.

## Files

- cmd/bd/integrity.go:101-152 (hasJSONLChanged function)
- cmd/bd/sync.go:130-143 (auto-import logic)
- cmd/bd/autoflush.go:698 (export updates last_import_hash)

## Related Issues

- bd-77gm: Import reports misleading counts
- bd-khnb: Content-based staleness detection (original implementation of hash checking)

## Fix Implemented (Option 1)

**Root Cause Confirmed:**
Git does NOT update mtime when checking out files. Testing confirmed that after `git checkout HEAD~1`, the file mtime remains unchanged even though content differs.

**Solution:**
Removed the mtime-based fast-path in `hasJSONLChanged()`. Now always computes content hash for comparison.

**Changes Made:**
1. **cmd/bd/integrity.go:107-116** - Removed mtime fast-path, always compute hash
2. **cmd/bd/sync.go:752** - Removed mtime storage after import
3. **cmd/bd/import.go:340** - Removed mtime storage after import
4. **cmd/bd/daemon_sync.go:280-301** - Removed mtime storage and updated comments
5. **cmd/bd/daemon_sync_test.go:479,607,628** - Removed mtime assertions from tests

**Performance Impact:**
Minimal. Hash computation takes ~10-50ms even for large databases, which is acceptable for sync operations.

**Testing:**
- All existing tests pass
- Test "mtime changed but content same - git operation scenario" verifies the fix
- Full test suite passes (cmd/bd and all internal packages)

**Verification:**
The fix ensures that after `git pull` updates the JSONL:
1. `hasJSONLChanged()` returns TRUE (content hash differs)
2. Auto-import runs and updates database
3. No data loss or resurrection of deleted issues


Add dependency operations to Transaction interface

Extend Transaction interface with dependency management operations.

## Tasks
1. Add to Transaction interface:
   - AddDependency(ctx, dep, actor) error
   - RemoveDependency(ctx, issueID, dependsOnID, actor) error

2. Implement on sqliteTxStorage:
   - Reuse cycle detection logic with conn
   - Ensure proper event recording within transaction
   - Handle blocked cache invalidation

3. Refactor addDependencyWithConn helper if needed

## Acceptance Criteria
- [ ] AddDependency works within transaction
- [ ] RemoveDependency works within transaction
- [ ] Cycle detection operates within transaction scope
- [ ] Events recorded atomically with dependency changes
- [ ] Test: create issue + add dependency atomically


Fix row iterator resource leak in recursive dependency queries

Critical resource leak in findAllDependentsRecursive() where rows.Close() is called AFTER early return on error, never executing.

Location: internal/storage/sqlite/sqlite.go:1131-1136

Problem:
- rows.Close() placed after return statement
- On scan error, iterator never closed
- Can exhaust SQLite connections under moderate load

Fix: Move defer rows.Close() to execute on all code paths

Impact: Connection exhaustion during dependency traversal


Daemon crash recovery: panic handler + socket cleanup

Improve daemon cleanup on unexpected exit:
1. Add top-level recover() in runDaemonLoop to capture panics
2. Write daemon-error file with stack trace on panic
3. Prefer return over os.Exit where possible (so defers run)
4. In stopDaemon forced-kill path, also remove stale socket if present

This ensures better diagnostics and cleaner state after crashes.


Apply wrapDBError to config.go


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


Apply wrapDBError to remaining storage files


Define Transaction interface and add RunInTransaction to Storage

Define the core Transaction interface and add RunInTransaction method to Storage interface.

## Tasks
1. Add Transaction interface to internal/storage/storage.go with core methods:
   - CreateIssue, CreateIssues, UpdateIssue, CloseIssue, DeleteIssue
   - GetIssue (for read-your-writes within transaction)

2. Add RunInTransaction method signature to Storage interface

3. Document transaction semantics in interface comments:
   - Commit on nil return
   - Rollback on error return or panic
   - IMMEDIATE mode for SQLite

## Acceptance Criteria
- [ ] Transaction interface defined with issue CRUD methods
- [ ] RunInTransaction added to Storage interface
- [ ] Clear documentation of transaction behavior
- [ ] Compiles without implementation (interface only)


Document CLI + hooks as recommended approach over MCP

Update documentation to position CLI + bd prime hooks as the primary recommended approach over MCP server, explaining why minimizing context matters even with large context windows (compute cost, energy, environment, latency).

## Goals

Position CLI + `bd prime` hooks as the **primary recommended approach** for AI agent integration, with MCP server as a legacy/fallback option.

Explore **hybrid mode** - if certain commands benefit from MCP (UX/DX advantages like no approval prompts), minimize MCP surface area to only those commands.

This requires production validation first - only update docs after CLI mode is proven reliable.

## Why Minimize Context (Even With Large Windows)

**Context window size ≠ free resource**

Large context windows (100k+, 200k+) don't mean we should fill them wastefully. Every token in context has real costs:

### Compute Cost
- **Processing overhead**: Larger context = more GPU/CPU cycles per request
- **Memory usage**: 10.5k tokens consume significant RAM/VRAM
- **Scaling impact**: Multiplied across all users, all sessions, all requests

### Energy & Environment
- **Electricity**: More compute = more power consumption
- **Carbon footprint**: Data centers running on grid power (not all renewable)
- **Sustainability**: Unnecessary token usage contributes to AI's environmental impact
- **Responsibility**: Efficient tools are better for the planet

### User Experience
- **Latency**: Larger context = slower processing (noticeable at 10k+ tokens)
- **Cost**: Many AI services charge per token (input + output)
- **Rate limits**: Context counts against API quotas

### Engineering Excellence
- **Efficiency**: Good engineering minimizes resource usage
- **Scalability**: Efficient tools scale better
- **Best practices**: Optimize for the common case

**The comparison:**

| Approach | Standing Context | Efficiency | User Cost | Environmental Impact |
|----------|-----------------|------------|-----------|---------------------|
| **CLI + hooks** | ~1-2k tokens | 80-90% reduction | Lower | Sustainable ✓ |
| **MCP minimal** | ~2-4k tokens | 60-80% reduction | Medium | Better ✓ |
| **MCP full** | ~10.5k tokens | Baseline | Higher | Wasteful ✗ |

**Functional equivalence:**
- CLI via Bash tool works just as well as MCP native calls
- Same features, same reliability
- No downside except initial learning curve

## Hybrid Mode: Minimal MCP Surface Area

**Philosophy:** MCP server doesn't have to expose everything.

If certain commands have legitimate UX/DX benefits from MCP (e.g., no approval prompts, cleaner syntax), we can expose ONLY those commands via MCP while using CLI for everything else.

### Potential MCP-Only Candidates (TBD)

Commands that might benefit from MCP native calls:
- `ready` - frequently checked, no side effects, approval prompt annoying
- `show` - read-only, frequently used, approval slows workflow
- `list` - read-only, no risk, approval adds friction

Commands that work fine via CLI:
- `create` - complex parameters, benefits from explicit confirmation
- `update` - state changes, good to see command explicitly
- `close` - state changes, explicit is better
- `dep` - relationships, good to see what's being linked
- `sync` - git operations, definitely want visibility

### Token Budget

**Full MCP** (current): ~10.5k tokens
- All ~20+ bd commands exposed
- All parameter schemas
- All descriptions and examples

**Minimal MCP** (proposed): ~2-4k tokens
- 3-5 high-frequency read commands only
- Simplified schemas
- Minimal descriptions
- Everything else via CLI

**Pure CLI**: ~1-2k tokens (only on SessionStart/PreCompact)
- No MCP tools loaded
- All commands via Bash

### Investigation Required

Before implementing hybrid mode, validate:

1. **Do MCP calls actually skip approval prompts?**
   - Test with Claude Code approval settings
   - Compare MCP tool calls vs Bash tool calls
   - Measure UX difference in real usage

2. **What's the actual token breakdown per command?**
   - Measure individual command schemas
   - Calculate token savings for minimal vs full

3. **Is approval prompt the only benefit?**
   - Are there other UX advantages to MCP?
   - Does native syntax actually improve experience?
   - User testing with both approaches

4. **Can we dynamically load MCP tools?**
   - Only load MCP when certain commands needed?
   - Hot-swap between CLI and MCP?
   - Probably not - MCP loads at startup

### Hybrid Mode Documentation (If Validated)

```markdown
## Choosing Your Integration Approach

Beads supports three AI agent integration approaches:

### CLI + Hooks (Recommended - Most Efficient)

**Setup:** `bd setup claude`

Uses Claude Code hooks to inject workflow context via `bd prime` command. Agent uses bd via Bash tool.

**Tokens:** ~1-2k (on SessionStart/PreCompact only)

**Pros:**
- Maximum efficiency (80-90% reduction vs full MCP)
- Lowest compute/energy usage
- Same functionality as MCP

**Cons:**
- Bash tool calls may require approval prompts
- Slightly more verbose in conversation

### Minimal MCP + Hooks (Balanced)

**Setup:** Install minimal MCP server (read-only commands) + `bd setup claude`

Exposes only high-frequency read commands via MCP (ready, show, list). Everything else via CLI.

**Tokens:** ~2-4k MCP + ~1-2k hooks

**Pros:**
- 60-80% reduction vs full MCP
- No approval prompts for common queries
- Cleaner syntax for frequent operations
- Still efficient

**Cons:**
- Requires MCP server (additional setup)
- Mixed interface (some MCP, some CLI)

### Full MCP + Hooks (Legacy)

**Setup:** Install full MCP server + `bd setup claude`

**Tokens:** ~10.5k MCP + hooks

**Pros:**
- All commands as native function calls
- Consistent interface

**Cons:**
- Highest token usage (worst for compute/energy/cost)
- Slowest processing
- Less sustainable

### Recommendation

1. **Start with CLI + hooks** - most efficient, works great
2. **Try minimal MCP** if approval prompts become annoying
3. **Avoid full MCP** - wasteful with no significant benefit
```

## Production Validation Checklist

Before making these documentation changes, validate CLI approach works reliably:

### Phase 1: Pure CLI Validation
- [ ] `bd prime` implemented and tested
- [ ] Hooks installed and working in Claude Code
- [ ] Real-world usage by at least 2-3 developers for 1+ weeks
- [ ] No significant usability issues reported
- [ ] Agent successfully uses bd via Bash tool
- [ ] Document which commands (if any) have approval prompt issues

### Phase 2: Hybrid Mode Investigation (Optional)
- [ ] Test if MCP calls skip approval prompts vs Bash calls
- [ ] Measure token cost per MCP command
- [ ] Identify minimal set of commands worth exposing via MCP
- [ ] Build minimal MCP server variant
- [ ] Validate token savings (should be 60-80% vs full MCP)
- [ ] User testing shows actual UX improvement

### Phase 3: Documentation Update
- [ ] Update based on validation results
- [ ] Include measured token counts (not estimates)
- [ ] Provide clear migration paths
- [ ] Update `bd doctor` recommendations

## Migration Guide (Optional)

For users currently using MCP:

```markdown
### Migrating from Full MCP to CLI + Hooks

Already using full MCP server? You can switch to the more efficient CLI approach:

1. Install hooks: `bd setup claude`
2. Test it works (hooks inject context, agent uses Bash tool)
3. Remove MCP server from `~/.claude/settings.json`
4. Restart Claude Code

You'll get the same functionality with 80-90% less token usage.

### Migrating to Minimal MCP (If Available)

If you find approval prompts annoying for certain commands:

1. Replace full MCP with minimal MCP in `~/.claude/settings.json`
2. Restart Claude Code
3. Verify high-frequency commands (ready, show, list) work via MCP
4. Everything else automatically uses CLI

You'll get 60-80% token reduction vs full MCP while keeping the UX benefits.
```

## Files to Update

- `README.md` - Add recommendation in AI Integration section
- `AGENTS.md` - Add "Choosing Your Integration Approach" section early
- `QUICKSTART.md` - Update AI integration section
- `docs/` - Any other AI integration docs if they exist
- `mcp-server/` - Create minimal variant if hybrid validated

## Future: Update `bd init`

Once validated, update `bd init` to:
- Default to recommending `bd setup claude` (hooks only)
- Mention minimal MCP as option for UX improvement
- Detect existing full MCP and suggest migration
- Provide token usage estimates for each approach

## MCP Server Architecture Note

**Key insight:** MCP server doesn't have to expose all bd functionality.

Current design exposes ~20+ commands (all bd subcommands). This is over-engineered.

**Better design:**
- **Minimal MCP**: 3-5 read-only commands (~2-4k tokens)
- **CLI**: Everything else via Bash tool
- **Hooks**: Context injection via `bd prime`

This achieves best of both worlds:
- Low token usage (efficient)
- No approval prompts for common queries (UX)
- Explicit visibility for state changes (safety)

If validation shows NO meaningful benefit to MCP (even minimal), skip hybrid mode entirely and recommend pure CLI.

- Documentation explains CLI + hooks as recommended approach
- Explains why context size matters (compute/energy/cost/latency)
- Token comparison table shows 80-90% reduction
- Migration guide for existing MCP users
- Only deployed AFTER production validation
- Clear that both approaches are supported


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.


Integration Layer Implementation

Build the adapter layer that makes Agent Mail optional and non-intrusive.

Progress: bd-m9th (Python adapter library) completed with full test coverage. Next: bd-fzbg (update python-agent example with Agent Mail integration).


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.


Add performance benchmarks for multi-repo hydration

The contributor-workflow-analysis.md asserts sub-second queries (line 702) and describes smart caching via file mtime tracking (Decision #4, lines 584-618), but doesn't provide concrete performance benchmarks.

VC's requirement (from VC feedback section):
- Executor polls GetReadyWork() every 5-10 seconds
- Queries must be sub-second (ideally <100ms)
- Smart caching must avoid re-parsing JSONLs on every query

Suggested performance targets to validate:
- File stat overhead: <1ms per repo
- Hydration (when needed): <500ms for typical JSONL (<25k)
- Query (from cache): <10ms
- Total GetReadyWork(): <100ms (VC's requirement)

Also test at scale:
- N=1 repo (baseline)
- N=3 repos (typical)
- N=10 repos (edge case)

These benchmarks are critical for library consumers like VC that run automated polling loops.

- Performance benchmark suite created for multi-repo hydration
- Benchmarks cover file stat, hydration, and query times
- Tests at N=1, N=3, N=10 repo scales
- Results documented in contributor-workflow-analysis.md
- Performance targets met or issues filed for optimization


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.

- Reviewed target files for code quality issues
- Filed discovered issues with detailed reasoning
- Completed sweep according to scope criteria

code-review-sweep review-area:.beads review-area:cmd/bd review-area:internal/rpc


MCP server: "No workspace set" and "chunk longer than limit" errors

Two related errors reported in beads-mcp v0.21:

**Error 1: "No workspace set" after successful set_context**
```
✓ Set beads context
✗ list
  Error calling tool 'list': No workspace set. Either provide workspace_root
  parameter or call set_context() first.
```

Hypothesis: Environment variable persistence issue between MCP tool calls, or ContextVar not being set correctly by @with_workspace decorator.

**Error 2: "Separator is found, but chunk is longer than limit"**
```
✗ list
  Error calling tool 'list': Separator is found, but chunk is longer than limit
```

Hypothesis: MCP protocol output size limit exceeded. Large issue databases may produce JSON output that exceeds MCP stdio buffer limits.

Platform: Fedora 43, using copilot-cli with Sonnet 4.5

Workaround: CLI works fine (`bd list --status open --json`)

## Fixes Implemented

**Issue 1: "No workspace set" after successful set_context** ✅ FIXED

Root cause: os.environ doesn't persist across MCP tool calls. When set_context() set BEADS_WORKING_DIR in os.environ, that change was lost on the next tool call.

Solution:
- Added module-level _workspace_context dict for persistent storage (server.py:51)
- Modified set_context() to store in both persistent dict and os.environ (server.py:265-287)
- Modified with_workspace() decorator to check persistent context first (server.py:129-133)
- Updated where_am_i() to check persistent context (server.py:302-330)

**Issue 2: "chunk longer than limit"** ✅ FIXED

Root cause: MCP stdio protocol has buffer limits. Large issue lists with full dependencies/dependents exceed this.

Solution:
- Reduced default list limit from 50 to 20 (server.py:356, models.py:122)
- Reduced max list limit from 1000 to 100 (models.py:122)
- Strip dependencies/dependents from list() and ready() responses (server.py:343-350, 368-373)
- Full dependency details still available via show() command

## Testing

✅ Python syntax validated with py_compile
✅ Changes are backward compatible
✅ Persistent context falls back to os.environ for compatibility

Users should now be able to call set_context() once and have it persist across all subsequent tool calls. Large databases will no longer cause buffer overflow errors.


Add guidance for self-hosting projects

The contributor-workflow-analysis.md is optimized for OSS contributors making PRs to upstream projects. However, it doesn't address projects like VC that use beads for their own development (self-hosting).

Self-hosting projects differ from OSS contributors:
- No upstream/downstream distinction (they ARE the project)
- May run automated executors (not just humans)
- In bootstrap/early phase (stability matters)
- Single team/owner (not multiple contributors with permissions)

Guidance needed on:
- When self-hosting projects should stay single-repo (default, recommended)
- When they should adopt multi-repo (team planning, multi-phase dev)
- How automated executors should handle multi-repo (if at all)
- Special considerations for projects in bootstrap phase

Examples of self-hosting projects: VC (building itself with beads), internal tools, pet projects

- Section added: 'For Projects Using Beads for Self-Hosting'
- Clear guidance on when to stay single-repo vs adopt multi-repo
- Recommendations for automated executor behavior with multi-repo
- Bootstrap phase considerations documented


Review multi-repo deletion tracking implementation

Thoroughly review the multi-repo deletion tracking fix (bd-4oob):

Files changed:
- cmd/bd/deletion_tracking.go: Added getMultiRepoJSONLPaths() helper
- cmd/bd/daemon_sync.go: Updated snapshot capture/update logic for multi-repo
- cmd/bd/deletion_tracking_test.go: Added 2 new tests (287 lines)

Review focus areas:
1. Correctness: Does getMultiRepoJSONLPaths() handle all edge cases?
2. Performance: Calling getMultiRepoJSONLPaths() 3x per sync (snapshot capture, merge, base update) - should we cache?
3. Error handling: What if some repos fail snapshot operations but others succeed?
4. Race conditions: Multiple daemons in different repos?
5. Test coverage: Are TestMultiRepoDeletionTracking and TestMultiRepoSnapshotIsolation sufficient?
6. Path handling: Absolute vs relative paths, tilde expansion

This is fresh code - needs careful review before considering deletion tracking production-ready.

Code review completed. Overall assessment: Core deletion tracking logic is sound, but error handling and path handling issues make this not yet production-ready for multi-repo scenarios.

Key findings:

CRITICAL ISSUES (Priority 1):
1. Inconsistent error handling in daemon_sync.go - snapshot/merge fail hard but base update warns. Can leave DB in inconsistent state with no rollback. See bd-sjmr.
2. No path normalization in getMultiRepoJSONLPaths() - tilde expansion, relative paths, duplicates not handled. See bd-iye7.

SHOULD FIX (Priority 2):
3. Missing test coverage for edge cases - empty paths, duplicates, partial failures. See bd-kdoh.
4. Performance - getMultiRepoJSONLPaths() called 3x per sync (minor issue). See bd-we4p.

WHAT WORKS WELL:
- Atomic file operations with PID-based temp files
- Good snapshot isolation between repos
- Race condition protection via exclusive locks
- Solid test coverage for happy path scenarios

VERDICT: Address bd-iye7 and bd-sjmr before considering deletion tracking production-ready for multi-repo mode.

Detailed review notes available in conversation history.


Fix validatePreExport to use content hash instead of mtime

validatePreExport() in integrity.go:70 still uses isJSONLNewer() (mtime-based), creating inconsistent behavior. Auto-import correctly uses hasJSONLChanged() (hash-based) but export validation still uses the old mtime approach. This can cause false positive blocks after git operations.

Fix: Replace isJSONLNewer() call with hasJSONLChanged() in validatePreExport().

Impact: Without this fix, the bd-khnb solution is incomplete - we prevent resurrection but still have export blocking issues.


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


Refactor create_test.go to use shared DB setup

Convert TestCreate_* functions to use test suites with shared database setup.

Example transformation:
- Before: 10 separate tests, each with newTestStore()
- After: 1 TestCreate() with 10 t.Run() subtests sharing one DB

Estimated speedup: 10x faster (1 DB setup instead of 10)


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


Refactor AGENTS.md to use external references

Suggestion to use external references (e.g., "ALWAYS REFER TO ./beads/prompt.md") instead of including all instructions directly within AGENTS.md.
Reasons:
1. Agents can follow external references.
2. Prevents context pollution/stuffing in AGENTS.md as more tools append instructions.


troubleshoot GH#278 daemon exits every few secs


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.


Add bd doctor --fix flag to automatically repair issues

Implement a --fix flag for bd doctor that can automatically repair detected issues.

Requirements:
- Add --fix flag to bd doctor command
- Show all fixable issues and prompt for confirmation before applying fixes
- Organize fix implementations under doctor/fix/<type_of_fix>.go
- Each fix type should have its own file (e.g., doctor/fix/hooks.go, doctor/fix/sync.go)
- Display what will be fixed and ask user to confirm (Y/n) before proceeding
- Support fixing issues like:
  - Missing or broken git hooks
  - Sync problems with remote
  - File permission issues
  - Any other auto-repairable issues doctor detects

Implementation notes:
- Maintain separation between detection (existing doctor code) and repair (new fix code)
- Each fix should be idempotent and safe to run multiple times
- Provide clear output about what was fixed
- Log any fixes that fail with actionable error messages


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

Add metadata updates after successful export in daemon_sync.go:

After line 307 in createExportFunc (after 'Exported to JSONL' log):
- Compute current JSONL hash
- Update last_import_hash metadata
- Update last_import_mtime metadata
- Same for createSyncFunc at line 497

This mirrors what import.go does at lines 310-336.

The logic is: after export, DB and JSONL are in sync, so metadata should reflect the current JSONL state to prevent false positives in hasJSONLChanged.

1. After daemon export, last_import_hash matches current JSONL hash
2. After daemon export, last_import_mtime matches current JSONL mtime
3. Subsequent exports don't fail with 'JSONL content has changed'
4. Import still correctly detects external JSONL changes
5. All existing export/import tests pass


bd config set succeeds but doesn't persist to config.toml

Commands like `bd config set daemon.auto_push true` return "Set daemon.auto_push = true" but the config file is never created and `bd info --json | jq '.config'` returns null.

**Steps to reproduce:**
1. Run `bd config set daemon.auto_push true`
2. See success message: "Set daemon.auto_push = true"
3. Check `cat .beads/config.toml` → file doesn't exist
4. Check `bd info --json | jq '.config'` → returns null

**Expected:**
- .beads/config.toml should be created with the setting
- bd info should show the config value

**Impact:**
Users can't enable auto-push/auto-commit via CLI as documented in AGENTS.md


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.

Investigation steps:
1. Check internal/storage/sqlite/migrations.go
2. Verify migration order and dependencies
3. Look at external_ref_column migration specifically
4. Ensure proper table/column creation before index
5. Test fix locally with fresh database
6. Push fix to main
7. Verify CI passes

- All tests pass on main branch
- CI is green
- syncbranch_test.go tests all passing


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._

- Tests written for uncovered functionality
- Test coverage verified
- All tests passing


Channel isolation test - beads

Resetting stale in_progress status from old executor run (13 days old)


Create deployment scripts for GCP

Automated provisioning scripts for GCP Compute Engine deployment.

Acceptance Criteria:
- Terraform/gcloud scripts
- Static IP allocation
- Firewall rules
- NGINX reverse proxy config
- TLS setup (Let's Encrypt)
- Systemd service file

File: deployment/agent-mail/gcp/


Prevent test pollution in production database

The bd-vxdr cleanup revealed test issues were created during manual testing in the production workspace (Nov 2-4, template feature development).

**Root cause:** Manual testing with `./bd create "Test issue"` pollutes the production .beads database.

**Prevention strategies:**
1. Use TEST_DB environment variable for manual testing
2. Add warning when creating issues with "Test" prefix
3. Improve developer docs about testing workflow
4. Consider adding `bd test-mode` command for isolated testing

**Implementation completed:**

1. ✅ Added warning when creating issues with "Test" prefix in production database
   - Shows yellow warning with ⚠ symbol
   - Suggests using BEADS_DB for isolated testing
   - Warning appears in create.go after title validation

2. ✅ Documented BEADS_DB testing workflow in AGENTS.md
   - Added "Testing Workflow" section in Development Guidelines
   - Includes manual testing examples with BEADS_DB
   - Includes automated testing examples with t.TempDir()
   - Clear warning about not polluting production database

3. ⚠ Decided against bd test-mode command
   - BEADS_DB already provides simple, flexible isolation
   - Additional command would add complexity without much benefit
   - Current approach follows Unix philosophy (env vars for config)

**Files modified:**
- cmd/bd/create.go - Added Test prefix warning
- AGENTS.md - Added Testing Workflow section

**Testing:**
- Verified warning appears when creating "Test" prefix issues
- Verified BEADS_DB isolation works correctly
- Built successfully with `go build`


Apply wrapDBError to queries.go