Refactor duplicate JSONL-from-git parsing code

Both readFirstIssueFromGit() in init.go and importFromGit() in autoimport.go have similar code patterns for:
1. Running git show <ref>:<path>
2. Scanning the output with bufio.Scanner
3. Parsing JSON lines

Could be refactored to share a helper like:
- readJSONLFromGit(gitRef, path string) ([]byte, error)
- Or a streaming version: streamJSONLFromGit(gitRef, path string) (io.Reader, error)

Files:
- cmd/bd/autoimport.go:225-256 (importFromGit)
- cmd/bd/init.go:1212-1243 (readFirstIssueFromGit)

Priority is low since code duplication is minimal and both functions work correctly.


Block direct status update to tombstone in UpdateIssue

Per bd-2m7 design: 'Should we support tombstone as valid input to bd update --status? Recommendation: No, tombstones only created via bd delete'

Currently nothing prevents 'bd update <id> --status=tombstone', which would create an invalid tombstone (missing deleted_at, deleted_by, etc.).

Location: internal/storage/sqlite/queries.go:500-664 UpdateIssue

Add validation to reject status=tombstone in updates.


Refactor AGENTS.md to use external references

Suggestion to use external references (e.g., "ALWAYS REFER TO ./beads/prompt.md") instead of including all instructions directly within AGENTS.md.
Reasons:
1. Agents can follow external references.
2. Prevents context pollution/stuffing in AGENTS.md as more tools append instructions.


troubleshoot GH#278 daemon exits every few secs


Add tests for tombstone edge cases

Missing test coverage for tombstone implementation:

1. Test tombstone validation (deleted_at requirement)
2. Test GetReadyWork excludes tombstones
3. Test SearchIssues behavior with tombstones
4. Integration test for creating/retrieving a tombstone via the delete command
5. Test that status=tombstone update is rejected


bd init resets metadata.json jsonl_export to beads.jsonl, ignoring existing issues.jsonl

When running 'bd init --prefix bd' in a repo that already has .beads/issues.jsonl, the init command overwrites metadata.json and sets jsonl_export back to 'beads.jsonl' instead of detecting and respecting the existing issues.jsonl file.

Steps to reproduce:
1. Have a repo with .beads/issues.jsonl (canonical) and metadata.json pointing to issues.jsonl
2. Delete beads.db and run 'bd init --prefix bd'
3. Check metadata.json - it now says jsonl_export: beads.jsonl

Expected: Init should detect existing issues.jsonl and use it.

Workaround: Manually edit metadata.json after init.


Performance Testing Documentation

Create docs/performance-testing.md documenting the performance testing framework.

Sections:
1. Overview - What the framework does, goals
2. Running Benchmarks
   - make bench command
   - Running specific benchmarks
   - Interpreting output (ns/op, allocs/op)
3. Profiling and Analysis
   - Viewing CPU profiles with pprof
   - Reading flamegraphs
   - Memory profiling
   - Finding hotspots
4. User Diagnostics
   - bd doctor --perf usage
   - Sharing profiles with bug reports
   - Understanding the report output
5. Comparing Performance
   - Using benchstat for before/after comparisons
   - Detecting regressions
6. Tips for Optimization
   - Common patterns
   - When to profile vs benchmark

Style:
- Concise, practical examples
- Screenshots/examples of pprof output
- Clear command-line examples
- Focus on workflow, not theory


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