Implement content-hash based collision resolution for deterministic convergence

The current collision resolution uses creation timestamps to decide which issue to keep vs. remap. This is non-deterministic when two clones create issues at nearly the same time.

Root cause of bd-71107098:
- Clone A creates test-1="Issue from clone A" at T0
- Clone B creates test-1="Issue from clone B" at T0+30ms
- Clone B syncs first, remaps Clone A's to test-2
- Clone A syncs second, sees collision, remaps Clone B's to test-2
- Result: titles are swapped between clones

Solution:
- Use content-based hashing (title + description + priority + type)
- Deterministic winner: always keep issue with lower hash
- Same collision on different clones produces same result (idempotent)

Implementation:
- Modify ScoreCollisions in internal/storage/sqlite/collision.go
- Replace timestamp-based scoring with content hash comparison
- Ensure hash function is stable across platforms

Rename detection successfully implemented and tested!

**What was implemented:**
1. Content-hash based rename detection in DetectCollisions
2. When importing JSONL, if an issue has different ID but same content as DB issue, treat as rename
3. Delete old ID and accept new ID from JSONL
4. Added post-import re-export in sync command to flush rename changes
5. Added post-import commit to capture rename changes

**Test results:**
TestTwoCloneCollision now shows full convergence:
- Clone A: test-2="Issue from clone A", test-1="Issue from clone B"
- Clone B: test-1="Issue from clone B", test-2="Issue from clone A"

Both clones have **identical content** (titles match IDs correctly). Only timestamps differ (expected).

**What remains:**
- Test still expects exact JSON match including timestamps
- Could normalize timestamp comparison, but content convergence is the critical success metric
- The two-clone collision workflow now works without data corruption!


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

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

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

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

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

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

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

## Progress Update

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

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

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

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

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

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

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


Add "bd daemons" command for multi-daemon management

Add a new "bd daemons" command with subcommands to manage daemon processes across all beads repositories/worktrees. Should show all running daemons with metadata (version, workspace, uptime, last sync), allow stopping/restarting individual daemons, auto-clean stale processes, view logs, and show exclusive lock status.


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)


Extract daemon logger functions to daemon_logger.go


Extract daemon server functions to daemon_server.go


Issue 1 to reopen


Extract normalizeLabels to shared utility package

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

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

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

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

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


Add tests for internal/importer package

Currently 0.0% coverage. Need tests for JSONL import logic including collision detection and resolution.


Add fsnotify dependency to go.mod


Extract hash ID generation functions to hash_ids.go

Move generateHashID, getNextChildNumber, GetNextChildID to hash_ids.go


Write Agent Mail integration guide

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

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

File: docs/AGENT_MAIL.md

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


Implement bd resolve-conflicts (git merge conflicts in JSONL)

Automatically detect and resolve git merge conflicts in .beads/issues.jsonl file.

Features:
- Detect conflict markers in JSONL
- Parse conflicting issues from HEAD and BASE
- Provide mechanical resolution (remap duplicate IDs)
- Support AI-assisted resolution (requires internal/ai package)

See repair_commands.md lines 125-353 for design.


Create queries.go with core issue CRUD methods


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-2752a7a2, bd-9826b69a.

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


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

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

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

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

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

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

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

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

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

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


Fix TestFallbackToDirectModeEnablesFlush deadlock causing 10min test timeout

## Problem

TestFallbackToDirectModeEnablesFlush in direct_mode_test.go deadlocks for 9m59s before timing out, causing the entire test suite to take 10+ minutes instead of <10 seconds.

## Root Cause

Database lock contention between test cleanup and flushToJSONL():
- Test cleanup (line 36) tries to close DB via defer
- flushToJSONL() (line 132) is still accessing DB
- Results in deadlock: database/sql.(*DB).Close() waits for mutex while GetJSONLFileHash() holds it

## Stack Trace Evidence

```
goroutine 512 [sync.Mutex.Lock, 9 minutes]:
database/sql.(*DB).Close(0x14000643790)
  .../database/sql/sql.go:927 +0x84
github.com/steveyegge/beads/cmd/bd.TestFallbackToDirectModeEnablesFlush.func1()
  .../direct_mode_test.go:36 +0xf4

While goroutine running flushToJSONL() holds DB connection via GetJSONLFileHash()
```

## Impact

- Test suite: 10+ minutes → should be <10 seconds
- ALL other tests pass in ~4 seconds
- This ONE test accounts for 99.9% of test runtime

## Related

This is the EXACT same issue documented in MAIN_TEST_REFACTOR_NOTES.md for why main_test.go refactoring was deferred - global state manipulation + DB cleanup = deadlock.

## Fix Approaches

1. **Add proper cleanup sequencing** - stop flush goroutines BEFORE closing DB
2. **Use test-specific DB lifecycle** - ensure flush completes before cleanup
3. **Mock the flush mechanism** - avoid real DB for testing this code path
4. **Add explicit timeout handling** - fail fast with clear error instead of hanging

## Files

- cmd/bd/direct_mode_test.go:36-132
- cmd/bd/autoflush.go:353 (validateJSONLIntegrity)
- cmd/bd/autoflush.go:508 (flushToJSONLWithState)

## Acceptance

- Test passes without timeout
- Test suite completes in <10 seconds
- No deadlock between cleanup and flush operations


Update AGENTS.md and README.md with "bd daemons" documentation

Document the new "bd daemons" command and all subcommands in AGENTS.md and README.md. Include examples and troubleshooting guidance.


Testing & Validation

Ensure reliability through comprehensive testing.

Completed comprehensive Agent Mail test coverage analysis and implementation.

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

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

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

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

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

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


Audit all cmd/bd tests and group into suites

Analyze all 279 tests in cmd/bd and identify:
1. Which tests can share DB setup (most of them\!)
2. Which tests actually need isolation (export/import, git ops)
3. Optimal grouping into test suites

Create a mapping document showing:
- Current: 279 individual test functions
- Proposed: ~10-15 test suites with subtests
- Expected speedup per suite

Blocks all refactoring work.


Investigate and upgrade to modernc.org/sqlite 1.39.1+

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

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

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

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

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

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


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.


GH#146: No color showing in terminal for some users

User reports color not working in macOS (Taho 26.0.1) with iTerm 3.6.4 and Terminal.app, despite color working elsewhere in terminal. Python rich and printf escape codes work.

Need to investigate:
- Is NO_COLOR env var set?
- Terminal type detection?
- fatih/color library configuration
- Does bd list show colors? bd ready? bd init?
- What's the output of: echo $TERM, echo $NO_COLOR


Add circular dependency detection to bd doctor

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


Blocking issue


Q4 Platform Improvements

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


Show dependency status in bd show output

When bd show displays dependencies and dependents, include their status (open/closed/in_progress/blocked) for quick progress tracking. Improves context rebuilding and planning.


AI-supervised database migrations for safer schema evolution

## Problem

Database migrations can lose user data through edge cases that are hard to anticipate (e.g., GH #201 where bd migrate failed to set issue_prefix, or bd-d355a07d false positive data loss warnings). Since beads is designed to be run by AI agents, we should leverage AI to make migrations safer.

## Current State

Migrations run blindly with:
- No pre-flight validation
- No data integrity verification
- No rollback on failure
- Limited post-migration testing

Recent issues:
- GH #201: Migration didn't set issue_prefix config, breaking commands
- bd-d355a07d: False positive "data loss" warnings on collision resolution
- Users reported migration data loss (fixed but broader problem remains)

## Proposal: AI-Supervised Migration Framework

Use AI to supervise migrations through structured verification:

### 1. Pre-Migration Analysis
- AI reads migration code and current schema
- Identifies potential data loss scenarios
- Generates validation queries to verify assumptions
- Creates snapshot queries for before/after comparison

### 2. Migration Execution
- Take database backup/snapshot
- Run validation queries (pre-state)
- Execute migration in transaction
- Run validation queries (post-state)

### 3. Post-Migration Verification
- AI compares pre/post snapshots
- Verifies data integrity invariants
- Checks for unexpected data loss
- Validates config completeness (like issue_prefix)

### 4. Rollback on Anomalies
- If AI detects data loss, rollback transaction
- Present human-readable error report
- Suggest fix before retrying

## Example Flow

```
$ bd migrate

→ Analyzing migration plan...
→ AI identified 3 potential data loss scenarios
→ Generating validation queries...
→ Creating pre-migration snapshot...
→ Running migration in transaction...
→ Verifying post-migration state...
✓ All 247 issues accounted for
✓ Config table complete (issue_prefix: "mcp")
✓ Dependencies intact (342 relationships verified)
→ Migration successful!
```

If something goes wrong:
```
$ bd migrate

→ Analyzing migration plan...
→ AI identified issue: Missing issue_prefix config after migration
→ Recommendation: Add prefix detection step
→ Aborting migration - database unchanged
```

## Implementation Ideas

### A. Migration Validator Tool
Create `bd migrate --validate` that:
- Simulates migration on copy of database
- Uses AI to verify data integrity
- Reports potential issues before real migration

### B. Migration Test Generator
AI generates test cases for migrations:
- Edge cases (empty DB, large DB, missing config)
- Data integrity checks
- Regression tests

### C. Migration Invariants
Define invariants that AI checks:
- Issue count should not decrease (unless collision resolution)
- All required config keys present
- Foreign key relationships intact
- No orphaned dependencies

### D. Self-Healing Migrations
AI detects incomplete migrations and suggests fixes:
- Missing config values (like GH #201)
- Orphaned data
- Index inconsistencies

## Benefits

1. **Catch edge cases**: AI explores scenarios humans miss
2. **Self-documenting**: AI explains what migration does
3. **Agent-friendly**: Agents can run migrations confidently
4. **Fewer rollbacks**: Detect issues before committing
5. **Better testing**: AI generates comprehensive test suites

## Open Questions

1. Which AI model? (Fast: Haiku, Thorough: Sonnet/GPT-4)
2. How to balance safety vs migration speed?
3. Should AI validation be required or optional?
4. How to handle offline scenarios (no API access)?
5. What invariants should always be checked?

## Related Work

- bd-b245: Migration registry (makes migrations introspectable)
- GH #201: issue_prefix migration bug (motivating example)
- bd-d355a07d: False positive data loss warnings

## Progress

### ✅ Phase 1: Migration Invariants (COMPLETED)

**Implemented:**
- Created internal/storage/sqlite/migration_invariants.go with 3 invariants
- Updated RunMigrations() to verify invariants after migrations
- All tests pass ✓

### ✅ Phase 2: Inspection Tools (COMPLETED & PUSHED)

**Commit:** 1abe4e7 - "Add migration inspection tools for AI agents (bd-627d Phase 2)"

**Implemented:**
1. ✅ bd migrate --inspect --json - Shows migration plan
2. ✅ bd info --schema --json - Returns schema details
3. ✅ Migration warnings system
4. ✅ Documentation updated in AGENTS.md
5. ✅ All tests pass

### ✅ Phase 3: MCP Tools (COMPLETED & PUSHED)

**Commit:** 2493693 - "Add MCP tools for migration inspection (bd-627d Phase 3)"

**Implemented:**
1. ✅ inspect_migration(workspace_root) tool in beads-mcp
2. ✅ get_schema_info(workspace_root) tool in beads-mcp
3. ✅ Abstract methods in BdClientBase
4. ✅ CLI client implementations
5. ✅ All tests pass

**All phases complete!** Migration inspection fully integrated into MCP server.


Add validation and documentation for metadata key format

## Issues

### 1. No validation that keySuffix doesn't contain separator
```go
if keySuffix != "" {
    hashKey += ":" + keySuffix  // ❌ What if keySuffix contains ":"?
}
```

Add validation:
```go
if strings.Contains(keySuffix, ":") {
    return fmt.Errorf("keySuffix cannot contain ':' separator")
}
```

### 2. Inconsistent keySuffix semantics
Empty string means "global", non-empty means "scoped". Better to always pass explicit key:
```go
const (
    SingleRepoKey = "."
    // Multi-repo uses source_repo identifier
)
```

### 3. Metadata key format not documented
Need to document:
- `last_import_hash` - single-repo mode
- `last_import_hash:<repo_key>` - multi-repo mode
- `last_import_time` - when last import occurred
- `last_import_mtime` - fast-path optimization

Add to internal/storage/sqlite/schema.go or docs/


Rapid 2


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


Test Epic 2


Test label update feature


Implement bd stale command to find abandoned/forgotten issues

Add bd stale command to surface issues that haven't been updated recently and may need attention.

Use cases:
- In-progress issues with no recent activity (may be abandoned)
- Open issues that have been forgotten
- Issues that might be outdated or no longer relevant

Query logic should find non-closed issues where updated_at exceeds a time threshold.

Should support:
- --days N flag (default 30-90 days)
- --status filter (e.g., only in_progress)
- --json output for automation

References GitHub issue #184 where user expected this command to exist.


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


Assigned issue


bd info shows 'auto_start_disabled' even when daemon is crashed/missing


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


Add alias field to database schema

Extend database schema to support human-friendly aliases alongside hash IDs.

## Database Changes

### 1. Add alias column to issues table
```sql
ALTER TABLE issues ADD COLUMN alias INTEGER UNIQUE;
CREATE INDEX idx_issues_alias ON issues(alias);
```

### 2. Add alias counter table
```sql
CREATE TABLE alias_counter (
    id INTEGER PRIMARY KEY CHECK (id = 1),
    next_alias INTEGER NOT NULL DEFAULT 1
);
INSERT INTO alias_counter (id, next_alias) VALUES (1, 1);
```

### 3. Add alias conflict tracking (for multi-clone scenarios)
```sql
CREATE TABLE alias_history (
    issue_id TEXT NOT NULL,
    alias INTEGER NOT NULL,
    assigned_at TIMESTAMP NOT NULL,
    workspace_id TEXT NOT NULL,
    PRIMARY KEY (issue_id, alias)
);
```

## API Changes

### CreateIssue
- Generate hash ID
- Assign next available alias
- Store both in database

### ResolveAliasConflicts (new function)
- Detect conflicting alias assignments after import
- Apply resolution strategy (content-hash ordering)
- Reassign losers to next available aliases

## Migration Path
```bash
bd migrate --add-aliases  # Adds columns, assigns aliases to existing issues
```

## Files to Modify
- internal/storage/sqlite/schema.go
- internal/storage/sqlite/sqlite.go (CreateIssue, GetIssue)
- internal/storage/sqlite/aliases.go (new file for alias logic)
- internal/storage/sqlite/migrations.go

## Testing
- Test alias auto-assignment on create
- Test alias uniqueness constraint
- Test alias lookup performance
- Test alias conflict resolution


Update Nix flake Go modules hash


Latency test


cmd/bd test suite is absurdly slow - 279 tests taking 8+ minutes

# Problem

The cmd/bd test suite is painfully slow:
- **279 tests** in cmd/bd alone
- Full suite takes **8+ minutes** to run
- Even with the 16 slowest integration tests now tagged with `integration` build tag, the remaining tests still take forever

This makes the development loop unusable. We can't wait 8+ minutes every time we want to run tests.

# Root Cause Analysis

## 1. Sheer Volume
279 tests is too many for a single package. Even at 0.1s per test, that's 28 seconds minimum just for cmd/bd.

## 2. Each Test Creates Full Database + Temp Directories
Every test does heavy setup:
- Creates temp directory (`t.TempDir()` or `os.MkdirTemp`)
- Initializes SQLite database
- Sets up git repo in many cases
- Creates full storage layer

Example from the tests:
```go
func setupCLITestDB(t *testing.T) string {
    tmpDir := createTempDirWithCleanup(t)
    runBDInProcess(t, tmpDir, "init", "--prefix", "test", "--quiet")
    return tmpDir
}
```

This happens 279 times!

## 3. Tests Are Not Properly Categorized
We have three types of tests mixed together:
- **Unit tests** - should be fast, test single functions
- **Integration tests** - test full workflows, need DB/git
- **End-to-end tests** - test entire CLI commands

They're all lumped together in cmd/bd, all running every time.

# What We've Already Fixed

Added `integration` build tags to 16 obviously-slow test files:
- import_profile_test.go (performance benchmarking tests)
- export_mtime_test.go (tests with time.Sleep calls)
- cli_fast_test.go (full CLI integration tests)
- delete_test.go, import_uncommitted_test.go, sync_local_only_test.go (git integration)
- And 10 more in internal/ packages

These are now excluded from the default `go test ./...` run.

# Proposed Solutions

## Option 1: Shared Test Fixtures (Quick Win)
Create a shared test database that multiple tests can use:
```go
var testDB *sqlite.SQLiteStorage
var testDBOnce sync.Once

func getSharedTestDB(t *testing.T) storage.Storage {
    testDBOnce.Do(func() {
        // Create one DB for all tests
    })
    return testDB
}
```

**Pros**: Easy to implement, immediate speedup
**Cons**: Tests become less isolated, harder to debug failures

## Option 2: Table-Driven Tests (Medium Win)
Collapse similar tests into table-driven tests:
```go
func TestCreate(t *testing.T) {
    tests := []struct{
        name string
        args []string
        want string
    }{
        {"basic issue", []string{"create", "Test"}, "created"},
        {"with description", []string{"create", "Test", "-d", "desc"}, "created"},
        // ... 50 more cases
    }

    db := setupOnce(t) // Setup once, not 50 times
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // test using shared db
        })
    }
}
```

**Pros**: Dramatically reduces setup overhead, tests run in parallel
**Cons**: Requires refactoring, tests share more state

## Option 3: Split cmd/bd Tests Into Packages (Big Win)
Move tests into focused packages:
- `cmd/bd/internal/clitests` - CLI integration tests (mark with integration tag)
- `cmd/bd/internal/unittests` - Fast unit tests
- Keep only essential tests in cmd/bd

**Pros**: Clean separation, easy to run just fast tests
**Cons**: Requires significant refactoring

## Option 4: Parallel Execution (Quick Win)
Add `t.Parallel()` to independent tests:
```go
func TestSomething(t *testing.T) {
    t.Parallel() // Run this test concurrently with others
    // ...
}
```

**Pros**: Easy to add, can cut time in half on multi-core machines
**Cons**: Doesn't reduce actual test work, just parallelizes it

## Option 5: In-Memory Databases (Medium Win)
Use `:memory:` SQLite databases instead of file-based:
```go
store, err := sqlite.New(ctx, ":memory:")
```

**Pros**: Faster than disk I/O, easier cleanup
**Cons**: Some tests need actual file-based DBs (export/import tests)

# Recommended Approach

**Short-term (this week)**:
1. Add `t.Parallel()` to all independent tests in cmd/bd
2. Use `:memory:` databases where possible
3. Create table-driven tests for similar test cases

**Medium-term (next sprint)**:
4. Split cmd/bd tests into focused packages
5. Mark more integration tests appropriately

**Long-term (backlog)**:
6. Consider shared test fixtures with proper isolation

# Current Status

We've tagged 16 files with `integration` build tag, but the remaining 279 tests in cmd/bd still take 8+ minutes. This issue tracks fixing the cmd/bd test performance specifically.

# Target

Get `go test ./...` (without `-short` or `-tags=integration`) down to **under 30 seconds**.

# THE REAL ROOT CAUSE (Updated Analysis)

After examining the actual test code, the problem is clear:

## Every Test Creates Its Own Database From Scratch

Look at `create_test.go`:
```go
func TestCreate_BasicIssue(t *testing.T) {
    tmpDir := t.TempDir()                          // ← Creates temp dir
    testDB := filepath.Join(tmpDir, ".beads", "beads.db")
    s := newTestStore(t, testDB)                   // ← Opens NEW SQLite connection
                                                    // ← Runs migrations
                                                    // ← Sets config
    // ... actual test (3 lines)
}

func TestCreate_WithDescription(t *testing.T) {
    tmpDir := t.TempDir()                          // ← Creates ANOTHER temp dir
    testDB := filepath.Join(tmpDir, ".beads", "beads.db")
    s := newTestStore(t, testDB)                   // ← Opens ANOTHER SQLite connection
    // ... actual test (3 lines)
}
```

**This happens 279 times!**

## These Tests Don't Need Isolation!

Most tests are just checking:
- "Can I create an issue with a title?"
- "Can I create an issue with a description?"
- "Can I add labels?"

They don't conflict with each other. They could all share ONE database!

## The Fix: Test Suites with Shared Setup

Instead of:
```go
func TestCreate_BasicIssue(t *testing.T) {
    s := newTestStore(t, t.TempDir()+"/db")  // ← Expensive!
    // test
}

func TestCreate_WithDesc(t *testing.T) {
    s := newTestStore(t, t.TempDir()+"/db")  // ← Expensive!
    // test
}
```

Do this:
```go
func TestCreate(t *testing.T) {
    // ONE setup for all subtests
    s := newTestStore(t, t.TempDir()+"/db")

    t.Run("basic_issue", func(t *testing.T) {
        t.Parallel()  // Can run concurrently - tests don't conflict
        // test using shared `s`
    })

    t.Run("with_description", func(t *testing.T) {
        t.Parallel()
        // test using shared `s`
    })

    // ... 50 more subtests, all using same DB
}
```

**Result**: 50 tests → 1 database setup instead of 50!

## Why This Works

SQLite is fine with concurrent reads and isolated transactions. These tests:
- ✅ Create different issues (no ID conflicts)
- ✅ Just read back what they created
- ✅ Don't depend on database state from other tests

They SHOULD share a database!

## Real Numbers

Current:
- 279 tests × (create dir + init SQLite + migrations) = **8 minutes**

After fix:
- 10 test suites × (create dir + init SQLite + migrations) = **30 seconds**
- 279 subtests running in parallel using those 10 DBs = **5 seconds**

**Total: ~35 seconds instead of 8 minutes!**

## Implementation Plan

1. **Group related tests** into suites (Create, List, Update, Delete, etc.)
2. **One setup per suite** instead of per test
3. **Use t.Run() for subtests** with t.Parallel()
4. **Keep tests that actually need isolation** separate (export/import tests, git operations)

This is way better than shuffling tests into folders!

## Progress Update (2025-11-21)

✅ **Completed**:
- Audited all 280 tests, created TEST_SUITE_AUDIT.md ([deleted:bd-c49])
- Refactored create_test.go to shared DB pattern ([deleted:bd-y6d])
- Proven the pattern works: 11 tests now run in 0.04s with 1 DB instead of 11

❌ **Current Reality**:
- Overall test suite: Still 8+ minutes (no meaningful change)
- Only 1 of 76 test files refactored
- Saved ~10 DB initializations out of 280

## Acceptance Criteria (REALISTIC)

This task is NOT complete until:
- [ ] All P1 files refactored (create ✅, dep, stale, comments, list, ready)
- [ ] Test suite runs in < 2 minutes
- [ ] Measured and verified actual speedup

## Next Steps

1. Refactor remaining 5 P1 files: dep_test.go, stale_test.go, comments_test.go, list_test.go, ready_test.go
2. Measure actual time improvement after each file
3. Continue with P2 files if needed to hit <2min target


bd message: Refactor duplicated error messages


Add comprehensive tests for show.go commands (show, update, edit, close)

Need to add tests for cmd/bd/show.go which contains show, update, edit, and close commands.

**Challenge**: The existing test patterns use rootCmd.SetArgs() and rootCmd.Execute(), but the global `store` variable needs to match what the commands use. Initial attempt created tests that failed with "no issue found" because the test's store instance wasn't the same as the command's store.

**Files to test**:
- show.go (contains showCmd, updateCmd, editCmd, closeCmd)

**Coverage needed**:
- show command (single issue, multiple issues, JSON output, with dependencies, with labels, with compaction)
- update command (status, priority, title, assignee, description, multiple fields, multiple issues)
- edit command (requires $EDITOR, may need mocking)
- close command (single issue, multiple issues, with reason, JSON output)

**Test approach**:
1. Study working test patterns in init_test.go, list_test.go, etc.
2. Ensure BEADS_NO_DAEMON=1 is set
3. Properly initialize database with bd init
4. Use the command's global store, not a separate instance
5. May need to reset global state between tests

**Success criteria**:
- All test functions pass
- Coverage for show.go increases significantly
- Tests follow existing patterns in cmd/bd/*_test.go


Add aider integration for beads issue tracking

Based on GH#206 discussion, add aider-specific integration to help aider users work with beads. Aider requires explicit user command invocation via /run, unlike Claude Code which executes autonomously. Need to create setup command, configuration template, and documentation that accounts for aider's human-in-the-loop workflow.


Extract Daemon struct and config into internal/daemonrunner

Create internal/daemonrunner with Config struct and Daemon struct. Move daemon runtime logic from cmd/bd/daemon.go Run function into Daemon.Start/Stop methods.

Refactoring complete! Created internal/daemonrunner package with:
- Config struct (config.go)
- Daemon struct with Start/Stop methods (daemon.go)
- RPC server lifecycle (rpc.go)
- Sync loop implementation (sync.go)
- Git operations (git.go)
- Process management (process.go, flock_*.go)
- Logger setup (logger.go)
- Platform-specific signal handling (signals_*.go)
- Database fingerprint validation (fingerprint.go)

Build succeeds and most daemon tests pass. Import functionality still delegated to cmd/bd (marked with TODO(bd-b5a3)).


Integration Testing

Verify cache removal doesn't break any workflows


Export deduplication breaks when JSONL and export_hashes table diverge

## Problem

The export deduplication feature (timestamp-only skipping) breaks when the JSONL file and export_hashes table get out of sync, causing exports to skip issues that aren't actually in the file.

## Symptoms

- `bd export` reports "Skipped 128 issue(s) with timestamp-only changes"
- JSONL file only has 38 lines but DB has 149 issues
- export_hashes table has 149 entries
- Auto-import doesn't trigger (hash matches despite missing data)
- Two repos on same commit show different issue counts

## Root Cause

shouldSkipExport() in autoflush.go compares current issue hash with stored export_hashes entry. If they match, it skips export assuming the issue is already in the JSONL.

This assumption fails when:
1. Git operations (pull, reset, checkout) change JSONL without clearing export_hashes
2. Manual JSONL edits or corruption
3. Import operations that modify DB but don't update export_hashes
4. Partial exports that update export_hashes but don't complete

## Impact

- **Critical data loss risk**: Issues appear to be tracked but aren't persisted to git
- Breaks multi-repo sync (root cause of today's debugging session)
- Auto-import fails to detect staleness (hash matches despite missing data)
- Silent data corruption (no error messages, just missing issues)

## Reproduction

1. Have DB with 149 issues, all in export_hashes table
2. Truncate JSONL to 38 lines (simulate git reset or corruption)
3. Run `bd export` - it skips 128 issues
4. JSONL still has only 38 lines but export thinks it succeeded

## Current Workaround

```bash
sqlite3 .beads/beads.db "DELETE FROM export_hashes"
bd export -o .beads/beads.jsonl
```

## Proposed Solutions

**Option 1: Verify JSONL integrity before skipping**
- Count lines in JSONL, compare with export_hashes count
- If mismatch, clear export_hashes and force full export
- Safe but adds I/O overhead

**Option 2: Hash-based JSONL validation**
- Store hash of entire JSONL file in metadata
- Before export, check if JSONL hash matches
- If mismatch, clear export_hashes
- More efficient, detects any JSONL corruption

**Option 3: Disable timestamp-only deduplication**
- Remove the feature entirely
- Always export all issues
- Simplest and safest, but creates larger git commits

**Option 4: Clear export_hashes on git operations**
- Add post-merge hook to clear export_hashes
- Clear on any import operation
- Defensive approach but may over-clear

## Recommended Fix

Combination of Options 2 + 4:
1. Store JSONL file hash in metadata after export
2. Check hash before export, clear export_hashes if mismatch
3. Clear export_hashes on import operations
4. Add `bd validate` check for JSONL/export_hashes sync

## Files Involved

- cmd/bd/autoflush.go (shouldSkipExport)
- cmd/bd/export.go (export with deduplication)
- internal/storage/sqlite/metadata.go (export_hashes table)

## Recovery Session (2025-10-29 21:30)

### What Happened
- Created 14 new hash ID issues (bd-f8b764c9 through bd-f8b764c9.1)
- bd sync appeared to succeed
- Canonical repo (~/src/beads): 162 issues in DB + JSONL ✓
- Secondary repo (fred/beads): Only 145 issues vs 162 in canonical ✗
- Both repos on same git commit but different issue counts!

### Bug Manifestation During Recovery

1. **Initial state**: fred/beads had 145 issues, 145 lines in JSONL, 145 export_hashes entries

2. **After git reset --hard origin/main**:
   - JSONL: 162 lines (from git)
   - DB: 150 issues (auto-import partially worked)
   - Auto-import failed with UNIQUE constraint error

3. **After manual import --resolve-collisions**:
   - DB: 160 issues
   - JSONL: Still 162 lines
   - export_hashes: 159 entries

4. **After bd export**:
   - **JSONL reduced to 17 lines!** ← The bug in action
   - export_hashes: 159 entries (skipped exporting 142 issues)
   - Silent data loss - no error message

5. **After clearing export_hashes and re-export**:
   - JSONL: 159 lines (missing 3 issues still)
   - DB: 159 issues
   - Still diverged from canonical

### The Bug Loop
Once export_hashes and JSONL diverge:
- Export skips issues already in export_hashes
- But those issues aren't actually in JSONL
- This creates corrupt JSONL with missing issues
- Auto-import can't detect the problem (file hash matches what was exported)
- Data is lost with no error messages

### Recovery Solution
Couldn't break the loop with export alone. Had to:
1. Copy .beads/beads.db from canonical repo
2. Clear export_hashes
3. Full re-export
4. Finally converged to 162 issues

### Key Learnings

1. **The bug is worse than we thought**: It can create corrupt exports (17 lines instead of 162!)

2. **Auto-import can't save you**: Once export is corrupt, auto-import just imports the corrupt data

3. **Silent failure**: No warnings, no errors, just missing issues

4. **Git operations trigger it**: git reset, git pull, etc. change JSONL without clearing export_hashes

5. **Import operations populate export_hashes**: Even manual imports update export_hashes, setting up future export failures

### Immediate Action Required

**DISABLE EXPORT DEDUPLICATION NOW**

This feature is fundamentally broken and causes data loss. Should be disabled until properly fixed.

Quick fix options:
- Set environment variable to disable feature
- Comment out shouldSkipExport check
- Always clear export_hashes before export
- Add validation that DB count == JSONL line count before allowing export

### Long-term Fix

Need Option 2 + 4 from proposed solutions:
1. Store JSONL file hash after every successful export
2. Before export, verify JSONL hash matches expected
3. If mismatch, log WARNING and clear export_hashes
4. Clear export_hashes on every import operation
5. Add git post-merge hook to clear export_hashes
6. Add `bd validate` command to detect divergence


Fix monitor-webui status filter


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)


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


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


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

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

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

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

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

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

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

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

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


bd delete fails to find closed issues

## Steps to Reproduce
1. Close some issues with `bd close`
2. Try to delete them with `bd delete <ids> --force`
3. Get error "issues not found"

## Expected Behavior
Should delete the closed issues

## Actual Behavior
```
Error: issues not found: bd-74ee, bd-9b13, bd-72w, bd-149, bd-5iv, bd-78w
```

But `bd list --status closed --json` shows they exist.

## Root Cause
Likely the delete command is only looking for open issues, or there's a race condition with auto-import.


bd-hv01: Three full JSONL reads on every sync (performance)

Problem: computeAcceptedDeletions reads three JSONL files completely into memory (base, left, merged). For 1000 issues at 1KB each, this is 3MB read and 3000 JSON parse operations.

Impact: Acceptable now (~20-35ms overhead) but will be slow for large repos (10k+ issues).

Possible optimizations: single-pass streaming, memory-mapped files, binary format, incremental snapshots.

Files: cmd/bd/deletion_tracking.go:101-208


Update FAQ: How does OpenSpec/spec-driven development relate to Beads?

Answer the question raised in GH discussion #240 about OpenSpec integration.

Answer Summary:
- Single-agent: OpenSpec → Beads → Code
- Multi-agent swarm: Use Gastown as coordination layer
- Gastown provides OpenSpec-style planning infrastructure
- Planning agents create specs, distribute as Beads issues
- 'Gastown plans, Beads remembers, Agents execute'

Action Items:
1. Add FAQ entry explaining the architecture layers
2. Link to Gastown planning design (gt-cke)
3. Reference discussion #240 and rbergman's insights
4. Clarify: Beads focuses on execution memory, not planning

Cross-Reference:
- Gastown issue gt-cke: Design OpenSpec-style planning layer
- GH discussion #240: https://github.com/steveyegge/beads/discussions/240

Keep FAQ concise - detailed architecture lives in Gastown docs.


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


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.


Test simple issue


Refactor create_test.go to use shared DB setup

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

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

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


Rapid 1


Integration Layer Implementation

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

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


Update MCP server for hash IDs

Update beads-mcp server to support hash IDs and aliases.

## Changes Needed

### 1. MCP Function Signatures (No Change)
Functions already use issue IDs as strings, so they work with hash IDs:

```python
# These already work!
beads_show(issue_id: str)           # Accepts bd-af78e9a2 or #42
beads_update(issue_id: str, ...)    # Accepts both formats
beads_close(issue_ids: List[str])   # Accepts both formats
```

### 2. Add Alias Resolution Helper
File: integrations/beads-mcp/src/beads_mcp/server.py

```python
def resolve_issue_id(issue_id: str) -> str:
    """Resolve alias to hash ID if needed."""
    # Hash ID: pass through
    if issue_id.startswith('bd-') and len(issue_id) == 11:
        return issue_id

    # Alias: #42 or 42
    alias_str = issue_id.lstrip('#')
    try:
        alias = int(alias_str)
        # Call bd to resolve
        result = subprocess.run(
            ['bd', 'alias', 'find', f'bd-{alias}'],
            capture_output=True, text=True
        )
        if result.returncode == 0:
            return result.stdout.strip()
    except ValueError:
        pass

    # Invalid format
    raise ValueError(f"Invalid issue ID: {issue_id}")
```

### 3. Update Response Formatting
Show aliases in responses:

```python
@server.call_tool()
async def beads_show(issue_id: str) -> List[TextContent]:
    resolved_id = resolve_issue_id(issue_id)

    result = subprocess.run(['bd', 'show', resolved_id], ...)

    # Parse response and add alias info
    # Format: "bd-af78e9a2 (alias: #42)"
    ...
```

### 4. Add beads_alias_* Functions

```python
@server.call_tool()
async def beads_alias_list() -> List[TextContent]:
    """List all alias mappings."""
    result = subprocess.run(['bd', 'alias', 'list'], ...)
    return [TextContent(type="text", text=result.stdout)]

@server.call_tool()
async def beads_alias_set(alias: int, issue_id: str) -> List[TextContent]:
    """Manually assign alias to issue."""
    result = subprocess.run(['bd', 'alias', 'set', str(alias), issue_id], ...)
    return [TextContent(type="text", text=result.stdout)]

@server.call_tool()
async def beads_alias_compact() -> List[TextContent]:
    """Compact aliases to fill gaps."""
    result = subprocess.run(['bd', 'alias', 'compact'], ...)
    return [TextContent(type="text", text=result.stdout)]
```

### 5. Update Documentation
File: integrations/beads-mcp/README.md

```markdown
## Issue IDs (v2.0+)

The MCP server accepts both hash IDs and aliases:

```python
# Using hash IDs
await beads_show(issue_id="bd-af78e9a2")

# Using aliases
await beads_show(issue_id="#42")
await beads_show(issue_id="42")  # Shorthand
```

## Alias Management

New functions for alias control:

- `beads_alias_list()` - List all alias mappings
- `beads_alias_set(alias, issue_id)` - Manually assign alias
- `beads_alias_compact()` - Compact aliases to fill gaps

## Migration

After migrating to hash IDs:
1. Update beads-mcp: `pip install --upgrade beads-mcp`
2. Restart MCP server
3. All existing workflows continue to work
```

### 6. Version Compatibility
Detect and handle both v1.x and v2.0 formats:

```python
def detect_beads_version() -> str:
    """Detect if beads is using sequential or hash IDs."""
    result = subprocess.run(['bd', 'list', '-n', '1'], ...)
    first_id = parse_first_issue_id(result.stdout)

    if first_id.startswith('bd-') and len(first_id) > 11:
        return '2.0'  # Hash ID
    else:
        return '1.x'  # Sequential ID

# On startup
beads_version = detect_beads_version()
logger.info(f"Detected beads version: {beads_version}")
```

## Testing

### Unit Tests
File: integrations/beads-mcp/tests/test_hash_ids.py

```python
def test_resolve_hash_id():
    """Hash IDs pass through unchanged."""
    assert resolve_issue_id("bd-af78e9a2") == "bd-af78e9a2"

def test_resolve_alias():
    """Aliases resolve to hash IDs."""
    # Mock bd alias find command
    assert resolve_issue_id("#42") == "bd-af78e9a2"
    assert resolve_issue_id("42") == "bd-af78e9a2"

def test_invalid_id():
    """Invalid IDs raise ValueError."""
    with pytest.raises(ValueError):
        resolve_issue_id("invalid")
```

### Integration Tests
```python
async def test_show_with_hash_id(mcp_server):
    result = await mcp_server.beads_show(issue_id="bd-af78e9a2")
    assert "bd-af78e9a2" in result[0].text

async def test_show_with_alias(mcp_server):
    result = await mcp_server.beads_show(issue_id="#42")
    assert "bd-af78e9a2" in result[0].text  # Resolved
```

## Backward Compatibility
The MCP server should work with both:
- Beads v1.x (sequential IDs)
- Beads v2.0+ (hash IDs)

Detection happens at runtime based on issue ID format.

## Files to Modify
- integrations/beads-mcp/src/beads_mcp/server.py
- integrations/beads-mcp/README.md
- integrations/beads-mcp/tests/test_hash_ids.py (new)
- integrations/beads-mcp/pyproject.toml (bump version)

## Deployment
```bash
cd integrations/beads-mcp
# Bump version to 2.0.0
poetry version 2.0.0
# Publish to PyPI
poetry publish --build
```


Fix `bd sync` failure due to daemon auto-export timestamp skew

`bd sync` fails with false-positive "JSONL is newer than database" after daemon auto-export.
Root Cause: Daemon exports local changes to JSONL, updating its timestamp. `bd sync` sees JSONL.mtime > DB.mtime and assumes external changes, blocking export.
Proposed Fixes:
1. `bd sync` auto-imports if timestamp matches but content differs (or just auto-imports).
2. Content-based comparison instead of timestamp only.


Message System Improvements

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


Create tip system infrastructure for contextual hints

Implement a tip/hint system that shows helpful contextual messages after successful commands. This is different from the existing error-path "Hint:" messages - tips appear on success paths to educate users about features they might not know about.


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


Use TryResurrectParentChain instead of TryResurrectParent in GetNextChildID

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

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

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

## Solution
Replace TryResurrectParent with TryResurrectParentChain:

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

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

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

## Testing
Add test case for deeply nested resurrection


Add error handling test for cmd.Help() in search command

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

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

In cmd/bd/search.go:39, the return value of cmd.Help() is not checked, flagged by errcheck linter.

Add test to verify:
- Error handling when cmd.Help() fails (e.g., output redirection fails)
- Proper error propagation to caller
- Command still exits gracefully on help error

This ensures the search command handles help errors properly and doesn't silently ignore failures.

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


Test basic bd commands in WASM (init, create, list)

Compile and verify basic bd functionality works in WASM:
- Test bd init --quiet
- Test bd create with simple issue
- Test bd list --json output
- Verify SQLite database creation and queries work
- Document any runtime issues or workarounds needed


Add integration tests for adapter library

Test suite for beads_mail_adapter.py covering all scenarios.

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

File: lib/test_beads_mail_adapter.py

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

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

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

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


Create Docker image for Agent Mail

Containerize Agent Mail server for easy deployment.

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

File: deployment/agent-mail/Dockerfile


Handle unchecked errors (errcheck - 683 issues)

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

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


Multi-repo contributor workflow support

Implement separate repository support for OSS contributors to prevent PR pollution while maintaining git ledger and multi-clone sync. Based on contributor-workflow-analysis.md Solution #4.


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.


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


Fix TestTwoCloneCollision timeout


Add test coverage for internal/config package


Implement "bd daemons logs" subcommand

Add command to view daemon logs for a specific workspace. Requires daemon logging to file (may need separate issue for log infrastructure).


Benchmark Suite for Critical Operations

Extend existing benchmark suite with comprehensive benchmarks for critical operations at 10K-20K scale.

Existing benchmarks (keep these):
- cycle_bench_test.go - Cycle detection up to 5K issues (linear, tree, dense graphs)
- compact_bench_test.go - Compaction candidate queries (100 issues)
- internal/rpc/bench_test.go - Daemon vs direct mode comparison

New benchmarks to add in sqlite_bench_test.go (~10-12 total):
1. GetReadyWork - Simple, deep hierarchies, cross-linked (CRITICAL - not currently benchmarked)
2. SearchIssues - No filters, complex filters (CRITICAL - not currently benchmarked)
3. CreateIssue - Single issue creation
4. UpdateIssue - Status/priority/assignee changes
5. AddDependency - Extend to 10K/20K scale (currently only up to 5K)
6. JSONL Export - Full export performance
7. JSONL Import - Import performance

Scale levels:
- Large: 10K issues (5K open, 5K closed)
- XLarge: 20K issues (10K open, 10K closed)

Implementation:
- NEW FILE: internal/storage/sqlite/sqlite_bench_test.go
- Keep existing cycle_bench_test.go and compact_bench_test.go unchanged
- Build tag: //go:build bench
- Standard testing.B benchmarks
- b.ReportAllocs() for memory tracking
- Test both SQLite and JSONL-imported databases

Always generates CPU and memory profiles for analysis.


Add 'new' as alias for 'create' command


bd show --json doesn't include dependency type field

Fix GitHub issue #202. The JSON output from bd show and bd list commands should include the dependency type field (and optionally created_at, created_by) to match internal storage format and enable better tooling integration.

PR #203 updated with cleaner implementation: https://github.com/steveyegge/beads/pull/203

## Final Implementation

Cleanest possible approach - no internal helper methods needed:

**Design:**
- `GetDependenciesWithMetadata()` / `GetDependentsWithMetadata()` - canonical implementations with full SQL query
- `GetDependencies()` / `GetDependents()` - thin wrappers that strip metadata for backward compat
- `scanIssuesWithDependencyType()` - shared helper for scanning rows with dependency type

**Benefits:**
- Single source of truth - the `...WithMetadata()` methods ARE the implementation
- Eliminated ~139 lines of duplicated SQL and scanning code
- All tests passing (14 dependency-related tests)
- Backward compatible
- dependency_type field appears correctly in JSON output

**Note on scan helpers:**
The duplication between `scanIssues()` and `scanIssuesWithDependencyType()` is necessary because they handle different SQL result shapes (16 vs 17 columns). This is justified as they serve fundamentally different purposes based on query structure.


Phase 3: Enhance daemon robustness for GH #353

Improve daemon health checks and metadata refresh to prevent staleness issues.

**Tasks:**
1. Enhance daemon health checks to detect unreachable daemons
2. Add daemon metadata refresh (check disk every 5s)
3. Comprehensive testing in sandbox environments

**Implementation:**
- cmd/bd/main.go: Better health check error handling (lines 300-367)
- cmd/bd/daemon_event_loop.go: Periodic metadata refresh
- cmd/bd/daemon_unix.go: Permission-aware process checks

**References:**
- docs/GH353_INVESTIGATION.md (Solutions 4 & 5, lines 161-209)
- Depends on: Phase 2 (bd-u3t)

**Acceptance Criteria:**
- Daemon detects when it's unreachable and auto-switches to direct mode
- Daemon picks up external import operations without restart
- All edge cases handled gracefully


Add bd init --team wizard

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


Wrap git operations in GitClient interface

Create internal/daemonrunner/git.go with GitClient interface (HasUpstream, HasChanges, Commit, Push, Pull). Default implementation using os/exec. Use in Syncer and Run loop for testability.


Delete server_cache_storage.go

Remove the entire cache implementation file (~286 lines)


Widespread double JSON encoding bug in daemon mode RPC calls

Multiple CLI commands had the same double JSON encoding bug found in bd-1048. All commands that called ResolveID via RPC used string(resp.Data) instead of properly unmarshaling the JSON response. This caused IDs to retain JSON quotes ("bd-1048" instead of bd-1048), which then got double-encoded when passed to subsequent RPC calls.

Affected commands:
- bd show (3 instances)
- bd dep add/remove/tree (5 instances)
- bd label add/remove/list (3 instances)
- bd reopen (1 instance)

Root cause: resp.Data is json.RawMessage (already JSON-encoded), so string() conversion preserves quotes.

Fix: Replace all string(resp.Data) with json.Unmarshal(resp.Data, &id) for proper deserialization.

All commands now tested and working correctly with daemon mode.


Export deduplication breaks when JSONL and export_hashes table diverge

## Problem

The export deduplication feature (timestamp-only skipping) breaks when the JSONL file and export_hashes table get out of sync, causing exports to skip issues that aren't actually in the file.

## Symptoms

- `bd export` reports "Skipped 128 issue(s) with timestamp-only changes"
- JSONL file only has 38 lines but DB has 149 issues
- export_hashes table has 149 entries
- Auto-import doesn't trigger (hash matches despite missing data)
- Two repos on same commit show different issue counts

## Root Cause

shouldSkipExport() in autoflush.go compares current issue hash with stored export_hashes entry. If they match, it skips export assuming the issue is already in the JSONL.

This assumption fails when:
1. Git operations (pull, reset, checkout) change JSONL without clearing export_hashes
2. Manual JSONL edits or corruption
3. Import operations that modify DB but don't update export_hashes
4. Partial exports that update export_hashes but don't complete

## Impact

- **Critical data loss risk**: Issues appear to be tracked but aren't persisted to git
- Breaks multi-repo sync (root cause of today's debugging session)
- Auto-import fails to detect staleness (hash matches despite missing data)
- Silent data corruption (no error messages, just missing issues)

## Reproduction

1. Have DB with 149 issues, all in export_hashes table
2. Truncate JSONL to 38 lines (simulate git reset or corruption)
3. Run `bd export` - it skips 128 issues
4. JSONL still has only 38 lines but export thinks it succeeded

## Current Workaround

```bash
sqlite3 .beads/beads.db "DELETE FROM export_hashes"
bd export -o .beads/beads.jsonl
```

## Proposed Solutions

**Option 1: Verify JSONL integrity before skipping**
- Count lines in JSONL, compare with export_hashes count
- If mismatch, clear export_hashes and force full export
- Safe but adds I/O overhead

**Option 2: Hash-based JSONL validation**
- Store hash of entire JSONL file in metadata
- Before export, check if JSONL hash matches
- If mismatch, clear export_hashes
- More efficient, detects any JSONL corruption

**Option 3: Disable timestamp-only deduplication**
- Remove the feature entirely
- Always export all issues
- Simplest and safest, but creates larger git commits

**Option 4: Clear export_hashes on git operations**
- Add post-merge hook to clear export_hashes
- Clear on any import operation
- Defensive approach but may over-clear

## Recommended Fix

Combination of Options 2 + 4:
1. Store JSONL file hash in metadata after export
2. Check hash before export, clear export_hashes if mismatch
3. Clear export_hashes on import operations
4. Add `bd validate` check for JSONL/export_hashes sync

## Files Involved

- cmd/bd/autoflush.go (shouldSkipExport)
- cmd/bd/export.go (export with deduplication)
- internal/storage/sqlite/metadata.go (export_hashes table)


Add richer query capabilities to bd list

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

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

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

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

## Progress Update

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

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

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

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


Add transaction helper to replace manual COMMIT/ROLLBACK

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

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


Baseline quality gate failure: lint

The lint quality gate is failing on the baseline (main branch).

This blocks the executor from claiming work until fixed.

Error: golangci-lint failed: exit status 1

Output:
```
cmd/bd/search.go:39:12: Error return value of `cmd.Help` is not checked (errcheck)
			cmd.Help()
			        ^
cmd/bd/clean.go:118:15: G304: Potential file inclusion via variable (gosec)
	file, err := os.Open(gitignorePath)
	             ^
cmd/bd/doctor/gitignore.go:98:12: G306: Expect WriteFile permissions to be 0600 or less (gosec)
	if err := os.WriteFile(gitignorePath, []byte(GitignoreTemplate), 0644); err != nil {
	          ^
cmd/bd/merge.go:121:16: G204: Subprocess launched with variable (gosec)
			gitRmCmd := exec.Command("git", "rm", "-f", "--quiet", fullPath)
			            ^
cmd/bd/doctor.go:167:20: `cancelled` is a misspelling of `canceled` (misspell)
		fmt.Println("Fix cancelled.")
		                 ^
cmd/bd/flush_manager.go:139:42: `cancelling` is a misspelling of `canceling` (misspell)
		// Send shutdown request FIRST (before cancelling context)
		                                       ^
cmd/bd/flush_manager.go:261:15: `cancelled` is a misspelling of `canceled` (misspell)
			// Context cancelled (shouldn't normally happen)
			           ^
cmd/bd/flush_manager.go:269:55: (*FlushManager).performFlush - result 0 (error) is always nil (unparam)
func (fm *FlushManager) performFlush(fullExport bool) error {
                                                      ^
8 issues:
* errcheck: 1
* gosec: 3
* misspell: 3
* unparam: 1

```


Add tests for DetectCycles to improve coverage from 29.6%

DetectCycles currently has 29.6% coverage. Need comprehensive tests for:
- Simple cycles (A->B->A)
- Complex multi-node cycles
- Acyclic graphs (should not detect cycles)
- Self-loops
- Multiple independent cycles
- Edge cases (empty graph, single node)


Document error handling strategy for metadata update failures

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

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


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.


Add test coverage for internal/git package


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


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.


bd-hv01: Multi-repo mode not tested with deletion tracking

Problem: Test suite has no coverage for multi-repo mode. ExportToMultiRepo creates multiple JSONL files but snapshot files are hardcoded to single JSONL location.

Impact: Deletion tracking likely silently broken for multi-repo users, could cause data loss.

Fix: Add test and update snapshot logic to handle multiple JSONL files.

Files: cmd/bd/deletion_tracking_test.go, cmd/bd/deletion_tracking.go, cmd/bd/daemon_sync.go:24-34


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)


Implement auto-merge functionality in duplicates command

The duplicates.go file has a TODO at line 95 to implement the performMerge function for automatic duplicate merging. Currently it just prints a warning message. This would automate the merge process instead of just suggesting commands.


Improve compact/daemon/merge documentation and UX

Multiple documentation and UX issues encountered:
1. "bd compact --analyze" fails with misleading "requires SQLite storage" error when daemon is running. Needs --no-daemon or better error.
2. "bd merge" help text is outdated (refers to 3-way merge instead of issue merging).
3. Daemon mode purpose isn't clear to local-only users.
4. Compact/cleanup commands are hard to discover.

Proposed fixes:
- Fix compact+daemon interaction or error message.
- Update "bd merge" help text.
- Add "when to use daemon" section to docs.
- Add maintenance section to quickstart.