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


Fix bd init to bootstrap from sync-branch when configured

When sync-branch is configured in config.yaml, bd init should import from that branch instead of HEAD. Currently it reads HEAD:.beads/issues.jsonl which may be empty or stale when issues are on the sync branch. Location: cmd/bd/autoimport.go:97 checkGitForIssues()


Add `bd merge` command wrapping 3-way merge logic

Implement CLI command to invoke beads-merge functionality.

**Interface**:
```bash
bd merge <output> <base> <left> <right>
bd merge --debug <output> <base> <left> <right>
```

**Behavior**:
- Exit code 0 on clean merge
- Exit code 1 if conflicts (write conflict markers)
- Support --debug flag for verbose output
- Match beads-merge's existing behavior

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


Stress test: event storm handling

Simulate 100+ rapid JSONL writes. Verify debouncer batches to single import. Verify no data loss. Test daemon stability.


Test issue 0


Add tests for daemon config module


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


Latency test


Latency test manual


Config option sync.require_confirmation_on_mass_delete

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

Option: sync.require_confirmation_on_mass_delete
Default: false

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

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

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


bd sync false positive: conflict detection triggers on JSON-encoded angle brackets in issue content

The bd sync --import-only command incorrectly detects conflict markers when issue descriptions contain the text '<<<<<<<' or '>>>>>>>' as legitimate content (e.g., documentation about git conflict markers).

**Reproduction:**
1. Create issue with design field containing: 'Read file, extract <<<<<<< / ======= / >>>>>>> markers'
2. Export to JSONL (gets JSON-encoded as \u003c\u003c\u003c...)
3. Commit and push
4. Pull from remote
5. bd sync --import-only fails with: 'Git conflict markers detected in JSONL file'

**Root cause:**
The conflict detection appears to decode JSON before checking for conflict markers, causing false positives when issue content legitimately contains these strings.

**Expected behavior:**
Conflict detection should only trigger on actual git conflict markers (literal '<<<<<<<' bytes in the raw file), not on JSON-encoded content within issue fields.

**Test case:**
VC project at ~/src/dave/vc has vc-85 'JSONL Conflict Parser' which documents conflict parsing and triggers this bug.

**Suggested fixes:**
1. Only scan for literal '<<<<<<<' bytes (not decoded JSON content)
2. Parse JSONL first and only flag unparseable lines
3. Check git merge state (git status) to confirm actual conflict
4. Add --skip-conflict-check flag for override


Update documentation for hash IDs and aliases

Update all documentation to explain hash-based IDs and aliasing system.

## Files to Update

### 1. README.md
Add section explaining hash IDs:
```markdown
## Issue IDs

Beads uses **hash-based IDs** for collision-free distributed issue tracking:

- **Hash ID**: `bd-af78e9a2` (8-char SHA256 prefix, immutable, globally unique)
- **Alias**: `#42` (sequential number, mutable, human-friendly)

### Using IDs
```bash
bd show bd-af78e9a2   # Use hash ID
bd show #42           # Use alias
bd show 42            # Use alias (shorthand)
```

### Why Hash IDs?
- **Collision-free**: Work offline without ID conflicts
- **Distributed**: No coordination needed between clones
- **Git-friendly**: Different IDs = different JSONL lines, fewer merge conflicts

### Aliases
Aliases are workspace-local shortcuts for hash IDs. They're:
- Automatically assigned on issue creation
- Reassigned deterministically on sync (if conflicts)
- Can be manually controlled with `bd alias` commands
```

### 2. AGENTS.md
Update agent workflow:
```markdown
## Hash-Based IDs (v2.0+)

Beads v2.0 uses hash-based IDs to eliminate collision problems:

**When creating issues**:
```bash
bd create "Fix bug" -p 1
# → Creates bd-af78e9a2 with alias #1
```

**When referencing issues**:
- In text: Use hash IDs (stable): "See bd-af78e9a2 for details"
- In CLI: Use aliases (readable): `bd update #42 --status done`

**After sync**:
- Alias conflicts resolved automatically (content-hash ordering)
- No ID collisions possible
- No remapping needed

**Migration from v1.x**:
```bash
bd migrate --hash-ids  # One-time migration
```
```

### 3. QUICKSTART.md (if exists)
Show alias usage in examples:
```bash
# Create issue (gets hash ID + alias)
bd create "Fix authentication bug" -p 1
# → Created bd-af78e9a2 (alias: #1)

# Reference by alias
bd show #1
bd update #1 --status in_progress
bd close #1 --reason "Fixed"
```

### 4. ADVANCED.md
Add section on hash ID internals:
```markdown
## Hash ID Generation

Hash IDs are generated deterministically:

```go
SHA256(title || description || timestamp || workspace_id)[:8]
```

**Collision probability**:
- 8 hex chars = 2^32 space = ~4 billion IDs
- Birthday paradox: 50% collision probability at ~65,000 issues
- For typical projects (<10,000 issues), collision risk is negligible

**Collision detection**:
If a hash collision occurs (extremely rare), beads:
1. Detects on insert (UNIQUE constraint)
2. Appends random suffix: `bd-af78e9a2-a1b2`
3. Retries insert

## Alias Conflict Resolution

When multiple clones assign same alias to different issues:

**Strategy**: Content-hash ordering (deterministic)
- Sort conflicting issue IDs lexicographically
- Lowest hash ID keeps the alias
- Others reassigned to next available aliases

**Example**:
```
Clone A: Assigns #42 to bd-a1b2c3d4
Clone B: Assigns #42 to bd-e5f6a7b8
After sync: bd-a1b2c3d4 keeps #42 (lower hash)
            bd-e5f6a7b8 gets #100 (next available)
```
```

### 5. MIGRATION.md (new file)
```markdown
# Migrating to Hash-Based IDs (v2.0)

## Overview
Beads v2.0 introduces hash-based IDs to eliminate collision problems. This is a **breaking change** requiring migration.

## Migration Steps

### 1. Backup
```bash
cp -r .beads .beads.backup
git commit -am "Pre-migration backup"
```

### 2. Run Migration
```bash
# Dry run first
bd migrate --hash-ids --dry-run

# Apply migration
bd migrate --hash-ids
```

### 3. Commit Changes
```bash
git add .beads/issues.jsonl
git commit -m "Migrate to hash-based IDs (v2.0)"
git push origin main
```

### 4. Coordinate with Collaborators
All clones must migrate before syncing:
1. Notify team: "Migrating to v2.0 on [date]"
2. All collaborators pull latest
3. All run `bd migrate --hash-ids`
4. All push changes
5. Resume normal work

## Rollback
```bash
# Restore backup
mv .beads.backup .beads
bd export  # Regenerate JSONL
git checkout .beads/issues.jsonl
```

## FAQ

**Q: Can I mix v1.x and v2.0 clones?**
A: No. All clones must be on same version.

**Q: Will my old issue IDs work?**
A: No, but aliases preserve the numbers: bd-1c63eb84 → #1

**Q: What happens to links like "see bd-cb64c226.3"?**
A: Migration updates all text references automatically.
```

### 6. CHANGELOG.md
```markdown
## v2.0.0 (YYYY-MM-DD)

### Breaking Changes
- **Hash-based IDs**: Issues now use collision-free hash IDs (bd-af78e9a2)
  instead of sequential IDs (bd-1c63eb84, bd-9063acda)
- **Aliasing system**: Human-friendly aliases (#42) for hash IDs
- **Migration required**: Run `bd migrate --hash-ids` to convert v1.x databases

### Added
- `bd alias` command for manual alias control
- `bd migrate --hash-ids` migration tool
- Alias conflict resolution (deterministic, content-hash ordering)

### Removed
- ID collision detection and resolution (~2,100 LOC)
- `bd import --resolve-collisions` flag (no longer needed)

### Benefits
- ✅ Zero ID collisions in distributed workflows
- ✅ Simpler codebase (-1,350 net LOC)
- ✅ Better git merge behavior
- ✅ True offline-first operation
```

## Testing
- Build docs locally (if using doc generator)
- Check all links work
- Verify examples are correct
- Spellcheck

## Files to Create/Modify
- README.md (hash ID section)
- AGENTS.md (workflow updates)
- ADVANCED.md (internals)
- MIGRATION.md (new)
- CHANGELOG.md (v2.0 entry)
- docs/ (any other docs)


bd-hv01: Add comprehensive edge case tests for deletion tracking

Need to add tests for: corrupted snapshot file, stale snapshot (> 1 hour), concurrent sync operations (daemon + manual), partial deletion failure, empty remote JSONL, multi-repo mode with deletions, git worktree scenario.

Also refine TestDeletionWithLocalModification to check for specific conflict error instead of accepting any error.

Files: cmd/bd/deletion_tracking_test.go


Auto-recovery mode (bd sync --auto-recover)

Add bd sync --auto-recover flag that: detects problematic sync state, backs up .beads/issues.db with timestamp, rebuilds DB from JSONL atomically, verifies consistency, reports what was fixed. Provides safety valve when sync integrity fails.


Sync sanitize incorrectly removes newly created issues

## Problem

The sync sanitize process incorrectly identifies newly created issues as 'deleted issues resurrected by git merge' and removes them from the local JSONL file.

## Reproduction

1. Create a new issue: bd create --title='Test issue'
2. Run bd sync
3. Observe: New issue appears in sanitize removal list
4. Issue is removed from local JSONL but preserved on beads-sync branch

## Observed Behavior

During sync, the sanitize step outputs:
```
→ Sanitized JSONL: removed 738 deleted issue(s) that were resurrected by git merge
  - bd-08ea  (newly created issue!)
  - bd-tnsq  (newly created issue!)
  ...
```

The newly created issues get removed locally but remain on beads-sync branch.

## Expected Behavior

Newly created issues should NOT be removed by sanitize. The sanitize should only remove issues that:
1. Were previously deleted (have tombstones or are in deletions manifest)
2. Are being resurrected from old git history

## Root Cause Investigation

The sanitize logic likely compares the local DB snapshot against some reference and incorrectly classifies new issues as 'resurrected deleted issues'. Possible causes:
- Snapshot protection logic not accounting for new issues
- Deletion manifest containing stale entries
- Race condition between export and sanitize

## Impact

- New issues disappear from local JSONL after sync
- Issues remain on beads-sync but cause confusion
- Multi-agent workflows affected when agents can't see new issues locally
- Requires manual intervention to recover

## Files to Investigate

- cmd/bd/sync.go - sanitize logic
- cmd/bd/snapshot_manager.go - snapshot comparison
- Deletion manifest handling

## Acceptance Criteria

- [ ] Newly created issues survive sync without being sanitized
- [ ] Only truly deleted/resurrected issues are removed
- [ ] Add test case for this scenario


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

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

Current coverage: 24.8% (improved from 20.2%)

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

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

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

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

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


Batch test 1


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

## Problem

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

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

## Current Behavior

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

## Expected Behavior

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

## Impact

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

## Fix

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

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

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


Extract hash ID generation functions to hash_ids.go

Move generateHashID, getNextChildNumber, GetNextChildID to hash_ids.go


Implement tombstone TTL and expiration logic

Add IsExpired() method to tombstones, implement TTL config reading from config.yaml, add clock skew grace period. Per design bd-1r5.


Implement "bd daemons logs" subcommand

Add command to view daemon logs for a specific workspace. Requires daemon logging to file (may need separate issue for log infrastructure).


Optimize cmd/bd long-mode tests by switching to in-process testing

The long-mode CLI tests in cmd/bd are slow (1.4-4.4 seconds each) because they spawn full bd processes via exec.Command() for every operation.

Current approach:
- Each runBD() call spawns new bd process via exec.Command(testBD, args...)
- Each process initializes Go runtime, loads SQLite, parses CLI flags
- Tests run serially (create → update → show → close)
- Even with --no-daemon flag, there's significant process spawn overhead

Example timing from test run:
- TestCLI_PriorityFormats: 2.21s
- TestCLI_Show: 2.26s
- TestCLI_Ready: 2.29s
- TestCLI_Import: 4.42s

Optimization strategy:
1. Convert most tests to in-process testing (call bd functions directly)
2. Reuse test databases across related operations instead of fresh init each time
3. Keep a few exec.Command() tests that batch multiple operations to verify the CLI path works end-to-end

This should reduce test time from ~40s to ~5s for the affected tests.

Converted all CLI tests in cli_fast_test.go to use in-process testing via rootCmd.Execute(). Created runBDInProcess() helper that:
- Calls rootCmd.Execute() directly instead of spawning processes
- Uses mutex to serialize execution (rootCmd/viper not thread-safe)
- Properly cleans up global state (store, daemonClient)
- Returns only stdout to avoid JSON parsing issues with stderr warnings

Performance results:
- In-process tests: ~0.6s each (cached even faster)
- exec.Command tests: ~3.7s each
- Speedup: ~10x faster

Kept TestCLI_EndToEnd() that uses exec.Command for end-to-end validation of the actual binary.


Add integration tests for adapter library

Test suite for beads_mail_adapter.py covering all scenarios.

Acceptance Criteria:
- Test enabled mode (server available)
- Test disabled mode (server unavailable)
- Test graceful degradation (server dies mid-operation)
- Test reservation conflicts
- Test message sending/receiving
- Mock HTTP server for testing
- 90%+ code coverage

File: lib/test_beads_mail_adapter.py

Test suite completed with 29 comprehensive tests covering:
- Enabled mode (server available): 10 tests
- Disabled mode (server unavailable): 2 tests
- Graceful degradation: 4 tests
- Reservation conflicts: 2 tests
- Configuration: 5 tests
- Health check scenarios: 3 tests
- HTTP error handling: 3 tests

**Performance**: All tests run in 10ms (fast!)

**Coverage highlights**:
✅ Server health checks (ok, degraded, error, timeout)
✅ All API operations (reserve, release, notify, check_inbox, get_reservations)
✅ HTTP errors (404, 409 conflict, 500, 503)
✅ Network errors (timeout, connection refused)
✅ Malformed responses (bad JSON, empty body, plain text errors)
✅ Environment variable configuration
✅ Graceful degradation when server dies mid-operation
✅ Conflict handling with both JSON and plain text errors
✅ Dict wrapper responses ({"messages": [...]} and {"reservations": [...]})
✅ Custom TTL for reservations
✅ Default agent name fallback

No external dependencies, no slow integration tests, just fast unit tests with mocks.


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


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 should recommend bd prime migration for existing repos

bd doctor should detect old beads integration patterns and recommend migrating to bd prime approach.

## Current behavior
- bd doctor checks if Claude hooks are installed globally
- Doesn't check project-level integration (AGENTS.md, CLAUDE.md)
- Doesn't recommend migration for repos using old patterns

## Desired behavior
bd doctor should detect and suggest:

1. **Old slash command pattern detected**
   - Check for /beads:* references in AGENTS.md, CLAUDE.md
   - Suggest: These slash commands are deprecated, use bd prime hooks instead

2. **No agent documentation**
   - Check if AGENTS.md or CLAUDE.md exists
   - Suggest: Run 'bd onboard' or 'bd setup claude' to document workflow

3. **Old MCP-only pattern**
   - Check for instructions to use MCP tools but no bd prime hooks
   - Suggest: Add bd prime hooks for better token efficiency

4. **Migration path**
   - Show: 'Run bd setup claude to add SessionStart/PreCompact hooks'
   - Show: 'Update AGENTS.md to reference bd prime instead of slash commands'

## Example output

⚠ Warning: Old beads integration detected in CLAUDE.md
  Found: /beads:* slash command references (deprecated)
  Recommend: Migrate to bd prime hooks for better token efficiency
  Fix: Run 'bd setup claude' and update CLAUDE.md

💡 Tip: bd prime + hooks reduces token usage by 80-99% vs slash commands
  MCP mode: ~50 tokens vs ~10.5k for full MCP scan
  CLI mode: ~1-2k tokens with automatic context recovery

## Benefits
- Helps existing repos adopt new best practices
- Clear migration path for users
- Better token efficiency messaging


Implement signal-aware context in CLI commands

Replace context.Background() with signal.NotifyContext() to enable graceful cancellation.

## Context
Part of context propagation work (bd-350). Phase 1 infrastructure is complete - sqlite.New() now accepts context parameter.

## Implementation
Set up signal-aware context at the CLI entry points:

```go
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
```

## Key Locations
- cmd/bd/main.go:438 (marked with TODO(bd-350))
- cmd/bd/daemon.go:317 (marked with TODO(bd-350))
- Other command entry points

## Benefits
- Ctrl+C cancels ongoing database operations
- Graceful shutdown on SIGTERM
- Better user experience during long operations

## Acceptance Criteria
- [ ] Ctrl+C during import cancels operation cleanly
- [ ] Ctrl+C during export cancels operation cleanly
- [ ] No database corruption on cancellation
- [ ] Proper cleanup on signal (defers execute)


bd-hv01: Storage backend extensibility broken by type assertion

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

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

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


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.


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


Update documentation for beads-merge integration

Document the integrated merge functionality.

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

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


Implement 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


Enhance bd doctor to suggest reset for broken states

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

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

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

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

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

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


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

Depends on bd-cbed9619.1, bd-0dcea000, bd-2752a7a2, bd-9826b69a.

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


Wrap git operations in GitClient interface

Create internal/daemonrunner/git.go with GitClient interface (HasUpstream, HasChanges, Commit, Push, Pull). Default implementation using os/exec. Use in Syncer and Run loop for testability.


Change default JSONL filename from beads.jsonl back to issues.jsonl throughout codebase


Add unit tests for parent resurrection

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


Fix Nix flake build test failures

Nix build is failing during test phase with same test errors as Windows.

**Error:**
```
error: Cannot build '/nix/store/rgyi1j44dm6ylrzlg2h3z97axmfq9hzr-beads-0.9.9.drv'.
Reason: builder failed with exit code 1.
FAIL github.com/steveyegge/beads/cmd/bd 16.141s
```

This may be related to test environment setup or the same issues affecting Windows tests.


Daemon race condition: stale export overwrites recent DB changes

**Symptom:**
Merged bd-fc2d into bd-fb05 in ~/src/beads (commit ce4d756), pushed to remote. The ~/src/fred/beads daemon then exported its stale DB state and committed (8cc1bb4), reverting bd-fc2d back to "open" status.

**Timeline:**
1. 21:45:12 - Merge committed from ~/src/beads (ce4d756): bd-fc2d closed
2. 21:49:42 - Daemon in ~/src/fred/beads exported stale state (8cc1bb4): bd-fc2d open again

**Root cause:**
The fred/beads daemon had a stale database (bd-fc2d still open) and didn't auto-import the newer JSONL before exporting. When it exported, it overwrote the merge with its stale state.

**Expected behavior:**
Daemon should detect that JSONL is newer than its last export and import before exporting.

**Actual behavior:**
Daemon exported stale DB state, creating a conflicting commit that reverted upstream changes.

**Impact:**
Multi-workspace setups with daemons can silently lose changes if one daemon has stale state and exports.


Database import silently fails when daemon version != CLI version


Fix unparam unused parameter in cmd/bd/doctor.go:1879

Linting issue: checkGitHooks - path is unused (unparam) at cmd/bd/doctor.go:1879:20. Error: func checkGitHooks(path string) doctorCheck {


Split internal/rpc/server.go into focused modules

The file `internal/rpc/server.go` is 2,273 lines with 50+ methods, making it difficult to navigate and prone to merge conflicts. Split into 8 focused files with clear responsibilities.

Current structure: Single 2,273-line file with:
- Connection handling
- Request routing
- All 40+ RPC method implementations
- Storage caching
- Health checks & metrics
- Cleanup loops

Target structure:
```
internal/rpc/
├── server.go          # Core server, connection handling (~300 lines)
├── methods_issue.go   # Issue operations (~400 lines)
├── methods_deps.go    # Dependency operations (~200 lines)
├── methods_labels.go  # Label operations (~150 lines)
├── methods_ready.go   # Ready work queries (~150 lines)
├── methods_compact.go # Compaction operations (~200 lines)
├── methods_comments.go # Comment operations (~150 lines)
├── storage_cache.go   # Storage caching logic (~300 lines)
└── health.go          # Health & metrics (~200 lines)
```

Migration strategy:
1. Create new files with appropriate methods
2. Keep `server.go` as main file with core server logic
3. Test incrementally after each file split
4. Final verification with full test suite


CI failing on all 3/4 test jobs despite individual tests passing

CI has been broken for a day+ with mysterious test failures. Issue #173 on GitHub tracks this.

## Current Status
- **Lint job**: ✅ PASSING
- **Test (Linux)**: ❌ FAILING (exit code 1)
- **Test (Windows)**: ❌ FAILING (exit code 1)
- **Test Nix Flake**: ❌ FAILING (exit code 1)

## Key Observations
All three failing jobs show identical pattern:
- Individual test output shows PASS for every test
- Final result: `FAIL github.com/steveyegge/beads/cmd/bd`
- Exit code 1 despite no visible test failures
- Last visible test output before failure: "No Reason Issue" test (TestCloseCommand/close_without_reason)

## Investigation So Far
1. All tests appear to pass when examined individually
2. Likely causes:
   - Race detector finding data races during test cleanup (`-race` flag)
   - Panic/error occurring after main tests complete
   - Test harness issue not reporting actual failure
   - Possible regression from PR #203 (dependency_type changes)

## Recent CI Runs
- Run 19015040655 (latest): 3/4 failing
- Multiple recent commits tried to fix Windows/lint issues
- Agent on rrnewton/beads fork attempting fixes (2/4 passing there)

## Next Steps
1. Run tests locally with `-race -v` to see full output
2. Check for unreported test failures or panics
3. Examine test cleanup/teardown code
4. Review recent changes around close command tests
5. Consider if race detector is too sensitive or catching real issues

## Progress Update

### ✅ Fixed (commits 09bd4d3, 21a29bc)
1. **Daemon auto-import** - Always recompute content_hash in importer to avoid stale hashes
2. **TestScripts failures** - Added bd binary to PATH for shell subprocess tests
3. **Test infrastructure** - Added .gitignore to test repos, fixed last_import_time metadata

### ✅ CI Status (Run 19015638968)
- **Test (Linux)**: ✅ SUCCESS - All tests passing
- **Test (Windows)**: ❌ FAILURE - Pre-existing Windows test failures
- **Test Nix Flake**: ❌ FAILURE - Build fails with same test errors
- **Lint**: ❌ FAILURE - Pre-existing issue in migrate.go:647

### ❌ Remaining Issues (not related to original bd-1231)

**Windows failures:**
- TestFindDatabasePathEnvVar
- TestHashIDs_MultiCloneConverge
- TestHashIDs_IdenticalContentDedup
- TestDatabaseReinitialization (5 subtests)
- TestFindBeadsDir_NotFound
- TestMetricsSnapshot/uptime

**Lint failure:**
- cmd/bd/migrate.go:647:37: cleanupWALFiles - result 0 (error) is always nil (unparam)

**Nix failure:**
- Build fails during test phase with same test errors

### Next Steps
1. Investigate Windows-specific test failures
2. Fix linting issue in migrate.go
3. Debug Nix build test failures


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


Update bash-agent example with Agent Mail integration

Add Agent Mail integration to examples/bash-agent/agent.sh using curl for HTTP calls.

Acceptance Criteria:
- Health check function using curl
- Reserve issue before claiming
- Send notifications on status change
- Release on completion
- Graceful degradation if curl fails
- No bash errors when Agent Mail unavailable

File: examples/bash-agent/agent.sh


Consolidate ID generation and validation into ids.go

Extract ID logic into ids.go: ValidateIssueIDPrefix, GenerateIssueID, EnsureIDs. Move GetAdaptiveIDLength here. Unify single and bulk ID generation flows.


Feature P1


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


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.


npm package for bd with native binaries

Create an npm package that wraps native bd binaries for easy installation in Claude Code for Web and other Node.js environments.

## Problem
Claude Code for Web sandboxes are full Linux VMs with npm support, but cannot easily download binaries from GitHub releases due to network restrictions or tooling limitations.

## Solution
Publish bd as an npm package that:
- Downloads platform-specific native binaries during postinstall
- Provides a CLI wrapper that invokes the native binary
- Works seamlessly in Claude Code for Web SessionStart hooks
- Maintains full feature parity (uses native SQLite)

## Benefits vs WASM
- ✅ Full SQLite support (no custom VFS needed)
- ✅ All features work identically to native bd
- ✅ Better performance (native vs WASM overhead)
- ✅ ~4 hours effort vs ~2 days for WASM
- ✅ Minimal maintenance burden

## Success Criteria
- npm install @beads/bd works in Claude Code for Web
- All bd commands function identically to native binary
- SessionStart hook documented for auto-installation
- Package published to npm registry


Final validation test


Add commit and branch to bd version output

The 'bd version' command reports commit and branch correctly when built with 'go build' (thanks to Go 1.25+ automatic VCS info embedding), but NOT when installed with 'go install' or 'make install'. Need to either: 1) Update Makefile to use 'go build' instead of 'go install', or 2) Add explicit ldflags to both Makefile and .goreleaser.yml to ensure commit/branch are always set regardless of build method.


Run bd doctor at end of bd init to verify setup

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


Block direct status update to tombstone in UpdateIssue

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

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

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

Add validation to reject status=tombstone in updates.


Add transaction helper to replace manual COMMIT/ROLLBACK

Create tx.go with withTx helper that handles transaction lifecycle. Replace manual transaction blocks in create/insert/update paths.

Refactoring complete:
- Created withTx() helper in util.go
- Added ExecInTransaction() as deprecated wrapper for backward compatibility
- Refactored all manual transaction blocks to use withTx():
  - events.go: AddComment
  - dirty.go: MarkIssuesDirty, ClearDirtyIssuesByID
  - labels.go: executeLabelOperation
  - dependencies.go: AddDependency, RemoveDependency
  - compact.go: ApplyCompaction
- All tests pass successfully


bd preflight: PR readiness checks for contributors

## Vision

Encode project-specific institutional knowledge into executable checks. CONTRIBUTING.md is documentation that's read once and forgotten; `bd preflight` is documentation that runs at exactly the right moment.

## Problem Statement

Contributors face a "last mile" problem - they do the work but stumble on project-specific gotchas at PR time:
- Nix vendorHash gets stale when go.sum changes
- Beads artifacts leak into PRs (see bd-umbf for namespace solution)
- Version mismatches between version.go and default.nix
- Tests/lint not run locally before pushing
- Other project-specific checks that only surface when CI fails

These are too obscure to remember, exist in docs nobody reads end-to-end, and waste CI round-trips.

## Why beads?

Beads already has a foothold in the contributor workflow. It knows:
- Git state (staged files, branch, dirty status)
- Project structure
- The specific issue being worked on
- Project-specific configuration

## Proposed Interface

### Tier 1: Checklist Mode (v1)

    $ bd preflight
    PR Readiness Checklist:

    [ ] Tests pass: go test -short ./...
    [ ] Lint passes: golangci-lint run ./...
    [ ] No beads pollution: check .beads/issues.jsonl diff
    [ ] Nix hash current: go.sum unchanged or vendorHash updated
    [ ] Version sync: version.go matches default.nix

    Run 'bd preflight --check' to validate automatically.

### Tier 2: Check Mode (v2)

    $ bd preflight --check
    ✓ Tests pass
    ✓ Lint passes
    ⚠ Beads pollution: 3 issues in diff - are these project issues or personal?
    ✗ Nix hash stale: go.sum changed, vendorHash needs update
      Fix: sha256-KRR6dXzsSw8OmEHGBEVDBOoIgfoZ2p0541T9ayjGHlI=
    ✓ Version sync

    1 error, 1 warning. Run 'bd preflight --fix' to auto-fix where possible.

### Tier 3: Fix Mode (v3)

    $ bd preflight --fix
    ✓ Updated vendorHash in default.nix
    ⚠ Cannot auto-fix beads pollution - manual review needed

## Checks to Implement

| Check | Description | Auto-fixable |
|-------|-------------|--------------|
| tests | Run go test -short ./... | No |
| lint | Run golangci-lint | Partial (gofmt) |
| beads-pollution | Detect personal issues in diff | No (see bd-umbf) |
| nix-hash | Detect stale vendorHash | Yes (if nix available) |
| version-sync | version.go matches default.nix | Yes |
| no-debug | No TODO/FIXME/console.log | Warn only |
| clean-stage | No unintended files staged | Warn only |

## Future: Configuration

Make checks configurable per-project via .beads/preflight.yaml:

    preflight:
      checks:
        - name: tests
          run: go test -short ./...
          required: true
        - name: no-secrets
          pattern: "**/*.env"
          staged: deny
        - name: custom-check
          run: ./scripts/validate.sh

This lets any project using beads define their own preflight checks.

## Implementation Phases

### Phase 1: Static Checklist
- Implement bd preflight with hardcoded checklist for beads
- No execution, just prints what to check
- Update CONTRIBUTING.md to reference it

### Phase 2: Automated Checks
- Implement bd preflight --check
- Run tests, lint, detect stale hashes
- Clear pass/fail/warn output

### Phase 3: Auto-fix
- Implement bd preflight --fix
- Fix vendorHash, version sync
- Integrate with bd-umbf solution for pollution

### Phase 4: Configuration
- .beads/preflight.yaml support
- Make it useful for other projects using beads
- Plugin/hook system for custom checks

## Dependencies

- bd-umbf: Namespace isolation for beads pollution (blocking for full solution)

## Success Metrics

- Fewer CI failures on first PR push
- Reduced "fix nix hash" commits
- Contributors report preflight caught issues before CI


Document shared memory test isolation pattern in test_helpers.go

Tests were failing because :memory: creates a shared database across all tests. The fix is to use "file::memory:?mode=memory&cache=private" for test isolation.

Should document this pattern in test_helpers.go and potentially update newTestStore to use private memory by default.


Run final validation and cleanup checks

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

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

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

Impact: Confirms all cleanup objectives achieved successfully

Validation completed:
- LOC: 52,372 lines total
- Dead code: 4 functions in import_shared.go (tracked in bd-6fe4622f)
- Build: ✓ Successful
- Test coverage: ~20-82% across packages
- Test failure: TestTwoCloneCollision (timeout issue)
- Linting: errcheck warnings present (defer close, fmt errors)
- Test time: ~20s

Issues found:
1. bd-6fe4622f: Remove unreachable import functions (renameImportedIssuePrefixes, etc)
2. TestTwoCloneCollision: Daemon killall timeout causing test failure
3. Linting: errcheck violations need fixing


bd prime: AI context loading and Claude Code integration

Implement `bd prime` command and Claude Code hooks for context recovery. Hooks work with BOTH MCP server and CLI approaches - they solve the context memory problem (keeping bd workflow fresh after compaction) not the tool access problem (MCP vs CLI).


Support --parent flag in daemon mode

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


Jira export script (jsonl2jira.py)

Create a Python script to push beads issues to Jira.

**Requires**: Jira import script to be complete first (need external_ref matching logic working)

**Features needed**:
- Create new Jira issues from beads issues without external_ref
- Update existing Jira issues matched by external_ref
- Map beads fields back to Jira fields
- Handle Jira workflow transitions (status changes may need transitions)
- Support custom field mapping for design/acceptance_criteria/notes

**Challenges**:
- Jira status changes often require workflow transitions, not direct updates
- Need to discover valid transitions via API
- Custom fields vary by Jira instance

**Usage**:
```bash
bd export | python jsonl2jira.py --create-only  # Only create, don't update
bd export | python jsonl2jira.py                 # Create and update
```

**After creation**: Sets external_ref on beads issue to link back


Issue with desc

This is a description


Review PR #512: clarify bd ready docs

Review and merge PR #512 from aspiers. This PR clarifies what bd ready does after git pull in README.md. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/512


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 import.orphan_handling config option

Add configuration option to control orphan handling behavior: 'strict' (fail on missing parent, current behavior), 'resurrect' (auto-resurrect from JSONL, recommended default), 'skip' (skip orphaned issues with warning), 'allow' (import orphans without validation). Update CONFIG.md documentation.


Cache getMultiRepoJSONLPaths() result during sync to avoid redundant calls

From bd-xo6b code review: getMultiRepoJSONLPaths() is called 3x per sync cycle.

**Current behavior:**
daemon_sync.go calls getMultiRepoJSONLPaths() three times per sync:
- Line 505: Snapshot capture before pull
- Line 575: Merge/prune after pull
- Line 613: Base snapshot update after import

**Cost per call:**
- Config lookup (likely cached, but still overhead)
- Path construction: O(N) where N = number of repos
- String allocations: (N + 1) × filepath.Join() calls

**Total per sync:** 3N path constructions + 3 config lookups + 3 slice allocations

**Impact:**
- For N=3 repos: Negligible (< 1ms)
- For N=10 repos: Still minimal
- For N=100+ repos: Wasteful

**Solution:**
Call once at sync start, reuse result:

```go
func createSyncFunc(...) func() {
    return func() {
        // ... existing setup ...

        // Call once at start
        multiRepoPaths := getMultiRepoJSONLPaths()

        // Snapshot capture
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths {
                if err := captureLeftSnapshot(path); err != nil { ... }
            }
        }

        // ... later ...

        // Merge/prune - reuse same paths
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths { ... }
        }

        // ... later ...

        // Base snapshot update - reuse same paths
        if multiRepoPaths != nil {
            for _, path := range multiRepoPaths { ... }
        }
    }
}
```

**Files:**
- cmd/bd/daemon_sync.go:449-636 (createSyncFunc)

**Note:** This is a performance optimization, not a correctness fix. Low priority unless multi-repo usage scales significantly.


Add MCP functions for repair commands

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

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


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.


Add 'bd comment' as alias for 'bd comments add'

The command 'bd comments add' is verbose and unintuitive. Add 'bd comment' as a shorter alias that works the same way.

## Rationale
- More natural: 'bd comment <issue-id> <text>' reads better than 'bd comments add <issue-id> <text>'
- Matches user expectations: users naturally try 'bd comment' first
- Follows convention: other commands like 'bd create', 'bd show', 'bd close' are verbs

## Implementation
Could be implemented as:
1. A new command that wraps bd comments add
2. An alias registered in cobra
3. Keep 'bd comments add' for backwards compatibility

## Examples
```bash
bd comment bd-1234 'This is a comment'
bd comment bd-1234 'Multi-line comment' --body 'Additional details here'
```


Makefile Integration for Benchmarks

Add a single 'bench' target to the Makefile for running performance benchmarks.

Target:
.PHONY: bench

bench:
	go test -bench=. -benchtime=3s -tags=bench \
		-cpuprofile=cpu.prof -memprofile=mem.prof \
		./internal/storage/sqlite/
	@echo ""
	@echo "Profiles generated. View flamegraph:"
	@echo "  go tool pprof -http=:8080 cpu.prof"

Features:
- Single simple target, no complexity
- Always generates CPU and memory profiles
- Clear output on how to view results
- 3 second benchmark time for reliable results
- Uses bench build tag for heavy benchmarks

Usage:
  make bench                              # Run all benchmarks
  go test -bench=BenchmarkGetReadyWork... # Run specific benchmark


Investigate jujutsu VCS as potential solution for conflict-free merging

## Context
Current N-way collision resolution struggles with Git line-based merge model. When 5+ clones create issues with same ID, Git merge conflicts require manual resolution, and our collision resolver can fail during convergence rounds.

## Research Question
Could jujutsu (jj) provide better conflict handling for JSONL files?

## Jujutsu Overview
- Next-gen VCS built on libgit2
- Designed to handle conflicts as first-class citizens
- Supports conflict-free replicated data types (CRDTs) in some scenarios
- Better handling of concurrent edits
- Can work with Git repos (compatible with existing infrastructure)

## Investigation Tasks
1. JSONL Merge Behavior - How does jj handle line-by-line JSONL conflicts?
2. Integration Feasibility - Can beads use jj as backend while maintaining Git compatibility?
3. Conflict Resolution Model - Does jj conflict model map to our collision resolution?
4. Operational Transform Support - Does jj implement operational transforms?

## Deliverables
1. Technical report on jj merge algorithm for JSONL
2. Proof-of-concept: 5-clone collision test using jj instead of Git
3. Performance comparison: Git vs jj for beads workload
4. Recommendation: Adopt, experiment further, or abandon

## References
- https://github.com/martinvonz/jj
- Related to bd-e6d71828, bd-7a2b58fc


Issue 2 to reopen


Handle unchecked errors (errcheck - 683 issues)

683 unchecked error returns, mostly in tests (Close, Rollback, RemoveAll). Many already excluded in config but still showing up.

Fixed all errcheck warnings in production code:
- Enabled errcheck linter (was disabled)
- Set tests: false in .golangci.yml to focus on production code
- Fixed 27 total errors in production code using Oracle guidance:
  * Database patterns: defer func() { _ = rows.Close() }() and defer func() { _ = tx.Rollback() }()
  * Best-effort closers: _ = store.Close(), _ = client.Close()
  * Proper error handling for file writes, fmt.Scanln(), os.Remove()
- All tests pass
- Only 2 "unused" linter warnings remain (not errcheck)


Performance Validation

Confirm no performance regression from cache removal


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.


Fix gosec lint warnings in doctor.go, main.go, and fix subdirectory

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

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


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

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

Uses embeddings by default for >100 issues.

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


Update AGENTS.md and README.md with "bd daemons" documentation

Document the new "bd daemons" command and all subcommands in AGENTS.md and README.md. Include examples and troubleshooting guidance.


Daemon crash recovery: panic handler + socket cleanup

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

This ensures better diagnostics and cleaner state after crashes.


Create Docker image for Agent Mail

Containerize Agent Mail server for easy deployment.

Acceptance Criteria:
- Dockerfile with Python 3.14
- Health check endpoint
- Volume mount for storage
- Environment variable configuration
- Multi-arch builds (amd64, arm64)

File: deployment/agent-mail/Dockerfile


Extract duplicated metadata update code in daemon_sync.go

## Problem
The same 22-line metadata update block appears identically in both:
- createExportFunc (lines 309-328)
- createSyncFunc (lines 520-539)

This violates DRY principle and makes maintenance harder.

## Solution
Extract to helper function:

```go
// updateExportMetadata updates last_import_hash and related metadata after a successful export.
// This prevents "JSONL content has changed since last import" errors on subsequent exports (bd-ymj fix).
func updateExportMetadata(ctx context.Context, store storage.Storage, jsonlPath string, log daemonLogger) {
    currentHash, err := computeJSONLHash(jsonlPath)
    if err != nil {
        log.log("Warning: failed to compute JSONL hash for metadata update: %v", err)
        return
    }

    if err := store.SetMetadata(ctx, "last_import_hash", currentHash); err != nil {
        log.log("Warning: failed to update last_import_hash: %v", err)
    }

    exportTime := time.Now().Format(time.RFC3339)
    if err := store.SetMetadata(ctx, "last_import_time", exportTime); err != nil {
        log.log("Warning: failed to update last_import_time: %v", err)
    }

    // Store mtime for fast-path optimization
    if jsonlInfo, statErr := os.Stat(jsonlPath); statErr == nil {
        mtimeStr := fmt.Sprintf("%d", jsonlInfo.ModTime().Unix())
        if err := store.SetMetadata(ctx, "last_import_mtime", mtimeStr); err != nil {
            log.log("Warning: failed to update last_import_mtime: %v", err)
        }
    }
}
```

## Files
- cmd/bd/daemon_sync.go

## Benefits
- Easier maintenance
- Single source of truth
- Consistent behavior


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


Add sync.branch configuration

Add configuration layer to support sync.branch setting via config file, environment variable, or CLI flag.

Tasks:
- Add sync.branch field to config schema
- Add BEADS_SYNC_BRANCH environment variable
- Add --branch flag to bd init
- Add bd config get/set sync.branch commands
- Validation (branch name format, conflicts)
- Config migration for existing users

Estimated effort: 1-2 days


Add bd reset command for clean slate restart

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

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

## Design

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

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

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

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

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

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


Integrate beads-merge tool by @neongreen

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

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

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

**Integration options**:

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

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

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

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

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

Next: Wait for response before proceeding with integration.

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

Next: Vendor beads-merge with full attribution


Set up WASM build pipeline

Configure Go→WASM compilation pipeline. Child of epic bd-44d0.

## Tasks
- [ ] Create build-wasm.sh script (GOOS=js GOARCH=wasm)
- [ ] Test basic WASM module loading in Node.js
- [ ] Set up wasm_exec.js wrapper
- [ ] Add WASM build to CI/CD
- [ ] Document build process

## Validation
- bd.wasm compiles successfully
- Can load in Node.js without errors
- Bundle size <10MB


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


bd message: Add input validation for --importance flag

The --importance flag accepts any string without validation, leading to confusing server errors.

**Location:** cmd/bd/message.go:256-258

**Fix:**
- Add flag validation for: low, normal, high, urgent
- Add shell completion support
- Validate in runMessageSend before sending

**Impact:** Better UX, prevents confusing errors


Need --no-git-history flag to disable git history backfill during import

During JSONL migration (beads.jsonl → issues.jsonl), the git history backfill mechanism causes data loss by finding issues in the old beads.jsonl git history and incorrectly treating them as deleted.

A --no-git-history flag for 'bd import' and 'bd sync' would allow users to disable the git history fallback when it's causing problems.

Use cases:
- JSONL filename migrations
- Repos with complex git history
- Debugging import issues
- Performance (skip slow git scans)

Related: bd-0gh (migration causes spurious deletions)


Implement content-first idempotent import

## Overview
Phase 4: Refactor import to be content-first and idempotent, ensuring importing same JSONL multiple times always converges correctly.

## Current Problem
Current import is ID-first:
1. Look up by ID
2. If exists, update
3. If not exists, create

This causes issues when:
- Same content arrives with different IDs (renames not detected)
- Multiple rounds of import needed for convergence
- Import order affects final state

## Solution
Make import content-first and idempotent:
1. Hash all incoming and existing issues
2. Match by content hash first (detect renames)
3. Handle ID conflicts second (using global resolution)
4. Ensure importing same data multiple times = no-op

## Implementation Tasks

### 1. Refactor ImportIssues to be content-first
File: internal/importer/importer.go

```go
func ImportIssues(ctx context.Context, dbPath string, store storage.Storage,
    issues []*types.Issue, opts Options) (*Result, error) {

    result := &Result{...}

    sqliteStore, needCloseStore, err := getOrCreateStore(ctx, dbPath, store)
    if err != nil {
        return nil, err
    }
    if needCloseStore {
        defer func() { _ = sqliteStore.Close() }()
    }

    // Phase 1: Compute content hashes for all incoming issues
    for _, issue := range issues {
        issue.ContentHash = issue.ComputeContentHash()
    }

    // Phase 2: Build content hash maps
    incomingByHash := buildHashMap(issues)
    dbIssues, _ := sqliteStore.SearchIssues(ctx, "", types.IssueFilter{})
    dbByHash := buildHashMap(dbIssues)
    dbByID := buildIDMap(dbIssues)

    // Phase 3: Content-first matching
    var newIssues []*types.Issue
    var idConflicts []*CollisionDetail

    for hash, incoming := range incomingByHash {
        if existing, found := dbByHash[hash]; found {
            // Same content exists
            if existing.ID == incoming.ID {
                // Exact match - idempotent case
                result.Unchanged++
            } else {
                // Same content, different ID - rename detected
                // Delete old ID, keep new ID (incoming is canonical)
                if err := handleRename(ctx, sqliteStore, existing, incoming); err != nil {
                    return nil, err
                }
                result.Updated++
            }
        } else {
            // New content - check for ID collision
            if existingWithID, found := dbByID[incoming.ID]; found {
                // ID exists but different content - collision
                idConflicts = append(idConflicts, &CollisionDetail{
                    ID:            incoming.ID,
                    IncomingIssue: incoming,
                    ExistingIssue: existingWithID,
                })
            } else {
                // Truly new issue
                newIssues = append(newIssues, incoming)
            }
        }
    }

    // Phase 4: Resolve ID conflicts using global algorithm
    if len(idConflicts) > 0 {
        if !opts.ResolveCollisions {
            return nil, fmt.Errorf("collision detected")
        }

        idMapping, err := sqlite.ResolveNWayCollisions(ctx, sqliteStore,
            idConflicts, issues)
        if err != nil {
            return nil, err
        }

        if err := applyIDMapping(ctx, sqliteStore, idMapping); err != nil {
            return nil, err
        }

        result.IDMapping = idMapping
        result.Collisions = len(idConflicts)
    }

    // Phase 5: Create new issues
    if len(newIssues) > 0 {
        if err := sqliteStore.CreateIssues(ctx, newIssues, "import"); err != nil {
            return nil, err
        }
        result.Created = len(newIssues)
    }

    // Phase 6: Import dependencies, labels, comments (existing logic)
    // ...

    return result, nil
}
```

### 2. Implement helper functions

```go
// buildHashMap creates a map of content hash → issue
func buildHashMap(issues []*types.Issue) map[string]*types.Issue {
    result := make(map[string]*types.Issue)
    for _, issue := range issues {
        result[issue.ContentHash] = issue
    }
    return result
}

// buildIDMap creates a map of ID → issue
func buildIDMap(issues []*types.Issue) map[string]*types.Issue {
    result := make(map[string]*types.Issue)
    for _, issue := range issues {
        result[issue.ID] = issue
    }
    return result
}

// handleRename handles content match with different IDs
func handleRename(ctx context.Context, s *SQLiteStorage,
    existing *types.Issue, incoming *types.Issue) error {

    // Delete old ID
    if err := s.DeleteIssue(ctx, existing.ID); err != nil {
        return fmt.Errorf("failed to delete old ID %s: %w", existing.ID, err)
    }

    // Create with new ID
    if err := s.CreateIssue(ctx, incoming, "import-rename"); err != nil {
        return fmt.Errorf("failed to create renamed issue %s: %w",
            incoming.ID, err)
    }

    // Update references from old ID to new ID
    idMapping := map[string]string{existing.ID: incoming.ID}
    return updateReferences(ctx, s, idMapping)
}
```

### 3. Add idempotency tests

Test cases:
1. Import same JSONL twice → second import reports all Unchanged
2. Import, modify DB, import again → reports Updated
3. Import with rename, import again → idempotent
4. Import with collision resolution, import again → idempotent

### 4. Update handleCollisions to use new flow
Current handleCollisions in importer.go needs to be updated to:
- Use content-first matching
- Call new ResolveNWayCollisions
- Apply results using ApplyCollisionResolution

## Acceptance Criteria
- Import matches by content hash before checking IDs
- Importing same JSONL multiple times is idempotent (reports Unchanged)
- Rename detection works (same content, different ID)
- ID conflicts resolved using global algorithm
- Result.Unchanged correctly tracks idempotent imports
- TestThreeCloneCollision passes
- All existing import tests still pass

## Testing Strategy

### Unit Tests
- buildHashMap correctly indexes by content hash
- buildIDMap correctly indexes by ID
- handleRename deletes old, creates new, updates references

### Integration Tests
- Import same data twice → idempotent
- Import renamed issue → handled correctly
- Import with collision → resolved globally
- Final pull after 3-way collision → all clones converge

### Property Tests
- Idempotency: Import(x); Import(x) ≡ Import(x)
- Commutativity: Import(a); Import(b) ≡ Import(b); Import(a) (for non-colliding issues)
- Convergence: After N rounds of sync, all clones identical

## Files to Modify
- internal/importer/importer.go (major refactor of ImportIssues)
- internal/importer/importer_test.go (new tests)
- cmd/bd/import_bug_test.go (update for new behavior)

## Dependencies
- Requires bd-cbed9619.5 (ContentHash field)
- Requires bd-cbed9619.4 (read-only detection)
- Requires bd-cbed9619.3 (global resolution)

## Risk Mitigation
Major refactor of import logic. Recommend:
1. Comprehensive tests before modifying
2. Feature flag to enable/disable
3. Keep old import code path for rollback
4. Test with all existing import tests
5. Manual testing with real repositories

## Success Metrics
After this phase:
- TestThreeCloneCollision should PASS
- All clones converge after final pull
- Import is demonstrably idempotent
- No data loss in N-way scenarios


bd doctor --fix needs non-interactive mode (-y/--yes flag)

When running `bd doctor --fix` in non-interactive mode (scripts, CI, Claude Code), it prompts 'Continue? (Y/n):' and fails with EOF.

**Expected**: A `-y` or `--yes` flag to auto-confirm fixes.

**Workaround**: Currently have to run `bd init` instead, but that's not discoverable from the doctor output.


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


GH#519: bd sync fails when sync.branch equals current branch

bd sync tries to create worktree when sync.branch matches current checkout, fails with 'already used by worktree'. Should detect and commit directly. See: https://github.com/steveyegge/beads/issues/519


Add fsnotify dependency to go.mod


Audit remaining cmd/bd files for error handling consistency

Extend ERROR_HANDLING_AUDIT.md to cover: daemon_sync.go, update.go, list.go, show.go, close.go, reopen.go, dep.go, label.go, comments.go, delete.go, compact.go, config.go, validate.go and other high-usage command files


Git history fallback has incorrect logic for detecting deletions

## Problem

The `wasInGitHistory` function in `internal/importer/importer.go:891` returns true if the ID is found **anywhere** in git history. But finding an ID in history doesn't necessarily mean it was deleted - it could mean:

1. The issue was added (appears in a commit adding it)
2. The issue was modified (appears in commits updating it)
3. The issue was deleted (appears in a commit removing it)

The current logic incorrectly treats all three cases as 'deleted'.

## Correct Logic

`git log -S` with `--oneline` shows commits where the string was added OR removed. To detect deletion specifically:

1. ID appears in git history (was once in JSONL)
2. ID is NOT currently in JSONL

The second condition is already checked by the caller (`purgeDeletedIssues`), so technically the logic is correct in context. But the function name and doc comment are misleading.

## Fix Options

1. **Rename function** to `wasEverInJSONL` and update doc comment to clarify
2. **Add explicit check** for current JSONL state in the function itself

Option 1 is simpler and correct since caller already filters.


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


Test integration issue

This is a real integration test


Refactor path canonicalization into helper function

The path canonicalization logic (filepath.Abs + EvalSymlinks) is duplicated in 3 places:
- beads.go:131-137 (BEADS_DIR handling)
- cmd/bd/main.go:446-451 (--no-db cleanup)
- cmd/bd/nodb.go:26-31 (--no-db initialization)

Refactoring suggestion:
Extract to a helper function like:
  func canonicalizePath(path string) string

This would:
- Reduce code duplication
- Make the logic easier to maintain
- Ensure consistent behavior across all path handling

Related to bd-e16b implementation.


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.


Upgrade notification persists after running suggested commands

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

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

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

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

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


Add tests for performFlush error handling in FlushManager

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

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

In cmd/bd/flush_manager.go:269, the performFlush method is flagged by unparam as always returning nil, indicating the error return value is never used.

Add tests to determine:
- Whether performFlush can actually return errors in failure scenarios
- If error return is needed, add tests for error cases (disk full, permission denied, etc.)
- If error return is not needed, refactor to remove unused return value
- Test full export vs incremental export error handling

This ensures proper error handling in the flush mechanism and removes dead code if the return value is unnecessary.

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


Test parent issue


Add tests for 3-way merge scenarios

Comprehensive test coverage for merge logic.

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

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


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.


Issue to close


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


AI-supervised database migrations for safer schema evolution

## Problem

Database migrations can lose user data through edge cases that are hard to anticipate (e.g., GH #201 where bd migrate failed to set issue_prefix, or bd-d355a07d false positive data loss warnings). Since beads is designed to be run by AI agents, we should leverage AI to make migrations safer.

## Current State

Migrations run blindly with:
- No pre-flight validation
- No data integrity verification
- No rollback on failure
- Limited post-migration testing

Recent issues:
- GH #201: Migration didn't set issue_prefix config, breaking commands
- bd-d355a07d: False positive "data loss" warnings on collision resolution
- Users reported migration data loss (fixed but broader problem remains)

## Proposal: AI-Supervised Migration Framework

Use AI to supervise migrations through structured verification:

### 1. Pre-Migration Analysis
- AI reads migration code and current schema
- Identifies potential data loss scenarios
- Generates validation queries to verify assumptions
- Creates snapshot queries for before/after comparison

### 2. Migration Execution
- Take database backup/snapshot
- Run validation queries (pre-state)
- Execute migration in transaction
- Run validation queries (post-state)

### 3. Post-Migration Verification
- AI compares pre/post snapshots
- Verifies data integrity invariants
- Checks for unexpected data loss
- Validates config completeness (like issue_prefix)

### 4. Rollback on Anomalies
- If AI detects data loss, rollback transaction
- Present human-readable error report
- Suggest fix before retrying

## Example Flow

```
$ bd migrate

→ Analyzing migration plan...
→ AI identified 3 potential data loss scenarios
→ Generating validation queries...
→ Creating pre-migration snapshot...
→ Running migration in transaction...
→ Verifying post-migration state...
✓ All 247 issues accounted for
✓ Config table complete (issue_prefix: "mcp")
✓ Dependencies intact (342 relationships verified)
→ Migration successful!
```

If something goes wrong:
```
$ bd migrate

→ Analyzing migration plan...
→ AI identified issue: Missing issue_prefix config after migration
→ Recommendation: Add prefix detection step
→ Aborting migration - database unchanged
```

## Implementation Ideas

### A. Migration Validator Tool
Create `bd migrate --validate` that:
- Simulates migration on copy of database
- Uses AI to verify data integrity
- Reports potential issues before real migration

### B. Migration Test Generator
AI generates test cases for migrations:
- Edge cases (empty DB, large DB, missing config)
- Data integrity checks
- Regression tests

### C. Migration Invariants
Define invariants that AI checks:
- Issue count should not decrease (unless collision resolution)
- All required config keys present
- Foreign key relationships intact
- No orphaned dependencies

### D. Self-Healing Migrations
AI detects incomplete migrations and suggests fixes:
- Missing config values (like GH #201)
- Orphaned data
- Index inconsistencies

## Benefits

1. **Catch edge cases**: AI explores scenarios humans miss
2. **Self-documenting**: AI explains what migration does
3. **Agent-friendly**: Agents can run migrations confidently
4. **Fewer rollbacks**: Detect issues before committing
5. **Better testing**: AI generates comprehensive test suites

## Open Questions

1. Which AI model? (Fast: Haiku, Thorough: Sonnet/GPT-4)
2. How to balance safety vs migration speed?
3. Should AI validation be required or optional?
4. How to handle offline scenarios (no API access)?
5. What invariants should always be checked?

## Related Work

- bd-b245: Migration registry (makes migrations introspectable)
- GH #201: issue_prefix migration bug (motivating example)
- bd-d355a07d: False positive data loss warnings

## Progress

### ✅ Phase 1: Migration Invariants (COMPLETED)

**Implemented:**
- Created internal/storage/sqlite/migration_invariants.go with 3 invariants
- Updated RunMigrations() to verify invariants after migrations
- All tests pass ✓

### ✅ Phase 2: Inspection Tools (COMPLETED & PUSHED)

**Commit:** 1abe4e7 - "Add migration inspection tools for AI agents (bd-627d Phase 2)"

**Implemented:**
1. ✅ bd migrate --inspect --json - Shows migration plan
2. ✅ bd info --schema --json - Returns schema details
3. ✅ Migration warnings system
4. ✅ Documentation updated in AGENTS.md
5. ✅ All tests pass

### ✅ Phase 3: MCP Tools (COMPLETED & PUSHED)

**Commit:** 2493693 - "Add MCP tools for migration inspection (bd-627d Phase 3)"

**Implemented:**
1. ✅ inspect_migration(workspace_root) tool in beads-mcp
2. ✅ get_schema_info(workspace_root) tool in beads-mcp
3. ✅ Abstract methods in BdClientBase
4. ✅ CLI client implementations
5. ✅ All tests pass

**All phases complete!** Migration inspection fully integrated into MCP server.


Latency test 1

Resetting stale in_progress status from old executor run (yesterday)


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 simple cleanup command to delete closed issues

Users want a simple command to delete all closed issues without requiring Anthropic API key (unlike compact). Requested in GH #243.


Audit Current Cache Usage

Understand exactly what code depends on the storage cache


False positive detection by Kaspersky Antivirus (Trojan)

Kaspersky Antivirus falsely detects beads (bd.exe v0.23.1) as a Trojan (PDM:Trojan.Win32.Generic) and removes it.
Event: Malicious object detected
Component: System Watcher
Object name: bd.exe


Add comprehensive filters to bd export

Enhance bd export with filtering options for selective exports.

**Currently only has:**
- --status

**Add filters:**
- --label, --label-any
- --assignee
- --type
- --priority, --priority-min, --priority-max
- --created-after, --created-before
- --updated-after, --updated-before

**Use case:**
- Export only open issues: bd export --status open
- Export high-priority bugs: bd export --type bug --priority-max 1
- Export recent issues: bd export --created-after 2025-01-01

**Files to modify:**
- cmd/bd/export.go
- Reuse filter logic from list.go


Test dependency parent


Extract daemon config functions to daemon_config.go


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.


Implement auto-merge functionality in duplicates command

The duplicates.go file has a TODO at line 95 to implement the performMerge function for automatic duplicate merging. Currently it just prints a warning message. This would automate the merge process instead of just suggesting commands.


Test simple issue


Merge PR #300: gitignore upgrade feature

PR #300 is ready to merge but has rebase conflicts with main.

**Context:**
- PR implements 3 mechanisms for .beads/.gitignore upgrade (bd doctor --fix, daemon auto-upgrade, bd init idempotent)
- Conflicts resolved locally but diverged branches make push difficult
- All fixes applied: removed merge artifact, applied new gitignore template
- Clean scope: only 6 files changed (+194/-42)

**Next steps:**
1. Option A: Merge via GitHub UI (resolve conflicts in web interface)
2. Option B: Fresh rebase on main and force push
3. Verify CI passes
4. Squash and merge

PR URL: https://github.com/steveyegge/beads/pull/300
Fixes: #274


bd init UX Improvements

bd init leaves users with incomplete setup, requiring manual bd doctor --fix. Issues found: (1) git hooks not installed if user declines prompt, (2) no auto-migration when CLI is upgraded, (3) stale merge driver configs from old versions. Fix by making bd init more robust with better defaults and auto-migration.


GH#508: Orphan detection false positive when directory name contains dot

Directory names with dots trigger orphan issue detection because dot is also used for orphan ID format. bd rename-prefix fixes it. See: https://github.com/steveyegge/beads/issues/508


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


Update docs for import bug fix

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


Add integration tests for multi-repo sync

Test: Clone A deletes issue, Clone B imports Clone A's JSONL. Verify Clone B handles deletion gracefully with resurrection. Test concurrent imports with same orphans (should be idempotent). Test round-trip fidelity (export→delete parent→import→verify structure).


Widespread double JSON encoding bug in daemon mode RPC calls

Multiple CLI commands had the same double JSON encoding bug found in bd-1048. All commands that called ResolveID via RPC used string(resp.Data) instead of properly unmarshaling the JSON response. This caused IDs to retain JSON quotes ("bd-1048" instead of bd-1048), which then got double-encoded when passed to subsequent RPC calls.

Affected commands:
- bd show (3 instances)
- bd dep add/remove/tree (5 instances)
- bd label add/remove/list (3 instances)
- bd reopen (1 instance)

Root cause: resp.Data is json.RawMessage (already JSON-encoded), so string() conversion preserves quotes.

Fix: Replace all string(resp.Data) with json.Unmarshal(resp.Data, &id) for proper deserialization.

All commands now tested and working correctly with daemon mode.


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)


Add test coverage for internal/config package


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


Git history fallback assumes .beads is direct child of repo root

## Problem

`checkGitHistoryForDeletions` assumes the repo structure:

```go
repoRoot := filepath.Dir(beadsDir)  // Assumes .beads is in repo root
jsonlPath := filepath.Join(".beads", "beads.jsonl")
```

But `.beads` could be in a subdirectory (monorepo, nested project), and the actual JSONL filename could be different (configured via `metadata.json`).

## Location
`internal/importer/importer.go:865-869`

## Impact
- Git search will fail silently for repos with non-standard structure
- Monorepo users won't get deletion propagation

## Fix
1. Use `git rev-parse --show-toplevel` to find actual repo root
2. Compute relative path from repo root to JSONL
3. Or use `git -C <dir>` to run from beadsDir directly


Document sync.require_confirmation_on_mass_delete config option


Documentation

Complete documentation for Agent Mail integration to enable adoption.