Fix goroutine leak in daemon restart

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

Location: cmd/bd/daemons.go:251

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

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

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

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

Impact: Goroutine leak on every daemon restart

Effort: 2 hours


Add --parent flag to bd onboard output

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

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


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.

# Design: Tombstone Merge Semantics

**Issue:** bd-zvg
**Author:** beads/refinery
**Status:** Draft
**Date:** 2025-12-05

## Overview

This document defines how tombstone records participate in the 3-way merge algorithm. The key question is how to resolve conflicts between tombstones and live issues.

## Current Behavior

The current 3-way merge in `internal/merge/merge.go` implements:

```go
// Lines 298-307: "RULE 2: deletion always wins over modification"
} else if inBase && inLeft && !inRight {
    // Deleted in right, maybe modified in left
    continue  // issue deleted
} else if inBase && !inLeft && inRight {
    // Deleted in left, maybe modified in right
    continue  // issue deleted
```

This is **implicit deletion** - an issue's absence means deletion. The new tombstone system makes deletion **explicit** with a `status: "tombstone"` record.

## Design Decision: Tombstone Wins Unless Expired

**Recommendation:** Preserve current "deletion wins" semantics, but with TTL expiration escape hatch.

### Rule 1: Non-Expired Tombstone Always Wins

When merging a tombstone against a live issue:

```
If tombstone.IsExpired(ttl):
    → Keep live issue (resurrection allowed)
Else:
    → Keep tombstone (issue remains deleted)
```

**Rationale:**
1. Preserves deterministic behavior users expect
2. Explicit deletion (tombstone) is a deliberate action
3. Prevents "zombie issues" that keep reappearing
4. TTL provides safety valve for stale clones

### Rule 2: Tombstone vs Tombstone - Latest Wins

When merging two tombstones for the same ID:

```go
func mergeTombstones(left, right *Issue) *Issue {
    // Both are tombstones - use later deleted_at
    if isTimeAfter(left.DeletedAt, right.DeletedAt) {
        return left
    }
    return right
}
```

**Rationale:**
- Most recent deletion is most authoritative
- Preserves audit trail (latest actor/reason)
- Matches `updated_at` conflict resolution pattern

### Rule 3: Closed Issue vs Tombstone

Special case: closed issue vs tombstone

```
If tombstone for ID X exists:
    → Tombstone wins (closed is not deleted)
```

**Rationale:**
- Closed issues are still visible in history
- Tombstones are for permanent removal
- User explicitly chose "delete" over "close"

## Merge Algorithm Update

The 3-way merge algorithm needs updates to handle tombstones explicitly:

```go
func merge3WayWithTombstones(base, left, right []Issue, ttl time.Duration) ([]Issue, []string) {
    // ... existing setup ...

    for key := range allKeys {
        baseIssue, inBase := baseMap[key]
        leftIssue, inLeft := leftMap[key]
        rightIssue, inRight := rightMap[key]

        leftTombstone := inLeft && IsTombstone(leftIssue)
        rightTombstone := inRight && IsTombstone(rightIssue)

        // NEW CASE: Tombstone vs Live Issue
        if leftTombstone && !rightTombstone && inRight {
            // Left deleted, right has live issue
            if leftIssue.IsExpired(ttl) {
                result = append(result, rightIssue)  // Resurrection allowed
            } else {
                result = append(result, leftIssue)   // Tombstone wins
            }
            continue
        }

        if rightTombstone && !leftTombstone && inLeft {
            // Right deleted, left has live issue
            if rightIssue.IsExpired(ttl) {
                result = append(result, leftIssue)   // Resurrection allowed
            } else {
                result = append(result, rightIssue)  // Tombstone wins
            }
            continue
        }

        // NEW CASE: Both are tombstones
        if leftTombstone && rightTombstone {
            // Merge tombstones - latest wins
            merged := mergeTombstones(leftIssue, rightIssue)
            result = append(result, merged)
            continue
        }

        // EXISTING CASE: Both are live issues
        // ... existing merge logic ...
    }
}
```

## Edge Cases

### 1. Issue Deleted Then Re-Created

**Scenario:**
1. Clone A deletes issue `bd-abc` (creates tombstone)
2. Clone B (unaware of deletion) closes issue `bd-abc`
3. Clone C creates new issue that happens to get ID `bd-abc` (unlikely with UUIDs)

**Resolution:** Tombstone wins. The ID is "claimed" by the tombstone until expiration.

### 2. Concurrent Deletion

**Scenario:**
1. Clone A deletes issue `bd-abc` at T1
2. Clone B deletes same issue `bd-abc` at T2
3. Both create tombstones with different metadata

**Resolution:** Merge tombstones, use max(T1, T2) as deleted_at.

### 3. Tombstone in Base Only

**Scenario:**
1. Base has tombstone for `bd-abc`
2. Neither left nor right have the tombstone (both pruned/compacted)

**Resolution:** Issue is truly gone. No record in output.

### 4. Fast-Forward Merge

**Scenario:**
- Base empty, left has issues A,B,C
- Right has issues A,B,D and tombstone for C

**Resolution:** Output has A, B, D, and tombstone for C.

## Integration Points

### With TTL Design (bd-1r5)

```go
func (t *Tombstone) IsExpired(ttl time.Duration) bool {
    expiresAt := t.DeletedAt.Add(ttl).Add(clockSkewGrace)
    return time.Now().After(expiresAt)
}
```

The merge algorithm must accept TTL as a parameter.

### With Storage Format (bd-2m7)

Tombstone detection:

```go
func IsTombstone(issue *Issue) bool {
    return issue.Status == "tombstone"
}
```

### With Migration (bd-dli)

During migration, entries from `deletions.jsonl` become tombstones that participate in merges normally.

## Summary of Merge Rules

| Left | Right | Condition | Result |
|------|-------|-----------|--------|
| Live | Tombstone | Not expired | Tombstone |
| Live | Tombstone | Expired | Live |
| Tombstone | Live | Not expired | Tombstone |
| Tombstone | Live | Expired | Live |
| Tombstone | Tombstone | - | Later deleted_at wins |
| Live | Live | - | Existing 3-way merge |
| Absent | Tombstone | - | Tombstone |
| Tombstone | Absent | - | Tombstone |
| Absent | Live | - | Live (new issue) |

## Implementation Notes

### Performance Consideration

The merge algorithm now needs TTL configuration. Options:
1. Pass TTL as parameter (recommended)
2. Read from config file during merge (slower, file I/O)
3. Embed TTL in tombstone record (per-bd-1r5 expires_at field)

**Recommendation:** Pass TTL as parameter, with optional `expires_at` field for future optimization.

### Backward Compatibility

- Old merges without tombstone awareness will treat `status: "tombstone"` as a regular status
- Won't break, but tombstone might not "win" against live issue
- Migration should happen before enabling sync-branch features

## Open Questions

1. **Should resurrection log a warning?**
   - Recommendation: Yes, `"Issue bd-abc resurrected (tombstone expired)"`

2. **Should we track resurrection count?**
   - Recommendation: No for v1, adds complexity

3. **Should `bd merge` command accept --ttl flag?**
   - Recommendation: Yes, for testing/debugging


Test dependency child


Run bd doctor at end of bd init to verify setup

Run bd doctor diagnostics at end of bd init (after line 398 in init.go). If issues found, warn user immediately: '⚠ Setup incomplete. Run bd doctor --fix to complete setup.' Catches configuration problems before user encounters them in normal workflow.