Issue 1 to reopen


bd-hv01: Silent partial deletion failures cause DB inconsistency

Problem: deletion_tracking.go:76-77 logs deletion errors as warnings but continues. If deletion fails midway (database locked, disk full), some issues delete but others don't. System thinks all deletions succeeded.

Impact: Database diverges from JSONL, silent corruption, issues may resurrect on next sync.

Fix: Collect errors and fail the operation:
var deletionErrors []error
for _, id := range acceptedDeletions {
    if err := d.DeleteIssue(ctx, id); err != nil {
        deletionErrors = append(deletionErrors, fmt.Errorf("issue %s: %w", id, err))
    }
}
if len(deletionErrors) > 0 {
    return false, fmt.Errorf("deletion failures: %v", deletionErrors)
}

Files: cmd/bd/deletion_tracking.go:73-82


Test numeric 1


Add bd doctor --fix flag to automatically repair issues

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

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

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


Document protected branch workflow

Create comprehensive documentation for protected branch workflow.

Tasks:
- Add "Protected Branch Workflow" section to AGENTS.md
- Create docs/PROTECTED_BRANCHES.md guide
- Update README.md quick start
- Add examples to examples/protected-branch/
- Update bd init --help documentation
- Add troubleshooting guide
- Add migration guide for existing users
- Record demo video (optional)

Estimated effort: 2-3 days

Completed protected branch workflow documentation. Created comprehensive guide (docs/PROTECTED_BRANCHES.md), updated AGENTS.md with workflow section, added feature to README.md, and created working example (examples/protected-branch/). All commands verified working (bd init --branch, bd sync --status, bd sync --merge, bd config get/set sync.branch).


Issue 2 to reopen


Post-merge hook silently fails on JSONL conflicts, poor UX

When git pull results in merge conflicts in .beads/issues.jsonl, the post-merge hook runs 'bd sync --import-only' which fails, but stderr was redirected to /dev/null. User only saw generic warning.

Fixed by capturing and displaying the actual error output, so users see 'Git conflict markers detected' message immediately.


Clarify .beads/.gitattributes handling in Protected Branches docs

Protected Branches docs quick start leaves untracked `.beads` directory and `.gitattributes`.
Question: Are these changes meant to be checked into the protected branch?
Need to clarify if these should be ignored or committed, or if the instructions are missing a step.


Speed up main_test.go - tests hang indefinitely due to nil rootCtx

## Problem

main_test.go tests are hanging indefinitely, making them unusable and blocking development.

## Root Cause

Tests that call flushToJSONL() or autoImportIfNewer() hang forever because:
- rootCtx is nil in test environment (defined in cmd/bd/main.go:67)
- flushToJSONL() uses rootCtx for DB operations (autoflush.go:503)
- When rootCtx is nil, DB calls timeout/hang indefinitely

## Affected Tests (10+)

- TestAutoFlushOnExit
- TestAutoFlushJSONLContent
- TestAutoFlushErrorHandling
- TestAutoImportIfNewer
- TestAutoImportDisabled
- TestAutoImportWithUpdate
- TestAutoImportNoUpdate
- TestAutoImportMergeConflict
- TestAutoImportConflictMarkerFalsePositive
- TestAutoImportClosedAtInvariant

## Proof

```bash
# Before fix: TestAutoFlushJSONLContent HANGS (killed after 2+ min)
# After adding rootCtx init: Completes in 0.04s
# Speedup: ∞ → 0.04s
```

## Solution

See MAIN_TEST_OPTIMIZATION_PLAN.md for complete fix plan.

**Quick Fix (5 min)**: Add to each hanging test:
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
oldRootCtx := rootCtx
rootCtx = ctx
defer func() { rootCtx = oldRootCtx }()
```

**Full Optimization (40 min total)**:
1. Fix rootCtx (5 min) - unblocks tests
2. Reduce sleep durations 10x (2 min) - saves ~280ms
3. Use in-memory DBs (10 min) - saves ~1s
4. Share test fixtures (15 min) - saves ~1.2s
5. Fix skipped debounce test (5 min)

## Expected Results

- Tests go from hanging → <5s total runtime
- Keep critical integration test coverage
- Tests caught real bugs: bd-270, bd-206, bd-160

## Files

- Analysis: MAIN_TEST_REFACTOR_NOTES.md
- Solution: MAIN_TEST_OPTIMIZATION_PLAN.md
- Tests: cmd/bd/main_test.go (18 tests, 1322 LOC)

## Priority

P1 - Blocking development, tests unusable in current state


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


Rapid 1


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


Remove unreachable utility functions

Several small utility functions are unreachable:

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

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

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

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

Impact: Removes 50-100 LOC depending on decisions


Add global verbosity flags (--verbose, --quiet)

Add consistent verbosity controls across all commands.

**Current state:**
- bd init has --quiet flag
- No other commands have verbosity controls
- Debug output controlled by BD_VERBOSE env var

**Proposal:**
Add persistent flags:
- --verbose / -v: Enable debug output
- --quiet / -q: Suppress non-essential output

**Implementation:**
- Add to rootCmd.PersistentFlags()
- Replace BD_VERBOSE checks with flag checks
- Standardize output levels:
  * Quiet: Errors only
  * Normal: Errors + success messages
  * Verbose: Errors + success + debug info

**Files to modify:**
- cmd/bd/main.go (add flags)
- internal/debug/debug.go (respect flags)
- Update all commands to respect quiet mode

**Testing:**
- Verify --verbose shows debug output
- Verify --quiet suppresses normal output
- Ensure errors always show regardless of mode


wish: list & ready show issues as hierarchy tree

`bd ready` and `bd list` just show a flat list, and it's up to the reader to parse which ones are dependent or sub-issues of others. It would be much easier to understand if they were shown in a tree format


Proposal workflow (propose/withdraw/accept)

Implement commands and state machine for moving issues between personal planning repos and canonical upstream repos, enabling contributors to propose work without polluting PRs.


Code Review Followup Summary: PR #481 + PR #551

## Merged PRs Summary

### PR #551: Persist close_reason to issues table
- ✅ Merged successfully
- ✅ Bug fix: close_reason now persisted in database column (not just events table)
- ✅ Comprehensive test coverage added
- ✅ Handles reopen case (clearing close_reason)

**Followup Issues Filed:**
- bd-lxzx: Document close_reason in JSONL export format
- bd-077e: Update CLI documentation for close_reason field

---

### PR #481: Context Engineering Optimizations (80-90% context reduction)
- ✅ Merged successfully
- ✅ Lazy tool discovery: discover_tools() + get_tool_info()
- ✅ Minimal issue models: IssueMinimal (~80% smaller than full Issue)
- ✅ Result compaction: Auto-compacts results >20 items
- ✅ All 28 tests passing
- ⚠ Breaking change: ready() and list() return type changed

**Followup Issues Filed:**
- bd-b318: Add integration tests for CompactedResult
- bd-4u2b: Make compaction settings configurable (THRESHOLD, PREVIEW_COUNT)
- bd-2kf8: Document CompactedResult response format in CONTEXT_ENGINEERING.md
- bd-pdr2: Document backwards compatibility considerations

---

## Overall Assessment

Both PRs are production-ready with solid implementations. All critical functionality works and tests pass. Followup issues focus on:
1. Documentation improvements (5 issues)
2. Integration test coverage (1 issue)
3. Configuration flexibility (1 issue)
4. Backwards compatibility guidance (1 issue)

No critical bugs or design issues found.

## Review Completed By
Code review process completed. Issues auto-created for tracking improvements.


Support Ubuntu 20.04 LTS (glibc compatibility issue)

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

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

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

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

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

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

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


Migration tool for existing users

Ensure smooth migration for existing users to separate branch workflow.

Tasks:
- Add bd migrate --separate-branch command
- Detect existing repos, migrate cleanly
- Preserve git history
- Add rollback mechanism
- Test migration on beads' own repo (dogfooding)
- Communication plan (GitHub discussion, docs)
- Version compatibility checks

Estimated effort: 2-3 days


Add tests for Comments API (AddIssueComment, GetIssueComments)

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


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


Test: N-clone scenario with hash IDs (no collisions)

Comprehensive test to verify hash IDs eliminate collision problems.

## Test: TestHashIDsNClones

### Purpose
Verify that N clones can work offline and sync without ID collisions using hash IDs.

### Test Scenario
```
Setup:
- 1 bare remote repo
- 5 clones (A, B, C, D, E)

Offline Work:
- Each clone creates 10 issues with different titles
- No coordination, no network access
- Total: 50 unique issues

Sync:
- Clones sync in random order
- Each pull/import other clones' issues

Expected Result:
- All 5 clones converge to 50 issues
- Zero ID collisions
- Zero remapping needed
- Alias conflicts resolved deterministically
```

### Implementation
File: cmd/bd/beads_hashid_test.go (new)

```go
func TestHashIDsFiveClones(t *testing.T) {
    tmpDir := t.TempDir()
    remoteDir := setupBareRepo(t, tmpDir)

    // Setup 5 clones
    clones := make(map[string]string)
    for _, name := range []string{"A", "B", "C", "D", "E"} {
        clones[name] = setupClone(t, tmpDir, remoteDir, name)
    }

    // Each clone creates 10 issues offline
    for name, dir := range clones {
        for i := 0; i < 10; i++ {
            createIssue(t, dir, fmt.Sprintf("%s-issue-%d", name, i))
        }
        // No sync yet!
    }

    // Sync in random order
    syncOrder := []string{"C", "A", "E", "B", "D"}
    for _, name := range syncOrder {
        syncClone(t, clones[name], name)
    }

    // Final convergence round
    for _, name := range []string{"A", "B", "C", "D", "E"} {
        finalPull(t, clones[name], name)
    }

    // Verify all clones have all 50 issues
    for name, dir := range clones {
        issues := getIssues(t, dir)
        if len(issues) != 50 {
            t.Errorf("Clone %s: expected 50 issues, got %d", name, len(issues))
        }

        // Verify all issue IDs are hash-based
        for _, issue := range issues {
            if !strings.HasPrefix(issue.ID, "bd-") || len(issue.ID) != 11 {
                t.Errorf("Invalid hash ID: %s", issue.ID)
            }
        }
    }

    // Verify no collision resolution occurred
    // (This would be in logs if it happened)

    t.Log("✓ All 5 clones converged to 50 issues with zero collisions")
}
```

### Edge Case Tests

#### Test: Hash Collision Detection (Artificial)
```go
func TestHashCollisionDetection(t *testing.T) {
    // Artificially inject collision by mocking hash function
    // Verify system detects and handles it
}
```

#### Test: Alias Conflicts Resolved Deterministically
```go
func TestAliasConflictsNClones(t *testing.T) {
    // Two clones assign same alias to different issues
    // Verify deterministic resolution (content-hash ordering)
    // Verify all clones converge to same alias assignments
}
```

#### Test: Mixed Sequential and Hash IDs (Should Fail)
```go
func TestMixedIDsRejected(t *testing.T) {
    // Try to import JSONL with sequential IDs into hash-ID database
    // Verify error or warning
}
```

### Performance Test

#### Benchmark: Hash ID Generation
```go
func BenchmarkHashIDGeneration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        GenerateHashID("title", "description", time.Now(), "workspace-id")
    }
}

// Expected: < 1μs per generation
```

#### Benchmark: N-Clone Convergence Time
```go
func BenchmarkNCloneConvergence(b *testing.B) {
    for _, n := range []int{3, 5, 10, 20} {
        b.Run(fmt.Sprintf("N=%d", n), func(b *testing.B) {
            // Measure total convergence time
        })
    }
}

// Expected: Linear scaling O(N)
```

### Acceptance Criteria
- TestHashIDsFiveClones passes reliably (10/10 runs)
- Zero ID collisions in any scenario
- All clones converge in single round (not multi-round like old system)
- Alias conflicts resolved deterministically
- Performance benchmarks meet targets (<1μs hash gen)

## Files to Create
- cmd/bd/beads_hashid_test.go

## Comparison to Old System
This test replaces:
- TestTwoCloneCollision (bd-71107098) - no longer needed
- TestThreeCloneCollision (bd-cbed9619) - no longer needed
- TestFiveCloneCollision (bd-a40f374f) - no longer needed

Old system required complex collision resolution and multi-round convergence.
New system: single-round convergence with zero collisions.


Audit Current Cache Usage

Understand exactly what code depends on the storage cache


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.


Latency test 1


Add Agent Mail to QUICKSTART.md

Mention Agent Mail as optional advanced feature in quickstart guide.

File: docs/QUICKSTART.md


fix: bd onboard hangs on Windows


Auto-invoke 3-way merge for JSONL conflicts

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

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

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

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

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


Fix checkExistingBeadsData to be worktree-aware

The checkExistingBeadsData function in cmd/bd/init.go checks for .beads in the current working directory, but for worktrees it should check the main repository root instead. This prevents proper worktree compatibility.


Implement transaction retry logic for SQLITE_BUSY

BEGIN IMMEDIATE fails immediately on SQLITE_BUSY instead of retrying with exponential backoff.

Location: internal/storage/sqlite/sqlite.go:223-225

Problem:
- Under concurrent write load, BEGIN IMMEDIATE can fail with SQLITE_BUSY
- Current implementation fails immediately instead of retrying
- Results in spurious failures under normal concurrent usage

Solution: Implement exponential backoff retry:
- Retry up to N times (e.g., 5)
- Backoff: 10ms, 20ms, 40ms, 80ms, 160ms
- Check for context cancellation between retries
- Only retry on SQLITE_BUSY/database locked errors

Impact: Spurious failures under concurrent write load

Effort: 3 hours


Test child issue


Create Go adapter library (optional)

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

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

File: pkg/agentmail/client.go

Note: Lower priority - can shell out to curl initially


Create cmd/bd/daemon_debouncer.go (~60 LOC)

Implement Debouncer to batch rapid events into single action. Default 500ms, configurable via BEADS_DEBOUNCE_MS. Thread-safe with mutex.


Daemon performAutoImport should update jsonl_content_hash after import

The daemon's performAutoImport function was not updating jsonl_content_hash after successful import, unlike the CLI import path. This could cause repeated imports if the file hash and DB hash diverge.

Fix: Added hash update after validatePostImport succeeds, using repoKey for multi-repo support.


bd cleanup fails with CHECK constraint on status/closed_at mismatch

## Problem

Running bd cleanup --force fails with:

Error: failed to create tombstone for bd-okh: sqlite3: constraint failed: CHECK constraint failed: (status = 'closed') = (closed_at IS NOT NULL)

## Root Cause

The database has a CHECK constraint ensuring closed issues have closed_at set and non-closed issues do not. When bd cleanup tries to convert a closed issue to a tombstone, this constraint fails.

## Impact

- bd cleanup --force cannot complete
- 722 closed issues cannot be cleaned up
- Blocks routine maintenance

## Investigation Needed

1. Check the bd-okh record in the database
2. Determine if this is data corruption or a bug in tombstone creation

## Proposed Solutions

1. If data corruption: Add a bd doctor --fix check that repairs status/closed_at mismatches
2. If code bug: Fix the tombstone creation to properly handle the constraint

## Files to Investigate

- internal/storage/sqlite/sqlite.go - DeleteIssue / tombstone creation
- Schema CHECK constraint definition

## Acceptance Criteria

- Identify root cause (data vs code bug)
- bd cleanup --force completes successfully
- bd doctor detects status/closed_at mismatches


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

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

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

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


Add close_reason to JSONL export format documentation

PR #551 now persists close_reason to the database, but there's a question about whether this field should be exported to JSONL format.

## Current State
- close_reason is stored in issues.close_reason column
- close_reason is also stored in events table (audit trail)
- The JSONL export format may or may not include close_reason

## Questions
1. Should close_reason be exported to JSONL format?
2. If yes, where should it go (root level or nested in events)?
3. Should there be any special handling to avoid duplication?
4. How should close_reason be handled during JSONL import?

## Why This Matters
- JSONL is the git-friendly sync format
- Other beads instances import from JSONL
- close_reason is meaningful data that should be preserved across clones

## Suggested Action
- Check if close_reason is currently exported in JSONL
- If not, add it to the export schema
- Document the field in JSONL format spec
- Add tests for round-trip (export -> import -> verify close_reason)


GH#245: Windows MCP subprocess timeout for git rev-parse

User reports git detection timing out on Windows in MCP server, but CLI works fine.

Path: C:\Users\chris\Documents\DEV_R\quarto-cli
Error: Git repository detection timed out after 5s
Works fine in CLI: `git rev-parse --show-toplevel` succeeds

Hypothesis: subprocess.run() with asyncio.to_thread() may have Windows-specific issues or the MCP runtime environment may not have proper PATH/git access.

Potential fixes:
1. Add subprocess shell=True on Windows
2. Increase timeout further for Windows
3. Add better error logging to capture subprocess stderr
4. Skip git resolution entirely on timeout and just use provided path


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


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


Use TryResurrectParentChain instead of TryResurrectParent in GetNextChildID

## Context
Current bd-dvd fix uses TryResurrectParent, which only resurrects the immediate parent. For deeply nested hierarchies, this might not be sufficient.

## Example Scenario
Creating child with parent "bd-abc.1.2" where:
- bd-abc exists in DB
- bd-abc.1 was deleted (missing from DB, exists in JSONL)
- bd-abc.1.2 was deleted (missing from DB, exists in JSONL)

Current fix: Only tries to resurrect "bd-abc.1.2", fails because its parent "bd-abc.1" is missing.

## Solution
Replace TryResurrectParent with TryResurrectParentChain:

```go
if count == 0 {
    // Try to resurrect entire parent chain from JSONL history (bd-dvd fix)
    // This handles deeply nested hierarchies where intermediate parents are also missing
    resurrected, err := s.TryResurrectParentChain(ctx, parentID)
    if err != nil {
        return "", fmt.Errorf("failed to resurrect parent chain for %s: %w", parentID, err)
    }
    if !resurrected {
        return "", fmt.Errorf("parent issue %s does not exist and could not be resurrected from JSONL history", parentID)
    }
}
```

## Investigation
- Check if this scenario actually happens in practice
- TryResurrectParentChain already exists (resurrection.go:174-209)
- May be overkill if users always create parents before children

## Files
- internal/storage/sqlite/hash_ids.go

## Testing
Add test case for deeply nested resurrection


Add --lock-timeout global flag

Add new global flag to control SQLite busy_timeout.

## Implementation
1. Add to cmd/bd/main.go:
   - `lockTimeout time.Duration` global variable
   - Register flag: `--lock-timeout=<duration>` (default 30s)

2. Add config support in internal/config/config.go:
   - `v.SetDefault("lock-timeout", "30s")`
   - Read from config.yaml if not set via flag

3. Pass timeout to sqlite.New() - see next task

## Acceptance Criteria
- `bd --lock-timeout=0 list` fails immediately if DB is locked
- `bd --lock-timeout=100ms list` waits max 100ms
- Config file setting works: `lock-timeout: 100ms`
- Default remains 30s for backward compatibility

Part of bd-olc1


Benchmark git traffic reduction

Automated benchmark comparing git operations with/without Agent Mail.

Acceptance Criteria:
- Script that processes 50 issues
- Counts git operations (pull, commit, push)
- Generates comparison report
- Verifies ≥70% reduction
- Fails if regression detected

File: tests/benchmarks/git_traffic.py

Output: Without Agent Mail: 450 git operations, With Agent Mail: 135 git operations, Reduction: 70%

Implemented automated benchmark script with following features:
- Processes configurable number of issues (default 50)
- Compares git operations in two modes: git-only vs Agent Mail
- Generates detailed comparison report with statistics
- Exit code reflects pass/fail based on 70% reduction target
- Results: 98.5% reduction (200 ops → 3 ops) for 50 issues

Files created:
- tests/benchmarks/git_traffic.py (main benchmark script)
- tests/benchmarks/README.md (documentation)
- tests/benchmarks/git_traffic_50_issues.md (sample results)

The benchmark vastly exceeds the 70% target, showing 98.5% reduction.


Test issue 0


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.


Extract database search logic into helper function

The logic for finding a database in a beads directory is duplicated:
- FindDatabasePath() BEADS_DIR section (beads.go:141-169)
- findDatabaseInTree() (beads.go:248-280)

Both implement the same search order:
1. Check config.json first (single source of truth)
2. Fall back to canonical beads.db
3. Search for *.db files, filtering backups and vc.db

Refactoring suggestion:
Extract to a helper function like:
  func findDatabaseInBeadsDir(beadsDir string) string

Benefits:
- Single source of truth for database search logic
- Easier to maintain and update search order
- Reduces code duplication

Related to [deleted:bd-e16b] implementation.


Code Review: PR #481 - Context Engineering Optimizations

Comprehensive code review of the merged context engineering PR (PR #481) that reduces MCP context usage by 80-90%.

## Summary
The PR successfully implements lazy tool schema loading and minimal issue models to reduce context window usage. Overall implementation is solid and well-tested.

## Positive Findings
✅ Well-designed models (IssueMinimal, CompactedResult)
✅ Comprehensive test coverage (28 tests, all passing)
✅ Clear documentation and comments
✅ Backward compatibility preserved (show() still returns full Issue)
✅ Sensible defaults (COMPACTION_THRESHOLD=20, PREVIEW_COUNT=5)
✅ Tool catalog complete with all 15 tools documented

## Issues Identified
See linked issues for specific followup tasks.

## Context Engineering Architecture
- discover_tools(): List tool names only (~500 bytes vs ~15KB)
- get_tool_info(name): Get specific tool details on-demand
- IssueMinimal: Lightweight model for list views (~80 bytes vs ~400 bytes)
- CompactedResult: Auto-compacts results with >20 issues
- _to_minimal(): Conversion function (efficient, no N+1 issues)


Review multi-repo deletion tracking implementation

Thoroughly review the multi-repo deletion tracking fix (bd-4oob):

Files changed:
- cmd/bd/deletion_tracking.go: Added getMultiRepoJSONLPaths() helper
- cmd/bd/daemon_sync.go: Updated snapshot capture/update logic for multi-repo
- cmd/bd/deletion_tracking_test.go: Added 2 new tests (287 lines)

Review focus areas:
1. Correctness: Does getMultiRepoJSONLPaths() handle all edge cases?
2. Performance: Calling getMultiRepoJSONLPaths() 3x per sync (snapshot capture, merge, base update) - should we cache?
3. Error handling: What if some repos fail snapshot operations but others succeed?
4. Race conditions: Multiple daemons in different repos?
5. Test coverage: Are TestMultiRepoDeletionTracking and TestMultiRepoSnapshotIsolation sufficient?
6. Path handling: Absolute vs relative paths, tilde expansion

This is fresh code - needs careful review before considering deletion tracking production-ready.

Code review completed. Overall assessment: Core deletion tracking logic is sound, but error handling and path handling issues make this not yet production-ready for multi-repo scenarios.

Key findings:

CRITICAL ISSUES (Priority 1):
1. Inconsistent error handling in daemon_sync.go - snapshot/merge fail hard but base update warns. Can leave DB in inconsistent state with no rollback. See bd-sjmr.
2. No path normalization in getMultiRepoJSONLPaths() - tilde expansion, relative paths, duplicates not handled. See bd-iye7.

SHOULD FIX (Priority 2):
3. Missing test coverage for edge cases - empty paths, duplicates, partial failures. See bd-kdoh.
4. Performance - getMultiRepoJSONLPaths() called 3x per sync (minor issue). See bd-we4p.

WHAT WORKS WELL:
- Atomic file operations with PID-based temp files
- Good snapshot isolation between repos
- Race condition protection via exclusive locks
- Solid test coverage for happy path scenarios

VERDICT: Address bd-iye7 and bd-sjmr before considering deletion tracking production-ready for multi-repo mode.

Detailed review notes available in conversation history.


Document CLI + hooks as recommended approach over MCP

Update documentation to position CLI + bd prime hooks as the primary recommended approach over MCP server, explaining why minimizing context matters even with large context windows (compute cost, energy, environment, latency).


WASM port of bd for Claude Code Web sandboxes

Enable beads to work in Claude Code Web sandboxes by compiling bd to WebAssembly.

## Problem
Claude Code Web sandboxes cannot install bd CLI due to network restrictions:
- GitHub releases return 403
- go install fails with DNS errors
- Binary cannot be downloaded

## Solution
Compile bd Go codebase to WASM, publish to npm as drop-in replacement.

## Technical Approach
- Use GOOS=js GOARCH=wasm to compile bd
- modernc.org/sqlite already supports js/wasm target
- Publish to npm as bd-wasm package
- Full feature parity with bd CLI

## Success Criteria
- bd-wasm installs via npm in web sandbox
- All core bd commands work identically
- JSONL output matches native bd
- Performance within 2x of native

WASM port abandoned - Claude Code Web has full VMs not browser restrictions. Better: npm + native binary


Updated title


Document CompactedResult response format in CONTEXT_ENGINEERING.md

The CompactedResult is a new response format that MCP clients need to understand, but it's not documented in CONTEXT_ENGINEERING.md.

## What's Missing
- Example CompactedResult JSON response
- How to detect if a result is compacted
- How to request full results (disable compaction or increase limit)
- Guidance on handling both list[IssueMinimal] and CompactedResult return types
- Migration guide for clients expecting list[Issue] from ready() and list()

## Documentation Gaps
Currently CONTEXT_ENGINEERING.md explains the optimization strategy but doesn't show:
1. Sample CompactedResult response
2. Client-side code examples for handling compaction
3. When compaction is triggered (>20 results)
4. How to get all results if needed

## Suggested Additions
Add new section \"Handling Large Result Sets\" with:
- CompactedResult schema documentation
- Python client example for handling both response types
- Guidance on re-issuing queries with better filters


Add mutation channel to internal/rpc/server.go

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


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)


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


Default to YES for git hooks and merge driver installation

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


Design contributor namespace isolation for beads pollution prevention

## Problem

When contributors work on beads-the-project using beads-the-tool, their personal work-tracking issues leak into PRs. The .beads/issues.jsonl is intentionally tracked (it's the project's issue database), but contributors' local issues pollute the diff.

This is a recursion problem unique to self-hosting projects.

## Possible Solutions to Explore

1. **Contributor namespaces** - Each contributor gets a private prefix (e.g., `bd-steve-xxxx`) that's gitignored or filtered
2. **Separate database** - Contributors use BEADS_DIR pointing elsewhere for personal tracking
3. **Issue ownership/visibility flags** - Mark issues as "local-only" vs "project"
4. **Prefix-based filtering** - Configure which prefixes are committed vs ignored

## Design Considerations

- Should be zero-friction for contributors (no manual setup)
- Must not break existing workflows
- Needs to work with sync/collaboration features
- Consider: what if a "personal" issue graduates to "project" issue?

## Expansion Needed

This is a placeholder. Needs detailed design exploration before implementation.


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


cmd/bd test suite is absurdly slow - 279 tests taking 8+ minutes

# Problem

The cmd/bd test suite is painfully slow:
- **279 tests** in cmd/bd alone
- Full suite takes **8+ minutes** to run
- Even with the 16 slowest integration tests now tagged with `integration` build tag, the remaining tests still take forever

This makes the development loop unusable. We can't wait 8+ minutes every time we want to run tests.

# Root Cause Analysis

## 1. Sheer Volume
279 tests is too many for a single package. Even at 0.1s per test, that's 28 seconds minimum just for cmd/bd.

## 2. Each Test Creates Full Database + Temp Directories
Every test does heavy setup:
- Creates temp directory (`t.TempDir()` or `os.MkdirTemp`)
- Initializes SQLite database
- Sets up git repo in many cases
- Creates full storage layer

Example from the tests:
```go
func setupCLITestDB(t *testing.T) string {
    tmpDir := createTempDirWithCleanup(t)
    runBDInProcess(t, tmpDir, "init", "--prefix", "test", "--quiet")
    return tmpDir
}
```

This happens 279 times!

## 3. Tests Are Not Properly Categorized
We have three types of tests mixed together:
- **Unit tests** - should be fast, test single functions
- **Integration tests** - test full workflows, need DB/git
- **End-to-end tests** - test entire CLI commands

They're all lumped together in cmd/bd, all running every time.

# What We've Already Fixed

Added `integration` build tags to 16 obviously-slow test files:
- import_profile_test.go (performance benchmarking tests)
- export_mtime_test.go (tests with time.Sleep calls)
- cli_fast_test.go (full CLI integration tests)
- delete_test.go, import_uncommitted_test.go, sync_local_only_test.go (git integration)
- And 10 more in internal/ packages

These are now excluded from the default `go test ./...` run.

# Proposed Solutions

## Option 1: Shared Test Fixtures (Quick Win)
Create a shared test database that multiple tests can use:
```go
var testDB *sqlite.SQLiteStorage
var testDBOnce sync.Once

func getSharedTestDB(t *testing.T) storage.Storage {
    testDBOnce.Do(func() {
        // Create one DB for all tests
    })
    return testDB
}
```

**Pros**: Easy to implement, immediate speedup
**Cons**: Tests become less isolated, harder to debug failures

## Option 2: Table-Driven Tests (Medium Win)
Collapse similar tests into table-driven tests:
```go
func TestCreate(t *testing.T) {
    tests := []struct{
        name string
        args []string
        want string
    }{
        {"basic issue", []string{"create", "Test"}, "created"},
        {"with description", []string{"create", "Test", "-d", "desc"}, "created"},
        // ... 50 more cases
    }

    db := setupOnce(t) // Setup once, not 50 times
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // test using shared db
        })
    }
}
```

**Pros**: Dramatically reduces setup overhead, tests run in parallel
**Cons**: Requires refactoring, tests share more state

## Option 3: Split cmd/bd Tests Into Packages (Big Win)
Move tests into focused packages:
- `cmd/bd/internal/clitests` - CLI integration tests (mark with integration tag)
- `cmd/bd/internal/unittests` - Fast unit tests
- Keep only essential tests in cmd/bd

**Pros**: Clean separation, easy to run just fast tests
**Cons**: Requires significant refactoring

## Option 4: Parallel Execution (Quick Win)
Add `t.Parallel()` to independent tests:
```go
func TestSomething(t *testing.T) {
    t.Parallel() // Run this test concurrently with others
    // ...
}
```

**Pros**: Easy to add, can cut time in half on multi-core machines
**Cons**: Doesn't reduce actual test work, just parallelizes it

## Option 5: In-Memory Databases (Medium Win)
Use `:memory:` SQLite databases instead of file-based:
```go
store, err := sqlite.New(ctx, ":memory:")
```

**Pros**: Faster than disk I/O, easier cleanup
**Cons**: Some tests need actual file-based DBs (export/import tests)

# Recommended Approach

**Short-term (this week)**:
1. Add `t.Parallel()` to all independent tests in cmd/bd
2. Use `:memory:` databases where possible
3. Create table-driven tests for similar test cases

**Medium-term (next sprint)**:
4. Split cmd/bd tests into focused packages
5. Mark more integration tests appropriately

**Long-term (backlog)**:
6. Consider shared test fixtures with proper isolation

# Current Status

We've tagged 16 files with `integration` build tag, but the remaining 279 tests in cmd/bd still take 8+ minutes. This issue tracks fixing the cmd/bd test performance specifically.

# Target

Get `go test ./...` (without `-short` or `-tags=integration`) down to **under 30 seconds**.

# THE REAL ROOT CAUSE (Updated Analysis)

After examining the actual test code, the problem is clear:

## Every Test Creates Its Own Database From Scratch

Look at `create_test.go`:
```go
func TestCreate_BasicIssue(t *testing.T) {
    tmpDir := t.TempDir()                          // ← Creates temp dir
    testDB := filepath.Join(tmpDir, ".beads", "beads.db")
    s := newTestStore(t, testDB)                   // ← Opens NEW SQLite connection
                                                    // ← Runs migrations
                                                    // ← Sets config
    // ... actual test (3 lines)
}

func TestCreate_WithDescription(t *testing.T) {
    tmpDir := t.TempDir()                          // ← Creates ANOTHER temp dir
    testDB := filepath.Join(tmpDir, ".beads", "beads.db")
    s := newTestStore(t, testDB)                   // ← Opens ANOTHER SQLite connection
    // ... actual test (3 lines)
}
```

**This happens 279 times!**

## These Tests Don't Need Isolation!

Most tests are just checking:
- "Can I create an issue with a title?"
- "Can I create an issue with a description?"
- "Can I add labels?"

They don't conflict with each other. They could all share ONE database!

## The Fix: Test Suites with Shared Setup

Instead of:
```go
func TestCreate_BasicIssue(t *testing.T) {
    s := newTestStore(t, t.TempDir()+"/db")  // ← Expensive!
    // test
}

func TestCreate_WithDesc(t *testing.T) {
    s := newTestStore(t, t.TempDir()+"/db")  // ← Expensive!
    // test
}
```

Do this:
```go
func TestCreate(t *testing.T) {
    // ONE setup for all subtests
    s := newTestStore(t, t.TempDir()+"/db")

    t.Run("basic_issue", func(t *testing.T) {
        t.Parallel()  // Can run concurrently - tests don't conflict
        // test using shared `s`
    })

    t.Run("with_description", func(t *testing.T) {
        t.Parallel()
        // test using shared `s`
    })

    // ... 50 more subtests, all using same DB
}
```

**Result**: 50 tests → 1 database setup instead of 50!

## Why This Works

SQLite is fine with concurrent reads and isolated transactions. These tests:
- ✅ Create different issues (no ID conflicts)
- ✅ Just read back what they created
- ✅ Don't depend on database state from other tests

They SHOULD share a database!

## Real Numbers

Current:
- 279 tests × (create dir + init SQLite + migrations) = **8 minutes**

After fix:
- 10 test suites × (create dir + init SQLite + migrations) = **30 seconds**
- 279 subtests running in parallel using those 10 DBs = **5 seconds**

**Total: ~35 seconds instead of 8 minutes!**

## Implementation Plan

1. **Group related tests** into suites (Create, List, Update, Delete, etc.)
2. **One setup per suite** instead of per test
3. **Use t.Run() for subtests** with t.Parallel()
4. **Keep tests that actually need isolation** separate (export/import tests, git operations)

This is way better than shuffling tests into folders!

## Progress Update (2025-11-21)

✅ **Completed**:
- Audited all 280 tests, created TEST_SUITE_AUDIT.md ([deleted:bd-c49])
- Refactored create_test.go to shared DB pattern ([deleted:bd-y6d])
- Proven the pattern works: 11 tests now run in 0.04s with 1 DB instead of 11

❌ **Current Reality**:
- Overall test suite: Still 8+ minutes (no meaningful change)
- Only 1 of 76 test files refactored
- Saved ~10 DB initializations out of 280

## Acceptance Criteria (REALISTIC)

This task is NOT complete until:
- [ ] All P1 files refactored (create ✅, dep, stale, comments, list, ready)
- [ ] Test suite runs in < 2 minutes
- [ ] Measured and verified actual speedup

## Next Steps

1. Refactor remaining 5 P1 files: dep_test.go, stale_test.go, comments_test.go, list_test.go, ready_test.go
2. Measure actual time improvement after each file
3. Continue with P2 files if needed to hit <2min target


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.


Update documentation after code health cleanup

Update all documentation to reflect code structure changes after cleanup phases complete.

Documentation to update:
1. **AGENTS.md** - Update file structure references
2. **CONTRIBUTING.md** (if exists) - Update build/test instructions
3. **Code comments** - Update any outdated references
4. **Package documentation** - Update godoc for reorganized packages

New documentation to add:
1. **internal/util/README.md** - Document shared utilities
2. **internal/debug/README.md** - Document debug logging
3. **internal/rpc/README.md** - Document new file organization
4. **internal/storage/sqlite/migrations/README.md** - Migration system docs

Impact: Keeps documentation in sync with code


Improve CheckStaleness error handling

## Problem

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

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

## Edge Cases That Return False

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

## Fix

Return errors for abnormal conditions:

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

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

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

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

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


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


Pre-push hook and daemon export produce different JSONL

After committing and pushing, git status shows .beads/beads.jsonl as dirty. Investigation shows:

1. Pre-push hook ran successfully and exported DB → JSONL
2. Push completed
3. Shortly after, daemon exported DB → JSONL again with different content
4. Diff shows comments added to old issues (bd-23a8, bd-6049, bd-87a0)

Timeline:
- Commit c731c45 "Update beads JSONL"
- Pre-push hook exported JSONL
- Push succeeded
- Daemon PID 33314 exported again with different content

Questions:
1. Did someone run a command between commit and daemon export?
2. Is there a timing issue where pre-push hook doesn't capture all DB changes?
3. Should pre-commit hook flush daemon changes before committing?

The comments appear to be from Nov 5 (created_at: 2025-11-05T08:38:46Z) but are only appearing in JSONL now. This suggests the DB had these comments but they weren't exported during pre-push.

Possible causes:
- Pre-push hook uses BEADS_NO_DAEMON=1 which might skip pending writes
- Daemon has unflushed changes in memory
- Race condition between pre-push export and daemon's periodic export

Improved fix based on oracle code review:
1. Pre-push now flushes pending changes first (prevents debounce race)
2. Uses git status --porcelain to catch all change types
3. Handles both beads.jsonl and issues.jsonl
4. Works even if bd not installed (git-only check)


Add import metrics for external_ref matching statistics

Add observability for external_ref matching behavior during imports to help debug and optimize import operations.

Metrics to track:
- Number of issues matched by external_ref
- Number of issues matched by ID
- Number of issues matched by content hash
- Number of external_ref updates vs creates
- Average import time with vs without external_ref

Output format:
- Add to ImportResult struct
- Include in import command output
- Consider structured logging

Use cases:
- Debugging slow imports
- Understanding match distribution
- Optimizing import performance

Related: bd-1022


Run final validation and cleanup checks

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

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

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

Impact: Confirms all cleanup objectives achieved successfully

## Validation Results

**Dead Code:** ✅ Found and removed 1 unreachable function (`DroppedEventsCount`)
**Tests:** ✅ All pass
**Coverage:**
- Main: 39.6%
- cmd/bd: 20.2%
- Created follow-up issues (bd-85487065 through bd-bc2c6191) to improve coverage

**Build:** ✅ Clean
**Linting:** 73 issues (up from 34 baseline)
- Increase due to unused functions from refactoring
- Need cleanup in separate issue

**Integration Tests:** ✅ All pass
**Metrics:** 56,464 LOC across 193 Go files
**Git:** 2 files modified (deadcode fix + auto-synced JSONL)

## Follow-up Issues Created
- bd-85487065: Add tests for internal/autoimport (0% coverage)
- bd-0dcea000: Add tests for internal/importer (0% coverage)
- bd-4d7fca8a: Add tests for internal/utils (0% coverage)
- bd-6221bdcd: Improve cmd/bd coverage (20.2% -> target higher)
- bd-bc2c6191: Improve internal/daemon coverage (22.5% -> target higher)


Update JSONL format to use hash IDs

Update JSONL import/export to use hash IDs, store aliases separately.

## Current JSONL Format
```jsonl
{"id":"bd-1c63eb84","title":"Fix bug","status":"open",...}
{"id":"bd-9063acda","title":"Add test","status":"open",...}
```

## New JSONL Format (Option A: Include Alias)
```jsonl
{"id":"bd-af78e9a2","alias":1,"title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","alias":2,"title":"Add test","status":"open",...}
```

## New JSONL Format (Option B: Hash ID Only)
```jsonl
{"id":"bd-af78e9a2","title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","title":"Add test","status":"open",...}
```

Store aliases in separate .beads/aliases.jsonl (local only, git-ignored):
```jsonl
{"hash":"bd-af78e9a2","alias":1}
{"hash":"bd-e5f6a7b8","alias":2}
```

**Recommendation**: Option B (hash only in main JSONL)
- Cleaner git diffs (no alias conflicts)
- Aliases are workspace-local preference
- Main JSONL is canonical, portable

## Export Changes
File: cmd/bd/export.go
```go
// Export issues with hash IDs
for _, issue := range issues {
    json := marshalIssue(issue)  // Uses issue.ID (hash)
    // Don't include alias in JSONL
}

// Separately export aliases to .beads/aliases.jsonl
exportAliases(issues)
```

## Import Changes
File: cmd/bd/import.go, internal/importer/importer.go
```go
// Import issues by hash ID
issue := unmarshalIssue(line)
// Assign new alias on import (don't use incoming alias)
issue.Alias = getNextAlias()

// No collision detection needed! Hash IDs are globally unique
```

## Dependency Reference Format
No change needed - already uses issue IDs:
```json
{"depends_on_id":"bd-af78e9a2","type":"blocks"}
```

## Files to Modify
- cmd/bd/export.go (use hash IDs)
- cmd/bd/import.go (import hash IDs, assign aliases)
- internal/importer/importer.go (remove collision detection!)
- .gitignore (add .beads/aliases.jsonl)

## Testing
- Test export produces hash IDs
- Test import assigns new aliases
- Test dependencies preserved with hash IDs
- Test no collision detection triggered


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

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

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

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

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

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


Run final validation and cleanup checks

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


Implement topological sort for import ordering

Refactor upsertIssues() to sort issues by hierarchy depth before batch creation. Ensures parents are created before children, fixing latent bug where parent-child pairs in same batch can fail if ordered wrong. Sort by dot count, create in depth-order batches (0→1→2→3).


Standardize daemon detection: use tryDaemonLock probe before RPC

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

This is extremely cheap and eliminates unnecessary connection attempts.

Apply across all client entry points that probe for daemon.


Vendor beads-merge Go code into internal/merge/

Copy beads-merge source code from @neongreen's repo into bd codebase.

**Tasks**:
- Create `internal/merge/` package
- Copy merge algorithm code
- Add attribution header to all files
- Update imports to use bd's internal types
- Add LICENSE/ATTRIBUTION file crediting @neongreen
- Keep original algorithm intact

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


Complete and commit beads-mcp type safety improvements

Uncommitted type safety work in beads-mcp needs review and completion


Refactor: extract common helpers in sync-branch hook checks

Code review of #532 fix identified duplication between checkSyncBranchHookCompatibility and checkSyncBranchHookQuick.

Suggested refactoring:
1. Extract getPrePushHookPath(path string) helper for git dir + hook path resolution
2. Extract extractHookVersion(hookContent string) helper for version parsing
3. Consider whether Warning for custom hooks is appropriate (vs OK with info message)
4. Document the intentional behavior difference between full check (Warning for custom) and quick check (OK for custom)


Convert magic numbers to named constants in FlushManager


Test dependency child


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

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

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

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


Multi-repo hydration layer

Build core infrastructure to hydrate database from N repos (N≥1), with smart caching via file mtime tracking and routing writes to correct JSONL based on source_repo metadata.


Add internal/ai package for AI-assisted repairs

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

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

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

See repair_commands.md lines 357-425 for design.


Optimize GetReadyWork performance - 752ms on 10K database (target: <50ms)

# Performance Analysis (10K Issue Database)

Analyzed using CPU profiles from benchmark suite on Apple M2 Pro.

## Operation Performance

| Operation                        | Time    | Allocations | Memory |
|----------------------------------|---------|-------------|--------|
| bd ready (GetReadyWork)          | ~752ms  | 167,466     | 16MB   |
| bd list (SearchIssues no filter) | ~11.6ms | 89,214      | 5.8MB  |
| bd list (SearchIssues filtered)  | ~9.2ms  | 62,365      | 3.5MB  |
| bd create (CreateIssue)          | ~2.6ms  | 146         | 8.6KB  |
| bd update (UpdateIssue)          | ~0.32ms | 364         | 15KB   |
| bd close (UpdateIssue)           | ~0.32ms | 364         | 15KB   |

**Target: <50ms for all operations on 10K database**

**Current issue: GetReadyWork is 15x over target (752ms vs 50ms)**

## Root Cause

GetReadyWork (internal/storage/sqlite/ready.go:90-128) uses recursive CTE to propagate blocking:
- 65x slower than SearchIssues
- Recalculates entire blocked issue tree on every call
- Algorithm:
  1. Find directly blocked issues via 'blocks' dependencies
  2. Recursively propagate blockage to descendants (max depth: 50)
  3. Exclude all blocked issues from results

## CPU Profile Analysis

- Database syscalls (pthread_cond_signal, syscall6): ~75%
- SQLite engine overhead: inherent to recursive CTE
- Application code (query construction): <1%

**Bottleneck is the recursive CTE query execution, not application code.**

## Optimization Recommendations

### High Impact (Likely to achieve <50ms target)

1. **Cache blocked issue calculation**
   - Add `blocked_issues` table updated on dependency changes
   - Trade write complexity for read speed (ready called >> dependency changes)
   - Eliminates recursive CTE on every read

2. **Add/verify database indexes**
   ```sql
   CREATE INDEX IF NOT EXISTS idx_dependencies_blocked
     ON dependencies(issue_id, type, depends_on_id);
   CREATE INDEX IF NOT EXISTS idx_issues_status
     ON issues(status);
   ```

### Medium Impact

3. **Reduce allocations** (167K allocations for GetReadyWork)
   - Profile `scanIssues()` for object pooling opportunities
   - Reuse slice capacity for repeated calls

### Low Impact (Not recommended)
- Query optimization for CRUD operations (already <3ms)
- Connection pooling tuning (not showing in profiles)

## Verification

Run benchmarks to validate optimization:
```bash
make bench-quick
go tool pprof -http=:8080 internal/storage/sqlite/bench-cpu-*.prof
```

Profile files automatically generated in `internal/storage/sqlite/`.


Optimize Memory backend GetIssueByExternalRef with index

Currently GetIssueByExternalRef in Memory storage uses O(n) linear search through all issues.

Current code (memory.go:282-308):
for _, issue := range m.issues {
    if issue.ExternalRef != nil && *issue.ExternalRef == externalRef {
        return &issueCopy, nil
    }
}

Proposed optimization:
- Add externalRefToID map[string]string to MemoryStorage
- Maintain it in CreateIssue, UpdateIssue, DeleteIssue
- Achieve O(1) lookup like SQLite's index

Impact: Low (--no-db mode typically has smaller datasets)
Related: bd-1022


Auto-configure git merge driver during `bd init`

Enhance `bd init` to optionally set up beads-merge as git merge driver.

**Tasks**:
- Prompt user to install git merge driver
- Configure `.git/config`: `merge.beads.driver "bd merge %A %O %L %R"`
- Create/update `.gitattributes`: `.beads/beads.jsonl merge=beads`
- Add `--skip-merge-driver` flag for non-interactive use
- Update AGENTS.md onboarding section

**Files**:
- `cmd/bd/init.go`
- `.gitattributes` template


Add integration test for cross-clone deletion propagation


Issue 2


Remove misleading placeholder 'bd merge' command from duplicates output

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

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

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

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

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

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

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


Clean up linter errors (914 total issues)

The codebase has 914 linter issues reported by golangci-lint. While many are documented as baseline in LINTING.md, we should clean these up systematically to improve code quality and maintainability.

Reduced from 56 to 41 issues locally, then to 0 issues.

**Fixed in commits:**
- c2c7eda: Fixed 15 actual errors (dupl, gosec, revive, staticcheck, unparam)
- 963181d: Configured exclusions to get to 0 issues locally

**Current status:**
- ✅ Local: golangci-lint reports 0 issues
- ❌ CI: Still failing (see [deleted:bd-cb64c226.1])

**Problem:**
Config v2 format or golangci-lint-action@v8 compatibility issue causing CI to fail despite local success.

**Next:** Debug [deleted:bd-cb64c226.1] to fix CI/local discrepancy


Test SQLite WASM compatibility

Verify modernc.org/sqlite works in WASM target. Child of epic bd-44d0.

## Tasks
- [ ] Compile minimal SQLite test to WASM
- [ ] Test database create/open operations
- [ ] Test query execution
- [ ] Test JSONL import/export
- [ ] Benchmark performance vs native

## Decision Point
If modernc.org/sqlite issues, evaluate ncruces/go-sqlite3 alternative.


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

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

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

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

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


Detect and warn about outdated git hooks

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


Update 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


Channel isolation test - beads

Resetting stale in_progress status from old executor run (13 days old)


GH#488: Support claude.local.md for local-only config

Allow CLAUDE.local.md for testing bd without committing to repo or overriding shared CLAUDE.md. Not every repo wants committed AI config. See: https://github.com/steveyegge/beads/issues/488


Add tests for merge driver auto-config in bd init

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

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

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


Blocking issue


Test all core bd commands in WASM for feature parity

Comprehensive testing of bd-wasm against native bd:
- Test all CRUD operations (create, update, show, close)
- Test dependency management (dep add, dep tree)
- Test sync operations (sync, import, export)
- Verify JSONL output matches native bd
- Run existing Go test suite in WASM if possible
- Benchmark performance (should be within 2x of native)


Implement clone-scoped ID allocation to prevent N-way collisions

## Problem
Current ID allocation uses per-clone atomic counters (issue_counters table) that sync based on local database state. In N-way collision scenarios:
- Clone B sees {test-1} locally, allocates test-2
- Clone D sees {test-1, test-2, test-3} locally, allocates test-4
- When same content gets assigned test-2 and test-4, convergence fails

Root cause: Each clone independently allocates IDs without global coordination, leading to overlapping assignments for the same content.

## Solution
Add clone UUID to ID allocation to make every ID globally unique:

**Current format:** `test-1`, `test-2`, `test-3`
**New format:** `test-1-a7b3`, `test-2-a7b3`, `test-3-c4d9`

Where suffix is first 4 chars of clone UUID.

## Implementation

### 1. Add clone_identity table
```sql
CREATE TABLE clone_identity (
    clone_uuid TEXT PRIMARY KEY,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```

### 2. Modify getNextIDForPrefix()
```go
func (s *SQLiteStorage) getNextIDForPrefix(ctx context.Context, prefix string) (string, error) {
    cloneUUID := s.getOrCreateCloneUUID(ctx)
    shortUUID := cloneUUID[:4]

    nextNum := s.getNextCounterForPrefix(ctx, prefix)
    return fmt.Sprintf("%s-%d-%s", prefix, nextNum, shortUUID), nil
}
```

### 3. Update ID parsing logic
All places that parse IDs (utils.ExtractIssueNumber, etc.) need to handle new format.

### 4. Migration strategy
- Existing IDs remain unchanged (no suffix)
- New IDs get clone suffix automatically
- Display layer can hide suffix in UI: `bd-cb64c226.3-a7b3` → `#42`

## Benefits
- **Zero collision risk**: Same content in different clones gets different IDs
- **Maintains readability**: Still sequential numbering within clone
- **No coordination needed**: Works offline, no central authority
- **Scales to 100+ clones**: 4-char hex = 65,536 unique clones

## Concerns
- ID format change may break existing integrations
- Need migration path for existing databases
- Display logic needs update to hide/show suffixes appropriately

## Success Criteria
- 10+ clone collision test passes without failures
- Existing issues continue to work (backward compatibility)
- Documentation updated with new ID format
- Migration guide for v1.x → v2.x

## Timeline
Medium-term (v1.1-v1.2), 2-3 weeks implementation

## References
- Related to bd-0dcea000 (immediate fix)
- See beads_nway_test.go for failing N-way tests


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

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

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


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


Add #nosec G304 comments to JSONL file reads in sync.go

sync.go:610 uses os.ReadFile(jsonlPath) without #nosec comment, inconsistent with other JSONL reads that have '// #nosec G304 - controlled path'.

Add comment for consistency with integrity.go:43 and import.go:316.


Document -short flag in testing guide

Add documentation about the -short flag and how it's used to skip slow tests. Should explain that developers can run 'go test -short ./...' for fast iteration and 'go test ./...' for full coverage.


Fix mypy errors in beads-mcp

Running `mypy .` in `integrations/beads-mcp` reports 287 errors. These should be addressed to improve type safety and code quality.


Blocked issue


DetectCycles SQL query has bug preventing cycle detection

The DetectCycles function's SQL query has a bug in the LIKE filter that prevents it from detecting cycles.

Current code (line 571):
```sql
AND p.path NOT LIKE '%' || d.depends_on_id || '→%'
```

This prevents ANY revisit to nodes, including returning to the start node to complete a cycle.

Fix:
```sql
AND (d.depends_on_id = p.start_id OR p.path NOT LIKE '%' || d.depends_on_id || '→%')
```

This allows revisiting the start node (to detect the cycle) while still preventing intermediate node revisits.

Impact: Currently DetectCycles cannot detect any cycles, but this hasn't been noticed because AddDependency prevents cycles from being created. The function would only matter if cycles were manually inserted into the database.


Fix TestExportUpdatesMetadata to test actual daemon functions

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

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

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

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

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

## Files
- cmd/bd/daemon_sync_test.go

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


Document error handling strategy for metadata update failures

Multiple places silently ignore metadata update failures (sync.go:614-617, import.go:320-322) with non-fatal warnings. This is intentional (degrades gracefully to mtime-based approach) but not well documented.

Add comments explaining:
- Why these failures are non-fatal
- How system degrades gracefully
- What the fallback behavior is (mtime-based detection)


Integration test: mutation to export latency

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

Test added to daemon_test.go as TestMutationToExportLatency().

Currently skipped with note that it should be enabled once bd-146 (event-driven daemon) is fully implemented and enabled by default.

The test structure is complete:
1. Sets up test environment with fast debounce (500ms)
2. SingleMutationLatency: measures latency from mutation to JSONL update
3. RapidMutationBatching: verifies multiple mutations batch into single export

Once event-driven mode is default, remove the t.Skip() line and the test will validate <500ms latency.


Implement bd stale command to find abandoned/forgotten issues

Add bd stale command to surface issues that haven't been updated recently and may need attention.

Use cases:
- In-progress issues with no recent activity (may be abandoned)
- Open issues that have been forgotten
- Issues that might be outdated or no longer relevant

Query logic should find non-closed issues where updated_at exceeds a time threshold.

Should support:
- --days N flag (default 30-90 days)
- --status filter (e.g., only in_progress)
- --json output for automation

References GitHub issue #184 where user expected this command to exist.


Git history fallback missing timeout - could hang on large repos

## Problem

The git commands in `checkGitHistoryForDeletions` have no timeout. On large repos with extensive history, `git log --all -S` or `git log --all -G` can take a very long time (minutes).

## Location
`internal/importer/importer.go:899` and `:930`

## Impact
- Import could hang indefinitely
- User has no feedback that git search is running
- No way to cancel except killing the process

## Fix
Add context with timeout to git commands:

```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "git", ...)
```

Also consider adding a `--since` flag to bound the git history search.


Fix Homebrew warning about Ruby file location

Homebrew warning: Found Ruby file outside steveyegge/beads tap formula directory.
Warning points to: /opt/homebrew/Library/Taps/steveyegge/homebrew-beads/bd.rb
It should likely be inside a Formula/ directory or similar structure expected by Homebrew taps.


Parent: bd-imj

Ensure deletions.jsonl is tracked in git (not ignored).

Update bd init and gitignore upgrade logic to:
1. NOT add deletions.jsonl to .gitignore
2. Ensure it is committed alongside beads.jsonl

The file must be in git for cross-clone propagation to work.

Acceptance criteria:
- bd init does not ignore deletions.jsonl
- Existing .gitignore files are not broken
- File appears in git status when modified


Test Issue


Command Set Standardization & Flag Consistency

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

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

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

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


Extract hash ID generation functions to hash_ids.go

Move generateHashID, getNextChildNumber, GetNextChildID to hash_ids.go


Test integration issue

This is a real integration test


Git merge driver uses invalid placeholders (%L, %R instead of %A, %B)

## Problem

The beads git merge driver is configured with invalid Git placeholders:

```
git config merge.beads.driver "bd merge %A %O %L %R"
```

Git doesn't recognize `%L` or `%R` as valid merge driver placeholders. The valid placeholders are:
- `%O` = base (common ancestor)
- `%A` = current version (ours)
- `%B` = other version (theirs)

## Impact

- Affects ALL users when they have `.beads/beads.jsonl` merge conflicts
- Automatic JSONL merge fails with error: "error reading left file: failed to open file: open 7: no such file or directory"
- Users must manually resolve conflicts instead of getting automatic merge

## Root Cause

The `bd init` command (or wherever the merge driver is configured) is using non-standard placeholders. When Git encounters `%L` and `%R`, it either passes them literally or interprets them incorrectly.

## Fix

Update the merge driver configuration to:
```
git config merge.beads.driver "bd merge %A %O %A %B"
```

Where:
- 1st `%A` = output file (current file, will be overwritten)
- `%O` = base (common ancestor)
- 2nd `%A` = left/current version
- `%B` = right/other version

## Action Items

1. Fix `bd init` (or equivalent setup command) to use correct placeholders
2. Add migration/warning for existing users with misconfigured merge driver
3. Update documentation with correct merge driver setup
4. Consider adding validation when `bd init` is run


Remove unused global daemon infrastructure (internal/daemonrunner/)

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

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

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

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

Total cleanup: ~1,500 LOC


Parameterize SQLite busy_timeout in store.go

Modify sqlite.New() to accept configurable busy_timeout.

## Current State
`internal/storage/sqlite/store.go` hardcodes:
```go
connStr = "file:" + path + "?_pragma=foreign_keys(ON)&_pragma=busy_timeout(30000)&..."
```

## Implementation Options

### Option A: Add timeout parameter to New()
```go
func New(ctx context.Context, path string, busyTimeout time.Duration) (*SQLiteStorage, error)
```
Pro: Simple, explicit
Con: Changes function signature (breaking change for callers)

### Option B: Options struct pattern
```go
type Options struct {
    BusyTimeout time.Duration // default 30s
}
func New(ctx context.Context, path string, opts *Options) (*SQLiteStorage, error)
```
Pro: Extensible, nil means defaults
Con: More boilerplate

### Recommendation: Option A with default
```go
func New(ctx context.Context, path string) (*SQLiteStorage, error) {
    return NewWithOptions(ctx, path, 30*time.Second)
}

func NewWithOptions(ctx context.Context, path string, busyTimeout time.Duration) (*SQLiteStorage, error)
```

## Acceptance Criteria
- busy_timeout is parameterized in connection string
- 0 timeout means no waiting (immediate SQLITE_BUSY)
- All existing callers continue to work (via default wrapper)

Part of bd-olc1


Optimize test suite performance - cut runtime by 50%+

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

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

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

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

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


Test update


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


Improve clean vs cleanup command naming/documentation

Clarify the difference between bd clean and bd cleanup to reduce user confusion.

**Current state:**
- bd clean: Remove temporary artifacts (.beads/bd.sock, logs, etc.)
- bd cleanup: Delete old closed issues from database

**Options:**
1. Rename for clarity:
   - bd clean → bd clean-temp
   - bd cleanup → bd cleanup-issues

2. Keep names but improve help text and documentation

3. Add prominent warnings in help output

**Preferred approach:** Option 2 (improve documentation)
- Update short/long descriptions in commands
- Add examples to help text
- Update README.md
- Add cross-references in help output

**Files to modify:**
- cmd/bd/clean.go
- cmd/bd/cleanup.go
- README.md or ADVANCED.md


Add WebSocket support

## Feature Request

[Describe the desired feature]

## Motivation

[Why is this feature needed? What problem does it solve?]

## Use Cases

1. **Use Case 1**: [description]
2. **Use Case 2**: [description]

## Proposed Solution

[High-level approach to implementing this feature]

## Alternatives Considered

- **Alternative 1**: [description and why not chosen]
- **Alternative 2**: [description and why not chosen]


Test child daemon mode


Extract validation functions to validators.go

Move validatePriority, validateStatus, validateIssueType, validateTitle, validateEstimatedMinutes, validateFieldUpdate to validators.go


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.


Improve sync error messages

When bd sync fails, immediately surface the actual root cause (prefix mismatch, orphaned children, forced push detected, etc.) instead of requiring multiple retries to discover it. Make errors actionable with specific recovery steps.


Support local-only git repos without remote origin

Daemon crashes when working with local git repos that don't have origin remote configured. Should gracefully degrade to local-only mode: skip git pull/push operations but maintain daemon features (RPC server, auto-flush, JSONL export).


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.


Refactor sqlite.go into focused modules

Split sqlite.go (2,298 lines) into focused modules: migrations.go, ids.go, issues.go, events.go, dirty.go, db.go. This will improve maintainability and reduce cognitive load.


Rename detection successfully implemented and tested!

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

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

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

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


Add guidance for self-hosting projects

The contributor-workflow-analysis.md is optimized for OSS contributors making PRs to upstream projects. However, it doesn't address projects like VC that use beads for their own development (self-hosting).

Self-hosting projects differ from OSS contributors:
- No upstream/downstream distinction (they ARE the project)
- May run automated executors (not just humans)
- In bootstrap/early phase (stability matters)
- Single team/owner (not multiple contributors with permissions)

Guidance needed on:
- When self-hosting projects should stay single-repo (default, recommended)
- When they should adopt multi-repo (team planning, multi-phase dev)
- How automated executors should handle multi-repo (if at all)
- Special considerations for projects in bootstrap phase

Examples of self-hosting projects: VC (building itself with beads), internal tools, pet projects


Assigned issue


GH#523: Fix closed issues missing closed_at timestamp during db upgrade

Old beads databases may have closed issues without closed_at timestamps, causing 'closed issues must have closed_at timestamp' validation errors on upgrade to 0.29.0. Need defensive handling during import. See: https://github.com/steveyegge/beads/issues/523


Add docs/architecture/event_driven.md

Copy event_driven_daemon.md into docs/ folder. Add to documentation index.


Create Python adapter library

Create beads_mail_adapter.py library that wraps Agent Mail HTTP calls with health checks and graceful degradation.

Acceptance Criteria:
- AgentMailAdapter class with health check on init
- enabled flag auto-disables if server unreachable
- All methods wrapped in try/catch (non-blocking failures)
- Methods: reserve_issue(), release_issue(), notify(), check_inbox()
- Environment-based configuration (AGENT_MAIL_URL, AGENT_MAIL_TOKEN)
- Unit tests for enabled/disabled modes

File: lib/beads_mail_adapter.py


Sync protection mechanism resurrects deleted issues

During bd sync, the 'Protecting N issue(s) from left snapshot' logic resurrects issues that are in the deletions manifest, causing UNIQUE constraint failures on subsequent syncs.

## Reproduction
1. Delete an issue with bd delete (creates tombstone + deletions.jsonl entry)
2. Run bd sync - succeeds
3. Run bd sync again - fails with 'UNIQUE constraint failed: issues.id'

## Root Cause
The protection mechanism (designed to prevent data loss during 3-way merge) keeps a snapshot of issues before sync. When importing after pull, it restores issues from this snapshot even if they're in the deletions manifest.

## Observed Behavior
- bd-3pd was deleted and added to deletions.jsonl
- First sync exports tombstone, commits, pulls, imports - succeeds
- Second sync: protection restores bd-3pd from left snapshot
- Import tries to create bd-3pd which already exists → UNIQUE constraint error

## Expected Behavior
Issues in deletions manifest should NOT be restored by protection mechanism.

## Workaround
Manually delete from DB: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE id = "bd-xxx"'

## Files to Investigate
- cmd/bd/sync.go - protection logic
- cmd/bd/snapshot_manager.go - left snapshot handling
- internal/importer/importer.go - import with protection


sync branch not match config

./bd sync
→ Exporting pending changes to JSONL...
→ No changes to commit
→ Pulling from sync branch 'gh-386'...
Error pulling from sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/worktree-db-fail/.git: not a directory
matt@blufin-framation ~/d/b/worktree-db-fail (worktree-db-fail) [1]> bd config list

Configuration:
  auto_compact_enabled = false
  compact_batch_size = 50
  compact_model = claude-3-5-haiku-20241022
  compact_parallel_workers = 5
  compact_tier1_days = 30
  compact_tier1_dep_levels = 2
  compact_tier2_commits = 100
  compact_tier2_days = 90
  compact_tier2_dep_levels = 5
  compaction_enabled = false
  issue_prefix = worktree-db-fail
  sync.branch = worktree-db-fail


Fix hasJSONLChanged to support per-repo metadata keys

## Problem
After bd-ar2.2, we store metadata with per-repo keys like `last_import_hash:<path>`, but `hasJSONLChanged` and `validatePreExport` still only check global keys.

## Impact
- Multi-repo mode won't benefit from bd-ymj fix
- validatePreExport will fail incorrectly or pass incorrectly
- Metadata updates are written but never read

## Location
- cmd/bd/integrity.go:97 (hasJSONLChanged)
- cmd/bd/integrity.go:138 (validatePreExport)

## Solution
Update hasJSONLChanged to accept optional keySuffix parameter:
```go
func hasJSONLChanged(ctx context.Context, store storage.Storage, jsonlPath string, keySuffix string) bool {
    // Build metadata keys with optional suffix
    hashKey := "last_import_hash"
    mtimeKey := "last_import_mtime"
    if keySuffix != "" {
        hashKey += ":" + keySuffix
        mtimeKey += ":" + keySuffix
    }
    // ... rest of function
}
```

Update all callers to pass correct keySuffix.


Test issue 2


Workaround for FastMCP outputSchema bug in Claude Code

The beads MCP server (v0.23.1) successfully connects to Claude Code, but all tools fail to load with a schema validation error due to a bug in FastMCP 2.13.1.

Error: "Invalid literal value, expected \"object\"" in outputSchema.

Root Cause: FastMCP generates outputSchema with $ref at root level without "type": "object" for self-referential models (Issue).

Workaround: Use slash commands (/beads:ready) or wait for FastMCP fix.


Fix row iterator resource leak in recursive dependency queries

Critical resource leak in findAllDependentsRecursive() where rows.Close() is called AFTER early return on error, never executing.

Location: internal/storage/sqlite/sqlite.go:1131-1136

Problem:
- rows.Close() placed after return statement
- On scan error, iterator never closed
- Can exhaust SQLite connections under moderate load

Fix: Move defer rows.Close() to execute on all code paths

Impact: Connection exhaustion during dependency traversal


Add 'new' as alias for 'create' command


Doctor: exclude merge artifacts from 'multiple JSONL' warning

## Problem
`bd doctor` warns about 'Multiple JSONL files found' when merge artifact files exist:
```
JSONL Files: Multiple JSONL files found: beads.base.jsonl, beads.left.jsonl, issues.jsonl ⚠
```

This is confusing because these aren't real issue JSONL files - they're temporary snapshots for deletion tracking.

## Fix
Exclude known merge artifact patterns from the multiple-JSONL warning:

```go
// In doctor JSONL check
skipPatterns := map[string]bool{
    "beads.base.jsonl": true,
    "beads.left.jsonl": true,
    "beads.right.jsonl": true,
}
```

## Files
- cmd/bd/doctor/ (JSONL check logic)


Add --body-file flag to bd create for reading descriptions from files

## Problem

Creating issues with long/complex descriptions via CLI requires shell escaping gymnastics:

```bash
# Current workaround - awkward heredoc quoting
bd create --title="..." --description="$(cat <<'EOF'
...markdown...
EOF
)"

# Often fails with quote escaping errors in eval context
# Agents resort to writing temp files then reading them
```

## Proposed Solution

Add `--body-file` and `--description-file` flags to read description from a file, matching `gh` CLI pattern.

```bash
# Natural pattern that aligns with training data
cat > /tmp/desc.md << 'EOF'
...markdown content...
EOF

bd create --title="..." --body-file=/tmp/desc.md
```

## Implementation

### 1. Add new flags to `bd create`

```go
createCmd.Flags().String("body-file", "", "Read description from file (use - for stdin)")
createCmd.Flags().String("description-file", "", "Alias for --body-file")
```

### 2. Flag precedence

- If `--body-file` or `--description-file` is provided, read from file
- If value is `-`, read from stdin
- Otherwise fall back to `--body` or `--description` flag
- If neither provided, description is empty (current behavior)

### 3. Error handling

- File doesn't exist → clear error message
- File not readable → clear error message
- stdin specified but not available → clear error message

## Benefits

✅ **Matches training data**: `gh issue create --body-file file.txt` is a common pattern
✅ **No shell escaping issues**: File content is read directly
✅ **Works with any content**: Markdown, special characters, quotes, etc.
✅ **Agent-friendly**: Agents already write complex content to temp files
✅ **User-friendly**: Easier for humans too when pasting long descriptions

## Related Commands

Consider adding similar support to:
- `bd update --body-file` (for updating descriptions)
- `bd comment --body-file` (if/when we add comments)

## Examples

```bash
# From file
bd create --title="Add new feature" --body-file=feature.md

# From stdin
echo "Quick description" | bd create --title="Bug fix" --body-file=-

# With other flags
bd create \
  --title="Security issue" \
  --type=bug \
  --priority=0 \
  --body-file=security-report.md \
  --label=security
```

## Testing

- Test with normal files
- Test with stdin (`-`)
- Test with non-existent files (error handling)
- Test with binary files (should handle gracefully)
- Test with empty files (valid - empty description)
- Test that `--description-file` and `--body-file` are equivalent aliases


Add tests for bd init --contributor wizard

Write integration tests for the contributor wizard:
- Test fork detection logic
- Test planning repo creation
- Test config setup
- Test with/without upstream remote
- Test with SSH vs HTTPS origins


Improve test coverage for cmd/bd CLI (26.2% → 50%)

The main CLI package (cmd/bd) has only 26.2% test coverage. CLI commands should have at least 50% coverage to ensure reliability.

Key areas with low/no coverage:
- daemon_autostart.go (multiple 0% functions)
- compact.go (several 0% functions)
- Various command handlers

Current coverage: 26.2%
Target coverage: 50%