Investigate performance issues in VS Code Copilot (Windows)

Beads unusable in Windows 11 VS Code Copilot chat with Sonnet 4.5.
Summary event happens every 3-4 turns, taking 3 minutes.
Copilot summarizes after ~125k tokens despite model supporting 1M.
Large context size of beads might be triggering aggressive summarization.
Need workaround or optimization for context size.


RPC diagnostics: BD_RPC_DEBUG timing logs

Add lightweight diagnostic logging for RPC connection attempts:
- BD_RPC_DEBUG=1 prints to stderr:
  - Socket path being dialed
  - Socket exists check result
  - Dial start/stop time
  - Connection outcome
- Improve bd daemon --status messaging when lock not held

This helps field triage of connection issues without verbose daemon logs.


Blocking issue


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


Improve Beads test coverage from 46% to 80%


bd delete bulk operations fight with auto-import/daemon causing data resurrection

When bulk deleting issues (e.g., 244 closed issues older than 24h), the process fights with auto-import and daemon infrastructure:

**Expected behavior:**
- Delete 244 issues from 468-issue database
- Export to JSONL (224 lines)
- Commit and push
- Result: 224 issues

**Actual behavior:**
- Delete 244 issues
- Import runs (from stale git JSONL with 468 issues)
- Resurrects deleted issues back into database
- Export writes 356 lines (not 224)
- Math: 468 - 244 = 224, but got 356 (132 issues resurrected)

**Root cause:**
Auto-import keeps re-importing from git during the delete operation, before the new JSONL is committed. The workflow is:
1. Delete from DB
2. Auto-import runs (reads old JSONL from git with deleted issues still present)
3. Issues come back
4. Export writes partially-deleted state

**Solution options:**
1. Add `--no-auto-import` flag to bulk delete operations
2. Atomic delete-export-commit operation that suppresses imports
3. Dedicated `bd prune` command that handles this correctly
4. Lock file to prevent auto-import during bulk mutations

**Impact:**
- Bulk cleanup operations don't work reliably
- Makes it nearly impossible to prune old closed issues
- Confusing UX (delete 244, but only 112 actually removed)

**FIXED**: Auto-import now skips during delete operations to prevent resurrection.

**Root cause confirmed**: Auto-import was running in PersistentPreRun before delete executed, causing it to re-import stale JSONL from git and resurrect deleted issues.

**Solution implemented**:
1. Added delete to skip list in main.go PersistentPreRun (alongside import and sync --dry-run)
2. Delete operations now complete atomically without auto-import interference
3. Added comprehensive test (TestBulkDeleteNoResurrection) to prevent regression

**Test verification**:
- Creates 20 issues, deletes 10
- Verifies no resurrection after delete
- Confirms JSONL has correct count (10 remaining)
- All existing tests still pass


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.


Remove unused global daemon infrastructure (internal/daemonrunner/)

The internal/daemonrunner/ package (1,468 LOC) contains the old global daemon implementation that is no longer used. We now use per-workspace daemons.

Deadcode analysis shows all these functions are unreachable:
- Daemon.Start, runGlobalDaemon, setupLock
- validateSingleDatabase, validateSchemaVersion
- registerDaemon, unregisterDaemon
- validateDatabaseFingerprint
- Full git client implementation (NewGitClient, HasUpstream, HasChanges, Commit, Push, Pull)
- Helper functions: isGitRepo, gitHasUpstream, gitHasChanges, gitCommit

The entire package appears unused since switching to per-workspace daemon architecture.

Files to remove:
- daemon.go (9,436 bytes)
- git.go (3,510 bytes)
- sync.go (6,401 bytes)
- fingerprint.go (2,076 bytes)
- process.go (3,332 bytes)
- rpc.go (994 bytes)
- config.go (486 bytes)
- logger.go (1,579 bytes)
- flock_*.go (platform-specific file locking)
- signals_*.go (platform-specific signal handling)
- All test files

Total cleanup: ~1,500 LOC


Publish @beads/bd package to npm registry

Publish the npm package to the public npm registry:

## Prerequisites
- npm account created
- Organization @beads created (or use different namespace)
- npm login completed locally
- Package tested locally (bd-f282 completed)

## Publishing steps
1. Verify package.json version matches current bd version
2. Run npm pack and inspect tarball contents
3. Test installation from tarball one more time
4. Run npm publish --access public
5. Verify package appears on https://www.npmjs.com/package/@beads/bd
6. Test installation from registry: npm install -g @beads/bd

## Post-publish
- Add npm badge to README.md
- Update CHANGELOG.md with npm package release
- Announce in release notes

## Note
- May need to choose different name if @beads namespace unavailable
- Alternative: beads-cli, bd-cli, or unscoped beads-issue-tracker


Review PR #512: clarify bd ready docs

Review and merge PR #512 from aspiers. This PR clarifies what bd ready does after git pull in README.md. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/512


Test simple issue


bd hooks install fails in git worktrees

When bd is used in a git worktree, bd hooks install fails with 'mkdir .git: not a directory' because .git is a file (gitdir pointer) not a directory. Beads should detect and follow the .git gitdir pointer to install hooks in the correct location. This blocks normal worktree workflows.

## Symptoms of this bug:
- Git hooks don't install automatically
- Auto-sync doesn't run (5-second debounce)
- Hash mismatch warnings in bd output
- Daemon fails to start with 'auto_start_failed'

## Workaround:
Use `git rev-parse --git-dir` to find the actual hooks directory and copy hooks manually:
```bash
mkdir -p $(git rev-parse --git-dir)/hooks
cp -r .beads-hooks/* $(git rev-parse --git-dir)/hooks/
```


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


Add --yes flag to bd doctor --fix for non-interactive mode

## Feature Request

Add a `--yes` or `-y` flag to `bd doctor --fix` that automatically confirms all prompts, enabling non-interactive usage in scripts and CI/CD pipelines.

## Current Behavior
`bd doctor --fix` prompts for confirmation before applying fixes, which blocks automated workflows.

## Desired Behavior
`bd doctor --fix --yes` should apply all fixes without prompting.

## Use Cases
- CI/CD pipelines that need to auto-fix issues
- Scripts that automate repository setup
- Pre-commit hooks that want to silently fix issues


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.


bd sync --import-only doesn't update DB mtime causing bd doctor false warning


Add tests for internal/autoimport package

Currently 0.0% coverage. Need tests for auto-import functionality that detects and imports updated JSONL files.


Create deployment scripts for GCP

Automated provisioning scripts for GCP Compute Engine deployment.

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

File: deployment/agent-mail/gcp/


Test issue 0


Support Ubuntu 20.04 LTS (glibc compatibility issue)

Starting at v0.22, precompiled binaries require GLIBC 2.32+ which is not available on Ubuntu 20.04 LTS (Focal Fossa). Ubuntu 20.04 has GLIBC 2.31.

Error:
```
bd: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by bd)
bd: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by bd)
```

Current workarounds:
1. Upgrade to Ubuntu 22.04+
2. Build from source: `go build -o bd ./cmd/bd/`

Root cause: Go 1.24+ runtime requires newer glibc. CGO is already disabled in .goreleaser.yml.

Possible solutions:
- Pin Go version to 1.21 or 1.22 for releases
- Use Docker/cross-compile with older build environment
- Provide separate build for older distros
- Document minimum requirements clearly

Decision: Document minimum requirements in README instead of pinning Go version.

Rationale:
- Ubuntu 20.04 LTS standard support ended April 2025 (already EOL)
- Pinning Go prevents security fixes, performance improvements, and new features
- Users on EOL distros can upgrade OS or build from source
- Added Requirements section to README with clear glibc 2.32+ requirement


Dogfood: Migrate beads repo to hash IDs

Final validation: migrate the beads project itself to hash-based IDs.

## Purpose
Dogfooding the migration on beads' own issue database to:
1. Validate migration tool works on real data
2. Discover edge cases
3. Verify all workflows still work
4. Build confidence for users

## Pre-Migration Checklist
- [ ] All bd-f8b764c9 child tasks completed
- [ ] All tests pass: `go test ./...`
- [ ] Migration tool tested on test databases
- [ ] Documentation updated
- [ ] MCP server updated and published
- [ ] Clean git status

## Migration Steps

### 1. Create Backup
```bash
# Backup database
cp -r .beads .beads.backup-1761798568

# Backup JSONL
cp .beads/beads.jsonl .beads/beads.jsonl.backup

# Create git branch for migration
git checkout -b hash-id-migration
git add .beads.backup-*
git commit -m "Pre-migration backup"
```

### 2. Run Migration (Dry Run)
```bash
bd migrate --hash-ids --dry-run > migration-plan.txt
cat migration-plan.txt

# Review:
# - Number of issues to migrate
# - Hash collision check (should be zero)
# - Text reference updates
# - Dependency updates
```

### 3. Run Migration (Real)
```bash
bd migrate --hash-ids 2>&1 | tee migration-log.txt

# Expected output:
# ✓ Backup created: .beads/beads.db.backup-1234567890
# ✓ Generated 150 hash IDs
# ✓ No hash collisions detected
# ✓ Updated issues table schema
# ✓ Updated 150 issue IDs
# ✓ Updated 87 dependencies
# ✓ Updated 234 text references
# ✓ Exported to .beads/beads.jsonl
# ✓ Migration complete!
```

### 4. Validation

#### Database Integrity
```bash
# Check all issues have hash IDs
bd list | grep -v "bd-[a-f0-9]\{8\}" && echo "FAIL: Non-hash IDs found"

# Check all issues have aliases
sqlite3 .beads/beads.db "SELECT COUNT(*) FROM issues WHERE alias IS NULL"
# Should be 0

# Check no alias duplicates
sqlite3 .beads/beads.db "SELECT alias, COUNT(*) FROM issues GROUP BY alias HAVING COUNT(*) > 1"
# Should be empty
```

#### Functionality Tests
```bash
# Test show by hash ID
bd show bd-

# Test show by alias
bd show #1

# Test create new issue
bd create "Test issue after migration" -p 2
# Should get hash ID + alias

# Test update
bd update #1 --priority 1

# Test dependencies
bd dep tree #1

# Test export
bd export
git diff .beads/beads.jsonl
# Should show hash IDs
```

#### Text Reference Validation
```bash
# Check that old IDs were updated in descriptions
grep -r "bd-[0-9]\{1,3\}[^a-f0-9]" .beads/beads.jsonl && echo "FAIL: Old ID format found"

# Verify hash ID references exist
grep -o "bd-[a-f0-9]\{8\}" .beads/beads.jsonl | sort -u | wc -l
# Should match number of hash IDs
```

### 5. Commit Migration
```bash
git add .beads/beads.jsonl .beads/beads.db
git commit -m "Migrate to hash-based IDs (v2.0)

- Migrated 150 issues to hash IDs
- Preserved aliases (#1-#150)
- Updated 87 dependencies
- Updated 234 text references
- Zero hash collisions

Migration log: migration-log.txt"

git push origin hash-id-migration
```

### 6. Create PR
```bash
gh pr create --title "Migrate to hash-based IDs (v2.0)" --body "## Summary
Migrates beads project to hash-based IDs as part of v2.0 release.

## Migration Stats
- Issues migrated: 150
- Dependencies updated: 87
- Text references updated: 234
- Hash collisions: 0
- Aliases assigned: 150

## Validation
- ✅ All tests pass
- ✅ Database integrity verified
- ✅ All workflows tested (show, update, create, deps)
- ✅ Text references updated correctly
- ✅ Export produces valid JSONL

## Files Changed
- `.beads/beads.jsonl` - Hash IDs in all entries
- `.beads/beads.db` - Schema updated with aliases

## Rollback
If issues arise:
\`\`\`bash
mv .beads.backup-1234567890 .beads
bd export
\`\`\`

See migration-log.txt for full details."
```

### 7. Merge and Cleanup
```bash
# After PR approval
git checkout main
git merge hash-id-migration
git push origin main

# Tag release
git tag v2.0.0
git push origin v2.0.0

# Cleanup
rm migration-log.txt migration-plan.txt
git checkout .beads.backup-*  # Keep in git history
```

## Rollback Procedure
If migration fails or has issues:

```bash
# Restore backup
mv .beads .beads.failed-migration
mv .beads.backup-1234567890 .beads

# Regenerate JSONL
bd export

# Verify restoration
bd list
git diff .beads/beads.jsonl

# Cleanup
git checkout hash-id-migration
git reset --hard main
```

## Post-Migration Communication

### GitHub Issue/Discussion
```markdown
## Beads v2.0 Released: Hash-Based IDs

We've migrated beads to hash-based IDs! 🎉

**What changed:**
- Issues now use hash IDs (bd-af78e9a2) instead of sequential (bd-cb64c226.3)
- Human-friendly aliases (#42) for easy reference
- Zero collision risk in distributed workflows

**Action required:**
If you have a local clone, you need to migrate:

\`\`\`bash
git pull origin main
bd migrate --hash-ids
git push origin main
\`\`\`

See MIGRATION.md for details.

**Benefits:**
- ✅ No more ID collisions
- ✅ Work offline without coordination
- ✅ Simpler codebase (-2,100 LOC)

Questions? Reply here or see docs/HASH_IDS.md
```

## Success Criteria
- [ ] Migration completes without errors
- [ ] All validation checks pass
- [ ] PR merged to main
- [ ] v2.0.0 tagged and released
- [ ] Documentation updated
- [ ] Community notified
- [ ] No rollback needed within 1 week

## Files to Create
- migration-log.txt (transient)
- migration-plan.txt (transient)

## Timeline
Execute after all other bd-f8b764c9 tasks complete (estimated: ~8 weeks from start)


## Problem

bd cleanup deletes closed issues (converting them to tombstones) but does NOT prune expired tombstones. Users expect 'cleanup' to do comprehensive cleanup.

## Current Behavior

1. bd cleanup --force converts closed issues to tombstones
2. Expired tombstones (>30 days) remain in issues.jsonl
3. User must separately run bd compact to prune tombstones
4. bd doctor warns about expired tombstones: Run bd compact to prune

## Expected Behavior

bd cleanup should also prune expired tombstones from issues.jsonl.

## Impact

With v0.30.0 making tombstones the default migration path, this UX gap becomes more visible. Users cleaning up their database should not need to know about a separate bd compact command.

## Proposed Solution

Call pruneExpiredTombstones() at the end of the cleanup command (same function used by compact).

## Files to Modify

- cmd/bd/cleanup.go - Add call to pruneExpiredTombstones after deleteBatch

## Acceptance Criteria

- bd cleanup --force prunes expired tombstones after deleting closed issues
- bd cleanup --dry-run shows what tombstones would be pruned
- JSON output includes tombstone prune results


Test reservation expiration and renewal

Verify TTL-based reservation expiration works correctly.

Acceptance Criteria:
- Reserve with short TTL (30s)
- Verify other agents can't claim
- Wait for expiration
- Verify reservation auto-released
- Other agent can now claim
- Test renewal/heartbeat mechanism

File: tests/integration/test_reservation_ttl.py

Implemented comprehensive TTL/expiration test suite in tests/integration/test_reservation_ttl.py

Test Coverage:
✅ Short TTL reservations (30s) - verifies TTL is properly set
✅ Reservation blocking - confirms agent2 cannot claim while agent1 holds reservation
✅ Auto-release after expiration - validates expired reservations are auto-cleaned and become available
✅ Renewal/heartbeat - tests that re-reserving extends expiration time

All 4 tests passing in 56.9s total (including 30s+ wait time for expiration tests).

Mock server implements full TTL management:
- Reservation class with expiration tracking
- Auto-cleanup of expired reservations on each request
- Renewal support (same agent re-reserving)
- 409 conflict for cross-agent reservation attempts


Fix default .beads/.gitignore to ignore merge artifacts (GH #274)

Updated the default .gitignore template created by `bd init` to properly ignore merge artifacts and fix overly broad patterns.

Changes:
- Added `*.db?*` pattern for database files with query strings
- Added explicit patterns for merge artifacts: beads.{base,left,right}.{jsonl,meta.json}
- Changed `!*.jsonl` to `!issues.jsonl` to avoid including merge artifact JSONL files

This fixes GitHub issue #274 reported by rscorer.


bd doctor --fix hydrates issues that remain in deletions manifest, causing perpetual skip warnings

When bd doctor --fix hydrates issues from git history, it doesn't remove them from the deletions manifest. This causes a conflict where:
1. Issue exists in database as 'open'
2. Issue also exists in deletions manifest
3. Every sync reports 'Skipping bd-xxx (in deletions manifest)'
4. Issue is 'Protected from incorrect sanitization'

**Reproduction:**
1. Run bd doctor --fix (which hydrates issues from git history)
2. Run bd sync
3. Observe conflicting messages about protected issues being skipped

**Affected issues in this session:**
bd-eyto, bd-6rl, bd-y2v, bd-abjw, bd-a0cp, bd-mql4, bd-nl2

**Expected behavior:**
When hydrating an issue, also remove it from the deletions manifest to prevent conflict.

**Workaround:** Manually remove conflicting IDs from deletions.jsonl


Fix checkExistingBeadsData to be worktree-aware

The checkExistingBeadsData function in cmd/bd/init.go checks for .beads in the current working directory, but for worktrees it should check the main repository root instead. This prevents proper worktree compatibility.


Add tests for internal/utils package

Currently 0.0% coverage. Need tests for utility functions including issue ID parsing and validation.


Investigate jujutsu VCS as potential solution for conflict-free merging

## Context
Current N-way collision resolution struggles with Git line-based merge model. When 5+ clones create issues with same ID, Git merge conflicts require manual resolution, and our collision resolver can fail during convergence rounds.

## Research Question
Could jujutsu (jj) provide better conflict handling for JSONL files?

## Jujutsu Overview
- Next-gen VCS built on libgit2
- Designed to handle conflicts as first-class citizens
- Supports conflict-free replicated data types (CRDTs) in some scenarios
- Better handling of concurrent edits
- Can work with Git repos (compatible with existing infrastructure)

## Investigation Tasks
1. JSONL Merge Behavior - How does jj handle line-by-line JSONL conflicts?
2. Integration Feasibility - Can beads use jj as backend while maintaining Git compatibility?
3. Conflict Resolution Model - Does jj conflict model map to our collision resolution?
4. Operational Transform Support - Does jj implement operational transforms?

## Deliverables
1. Technical report on jj merge algorithm for JSONL
2. Proof-of-concept: 5-clone collision test using jj instead of Git
3. Performance comparison: Git vs jj for beads workload
4. Recommendation: Adopt, experiment further, or abandon

## References
- https://github.com/martinvonz/jj
- Related to bd-e6d71828, bd-7a2b58fc


Log errors from timer-triggered flushes instead of discarding


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.


bd validate - Comprehensive health check

Run all validation checks in one command.

Checks:
- Duplicates
- Orphaned dependencies
- Test pollution
- Git conflicts

Supports --fix-all for auto-repair.

Depends on bd-cbed9619.1, bd-0dcea000, bd-31aab707, bd-9826b69a.

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


bd doctor --json flag not working

The --json flag on bd doctor command doesn't produce JSON output. It continues to show human-readable output instead. The flag is registered locally on doctorCmd but the code uses the global jsonOutput variable set by PersistentPreRun. Need to investigate why the flag isn't being honored.


Implement signal-aware context in CLI commands

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

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

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

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

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

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

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


Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1186

Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1186:2. Error: rows.Close()


Code Review Sweep: thorough

Perform thorough code review sweep based on accumulated activity.

**AI Reasoning:**
Significant code volume added (150,273 lines) across multiple critical areas, including cmd/bd, internal/storage/sqlite, and internal/rpc. High file change count (616) indicates substantial refactoring or new functionality. The metrics suggest potential for subtle architectural or implementation issues that warrant review.

**Scope:** thorough
**Target Areas:** cmd/bd, internal/storage/sqlite, internal/rpc
**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.


Centralize BD_DEBUG logging into debug package

The codebase has 43 scattered instances of `if os.Getenv("BD_DEBUG") != ""` debug checks across 6 files. Centralize into a debug logging package.

Current locations:
- `cmd/bd/main.go` - 15 checks
- `cmd/bd/autoflush.go` - 6 checks
- `cmd/bd/nodb.go` - 4 checks
- `internal/rpc/server.go` - 2 checks
- `internal/rpc/client.go` - 5 checks
- `cmd/bd/daemon_autostart.go` - 11 checks

Target structure:
```
internal/debug/
└── debug.go
```

Benefits:
- Centralized debug logging
- Easier to add structured logging later
- Testable (can mock debug output)
- Consistent debug message format

Impact: Removes 43 scattered checks, improves code clarity


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.


Add security tests for WriteFile permissions in doctor command

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

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

In cmd/bd/doctor/gitignore.go:98, os.WriteFile uses 0644 permissions, flagged by gosec G306 as potentially too permissive.

Add tests to verify:
- File is created with appropriate permissions (0600 or less)
- Existing file permissions are not loosened
- File ownership is correct
- Sensitive data handling if .gitignore contains secrets

This ensures .gitignore files are created with secure permissions to prevent unauthorized access.

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


Optimize GetReadyWork performance - 752ms on 10K database (target: <50ms)

# Performance Analysis (10K Issue Database)

Analyzed using CPU profiles from benchmark suite on Apple M2 Pro.

## Operation Performance

| Operation                        | Time    | Allocations | Memory |
|----------------------------------|---------|-------------|--------|
| bd ready (GetReadyWork)          | ~752ms  | 167,466     | 16MB   |
| bd list (SearchIssues no filter) | ~11.6ms | 89,214      | 5.8MB  |
| bd list (SearchIssues filtered)  | ~9.2ms  | 62,365      | 3.5MB  |
| bd create (CreateIssue)          | ~2.6ms  | 146         | 8.6KB  |
| bd update (UpdateIssue)          | ~0.32ms | 364         | 15KB   |
| bd close (UpdateIssue)           | ~0.32ms | 364         | 15KB   |

**Target: <50ms for all operations on 10K database**

**Current issue: GetReadyWork is 15x over target (752ms vs 50ms)**

## Root Cause

GetReadyWork (internal/storage/sqlite/ready.go:90-128) uses recursive CTE to propagate blocking:
- 65x slower than SearchIssues
- Recalculates entire blocked issue tree on every call
- Algorithm:
  1. Find directly blocked issues via 'blocks' dependencies
  2. Recursively propagate blockage to descendants (max depth: 50)
  3. Exclude all blocked issues from results

## CPU Profile Analysis

- Database syscalls (pthread_cond_signal, syscall6): ~75%
- SQLite engine overhead: inherent to recursive CTE
- Application code (query construction): <1%

**Bottleneck is the recursive CTE query execution, not application code.**

## Optimization Recommendations

### High Impact (Likely to achieve <50ms target)

1. **Cache blocked issue calculation**
   - Add `blocked_issues` table updated on dependency changes
   - Trade write complexity for read speed (ready called >> dependency changes)
   - Eliminates recursive CTE on every read

2. **Add/verify database indexes**
   ```sql
   CREATE INDEX IF NOT EXISTS idx_dependencies_blocked
     ON dependencies(issue_id, type, depends_on_id);
   CREATE INDEX IF NOT EXISTS idx_issues_status
     ON issues(status);
   ```

### Medium Impact

3. **Reduce allocations** (167K allocations for GetReadyWork)
   - Profile `scanIssues()` for object pooling opportunities
   - Reuse slice capacity for repeated calls

### Low Impact (Not recommended)
- Query optimization for CRUD operations (already <3ms)
- Connection pooling tuning (not showing in profiles)

## Verification

Run benchmarks to validate optimization:
```bash
make bench-quick
go tool pprof -http=:8080 internal/storage/sqlite/bench-cpu-*.prof
```

Profile files automatically generated in `internal/storage/sqlite/`.


Add bd hooks install command to embed git hooks in binary

Currently git hooks are installed via `examples/git-hooks/install.sh`, which only exists in the beads source repo. Users who install bd via installer/homebrew/npm can't easily install hooks.

**Proposal:**
Add `bd hooks install` command that:
- Embeds hook scripts in the bd binary (using go:embed)
- Installs them to .git/hooks/ in current repo
- Backs up existing hooks
- Makes them executable

**Commands:**
- `bd hooks install` - Install all hooks
- `bd hooks uninstall` - Remove hooks
- `bd hooks list` - Show installed hooks status

**Benefits:**
- Works for all bd users, not just source repo users
- More discoverable (shows in bd --help)
- Consistent with bd workflow
- Can version hooks with bd releases


Add simple cleanup command to delete closed issues

Users want a simple command to delete all closed issues without requiring Anthropic API key (unlike compact). Requested in GH #243.


Fix linting error in migrate.go

Linter reports error:
```
cmd/bd/migrate.go:647:37: cleanupWALFiles - result 0 (error) is always nil (unparam)
```

The `cleanupWALFiles` function always returns nil, so the error return type should be removed or the function should actually return errors when appropriate.


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-e6d71828, bd-7a2b58fc, bd-81abb639


bd import auto-detects wrong prefix from directory name instead of issue IDs

When importing issues.jsonl into a fresh database, 'bd import' prints:

  ✓ Initialized database with prefix 'beads' (detected from issues)

But the issues all have prefix 'bd-' (e.g., bd-03r). It appears to be detecting the prefix from the directory name (.beads/) rather than from the actual issue IDs in the JSONL.

This causes import to fail with:
  validate ID prefix for bd-03r: issue ID 'bd-03r' does not match configured prefix 'beads'

Workaround: Run 'bd config set issue_prefix bd' before import, or use 'bd init --prefix bd'.


Clean up merge conflict artifacts in .beads directory

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

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

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

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


Daemon fails to auto-import after git pull updates JSONL

After git pull updates .beads/issues.jsonl, daemon doesn't automatically re-import changes, causing stale data to be shown until next sync cycle (up to 5 minutes).

Reproduction:
1. Repo A: Close issues, export, commit, push
2. Repo B: git pull (successfully updates .beads/issues.jsonl)
3. bd show <issue> shows OLD status from daemon's SQLite db
4. JSONL on disk has correct new status

Root cause: Daemon sync cycle runs on timer (5min). When user manually runs git pull, daemon doesn't detect JSONL was updated externally and continues serving stale data from SQLite.

Impact:
- High for AI agents using beads in git workflows
- Breaks fundamental git-as-source-of-truth model
- Confusing UX: git log shows commit, bd shows old state
- Data consistency issues between JSONL and daemon

See WYVERN_SYNC_ISSUE.md for full analysis.

**Fixed in v0.21.2!**

The daemon auto-import is fully implemented:
- internal/autoimport package handles staleness detection
- internal/importer package provides shared import logic (used by both CLI and daemon)
- daemon's checkAndAutoImportIfStale() calls autoimport.AutoImportIfNewer()
- importFunc uses importer.ImportIssues() with auto-rename enabled
- All tests passing

The critical data corruption bug is FIXED:
✅ After git pull, daemon detects JSONL is newer (mtime check)
✅ Daemon auto-imports before serving requests
✅ No stale data served
✅ No data loss in multi-agent workflows

Verification needed: Run two-repo test to confirm end-to-end behavior.


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.


Optimize cmd/bd long-mode tests by switching to in-process testing

The long-mode CLI tests in cmd/bd are slow (1.4-4.4 seconds each) because they spawn full bd processes via exec.Command() for every operation.

Current approach:
- Each runBD() call spawns new bd process via exec.Command(testBD, args...)
- Each process initializes Go runtime, loads SQLite, parses CLI flags
- Tests run serially (create → update → show → close)
- Even with --no-daemon flag, there's significant process spawn overhead

Example timing from test run:
- TestCLI_PriorityFormats: 2.21s
- TestCLI_Show: 2.26s
- TestCLI_Ready: 2.29s
- TestCLI_Import: 4.42s

Optimization strategy:
1. Convert most tests to in-process testing (call bd functions directly)
2. Reuse test databases across related operations instead of fresh init each time
3. Keep a few exec.Command() tests that batch multiple operations to verify the CLI path works end-to-end

This should reduce test time from ~40s to ~5s for the affected tests.

Converted all CLI tests in cli_fast_test.go to use in-process testing via rootCmd.Execute(). Created runBDInProcess() helper that:
- Calls rootCmd.Execute() directly instead of spawning processes
- Uses mutex to serialize execution (rootCmd/viper not thread-safe)
- Properly cleans up global state (store, daemonClient)
- Returns only stdout to avoid JSON parsing issues with stderr warnings

Performance results:
- In-process tests: ~0.6s each (cached even faster)
- exec.Command tests: ~3.7s each
- Speedup: ~10x faster

Kept TestCLI_EndToEnd() that uses exec.Command for end-to-end validation of the actual binary.


Parameterize SQLite busy_timeout in store.go

Modify sqlite.New() to accept configurable busy_timeout.

## Current State
`internal/storage/sqlite/store.go` hardcodes:
```go
connStr = "file:" + path + "?_pragma=foreign_keys(ON)&_pragma=busy_timeout(30000)&..."
```

## Implementation Options

### Option A: Add timeout parameter to New()
```go
func New(ctx context.Context, path string, busyTimeout time.Duration) (*SQLiteStorage, error)
```
Pro: Simple, explicit
Con: Changes function signature (breaking change for callers)

### Option B: Options struct pattern
```go
type Options struct {
    BusyTimeout time.Duration // default 30s
}
func New(ctx context.Context, path string, opts *Options) (*SQLiteStorage, error)
```
Pro: Extensible, nil means defaults
Con: More boilerplate

### Recommendation: Option A with default
```go
func New(ctx context.Context, path string) (*SQLiteStorage, error) {
    return NewWithOptions(ctx, path, 30*time.Second)
}

func NewWithOptions(ctx context.Context, path string, busyTimeout time.Duration) (*SQLiteStorage, error)
```

## Acceptance Criteria
- busy_timeout is parameterized in connection string
- 0 timeout means no waiting (immediate SQLITE_BUSY)
- All existing callers continue to work (via default wrapper)

Part of bd-olc1


Make MCP compaction settings configurable (COMPACTION_THRESHOLD, PREVIEW_COUNT)

Currently COMPACTION_THRESHOLD (20) and PREVIEW_COUNT (5) are hardcoded constants at the module level. This prevents users from tuning context engineering behavior.

## Current State
```python
COMPACTION_THRESHOLD = 20  # Compact results with more than 20 issues
PREVIEW_COUNT = 5          # Show first 5 issues in preview
```

## Problems
1. No way for MCP clients to request different preview sizes
2. No way to disable compaction for debugging
3. No way to adapt to different context window sizes
4. Values are not documented in tool schema

## Recommended Solution
Add environment variables or MCP tool parameters:
- `BEADS_MCP_COMPACTION_THRESHOLD` env var (default: 20)
- `BEADS_MCP_PREVIEW_COUNT` env var (default: 5)
- Optional `compact_threshold` and `preview_size` parameters to list/ready tools

## Implementation Notes
- Keep current defaults for backward compatibility
- Read from environment at server initialization
- Document in CONTEXT_ENGINEERING.md
- Add tests verifying env var behavior


Test parent issue


Add tests for daemon vs non-daemon parity in list filters

After bd-o43 RPC integration, we need tests to verify daemon mode behaves identically to direct mode for all new filter flags.

Test coverage needed:
- Pattern matching: --title-contains, --desc-contains, --notes-contains
- Date ranges: all 6 date filter flags (created/updated/closed after/before)
- Empty/null checks: --empty-description, --no-assignee, --no-labels
- Priority ranges: --priority-min, --priority-max
- Status normalization: --status all vs no status flag
- Date parsing: YYYY-MM-DD, RFC3339, and error cases
- Backward compat: deprecated --label flag still works

Oracle review findings (bd-o43):
- Date parsing should support multiple formats
- Status 'all' should be treated as unset
- NoLabels field was missing from RPC protocol
- Error messages should be clear and actionable

Test approach:
- Create RPC integration tests in internal/rpc/server_issues_epics_test.go
- Compare daemon client.List() vs direct store.SearchIssues() for same filters
- Verify error messages match between modes
- Test with real daemon instance, not just unit tests


Add path normalization to getMultiRepoJSONLPaths()

From bd-xo6b code review: getMultiRepoJSONLPaths() does not handle non-standard paths correctly.

Problems:
- No tilde expansion: ~/repos/foo treated as literal path
- No absolute path conversion: ../other-repo breaks if working directory changes
- No duplicate detection: If Primary=. and Additional=[.], same JSONL processed twice
- No empty string handling: Empty paths create invalid /.beads/issues.jsonl

Impact:
Config with tilde or relative paths will fail

Fix needed:
1. Use filepath.Abs() for all paths
2. Add tilde expansion via os.UserHomeDir()
3. Deduplicate paths (use map to track seen paths)
4. Filter out empty strings
5. Validate paths exist and are readable

Files:
- cmd/bd/deletion_tracking.go:333-358 (getMultiRepoJSONLPaths function)


Separate process/lock/PID concerns into process.go

Create internal/daemonrunner/process.go with: acquireDaemonLock, PID file read/write, stopDaemon, isDaemonRunning, getPIDFilePath, socket path helpers, version check.


Improve test coverage for internal/daemon (27.3% → 60%)

The daemon package has only 27.3% test coverage. The daemon is critical for background operations and reliability.

Key areas needing tests:
- Daemon autostart logic
- Socket handling
- PID file management
- Health checks

Current coverage: 27.3%
Target coverage: 60%


Remove all deletions.jsonl code except migration

There's deletions manifest code spread across the entire codebase that should have been removed after tombstone migration:

Files with deletions code (non-migration):
- internal/deletions/ - entire package
- cmd/bd/sync.go - 25+ references, auto-compact, sanitize
- cmd/bd/delete.go - dual-writes to deletions.jsonl
- internal/importer/importer.go - checks deletions manifest
- internal/syncbranch/worktree.go - merges deletions.jsonl
- cmd/bd/doctor/fix/sync.go - cleanupDeletionsManifest
- cmd/bd/doctor/fix/deletions.go - HydrateDeletionsManifest
- cmd/bd/integrity.go - checks deletions for data loss
- cmd/bd/deleted.go - entire command
- cmd/bd/compact.go - pruneDeletionsManifest
- cmd/bd/doctor.go - checkDeletionsManifest
- Plus many more

Action: Aggressively remove all non-migration deletions code. Tombstones are the only deletion mechanism now.


Add --body-file flag to bd create for reading descriptions from files

## Problem

Creating issues with long/complex descriptions via CLI requires shell escaping gymnastics:

```bash
# Current workaround - awkward heredoc quoting
bd create --title="..." --description="$(cat <<'EOF'
...markdown...
EOF
)"

# Often fails with quote escaping errors in eval context
# Agents resort to writing temp files then reading them
```

## Proposed Solution

Add `--body-file` and `--description-file` flags to read description from a file, matching `gh` CLI pattern.

```bash
# Natural pattern that aligns with training data
cat > /tmp/desc.md << 'EOF'
...markdown content...
EOF

bd create --title="..." --body-file=/tmp/desc.md
```

## Implementation

### 1. Add new flags to `bd create`

```go
createCmd.Flags().String("body-file", "", "Read description from file (use - for stdin)")
createCmd.Flags().String("description-file", "", "Alias for --body-file")
```

### 2. Flag precedence

- If `--body-file` or `--description-file` is provided, read from file
- If value is `-`, read from stdin
- Otherwise fall back to `--body` or `--description` flag
- If neither provided, description is empty (current behavior)

### 3. Error handling

- File doesn't exist → clear error message
- File not readable → clear error message
- stdin specified but not available → clear error message

## Benefits

✅ **Matches training data**: `gh issue create --body-file file.txt` is a common pattern
✅ **No shell escaping issues**: File content is read directly
✅ **Works with any content**: Markdown, special characters, quotes, etc.
✅ **Agent-friendly**: Agents already write complex content to temp files
✅ **User-friendly**: Easier for humans too when pasting long descriptions

## Related Commands

Consider adding similar support to:
- `bd update --body-file` (for updating descriptions)
- `bd comment --body-file` (if/when we add comments)

## Examples

```bash
# From file
bd create --title="Add new feature" --body-file=feature.md

# From stdin
echo "Quick description" | bd create --title="Bug fix" --body-file=-

# With other flags
bd create \
  --title="Security issue" \
  --type=bug \
  --priority=0 \
  --body-file=security-report.md \
  --label=security
```

## Testing

- Test with normal files
- Test with stdin (`-`)
- Test with non-existent files (error handling)
- Test with binary files (should handle gracefully)
- Test with empty files (valid - empty description)
- Test that `--description-file` and `--body-file` are equivalent aliases


Vendor beads-merge Go code into internal/merge/

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

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

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


TestHooksCheckGitHooks failing - version mismatch (0.23.0 vs 0.23.1)

The test is checking embedded hook versions and expecting 0.23.1, but got 0.23.0. This appears to be a version consistency issue that needs investigation.

Test output:
```
Hook pre-commit version mismatch: got 0.23.0, want 0.23.1
Hook post-merge version mismatch: got 0.23.0, want 0.23.1
Hook pre-push version mismatch: got 0.23.0, want 0.23.1
```

This is blocking the landing of GH #274 fix.


Test child direct


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)


Fix variable shadowing in GetNextChildID resurrection code

## Problem
Minor variable shadowing in hash_ids.go:

```go
// Line 33: err declared here
err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM issues WHERE id = ?`, parentID).Scan(&count)

// Line 38: err reused/shadowed here
resurrected, err := s.TryResurrectParent(ctx, parentID)
```

While Go allows this, it can be confusing and some linters flag it.

## Solution
Use different variable name:

```go
resurrected, resurrectErr := s.TryResurrectParent(ctx, parentID)
if resurrectErr != nil {
    return "", fmt.Errorf("failed to resurrect parent %s: %w", parentID, resurrectErr)
}
```

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

## Priority
Low - code works correctly, just a style improvement


Document -short flag in testing guide

Add documentation about the -short flag and how it's used to skip slow tests. Should explain that developers can run 'go test -short ./...' for fast iteration and 'go test ./...' for full coverage.


Parent: bd-imj

Ensure deletions.jsonl is tracked in git (not ignored).

Update bd init and gitignore upgrade logic to:
1. NOT add deletions.jsonl to .gitignore
2. Ensure it is committed alongside beads.jsonl

The file must be in git for cross-clone propagation to work.

Acceptance criteria:
- bd init does not ignore deletions.jsonl
- Existing .gitignore files are not broken
- File appears in git status when modified


Thread Test 2

Testing direct mode


bd-hv01: Refactor snapshot management into dedicated module

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

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

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

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


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

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

Related to GH #334.

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

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

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


GH#520: Daemon sync-branch commit fails with pre-commit hooks (needs --no-verify)

gitCommitInWorktree in daemon_sync_branch.go missing --no-verify flag, causing commit failures when pre-commit hooks installed. Library function in worktree.go:684 has correct impl. See: https://github.com/steveyegge/beads/issues/520


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


Schema: Add message type and new fields

Add to internal/storage/sqlite/schema.go and models:

New issue_type value:
- message

New optional fields on Issue struct:
- Sender string (who sent this)
- Ephemeral bool (can be bulk-deleted)
- RepliesTo string (issue ID for threading)
- RelatesTo []string (issue IDs for knowledge graph)
- Duplicates string (canonical issue ID)
- SupersededBy string (replacement issue ID)

Update:
- internal/storage/sqlite/schema.go - add columns
- internal/models/issue.go - add fields to struct
- internal/storage/sqlite/sqlite.go - CRUD operations
- Create migration from v0.30.1

Ensure backward compatibility - all new fields optional.


Add unit tests for hasJSONLChanged() function

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

Also add performance benchmarks for large JSONL files.


Latency test 1


Consider consolidating config.yaml parsing into shared utility

Multiple places parse config.yaml with custom structs:

1. **autoimport.go:148** - `localConfig{SyncBranch}`
2. **main.go:310** - strings.Contains for no-db (fragile, see bd-r6k2)
3. **doctor.go:863** - strings.Contains for no-db (fragile, see bd-r6k2)
4. **internal/config/config.go** - Uses viper (but caches at startup, problematic for tests)

Consider creating a shared utility in `internal/configfile/` or extending the viper config:

```go
// internal/configfile/yaml.go
type YAMLConfig struct {
    SyncBranch string `yaml:"sync-branch"`
    NoDb       bool   `yaml:"no-db"`
    IssuePrefix string `yaml:"issue-prefix"`
    Author     string `yaml:"author"`
}

func LoadYAML(beadsDir string) (*YAMLConfig, error) {
    // Parse config.yaml with proper YAML library
}
```

Benefits:
- Single source of truth for config.yaml structure
- Proper YAML parsing everywhere
- Easier to add new config fields

Trade-off: May add complexity for simple one-off reads.


Fix :memory: database connection pool issue causing "no such table" errors

Critical bug in v0.21.6 where :memory: databases with cache=shared create multiple connections in the pool, causing intermittent "no such table" errors. SQLite's shared cache for in-memory databases only works reliably with a single connection.

Root cause: Missing db.SetMaxOpenConns(1) after sql.Open() for :memory: databases.

Impact: 37 test failures in VC project, affects all consumers using :memory: for testing.


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


Auto-recovery mode (bd sync --auto-recover)

Add bd sync --auto-recover flag that: detects problematic sync state, backs up .beads/issues.db with timestamp, rebuilds DB from JSONL atomically, verifies consistency, reports what was fixed. Provides safety valve when sync integrity fails.


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


Add test coverage for internal/config package


Integrate beads-merge tool by @neongreen

**Context**: @neongreen built a production-ready 3-way merge tool for JSONL files that works with both Git and Jujutsu. This is superior to our planned bd resolve-conflicts because it prevents conflicts proactively instead of resolving them after the fact.

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

**What it does**:
- 3-way merge of JSONL files (base, left, right)
- Field-level merging (titles, status, priority, etc.)
- Smart dependency merging (union + dedup)
- Conflict markers for unresolvable conflicts
- Exit code 1 for conflicts (standard)

**Integration options**:

1. **Recommend (minimal effort)** - Document in AGENTS.md + TROUBLESHOOTING.md
2. **Bundle binary** - Include in releases (cross-platform builds)
3. **Port to Go** - Reimplement in bd codebase
4. **Auto-install hook** - During bd init, offer to install merge driver

**Recommendation**: Start with option 1 (document), then option 2 (bundle) once proven.

**Related**: bd-5f483051 (bd resolve-conflicts - can close as superseded)

Created GitHub issue to discuss integration approach with @neongreen: https://github.com/neongreen/mono/issues/240

Awaiting their preference on:
1. Vendor with attribution (fastest)
2. Extract as importable module (best long-term)
3. Keep as separate tool (current state)

Next: Wait for response before proceeding with integration.

UPDATE 2025-11-06: @neongreen gave permission to vendor! Quote: "I switched from beads to my own thing (tk) so I'm very happy to give beads-merge away — feel free to move it into the beads repo and I will point mono's readme to beads"

Next: Vendor beads-merge with full attribution


Consider backwards compatibility for ready() and list() return type change

PR #481 changed the return types of `ready()` and `list()` from `list[Issue]` to `list[IssueMinimal] | CompactedResult`. This is a breaking change for MCP clients.

## Impact Assessment
Breaking change affects:
- Any MCP client expecting `list[Issue]` from ready()
- Any MCP client expecting `list[Issue]` from list()
- Client code that accesses full Issue fields (description, design, acceptance_criteria, timestamps, dependencies, dependents)

## Current Behavior
- ready() returns `list[IssueMinimal] | CompactedResult`
- list() returns `list[IssueMinimal] | CompactedResult`
- show() still returns full `Issue` (good)

## Considerations
**Pros of current approach:**
- Forces clients to use show() for full details (good for context efficiency)
- Simple mental model (always use show for full data)
- Documentation warns about this

**Cons:**
- Clients expecting list[Issue] will break
- No graceful degradation option
- No migration period

## Potential Solutions
1. Add optional parameter `full_details=false` to ready/list (would increase payload)
2. Create separate tools: ready_minimal/list_minimal + ready_full/list_full
3. Accept breaking change and document upgrade path (current approach)
4. Version the MCP server and document migration guide

## Recommendation
Current approach (solution 3) is reasonable if:
- Changelog clearly documents the breaking change
- Migration guide provided to clients
- Error handling is graceful for clients expecting specific fields


Split test suites: fast vs. integration

Reorganize tests into separate packages/files for fast unit tests vs slow integration tests.

Benefits:
- Clear separation of concerns
- Easier to run just fast tests during development
- Can parallelize CI jobs better

Files to organize:
- beads_hash_multiclone_test.go (slow integration tests)
- beads_integration_test.go (medium-speed integration tests)
- Other test files (fast unit tests)


Identity Configuration

Implement identity system for sender field.

Configuration sources (in priority order):
1. --identity flag on commands
2. BEADS_IDENTITY environment variable
3. .beads/config.json: {"identity": "worker-name"}
4. Default: git user.name or hostname

New config file support:
- .beads/config.json for per-repo settings
- identity field for messaging

Helper function:
- GetIdentity() string - resolves identity from sources

Update bd mail send to use GetIdentity() for sender field.


Extract daemon logger functions to daemon_logger.go


Implement alias conflict resolution

Handle alias conflicts when multiple clones assign same alias to different issues.

## Scenario
```
Clone A: Creates bd-a1b2c3d4, assigns alias #42
Clone B: Creates bd-e5f6a7b8, assigns alias #42
After sync: Conflict! Which issue gets #42?
```

## Resolution Strategy: Content-Hash Ordering
Deterministic, same result on all clones:
```go
func ResolveAliasConflicts(conflicts []AliasConflict) []AliasRemapping {
    for _, conflict := range conflicts {
        // Sort by hash ID (lexicographic)
        sort.Strings(conflict.IssueIDs)

        // Winner: lowest hash ID (arbitrary but deterministic)
        winner := conflict.IssueIDs[0]

        // Losers: reassign to next available aliases
        for _, loser := range conflict.IssueIDs[1:] {
            newAlias := getNextAlias()
            remappings = append(remappings, AliasRemapping{
                IssueID:  loser,
                OldAlias: conflict.Alias,
                NewAlias: newAlias,
            })
        }
    }
    return remappings
}
```

## Detection During Import
File: internal/importer/importer.go
```go
func handleAliasConflicts(imported []Issue, existing []Issue) error {
    // Build alias map from imported issues
    aliasMap := make(map[int][]string)  // alias → issue IDs

    for _, issue := range imported {
        aliasMap[issue.Alias] = append(aliasMap[issue.Alias], issue.ID)
    }

    // Check against existing aliases
    for alias, importedIDs := range aliasMap {
        existingID := storage.GetIssueIDByAlias(alias)
        if existingID != "" {
            // Conflict! Resolve it
            allIDs := append(importedIDs, existingID)
            conflicts = append(conflicts, AliasConflict{
                Alias:    alias,
                IssueIDs: allIDs,
            })
        }
    }

    // Resolve and apply
    remappings := ResolveAliasConflicts(conflicts)
    applyAliasRemappings(remappings)
}
```

## Alternative Strategies (For Future Consideration)

### Priority-Based
```go
// Higher priority keeps alias
if issueA.Priority < issueB.Priority {
    winner = issueA
}
```

### Timestamp-Based (Last-Write-Wins)
```go
// Newer issue keeps alias
if issueA.UpdatedAt.After(issueB.UpdatedAt) {
    winner = issueA
}
```

### Manual Resolution
```bash
bd resolve-aliases --manual
# Interactive prompt for each conflict
```

## User Notification
```bash
$ bd sync
✓ Synced 5 issues
⚠ Alias conflicts resolved:
  - Issue bd-e5f6a7b8: alias changed from #42 to #100
  - Issue bd-9a8b7c6d: alias changed from #15 to #101
```

## Files to Create/Modify
- internal/storage/sqlite/alias_conflicts.go (new)
- internal/importer/importer.go (detect conflicts)
- cmd/bd/sync.go (show conflict notifications)

## Testing
- Test two clones assign same alias to different issues
- Test conflict resolution is deterministic (same on all clones)
- Test loser gets new alias
- Test winner keeps original alias
- Test multiple conflicts resolved in one import


GH#517: Fix Claude setting wrong priority syntax on new install

Claude uses 'medium' instead of P2/2 for priority, causing infinite error loops. bd prime hook or docs not clear enough. See: https://github.com/steveyegge/beads/issues/517


cmd/bd tests fail without -short flag (parallel test deadlock)


Improve test coverage for internal/export (37.1% → 60%)

The export package has only 37.1% test coverage. Export functionality needs good coverage to ensure data integrity.

Current coverage: 37.1%
Target coverage: 60%


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.


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

### ValidateState tests
- .beads/ exists → success
- .beads/ missing → appropriate error
- git dirty state detection

### CountImpact tests
- Empty .beads/ → zero counts
- With issues → correct count (open vs closed)
- With tombstones → correct count
- Returns HasUncommitted correctly

### Backup tests
- Creates backup with correct timestamp format
- Preserves all files and permissions
- Returns correct path
- Handles missing .beads/ gracefully
- Errors on pre-existing backup dir

### Git operation tests
- CheckGitState detects dirty, detached, not-a-repo
- GitRemoveBeads removes correct files
- GitCommitReset creates commit with message
- Operations skip gracefully when not in git repo

### Reset tests (with mocks/temp dirs)
- Soft reset removes files, calls init
- Hard reset includes git operations
- Dry run doesn't modify anything
- SkipInit flag prevents re-initialization
- Daemon killall is called
- Backup is created when requested

## Approach
- Can start with interface definitions (TDD style)
- Use testify for assertions
- Create temp directories for isolation
- Mock git operations where needed
- Test completion depends on implementation tasks

## File Location
`internal/reset/reset_test.go`
`internal/reset/backup_test.go`
`internal/reset/git_test.go`


Workaround for FastMCP outputSchema bug in Claude Code

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

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

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

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


Add topological sort utility functions

Create internal/importer/sort.go with utilities for depth-based sorting of issues. Functions: GetHierarchyDepth(id), SortByDepth(issues), GroupByDepth(issues). Include stable sorting for same-depth issues.


Fix revive style issues (78 issues)

Style violations: unused parameters (many cmd/args in cobra commands), missing exported comments, stuttering names (SQLiteStorage), indent-error-flow issues.

Fixed 19 revive issues:
- 14 unused-parameter (renamed to _)
- 2 redefines-builtin-id (max→maxCount, min→minInt)
- 3 indent-error-flow (gofmt fixed 2, skipped 1 complex nested one)

Remaining issues are acceptable: 11 unused-params in deeper code, 2 empty-blocks with comments, 1 complex indent case, 1 superfluous-else in test.


Add WebSocket support

## Feature Request

[Describe the desired feature]

## Motivation

[Why is this feature needed? What problem does it solve?]

## Use Cases

1. **Use Case 1**: [description]
2. **Use Case 2**: [description]

## Proposed Solution

[High-level approach to implementing this feature]

## Alternatives Considered

- **Alternative 1**: [description and why not chosen]
- **Alternative 2**: [description and why not chosen]


Code Review Followup Summary: PR #481 + PR #551

## Merged PRs Summary

### PR #551: Persist close_reason to issues table
- ✅ Merged successfully
- ✅ Bug fix: close_reason now persisted in database column (not just events table)
- ✅ Comprehensive test coverage added
- ✅ Handles reopen case (clearing close_reason)

**Followup Issues Filed:**
- bd-lxzx: Document close_reason in JSONL export format
- bd-077e: Update CLI documentation for close_reason field

---

### PR #481: Context Engineering Optimizations (80-90% context reduction)
- ✅ Merged successfully
- ✅ Lazy tool discovery: discover_tools() + get_tool_info()
- ✅ Minimal issue models: IssueMinimal (~80% smaller than full Issue)
- ✅ Result compaction: Auto-compacts results >20 items
- ✅ All 28 tests passing
- ⚠ Breaking change: ready() and list() return type changed

**Followup Issues Filed:**
- bd-b318: Add integration tests for CompactedResult
- bd-4u2b: Make compaction settings configurable (THRESHOLD, PREVIEW_COUNT)
- bd-2kf8: Document CompactedResult response format in CONTEXT_ENGINEERING.md
- bd-pdr2: Document backwards compatibility considerations

---

## Overall Assessment

Both PRs are production-ready with solid implementations. All critical functionality works and tests pass. Followup issues focus on:
1. Documentation improvements (5 issues)
2. Integration test coverage (1 issue)
3. Configuration flexibility (1 issue)
4. Backwards compatibility guidance (1 issue)

No critical bugs or design issues found.

## Review Completed By
Code review process completed. Issues auto-created for tracking improvements.


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.


GH#524: Package for Windows (winget)

Request to publish beads on winget for easy Windows installation. See: https://github.com/steveyegge/beads/issues/524


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