Add bd doctor --fix flag to automatically repair issues

Implement a --fix flag for bd doctor that can automatically repair detected issues.

Requirements:
- Add --fix flag to bd doctor command
- Show all fixable issues and prompt for confirmation before applying fixes
- Organize fix implementations under doctor/fix/<type_of_fix>.go
- Each fix type should have its own file (e.g., doctor/fix/hooks.go, doctor/fix/sync.go)
- Display what will be fixed and ask user to confirm (Y/n) before proceeding
- Support fixing issues like:
  - Missing or broken git hooks
  - Sync problems with remote
  - File permission issues
  - Any other auto-repairable issues doctor detects

Implementation notes:
- Maintain separation between detection (existing doctor code) and repair (new fix code)
- Each fix should be idempotent and safe to run multiple times
- Provide clear output about what was fixed
- Log any fixes that fail with actionable error messages


Support P-prefix priority format (P0-P4) in create and update commands


Remove unused global daemon infrastructure (internal/daemonrunner/)

The internal/daemonrunner/ package (1,468 LOC) contains the old global daemon implementation that is no longer used. We now use per-workspace daemons.

Deadcode analysis shows all these functions are unreachable:
- Daemon.Start, runGlobalDaemon, setupLock
- validateSingleDatabase, validateSchemaVersion
- registerDaemon, unregisterDaemon
- validateDatabaseFingerprint
- Full git client implementation (NewGitClient, HasUpstream, HasChanges, Commit, Push, Pull)
- Helper functions: isGitRepo, gitHasUpstream, gitHasChanges, gitCommit

The entire package appears unused since switching to per-workspace daemon architecture.

Files to remove:
- daemon.go (9,436 bytes)
- git.go (3,510 bytes)
- sync.go (6,401 bytes)
- fingerprint.go (2,076 bytes)
- process.go (3,332 bytes)
- rpc.go (994 bytes)
- config.go (486 bytes)
- logger.go (1,579 bytes)
- flock_*.go (platform-specific file locking)
- signals_*.go (platform-specific signal handling)
- All test files

Total cleanup: ~1,500 LOC


Add ON DELETE CASCADE to child_counters schema

Update schema.go child_counters table foreign key with ON DELETE CASCADE. When parent deleted, child counter should also be deleted. If parent is resurrected, counter gets recreated from scratch. Add migration for existing databases.


Consolidate export/import/commit/push into sync.go

Create internal/daemonrunner/sync.go with Syncer type. Add ExportOnce, ImportOnce, CommitAndMaybePush methods. Replace createExportFunc/createAutoImportFunc with thin closures calling Syncer.


Implicit deletion cases do not check for tombstones in remaining side

In merge3WayWithTTL(), the implicit deletion cases (lines 426-435) handle when an issue exists in base and one side but not the other. The current logic does not check if the remaining side is a tombstone:

Case: inBase && inLeft && !inRight (deleted implicitly in right)
- If leftIssue is a tombstone, we should keep it (tombstone propagation)
- Currently: continue (drops the tombstone)

Case: inBase && !inLeft && inRight (deleted implicitly in left)
- If rightIssue is a tombstone, we should keep it
- Currently: continue (drops the tombstone)

This could cause tombstones to be lost during merge if one side has a tombstone and the other side implicitly deleted. The tombstone should win since its an explicit deletion record.

Files: internal/merge/merge.go:426-435


Integration Testing

Verify cache removal doesn't break any workflows


Extract daemon lifecycle functions to daemon_lifecycle.go


Add TestThreeCloneCollision for regression protection

Add a 3-clone collision test to document behavior and provide regression protection.

Purpose:
- Verify content convergence regardless of sync order
- Document the ID non-determinism behavior (IDs may be assigned differently based on sync order)
- Provide regression protection for multi-way collisions

Test design:
- 3 clones create same ID with different content
- Test two different sync orders (A→B→C vs C→A→B)
- Assert content sets match (ignore specific ID assignments)
- Add comment explaining ID non-determinism is expected behavior

Known limitation:
- Content always converges correctly (all issues present with correct titles)
- Numeric ID assignments (test-2 vs test-3) depend on sync order
- This is acceptable if content convergence is the primary goal


Assigned issue


Fix daemon exiting after 5s on macOS due to PID 1 parent monitoring

GitHub issue #278 reports that the daemon exits after <=5 seconds on macOS because it incorrectly treats PID 1 (launchd) as a dead parent.

When the daemon detaches on macOS, it gets reparented to PID 1 (launchd), which is the init process. The checkParentProcessAlive function was incorrectly treating PID 1 as a sign that the parent died.

Fixed by changing the logic to treat PID 1 as a valid parent for detached daemons.


bd message: Improve type safety with typed parameter structs


Update documentation for merge driver auto-config

Update documentation to reflect the new merge driver auto-configuration during `bd init`.

**Files to update:**
- README.md - Mention merge driver setup in initialization section
- AGENTS.md - Update onboarding section about merge driver
- Possibly QUICKSTART.md

**Content:**
- Explain what the merge driver does
- Show --skip-merge-driver flag usage
- Manual installation steps for post-init setup


Baseline quality gate failure: test

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

This blocks the executor from claiming work until fixed.

Error: go test failed: exit status 1

Output:
```
?   	github.com/steveyegge/beads	[no test files]
# github.com/steveyegge/beads/internal/beads_test [github.com/steveyegge/beads/internal/beads.test]
internal/beads/routing_integration_test.go:142:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
# github.com/steveyegge/beads/internal/compact [github.com/steveyegge/beads/internal/compact.test]
internal/compact/compactor_test.go:17:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
# github.com/steveyegge/beads/cmd/bd [github.com/steveyegge/beads/cmd/bd.test]
cmd/bd/integrity_content_test.go:31:32: undefined: ctx
cmd/bd/integrity_content_test.go:183:32: undefined: ctx
FAIL	github.com/steveyegge/beads/cmd/bd [build failed]
# github.com/steveyegge/beads/internal/daemon [github.com/steveyegge/beads/internal/daemon.test]
internal/daemon/discovery_test.go:21:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/daemon/discovery_test.go:59:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/daemon/discovery_test.go:232:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
# github.com/steveyegge/beads/internal/importer [github.com/steveyegge/beads/internal/importer.test]
internal/importer/external_ref_test.go:22:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/importer/external_ref_test.go:106:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/importer/external_ref_test.go:197:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/importer/external_ref_test.go:295:27: not enough arguments in call to sqlite.New
	have (string)
	want (context.Context, string)
internal/importer/importer_test.go:566:27: not enough arguments in call to sqlite.New
	have (st
... (truncated, see full output in logs)
```

Released by executor after budget limit hit. Tests were already fixed manually. Issue can be closed when tests are verified to pass.


Create deployment scripts for GCP

Automated provisioning scripts for GCP Compute Engine deployment.

Acceptance Criteria:
- Terraform/gcloud scripts
- Static IP allocation
- Firewall rules
- NGINX reverse proxy config
- TLS setup (Let's Encrypt)
- Systemd service file

File: deployment/agent-mail/gcp/


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


Add Claude Code web installation docs to README

GH #439 reported installation issues in Claude Code web environment. The go install fallback works, but users need guidance. Add a section to README documenting the workaround: go install + PATH export.


Auto-migrate database on CLI version bump

When CLI is upgraded (e.g., 0.24.0 → 0.24.1), database version becomes stale. Add auto-migration in PersistentPreRun or daemon startup. Check dbVersion != CLIVersion and run bd migrate automatically. Fixes recurring UX issue where bd doctor shows version mismatch after every CLI upgrade.


Sync should cleanup snapshot files after completion

After sync completion, orphan .base.jsonl and .left.jsonl snapshot files remain in .beads/ directory. These should be cleaned up on successful sync.


Add tests for extracted modules

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


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.


Test child issue


Implement bd migrate command

Add bd migrate command to move issues between repos with filtering. Should support: filtering by status/priority/labels, dry-run mode, preserving dependencies, handling source_repo field updates.


show commit hash in 'bd version' when built from source'


Add --yes flag to bd doctor --fix

bd doctor --fix prompts for confirmation, which blocks automation (CI/CD, scripts, hooks).

Requirements:
1. Add --yes or -y flag to bd doctor command
2. When --yes is passed, skip confirmation prompts
3. Still log what's being fixed (don't go completely silent)


Platform tests: Linux, macOS, Windows

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


purgeDeletedIssues still writes to deletions.jsonl during git-history-backfill

In purgeDeletedIssues (lines 929-939), when a deletion is recovered from git history, we:
1. Append to deletions.jsonl (backfill)
2. Create a tombstone in DB

With the tombstone approach, writing to deletions.jsonl is redundant since the tombstone will be exported to JSONL. Consider removing the deletions.jsonl backfill once we're confident in the tombstone approach.

This is related to the Phase 2 migration (bd-vw8) - we should stop writing to deletions.jsonl entirely.


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.


Expand checkHooksQuick to verify all hook versions

Currently checkHooksQuick only checks post-merge hook version. Should also check pre-commit, pre-push, and post-checkout for completeness. Keep it lightweight but catch more outdated hooks.


Imperator clone on beads-sync branch causes bd sync failures


Switch from deletions manifest to inline tombstones

Replace the current deletions.jsonl manifest with inline tombstone records in issues.jsonl.

## Problem Statement

The current deletions manifest approach has several issues:

1. **Wild poisoning** - A stale clone's deletions manifest can poison fresh databases when synced
2. **Two-level merge inconsistency** - Git content merge and beads snapshot merge use different bases
3. **SyncJSONLToWorktree overwrites** - Blindly copies local JSONL to worktree, losing remote issues
4. **3-day TTL too aggressive** - Deletions expire before dormant branches get merged

## Proposed Solution: Inline Tombstones

Instead of a separate deletions.jsonl file, embed deletion records directly in issues.jsonl:

```json
{"id":"beads-abc","status":"tombstone","title":"Original title","deleted_at":"2025-12-01T...","deleted_by":"user","expires_at":"2025-12-31T..."}
```

### Benefits

1. **Single source of truth** - No separate manifest to sync/merge
2. **Participates in normal 3-way merge** - Deletion conflicts resolved same as other fields
3. **Atomic with issue data** - Can't have orphaned deletions or missing tombstones
4. **Preserves metadata** - Can optionally keep title/type for audit trail

### Design Decisions Needed

1. **TTL duration** - 30 days default? Configurable via config.yaml?
2. **Tombstone content** - Minimal (just ID + timestamps) vs. full issue preservation?
3. **Migration path** - How to handle existing deletions.jsonl files?
4. **Status value** - Use "tombstone" or "deleted"? (tombstone clearer, deleted more intuitive)
5. **Merge semantics** - Does tombstone always win, or use updated_at like other fields?

### Migration Strategy

1. On import, convert deletions.jsonl entries to tombstones in JSONL
2. Deprecate but still read deletions.jsonl for backward compatibility
3. Stop writing to deletions.jsonl after N versions

## Related Issues

- GitHub #464: Beads deletes issues (fresh clone sync problem)
- bd-2e0: Add TTL to deletions manifest entries (current 3-day TTL)
- bd-53c: bd sync corrupts issues.jsonl in multi-clone environments


Fresh clone: bd should suggest 'bd init' when no database exists

On a fresh clone of a repo using beads, running `bd stats` or `bd list` gives a cryptic error:

```
Error: failed to open database: post-migration validation failed: migration invariants failed:
  - required_config_present: required config key missing: issue_prefix (database has 2 issues)
```

**Expected**: A helpful message like:
```
No database found. This appears to be a fresh clone.
Run 'bd init --prefix <prefix>' to hydrate from the committed JSONL file.
Found: .beads/beads.jsonl (38 issues)
```

**Why this matters**: The current UX is confusing for new contributors or fresh clones. The happy path should be obvious.


Update import/export to handle tombstones

Modify importer to read tombstones from issues.jsonl and legacy deletions.jsonl. Modify exporter to write tombstones. Filter tombstoned issues during import. Per design bd-dli.


Batch test 3


Add t.Parallel() to slow hash multiclone tests

Add t.Parallel() to TestHashIDs_MultiCloneConverge and TestHashIDs_IdenticalContentDedup so they run concurrently.

Expected savings: ~10 seconds (from 20s to ~11s)

Implementation:
- Add t.Parallel() call at start of each test function
- Verify tests don't share resources that would cause conflicts
- Run tests to confirm they work in parallel

File: beads_hash_multiclone_test.go:34, :101


Support Ubuntu 20.04 LTS (glibc compatibility issue)

Starting at v0.22, precompiled binaries require GLIBC 2.32+ which is not available on Ubuntu 20.04 LTS (Focal Fossa). Ubuntu 20.04 has GLIBC 2.31.

Error:
```
bd: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by bd)
bd: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by bd)
```

Current workarounds:
1. Upgrade to Ubuntu 22.04+
2. Build from source: `go build -o bd ./cmd/bd/`

Root cause: Go 1.24+ runtime requires newer glibc. CGO is already disabled in .goreleaser.yml.

Possible solutions:
- Pin Go version to 1.21 or 1.22 for releases
- Use Docker/cross-compile with older build environment
- Provide separate build for older distros
- Document minimum requirements clearly

Decision: Document minimum requirements in README instead of pinning Go version.

Rationale:
- Ubuntu 20.04 LTS standard support ended April 2025 (already EOL)
- Pinning Go prevents security fixes, performance improvements, and new features
- Users on EOL distros can upgrade OS or build from source
- Added Requirements section to README with clear glibc 2.32+ requirement


CRITICAL: git-history-backfill purges entire database when JSONL reset

When a clone gets reset (git reset --hard origin/main), the git-history-backfill logic incorrectly adds ALL issues to the deletions manifest, then sync purges the entire database.\n\nFix adds safety guard: never delete more than 50% of issues via git-history-backfill. If threshold exceeded, abort with warning message.


string(rune()) pattern in other test files still only works for single digits

The bd-c5m fix only addressed worktree_divergence_test.go, but several other test files have the same issue:

- cmd/bd/stale_test.go:218, 234
- cmd/bd/export_integrity_integration_test.go:45-47, 279-280
- internal/storage/sqlite/comments_test.go:146, 165
- internal/storage/sqlite/blocked_cache_test.go:292
- internal/storage/sqlite/compact_bench_test.go:117
- internal/storage/sqlite/cycle_detection_test.go:418

These use string(rune('0'+i)) which produces wrong characters when i >= 10.
Should use strconv.Itoa(i) instead.


Add validation for duplicate external_ref in batch imports

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

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

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

Related: bd-1022


Use in-memory filesystem for test git operations

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

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

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


Add tests for Integration Layer Implementation

While implementing bd-wfmw, noticed missing tests

Reviewed existing coverage:
- Basic test coverage exists in lib/test_beads_mail_adapter.py
- Integration tests cover failure scenarios in tests/integration/test_mail_failures.py
- Good coverage of: enabled/disabled modes, graceful degradation, 409 conflicts, HTTP errors, config
- Missing: authorization headers detail, request body structure validation, concurrent reservation timing, TTL edge cases


CLI accepts both hash IDs and aliases

Update all CLI commands to accept both hash IDs (bd-af78e9a2) and aliases (#42, or just 42).

## Parsing Logic
```go
// internal/utils/id_parser.go
func ParseIssueID(input string) (issueID string, err error) {
    // Hash ID: bd-af78e9a2
    if strings.HasPrefix(input, "bd-") {
        return input, nil
    }

    // Alias: #42 or 42
    aliasStr := strings.TrimPrefix(input, "#")
    alias, err := strconv.Atoi(aliasStr)
    if err != nil {
        return "", fmt.Errorf("invalid issue ID: %s", input)
    }

    // Resolve alias to hash ID
    return storage.GetIssueIDByAlias(alias)
}
```

## Commands to Update
All commands that accept issue IDs:

### 1. bd show
```bash
bd show bd-af78e9a2        # Hash ID
bd show #42                # Alias
bd show 42                 # Alias (shorthand)
bd show bd-af78e9a2 #42    # Mixed (multiple IDs)
```

### 2. bd update
```bash
bd update #42 --status in_progress
bd update bd-af78e9a2 --priority 1
```

### 3. bd close
```bash
bd close #42 --reason "Done"
```

### 4. bd dep add/tree
```bash
bd dep add #42 #1 --type blocks
bd dep tree bd-af78e9a2
```

### 5. bd label add/remove
```bash
bd label add #42 critical
```

### 6. bd merge
```bash
bd merge #42 #43 --into #41
```

## Display Format
Default to showing aliases in output:
```bash
$ bd list
#1   Fix authentication bug          P1  open
#2   Add logging to daemon           P2  open
#42  Investigate jujutsu integration P3  open
```

With `--format=hash` flag:
```bash
$ bd list --format=hash
bd-af78e9a2  Fix authentication bug          P1  open
bd-e5f6a7b8  Add logging to daemon           P2  open
bd-1a2b3c4d  Investigate jujutsu integration P3  open
```

## Files to Modify
- internal/utils/id_parser.go (new)
- cmd/bd/show.go
- cmd/bd/update.go
- cmd/bd/close.go
- cmd/bd/reopen.go
- cmd/bd/dep.go
- cmd/bd/label.go
- cmd/bd/merge.go
- cmd/bd/list.go (add --format flag)

## Testing
- Test hash ID parsing
- Test alias parsing (#42, 42)
- Test mixed IDs in single command
- Test error on invalid ID
- Test alias resolution failure


Implement global N-way collision resolution algorithm

## Overview
Phase 3: Replace pairwise collision resolution with global N-way resolution that produces deterministic results regardless of sync order.

## Current Problem
ScoreCollisions (collision.go:228) compares issues pairwise:
```go
collision.RemapIncoming = existingHash < incomingHash
```

This works for 2-way but fails for 3+ way because:
- Each clone makes local decisions without global context
- No guarantee intermediate states are consistent
- Remapping decisions depend on sync order
- Can't detect transitive remap chains (test-1 → test-2 → test-3)

## Solution
Implement global resolution that:
1. Collects ALL versions of same logical issue
2. Sorts by content hash (deterministic)
3. Assigns sequential IDs based on sorted order
4. All clones converge to same assignments

## Implementation Tasks

### 1. Create ResolveNWayCollisions function
File: internal/storage/sqlite/collision.go

Replace ScoreCollisions with:
```go
// ResolveNWayCollisions handles N-way collisions deterministically.
// Groups all versions with same base ID, sorts by content hash,
// assigns sequential IDs. Returns mapping of old ID → new ID.
func ResolveNWayCollisions(ctx context.Context, s *SQLiteStorage,
    collisions []*CollisionDetail, incoming []*types.Issue) (map[string]string, error) {

    if len(collisions) == 0 {
        return make(map[string]string), nil
    }

    // Group by base ID pattern (e.g., test-1, test-2 → base "test-1")
    groups := groupCollisionsByBaseID(collisions)

    idMapping := make(map[string]string)

    for baseID, versions := range groups {
        // 1. Collect all unique versions by content hash
        uniqueVersions := deduplicateVersionsByContentHash(versions)

        // 2. Sort by content hash (deterministic!)
        sort.Slice(uniqueVersions, func(i, j int) bool {
            return uniqueVersions[i].ContentHash < uniqueVersions[j].ContentHash
        })

        // 3. Assign sequential IDs based on sorted order
        prefix := extractPrefix(baseID)
        baseNum := extractNumber(baseID)

        for i, version := range uniqueVersions {
            targetID := fmt.Sprintf("%s-%d", prefix, baseNum+i)

            // Map this version to its deterministic ID
            if version.ID != targetID {
                idMapping[version.ID] = targetID
            }
        }
    }

    return idMapping, nil
}
```

### 2. Implement helper functions

```go
// groupCollisionsByBaseID groups collisions by their logical base ID
func groupCollisionsByBaseID(collisions []*CollisionDetail) map[string][]*types.Issue {
    groups := make(map[string][]*types.Issue)
    for _, c := range collisions {
        baseID := c.ID  // All share same ID (that's why they collide)
        groups[baseID] = append(groups[baseID], c.ExistingIssue, c.IncomingIssue)
    }
    return groups
}

// deduplicateVersionsByContentHash keeps one issue per unique content hash
func deduplicateVersionsByContentHash(issues []*types.Issue) []*types.Issue {
    seen := make(map[string]*types.Issue)
    for _, issue := range issues {
        if _, found := seen[issue.ContentHash]; !found {
            seen[issue.ContentHash] = issue
        }
    }
    result := make([]*types.Issue, 0, len(seen))
    for _, issue := range seen {
        result = append(result, issue)
    }
    return result
}
```

### 3. Update handleCollisions in importer
File: internal/importer/importer.go

Replace ScoreCollisions call with:
```go
// OLD:
if err := sqlite.ScoreCollisions(ctx, sqliteStore, collisionResult.Collisions, allExistingIssues); err != nil {
    return nil, fmt.Errorf("failed to score collisions: %w", err)
}

// NEW:
idMapping, err := sqlite.ResolveNWayCollisions(ctx, sqliteStore,
    collisionResult.Collisions, issues)
if err != nil {
    return nil, fmt.Errorf("failed to resolve collisions: %w", err)
}
```

### 4. Update RemapCollisions
RemapCollisions currently uses collision.RemapIncoming field. Update to use idMapping directly:
- Remove RemapIncoming logic
- Use idMapping to determine what to remap
- Simplify to just apply the computed mapping

### 5. Add comprehensive tests

Test cases:
1. 3-way collision with different content → 3 sequential IDs
2. 3-way collision with 2 identical content → 2 IDs (dedupe works)
3. Sync order independence (A→B→C vs C→A→B produce same result)
4. Content hash ordering is respected
5. Works with 5+ clones

## Acceptance Criteria
- ResolveNWayCollisions implemented and replaces ScoreCollisions
- Groups all versions of same ID together
- Deduplicates by content hash
- Sorts by content hash deterministically
- Assigns sequential IDs starting from base ID
- Returns complete mapping (old ID → new ID)
- All clones converge to same ID assignments
- Works for arbitrary N-way collisions
- TestThreeCloneCollision passes (or gets much closer)

## Files to Modify
- internal/storage/sqlite/collision.go (new function, helpers)
- internal/importer/importer.go (call new function)
- internal/storage/sqlite/collision_test.go (comprehensive tests)

## Testing Strategy

### Unit Tests
- groupCollisionsByBaseID correctly groups
- deduplicateVersionsByContentHash removes duplicates
- Sorting by hash is stable and deterministic
- Sequential ID assignment is correct

### Integration Tests
- 3-way collision resolves to 3 issues
- Sync order doesn't affect final IDs
- Content hash ordering determines winner

### Property Tests
- For any N clones with same content, all converge to same IDs
- Idempotent: running resolution twice produces same result

## Dependencies
- Requires bd-cbed9619.5 (ContentHash field) to be completed first
- Requires bd-cbed9619.4 (read-only detection) for clean integration

## Notes
This is the core algorithm that enables convergence. The key insight:
**Sort by content hash globally, not pairwise comparison.**


Mass deletion logging for forensics

Log detailed information when the safety check triggers during auto-push.

When >50% issues vanish AND >5 existed:
- Log which issues vanished
- Log the before/after counts
- Log timestamp for forensic analysis

This gives users visibility into what happened if something goes wrong, without blocking the sync.

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


Optimize CLI test suite - eliminate redundant git init calls

Current: Each of 13 CLI tests calls git init (31s total). Solution: Use single test binary built once in init(), skip git operations where possible, or use mock filesystem.


Fix: --parent flag doesn't create parent-child dependency

When using `bd create --parent <id>`, the code generates a hierarchical child ID (e.g., bd-123.1) but never creates a parent-child dependency. This causes `bd epic status` to show zero children even though child issues exist.

Root cause: create.go generates child ID using store.GetNextChildID() but never calls store.AddDependency() with type parent-child.

Fix: After creating the issue when parentID is set, automatically add a parent-child dependency linking child -> parent.


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


Prevent test pollution in production database

The bd-vxdr cleanup revealed test issues were created during manual testing in the production workspace (Nov 2-4, template feature development).

**Root cause:** Manual testing with `./bd create "Test issue"` pollutes the production .beads database.

**Prevention strategies:**
1. Use TEST_DB environment variable for manual testing
2. Add warning when creating issues with "Test" prefix
3. Improve developer docs about testing workflow
4. Consider adding `bd test-mode` command for isolated testing

**Implementation completed:**

1. ✅ Added warning when creating issues with "Test" prefix in production database
   - Shows yellow warning with ⚠ symbol
   - Suggests using BEADS_DB for isolated testing
   - Warning appears in create.go after title validation

2. ✅ Documented BEADS_DB testing workflow in AGENTS.md
   - Added "Testing Workflow" section in Development Guidelines
   - Includes manual testing examples with BEADS_DB
   - Includes automated testing examples with t.TempDir()
   - Clear warning about not polluting production database

3. ⚠ Decided against bd test-mode command
   - BEADS_DB already provides simple, flexible isolation
   - Additional command would add complexity without much benefit
   - Current approach follows Unix philosophy (env vars for config)

**Files modified:**
- cmd/bd/create.go - Added Test prefix warning
- AGENTS.md - Added Testing Workflow section

**Testing:**
- Verified warning appears when creating "Test" prefix issues
- Verified BEADS_DB isolation works correctly
- Built successfully with `go build`


Need comprehensive daemon health check command (bd daemon doctor?)


Add test coverage for internal/git package


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.


Add topological sort utility functions

Create internal/importer/sort.go with utilities for depth-based sorting of issues. Functions: GetHierarchyDepth(id), SortByDepth(issues), GroupByDepth(issues). Include stable sorting for same-depth issues.


Implement `bd setup claude` command for Claude Code integration

Create a `bd setup claude` command that installs Claude Code integration files (slash commands and hooks). This is idempotent and safe to run multiple times.


Fix import failure on missing parent issues

Import process fails atomically when JSONL references deleted parent issues. Implement hybrid solution: topological sorting + parent resurrection to handle deleted parents gracefully while maintaining referential integrity. See docs/import-bug-analysis-bd-3xq.md for full analysis.


Add Result.ConvertedToTombstone counter for import statistics

The import Result struct has SkippedDeleted but doesn't distinguish between:
1. Issues skipped because they're in deletions manifest (old behavior)
2. Issues converted from deletions.jsonl to tombstones (new bd-dve behavior)

Add a new ConvertedToTombstone counter to track legacy deletions converted to tombstones during import. This provides better visibility into the migration process.


Refactor: Move staleness check inside daemon branch

## Problem

Currently ensureDatabaseFresh() is called before the daemon mode check, but it checks daemonClient != nil internally and returns early. This is redundant.

**Location:** All read commands (list.go:196, show.go:27, ready.go:102, status.go:80, etc.)

## Current Pattern

Call happens before daemon check, function checks daemonClient internally.

## Better Pattern

Move staleness check to direct mode branch only, after daemon check.

## Impact
Low - minor performance improvement (avoids one function call per command in daemon mode)

## Effort
Medium - requires refactoring 8 command files

## Priority
Low - can defer to future cleanup PR


Fix :memory: database connection pool issue causing "no such table" errors

Critical bug in v0.21.6 where :memory: databases with cache=shared create multiple connections in the pool, causing intermittent "no such table" errors. SQLite's shared cache for in-memory databases only works reliably with a single connection.

Root cause: Missing db.SetMaxOpenConns(1) after sql.Open() for :memory: databases.

Impact: 37 test failures in VC project, affects all consumers using :memory: for testing.


Default to YES for git hooks and merge driver installation

Currently bd init prompts user to install git hooks and merge driver, but setup is incomplete if user declines. Change to install by default unless --skip-hooks or --skip-merge-driver flags are passed. Better safe defaults. If installation fails, warn user and suggest bd doctor --fix.


Configure database connection pool limits for daemon mode

Database connection pool not configured for file-based databases when running in daemon mode.

Location: internal/storage/sqlite/sqlite.go:108-116

Problem:
- Daemon is a long-running server handling concurrent RPC requests
- Multiple CLI commands hit same daemon simultaneously
- Go default: unlimited connections (MaxOpenConns=0)
- SQLite IMMEDIATE transactions serialize on write lock
- Can have 100+ goroutines blocked waiting, each holding connection
- Results in connection exhaustion and 'database is locked' errors

Current code only limits in-memory DBs:
if isInMemory {
    db.SetMaxOpenConns(1)
    db.SetMaxIdleConns(1)
}
// File DBs: UNLIMITED connections!

Fix:
if !isInMemory {
    maxConns := runtime.NumCPU() + 1  // 1 writer + N readers
    db.SetMaxOpenConns(maxConns)
    db.SetMaxIdleConns(2)
    db.SetConnMaxLifetime(0)
}

Impact: 'database is locked' errors under concurrent load in daemon mode

Note: NOT an issue for direct CLI usage (each process isolated). Only affects daemon mode where multiple CLI commands share one database pool.

Effort: 1 hour


Fix compact --analyze/--apply error messages to clarify direct mode requirement

**Problem:**
When users run `bd compact --analyze` with daemon running, they get:
```
Error: compact requires SQLite storage
```

This is misleading because they ARE using SQLite (via daemon), but the command needs DIRECT SQLite access.

**Current behavior:**
- Error message suggests they don't have SQLite
- No hint about using --no-daemon flag
- Related to issue #349 item #1

**Proposed fix:**
1. Update error messages in cmd/bd/compact.go lines 106-114 (analyze) and 121-137 (apply)
2. Add explicit hint: "Use --no-daemon flag to bypass daemon"
3. Change SQLite check error from "requires SQLite storage" to "failed to open database in direct mode"

**Files to modify:**
- cmd/bd/compact.go (lines ~106-137)

**Testing:**
- Run with daemon: `bd compact --analyze` should show clear error + hint
- Run with --no-daemon: `bd compact --analyze --no-daemon` should work
- Verify error message is actionable


Add test case for symmetric collision (both clones create same ID simultaneously)

TestTwoCloneCollision demonstrates the problem, but we need a simpler unit test for the collision resolver itself.

Test should verify:
- Two issues with same ID, different content
- Content hash determines winner deterministically
- Result is same regardless of which clone imports first
- No title swapping occurs

This can be a simpler test than the full integration test.


bd doctor: handle missing daemon registry gracefully

When no daemon has ever been started, bd doctor shows an error parsing the registry: 'failed to parse registry: invalid character 0 after top-level value'. The doctor should handle the case where ~/.bd/daemon/registry.json doesn't exist, treating it as 'no daemon running' rather than an error.


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

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

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

**Proposed behavior:**
- `bd sync --squash` accumulates pending exports without committing
- Commits accumulated changes on session end or explicit `bd sync` (without --squash)
- Default behavior unchanged (immediate commits for orchestration)

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

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


Jira import script (jira2jsonl.py)

Create a Python script to import Jira issues into beads JSONL format.

**Pattern**: Follow examples/github-import/gh2jsonl.py

**Features needed**:
- Fetch issues via Jira REST API with JQL queries
- Pagination handling (100 issues per request)
- Map Jira fields to beads fields (see schema mapping in epic)
- Set external_ref to Jira issue URL for re-sync
- Support both Jira Cloud and Server/Data Center APIs
- Read config from bd config (jira.url, jira.project, jira.api_token)

**Config-driven mapping**:
- jira.status_map.* for status conversion
- jira.type_map.* for issue type conversion
- jira.priority_map.* for priority conversion

**Usage**:
```bash
python jira2jsonl.py --project PROJ | bd import
# or
python jira2jsonl.py --jql 'project=PROJ AND status\!=Done' | bd import
```

**Output**: JSONL with external_ref set to Jira issue URL


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


Ready issue


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)


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 normalizeLabels to shared utility package

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

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

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

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

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


VCS-agnostic sync support

Make bd sync work with multiple VCS types (git, jujutsu, mercurial, sapling) by detecting VCS per repo and using appropriate sync commands, supporting mixed-VCS multi-repo configs.


Investigate database pollution - issue count anomalies

Multiple repos showing inflated issue counts suggesting cross-repo pollution:
- ~/src/dave/beads: 895 issues (675 open) - clearly polluted
- ~/src/stevey/src/beads: 280 issues (expected ~209-220) - possibly polluted

Need to investigate:
1. Source of pollution (multi-repo sync issues?)
2. How many duplicate/foreign issues exist
3. Whether recent sync operations caused cross-contamination
4. How to clean up and prevent future pollution

Investigation findings:

**Root cause identified:**
- NOT cross-repo contamination
- NOT automated test leakage (tests properly use t.TempDir())
- Manual testing during template feature development (Nov 2-4)
- Commit ba325a2: "test issues were accidentally committed during template feature development"

**Database growth timeline:**
- Nov 3: 19 issues (baseline)
- Nov 2-5: +244 issues (massive development spike)
- Nov 6-7: +40 issues (continued growth)
- Current: 291 issues → 270 after cleanup

**Test pollution breakdown:**
- 21 issues matching "Test " prefix pattern
- Most created Nov 2-5 during feature development
- Pollution from manual `./bd create "Test issue"` commands in production workspace
- All automated tests properly isolated with t.TempDir()

**Cleanup completed:**
- Ran scripts/cleanup-test-pollution.sh successfully
- Removed 21 test issues
- Database reduced from 291 → 270 issues (7.2% cleanup)
- JSONL synced to git

**Prevention strategy:**
- Filed follow-up issue for prevention mechanisms
- Script can be deleted once prevention is in place
- Tests are already properly isolated - no code changes needed there


Create backup and restore procedures

Disaster recovery procedures for Agent Mail data.

Acceptance Criteria:
- Automated daily snapshots (GCP persistent disk)
- SQLite backup script
- Git repository backup
- Restore procedure documentation
- Test restore from backup

File: deployment/agent-mail/backup.sh


Add tests for merge driver auto-config in bd init

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

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

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


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.


Fix revive style issues (78 issues)

Style violations: unused parameters (many cmd/args in cobra commands), missing exported comments, stuttering names (SQLiteStorage), indent-error-flow issues.

Fixed 19 revive issues:
- 14 unused-parameter (renamed to _)
- 2 redefines-builtin-id (max→maxCount, min→minInt)
- 3 indent-error-flow (gofmt fixed 2, skipped 1 complex nested one)

Remaining issues are acceptable: 11 unused-params in deeper code, 2 empty-blocks with comments, 1 complex indent case, 1 superfluous-else in test.


Missing test for PullResult.SafetyWarnings population


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.


Feature P1


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


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


Add tombstone doctor checks

Add bd doctor checks for: unmigrated deletions.jsonl, stale deletions.jsonl in Phase 3, tombstones expiring soon. Per design bd-dli.


Second worktree test


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

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


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.


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.


Update AGENTS.md with event-driven mode

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


Optimize test suite performance - cut runtime by 50%+

## Problem
Test suite takes ~20.8 seconds, with 95% of time spent in just 2 tests:
- TestHashIDs_MultiCloneConverge: 11.08s (53%)
- TestHashIDs_IdenticalContentDedup: 8.78s (42%)

Both tests in beads_hash_multiclone_test.go perform extensive Git operations (bare repos, multiple clones, sync rounds).

## Goal
Cut total test time by at least 50% (to ~10 seconds or less).

## Analysis
Tests already have some optimizations:
- --shared --depth=1 --no-tags for fast cloning
- Disabled hooks, gc, fsync
- Support -short flag

## Impact
- Faster development feedback loop
- Reduced CI costs and time
- Better developer experience


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

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

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

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

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


Test npm package installation locally

Verify npm package works before publishing:

## Local testing
- Run npm pack in npm/ directory
- Install tarball globally: npm install -g beads-bd-0.21.5.tgz
- Test basic commands:
  - bd --version
  - bd init --quiet --prefix test
  - bd create "Test issue" -p 1 --json
  - bd list --json
  - bd sync

## Test environments
- macOS (darwin-arm64 and darwin-amd64)
- Linux (ubuntu docker container for linux-amd64)
- Windows (optional, if available)

## Validation
- Binary downloads during postinstall
- All bd commands work identically to native
- No permission issues
- Proper error messages on failure


Batch test 4


Tombstone import creates duplicate entries when deletions.jsonl has entries already in JSONL

In the tombstone import logic (bd-dve), when a non-tombstone issue is in both JSONL and deletions.jsonl, we:
1. Skip the issue (line 149-155)
2. Create a tombstone from deletions.jsonl (lines 161-171)

This means if the JSONL contains issue 'bd-abc' (non-tombstone) and deletions.jsonl also has 'bd-abc', we skip the JSONL version but then add a tombstone version from deletions.jsonl. This is intentional but the skipped issue count may be confusing.

Consider: should we track converted-to-tombstone separately from skipped-deleted in the Result struct?


Publish bd-wasm to npm

Package and publish WASM build to npm. Child of epic bd-44d0.

## Tasks
- [ ] Optimize WASM bundle (compression)
- [ ] Create README for npm package
- [ ] Set up npm publishing workflow
- [ ] Publish v0.1.0-alpha
- [ ] Test installation in clean environment
- [ ] Update beads AGENTS.md with installation instructions

## Package Name
bd-wasm (or @beads/wasm-cli)


User Diagnostics (bd doctor --perf)

Extend cmd/bd/doctor.go to add --perf flag for user performance diagnostics.

Functionality:
- Add --perf flag to existing bd doctor command
- Collect system info (OS, arch, Go version, SQLite version)
- Collect database stats (size, issue counts, dependency counts)
- Time key operations on user's actual database:
  * bd ready
  * bd list --status=open
  * bd show <random-issue>
  * bd create (with rollback)
  * Search with filters
- Generate CPU profile automatically (timestamped filename)
- Output simple report with platform info, timings, profile location

Output example:
  Beads Performance Diagnostics
  Platform: darwin/arm64
  Database: 8,234 issues (4,123 open)

  Operation Performance:
    bd ready                  42ms
    bd list --status=open     15ms

  Profile saved: beads-perf-2025-11-13.prof
  View: go tool pprof -http=:8080 beads-perf-2025-11-13.prof

Implementation:
- Extend cmd/bd/doctor.go (~100 lines)
- Use runtime/pprof for CPU profiling
- Use time.Now()/time.Since() for timing
- Rollback test operations (don't modify user's database)


Investigate TestRoutingIntegration failure

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


Delete server_cache_storage.go

Remove the entire cache implementation file (~286 lines)


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)


Remove misleading placeholder 'bd merge' command from duplicates output

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

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

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

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

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

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

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


bd migrate --update-repo-id triggers auto-import that resurrects deleted issues

**Bug:** Running `bd migrate --update-repo-id` can resurrect previously deleted issues from git history.

## What Happened

User deleted 490 closed issues:
- Deletion committed successfully (06d655a) with JSONL at 48 lines
- Database had 48 issues after deletion
- User ran `bd migrate --update-repo-id` to fix legacy database
- Migration triggered daemon auto-import
- JSONL had been restored to 538 issues (from commit 6cd3a32 - before deletion)
- Auto-import loaded the old JSONL over the cleaned database
- Result: 490 deleted issues resurrected

## Root Cause

The auto-import logic in `cmd/bd/sync.go:130-136`:
```go
if isJSONLNewer(jsonlPath) {
    fmt.Println("→ JSONL is newer than database, importing first...")
    if err := importFromJSONL(ctx, jsonlPath, renameOnImport); err != nil {
```

This checks if JSONL mtime is newer than database and auto-imports. The problem:
1. Git operations (pull, merge, checkout) can restore old JSONL files
2. Restored file has recent mtime (time of git operation)
3. Auto-import sees "newer" JSONL and imports it
4. Old data overwrites current database state

## Timeline

- 19:59: Commit 6cd3a32 restored JSONL to 538 issues from d99222d
- 20:22: Commit 3520321 (bd sync)
- 20:23: Commit 06d655a deleted 490 issues → JSONL now 48 lines
- 20:23: User ran `bd migrate --update-repo-id`
- Migration completed, daemon started
- Daemon saw JSONL (restored earlier to 538) was "newer" than database
- Auto-import resurrected 490 deleted issues

## Impact

- **Critical data loss bug** - user deletions can be undone silently
- Affects any workflow that uses git branches, merges, or checkouts
- Auto-import has no safety checks against importing older data
- Users have no warning that old data will overwrite current state

## Fix Options

1. **Content-based staleness** (not mtime-based)
   - Compare JSONL content hash vs database content hash
   - Only import if content actually changed
   - Prevents re-importing old data with new mtime

2. **Database timestamp check**
   - Store "last export timestamp" in database metadata
   - Only import JSONL if it's newer than last export
   - Prevents importing old JSONL after git operations

3. **User confirmation**
   - Before auto-import, show diff of what will change
   - Require confirmation for large changes (>10% issues affected)
   - Safety valve for destructive imports

4. **Explicit sync mode**
   - Disable auto-import entirely
   - Require explicit `bd sync` or `bd import` commands
   - Trade convenience for safety

## Recommended Solution

Combination of #1 and #2:
- Add `last_export_timestamp` to database metadata
- Check JSONL mtime > last_export_timestamp before importing
- Add content hash check as additional safety
- Show warning if importing would delete >10 issues

This preserves auto-import convenience while preventing data loss.

## Files Involved

- `cmd/bd/sync.go:130-136` - Auto-import logic
- `cmd/bd/daemon_sync.go` - Daemon export/import cycle
- `internal/autoimport/autoimport.go` - Staleness detection

## Reproduction Steps

1. Create and delete some issues, commit to git
2. Checkout an earlier commit (before deletion)
3. Checkout back to current commit
4. JSONL file now has recent mtime but old content
5. Run any bd command that triggers auto-import
6. Deleted issues are resurrected


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

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

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

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

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

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

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


Fix GH #227: bd edit broken pipe errors

bd edit command gets "broken pipe" errors when using daemon mode because editing can take minutes and the daemon connection times out.

Solution: Force bd edit to always use direct mode (--no-daemon) since it's human-only and interactive.

Fixed by checking cmd.Name() == "edit" in main.go PersistentPreRun and setting noDaemon = true.


Test integration issue

This is a real integration test


Rapid 1


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


Refactor create_test.go to use shared DB setup

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

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

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


Standardize daemon detection: use tryDaemonLock probe before RPC

Before attempting RPC connection, call tryDaemonLock() to check if lock is held:
- If lock NOT held: skip RPC attempt (no daemon running)
- If lock IS held: proceed with RPC + short timeout

This is extremely cheap and eliminates unnecessary connection attempts.

Apply across all client entry points that probe for daemon.


Add unit tests for config modes

Test all four orphan_handling modes: strict (fails), resurrect (creates tombstone), skip (logs warning), allow (imports orphan). Verify error messages and logging output for each mode.


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.


Add AI planning docs management guidance to bd onboard (GH-196)

Enhanced bd onboard command to provide guidance for managing AI-generated planning documents (Claude slop).

Addresses GitHub issue #196: https://github.com/steveyegge/beads/issues/196

Changes:
- Added Managing AI-Generated Planning Documents section to bd onboard
- Recommends using history/ directory for ephemeral planning files
- Updated AGENTS.md to demonstrate the pattern
- Added comprehensive tests

Commit: d46177d


Update documentation for bd prime and Claude integration

Update AGENTS.md, README.md, and QUICKSTART.md to document the new `bd prime` command, `bd setup claude` command, and tip system.


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


bd-hv01: Deletion tracking production readiness

Epic to track all improvements and fixes needed to make the deletion tracking implementation ([deleted:bd-hv01]) production-ready.

The core 3-way merge algorithm is sound, but there are critical issues around atomicity, error handling, and edge cases that need to be addressed before this can be safely used in production.

Critical path (P1):
- Non-atomic snapshot operations
- Brittle JSON string comparison
- Silent partial deletion failures
- Race conditions in concurrent scenarios

Follow-up work (P2-P3):
- Test coverage for edge cases and multi-repo mode
- Performance optimizations
- Code refactoring and observability

Related commit: 708a81c


Command Set Standardization & Flag Consistency

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

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

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

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


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! 🚀


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.


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

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

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

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

## Proposed Architecture

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

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

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

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

## Implementation Phases

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

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

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

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

## Risks & Mitigations

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

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

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

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

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

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

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


Rapid 4


Baseline quality gate failure: lint

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

This blocks the executor from claiming work until fixed.

Error: golangci-lint failed: exit status 1

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

```


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

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

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

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

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

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

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


UX nightmare: multiple ways daemon can fail with misleading messages


Refactor daemon local-only sync functions to reduce duplication

PR #433 added three new local-only sync functions (createLocalSyncFunc, createLocalExportFunc, createLocalAutoImportFunc) that are largely copy-paste of existing ones with git operations removed. Refactor to use a single implementation with a skipGit bool parameter or extract shared logic into helper functions to reduce ~200 lines of duplication.


Issue with desc

This is a description


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

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

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

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

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


Don't search parent directories for .beads databases

bd currently walks up the directory tree looking for .beads directories, which can find unrelated databases (e.g., ~/.beads). This causes confusing warnings and potential data pollution.

Should either:
1. Stop at git root (don't search above it)
2. Only use explicit BEADS_DB env var or local .beads
3. At minimum, don't search in home directory


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

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

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

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


Add migration registry and simplify New()

Create migrations.go with Migration type and registry. Change New() to: openDB -> initSchema -> RunMigrations(db). This removes 8+ separate migrate functions from New().


Test graceful degradation when server unavailable

Verify that agents continue working normally when Agent Mail server is stopped or unreachable.

Acceptance Criteria:
- Agent detects server unavailable on startup
- Logs "falling back to Beads-only mode"
- All bd commands work normally
- Agent can claim issues (no reservations, like today)
- Git sync operates as normal
- No errors or crashes

Success Metric: Zero functional difference when Agent Mail unavailable


bd init overwrites existing git hooks without detection or chaining

GH #254: bd init silently overwrites existing git hooks (like pre-commit framework) without detecting them, backing them up, or offering to chain. This breaks workflows and can result in committed code with failing tests.

Fix: Detect existing hooks, prompt user with options to chain/overwrite/skip.


Consider extracting error handling helpers

Evaluate creating FatalError() and WarnError() helpers as suggested in ERROR_HANDLING.md to reduce boilerplate and enforce consistency. Prototype in a few files first to validate the approach.


Consider using types.Status in merge package for type safety

The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521


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


Duplicate safety check message when confirmation required


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.


Extract daemon sync functions to daemon_sync.go


Fix inconsistent error handling in multi-repo deletion tracking

From bd-xo6b code review: Multi-repo deletion tracking has mixed failure modes that can leave system in inconsistent state.

**Current behavior (daemon_sync.go):**
- Snapshot capture (L505-514): Hard fail → aborts sync
- Merge/prune (L575-584): Hard fail → aborts sync
- Base snapshot update (L613-619): Soft fail → logs warning, continues

**Critical problem:**
If merge fails on repo 3 of 5:
- Repos 1-2 have already merged and deleted issues (irreversible)
- Repos 3-5 are untouched
- Database is in partially-updated state
- No rollback mechanism

**Real-world scenario:**
```
Sync with repos [A, B, C]:
1. Capture snapshots A ✓, B ✓, C ✗ → ABORT (good)
2. Merge A ✓, B ✗ → ABORT but A already deleted issues (BAD - no rollback)
3. Update base A ⚠, B ⚠ → Warnings only (inconsistent with 1 & 2)
```

**Solution options:**
1. **Two-phase commit:**
   - Phase 1: Validate all repos (check files exist, readable, parseable)
   - Phase 2: Apply changes atomically (or fail entirely before any mutations)

2. **Fail-fast validation:**
   - Before any snapshot/merge operations, validate all repos upfront
   - Abort entire sync if any repo fails validation

3. **Make base snapshot update consistent:**
   - Either make it hard-fail like the others, or make all soft-fail

**Files:**
- cmd/bd/daemon_sync.go:505-514 (snapshot capture)
- cmd/bd/daemon_sync.go:575-584 (merge/prune)
- cmd/bd/daemon_sync.go:613-619 (base snapshot update)

**Recommendation:** Use option 1 (two-phase) or option 2 (fail-fast validation) + fix base snapshot inconsistency.


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

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

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

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


bd import auto-detects wrong prefix from directory name instead of issue IDs

When importing issues.jsonl into a fresh database, 'bd import' prints:

  ✓ Initialized database with prefix 'beads' (detected from issues)

But the issues all have prefix 'bd-' (e.g., bd-03r). It appears to be detecting the prefix from the directory name (.beads/) rather than from the actual issue IDs in the JSONL.

This causes import to fail with:
  validate ID prefix for bd-03r: issue ID 'bd-03r' does not match configured prefix 'beads'

Workaround: Run 'bd config set issue_prefix bd' before import, or use 'bd init --prefix bd'.


bd jira sync command

Add a built-in bd command for Jira synchronization.

**Requires**: Both import and export scripts working

**Features**:
- `bd jira sync --pull` - Import from Jira to beads
- `bd jira sync --push` - Export from beads to Jira
- `bd jira sync` - Bidirectional (pull then push, with conflict resolution)
- `bd jira status` - Show sync status and last sync time

**Conflict resolution**:
- Timestamp-based: newer update wins
- Option for --prefer-local or --prefer-jira to override
- Interactive mode for manual conflict resolution (optional)

**Integration**:
- Uses jira.* config settings from bd config
- Stores last sync timestamp in config
- Logs sync activity for audit

**Stretch goals**:
- Webhook integration for real-time sync
- Selective sync by JQL filter


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

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

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

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

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


Add command injection prevention tests for git rm in merge command

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

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

In cmd/bd/merge.go:121, exec.Command is called with variable fullPath, flagged by gosec G204 for potential command injection.

Add tests covering:
- File paths with shell metacharacters (;, |, &, $, etc.)
- Paths with spaces and special characters
- Paths attempting command injection (e.g., 'file; rm -rf /')
- Validation that fullPath is properly sanitized
- Only valid git-tracked files can be removed

This is a critical security path preventing arbitrary command execution.

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


Test reservation expiration and renewal

Verify TTL-based reservation expiration works correctly.

Acceptance Criteria:
- Reserve with short TTL (30s)
- Verify other agents can't claim
- Wait for expiration
- Verify reservation auto-released
- Other agent can now claim
- Test renewal/heartbeat mechanism

File: tests/integration/test_reservation_ttl.py

Implemented comprehensive TTL/expiration test suite in tests/integration/test_reservation_ttl.py

Test Coverage:
✅ Short TTL reservations (30s) - verifies TTL is properly set
✅ Reservation blocking - confirms agent2 cannot claim while agent1 holds reservation
✅ Auto-release after expiration - validates expired reservations are auto-cleaned and become available
✅ Renewal/heartbeat - tests that re-reserving extends expiration time

All 4 tests passing in 56.9s total (including 30s+ wait time for expiration tests).

Mock server implements full TTL management:
- Reservation class with expiration tracking
- Auto-cleanup of expired reservations on each request
- Renewal support (same agent re-reserving)
- 409 conflict for cross-agent reservation attempts


Detect and warn about outdated git hooks

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


Update LINTING.md with current baseline

After cleanup, document the remaining acceptable baseline in LINTING.md so we can track regression.


Implement content-hash based collision resolution for deterministic convergence

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

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

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

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

Rename detection successfully implemented and tested!

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

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

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

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


Clarify JSONL size bounds with multi-repo

The contributor-workflow-analysis.md states (line 226): 'Keep beads.jsonl small enough for agents to read (<25k)'

With multi-repo hydration, it's unclear whether this bound applies to:
- Each individual JSONL file (likely intention)
- The total hydrated size across all repos (unclear)
- Both (most conservative)

Clarification needed because:
- VC monitors .beads/issues.jsonl size to stay under limit
- With multi-repo, VC needs to know if each additional repo also has 25k limit
- Agents reading hydrated data need to know total size bounds
- Performance characteristics depend on total vs per-repo limits

Example scenario:
- Primary repo: 20k JSONL
- Planning repo: 15k JSONL
- Total hydrated: 35k
Is this acceptable or does it violate the <25k principle?


Add MCP server functions for repair commands

Expose new repair commands via MCP server for agent access:

Functions to add:
- beads_repair_deps()
- beads_detect_pollution()
- beads_validate()
- beads_resolve_conflicts() (when implemented)

Update integrations/beads-mcp/src/beads_mcp/server.py

See repair_commands.md lines 803-884 for design.

Implemented all three MCP server functions:

1. **repair_deps(fix=False)** - Find/fix orphaned dependencies
2. **detect_pollution(clean=False)** - Detect/clean test issues
3. **validate(checks=None, fix_all=False)** - Run comprehensive health checks

Changes:
- Added abstract methods to BdClientBase
- Implemented in BdCliClient (CLI execution)
- Added NotImplementedError stubs in BdDaemonClient (falls back to CLI)
- Created wrapper functions in tools.py
- Registered @mcp.tool decorators in server.py

All commands tested and working with --no-daemon flag.


Document sandbox workarounds for GH #353

Add documentation for sandbox troubleshooting and new flags.

**Tasks:**
1. Create or update TROUBLESHOOTING.md with sandbox section
2. Document new flags in CLI reference
3. Add comment to GH #353 with immediate workarounds

**Content needed:**
- Symptoms of daemon lock issues in sandboxed environments
- Usage guide for --sandbox, --force, and --allow-stale flags
- Step-by-step troubleshooting for Codex users
- Examples of each escape hatch

**Files to update:**
- docs/TROUBLESHOOTING.md (create if needed)
- docs/CLI_REFERENCE.md or README.md
- GitHub issue #353

**References:**
- docs/GH353_INVESTIGATION.md (lines 240-276)


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


bd doctor --json flag not working

The --json flag on bd doctor command doesn't produce JSON output. It continues to show human-readable output instead. The flag is registered locally on doctorCmd but the code uses the global jsonOutput variable set by PersistentPreRun. Need to investigate why the flag isn't being honored.


Testing event-driven mode!


bd doctor: add SQLite integrity check (PRAGMA integrity_check)

Add a database integrity check using SQLite's PRAGMA integrity_check to detect corruption. Currently bd doctor checks schema compatibility but not data integrity. This would help diagnose corrupted databases that pass schema checks but have internal inconsistencies.


Add bd migrate-tombstones command

Create migration command to convert existing deletions.jsonl entries to tombstones. Support --dry-run and --verbose flags. Archive old file with .migrated suffix. Per design bd-dli.


Update MCP server for hash IDs

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

## Changes Needed

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

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

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

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

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

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

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

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

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

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

### 4. Add beads_alias_* Functions

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

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

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

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

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

The MCP server accepts both hash IDs and aliases:

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

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

## Alias Management

New functions for alias control:

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

## Migration

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

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

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

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

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

## Testing

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

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

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

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

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

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

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

Detection happens at runtime based on issue ID format.

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

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


Auto-invoke 3-way merge for JSONL conflicts

Currently when git pull encounters merge conflicts in .beads/issues.jsonl, the post-merge hook fails with an error message pointing users to manual resolution or the beads-merge tool.

This is a poor user experience - the conflict detection is working, but we should automatically invoke the advanced 3-way merging instead of just telling users about it.

**Current behavior:**
- Detect conflict markers in JSONL
- Display error with manual resolution options
- Exit with failure

**Desired behavior:**
- Detect conflict markers in JSONL
- Automatically invoke beads-merge 3-way merge
- Only fail if automatic merge cannot resolve the conflicts

**Reference:**
- beads-merge tool: https://github.com/neongreen/mono/tree/main/beads-merge
- Error occurs in post-merge hook during bd sync after git pull


Daemon crashes silently on RPC query after startup

The daemon fails to handle 'show' RPC commands when:
1) JSONL is newer than database (needs import)
2) git pull fails due to uncommitted changes

Symptoms:
- Daemon appears to run (ps shows process)
- 'bd list' and other commands work fine
- 'bd show <id>' returns "failed to read response: EOF"
- No panic or error logged in daemon.log

Root cause likely: auto-import deadlock or state corruption when import is blocked by git conflicts.

Workaround:
- Restart daemon after syncing git state (commit/push changes)
- OR use --no-daemon flag for all commands

The panic recovery added in server_lifecycle_conn.go:183 didn't catch any panics, confirming this isn't a panic-based crash.

Root cause found and fixed: Two bugs - (1) nil pointer check missing in handleShow causing panic, (2) double JSON encoding in show.go ID resolution. Both fixed. bd show now works with daemon.


Add tests for internal/utils package

Currently 0.0% coverage. Need tests for utility functions including issue ID parsing and validation.


Delete collision resolution code

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

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

## Code to Remove from Existing Files

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

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

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

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

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

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

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

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

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

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