Split internal/rpc/server.go into focused modules

The file `internal/rpc/server.go` is 2,273 lines with 50+ methods, making it difficult to navigate and prone to merge conflicts. Split into 8 focused files with clear responsibilities.

Current structure: Single 2,273-line file with:
- Connection handling
- Request routing
- All 40+ RPC method implementations
- Storage caching
- Health checks & metrics
- Cleanup loops

Target structure:
```
internal/rpc/
├── server.go          # Core server, connection handling (~300 lines)
├── methods_issue.go   # Issue operations (~400 lines)
├── methods_deps.go    # Dependency operations (~200 lines)
├── methods_labels.go  # Label operations (~150 lines)
├── methods_ready.go   # Ready work queries (~150 lines)
├── methods_compact.go # Compaction operations (~200 lines)
├── methods_comments.go # Comment operations (~150 lines)
├── storage_cache.go   # Storage caching logic (~300 lines)
└── health.go          # Health & metrics (~200 lines)
```

Migration strategy:
1. Create new files with appropriate methods
2. Keep `server.go` as main file with core server logic
3. Test incrementally after each file split
4. Final verification with full test suite


Fix Windows-specific test failures in CI

Several tests are failing on Windows but passing on Linux:

**Failing tests:**
- TestFindDatabasePathEnvVar
- TestHashIDs_MultiCloneConverge
- TestHashIDs_IdenticalContentDedup
- TestDatabaseReinitialization (all 5 subtests):
  - fresh_clone_auto_import
  - database_removal_scenario
  - legacy_filename_support
  - precedence_test
  - init_safety_check
- TestFindBeadsDir_NotFound
- TestMetricsSnapshot/uptime (in internal/rpc)

**CI Run:** https://github.com/steveyegge/beads/actions/runs/19015638968

These are likely path separator or filesystem behavior differences between Windows and Linux.

Fixed all Windows path issues:
1. TestFindDatabasePathEnvVar - expects canonicalized paths ✅
2. TestHashIDs tests - use platform-specific bd.exe command ✅
3. TestMetricsSnapshot/uptime - enforce minimum 1 second uptime ✅
4. TestFindBeadsDir_NotFound - allow finding .beads in parent dirs ✅
5. TestDatabaseReinitialization - fix git path conversion on Windows (git returns /c/Users/... but filepath needs C:\Users\...) ✅

CI run in progress to verify all fixes.


Message System Improvements

Consolidate improvements to the bd message command including core functionality (message reading), reliability (timeouts), validation, and code quality refactoring


Publish official devcontainer configuration

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


Debug MCP Agent Mail tool execution errors

**EXTERNAL WORK**: Debug the standalone MCP Agent Mail server (separate from beads integration).

The Agent Mail server runs as an independent service at ~/src/mcp_agent_mail. This is NOT beads code - it's a separate GitHub project we're evaluating for optional coordination features.

Current Issue:
- MCP API endpoint returns errors when calling ensure_project tool
- Error: "Server encountered an unexpected error while executing tool"
- Core HTTP server works, web UI functional, but tool wrapper layer fails

Server Details:
- Location: ~/src/mcp_agent_mail (separate repo)
- Repository: https://github.com/Dicklesworthstone/mcp_agent_mail
- Runs on: http://127.0.0.1:8765
- Bearer token: In .env file

Investigation Steps:
1. Check tool execution logs for full stack trace
2. Verify Git storage initialization at ~/.mcp_agent_mail_git_mailbox_repo
3. Review database setup (storage.sqlite3)
4. Test with simpler MCP tools if available
5. Compare with working test cases in tests/

Why This Matters:
- Blocks [deleted:bd-6hji] (testing file reservations)
- Need working MCP API to validate Agent Mail benefits
- Proof of concept for lightweight beads integration later

Note: The actual beads integration (bd-wfmw) will be lightweight HTTP client code only.


Add TestNWayCollision for 5+ clones

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

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

## Implementation Tasks

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

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

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

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

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

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

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

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

    tmpDir := t.TempDir()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Testing Strategy

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

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

## Dependencies
- Requires bd-cbed9619.5, bd-cbed9619.4, bd-cbed9619.3, bd-dcd6f14b to be completed
- TestThreeCloneCollision must pass first

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


Update docs for import bug fix

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


fix: bd onboard hangs on Windows


Implicit deletion cases do not check for tombstones in remaining side

In merge3WayWithTTL(), the implicit deletion cases (lines 426-435) handle when an issue exists in base and one side but not the other. The current logic does not check if the remaining side is a tombstone:

Case: inBase && inLeft && !inRight (deleted implicitly in right)
- If leftIssue is a tombstone, we should keep it (tombstone propagation)
- Currently: continue (drops the tombstone)

Case: inBase && !inLeft && inRight (deleted implicitly in left)
- If rightIssue is a tombstone, we should keep it
- Currently: continue (drops the tombstone)

This could cause tombstones to be lost during merge if one side has a tombstone and the other side implicitly deleted. The tombstone should win since its an explicit deletion record.

Files: internal/merge/merge.go:426-435


Fix mypy errors in beads-mcp

Running `mypy .` in `integrations/beads-mcp` reports 287 errors. These should be addressed to improve type safety and code quality.


Improve timestamp comparison readability in import

The timestamp comparison logic uses double-negative which can be confusing:

Current code:
if !incoming.UpdatedAt.After(existing.UpdatedAt) {
    // skip update
}

More readable:
if incoming.UpdatedAt.After(existing.UpdatedAt) {
    // perform update
} else {
    // skip (local is newer)
}

This is a minor refactor for code clarity.

Related: bd-1022
Files: internal/importer/importer.go:411, 488


Add MCP server functions for repair commands

Expose new repair commands via MCP server for agent access:

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

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

See repair_commands.md lines 803-884 for design.

Implemented all three MCP server functions:

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

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

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


Investigate and optimize test suite performance

Test suite is taking very long to run (>45s for cmd/bd tests, full suite timing unknown but was cancelled).

This impacts development velocity and CI/CD performance.

Investigation needed:
- Profile which tests are slowest
- Identify bottlenecks (disk I/O, network, excessive setup/teardown?)
- Consider parallelization opportunities
- Look for redundant test cases
- Check if integration tests can be optimized


Fix FOREIGN KEY constraint failures in AddComment and ApplyCompaction

The 'CloseIssue', 'UpdateIssueID', and 'RemoveLabel' methods were fixed in PR #348 to prevent foreign key constraint failures when operating on non-existent issues.

However, the Oracle identified two other methods that follow the same problematic pattern:
1. `AddComment` (in `internal/storage/sqlite/events.go`)
2. `ApplyCompaction` (in `internal/storage/sqlite/compact.go`)

These methods attempt to insert an event record after updating the issue, without verifying that the issue update actually affected any rows. This leads to a foreign key constraint failure if the issue does not exist.

We need to:
1. Create reproduction tests for these failure cases
2. Apply the same fix pattern: check `RowsAffected()` after the update, and return a proper "issue not found" error if it is 0, before attempting to insert the event.
3. Standardize the error message format to "issue %s not found" or "issue not found: %s" for consistency.


Testing event-driven mode!


Remove unreachable RPC methods

Several RPC server and client methods are unreachable and should be removed:

Server methods (internal/rpc/server.go):
- `Server.GetLastImportTime` (line 2116)
- `Server.SetLastImportTime` (line 2123)
- `Server.findJSONLPath` (line 2255)

Client methods (internal/rpc/client.go):
- `Client.Import` (line 311) - RPC import not used (daemon uses autoimport)

Evidence:
```bash
go run golang.org/x/tools/cmd/deadcode@latest -test ./...
```

Impact: Removes ~80 LOC of unused RPC code


Add AI planning docs management guidance to bd onboard (GH-196)

Enhanced bd onboard command to provide guidance for managing AI-generated planning documents (Claude slop).

Addresses GitHub issue #196: https://github.com/steveyegge/beads/issues/196

Changes:
- Added Managing AI-Generated Planning Documents section to bd onboard
- Recommends using history/ directory for ephemeral planning files
- Updated AGENTS.md to demonstrate the pattern
- Added comprehensive tests

Commit: d46177d


Add tests for bd init --contributor wizard

Write integration tests for the contributor wizard:
- Test fork detection logic
- Test planning repo creation
- Test config setup
- Test with/without upstream remote
- Test with SSH vs HTTPS origins


Migration and onboarding for multi-repo

Create migration tools, wizards, and documentation to help users adopt multi-repo workflow, with special focus on OSS contributor onboarding and team adoption scenarios.


Improve test coverage for cmd/bd/setup (28.4% → 50%)

The setup package has only 28.4% test coverage. Setup commands are critical for first-time user experience.

Current coverage: 28.4%
Target coverage: 50%


Sync validation false positive: legitimate deletions trigger 'data loss detected'

## Problem
`bd sync` fails with false positive data loss detection when legitimate deletions occur:
```
Post-import validation failed: import reduced issue count: 26 → 25 (data loss detected!)
```

## Root Cause
The validation in `sync.go:329-340` counts DB issues BEFORE import, but `purgeDeletedIssues()` in `importer.go:159` legitimately removes issues DURING import. The validation doesn't account for expected deletions.

**The Flow:**
```
sync.go:293     → beforeCount = countDBIssues() = 26
sync.go:310-319 → sanitizeJSONL removes deleted issues from JSONL (RemovedCount=N)
sync.go:323     → importFromJSONL() runs subprocess
                  └→ importer.go:159 purgeDeletedIssues() removes issues from DB
sync.go:331     → afterCount = countDBIssues() = 25
sync.go:335     → validatePostImport(26, 25) → ERROR
```

## Fix
Pass `sanitizeResult.RemovedCount` to validation and account for expected deletions:

```go
// sync.go around line 335
expectedDecrease := 0
if sanitizeResult != nil {
    expectedDecrease = sanitizeResult.RemovedCount
}
if err := validatePostImportWithDeletions(beforeCount, afterCount, expectedDecrease); err != nil {
    // ...
}
```

```go
// integrity.go - new or modified function
func validatePostImportWithDeletions(before, after, expectedDeletions int) error {
    if after < before - expectedDeletions {
        return fmt.Errorf("unexpected data loss: %d → %d (expected max decrease: %d)",
            before, after, expectedDeletions)
    }
    // ...
}
```

## Files
- cmd/bd/sync.go:329-340
- cmd/bd/integrity.go:289-301


Fix hasJSONLChanged to support per-repo metadata keys

## Problem
After bd-ar2.2, we store metadata with per-repo keys like `last_import_hash:<path>`, but `hasJSONLChanged` and `validatePreExport` still only check global keys.

## Impact
- Multi-repo mode won't benefit from bd-ymj fix
- validatePreExport will fail incorrectly or pass incorrectly
- Metadata updates are written but never read

## Location
- cmd/bd/integrity.go:97 (hasJSONLChanged)
- cmd/bd/integrity.go:138 (validatePreExport)

## Solution
Update hasJSONLChanged to accept optional keySuffix parameter:
```go
func hasJSONLChanged(ctx context.Context, store storage.Storage, jsonlPath string, keySuffix string) bool {
    // Build metadata keys with optional suffix
    hashKey := "last_import_hash"
    mtimeKey := "last_import_mtime"
    if keySuffix != "" {
        hashKey += ":" + keySuffix
        mtimeKey += ":" + keySuffix
    }
    // ... rest of function
}
```

Update all callers to pass correct keySuffix.


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

## Problem

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

## Issues

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

## Proposed Fix

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

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


Issue 1 to reopen


Auto-migrate database on CLI version bump

When CLI is upgraded (e.g., 0.24.0 → 0.24.1), database version becomes stale. Add auto-migration in PersistentPreRun or daemon startup. Check dbVersion != CLIVersion and run bd migrate automatically. Fixes recurring UX issue where bd doctor shows version mismatch after every CLI upgrade.


Add docs/architecture/event_driven.md

Copy event_driven_daemon.md into docs/ folder. Add to documentation index.


Batch test 2


wish: list & ready show issues as hierarchy tree

`bd ready` and `bd list` just show a flat list, and it's up to the reader to parse which ones are dependent or sub-issues of others. It would be much easier to understand if they were shown in a tree format


Audit and fix JSONL filename mismatches across all repo clones

## Problem

Multiple clones of repos are configured with different JSONL filenames (issues.jsonl vs beads.jsonl), causing:
1. JSONL files to be resurrected after deletion (one clone pushes issues.jsonl, another pushes beads.jsonl)
2. Agents unable to see issues filed by other agents after sync
3. Merge conflicts and data inconsistencies

## Root Cause

When repos were "bd doctored" or initialized at different times, some got issues.jsonl (old default) and others got beads.jsonl (Beads repo specific). These clones push their respective files, creating duplicates.

## Task

Scan all repo clones under ~/src/ (1-2 levels deep) and standardize their JSONL configuration.

### Step 1: Find all beads-enabled repos

```bash
# Find all directories named 'beads' at levels 1-2 under ~/src/
find ~/src -maxdepth 2 -type d -name beads
```

### Step 2: For each repo found, check configuration

For each directory from Step 1, check:
- Does `.beads/metadata.json` exist?
- What is the `jsonl_export` value?
- What JSONL files actually exist in `.beads/`?
- Are there multiple JSONL files (problem!)?

### Step 3: Create audit report

Generate a report showing:
```
Repo Path                           | Config        | Actual Files           | Status
----------------------------------- | ------------- | ---------------------- | --------
~/src/beads                         | beads.jsonl   | beads.jsonl            | OK
~/src/dave/beads                    | issues.jsonl  | issues.jsonl           | MISMATCH
~/src/emma/beads                    | issues.jsonl  | issues.jsonl, beads.jsonl | DUPLICATE!
```

### Step 4: Determine canonical name for each repo

For repos that are the SAME git repository (check `git remote -v`):
- Group them together
- Determine which JSONL filename should be canonical (majority wins, or beads.jsonl for the beads repo itself)
- List which clones need to be updated

### Step 5: Generate fix script

Create a script that for each mismatched clone:
1. Updates `.beads/metadata.json` to use the canonical name
2. If JSONL file needs renaming: `git mv .beads/old.jsonl .beads/new.jsonl`
3. Removes any duplicate JSONL files: `git rm .beads/duplicate.jsonl`
4. Commits the change
5. Syncs: `bd sync`

### Expected Output

1. Audit report showing all repos and their config status
2. List of repos grouped by git remote (same repository)
3. Fix script or manual instructions for standardizing each repo
4. Verification that after fixes, all clones of the same repo use the same JSONL filename

### Edge Cases

- Handle repos without metadata.json (use default discovery)
- Handle repos with no git remote (standalone/local)
- Handle repos that are not git repositories
- Don't modify repos with uncommitted changes (warn instead)

### Success Criteria

- All clones of the same git repository use the same JSONL filename
- No duplicate JSONL files in any repo
- All configurations documented in metadata.json
- bd doctor passes on all repos


Test dependency parent


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?


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


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)


Add test case for symmetric collision (both clones create same ID simultaneously)

TestTwoCloneCollision demonstrates the problem, but we need a simpler unit test for the collision resolver itself.

Test should verify:
- Two issues with same ID, different content
- Content hash determines winner deterministically
- Result is same regardless of which clone imports first
- No title swapping occurs

This can be a simpler test than the full integration test.


WASM integration testing

Comprehensive testing of WASM build. Child of epic bd-44d0.

## Tasks
- [ ] Unit tests for WASM module
- [ ] Integration tests with real JSONL files
- [ ] Test all bd commands for parity
- [ ] Performance benchmarks
- [ ] Test in actual Claude Code Web sandbox
- [ ] Document any limitations

## Test Coverage Target
- >90% of bd CLI commands work identically


Assigned issue


Document 3-clone ID non-determinism in collision resolution

Document the known behavior of 3+ way collision resolution where ID assignments may vary based on sync order, even though content always converges correctly.

Updates needed:
- Update bd-71107098 notes to mark 2-clone case as solved
- Document 3-clone ID non-determinism as known limitation
- Add explanation to ADVANCED.md or collision resolution docs
- Explain why this happens (pairwise hash comparison is deterministic, but multi-way ID allocation uses sync-order dependent counters)
- Clarify trade-offs: content convergence ✅ vs ID stability ❌

Key points to document:
- Hash-based resolution is pairwise deterministic
- Content always converges correctly (all issues present with correct data)
- Numeric ID assignments in 3+ way collisions depend on sync order
- This is acceptable for most use cases (content convergence is primary goal)
- Full determinism would require complex multi-way comparison


Refactor path canonicalization into helper function

The path canonicalization logic (filepath.Abs + EvalSymlinks) is duplicated in 3 places:
- beads.go:131-137 (BEADS_DIR handling)
- cmd/bd/main.go:446-451 (--no-db cleanup)
- cmd/bd/nodb.go:26-31 (--no-db initialization)

Refactoring suggestion:
Extract to a helper function like:
  func canonicalizePath(path string) string

This would:
- Reduce code duplication
- Make the logic easier to maintain
- Ensure consistent behavior across all path handling

Related to bd-e16b implementation.


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

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

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

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

All fixes merged. Ready for v0.22.2 release.


Fixture Generator for Realistic Test Data

Create internal/testutil/fixtures/fixtures.go with functions to generate realistic test data at scale.

Functions:
- LargeSQLite(storage) - 10K issues, native SQLite
- XLargeSQLite(storage) - 20K issues, native SQLite
- LargeFromJSONL(storage) - 10K issues imported from JSONL
- XLargeFromJSONL(storage) - 20K issues imported from JSONL

Data characteristics:
- Epic hierarchies (depth 4): Epic → Feature → Task → Subtask
- Cross-linked dependencies (tasks blocking across epics)
- Realistic status/priority/label distribution
- Representative assignees and temporal data

Implementation:
- Single file: internal/testutil/fixtures/fixtures.go
- No config structs, simple direct functions
- Seeded RNG for reproducibility
- Reusable by both benchmarks and tests


Auto-configure git merge driver during `bd init`

Enhance `bd init` to optionally set up beads-merge as git merge driver.

**Tasks**:
- Prompt user to install git merge driver
- Configure `.git/config`: `merge.beads.driver "bd merge %A %O %L %R"`
- Create/update `.gitattributes`: `.beads/beads.jsonl merge=beads`
- Add `--skip-merge-driver` flag for non-interactive use
- Update AGENTS.md onboarding section

**Files**:
- `cmd/bd/init.go`
- `.gitattributes` template


Make two-clone workflow actually work (no hacks)

TestTwoCloneCollision proves beads CANNOT handle two independent clones filing issues simultaneously. This is the basic collaborative workflow and it must work cleanly.

Test location: beads_twoclone_test.go

The test creates two git clones, both file issues with same ID (test-1), --resolve-collisions remaps clone B's to test-2, but after sync:
- Clone A has test-1="Issue from clone A", test-2="Issue from clone B"
- Clone B has test-1="Issue from clone B", test-2="Issue from clone A"

The TITLES are swapped! Both clones have 2 issues but with opposite title assignments.

We've tried many fixes (per-project daemons, auto-sync, lamport hashing, precommit hooks) but nothing has made the test pass.

Goal: Make the test pass WITHOUT hacks. The two clones should converge to identical state after sync.

**Major progress achieved!** The two-clone workflow now converges correctly.

**What was fixed:**
--3d844c58: Implemented content-hash based rename detection
- bd-64c05d00.1: Fixed test to compare content not timestamps
- Both clones now converge to identical issue databases
- test-1 and test-2 have correct titles in both clones
- No more title swapping!

**Current status (VERIFIED):**
✅ Acceptance criteria 1: TestTwoCloneCollision passes (confirmed Oct 28)
✅ Acceptance criteria 2: Both clones converge to identical issue database (content matches)
✅ Acceptance criteria 3: No manual conflict resolution required (automatic)
✅ Acceptance criteria 4: Git status clean
✅ Acceptance criteria 5: bd ready output identical (timestamps are expected difference)

**ALL ACCEPTANCE CRITERIA MET!** This issue is complete and can be closed.


bd sync corrupts issues.jsonl in multi-clone environments

During parallel swarm operations on 2025-11-29, bd sync corrupted the issues database. Commit 93718056 purged all 80 issues from issues.jsonl.

Reproduction:
1. Multiple clones of same repo (polecat swarm)
2. Each clone runs bd sync in parallel
3. One sync overwrites others, resulting in data loss

Even in single-clone scenarios (~/src/beads), running bd sync after git pull seems to nuke the JSONL.

Workaround: Use bd import -i .beads/issues.jsonl --no-git-history instead of bd sync.

Root cause investigation needed. Options:
1. Pessimistic locking (flock)
2. Single point of merge (polecats on ephemeral branches)
3. Beads-level locking (bd sync --lock)


Fresh clone: bd should suggest 'bd init' when no database exists

On a fresh clone of a repo using beads, running `bd stats` or `bd list` gives a cryptic error:

```
Error: failed to open database: post-migration validation failed: migration invariants failed:
  - required_config_present: required config key missing: issue_prefix (database has 2 issues)
```

**Expected**: A helpful message like:
```
No database found. This appears to be a fresh clone.
Run 'bd init --prefix <prefix>' to hydrate from the committed JSONL file.
Found: .beads/beads.jsonl (38 issues)
```

**Why this matters**: The current UX is confusing for new contributors or fresh clones. The happy path should be obvious.


Update import/export to handle tombstones

Modify importer to read tombstones from issues.jsonl and legacy deletions.jsonl. Modify exporter to write tombstones. Filter tombstoned issues during import. Per design bd-dli.


Add label-based filtering to bd ready command

Allow filtering ready work by labels to help organize work by sprint, week, or category.

Example usage:
  bd ready --label week1-2
  bd ready --label frontend,high-priority

This helps teams organize work into batches and makes it easier for agents to focus on specific categories of work.

Implementation notes:
- Add --label flag to ready command
- Support comma-separated labels (AND logic)
- Should work with existing ready work logic (unblocked issues)


Document shared memory test isolation pattern in test_helpers.go

Tests were failing because :memory: creates a shared database across all tests. The fix is to use "file::memory:?mode=memory&cache=private" for test isolation.

Should document this pattern in test_helpers.go and potentially update newTestStore to use private memory by default.


Auto-import runs during --no-auto-import operations via stats/ready commands

Even when using --no-auto-import flag, certain commands like 'bd stats' and 'bd ready' still trigger auto-import internally, which can cause the git history backfill bug to corrupt data.

Example:
  bd stats --no-auto-import
  # Still prints 'Purged bd-xxx (recovered from git history...)'

The flag should completely disable auto-import for the command, but it appears some code paths still trigger it.

Workaround: Use --allow-stale instead, or --sandbox mode.


Update AGENTS.md with event-driven mode

Document BEADS_DAEMON_MODE env var. Explain opt-in during Phase 1. Add troubleshooting for watcher failures.


Client self-heal: remove stale pid when lock free + socket missing

When client detects:
- Socket is missing AND
- tryDaemonLock shows lock NOT held

Then automatically:
1. Remove stale daemon.pid file
2. Optionally auto-start daemon (behind BEADS_AUTO_START_DAEMON=1 env var)

This prevents stale artifacts from accumulating after daemon crashes.


Add --json flags to all bd commands for agent-friendly output


Add template support for issue creation

Support creating issues from predefined templates to streamline common workflows like epics, bug reports, or feature proposals.

Example usage:
  bd create --from-template epic "Phase 3 Features"
  bd create --from-template bug "Login failure"
  bd template list
  bd template create epic

Templates should include:
- Pre-filled description structure
- Suggested priority and type
- Common labels
- Design/acceptance criteria sections

Implementation notes:
- Store templates in .beads/templates/ directory
- Support YAML or JSON format
- Ship with built-in templates (epic, bug, feature)
- Allow custom project-specific templates


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`


Upgrade notification persists after running suggested commands

The upgrade notification kept showing even after running 'bd upgrade review' and 'bd doctor' as suggested.

Expected: Notification should be dismissed after running either/both commands
Actual: Notification persisted on every bd command

Investigation findings:
- metadata.json correctly shows last_bd_version=0.28.0
- The notification stopped appearing after a few more invocations
- May be related to daemon caching or a race condition
- The 'upgradeAcknowledged' flag in version_tracking.go:115 only persists per-session (in-memory)

Possible causes:
1. Daemon vs direct mode inconsistency - daemon may have stale version info
2. The version is checked/updated before daemon connection, but daemon has cached state
3. Race between trackBdVersion() and daemon startup

Status: May have resolved itself, but root cause unclear. Close if no longer reproducible.


Fix Nix flake build test failures

Nix build is failing during test phase with same test errors as Windows.

**Error:**
```
error: Cannot build '/nix/store/rgyi1j44dm6ylrzlg2h3z97axmfq9hzr-beads-0.9.9.drv'.
Reason: builder failed with exit code 1.
FAIL github.com/steveyegge/beads/cmd/bd 16.141s
```

This may be related to test environment setup or the same issues affecting Windows tests.


Update git hooks to be sync.branch aware

## Problem

The pre-push hook blocks pushes when .beads/issues.jsonl has uncommitted changes. But with sync.branch configured, those changes are intentionally NOT committed to main - they go to the sync branch via worktree.

## Current Behavior

1. User configures sync.branch=beads-sync
2. bd sync commits changes to beads-sync via worktree
3. Local .beads/issues.jsonl is updated (needed for import)
4. git push to main triggers pre-push hook
5. Hook sees uncommitted .beads changes and blocks push
6. User must use --no-verify to push

## Expected Behavior

When sync.branch is configured, the pre-push hook should:
1. Check if sync.branch is set (bd config get sync.branch)
2. If set, skip the .beads uncommitted check OR
3. Verify changes are committed to the sync branch instead

## Affected Files

- examples/git-hooks/pre-push
- examples/git-hooks/pre-commit (may also need update)

## Workaround

Use git push --no-verify


Refactor check-health DB access to avoid repeated path resolution

The runCheckHealth lightweight checks (hintsDisabled, checkVersionMismatch, checkSyncBranchQuick) each have duplicated database path resolution logic. Extract a helper function to DRY this up.


Add 'bd comment' as alias for 'bd comments add'

The command 'bd comments add' is verbose and unintuitive. Add 'bd comment' as a shorter alias that works the same way.

## Rationale
- More natural: 'bd comment <issue-id> <text>' reads better than 'bd comments add <issue-id> <text>'
- Matches user expectations: users naturally try 'bd comment' first
- Follows convention: other commands like 'bd create', 'bd show', 'bd close' are verbs

## Implementation
Could be implemented as:
1. A new command that wraps bd comments add
2. An alias registered in cobra
3. Keep 'bd comments add' for backwards compatibility

## Examples
```bash
bd comment bd-1234 'This is a comment'
bd comment bd-1234 'Multi-line comment' --body 'Additional details here'
```


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.


Implement topological sort for import ordering

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


Integration test: mutation to export latency

Measure time from bd create to JSONL update. Verify <500ms latency. Test with multiple rapid mutations to verify batching.


Sync protection mechanism resurrects deleted issues

During bd sync, the 'Protecting N issue(s) from left snapshot' logic resurrects issues that are in the deletions manifest, causing UNIQUE constraint failures on subsequent syncs.

## Reproduction
1. Delete an issue with bd delete (creates tombstone + deletions.jsonl entry)
2. Run bd sync - succeeds
3. Run bd sync again - fails with 'UNIQUE constraint failed: issues.id'

## Root Cause
The protection mechanism (designed to prevent data loss during 3-way merge) keeps a snapshot of issues before sync. When importing after pull, it restores issues from this snapshot even if they're in the deletions manifest.

## Observed Behavior
- bd-3pd was deleted and added to deletions.jsonl
- First sync exports tombstone, commits, pulls, imports - succeeds
- Second sync: protection restores bd-3pd from left snapshot
- Import tries to create bd-3pd which already exists → UNIQUE constraint error

## Expected Behavior
Issues in deletions manifest should NOT be restored by protection mechanism.

## Workaround
Manually delete from DB: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE id = "bd-xxx"'

## Files to Investigate
- cmd/bd/sync.go - protection logic
- cmd/bd/snapshot_manager.go - left snapshot handling
- internal/importer/importer.go - import with protection


Update documentation for merge driver auto-config

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

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

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


Add WaitGroup tracking to FileWatcher goroutines

FileWatcher spawns goroutines without WaitGroup tracking, causing race condition on shutdown.

Location: cmd/bd/daemon_watcher.go:123-182, 215-291

Problem:
- Goroutines spawned without sync.WaitGroup
- Close() cancels context but doesn't wait for goroutines to exit
- Race condition: goroutine may access fw.debouncer during Close() cleanup
- No guarantee goroutine stopped before fw.watcher.Close() is called

Solution:
- Add sync.WaitGroup field to FileWatcher
- Track goroutines with wg.Add(1) and defer wg.Done()
- Call wg.Wait() in Close() before cleanup

Impact: Race condition on daemon shutdown; potential panic

Effort: 2 hours


Respond to GitHub issue #479 with solution

Once bd reset is implemented and released, respond to GitHub issue #479.

## Response should include
- Announce the new bd reset command
- Show basic usage examples
- Link to any documentation
- Thank the user for the feedback

## Example response
```
Thanks for raising this! We've added a `bd reset` command to handle this case.

Usage:
- `bd reset` - Reset to clean state (prompts for confirmation)
- `bd reset --backup` - Create backup first
- `bd reset --hard` - Also clean up git history

This is available in version X.Y.Z.
```

## Notes
- Wait until feature is merged and released
- Consider if issue should be closed or left for user confirmation


Fix SyncJSONLToWorktree to merge instead of overwrite

SyncJSONLToWorktree blindly copies local JSONL to worktree, overwriting remote issues.

## Root Cause (GitHub #464)
When Computer B syncs with fewer issues than remote:
1. preemptiveFetchAndFastForward updates worktree (10 issues)
2. SyncJSONLToWorktree overwrites with local (1 issue)
3. Commit records deletion of 9 issues
4. 3-way merge sees "deletion in left" → deletion wins

## Fix
Before copying, check if worktree has more issues. If so:
- Option A: Merge local into worktree (preferred)
- Option B: Fail with helpful error message
- Option C: Warn and require --force flag

## Location
internal/syncbranch/worktree.go:107 - SyncJSONLToWorktree call

## Test Case
1. Clone repo with sync-branch containing issues
2. bd init (starts empty since JSONL on sync-branch)
3. bd create "test"
4. bd sync
5. Verify all issues preserved, not just "test"


Consolidate check-health DB opens into single connection

The --check-health flag opens the database 3 separate times (once per quick check). Consolidate into a single DB open for better performance, especially on slower filesystems.


Issue 2


Update bd compact to prune expired tombstones

Modify compaction to identify and remove expired tombstones from issues.jsonl. Report count of removed tombstones. Per design bd-1r5.


Add uninstall documentation (GH #445)


Frontend task


bd doctor --fix needs non-interactive mode (-y/--yes flag)

When running `bd doctor --fix` in non-interactive mode (scripts, CI, Claude Code), it prompts 'Continue? (Y/n):' and fails with EOF.

**Expected**: A `-y` or `--yes` flag to auto-confirm fixes.

**Workaround**: Currently have to run `bd init` instead, but that's not discoverable from the doctor output.


Optimize CLI test suite - eliminate redundant git init calls

Current: Each of 13 CLI tests calls git init (31s total). Solution: Use single test binary built once in init(), skip git operations where possible, or use mock filesystem.


Extract daemon sync functions to daemon_sync.go


Create cmd/bd/daemon_debouncer.go (~60 LOC)

Implement Debouncer to batch rapid events into single action. Default 500ms, configurable via BEADS_DEBOUNCE_MS. Thread-safe with mutex.


Issue to reopen with reason


Update documentation for hash IDs and aliases

Update all documentation to explain hash-based IDs and aliasing system.

## Files to Update

### 1. README.md
Add section explaining hash IDs:
```markdown
## Issue IDs

Beads uses **hash-based IDs** for collision-free distributed issue tracking:

- **Hash ID**: `bd-af78e9a2` (8-char SHA256 prefix, immutable, globally unique)
- **Alias**: `#42` (sequential number, mutable, human-friendly)

### Using IDs
```bash
bd show bd-af78e9a2   # Use hash ID
bd show #42           # Use alias
bd show 42            # Use alias (shorthand)
```

### Why Hash IDs?
- **Collision-free**: Work offline without ID conflicts
- **Distributed**: No coordination needed between clones
- **Git-friendly**: Different IDs = different JSONL lines, fewer merge conflicts

### Aliases
Aliases are workspace-local shortcuts for hash IDs. They're:
- Automatically assigned on issue creation
- Reassigned deterministically on sync (if conflicts)
- Can be manually controlled with `bd alias` commands
```

### 2. AGENTS.md
Update agent workflow:
```markdown
## Hash-Based IDs (v2.0+)

Beads v2.0 uses hash-based IDs to eliminate collision problems:

**When creating issues**:
```bash
bd create "Fix bug" -p 1
# → Creates bd-af78e9a2 with alias #1
```

**When referencing issues**:
- In text: Use hash IDs (stable): "See bd-af78e9a2 for details"
- In CLI: Use aliases (readable): `bd update #42 --status done`

**After sync**:
- Alias conflicts resolved automatically (content-hash ordering)
- No ID collisions possible
- No remapping needed

**Migration from v1.x**:
```bash
bd migrate --hash-ids  # One-time migration
```
```

### 3. QUICKSTART.md (if exists)
Show alias usage in examples:
```bash
# Create issue (gets hash ID + alias)
bd create "Fix authentication bug" -p 1
# → Created bd-af78e9a2 (alias: #1)

# Reference by alias
bd show #1
bd update #1 --status in_progress
bd close #1 --reason "Fixed"
```

### 4. ADVANCED.md
Add section on hash ID internals:
```markdown
## Hash ID Generation

Hash IDs are generated deterministically:

```go
SHA256(title || description || timestamp || workspace_id)[:8]
```

**Collision probability**:
- 8 hex chars = 2^32 space = ~4 billion IDs
- Birthday paradox: 50% collision probability at ~65,000 issues
- For typical projects (<10,000 issues), collision risk is negligible

**Collision detection**:
If a hash collision occurs (extremely rare), beads:
1. Detects on insert (UNIQUE constraint)
2. Appends random suffix: `bd-af78e9a2-a1b2`
3. Retries insert

## Alias Conflict Resolution

When multiple clones assign same alias to different issues:

**Strategy**: Content-hash ordering (deterministic)
- Sort conflicting issue IDs lexicographically
- Lowest hash ID keeps the alias
- Others reassigned to next available aliases

**Example**:
```
Clone A: Assigns #42 to bd-a1b2c3d4
Clone B: Assigns #42 to bd-e5f6a7b8
After sync: bd-a1b2c3d4 keeps #42 (lower hash)
            bd-e5f6a7b8 gets #100 (next available)
```
```

### 5. MIGRATION.md (new file)
```markdown
# Migrating to Hash-Based IDs (v2.0)

## Overview
Beads v2.0 introduces hash-based IDs to eliminate collision problems. This is a **breaking change** requiring migration.

## Migration Steps

### 1. Backup
```bash
cp -r .beads .beads.backup
git commit -am "Pre-migration backup"
```

### 2. Run Migration
```bash
# Dry run first
bd migrate --hash-ids --dry-run

# Apply migration
bd migrate --hash-ids
```

### 3. Commit Changes
```bash
git add .beads/issues.jsonl
git commit -m "Migrate to hash-based IDs (v2.0)"
git push origin main
```

### 4. Coordinate with Collaborators
All clones must migrate before syncing:
1. Notify team: "Migrating to v2.0 on [date]"
2. All collaborators pull latest
3. All run `bd migrate --hash-ids`
4. All push changes
5. Resume normal work

## Rollback
```bash
# Restore backup
mv .beads.backup .beads
bd export  # Regenerate JSONL
git checkout .beads/issues.jsonl
```

## FAQ

**Q: Can I mix v1.x and v2.0 clones?**
A: No. All clones must be on same version.

**Q: Will my old issue IDs work?**
A: No, but aliases preserve the numbers: bd-1c63eb84 → #1

**Q: What happens to links like "see bd-cb64c226.3"?**
A: Migration updates all text references automatically.
```

### 6. CHANGELOG.md
```markdown
## v2.0.0 (YYYY-MM-DD)

### Breaking Changes
- **Hash-based IDs**: Issues now use collision-free hash IDs (bd-af78e9a2)
  instead of sequential IDs (bd-1c63eb84, bd-9063acda)
- **Aliasing system**: Human-friendly aliases (#42) for hash IDs
- **Migration required**: Run `bd migrate --hash-ids` to convert v1.x databases

### Added
- `bd alias` command for manual alias control
- `bd migrate --hash-ids` migration tool
- Alias conflict resolution (deterministic, content-hash ordering)

### Removed
- ID collision detection and resolution (~2,100 LOC)
- `bd import --resolve-collisions` flag (no longer needed)

### Benefits
- ✅ Zero ID collisions in distributed workflows
- ✅ Simpler codebase (-1,350 net LOC)
- ✅ Better git merge behavior
- ✅ True offline-first operation
```

## Testing
- Build docs locally (if using doc generator)
- Check all links work
- Verify examples are correct
- Spellcheck

## Files to Create/Modify
- README.md (hash ID section)
- AGENTS.md (workflow updates)
- ADVANCED.md (internals)
- MIGRATION.md (new)
- CHANGELOG.md (v2.0 entry)
- docs/ (any other docs)


Isolate RPC server startup into rpc_server.go

Create internal/daemonrunner/rpc_server.go with StartRPC function. Move startRPCServer logic here and return typed handle.


Add integration test for cross-clone deletion propagation


Build Node.js CLI wrapper for WASM

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

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

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


Design tombstone TTL and expiration semantics

Design the TTL and expiration behavior for tombstone records. Key decisions: default TTL (recommend 30 days), configuration via config.yaml, expiration semantics (remove vs keep expired), merge behavior when tombstone expires (live issue wins), clock skew handling.


Optimize test suite performance - cut runtime by 50%+

## Problem
Test suite takes ~20.8 seconds, with 95% of time spent in just 2 tests:
- TestHashIDs_MultiCloneConverge: 11.08s (53%)
- TestHashIDs_IdenticalContentDedup: 8.78s (42%)

Both tests in beads_hash_multiclone_test.go perform extensive Git operations (bare repos, multiple clones, sync rounds).

## Goal
Cut total test time by at least 50% (to ~10 seconds or less).

## Analysis
Tests already have some optimizations:
- --shared --depth=1 --no-tags for fast cloning
- Disabled hooks, gc, fsync
- Support -short flag

## Impact
- Faster development feedback loop
- Reduced CI costs and time
- Better developer experience


Implement fs bridge layer for WASM (Go syscall/js to Node.js fs)

Go's os package in WASM returns 'not implemented on js' for mkdir and other file operations. Need to create a bridge layer that:

1. Detects WASM environment (GOOS=js)
2. Uses syscall/js to call Node.js fs module functions
3. Implements wrappers for:
   - os.MkdirAll
   - os.ReadFile / os.WriteFile
   - os.Open / os.Create
   - os.Stat / os.Lstat
   - filepath operations

Approach:
- Create internal/wasm/fs_bridge.go with //go:build js && wasm
- Export Node.js fs functions to Go using global.readFileSync, global.writeFileSync, etc.
- Wrap in Go API that matches os package signatures
- Update beads.go and storage layer to use bridge when in WASM

This unblocks bd-4462 (basic WASM testing) and [deleted:bd-5bbf] (feature parity testing).


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.


bd-hv01: Storage backend extensibility broken by type assertion

Problem: deletion_tracking.go:69-82 uses type assertion for DeleteIssue which breaks if someone adds a new storage backend.

Fix: Check capability before starting merge or add DeleteIssue to Storage interface.

Files: cmd/bd/deletion_tracking.go:69-82, internal/storage/storage.go


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

Approaches:
1. Mechanical: Exact title/description matching
2. Embeddings: Cosine similarity (cheap, scalable)
3. AI: LLM-based semantic comparison (expensive, accurate)

Uses embeddings by default for >100 issues.

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


bd doctor: add SQLite integrity check (PRAGMA integrity_check)

Add a database integrity check using SQLite's PRAGMA integrity_check to detect corruption. Currently bd doctor checks schema compatibility but not data integrity. This would help diagnose corrupted databases that pass schema checks but have internal inconsistencies.


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


Extract validation functions to validators.go

Move validatePriority, validateStatus, validateIssueType, validateTitle, validateEstimatedMinutes, validateFieldUpdate to validators.go


Optimize --sandbox mode: skip FlushManager

When `--sandbox` mode is enabled, skip FlushManager creation entirely.

## Current Behavior
`main.go:561` always creates FlushManager:
```go
flushManager = NewFlushManager(autoFlushEnabled, getDebounceDuration())
```

Even with `--sandbox` (which sets `noAutoFlush=true`), the FlushManager goroutine is still running.

## Proposed Change
```go
// Only create FlushManager if we might actually need it
if !sandboxMode && autoFlushEnabled {
    flushManager = NewFlushManager(true, getDebounceDuration())
}
```

## Handle nil FlushManager
Update PersistentPostRun to handle nil:
```go
if flushManager != nil && !skipFinalFlush {
    if err := flushManager.Shutdown(); err != nil {
        // ...
    }
}
```

## Synchronous Export Fallback
When sandbox mode + write operation occurs, do synchronous export:
```go
if sandboxMode && isDirty {
    performSynchronousExport()
}
```

## Also: Default lock-timeout to 100ms in sandbox mode
In PersistentPreRun, when sandboxMode is detected:
```go
if sandboxMode {
    noDaemon = true
    noAutoFlush = true
    noAutoImport = true
    if lockTimeout == 30*time.Second { // Not explicitly set
        lockTimeout = 100 * time.Millisecond
    }
}
```

Part of bd-olc1


Implement CLI command (cmd/bd/reset.go)

Wire up the reset command with Cobra CLI.

## Responsibilities
- Define command and all flags
- User confirmation prompt (unless --force)
- Display impact summary before confirmation
- Colored output and progress indicators
- Call core reset package
- Handle errors with user-friendly messages
- Register command with rootCmd in init()

## Flags
```go
--hard       bool   "Also remove from git and commit"
--force      bool   "Skip confirmation prompt"
--backup     bool   "Create backup before reset"
--dry-run    bool   "Preview what would happen"
--skip-init  bool   "Do not re-initialize after reset"
--verbose    bool   "Show detailed progress output"
```

## Output Format
```
⚠  This will reset beads to a clean state.

Will be deleted:
  • 47 issues (23 open, 24 closed)
  • 12 tombstones

Continue? [y/N] y

→ Stopping daemons... ✓
→ Removing .beads/... ✓
→ Initializing fresh... ✓

✓ Reset complete. Run 'bd onboard' to set up hooks.
```

## Implementation Notes
- Confirmation logic lives HERE, not in core package
- Use color package (github.com/fatih/color) for output
- Follow patterns from other commands (init.go, doctor.go)
- Add to rootCmd in init() function

## File Location
`cmd/bd/reset.go`


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

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

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

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

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

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

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


Test Epic 2

## Overview

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

## Success Criteria

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

## Background

[Provide context and motivation]

## Scope

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

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


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.


Implement `bd setup claude` command for Claude Code integration

Create a `bd setup claude` command that installs Claude Code integration files (slash commands and hooks). This is idempotent and safe to run multiple times.


Remove obsolete stale.go command (executor tables never implemented)


bd jira sync command

Add a built-in bd command for Jira synchronization.

**Requires**: Both import and export scripts working

**Features**:
- `bd jira sync --pull` - Import from Jira to beads
- `bd jira sync --push` - Export from beads to Jira
- `bd jira sync` - Bidirectional (pull then push, with conflict resolution)
- `bd jira status` - Show sync status and last sync time

**Conflict resolution**:
- Timestamp-based: newer update wins
- Option for --prefer-local or --prefer-jira to override
- Interactive mode for manual conflict resolution (optional)

**Integration**:
- Uses jira.* config settings from bd config
- Stores last sync timestamp in config
- Logs sync activity for audit

**Stretch goals**:
- Webhook integration for real-time sync
- Selective sync by JQL filter


Need --no-git-history flag to disable git history backfill during import

During JSONL migration (beads.jsonl → issues.jsonl), the git history backfill mechanism causes data loss by finding issues in the old beads.jsonl git history and incorrectly treating them as deleted.

A --no-git-history flag for 'bd import' and 'bd sync' would allow users to disable the git history fallback when it's causing problems.

Use cases:
- JSONL filename migrations
- Repos with complex git history
- Debugging import issues
- Performance (skip slow git scans)

Related: bd-0gh (migration causes spurious deletions)


Extract batch operations to batch_ops.go

Move validateBatchIssues, generateBatchIDs, bulkInsertIssues, bulkRecordEvents, bulkMarkDirty, CreateIssues to batch_ops.go


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


Jira export script (jsonl2jira.py)

Create a Python script to push beads issues to Jira.

**Requires**: Jira import script to be complete first (need external_ref matching logic working)

**Features needed**:
- Create new Jira issues from beads issues without external_ref
- Update existing Jira issues matched by external_ref
- Map beads fields back to Jira fields
- Handle Jira workflow transitions (status changes may need transitions)
- Support custom field mapping for design/acceptance_criteria/notes

**Challenges**:
- Jira status changes often require workflow transitions, not direct updates
- Need to discover valid transitions via API
- Custom fields vary by Jira instance

**Usage**:
```bash
bd export | python jsonl2jira.py --create-only  # Only create, don't update
bd export | python jsonl2jira.py                 # Create and update
```

**After creation**: Sets external_ref on beads issue to link back


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.


Refactor: extract common helpers in sync-branch hook checks

Code review of #532 fix identified duplication between checkSyncBranchHookCompatibility and checkSyncBranchHookQuick.

Suggested refactoring:
1. Extract getPrePushHookPath(path string) helper for git dir + hook path resolution
2. Extract extractHookVersion(hookContent string) helper for version parsing
3. Consider whether Warning for custom hooks is appropriate (vs OK with info message)
4. Document the intentional behavior difference between full check (Warning for custom) and quick check (OK for custom)


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


Fix goroutine leak in daemon restart

Fire-and-forget goroutine in daemon restart leaks on every restart.

Location: cmd/bd/daemons.go:251

Problem:
go func() { _ = daemonCmd.Wait() }()

- Spawns goroutine without timeout or cancellation
- If daemon command never completes, goroutine leaks forever
- Each daemon restart leaks one more goroutine

Solution: Add timeout and cleanup:
go func() {
    done := make(chan struct{})
    go func() {
        _ = daemonCmd.Wait()
        close(done)
    }()

    select {
    case <-done:
        // Exited normally
    case <-time.After(10 * time.Second):
        // Timeout - daemon should have forked by now
        _ = daemonCmd.Process.Kill()
    }
}()

Impact: Goroutine leak on every daemon restart

Effort: 2 hours


Add is_tombstone flag to schema

Optionally add is_tombstone boolean field to issues table. Marks resurrected parents that were deleted. Allows distinguishing tombstones from normal deleted issues. Update schema.go and create migration.


Implement tombstone TTL and expiration logic

Add IsExpired() method to tombstones, implement TTL config reading from config.yaml, add clock skew grace period. Per design bd-1r5.


Test label


Add transaction + retry logic for N-way collision resolution

## Problem
Current N-way collision resolution fails on UNIQUE constraint violations during convergence rounds when 5+ clones sync. The RemapCollisions function is non-atomic and performs operations sequentially:
1. Delete old issues (CASCADE deletes dependencies)
2. Create remapped issues (can fail with UNIQUE constraint)
3. Recreate dependencies
4. Update text references

Failure at step 2 leaves database in inconsistent state.

## Solution
Wrap collision resolution in database transaction with retry logic:
- Make entire RemapCollisions operation atomic
- Retry up to 3 times on UNIQUE constraint failures
- Re-sync counters between retries
- Add better error messages for debugging

## Implementation
Location: internal/storage/sqlite/collision.go:342 (RemapCollisions function)

```go
// Retry up to 3 times on UNIQUE constraint failures
for attempt := 0; attempt < 3; attempt++ {
    err := s.db.ExecInTransaction(func(tx *sql.Tx) error {
        // All collision resolution operations
    })
    if !isUniqueConstraintError(err) {
        return err
    }
    s.SyncAllCounters(ctx)
}
```

## Success Criteria
- 5-clone collision test passes reliably
- No partial state on UNIQUE constraint errors
- Automatic recovery from transient ID conflicts

## References
- See beads_nway_test.go:124 for the KNOWN LIMITATION comment
- Related to-7c5915ae (transaction support)

## Progress Made

1. Added `ExecInTransaction` helper to SQLiteStorage for atomic database operations
2. Added `IsUniqueConstraintError` function to detect UNIQUE constraint violations
3. Wrapped `RemapCollisions` with retry logic (up to 3 attempts) with counter sync between retries
4. Enhanced `handleRename` to detect and handle race conditions where target ID already exists
5. Added defensive checks for when old ID has been deleted by another clone

## Test Results

The changes improve N-way collision handling but don't fully solve the problem:
- Original error: `UNIQUE constraint failed: issues.id` during first convergence round
- With changes: Test proceeds further but encounters different collision scenarios
- New error: `target ID already exists with different content` in later convergence rounds

## Root Cause Analysis

The issue is more complex than initially thought. In N-way scenarios:
1. Clone A remaps bd-1c63eb84 → test-2 → test-4
2. Clone B remaps bd-1c63eb84 → test-3 → test-4
3. Both try to create test-4, but with different intermediate states
4. This creates legitimate content collisions that require additional resolution

## Next Steps

The full solution requires:
1. Making remapping fully deterministic across clones (same input → same remapped ID)
2. OR making `handleRename` more tolerant of mid-flight collisions
3. OR implementing full transaction support for multi-step collision resolution -7c5915ae)

The retry logic added here provides a foundation but isn't sufficient for complex N-way scenarios.


Inconsistent stderr printing: bd-feh warning still prints directly


Add orphan detection migration

Create migration to detect orphaned children in existing databases. Query: SELECT id FROM issues WHERE id LIKE '%.%' AND substr(id, 1, instr(id || '.', '.') - 1) NOT IN (SELECT id FROM issues). Log results, let user decide action (delete orphans or convert to top-level).


isLikelyHash rejects 3-char all-letter base36 hashes

ExtractIssuePrefix fails for multi-hyphen prefixes when the 3-char hash suffix has no digits.

Example: `xa-adt-bat` returns prefix `xa` instead of `xa-adt`.

Root cause: `isLikelyHash()` requires at least one digit to distinguish hashes from English words, but base36 hashes can be all-letters by chance.

Affected: 20 issues in xa-adapt with suffixes like bat, dev, fbi, oil, etc.


Rapid 3


Investigate bd export/import sync issue - database and JSONL out of sync

Observed in VC repo: database has 1137 issues but beads.jsonl only has 309. Running 'bd export -o .beads/issues.jsonl' doesn't update the file. Running 'bd import' hangs indefinitely.

Reproduction context:
- VC repo: 1137 issues in DB, 309 in JSONL
- Created 4 new issues with bd create
- bd export didn't write them to JSONL
- bd import hung (possibly daemon lock conflict?)

Need to investigate:
1. Why export doesn't update JSONL when DB has more issues
2. Why import hangs
3. Daemon lock interaction with export/import
4. File path handling (issues.jsonl vs beads.jsonl)

## Root Cause Analysis

The issue was NOT about file path handling or export failing to update JSONL. The actual problem was:

**Import/Export hanging when daemon is running** due to SQLite lock contention:

1. When daemon is connected, `PersistentPreRun` in main.go returns early without initializing the `store` variable
2. Import/Export commands then tried to open the database directly with `sqlite.New(dbPath)`
3. This blocked waiting for the database lock that the daemon already holds → **HANGS INDEFINITELY**

## Solution Implemented

Modified both import.go and export.go to:
1. Detect when `daemonClient` is connected
2. Explicitly close the daemon connection before opening direct SQLite access
3. Added debug logging to help diagnose similar issues

This ensures import/export commands always run in direct mode, avoiding lock contention.

## File Path Handling

The file path confusion (issues.jsonl vs beads.jsonl) was a red herring. The code uses `filepath.Glob("*.jsonl")` which correctly finds ANY `.jsonl` file in `.beads/` directory, so both filenames work.


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.


Design tombstone merge semantics

Define how tombstones participate in 3-way merge. Key question: does tombstone always win over live issue (like current deletion-wins rule), or should we use updated_at comparison? Consider: tombstone with older timestamp vs live issue with newer edits. Recommendation: tombstone wins unless expired, matching current behavior but with longer TTL window.


Fix FindBeadsDir to prioritize main repo .beads for worktrees

The FindBeadsDir function should prioritize finding .beads in the main repository root when accessed from a worktree, rather than finding worktree-local .beads directories. This ensures proper sharing of the database across all worktrees.


Remove unreachable utility functions

Several small utility functions are unreachable:

Files to clean:
1. `internal/storage/sqlite/hash.go` - `computeIssueContentHash` (line 17)
   - Check if entire file can be deleted if only contains this function

2. `internal/config/config.go` - `FileUsed` (line 151)
   - Delete unused config helper

3. `cmd/bd/git_sync_test.go` - `verifyIssueOpen` (line 300)
   - Delete dead test helper

4. `internal/compact/haiku.go` - `HaikuClient.SummarizeTier2` (line 81)
   - Tier 2 summarization not implemented
   - Options: implement feature OR delete method

Impact: Removes 50-100 LOC depending on decisions


Comprehensive testing for separate branch workflow including unit tests, integration tests, and performance testing.

Tasks:
- Unit tests for worktree management
- Unit tests for config parsing
- Integration tests: create/update/close → beads branch
- Integration test: merge beads → main
- Integration test: protected branch scenario
- Integration test: network failure recovery
- Integration test: config change handling
- Manual testing guide
- Performance testing (worktree overhead)

Test scenarios: fresh setup, issue operations, merge workflow, protected branch, error handling, migration, multiple workspaces, sparse checkout

Estimated effort: 4-5 days

Completed comprehensive test coverage. Added 4 new integration tests: config change handling, multiple concurrent clones (3-way), performance testing (avg 77ms < 150ms target), and network failure recovery. All tests pass. Coverage includes fresh setup, issue ops, error handling, multiple workspaces, sparse checkout, config changes, network failures, and performance.


Delete collision resolution code

Remove ~2,100 LOC of ID collision detection and resolution code (no longer needed with hash IDs).

## Files to Delete Entirely
```
internal/storage/sqlite/collision.go              (~800 LOC)
internal/storage/sqlite/collision_test.go         (~300 LOC)
cmd/bd/autoimport_collision_test.go               (~400 LOC)
```

## Code to Remove from Existing Files

### internal/importer/importer.go
Remove:
- `DetectCollisions()` calls
- `ScoreCollisions()` logic
- `RemapCollisions()` calls
- `handleRename()` function
- All collision-related error handling

Keep:
- Basic import logic
- Exact match detection (idempotent import)

### beads_twoclone_test.go
Remove:
- `TestTwoCloneCollision` (bd-71107098)
- `TestThreeCloneCollision` (bd-cbed9619)
- `TestFiveCloneCollision` (bd-a40f374f)
- All N-way collision tests

### cmd/bd/import.go
Remove:
- `--resolve-collisions` flag
- `--dry-run` collision preview
- Collision reporting

## Issues Closed by This Change
- bd-71107098: Add test for symmetric collision
--89: Content-hash collision resolution
- bd-cbed9619: N-way collision resolution epic
- bd-cbed9619.5: Add ScoreCollisions (already done but now unnecessary)
- bd-cbed9619.4: Make DetectCollisions read-only
- bd-cbed9619.3: ResolveNWayCollisions function
- bd-cbed9619.2: Multi-round import convergence
- bd-cbed9619.1: Multi-round convergence for N-way collisions
- bd-e6d71828: Transaction + retry logic for collisions
- bd-70419816: Test case for symmetric collision

## Verification Steps
1. `grep -r "collision" --include="*.go"` → should only find alias conflicts
2. `go test ./...` → all tests pass
3. `go build ./cmd/bd` → clean build
4. Check LOC reduction: `git diff --stat`

## Expected Metrics
- **Files deleted**: 3
- **LOC removed**: ~2,100
- **Test coverage**: Should increase (less untested code)
- **Binary size**: Slightly smaller

## Caution
Do NOT delete:
- Alias conflict resolution (new code in bd-f8b764c9.6)
- Duplicate detection (bd-581b80b3, bd-149) - different from ID collisions
- Merge conflict resolution (bd-7e7ddffa.1, bd-5f483051) - git conflicts, not ID collisions

## Files to Modify
- internal/importer/importer.go (remove collision handling)
- cmd/bd/import.go (remove --resolve-collisions flag)
- beads_twoclone_test.go (remove collision tests)
- Delete: internal/storage/sqlite/collision.go
- Delete: internal/storage/sqlite/collision_test.go
- Delete: cmd/bd/autoimport_collision_test.go

## Testing
- Ensure all remaining tests pass
- Manual test: create issue on two clones, sync → no collisions
- Verify error if somehow hash collision occurs (extremely unlikely)


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.


Add concurrent import race condition tests

Currently no tests verify behavior when multiple clones import simultaneously with external_ref matching.

Scenarios to test:
1. Two clones import same external_ref update at same time
2. Clone A imports while Clone B updates same issue
3. Verify transaction isolation prevents corruption
4. Document expected behavior (last-write-wins vs timestamp-based)

Related: bd-1022
Files: internal/importer/external_ref_test.go


Implement tombstone types and schema migration

Add tombstone fields to Issue struct (deleted_at, deleted_by, delete_reason, original_type), add StatusTombstone constant, create SQLite migration for new columns. Per design bd-2m7.


convertDeletionToTombstone uses hardcoded default values

The convertDeletionToTombstone helper (importer.go:1114-1130) uses hardcoded defaults:
- Priority: 2
- IssueType: types.TypeTask
- Title: "(deleted)"

These defaults work but could be improved:
1. Consider using Priority: 0 (unset) to distinguish from user-set priority
2. OriginalType is empty string - consider setting IssueType to empty to be consistent
3. Title could include the original ID for debugging: "(deleted: bd-xyz)"

Low priority cleanup for consistency.


Improve test coverage for internal/rpc (47.5% → 60%)

The RPC package has only 47.5% test coverage. RPC is the communication layer for daemon operations.

Current coverage: 47.5%
Target coverage: 60%


Daemon gets stuck when auto-import blocked by git conflicts

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

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

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

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

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

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


Add --parent flag to bd onboard output

bd onboard didn't document --parent flag for epic subtasks, causing AI agents to guess wrong syntax. Added --parent example and CLI help section pointing to bd <cmd> --help.

Fixes: https://github.com/steveyegge/beads/issues/402


Set up WASM build pipeline

Configure Go→WASM compilation pipeline. Child of epic bd-44d0.

## Tasks
- [ ] Create build-wasm.sh script (GOOS=js GOARCH=wasm)
- [ ] Test basic WASM module loading in Node.js
- [ ] Set up wasm_exec.js wrapper
- [ ] Add WASM build to CI/CD
- [ ] Document build process

## Validation
- bd.wasm compiles successfully
- Can load in Node.js without errors
- Bundle size <10MB


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


purgeDeletedIssues still writes to deletions.jsonl during git-history-backfill

In purgeDeletedIssues (lines 929-939), when a deletion is recovered from git history, we:
1. Append to deletions.jsonl (backfill)
2. Create a tombstone in DB

With the tombstone approach, writing to deletions.jsonl is redundant since the tombstone will be exported to JSONL. Consider removing the deletions.jsonl backfill once we're confident in the tombstone approach.

This is related to the Phase 2 migration (bd-vw8) - we should stop writing to deletions.jsonl entirely.


Add MCP server functions for repair commands

Expose new repair commands via MCP server for agent access:

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

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

See repair_commands.md lines 803-884 for design.


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.


Performance Testing Framework

Add comprehensive performance testing for beads focusing on optimization guidance and validating 10K+ database scale. Uses standard Go tooling, follows existing patterns, minimal complexity.

Components:
- Benchmark suite for critical operations at 10K-20K scale
- Fixture generator for realistic test data (epic hierarchies, cross-links)
- User diagnostics via bd doctor --perf
- Always-on profiling integration

Goals:
- Identify bottlenecks for optimization work
- Validate performance at 10K+ issue scale
- Enable users to collect diagnostics for bug reports
- Support both SQLite and JSONL import paths


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


Fix dependency naming inconsistencies (GH #440)

Parent-child dependency documentation is backwards and UI labels are confusing.\n\nProblems:\n1. DEPENDENCIES.md says 'bd dep add PARENT CHILD' but this is rejected\n2. bd show displays epic children under 'Blocks' instead of 'Children'\n3. bd dep tree EPIC shows nothing (need --direction=up)\n4. Inconsistent with 'bd epic status' which uses 'children'\n\nSee: https://github.com/steveyegge/beads/issues/440


Consolidate export/import/commit/push into sync.go

Create internal/daemonrunner/sync.go with Syncer type. Add ExportOnce, ImportOnce, CommitAndMaybePush methods. Replace createExportFunc/createAutoImportFunc with thin closures calling Syncer.


Improve integration test coverage for stateful features


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

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

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

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

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

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

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


Add MCP functions for repair commands

Add repair commands to beads-mcp for agent access:
- beads_resolve_conflicts()
- beads_find_duplicates()
- beads_detect_pollution()
- beads_validate()

Files: integrations/beads-mcp/src/beads_mcp/server.py


Add test coverage for internal/validation package


Block direct status update to tombstone in UpdateIssue

Per bd-2m7 design: 'Should we support tombstone as valid input to bd update --status? Recommendation: No, tombstones only created via bd delete'

Currently nothing prevents 'bd update <id> --status=tombstone', which would create an invalid tombstone (missing deleted_at, deleted_by, etc.).

Location: internal/storage/sqlite/queries.go:500-664 UpdateIssue

Add validation to reject status=tombstone in updates.


Create Go adapter library (optional)

For agents written in Go, provide native adapter library instead of shelling out to curl.

Acceptance Criteria:
- agentmail.Client struct
- HTTP client with timeout/retry logic
- Same API as Python adapter
- Example usage in examples/go-agent/
- Unit tests

File: pkg/agentmail/client.go

Note: Lower priority - can shell out to curl initially


CRDT-based architecture for guaranteed convergence (v2.0)

## Vision
Redesign beads around Conflict-Free Replicated Data Types (CRDTs) to provide mathematical guarantees for N-way collision resolution at arbitrary scale.

## Current Limitations
- Content-hash based collision resolution fails at 5+ clones
- Non-deterministic convergence in multi-round scenarios
- UNIQUE constraint violations during rename operations
- No formal proof of convergence properties

## CRDT Benefits
- Provably convergent (Strong Eventual Consistency)
- Commutative/Associative/Idempotent operations
- No coordination required between clones
- Scales to 100+ concurrent workers
- Well-understood mathematical foundations

## Proposed Architecture

### 1. UUID-Based IDs
Replace sequential IDs with UUIDs:
- Current: bd-1c63eb84, bd-9063acda, bd-4d80b7b1
- CRDT: bd-a1b2c3d4-e5f6-7890-abcd-ef1234567890
- Human aliases maintained separately: #42 maps to UUID

### 2. Last-Write-Wins (LWW) Elements
Each field becomes an LWW register:
- title: (timestamp, clone_id, value)
- status: (timestamp, clone_id, value)
- Deterministic conflict resolution via Lamport timestamp + clone_id tiebreaker

### 3. Operation Log
Track all operations as CRDT ops:
- CREATE(uuid, timestamp, clone_id, fields)
- UPDATE(uuid, field, timestamp, clone_id, value)
- DELETE(uuid, timestamp, clone_id) - tombstone, not hard delete

### 4. Sync as Merge
Syncing becomes merging two CRDT states:
- No merge conflicts possible
- Deterministic merge function
- Guaranteed convergence

## Implementation Phases

### Phase 1: Research & Design (4 weeks)
- Study existing CRDT implementations (Automerge, Yjs, Loro)
- Design schema for CRDT-based issue tracking
- Prototype LWW-based Issue CRDT
- Benchmark performance vs current system

### Phase 2: Parallel Implementation (6 weeks)
- Implement CRDT storage layer alongside SQLite
- Build conversion tools: SQLite ↔ CRDT
- Maintain backward compatibility with v1.x format
- Migration path for existing databases

### Phase 3: Testing & Validation (4 weeks)
- Formal verification of convergence properties
- Stress testing with 100+ clone scenario
- Performance profiling and optimization
- Documentation and examples

### Phase 4: Migration & Rollout (4 weeks)
- Release v2.0-beta with CRDT backend
- Gradual migration from v1.x
- Monitoring and bug fixes
- Final v2.0 release

## Risks & Mitigations

**Risk 1: Performance overhead**
- Mitigation: Benchmark early, optimize hot paths
- CRDTs can be slower than append-only logs
- May need compaction strategy

**Risk 2: Storage bloat**
- Mitigation: Implement operation log compaction
- Tombstone garbage collection for deleted issues
- Periodic snapshots to reduce log size

**Risk 3: Breaking changes**
- Mitigation: Maintain v1.x compatibility layer
- Gradual migration tools
- Dual-mode operation during transition

**Risk 4: Complexity**
- Mitigation: Use battle-tested CRDT libraries
- Comprehensive documentation
- Clear migration guide

## Success Criteria
- 100-clone collision test passes without failures
- Formal proof of convergence properties
- Performance within 2x of current system
- Zero manual conflict resolution required
- Backward compatible with v1.x databases

## Timeline
18-20 weeks total (4-5 months)

## References
- Automerge: https://automerge.org
- Yjs: https://docs.yjs.dev
- Loro: https://loro.dev
- CRDT theory: Shapiro et al, A comprehensive study of CRDTs
- Related issues: bd-0dcea000, bd-4d7fca8a, bd-6221bdcd


Add tests for helper functions (GetDirtyIssueHash, GetAllDependencyRecords, export hashes)

Several utility functions have 0% coverage:
- GetDirtyIssueHash (dirty.go)
- GetAllDependencyRecords (dependencies.go)
- GetExportHash, SetExportHash, ClearAllExportHashes (hash.go)

These are lower priority but should have basic coverage.


bd sync should not stage gitignored snapshot files

## Problem

`gitCommitBeadsDir` in `cmd/bd/sync.go` runs `git add .beads/` which stages all files in the directory, including snapshot files that are listed in `.beads/.gitignore`.

If a snapshot file (e.g., `beads.left.meta.json`) was ever committed before being added to `.gitignore`, git continues to track it. This causes merge conflicts when multiple polecats run `bd sync` concurrently, since each one modifies and commits these temporary files.

## Root Cause

Line ~568 in sync.go:
```go
addCmd := exec.CommandContext(ctx, "git", "add", beadsDir)
```

This stages everything in `.beads/`, but `.gitignore` only prevents *untracked* files from being added - it doesn't affect already-tracked files.

## Suggested Fix

Option A: After `git add .beads/`, run `git reset` on snapshot files:
```go
exec.Command("git", "reset", "HEAD", ".beads/beads.*.jsonl", ".beads/*.meta.json")
```

Option B: Stage only specific files instead of the whole directory:
```go
exec.Command("git", "add", ".beads/issues.jsonl", ".beads/deletions.jsonl", ".beads/metadata.json")
```

Option C: Detect and untrack snapshot files if they're tracked:
```go
// Check if file is tracked: git ls-files --error-unmatch <file>
// If tracked, run: git rm --cached <file>
```

Option B is probably cleanest - explicitly add only the files that should be committed.


Ready issue


Extract daemon lifecycle functions to daemon_lifecycle.go


Add bd reset command for clean slate restart

Implement a `bd reset` command to reset beads to a clean starting state.

## Context
GitHub issue #479 - users sometimes get beads into an invalid state after updates, and there's no clean way to start fresh. The git backup/restore mechanism that protects against accidental deletion also makes it hard to intentionally reset.

## Design

### Command Interface
```
bd reset [--hard] [--force] [--backup] [--dry-run] [--no-init]
```

| Flag | Effect |
|------|--------|
| `--hard` | Also remove from git index and commit |
| `--force` | Skip confirmation prompt |
| `--backup` | Create `.beads-backup-{timestamp}/` first |
| `--dry-run` | Preview what would happen |
| `--no-init` | Don't re-initialize after clearing |

### Reset Levels
1. **Soft Reset (default)** - Kill daemons, clear .beads/, re-init. Git history unchanged.
2. **Hard Reset (`--hard`)** - Also git rm and commit the removal, then commit fresh state.

### Implementation Flow
1. Validate .beads/ exists
2. If not --force: show impact summary, prompt confirmation
3. If --backup: copy .beads/ to .beads-backup-{timestamp}/
4. Kill daemons
5. If --hard: git rm + commit
6. rm -rf .beads/*
7. If not --no-init: bd init (and git add+commit if --hard)
8. Print summary

### Safety Mechanisms
- Confirmation prompt (skip with --force)
- Impact summary (issue/tombstone counts)
- Backup option
- Dry-run preview
- Git dirty check warning

### Code Structure
- `cmd/bd/reset.go` - CLI command
- `internal/reset/` - Core logic package