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

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

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

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

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


Tombstone export includes tombstones even with --status filter

In export.go, we set IncludeTombstones: true unconditionally. However, if a user runs:

  bd export --status=open

They might not expect tombstones in the output. The IncludeTombstones flag should probably only be set when no status filter is specified, or when --status=tombstone is explicitly requested.

Current behavior: exports tombstones regardless of status filter
Expected behavior: respect status filter, only include tombstones when explicitly requested or no filter


bd doctor: add configuration value validation

Currently bd doctor checks for missing config files but doesn't validate config values. Add validation for: invalid priority values, malformed sync intervals, invalid branch names, unsupported storage backends, etc. This would catch misconfigurations before they cause runtime errors.


Daemons don't exit when parent process dies, causing accumulation and race conditions

Multiple daemon processes accumulate over time because daemons don't automatically stop when their parent process (e.g., coding agent) is killed. This causes:

1. Race conditions: 8+ daemons watching same .beads/beads.db, each with own 30s debounce timer
2. Git conflicts: Multiple daemons racing to commit/push .beads/issues.jsonl
3. Resource waste: Orphaned daemons from sessions days/hours old still running

Example: User had 8 daemons from multiple sessions (12:37AM, 7:20PM, 7:22PM, 7:47PM, 9:19PM yesterday + 9:54AM, 10:55AM today).

Solutions to consider:
1. Track parent PID and exit when parent dies
2. Use single global daemon instead of per-session
3. Document manual cleanup: pkill -f "bd daemon"
4. Add daemon lifecycle management (auto-cleanup of stale daemons)

Implementation complete:

1. Added ParentPID field to DaemonLockInfo struct (stored in daemon.lock JSON)
2. Daemon now tracks parent PID via os.Getppid() at startup
3. Both event loops (polling and event-driven) check parent process every 10 seconds
4. Daemon gracefully exits if parent process dies (detected via isProcessRunning check)
5. Handles edge cases:
   - ParentPID=0: Older daemons without tracking (ignored)
   - ParentPID=1: Adopted by init means parent died (exits)
   - Otherwise checks if parent process is still running

The fix prevents daemon accumulation by ensuring orphaned daemons automatically exit within 10 seconds of parent death.


Enhance `bd doctor` to verify Claude Code integration

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


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


Test simple issue


Add unit tests for hasJSONLChanged() function

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

Also add performance benchmarks for large JSONL files.


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.


Safety check tests use string(rune()) which only works for single digits


TestImportIssues_LegacyDeletionsConvertedToTombstones is failing

The test TestImportIssues_LegacyDeletionsConvertedToTombstones in internal/importer/importer_test.go is failing:

Expected 3 created (1 regular + 2 tombstones from deletions.jsonl), got 2
Expected tombstone for test-deleted2 not found

The test expects legacy deletions.jsonl entries to be converted to tombstones during import, but test-deleted2 is not being converted.

Location: internal/importer/importer_test.go:1344


Extract computeJSONLHash helper to eliminate code duplication

SHA256 hash computation is duplicated in 3 places:
- cmd/bd/integrity.go:50-52
- cmd/bd/sync.go:611-613
- cmd/bd/import.go:318-319

Extract to shared helper function computeJSONLHash(jsonlPath string) (string, error) that includes proper #nosec comment and error handling.


Issue with labels


Invert control for compact: provide tools for agent-driven compaction

Currently compact requires Anthropic API key because bd calls the AI directly. This is backwards - we should provide tools (like all other bd commands) that let an AI agent perform the compaction. The agent decides what to keep/merge, not bd. Related to GH #243 complaint about API key requirement.


RPC fast-fail: stat socket before dial, cap timeouts to 200ms

Eliminate 5s delay when daemon socket is missing by:
1. Add os.Stat(socketPath) check before dialing in TryConnect
2. Return (nil, nil) immediately if socket doesn't exist
3. Set default dial timeout to 200ms in TryConnect
4. Keep TryConnectWithTimeout for explicit health/status checks (1-2s)

This prevents clients from waiting through full timeout when no daemon is running.


Implement alias conflict resolution

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

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

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

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

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

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

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

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

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

## Alternative Strategies (For Future Consideration)

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

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

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

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

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

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


bd import fails on duplicate external_ref with no resolution options

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

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

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

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


Fix bd doctor --fix recursive message for deletions manifest

When running bd doctor --fix, if the deletions manifest check fails but there are no deleted issues in git history, the fix succeeds but doesn't create the file. The check then runs again and tells user to run bd doctor --fix - the same command they just ran.

Fix: Create empty deletions.jsonl when hydration finds no deletions, and recognize empty file as valid in the check.

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


Assigned issue


Fix dependency naming inconsistencies (GH #440)

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


CONFIG.md docs for sync.require_confirmation_on_mass_delete missing threshold detail

The docs say 'if >50% of issues vanish' but the actual code (worktree.go:380) also requires '>5 issues existed before'. The docs should mention both conditions for completeness.


Upgrade notification persists after running suggested commands

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

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

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

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

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


Test FK constraint via close


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

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

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

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

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

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

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

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

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


Add bd reset command for clean slate restart

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

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

## Design

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

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

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

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

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

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


Log errors from timer-triggered flushes instead of discarding


Remove unreachable utility functions

Several small utility functions are unreachable:

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

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

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

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

Impact: Removes 50-100 LOC depending on decisions


Test sync branch setup


Add test coverage for internal/autoimport package


bd info whats-new missing releases

Current release is v0.25.1 but `bd info --whats-new` stops at v0.23.0, indicating something is missing in the create new release workflow.


Deletions pruning doesn't include results in JSON output

## Problem

When `bd compact --json` runs with deletions pruning, the prune results are silently discarded:

```go
// Only report if there were deletions to prune
if result.PrunedCount > 0 {
    if jsonOutput {
        // JSON output will be included in the main response
        return  // <-- BUG: results are NOT included anywhere
    }
    ...
}
```

## Location
`cmd/bd/compact.go:925-929`

## Impact
- JSON consumers don't know deletions were pruned
- No way to audit pruning via automation

## Fix
Return prune results and include in JSON output structure:

```json
{
  "success": true,
  "compacted": {...},
  "deletions_pruned": {
    "count": 5,
    "retention_days": 7
  }
}
```


Integrate beads-merge tool by @neongreen

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

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

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

**Integration options**:

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

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

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

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

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

Next: Wait for response before proceeding with integration.

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

Next: Vendor beads-merge with full attribution


Add mutation channel to internal/rpc/server.go

Add mutationChan chan MutationEvent to Server struct. Emit events on CreateIssue, UpdateIssue, DeleteIssue, AddComment. Non-blocking send with default case for full channel.


Unit tests for FileWatcher

Test watcher detects JSONL changes. Test git ref changes trigger import. Test debounce integration. Test watcher recovery from file removal/rename.


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


Add database maintenance commands section to QUICKSTART.md

**Problem:**
Users don't discover `bd compact` or `bd cleanup` commands until their database grows large. These maintenance commands aren't mentioned in quickstart documentation.

Related to issue #349 item #4.

**Current state:**
docs/QUICKSTART.md ends at line 217 with "See README.md for full documentation" but has no mention of maintenance operations.

**Proposed addition:**
Add a "Database Maintenance" section after line 140 (before "Advanced: Agent Mail" section) covering:
- When database grows (many closed issues)
- How to view compaction statistics
- How to compact old issues
- How to delete closed issues
- Warning about permanence

**Example content:**
```markdown
## Database Maintenance

As your project accumulates closed issues, the database grows. Use these commands to manage size:

```bash
# View compaction statistics
bd compact --stats

# Preview compaction candidates (30+ days closed)
bd compact --analyze --json

# Apply agent-generated summary
bd compact --apply --id bd-42 --summary summary.txt

# Immediately delete closed issues (use with caution!)
bd cleanup --force
```

**When to compact:**
- Database file > 10MB with many old closed issues
- After major project milestones
- Before archiving a project phase
```

**Files to modify:**
- docs/QUICKSTART.md (add section after line 140)


Update flake.nix vendorHash after dependency bumps

The Nix flake CI is failing due to stale vendorHash.

Error from CI:
```
error: hash mismatch in fixed-output derivation beads-0.24.4-go-modules.drv
```

Root cause: go.mod/go.sum was updated by dependabot (wazero, anthropic-sdk-go, etc.) but flake.nix vendorHash wasn't updated.

## Immediate Fix
Run `nix build` locally, get the correct hash from the error message, update flake.nix.

## Long-term Fix
Add a GitHub Action that auto-updates vendorHash when go.mod/go.sum changes:
1. Trigger on push to main when go.mod/go.sum changes
2. Run `nix build` to get the new hash from the error
3. Update flake.nix with the correct hash
4. Commit and push


Fix validatePreExport to use content hash instead of mtime

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

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

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


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 MCP functions for repair commands

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

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


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


bd-hv01: Storage backend extensibility broken by type assertion

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

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

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


Rapid 2


Add optional post-merge git hook example for bd sync

Create example git hook that auto-runs bd sync after git pull/merge.

Add to examples/git-hooks/:
- post-merge hook that checks if .beads/issues.jsonl changed
- If changed: run `bd sync` automatically
- Make it optional/documented (not auto-installed)

Benefits:
- Zero-friction sync after git pull
- Complements auto-detection as belt-and-suspenders

Note: post-merge hook already exists for pre-commit/post-merge. Extend it to support sync.


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


Add bd init --team wizard

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


Optimize cmd/bd test suite performance (currently 30+ minutes)

CLI test suite is extremely slow (~30+ minutes for full run). Tests are poorly designed and need performance optimization before expanding coverage.

Current coverage: 24.8% (improved from 20.2%)

**Problem**: Tests take far too long to run, making development iteration painful.

**Priority**: Fix test performance FIRST, then consider increasing coverage.

**Investigation needed**:
- Profile test execution to identify bottlenecks
- Look for redundant git operations, database initialization, or daemon operations
- Identify opportunities for test parallelization
- Consider mocking or using in-memory databases where appropriate
- Review test design patterns

**Related**: bd-ktng mentions 13 CLI tests with redundant git init calls (31s total)

**Goal**: Get full test suite under 1-2 minutes before adding more tests.


Isolate RPC server startup into rpc_server.go

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


Update 3-way merge to handle tombstones

Modify internal/merge/merge.go to detect tombstones, implement tombstone-wins-unless-expired logic, add TTL parameter to merge functions. Per design bd-zvg.


Add tests for Comments API (AddIssueComment, GetIssueComments)

Comments API currently has 0% coverage. Need tests for:
- AddIssueComment - adding comments to issues
- GetIssueComments - retrieving comments
- Comment ordering and pagination
- Edge cases (non-existent issues, empty comments)


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

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

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

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

## Proposed Architecture

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

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

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

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

## Implementation Phases

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

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

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

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

## Risks & Mitigations

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

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

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

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

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

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

## References
- Automerge: https://automerge.org
- Yjs: https://docs.yjs.dev
- Loro: https://loro.dev
- CRDT theory: Shapiro et al, A comprehensive study of CRDTs
- Related issues: bd-e6d71828, bd-7a2b58fc, bd-81abb639


Fix auto-import creating duplicates instead of updating issues

ROOT CAUSE: server_export_import_auto.go line 221 uses ResolveCollisions: true for ALL auto-imports. This is wrong.

Problem:
- ResolveCollisions is for branch merges (different issues with same ID)
- Auto-import should UPDATE existing issues, not create duplicates
- Every git pull creates NEW duplicate issues with different IDs
- Two agents ping-pong creating endless duplicates

Evidence:
- 31 duplicate groups found (bd duplicates)
- bd-236-246 are duplicates of bd-224-235
- Both agents keep pulling and creating more duplicates
- JSONL file grows endlessly with duplicates

The Fix:
Change checkAndAutoImportIfStale in server_export_import_auto.go:
- Remove ResolveCollisions: true (line 221)
- Use normal import logic that updates existing issues by ID
- Only use ResolveCollisions for explicit bd import --resolve-collisions

Impact: Critical - makes beads unusable for multi-agent workflows


Create Go adapter library (optional)

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

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

File: pkg/agentmail/client.go

Note: Lower priority - can shell out to curl initially


Migration tool: sequential → hash IDs

Create migration tool to convert existing sequential-ID databases to hash-ID format.

## Command: bd migrate --hash-ids

```bash
bd migrate --hash-ids [--dry-run]
```

## Migration Process

### 1. Backup Database
```bash
cp .beads/beads.db .beads/beads.db.backup-1761798384
echo "✓ Backup created: .beads/beads.db.backup-1234567890"
```

### 2. Generate Hash IDs for Existing Issues
```go
func migrateToHashIDs(db *SQLiteStorage) error {
    // Read all issues
    issues, err := db.ListIssues(ctx, ListOptions{Status: "all"})

    // Generate mapping: old ID → new hash ID
    mapping := make(map[string]string)
    for _, issue := range issues {
        hashID := GenerateHashID(
            issue.Title,
            issue.Description,
            issue.CreatedAt,
            db.workspaceID,
        )
        mapping[issue.ID] = hashID
    }

    // Detect hash collisions (extremely rare)
    if hasCollisions(mapping) {
        return fmt.Errorf("hash collision detected, aborting")
    }

    return nil
}
```

### 3. Update Database Schema
```sql
-- Add alias column
ALTER TABLE issues ADD COLUMN alias INTEGER UNIQUE;

-- Populate aliases from old IDs
UPDATE issues SET alias = CAST(SUBSTR(id, 4) AS INTEGER)
  WHERE id LIKE 'bd-%' AND SUBSTR(id, 4) GLOB '[0-9]*';

-- Create new issues_new table with hash IDs
CREATE TABLE issues_new (
    id TEXT PRIMARY KEY,  -- Hash IDs now
    alias INTEGER UNIQUE,
    title TEXT NOT NULL,
    -- ... rest of schema
);

-- Copy data with ID mapping
INSERT INTO issues_new SELECT
    <new_hash_id>,  -- From mapping
    alias,
    title,
    -- ...
FROM issues;

-- Drop old table, rename new
DROP TABLE issues;
ALTER TABLE issues_new RENAME TO issues;
```

### 4. Update Dependencies
```sql
-- Update depends_on_id using mapping
UPDATE dependencies
SET issue_id = <new_hash_id>,
    depends_on_id = <new_depends_on_hash_id>
FROM mapping;
```

### 5. Update Text References
```go
// Update all text fields that mention old IDs
func updateTextReferences(db *SQLiteStorage, mapping map[string]string) error {
    for oldID, newID := range mapping {
        // Update description, notes, design, acceptance_criteria
        db.Exec(`UPDATE issues SET
            description = REPLACE(description, ?, ?),
            notes = REPLACE(notes, ?, ?),
            design = REPLACE(design, ?, ?),
            acceptance_criteria = REPLACE(acceptance_criteria, ?, ?)
        `, oldID, newID, oldID, newID, oldID, newID, oldID, newID)
    }
}
```

### 6. Export to JSONL
```bash
bd export  # Writes hash IDs to .beads/issues.jsonl
git add .beads/issues.jsonl
git commit -m "Migrate to hash-based IDs"
```

## Output
```bash
$ bd migrate --hash-ids
Migrating to hash-based IDs...
✓ Backup created: .beads/beads.db.backup-1730246400
✓ Generated 150 hash IDs
✓ No hash collisions detected
✓ Updated issues table schema
✓ Updated 150 issue IDs
✓ Updated 87 dependencies
✓ Updated 234 text references
✓ Exported to .beads/issues.jsonl
✓ Migration complete!

Next steps:
  1. Test: bd list, bd show #1, etc.
  2. Commit: git commit -m "Migrate to hash-based IDs"
  3. Push: git push origin main
  4. Notify collaborators to pull and re-init
```

## Dry Run Mode
```bash
$ bd migrate --hash-ids --dry-run
[DRY RUN] Would migrate 150 issues:
  bd-1c63eb84   → bd-af78e9a2  (alias: #1)
  bd-9063acda   → bd-e5f6a7b8  (alias: #2)
  ...
  bd-150 → bd-9a8b7c6d  (alias: #150)

[DRY RUN] Would update:
  - 150 issue IDs
  - 87 dependencies
  - 234 text references in descriptions/notes

No changes made. Run without --dry-run to apply.
```

## Files to Create
- cmd/bd/migrate.go (new command)
- internal/storage/sqlite/migrations/hash_ids.go

## Testing
- Test migration on small database (10 issues)
- Test migration on large database (1000 issues)
- Test hash collision detection (inject collision artificially)
- Test text reference updates
- Test rollback (restore from backup)
- Test migrated database works correctly

## Rollback Procedure
```bash
# If migration fails or has issues
mv .beads/beads.db.backup-1234567890 .beads/beads.db
bd export  # Restore JSONL from backup DB
```

## Multi-Clone Coordination
**Important**: All clones must migrate before syncing:

1. Coordinator sends message: "Migrating to hash IDs on 2025-10-30 at 10:00 UTC"
2. All collaborators pull latest changes
3. All run: `bd migrate --hash-ids`
4. All push changes
5. New work can continue with hash IDs

**Do NOT**:
- Mix sequential and hash IDs in same database
- Sync before all clones migrate


Split test suites: fast vs. integration

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

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

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


Update bd compact to prune expired tombstones

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


Add bd init --contributor wizard

Interactive wizard for OSS contributor setup. Guides user through: fork workflow setup, separate planning repo configuration, auto-detection of fork relationships, examples of common OSS workflows.


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


Implement hash ID generation in CreateIssue

Replace sequential ID generation with hash-based IDs in CreateIssue function.

## Current Behavior (internal/storage/sqlite/sqlite.go)
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // ID comes from auto-increment counter
    // Collisions possible across clones
}
```

## New Behavior
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // Generate hash ID if not provided
    if issue.ID == "" {
        issue.ID = idgen.GenerateHashID(
            issue.Title,
            issue.Description,
            time.Now(),
            s.workspaceID,
        )
    }

    // Assign next alias
    issue.Alias = s.getNextAlias()

    // Insert with hash ID + alias
    // ...
}
```

## Workspace ID Generation
Add to database initialization:
```go
// Generate stable workspace ID (persisted in .beads/workspace_id)
workspaceID := getOrCreateWorkspaceID()
```

Options for workspace ID:
1. Hostname + random suffix
2. UUID (random)
3. Git remote URL hash (deterministic per repo)

Recommended: Option 3 (git remote hash) for reproducibility

## Hash Collision Detection
```go
// On insert, check for collision (unlikely but possible)
existing, err := s.GetIssue(ctx, issue.ID)
if err == nil {
    // Hash collision! Add random suffix and retry
    issue.ID = issue.ID + "-" + randomSuffix(4)
}
```

## Files to Create/Modify
- internal/types/id_generator.go (new)
- internal/storage/sqlite/sqlite.go (CreateIssue)
- internal/storage/sqlite/workspace.go (new - workspace ID management)
- .beads/workspace_id (new file, git-ignored)

## Testing
- Test hash ID generation is deterministic
- Test collision detection and retry
- Test workspace ID persistence
- Benchmark: hash generation performance (<1μs)


Run final validation and cleanup checks

Final validation pass to ensure all cleanup objectives met and no regressions introduced.

Validation checklist:
1. Dead code verification: `go run golang.org/x/tools/cmd/deadcode@latest -test ./...`
2. Test coverage: `go test -cover ./...`
3. Build verification: `go build ./cmd/bd/`
4. Linting: `golangci-lint run`
5. Integration tests
6. Metrics verification
7. Git clean check

Final metrics to report:
- LOC removed: ~____
- Files deleted: ____
- Files created: ____
- Test coverage: ____%
- Build time: ____ (before/after)
- Test run time: ____ (before/after)

Impact: Confirms all cleanup objectives achieved successfully

## Validation Results (Oct 31, 2025)

**Dead Code:** ✅ Removed 5 unreachable functions (~200 LOC)
- computeIssueContentHash, shouldSkipExport (autoflush.go)
- addDependencyUnchecked, removeDependencyIfExists (dependencies.go)
- isUniqueConstraintError (util.go)

**Tests:** ✅ All pass
**Coverage:**
- Main package: 39.6%
- cmd/bd: 19.5%
- internal/daemon: 37.8%
- internal/storage/sqlite: 58.1%
- internal/rpc: 58.6%

**Build:** ✅ Clean (24.5 MB binary)
**Linting:** 247 issues (mostly errcheck on defer/Close statements)
**Integration Tests:** ✅ All pass
**Metrics:** 55,622 LOC across 200 Go files
**Git:** 3 files modified (dead code removal)


Add test coverage for internal/config package


Replace getStorageForRequest with Direct Access

Replace all getStorageForRequest(req) calls with s.storage


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


Add tests for DetectCycles to improve coverage from 29.6%

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


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.


Auto-resolve all field conflicts in merge.go

Eliminate manual conflict resolution by adding deterministic auto-resolve rules for all fields:

- Priority: higher priority wins (lower number = more urgent)
- IssueType: local wins
- Notes: concatenate both sides with separator
- Title: side with latest updated_at on the issue wins
- Description: side with latest updated_at on the issue wins

Currently true conflicts (both sides changed same field to different values) fail sync. With this change, NO conflicts ever require manual resolution.

Location: internal/merge/merge.go
Parent issue: bd-3s8


Add tests for extracted modules

Create tests for migrations.go, hash_ids.go, batch_ops.go, and validators.go


Create examples for common workflows

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


bd-hv01: Missing cleanup of .merged temp file on failure

Problem: deletion_tracking.go:49 creates tmpMerged file but does not clean up on failure, causing disk space leak and potential interference with subsequent syncs.

Fix: Add defer os.Remove(tmpMerged) after creating temp file path.

Files: cmd/bd/deletion_tracking.go:38-89


Fix TestExportUpdatesMetadata to test actual daemon functions

## Problem
TestExportUpdatesMetadata (daemon_sync_test.go:296) manually replicates the metadata update logic rather than calling the fixed functions (createExportFunc/createSyncFunc).

This means:
- Test verifies the CONCEPT works
- But doesn't verify the IMPLEMENTATION in the actual daemon functions
- If daemon code is wrong, test still passes

## Current Test Flow
```go
// Test does:
exportToJSONLWithStore()  // ← Direct call
// ... manual metadata update ...
exportToJSONLWithStore()  // ← Direct call again
```

## Should Be
```go
// Test should:
createExportFunc(...)()  // ← Call actual daemon function
// ... verify metadata was updated by the function ...
createExportFunc(...)()  // ← Call again, should not fail
```

## Challenge
createExportFunc returns a closure that's run by the daemon. Testing this requires:
- Mock logger
- Proper context setup
- Git operations might need mocking

## Files
- cmd/bd/daemon_sync_test.go

## Acceptance Criteria
- Test calls actual createExportFunc and createSyncFunc
- Test verifies metadata updated by daemon code, not test code
- Both functions tested (export and sync)


bd hooks install fails in git worktrees

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

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

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


Jira import/export integration

Add ability to import issues from Jira and export beads issues to Jira. See GitHub discussion #430 for user request.

**Background**: User @kaihendry configured jira.* settings but found bd sync doesn't sync with Jira - only with git. The config namespace exists but the integration doesn't.

**Existing infrastructure**:
- external_ref field for linking to external systems
- Import logic matches by external_ref first (priority over ID matching)
- Config namespace jira.* documented in CONFIG.md
- GitHub import example (examples/github-import/gh2jsonl.py) provides pattern

**Schema mapping challenges**:
- Jira workflows are highly customizable (status mapping)
- Jira has more field types (components, versions, custom fields)
- Two-way sync needs conflict resolution
- Jira Cloud vs Server/Data Center have different APIs

**Approach**: Phased implementation starting with import, then export, then bidirectional sync.


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


Fix PR #319: Performance Improvements - CI failures and lint errors

PR #319 (Performance Improvements) has excellent performance optimizations but is blocked by CI failures.

## The PR
- URL: https://github.com/steveyegge/beads/pull/319
- Author: @rsnodgrass (Ryan)
- Claimed improvements: bd ready 20.5x faster (752ms → 36.6ms), startup 10.5x faster

## CI Failures

### Lint Errors (8 total)
1. cmd/bd/deletion_tracking.go:57 - unchecked os.Remove
2. cmd/bd/import.go:548 - unchecked os.RemoveAll
3. cmd/bd/message.go:205 - unchecked resp.Body.Close
4. cmd/bd/migrate_issues.go:633 - unchecked fmt.Scanln
5. cmd/bd/migrate_issues.go:701 - unchecked MarkFlagRequired
6. cmd/bd/migrate_issues.go:702 - unchecked MarkFlagRequired
7. cmd/bd/show.go:610 - gosec G104 unhandled error
8. cmd/bd/show.go:614 - gosec G104 unhandled error

### Test Failures
All syncbranch_test.go tests failing with:
"migration external_ref_column failed: failed to create index on external_ref: sqlite3: SQL logic error: no such table: main.issues"

This suggests the PR branch needs rebasing on current main.

## Required Work

### 1. Fix Lint Errors
Add proper error handling for all 8 flagged locations. Most can use _ = or log warnings.

### 2. Rebase on Current Main
The migration test failures indicate the branch is out of sync. Need to:
- git fetch upstream
- git rebase upstream/main
- Resolve any conflicts
- Verify tests pass locally

### 3. Verify CI Passes
- All lint checks green
- All tests pass (Linux, Windows, Nix)

## Optional Improvements
- Consider splitting into smaller PRs (core index, WASM cache, testing infra)
- Add documentation for benchmark usage
- Extract helper functions in doctor/perf.go for better testability

## Value
This PR delivers real performance improvements. The index optimization alone is worth merging quickly once CI is fixed.


Config option sync.require_confirmation_on_mass_delete

Add config option for paranoid users who want confirmation before pushing after mass deletion.

Option: sync.require_confirmation_on_mass_delete
Default: false

When enabled AND safety check triggers:
- Prompt user for confirmation before pushing
- Show them what would be pushed

For users who have been burned and want extra safety. Most users will not need this.

Location: internal/config/ and internal/syncbranch/worktree.go
Parent issue: bd-3s8


Fix autoimport tests for content-hash collision scoring

## Overview
Three autoimport tests are failing after [deleted:bd-cbed9619.4] because they expect behavior based on the old reference-counting collision resolution, but the system now uses deterministic content-hash scoring.

## Failing Tests
1. `TestAutoImportMultipleCollisionsRemapped` - expects local versions preserved
2. `TestAutoImportAllCollisionsRemapped` - expects local versions preserved
3. `TestAutoImportCollisionRemapMultipleFields` - expects specific collision resolution behavior

## Root Cause
These tests were written when ScoreCollisions used reference counting to determine which version to keep. Now it uses content-hash comparison (introduced in commit 2e87329), which produces different but deterministic results.

## Example
Old behavior: Issue with more references would be kept
New behavior: Issue with lexicographically lower content hash is kept

## Solution
Update each test to:
1. Verify the new content-hash based behavior is correct
2. Check that the remapped issue (not necessarily local/remote) has the expected content
3. Ensure dependencies are preserved on the correct remapped issue

## Acceptance Criteria
- All three autoimport tests pass
- Tests verify content-hash determinism (same collision always resolves the same way)
- Tests check dependency preservation on remapped issues
- Test documentation explains content-hash scoring expectations

## Files to Modify
- `cmd/bd/autoimport_collision_test.go`

## Testing
Run: `go test ./cmd/bd -run "TestAutoImport.*Collision" -v`


Extract duplicated validation logic from CLI commands

~150 lines of identical validation logic duplicated between cmd_create.go and cmd_update.go

Duplication found:
- validateBeadFields(): 2 identical copies (50+ lines each)
- parseTimeWithDefault(): 2 identical copies (30 lines each)
- Flag definitions: 15+ duplicate registrations

Solution: Extract to shared packages:
- internal/validation/bead.go - Centralized validation
- internal/utils/time.go - Consolidate time parsing (already exists)
- cmd/bd/flags.go - Shared flag registration

Impact: Changes require touching 2+ files; high risk of inconsistency; steep learning curve

Effort: 4-6 hours


Use external_ref as primary matching key for import updates

Enable re-syncing from external systems (Jira, GitHub, Linear) by using external_ref as the primary matching key during imports. Currently imports treat any content change as a collision, making it impossible to sync updates from external systems without creating duplicates.

See GH #142 for detailed proposal and implementation plan.

Key changes needed:
1. Add findByExternalRef() query function
2. Update DetectCollisions() to match by external_ref first
3. Update import_shared.go to update existing issues when external_ref matches
4. Add index on external_ref for performance
5. Preserve local issues (no external_ref) from being overwritten

This enables hybrid workflows: import external backlog, break down with local tasks, re-sync anytime.

## Code Review Complete ✅

**Overall Assessment**: EXCELLENT - Production ready

### Implementation Quality
- ✓ Clean architecture with proper interface extension
- ✓ Dual backend support (SQLite + Memory)
- ✓ Smart matching priority: external_ref → ID → content hash
- ✓ O(1) lookups with database index
- ✓ Timestamp-based conflict resolution
- ✓ Comprehensive test coverage (11 test cases)

### Follow-up Issues Filed
High Priority (P2):
- bd-897a: Add UNIQUE constraint on external_ref column
- bd-7315: Add validation for duplicate external_ref in batch imports

Medium Priority (P3):
- bd-f9a1: Add index usage verification test
- bd-3f6a: Add concurrent import race condition tests

Low Priority (P4):
- bd-e166: Improve timestamp comparison readability
- bd-9e23: Optimize Memory backend with index
- bd-537e: Add external_ref change tracking
- bd-df11: Add import metrics
- bd-9f4a: Document external_ref in content hash

### Key Features
✅ External systems (Jira, GitHub, Linear) can re-sync without duplicates
✅ Hybrid workflows: import external backlog, add local tasks, re-sync anytime
✅ Local issues protected from being overwritten
✅ Timestamp checking ensures only newer updates applied
✅ Performance optimized with database index

**Confidence Level**: 95% - Ship it! 🚀


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.


Improve CheckStaleness error handling

## Problem

CheckStaleness returns 'false' (not stale) for multiple error conditions instead of returning errors. This masks problems.

**Location:** internal/autoimport/autoimport.go:253-285

## Edge Cases That Return False

1. **Invalid last_import_time format** (line 259-262)
2. **No JSONL file found** (line 267-277)
3. **JSONL stat fails** (line 279-282)

## Fix

Return errors for abnormal conditions:

```go
lastImportTime, err := time.Parse(time.RFC3339, lastImportStr)
if err != nil {
    return false, fmt.Errorf("corrupted last_import_time: %w", err)
}

if jsonlPath == "" {
    return false, fmt.Errorf("no JSONL file found")
}

stat, err := os.Stat(jsonlPath)
if err != nil {
    return false, fmt.Errorf("cannot stat JSONL: %w", err)
}
```

## Impact
Medium - edge cases are rare but should be handled

## Effort
30 minutes - requires updating callers in RPC server


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


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

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

Uses embeddings by default for >100 issues.

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


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

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

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

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

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


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.


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

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

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


Add tests for internal/autoimport package

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


Add path normalization to getMultiRepoJSONLPaths()

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

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

Impact:
Config with tilde or relative paths will fail

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

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


Missing test for PullResult.SafetyWarnings population


Update bd delete to create tombstones

Modify delete command to create tombstone records instead of (or in addition to) deletions.jsonl entries. Phase 1: dual-write mode. Per design bd-dli.


Design tombstone merge semantics

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


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


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.


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

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

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

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

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

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

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


Add .beads-backup-* pattern to gitignore template

Update the gitignore template in doctor package to include backup directories.

## Change
Add `.beads-backup-*/` to the GitignoreTemplate in `cmd/bd/doctor/gitignore.go`

## Why
Backup directories created by `bd reset --backup` should not be committed to git.
They are local-only recovery tools.

## File
`cmd/bd/doctor/gitignore.go` - look for GitignoreTemplate constant


Add internal/ai package for AI-assisted repairs

Add AI integration package to support AI-powered repair commands.

Providers:
- Anthropic (Claude)
- OpenAI
- Ollama (local)

Features:
- Conflict resolution analysis
- Duplicate detection via embeddings
- Configuration via env vars (BEADS_AI_PROVIDER, BEADS_AI_API_KEY, etc.)

See repair_commands.md lines 357-425 for design.


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)


Debug test


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

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


Test Issue


Registry has cross-process race condition

The global daemon registry (~/.beads/registry.json) can be corrupted when multiple daemons from different workspaces write simultaneously.

**Root cause:**
- Registry uses an in-process mutex but no file-level locking
- Register() and Unregister() release the mutex between read and write
- Multiple daemon processes can interleave their read-modify-write cycles

**Evidence:**
Found registry.json with double closing bracket: `]]` instead of `]`

**Fix options:**
1. Use file locking (flock/fcntl) around the entire read-modify-write cycle
2. Use atomic write pattern (write to temp file, rename)
3. Both (belt and suspenders)

**Files:**
- internal/daemon/registry.go:46-64 (readEntries)
- internal/daemon/registry.go:67-87 (writeEntries)
- internal/daemon/registry.go:90-108 (Register - the race window)


Blocked issue


Short tests taking 13+ minutes (performance regression)


bd resolve-conflicts - Git merge conflict resolver

Automatically resolve JSONL merge conflicts.

Modes:
- Mechanical: ID remapping (no AI)
- AI-assisted: Smart merge/keep decisions
- Interactive: Review each conflict

Handles <<<<<<< conflict markers in .beads/beads.jsonl

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


Audit Current Cache Usage

Understand exactly what code depends on the storage cache


bd doctor should detect fresh clone and recommend 'bd init'

When running `bd doctor` on a fresh clone (JSONL exists, no .db file), it should:

1. Detect this is a fresh clone situation
2. Recommend `bd init --prefix <detected-prefix>` as the fix
3. Show the prefix detected from the JSONL file

Currently it shows various warnings (git hooks, merge driver, etc.) but doesn't address the fundamental issue: the database needs to be hydrated.


Replace BEADS_DB with BEADS_DIR environment variable

Implement BEADS_DIR as a replacement for BEADS_DB to point to the .beads directory instead of the database file directly.

Rationale:
- With --no-db mode, there's no .db file to point to
- The .beads directory is the logical unit (contains config.yaml, db files, jsonl files)
- More intuitive: point to the beads directory not the database file

Implementation:
1. Add BEADS_DIR environment variable support
2. Maintain backward compatibility with BEADS_DB
3. Priority order: BEADS_DIR > BEADS_DB > auto-discovery
4. If BEADS_DIR is set, look for config.yaml in that directory to find actual database path
5. Update documentation and migration guide

Files to modify:
- beads.go (FindDatabasePath function)
- cmd/bd/main.go (initialization)
- Documentation (CLI_REFERENCE.md, TROUBLESHOOTING.md, etc.)
- MCP integration (integrations/beads-mcp/src/beads_mcp/config.py)

Testing:
- Ensure BEADS_DB still works (backward compatibility)
- Test BEADS_DIR with both db and --no-db modes
- Test priority order when both are set
- Update integration tests

Related to GitHub issue #179


Import fails on same-content-different-ID instead of treating as update

## Problem

The importer still has rename detection (importer.go:482-500) that triggers when same content hash has different IDs. With hash IDs, this shouldn't happen, but when it does (test data, bugs, legacy data), the import fails:

```
failed to handle rename bd-ce75 -> bd-5a90: rename collision handling removed - should not occur with hash IDs
```

## Current Behavior

1. Importer finds same content hash with different IDs
2. Calls handleRename() (line 490)
3. handleRename() errors out (line 294): "rename collision handling removed"
4. Import fails

## Expected Behavior

Same content hash + different IDs should be treated as an **update**, not a rename:
- Keep existing ID (already in database)
- Update fields if incoming has newer timestamp
- Discard incoming ID (it's wrong - hash should have generated same ID)

## Impact

- Import fails on legitimate edge cases (test data, data corruption)
- Cryptic error message
- Blocks sync operations

## Fix

In handleRename() or import loop, instead of erroring:
```go
// Same content, different ID - treat as update
if incoming.UpdatedAt.After(existing.UpdatedAt) {
    existing.Status = incoming.Status
    // ... copy other fields
    s.UpdateIssue(ctx, existing)
}
result.Updated++
```

## Files
- internal/importer/importer.go:271-294 (handleRename)
- internal/importer/importer.go:482-500 (rename detection)

## Repro
Import JSONL with bd-ce75 and bd-5a90 (both "Test parent issue" but different content hashes).


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


Refactor daemon.go into internal/daemonrunner

Extract daemon runtime from daemon.go (1,565 lines) into internal/daemonrunner with focused modules: config.go, daemon.go, process.go, rpc_server.go, sync.go, git.go. Keep cobra command thin.


Add alias field to database schema

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

## Database Changes

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

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

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

## API Changes

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

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

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

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

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


Design tombstone storage format

Define the JSON schema for tombstone records. Options: minimal (id, deleted_at, deleted_by, expires_at) vs full preservation (keep all original fields). Trade-offs: storage size vs audit trail. Consider: should tombstones be distinguishable from regular issues by status field or separate record type?


Add label-based filtering to bd ready command

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

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

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

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


Install and test MCP Agent Mail locally

Install MCP Agent Mail on a single development machine and verify basic functionality.

Acceptance Criteria:
- Server installed via one-line installer
- Server running on port 8765
- Can register a project via HTTP
- Can register an agent identity
- Web UI accessible at /mail

Tested local installation. Server runs on port 8765, web UI works. MCP API tool execution has errors - needs debugging. See /tmp/bd-muls-report.md for details.


Debug MCP Agent Mail tool execution errors

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

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

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

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

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

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

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


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


Design hash ID generation algorithm

Design and specify the hash-based ID generation algorithm.

## Requirements
- Deterministic: same inputs → same ID
- Collision-resistant: ~2^32 space for 8-char hex
- Fast: <1μs per generation
- Includes timestamp for uniqueness
- Includes creator/workspace for distributed uniqueness

## Proposed Algorithm
```go
func GenerateIssueID(title, desc string, created time.Time, workspaceID string) string {
    h := sha256.New()
    h.Write([]byte(title))
    h.Write([]byte(desc))
    h.Write([]byte(created.Format(time.RFC3339Nano)))
    h.Write([]byte(workspaceID))
    hash := hex.EncodeToString(h.Sum(nil))
    return "bd-" + hash[:8]  // 8-char prefix = 2^32 space
}
```

## Open Questions
1. 8 chars (2^32) or 16 chars (2^64) for collision resistance?
2. Include priority/type in hash? (Pro: more entropy. Con: immutable)
3. How to handle workspace ID generation? (hostname? UUID?)
4. What if title+desc change? (Answer: ID stays same - hash only used at creation)

## Deliverables
- Design doc: docs/HASH_ID_DESIGN.md
- Collision probability analysis
- Performance benchmarks
- Prototype implementation in internal/types/id_generator.go


Add WaitGroup tracking to FileWatcher goroutines

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

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

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

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

Impact: Race condition on daemon shutdown; potential panic

Effort: 2 hours


Add "bd daemons" command for multi-daemon management

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


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


Fix bd doctor hash ID detection for short all-numeric hashes

bd doctor incorrectly flags hash-based IDs as sequential when they are short (3-4 chars) and all-numeric (e.g., pf-158).

Root cause: isHashID() in cmd/bd/migrate_hash_ids.go:328-358 uses faulty heuristic:
- For IDs < 5 chars, only returns true if contains letters
- But base36 hash IDs can be 3+ chars and all-numeric (MinLength: 3)
- Example: pf-158 is valid hash ID but flagged as sequential

Fix: Check multiple IDs (10-20 samples) instead of single-ID pattern matching:
- Sample IDs across database
- Check majority pattern (sequential vs hash format)
- Sequential: 1-4 digits (bd-1, bd-2...)
- Hash: 3-8 chars base36 (pf-158, pf-3s9...)

Impact: False positive warnings in bd doctor output


Daemon gets stuck when auto-import blocked by git conflicts

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

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

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

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

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

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


TestHooksCheckGitHooks failing - version mismatch (0.23.0 vs 0.23.1)

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

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

This is blocking the landing of GH #274 fix.


bd init should refuse when JSONL already has issues (safety guard)

When running `bd init` in a directory with an existing JSONL containing issues, bd should refuse and suggest the correct action instead of proceeding.

## The Problem

Current behavior when database is missing but JSONL exists:
```
$ bd create "test"
Error: no beads database found
Hint: run 'bd init' to create a database...
```

This leads users (and AI agents) to reflexively run `bd init`, which can cause:
- Prefix mismatch if wrong prefix specified
- Data corruption if JSONL is damaged
- Confusion about what actually happened

## Proposed Behavior

```
$ bd init --prefix bd

⚠ Found existing .beads/issues.jsonl with 76 issues.

This appears to be a fresh clone, not a new project.

To hydrate the database from existing JSONL:
  bd doctor --fix

To force re-initialization (may cause data loss):
  bd init --prefix bd --force

Aborting.
```

## Trigger Conditions

- `.beads/issues.jsonl` or `.beads/beads.jsonl` exists
- File contains > 0 valid issue lines
- No `--force` flag provided

## Edge Cases

- Empty JSONL (0 issues) → allow init (new project)
- Corrupted JSONL → warn but allow with confirmation
- Existing `.db` file → definitely refuse (weird state)

## Related

- bd-dmb: Fresh clone should suggest hydration (better error messages)
- bd-4ew: bd doctor should detect fresh clone

This issue is about the safety guard on `bd init` itself, not the error messages from other commands.


bd sync: use worktree for sync.branch commits (non-daemon mode)

## Summary

When `sync.branch` is configured (e.g., `beads-sync`), `bd sync` should commit beads changes to that branch via git worktree, even when the user's working directory is on a different branch (e.g., `main`).

Currently:
- `bd sync` always commits to the current branch
- Daemon with `--auto-commit` uses worktrees to commit to sync branch
- Gap: `bd sync` ignores sync.branch config

## Goal

Workers stay on `main` (or feature branches) while beads metadata automatically flows to `beads-sync` branch. No daemon required.

## Design Considerations

1. **Worktree lifecycle**: Create on first use, reuse thereafter
2. **Daemon interaction**: Ensure daemon and bd sync don't conflict
3. **MCP server**: Uses daemon or direct mode - needs same behavior
4. **Pull semantics**: Pull from sync branch, not current branch
5. **Push semantics**: Push to sync branch remote
6. **Error handling**: Worktree corruption, missing branch, etc.

## Affected Components

- `cmd/bd/sync.go` - Main changes
- `cmd/bd/daemon_sync_branch.go` - Reuse existing functions
- `internal/git/worktree.go` - Already implemented
- MCP server (if it bypasses daemon)

## Edge Cases

- Fresh clone (no sync branch exists yet)
- Worktree exists but is corrupted
- Concurrent bd sync from multiple processes
- sync.branch configured but remote doesn't have that branch
- User switches sync.branch config mid-session


Create cmd/bd/daemon_watcher.go (~150 LOC)

Implement FileWatcher using fsnotify to watch JSONL file and git refs. Handle platform differences (inotify/FSEvents/ReadDirectoryChangesW). Include edge case handling for file rename, event storm, watcher failure.


Extract migration functions to migrations.go

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


Extract daemon config functions to daemon_config.go


Fix autoimport tests for content-hash collision scoring

## Overview
Three autoimport tests are failing after bd-cbed9619.4 because they expect behavior based on the old reference-counting collision resolution, but the system now uses deterministic content-hash scoring.

## Failing Tests
1. `TestAutoImportMultipleCollisionsRemapped` - expects local versions preserved
2. `TestAutoImportAllCollisionsRemapped` - expects local versions preserved
3. `TestAutoImportCollisionRemapMultipleFields` - expects specific collision resolution behavior

## Root Cause
These tests were written when ScoreCollisions used reference counting to determine which version to keep. Now it uses content-hash comparison (introduced in commit 2e87329), which produces different but deterministic results.

## Example
Old behavior: Issue with more references would be kept
New behavior: Issue with lexicographically lower content hash is kept

## Solution
Update each test to:
1. Verify the new content-hash based behavior is correct
2. Check that the remapped issue (not necessarily local/remote) has the expected content
3. Ensure dependencies are preserved on the correct remapped issue

## Acceptance Criteria
- All three autoimport tests pass
- Tests verify content-hash determinism (same collision always resolves the same way)
- Tests check dependency preservation on remapped issues
- Test documentation explains content-hash scoring expectations

## Files to Modify
- `cmd/bd/autoimport_collision_test.go`

## Testing
Run: `go test ./cmd/bd -run "TestAutoImport.*Collision" -v`


Add embedding generation for duplicate detection

Use embeddings for scalable duplicate detection.

Model: text-embedding-3-small (OpenAI) or all-MiniLM-L6-v2 (local)
Storage: SQLite vector extension or in-memory
Cost: ~/bin/bash.0002 per 100 issues

Much cheaper than LLM comparisons for large databases.

Files: internal/embeddings/ (new package)


getLocalSyncBranch YAML parsing is fragile

The getLocalSyncBranch() function in autoimport.go uses a simple line-by-line YAML parser that could fail on:
- Indented sync-branch: (nested in a section)
- Comments containing sync-branch:
- Multi-line values
- Values with special characters

Current implementation at autoimport.go:161-172 does:
```go
for _, line := range strings.Split(string(data), "\n") {
    line = strings.TrimSpace(line)
    if strings.HasPrefix(line, "sync-branch:") {
        value := strings.TrimPrefix(line, "sync-branch:")
        ...
    }
}
```

The reason we can't use viper's cached config is that it's initialized at process startup from the main repo's CWD, not from the current directory at runtime. This causes issues in tests where t.Chdir() changes the directory.

Options:
1. Accept the limitation (sync-branch must be top-level, unquoted)
2. Use a proper YAML library (gopkg.in/yaml.v3) to parse just this value
3. Refactor config package to support re-initialization per directory


Optimize --sandbox mode: skip FlushManager

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

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

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

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

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

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

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

Part of bd-olc1

recovered from git history (pruned from manifest)


Update docs for import bug fix

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


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


Add tests for internal/importer package

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


Git history fallback doesn't escape regex special chars in IDs

## Problem

In `batchCheckGitHistory`, IDs are directly interpolated into a regex pattern:

```go
patterns = append(patterns, fmt.Sprintf(\`"id":"%s"\`, id))
searchPattern := strings.Join(patterns, "|")
cmd := exec.Command("git", "log", "--all", "-G", searchPattern, ...)
```

If an ID contains regex special characters (e.g., `bd-foo.bar` or `bd-test+1`), the pattern will be malformed or match unintended strings.

## Location
`internal/importer/importer.go:923-926`

## Impact
- False positives: IDs with `.` could match any character
- Regex errors: IDs with `[` or `(` could cause git to fail
- Security: potential for regex injection (low risk since IDs are validated)

## Fix
Escape regex special characters:

```go
import "regexp"

escapedID := regexp.QuoteMeta(id)
patterns = append(patterns, fmt.Sprintf(\`"id":"%s"\`, escapedID))
```


Build Node.js CLI wrapper for WASM

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

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

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


Integration test: mutation to export latency

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


Fragile no-db detection uses strings.Contains instead of YAML parsing

Two locations use `strings.Contains(string(configData), "no-db: true")` to detect JSONL-only mode:

1. **cmd/bd/main.go:310** - Auto-enabling no-db mode
2. **cmd/bd/doctor.go:863** - Database status check

This is fragile because it could match:
- Comments: `# no-db: true for testing`
- Nested keys: `settings:\n  no-db: true`
- String values: `note: "set no-db: true to disable"`

Should use proper YAML parsing like we did for sync-branch in bd-0rh:

```go
type noDbConfig struct {
    NoDb bool `yaml:"no-db"`
}

var cfg noDbConfig
if err := yaml.Unmarshal(configData, &cfg); err == nil {
    isNoDbMode = cfg.NoDb
}
```

Related: bd-0rh (fixed similar issue for sync-branch)


Help agents understand version changes with bd info --whats-new

**Problem** (from GH Discussion #239 by @maphew):
Weekly major versions mean agents need to adapt workflows, but currently there's no efficient way to communicate "what changed that affects you."

**Proposed solutions:**

1. **bd info --whats-new** - Show agent-actionable changes since last version
   ```
   Since v0.20.1:
   • Hash IDs eliminate collisions - remove ID coordination workarounds
   • Event-driven daemon (opt-in) - add BEADS_DAEMON_MODE=events
   • Merge driver auto-configured - conflicts rarer
   ```

2. **Version-aware bd onboard** - Detect version changes and show diff of agent-relevant changes

3. **AGENTS.md top section** - "🆕 Recent Changes (Last 3 Versions)" with workflow impacts

**Why agents need this:**
- Raw CHANGELOG is token-heavy and buried in release details
- Full bd onboard re-run wasteful if only 2-3 things changed
- Currently requires user to manually explain updates

**Related:** https://github.com/steveyegge/beads/discussions/239