Channel isolation test - beads

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


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

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

Current coverage: 28.4%
Target coverage: 50%


bd-hv01: Brittle string comparison breaks with JSON field reordering

## Problem
deletion_tracking.go:125 uses string comparison to detect unchanged issues:

```go
if leftLine, existsInLeft := leftIndex[id]; existsInLeft && leftLine == baseLine {
    deletions = append(deletions, id)
}
```

This breaks if:
- JSON field order changes (legal in JSON)
- Timestamps updated by import/export
- Whitespace/formatting changes
- Floating point precision varies

## Example Failure
```json
// baseLine
{"id":"bd-1","priority":1,"status":"open"}
// leftLine (same data, different order)
{"id":"bd-1","status":"open","priority":1}
```
These are semantically identical but string comparison fails.

## Fix
Parse and compare JSON semantically:
```go
func jsonEquals(a, b string) bool {
    var objA, objB map[string]interface{}
    json.Unmarshal([]byte(a), &objA)
    json.Unmarshal([]byte(b), &objB)
    return reflect.DeepEqual(objA, objB)
}
```

## Files Affected
- cmd/bd/deletion_tracking.go:125
- cmd/bd/deletion_tracking.go:134-170 (buildIDToLineMap)


bd sync should not stage gitignored snapshot files

## Problem

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

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

## Root Cause

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

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

## Suggested Fix

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

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

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

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


Optimize Memory backend GetIssueByExternalRef with index

Currently GetIssueByExternalRef in Memory storage uses O(n) linear search through all issues.

Current code (memory.go:282-308):
for _, issue := range m.issues {
    if issue.ExternalRef != nil && *issue.ExternalRef == externalRef {
        return &issueCopy, nil
    }
}

Proposed optimization:
- Add externalRefToID map[string]string to MemoryStorage
- Maintain it in CreateIssue, UpdateIssue, DeleteIssue
- Achieve O(1) lookup like SQLite's index

Impact: Low (--no-db mode typically has smaller datasets)
Related: bd-1022


Test lowercase p0


Code review follow-up for bd-dvd and bd-ymj fixes

Track improvements and issues identified during code review of parent resurrection (bd-dvd) and export metadata (bd-ymj) bug fixes.

## Context
Code review identified several areas for improvement:
- Code duplication in metadata updates
- Missing multi-repo support
- Test coverage gaps
- Potential race conditions

## Related Issues
Original bugs fixed: bd-dvd, bd-ymj

## Goals
- Eliminate code duplication
- Add multi-repo support where needed
- Improve test coverage
- Address edge cases


Beads commands operate on stale database without warning

All beads read operations should validate database is in sync with JSONL before proceeding.

**Current Behavior:**
- Commands can query/read from stale database
- Only mutation operations (like 'bd sync') check if JSONL is newer
- User gets incorrect results without realizing database is out of sync

**Expected Behavior:**
- All beads commands should have pre-flight check for database freshness
- If JSONL is newer than database, refuse to operate with error: "Database out of sync. Run 'bd import' first."
- Same safety check that exists for 'bd sync' should apply to ALL operations

**Impact:**
- Users make decisions based on incomplete/outdated data
- Silent failures lead to confusion (e.g., thinking issues don't exist when they do)
- Similar to running git commands on stale repo without being warned to pull

**Example:**
- Searched for bd-g9eu issue file: not found
- Issue exists in .beads/issues.jsonl (in git)
- Database was stale, but no warning was given
- Led to incorrect conclusion that issue was already closed/deleted

## Implementation Complete

**Phase 1: Created staleness check (cmd/bd/staleness.go)**
- ensureDatabaseFresh() function checks JSONL mtime vs last_import_time
- Returns error with helpful message when database is stale
- Auto-skips in daemon mode (daemon has auto-import)

**Phase 2: Added to all read commands**
- list, show, ready, status, stale, info, duplicates, validate
- Check runs before database queries in direct mode
- Daemon mode already protected via checkAndAutoImportIfStale()

**Phase 3: Code Review Findings**
See follow-up issues:
- bd-XXXX: Add warning when staleness check errors
- bd-YYYY: Improve CheckStaleness error handling
- bd-ZZZZ: Refactor redundant daemon checks (low priority)

**Testing:**
- Build successful: go build ./cmd/bd
- Binary works: ./bd --version
- Ready for manual testing

**Next Steps:**
1. Test with stale database scenario
2. Implement review improvements
3. Close issue when tests pass


Add bd deleted command for audit trail

Parent: bd-imj

Add command to view deletion history.

Usage:
  bd deleted              # Show recent deletions (last 7 days)
  bd deleted --since=30d  # Show deletions in last 30 days
  bd deleted --all        # Show all tracked deletions
  bd deleted bd-xxx       # Show deletion details for specific issue

Output format:
  bd-xxx  2025-11-25 10:00  stevey  duplicate of bd-yyy
  bd-yyy  2025-11-25 10:05  claude  cleanup

Acceptance criteria:
- List deletions with timestamp, actor, reason
- Filter by time range
- Lookup specific issue ID
- JSON output option for scripting


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.


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.


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.


bd blocked shows empty (GH#545)

bd blocked only shows dependency-blocked issues, not status=blocked issues.

Either:
- Include status=blocked issues, OR
- Document/rename to clarify behavior

Fix in: cmd/bd/blocked.go


Daemon crash recovery: panic handler + socket cleanup

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

This ensures better diagnostics and cleaner state after crashes.


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


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.


Add comprehensive tests for config.yaml issue-prefix migration

The GH #209 config.yaml migration lacks test coverage:

Missing tests:
- config.SetIssuePrefix() edge cases (empty file, comments, malformed YAML)
- config.GetIssuePrefix() with various config states
- MigrateConfigToYAML() automatic migration logic
- bd init writing to config.yaml instead of DB
- bd migrate DB→config.yaml migration path

Test scenarios needed:
1. SetIssuePrefix with empty config.yaml
2. SetIssuePrefix with existing config.yaml (preserves other settings)
3. SetIssuePrefix with commented issue-prefix line
4. SetIssuePrefix atomic write (temp file cleanup)
5. GetIssuePrefix fallback behavior
6. MigrateConfigToYAML when config.yaml missing prefix but DB has it
7. MigrateConfigToYAML when both missing (detect from issues)
8. MigrateConfigToYAML when config.yaml already has prefix (no-op)
9. Integration test: fresh bd init writes to config.yaml only
10. Integration test: upgrade from v0.21 DB migrates to config.yaml

Priority 1 because this is a user-facing migration affecting all users upgrading to v0.22.


Benchmark git traffic reduction

Automated benchmark comparing git operations with/without Agent Mail.

Acceptance Criteria:
- Script that processes 50 issues
- Counts git operations (pull, commit, push)
- Generates comparison report
- Verifies ≥70% reduction
- Fails if regression detected

File: tests/benchmarks/git_traffic.py

Output: Without Agent Mail: 450 git operations, With Agent Mail: 135 git operations, Reduction: 70%

Implemented automated benchmark script with following features:
- Processes configurable number of issues (default 50)
- Compares git operations in two modes: git-only vs Agent Mail
- Generates detailed comparison report with statistics
- Exit code reflects pass/fail based on 70% reduction target
- Results: 98.5% reduction (200 ops → 3 ops) for 50 issues

Files created:
- tests/benchmarks/git_traffic.py (main benchmark script)
- tests/benchmarks/README.md (documentation)
- tests/benchmarks/git_traffic_50_issues.md (sample results)

The benchmark vastly exceeds the 70% target, showing 98.5% reduction.


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


bd doctor should check for multiple DBs, multiple JSONLs, daemon health


bd message: Refactor duplicated error messages


Mail Commands: bd mail send/inbox/read/ack

Implement core mail commands in cmd/bd/mail.go

Commands:
- bd mail send <recipient> -s 'Subject' -m 'Body' [--urgent]
  - Creates issue with type=message, sender=identity, assignee=recipient
  - --urgent sets priority=0

- bd mail inbox [--from <sender>] [--priority <n>]
  - Lists open messages where assignee=my identity
  - Sorted by priority, then date

- bd mail read <id>
  - Shows full message content (subject, body, sender, timestamp)
  - Does NOT close (separate from ack)

- bd mail ack <id>
  - Marks message as read by closing it
  - Can ack multiple: bd mail ack <id1> <id2> ...

Requires: Identity configuration (bd-kwro.7)


Issue with desc

This is a description


bd init should stop running daemon to avoid stale cache

When running bd init, any running daemon continues with stale cached data, causing bd stats and other commands to show old counts.

Repro:
1. Have daemon running with 788 issues cached
2. Clean JSONL to 128 issues, delete db, run bd init
3. bd stats still shows 788 (daemon cache)
4. Must manually run bd daemon --stop

Fix: bd init should automatically stop any running daemon before reinitializing.


Fix FindBeadsDir to prioritize main repo .beads for worktrees

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


Thread Test 2

Testing direct mode


Update documentation for beads-merge integration

Document the integrated merge functionality.

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

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


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.


Use stable repo identifiers instead of full paths in metadata keys

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

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

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

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

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


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.


Benchmark Helper Functions

Extend existing benchmark helpers in internal/storage/sqlite/bench_helpers_test.go (or create if organizing separately).

Existing helper (in compact_bench_test.go):
- setupBenchDB(tb) - Creates temp SQLite database with basic config
  * Used by compact and cycle benchmarks
  * Returns (*SQLiteStorage, cleanup func())

New helpers to add:
- setupLargeBenchDB(b *testing.B) storage.Storage
  * Creates 10K issue database using LargeSQLite fixture
  * Returns configured storage instance

- setupXLargeBenchDB(b *testing.B) storage.Storage
  * Creates 20K issue database using XLargeSQLite fixture
  * Returns configured storage instance

Implementation options:
1. Add to existing compact_bench_test.go (co-located with setupBenchDB)
2. Create new bench_helpers_test.go for organization

Both approaches:
- Build tag: //go:build bench
- Uses fixture generator from internal/testutil/fixtures
- Follows existing setupBenchDB() pattern
- Handles database cleanup

These helpers reduce duplication across new benchmark functions and provide consistent large-scale database setup.


bd-hv01: No snapshot versioning or timestamps causes stale data usage

Problem: If sync is interrupted (crash, kill -9, power loss), stale snapshots persist indefinitely. Next sync uses stale data leading to incorrect deletions.

Fix: Add metadata to snapshots with timestamp, version, and commit SHA. Validate snapshots are recent (< 1 hour old), from compatible version, and from expected git commit.

Files: cmd/bd/deletion_tracking.go (all snapshot functions)


Test issue 0


DetectCycles SQL query has bug preventing cycle detection

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

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

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

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

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

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


bd prime: Detect ephemeral branches and adjust workflow output

When 'bd prime' runs on a branch with no upstream (ephemeral branch), it should output a different SESSION CLOSE PROTOCOL.

**Current output (wrong for ephemeral branches):**
```
[ ] 1. git status
[ ] 2. git add <files>
[ ] 3. bd sync
[ ] 4. git commit -m "..."
[ ] 5. bd sync
[ ] 6. git push
```

**Needed output for ephemeral branches:**
```
[ ] 1. git status
[ ] 2. git add <files>
[ ] 3. bd sync --from-main  (pull updates from main)
[ ] 4. git commit -m "..."
[ ] 5. (no push - branch is ephemeral)
```

**Detection:** `git rev-parse --abbrev-ref --symbolic-full-name @{u}` returns error code 128 if no upstream.

Also update Sync & Collaboration section to mention `bd sync --from-main` for ephemeral branches.

**Use case:** Gastown polecats work on ephemeral local branches that are never pushed. Their code gets merged to main via local merge, and beads changes stay local (communicated via gm mail to Overseer).


bd message: Improve type safety with typed parameter structs


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 comments to 30+ silently ignored errors or fix them

Code health review found 30+ instances of error suppression using blank identifier without explanation:

Good examples (with comments):
- merge.go: _ = gitRmCmd.Run() // Ignore errors
- daemon_watcher.go: _ = watcher.Add(...) // Ignore error

Bad examples (no context):
- create.go:213: dbPrefix, _ = store.GetConfig(ctx, "issue_prefix")
- daemon_sync_branch.go: _ = daemonClient.Close()
- migrate_hash_ids.go, version_tracking.go: _ = store.Close()

Fix: Add comments explaining WHY errors are ignored, or handle them properly.


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.


Implement postinstall script for binary download

Create npm/scripts/postinstall.js that downloads platform-specific binaries:

## Platform detection
- Detect os.platform() and os.arch()
- Map to GitHub release asset names:
  - linux-amd64 → bd-linux-amd64
  - linux-arm64 → bd-linux-arm64
  - darwin-amd64 → bd-darwin-amd64
  - darwin-arm64 → bd-darwin-arm64
  - win32-x64 → bd-windows-amd64.exe

## Download logic
- Fetch from GitHub releases: https://github.com/steveyegge/beads/releases/latest/download/${asset}
- Save to npm/bin/bd (or bd.exe on Windows)
- Set executable permissions (chmod +x)
- Handle errors gracefully with helpful messages

## Error handling
- Check for unsupported platforms
- Retry on network failures
- Provide manual download instructions if automated fails
- Skip download if binary already exists (for local development)


Sync protection mechanism resurrects deleted issues

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

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

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

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

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

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

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


Update JSONL format to use hash IDs

Update JSONL import/export to use hash IDs, store aliases separately.

## Current JSONL Format
```jsonl
{"id":"bd-1c63eb84","title":"Fix bug","status":"open",...}
{"id":"bd-9063acda","title":"Add test","status":"open",...}
```

## New JSONL Format (Option A: Include Alias)
```jsonl
{"id":"bd-af78e9a2","alias":1,"title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","alias":2,"title":"Add test","status":"open",...}
```

## New JSONL Format (Option B: Hash ID Only)
```jsonl
{"id":"bd-af78e9a2","title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","title":"Add test","status":"open",...}
```

Store aliases in separate .beads/aliases.jsonl (local only, git-ignored):
```jsonl
{"hash":"bd-af78e9a2","alias":1}
{"hash":"bd-e5f6a7b8","alias":2}
```

**Recommendation**: Option B (hash only in main JSONL)
- Cleaner git diffs (no alias conflicts)
- Aliases are workspace-local preference
- Main JSONL is canonical, portable

## Export Changes
File: cmd/bd/export.go
```go
// Export issues with hash IDs
for _, issue := range issues {
    json := marshalIssue(issue)  // Uses issue.ID (hash)
    // Don't include alias in JSONL
}

// Separately export aliases to .beads/aliases.jsonl
exportAliases(issues)
```

## Import Changes
File: cmd/bd/import.go, internal/importer/importer.go
```go
// Import issues by hash ID
issue := unmarshalIssue(line)
// Assign new alias on import (don't use incoming alias)
issue.Alias = getNextAlias()

// No collision detection needed! Hash IDs are globally unique
```

## Dependency Reference Format
No change needed - already uses issue IDs:
```json
{"depends_on_id":"bd-af78e9a2","type":"blocks"}
```

## Files to Modify
- cmd/bd/export.go (use hash IDs)
- cmd/bd/import.go (import hash IDs, assign aliases)
- internal/importer/importer.go (remove collision detection!)
- .gitignore (add .beads/aliases.jsonl)

## Testing
- Test export produces hash IDs
- Test import assigns new aliases
- Test dependencies preserved with hash IDs
- Test no collision detection triggered


Add merge helper commands (bd sync --merge)

Add commands to merge beads branch back to main.

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

Estimated effort: 2-3 days


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

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

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

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

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

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

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

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

## Fixes Implemented

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

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

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

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

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

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

## Testing

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

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


Fix bd sync race condition with daemon causing dirty working directory

After bd sync completes with sync.branch mode, subsequent bd commands or daemon file watcher would see a hash mismatch and trigger auto-import, which then schedules re-export, dirtying the working directory.

**Root cause:**
1. bd sync exports JSONL with NEW content (hash H1)
2. bd sync updates jsonl_content_hash = H1 in DB
3. bd sync restores JSONL from HEAD (OLD content, hash H0)
4. Now: file hash = H0, DB hash = H1 (MISMATCH)
5. Daemon or next CLI command sees mismatch, imports from OLD JSONL
6. Import triggers re-export → file is dirty

**Fix:**
After restoreBeadsDirFromBranch(), update jsonl_content_hash to match the restored file's hash. This ensures daemon and CLI see file hash = DB hash → no spurious import/export cycle.

Related: bd-c83r (multiple daemon prevention)


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

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

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


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


Unit tests for Debouncer

Test debouncer batches multiple triggers into single action. Test timer reset on subsequent triggers. Test cancel during wait. Test thread safety.


Add unit tests for parent resurrection

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


Create npm package structure with package.json

Set up initial npm package structure for @beads/bd:

## Files to create
- npm/package.json - Package metadata, dependencies, scripts
- npm/bin/bd - CLI wrapper script that invokes native binary
- npm/.gitignore - Ignore downloaded binaries
- npm/README.md - Installation and usage instructions

## package.json structure
- Name: @beads/bd (scoped package)
- Main: index.js (exports binary path)
- Bin: bin/bd (CLI entry point)
- Scripts: postinstall (download binary)
- Keywords: issue-tracker, cli, beads, bd
- License: MIT

## Bin wrapper
Simple Node.js script that:
- Spawns native binary with child_process.spawn
- Passes through all arguments and stdio
- Exits with binary's exit code


Refactor: extract common helpers in sync-branch hook checks

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

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


Research Go WASM compilation and modernc.org/sqlite WASM support

Investigate technical requirements for compiling bd to WASM:
- Verify modernc.org/sqlite has working js/wasm support
- Identify Go stdlib limitations in WASM (syscalls, file I/O, etc.)
- Research wasm_exec.js runtime and Node.js integration
- Document any API differences between native and WASM builds


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 tests for 3-way merge scenarios

Comprehensive test coverage for merge logic.

**Test cases**:
- Simple field updates (left vs right)
- Dependency merging (union + dedup)
- Timestamp handling (max wins)
- Deletion detection (deleted in one, modified in other)
- Conflict generation (incompatible changes)
- Issue resurrection prevention (bd-hv01 regression test)

**Files**:
- `internal/merge/merge_test.go`
- `cmd/bd/merge_test.go`


Vendor and integrate beads-merge tool

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

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

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

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

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


Add bd daemon --stop-all command to kill all daemon processes

Currently there's no easy way to stop all running bd daemon processes. Users must resort to pkill -f 'bd daemon' or similar shell commands.

Add a --stop-all flag to bd daemon that:
1. Finds all running bd daemon processes (not just the current repo's daemon)
2. Gracefully stops them all
3. Reports how many were stopped

This is useful when:
- Multiple daemons are running and causing race conditions
- User wants a clean slate before running bd sync
- Debugging daemon-related issues


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

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

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

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

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

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

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


Fix CI failures before 0.21.3 release

CI is failing on multiple jobs:
1. Nix flake: Tests fail due to missing git in build environment
2. Windows tests: Need to check what's failing
3. Linux tests: Need to check what's failing
4. Linter errors: Many unchecked errors need fixing

Need to fix before tagging v0.21.3 release.

Fixed linter errors (errcheck, misspell), Nix flake git dependency, and import database discovery bug. Tests still failing - need to investigate further.


Investigate TestRoutingIntegration failure

TestRoutingIntegration/maintainer_with_SSH_remote failed during pre-commit check with "expected role maintainer, got contributor".
This occurred while running `go test -short ./...` on darwin/arm64.
The failure appears unrelated to storage/sqlite changes.
Need to investigate if this is a flaky test or environmental issue.


Respond to GitHub issue #479 with solution

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

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

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

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

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

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


Refactor check-health DB access to avoid repeated path resolution

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


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.


bd sync --squash: batch multiple syncs into single commit

For solo developers who don't need real-time multi-agent coordination, add a --squash option to bd sync that accumulates changes and commits them in a single commit rather than one commit per sync.

This addresses the git history pollution concern (many 'bd sync: timestamp' commits) while preserving the default behavior needed for orchestration.

**Proposed behavior:**
- `bd sync --squash` accumulates pending exports
- Only commits when explicitly requested or on session end
- Default behavior unchanged (immediate commits for orchestration)

**Use case:** Solo developers who want cleaner git history but don't need real-time coordination between agents.

**Related:** PR #411 (docs: reduce bd sync commit pollution)
**See also:** Multi-repo support as alternative solution (docs/MULTI_REPO_AGENTS.md)


Add multi-repo support to export metadata updates

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

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

But metadata updates are missing!

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

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

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

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

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


Delete collision resolution code

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

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

## Code to Remove from Existing Files

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

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

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

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

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

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

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

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

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

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


Improve sync error messages

When bd sync fails, immediately surface the actual root cause (prefix mismatch, orphaned children, forced push detected, etc.) instead of requiring multiple retries to discover it. Make errors actionable with specific recovery steps.


Add .beads/README.md during 'bd init' for project documentation and promotion

When 'bd init' is run, automatically generate a .beads/README.md file that:

1. Briefly explains what Beads is (AI-native issue tracking that lives in your repo)
2. Links to the main repository: https://github.com/steveyegge/beads
3. Provides a quick reference of essential commands:
   - bd create: Create new issues
   - bd list: View all issues
   - bd update: Modify issue status/details
   - bd show: View issue details
   - bd sync: Sync with git remote
4. Highlights key benefits for AI coding agents and developers
5. Encourages developers to try it out

The README should be enthusiastic and compelling to get open source contributors excited about using Beads for their AI-assisted development workflows.


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)


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)


Fix bd --version and bd completion to work without database


Remove dead test code in info_test.go

Code health review found cmd/bd/info_test.go has two tests permanently skipped:

- TestInfoCommand
- TestInfoCommandNoDaemon

Both skip with: 'Manual test - bd info command is working, see manual testing'

These are essentially dead code. Either automate them or remove them entirely.


Auto-compact deletions during bd sync

Parent: bd-imj

## Task
Optionally prune deletions manifest during sync when threshold exceeded.

**Note: Opt-in feature** - disabled by default to avoid sync latency.

## Implementation

In `bd sync`:
```go
func (s *Syncer) Sync() error {
    // ... existing sync logic ...

    // Auto-compact only if enabled
    if s.config.GetBool("deletions.auto_compact", false) {
        deletionCount := deletions.Count(".beads/deletions.jsonl")
        threshold := s.config.GetInt("deletions.auto_compact_threshold", 1000)

        if deletionCount > threshold {
            retentionDays := s.config.GetInt("deletions.retention_days", 7)
            if err := s.compactor.PruneDeletions(retentionDays); err != nil {
                log.Warnf("Failed to auto-compact deletions: %v", err)
                // Non-fatal, continue sync
            }
        }
    }

    // ... rest of sync ...
}
```

## Configuration
```yaml
deletions:
  retention_days: 7
  auto_compact: false              # Opt-in, disabled by default
  auto_compact_threshold: 1000     # Trigger when > N entries (if enabled)
```

## Acceptance Criteria
- [ ] Auto-compact disabled by default
- [ ] Enabled via config `deletions.auto_compact: true`
- [ ] Sync checks deletion count only when enabled
- [ ] Auto-prunes when threshold exceeded
- [ ] Failure is non-fatal (logged warning)
- [ ] Test: no compaction when disabled
- [ ] Test: compaction triggers when enabled and threshold exceeded


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


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

## Problem

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

## Root Cause

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

## Affected Tests (10+)

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

## Proof

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

## Solution

See MAIN_TEST_OPTIMIZATION_PLAN.md for complete fix plan.

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

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

## Expected Results

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

## Files

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

## Priority

P1 - Blocking development, tests unusable in current state


Batch test 4


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-47], 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 gosec lint warnings in doctor.go, main.go, and fix subdirectory

CI lint job failing with 4 gosec warnings:
- cmd/bd/doctor.go:664 (G304: file inclusion via variable)
- cmd/bd/doctor/fix/database_config.go:166 (G304: file inclusion via variable)
- cmd/bd/doctor/fix/untracked.go:61 (G204: subprocess launched with variable)
- cmd/bd/main.go:645 (G304: file inclusion via variable)

Either suppress with `// #nosec` if false positives, or refactor to validate paths properly.


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


Centralize error handling patterns in storage layer

80+ instances of inconsistent error handling across sqlite.go with mix of %w, %v, and no wrapping.

Location: internal/storage/sqlite/sqlite.go (throughout)

Problem:
- Some use fmt.Errorf("op failed: %w", err) - correct wrapping
- Some use fmt.Errorf("op failed: %v", err) - loses error chain
- Some return err directly - no context
- Hard to debug production issues
- Can't distinguish error types

Solution: Create internal/storage/sqlite/errors.go:
- Define sentinel errors (ErrNotFound, ErrInvalidID, etc.)
- Create wrapDBError(op string, err error) helper
- Convert sql.ErrNoRows to ErrNotFound
- Always wrap with operation context

Impact: Lost error context; inconsistent messages; hard to debug

Effort: 5-7 hours


Extract daemon config functions to daemon_config.go


Implement `bd setup cursor` for Cursor IDE integration

Create a `bd setup cursor` command that integrates Beads workflow into Cursor IDE via .cursorrules file. Unlike Claude Code (which has hooks), Cursor uses a static rules file to provide context to its AI.


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


Import reports misleading '0 created, 0 updated' when actually importing all issues

When running 'bd import' on a fresh database (no existing issues), the command reports 'Import complete: 0 created, 0 updated' even though it successfully imported all issues from the JSONL file.

**Steps to reproduce:**
1. Delete .beads/beads.db
2. Run: bd import .beads/issues.jsonl
3. Observe output: 'Import complete: 0 created, 0 updated'
4. Run: bd list
5. Confirm: All issues are actually present in the database

**Expected behavior:**
Report the actual number of issues imported, e.g., 'Import complete: 523 created, 0 updated'

**Actual behavior:**
'Import complete: 0 created, 0 updated' (misleading - makes user think import failed)

**Impact:**
- Users think import failed when it succeeded
- Confusing during database sync operations (e.g., after git pull)
- Makes debugging harder (can't tell if import actually worked)

**Context:**
Discovered during VC session when syncing database after git pull. The misleading message caused confusion about whether the database was properly synced with the canonical JSONL file.


Audit Current Cache Usage

Understand exactly what code depends on the storage cache


Add validation for duplicate external_ref in batch imports

Currently, if a batch import contains multiple issues with the same external_ref, the behavior is undefined. We should detect and handle this case.

Current behavior:
- No validation for duplicate external_ref within a batch
- Last-write-wins or non-deterministic behavior

Proposed solution:
- Detect duplicate external_ref values in incoming batch
- Fail with clear error message OR
- Merge duplicates intelligently (use newest timestamp)
- Add test case for this scenario

Related: bd-1022


SearchIssues N+1 query causes context timeout with GetLabels

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


GH#509: bd fails to find .beads from nested worktrees

findDatabaseInTree stops at worktree git root, missing .beads in parent repo. Should check git-common-dir and search past worktree root. See: https://github.com/steveyegge/beads/issues/509


Test Thread

Initial message for threading test


Command Set Standardization & Flag Consistency

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

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

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

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


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


Support --parent flag in daemon mode

Added support for hierarchical child issue creation using --parent flag in daemon mode. Previously only worked in direct mode.


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


Test Epic 2

## Overview

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

## Success Criteria

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

## Background

[Provide context and motivation]

## Scope

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

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


bd cleanup --hard should skip tombstone creation for true permanent deletion

## Problem

When using bd cleanup --hard --older-than N --force, the command:
1. Deletes closed issues older than N days (converting them to tombstones with NOW timestamp)
2. Then tries to prune tombstones older than N days (finds none because they were just created)

This leaves the database bloated with fresh tombstones that will not be pruned.

## Expected Behavior

In --hard mode, the deletion should be permanent without creating tombstones, since the user explicitly requested bypassing sync safety.

## Workaround

Manually delete from database: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE status=tombstone'

## Fix Options

1. In --hard mode, use a different delete path that does not create tombstones
2. After deleting, immediately prune the just-created tombstones regardless of age
3. Pass a skip_tombstone flag to the delete operation

Option 1 is cleanest - --hard should mean permanent delete without tombstone.


Improve test coverage for internal/lockfile (42.0% → 60%)

The lockfile package has only 42.0% test coverage. Lock file handling is critical for preventing data corruption in concurrent scenarios.

Current coverage: 42.0%
Target coverage: 60%


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


bd export should verify JSONL line count matches database count

After export completes, bd should verify that the JSONL file line count matches the number of issues exported. This would catch silent failures where the export appears to succeed but doesn't actually write all issues.

Real-world scenario from VC project:
- Ran direct SQL DELETE to remove 240 issues
- Ran 'bd export -o .beads/issues.jsonl'
- No error shown, appeared to succeed
- But JSONL file was not updated (still had old line count)
- Later session found all 240 issues still in JSONL
- Had to repeat the cleanup

If export had verified line count, it would have immediately shown:
  Error: Export verification failed
    Expected: 276 issues
    JSONL file: 516 lines
    Mismatch indicates export failed to write all issues

This is especially important because:
1. JSONL is source of truth in git
2. Silent export failures cause data inconsistency
3. Users assume export succeeded if no error shown
4. The verification is cheap (just count lines)

Implementation:
- After writing JSONL, count lines in file
- Compare to len(exportedIDs)
- If mismatch, remove temp file and return error
- Show clear error message with both counts


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.


Improve timestamp comparison readability in import

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

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

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

This is a minor refactor for code clarity.

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


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


Remove misleading placeholder 'bd merge' command from duplicates output

**Problem:**
The `bd duplicates` command suggests running a command that doesn't exist:
```
bd merge <source-ids> --into <target-id>
```

This is confusing because:
1. `bd merge` is actually a git 3-way JSONL merge driver (takes 4 file paths)
2. The suggested syntax for merging duplicate issues is not implemented
3. Line 75 in duplicates.go even has: `// TODO: performMerge implementation pending`

**Current behavior:**
- Users see suggested command that doesn't work
- No indication that feature is unimplemented
- Related to issue #349 item #2

**Proposed fix:**
Replace line 77 in cmd/bd/duplicates.go with either:

Option A (conservative):
```go
cmd := fmt.Sprintf("# TODO: Merge %s into %s (merge command not yet implemented)",
    strings.Join(sources, " "), target.ID)
```

Option B (actionable):
```go
cmd := fmt.Sprintf("# Duplicate found: %s\n# Manual merge: Close duplicates with 'bd close %s' and link to %s as 'related'",
    strings.Join(sources, " "), strings.Join(sources, " "), target.ID)
```

**Files to modify:**
- cmd/bd/duplicates.go (line ~77)


Extract database search logic into helper function

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

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

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

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

Related to [deleted:bd-e16b] implementation.


Duplicate DefaultRetentionDays constants

## Problem

There are now two constants for the same value:

1. `deletions.DefaultRetentionDays = 7` in `internal/deletions/deletions.go:184`
2. `configfile.DefaultDeletionsRetentionDays = 7` in `internal/configfile/configfile.go:102`

## Impact
- DRY violation
- Risk of values getting out of sync
- Confusing which one to use

## Fix
Remove the constant from `deletions` package and have it import from `configfile`, or create a shared constants package.


Re: Thread Test 2

Got your message. Testing reply feature.


Detect and warn about outdated git hooks

Users may have outdated git hooks installed that reference removed flags (e.g., --resolve-collisions). bd should detect this and warn users to reinstall.


Implement core reset package (internal/reset)

Create the core reset logic in internal/reset/ package.

## Responsibilities
- ResetOptions struct with all flag options
- CountImpact() - count issues/tombstones that will be deleted
- ValidateState() - check .beads/ exists, check git dirty state
- ExecuteReset() - main reset logic (without CLI concerns)
- Integrate with daemon killall

## Interface Design
```go
type ResetOptions struct {
    Hard      bool   // Include git operations (git rm, commit)
    Backup    bool   // Create backup before reset
    DryRun    bool   // Preview only, don't execute
    SkipInit  bool   // Don't re-initialize after reset
}

type ResetResult struct {
    IssuesDeleted     int
    TombstonesDeleted int
    BackupPath        string  // if backup was created
    DaemonsKilled     int
}

type ImpactSummary struct {
    IssueCount      int
    OpenCount       int
    ClosedCount     int
    TombstoneCount  int
    HasUncommitted  bool  // git dirty state
}

func Reset(opts ResetOptions) (*ResetResult, error)
func CountImpact() (*ImpactSummary, error)
func ValidateState() error
```

## IMPORTANT: CLI vs Core Separation
- `Force` (skip confirmation) is NOT in ResetOptions - that's a CLI concern
- Core always executes when called; CLI decides whether to prompt first
- Keep CLI-agnostic: no prompts, no colored output, no user interaction
- Return errors for CLI to handle with user-friendly messages
- Unit testable in isolation

## Dependencies
- Uses daemon.KillAllDaemons() from internal/daemon/
- Calls bd init logic after reset (unless SkipInit)


Add depth-based batch creation in upsertIssues

Replace single batch creation with depth-level batching (max depth 3). Create issues at depth 0, then 1, then 2, then 3. Prevents parent validation errors when importing hierarchical issues in same batch. File: internal/importer/importer.go:534-546


Test dependency child


Event-driven daemon architecture

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

## Implementation Progress

**Completed:**
1. ✅ Mutation events infrastructure (bd-143 equivalent)
   - MutationEvent channel in RPC server
   - Events emitted for all write operations: create, update, close, label add/remove, dep add/remove, comment add
   - Non-blocking emission with dropped event counter

2. ✅ FileWatcher with fsnotify (bd-b0c7f7ef related)
   - Watches .beads/issues.jsonl and .git/refs/heads
   - 500ms debounce
   - Polling fallback if fsnotify unavailable

3. ✅ Debouncer (bd-144 equivalent)
   - 500ms debounce for both export and import triggers
   - Thread-safe trigger/cancel

4. ✅ Separate export-only and import-only functions
   - createExportFunc(): exports + optional commit/push (no pull/import)
   - createAutoImportFunc(): pull + import (no export)
   - Target latency <500ms achieved by avoiding full sync

5. ✅ Dropped events safety net (bd-eef03e0a related)
   - Atomic counter tracks dropped mutation events
   - 60-second health check triggers export if events were dropped
   - Prevents silent data loss from event storms

**Still Needed:**
- Platform-specific tests (bd-69bce74a)
- Integration test for mutation→export latency (bd-140)
- Unit tests for FileWatcher (bd-b0c7f7ef)
- Unit tests for Debouncer (bd-144)
- Event storm stress test (bd-eef03e0a)
- Documentation update (bd-142)

**Next Steps:**
Add comprehensive test coverage before enabling events mode by default.


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


Update AGENTS.md with event-driven mode

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


Split large cmd/bd files: doctor.go (2948 lines), sync.go (2121 lines)

Code health review found several oversized files:

1. doctor.go - 2948 lines, 48 functions mixed together
   - Should split into doctor/checks/*.go for individual diagnostics
   - applyFixes() and previewFixes() are nearly identical

2. sync.go - 2121 lines
   - ZFC (Zero Flush Check) logic embedded inline (lines 213-247)
   - Multiple mode handlers should be extracted

3. init.go - 1732 lines
4. compact.go - 1097 lines
5. show.go - 1069 lines

Recommendation: Extract into focused sub-packages or split into logical files.


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

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

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

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

## Proposed Architecture

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

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

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

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

## Implementation Phases

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

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

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

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

## Risks & Mitigations

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

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

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

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

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

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

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


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

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


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


Thread Test

Testing the thread feature


Latency test 1

Resetting stale in_progress status from old executor run (yesterday)


Document deletions manifest in AGENTS.md and README

Parent: bd-imj

## Task
Add documentation about the deletions manifest feature.

## Locations to Update

### AGENTS.md
- Explain that deletions.jsonl is tracked in git
- Document that `bd delete` records to the manifest
- Explain cross-clone propagation mechanism

### README.md
- Brief mention in .beads directory structure section
- Link to detailed docs if needed

### docs/deletions.md (new file)
- Full technical documentation
- Format specification
- Pruning policy
- Git history fallback
- Troubleshooting

## Acceptance Criteria
- AGENTS.md updated with deletion workflow
- README.md mentions deletions.jsonl purpose
- New docs/deletions.md with complete reference


Add cancellation and timeout tests

Add comprehensive tests for context cancellation and timeout behavior.

## Context
Part of context propagation work. Validates that bd-rtp and bd-yb8 work correctly.

## Test Coverage Needed

### 1. Cancellation Tests
- [ ] Import operation cancelled mid-stream
- [ ] Export operation cancelled mid-stream
- [ ] Database query cancelled during long operation
- [ ] No corruption after cancellation
- [ ] Proper cleanup (defers execute, connections closed)

### 2. Timeout Tests
- [ ] Operations respect context deadlines
- [ ] Appropriate error messages on timeout
- [ ] State remains consistent after timeout

### 3. Signal Handling Tests
- [ ] SIGINT (Ctrl+C) triggers cancellation
- [ ] SIGTERM triggers graceful shutdown
- [ ] Multiple signals handled correctly

## Implementation Approach
```go
func TestImportCancellation(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())

    // Start import in goroutine
    go func() {
        err := runImport(ctx, largeFile)
        assert.Error(err, context.Canceled)
    }()

    // Cancel after short delay
    time.Sleep(100 * time.Millisecond)
    cancel()

    // Verify database integrity
    assertDatabaseConsistent(t, store)
}
```

## Files to Create/Update
- cmd/bd/import_test.go - cancellation tests
- cmd/bd/export_test.go - cancellation tests
- internal/storage/sqlite/*_test.go - context timeout tests

## Acceptance Criteria
- [ ] All critical operations have cancellation tests
- [ ] Tests verify database integrity after cancellation
- [ ] Signal handling tested (if feasible)
- [ ] Test coverage >80% for context paths


Use in-memory filesystem for test git operations

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

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

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


Add bd alias command for manual alias control

Add command for users to manually view and reassign aliases.

## Command: bd alias

### Subcommands

#### 1. bd alias list
Show all alias mappings:
```bash
$ bd alias list
#1    → bd-af78e9a2  Fix authentication bug
#2    → bd-e5f6a7b8  Add logging to daemon
#42   → bd-1a2b3c4d  Investigate jujutsu integration
#100  → bd-9a8b7c6d  (reassigned after conflict)
```

#### 2. bd alias set <alias> <hash-id>
Manually assign alias to specific issue:
```bash
$ bd alias set 42 bd-1a2b3c4d
✓ Assigned alias #42 to bd-1a2b3c4d

$ bd alias set 1 bd-af78e9a2
✗ Error: Alias #1 already assigned to bd-e5f6a7b8
Use --force to override
```

#### 3. bd alias compact
Renumber all aliases to fill gaps:
```bash
$ bd alias compact [--dry-run]

Current aliases: #1, #2, #5, #7, #100, #101
After compacting: #1, #2, #3, #4, #5, #6

Renumbering:
  #5   → #3
  #7   → #4
  #100 → #5
  #101 → #6

Apply changes? [y/N]
```

#### 4. bd alias reset
Regenerate all aliases (sequential from 1):
```bash
$ bd alias reset [--sort-by=priority|created|id]

WARNING: This will reassign ALL aliases. Continue? [y/N]

Reassigning 150 issues by priority:
  bd-a1b2c3d4 → #1   (P0: Critical security bug)
  bd-e5f6a7b8 → #2   (P0: Data loss fix)
  bd-1a2b3c4d → #3   (P1: Jujutsu integration)
  ...
```

#### 5. bd alias find <hash-id>
Look up alias for hash ID:
```bash
$ bd alias find bd-af78e9a2
#1

$ bd alias find bd-nonexistent
✗ Error: Issue not found
```

## Use Cases

### 1. Keep Important Issues Low-Numbered
```bash
# After closing many P0 issues, compact to free low numbers
bd alias compact

# Or manually set
bd alias set 1 bd-<critical-bug-hash>
```

### 2. Consistent Aliases Across Clones
```bash
# After migration, coordinator assigns canonical aliases
bd alias reset --sort-by=id
git add .beads/aliases.jsonl
git commit -m "Canonical alias assignments"
git push

# Other clones pull and adopt
git pull
bd import  # Alias conflicts resolved automatically
```

### 3. Debug Alias Conflicts
```bash
# See which aliases were reassigned
bd alias list | grep "#100"
```

## Flags

### Global
- `--dry-run`: Preview changes without applying
- `--force`: Override existing alias assignments

### bd alias reset
- `--sort-by=priority`: Assign by priority (P0 first)
- `--sort-by=created`: Assign by creation time (oldest first)
- `--sort-by=id`: Assign by hash ID (lexicographic)

## Implementation

File: cmd/bd/alias.go
```go
func aliasListCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "list",
        Short: "List all alias mappings",
        Run: func(cmd *cobra.Command, args []string) {
            aliases := storage.GetAllAliases()
            for _, a := range aliases {
                fmt.Printf("#%-4d → %s  %s\n",
                    a.Alias, a.IssueID, a.Title)
            }
        },
    }
}

func aliasSetCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "set <alias> <hash-id>",
        Short: "Manually assign alias to issue",
        Args:  cobra.ExactArgs(2),
        Run: func(cmd *cobra.Command, args []string) {
            alias, _ := strconv.Atoi(args[0])
            hashID := args[1]

            force, _ := cmd.Flags().GetBool("force")
            if err := storage.SetAlias(alias, hashID, force); err != nil {
                fmt.Fprintf(os.Stderr, "Error: %v\n", err)
                os.Exit(1)
            }
            fmt.Printf("✓ Assigned alias #%d to %s\n", alias, hashID)
        },
    }
}
```

## Files to Create
- cmd/bd/alias.go

## Testing
- Test alias list output
- Test alias set with/without force
- Test alias compact removes gaps
- Test alias reset with different sort orders
- Test alias find lookup


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


Auth tokens expire too quickly

## Summary

[Brief description of the bug]

## Steps to Reproduce

1. Step 1
2. Step 2
3. Step 3

## Expected Behavior

[What should happen]

## Actual Behavior

[What actually happens]

## Environment

- OS: [e.g., macOS 15.7.1]
- Version: [e.g., bd 0.20.1]
- Additional context: [any relevant details]

## Additional Context

[Screenshots, logs, or other relevant information]


Improve integration test coverage for stateful features


Platform tests: Linux, macOS, Windows

Test event-driven mode on all platforms. Verify inotify (Linux), FSEvents (macOS), ReadDirectoryChangesW (Windows). Test fallback behavior on each.


Proposal workflow (propose/withdraw/accept)

Implement commands and state machine for moving issues between personal planning repos and canonical upstream repos, enabling contributors to propose work without polluting PRs.


Switch from modernc.org/sqlite to ncruces/go-sqlite3 for WASM support

modernc.org/sqlite depends on modernc.org/libc which has no js/wasm support (platform-specific syscalls). Need to switch to ncruces/go-sqlite3 which wraps a WASM build of SQLite using wazero runtime.

Key differences:
- ncruces/go-sqlite3: Uses WASM build of SQLite + wazero runtime
- modernc.org/sqlite: Pure Go translation, requires libc for syscalls

This is a prerequisite for bd-62a0 (WASM build infrastructure).


Fix TestListCommand duplicate dependency constraint violation


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)


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.


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


Add tests for merge driver auto-config in bd init

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

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

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


Testing event-driven mode!


Add integration test for cross-clone deletion propagation


Add index usage verification test for external_ref lookups

Currently we test that idx_issues_external_ref index exists, but we don't verify that it's actually being used by the query planner.

Proposed solution:
- Add test using EXPLAIN QUERY PLAN
- Verify that 'SEARCH TABLE issues USING INDEX idx_issues_external_ref' appears in plan
- Ensures O(1) lookup performance is maintained

Related: bd-1022
Files: internal/storage/sqlite/external_ref_test.go:260


Debug test


> bd sync --no-daemon
→ Exporting pending changes to JSONL...
→ Committing changes to sync branch 'beads-sync'...
Error committing to sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory

**Problem Diagnosed**: The `bd sync` command was failing with "mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory" because it was being executed from the wrong directory.

**Root Cause**: The command was run from `/var/home/matt/dev/beads` (where the `fix-ci` worktree exists) instead of the main repository directory `/var/home/matt/dev/beads/main`. Since `fix-ci` is a git worktree with a `.git` file (not directory), the worktree creation logic failed when trying to create `<current_dir>/.git/beads-worktrees/<branch>`.

**Solution Verified**: Execute `bd sync` from the main repository directory:
```bash
cd main && bd sync --dry-run
```


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


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.


Extract migration functions to migrations.go

Move migrateDirtyIssuesTable, migrateExternalRefColumn, migrateCompositeIndexes, migrateClosedAtConstraint, migrateCompactionColumns, migrateSnapshotsTable, migrateCompactionConfig, migrateCompactedAtCommitColumn, migrateExportHashesTable, migrateContentHashColumn to a separate migrations.go file


Test parent issue


Test issue 2


Update AGENTS.md with event-driven mode

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


Audit and fix JSONL filename mismatches across all repo clones

## Problem

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

## Root Cause

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

## Task

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

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

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

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

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

### Step 3: Create audit report

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

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

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

### Step 5: Generate fix script

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

### Expected Output

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

### Edge Cases

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

### Success Criteria

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


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.


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.


Enhance bd doctor to suggest reset for broken states

Update bd doctor to detect severely broken states and suggest reset.

## Detection Criteria
Suggest reset when:
- Multiple unfixable errors detected
- Corrupted JSONL that can't be repaired
- Schema version mismatch that can't be migrated
- Daemon state inconsistent and unkillable

## Implementation
Add to doctor's check/fix flow:
```go
if unfixableErrors > threshold {
    suggest('State may be too broken to fix. Consider: bd reset')
}
```

## Output Example
```
✗ Found 5 unfixable errors

  Your beads state may be too corrupted to repair.
  Consider running 'bd reset' to start fresh.
  (Use 'bd reset --backup' to save current state first)
```

## Notes
- Don't auto-run reset, just suggest
- This is lower priority, can be done in parallel with main work


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


Add warning when staleness check errors

## Problem

When ensureDatabaseFresh() calls CheckStaleness() and it errors (corrupted metadata, permission issues, etc.), we silently proceed with potentially stale data.

**Location:** cmd/bd/staleness.go:27-32

**Scenarios:**
- Corrupted metadata table
- Database locked by another process
- Permission issues reading JSONL file
- Invalid last_import_time format in DB

## Current Code

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    // If we can't determine staleness, allow operation to proceed
    // (better to show potentially stale data than block user)
    return nil
}
```

## Fix

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    fmt.Fprintf(os.Stderr, "Warning: Could not verify database freshness: %v\n", err)
    fmt.Fprintf(os.Stderr, "Proceeding anyway. Data may be stale.\n\n")
    return nil
}
```

## Impact
Medium - users should know when staleness check fails

## Effort
Easy - 5 minutes


No logging/debugging for tombstone resurrection events

Per the design document bd-zvg Open Question 1: Should resurrection log a warning? Recommendation was Yes. Currently, when an expired tombstone loses to a live issue (resurrection), there is no logging or debugging output. This makes it hard to understand why an issue reappeared. Recommendation: Add optional debug logging when resurrection occurs, e.g., Issue bd-abc resurrected (tombstone expired). Files: internal/merge/merge.go:359-366, 371-378, 400-405, 410-415


Issue 1 to reopen


Document CLI + hooks as recommended approach over MCP

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


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)


GH#519: bd sync fails when sync.branch is currently checked-out branch


WASM integration testing

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

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

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


bd ready includes blocked issues (GH#544)

Issues with 'blocks' dependencies still appear in bd ready. The ready query should exclude issues that have unresolved blockers.

Fix in: cmd/bd/ready.go or internal query logic.


Documentation

Complete documentation for Agent Mail integration to enable adoption.