bd doctor: detect and fix stale beads-sync branch

## Problem

When beads-sync diverges significantly from main (on source code, not just .beads/), it needs to be reset. After a force-push reset, other clones/contributors will have orphaned local beads-sync branches.

## Symptoms
- Local beads-sync is many commits behind main on source files
- Local beads-sync doesn't share recent history with remote beads-sync (after force-push)
- bd sync may behave unexpectedly

## Proposed bd doctor checks

1. **Check beads-sync alignment with main**
   - If beads-sync exists and is >N commits behind main on non-.beads/ files, warn
   - Suggests: reset beads-sync to main

2. **Check beads-sync alignment with remote**
   - If local beads-sync has diverged from origin/beads-sync (no common recent ancestor)
   - This happens after someone force-pushed a reset
   - Suggests: reset local beads-sync to origin/beads-sync

## Proposed fixes (--fix)

- Reset local beads-sync branch to origin/beads-sync or main
- Handle worktree if present (git worktree remove, then recreate on next sync)

## Context
Discovered when beads-sync drifted 126 commits behind main. Required manual reset and force-push. Contributors will need to reset their local branches too.


Test lowercase p0


Test parent issue


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

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

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

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

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


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

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

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

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

Commit: d46177d


bd delete bulk operations fight with auto-import/daemon causing data resurrection

When bulk deleting issues (e.g., 244 closed issues older than 24h), the process fights with auto-import and daemon infrastructure:

**Expected behavior:**
- Delete 244 issues from 468-issue database
- Export to JSONL (224 lines)
- Commit and push
- Result: 224 issues

**Actual behavior:**
- Delete 244 issues
- Import runs (from stale git JSONL with 468 issues)
- Resurrects deleted issues back into database
- Export writes 356 lines (not 224)
- Math: 468 - 244 = 224, but got 356 (132 issues resurrected)

**Root cause:**
Auto-import keeps re-importing from git during the delete operation, before the new JSONL is committed. The workflow is:
1. Delete from DB
2. Auto-import runs (reads old JSONL from git with deleted issues still present)
3. Issues come back
4. Export writes partially-deleted state

**Solution options:**
1. Add `--no-auto-import` flag to bulk delete operations
2. Atomic delete-export-commit operation that suppresses imports
3. Dedicated `bd prune` command that handles this correctly
4. Lock file to prevent auto-import during bulk mutations

**Impact:**
- Bulk cleanup operations don't work reliably
- Makes it nearly impossible to prune old closed issues
- Confusing UX (delete 244, but only 112 actually removed)

**FIXED**: Auto-import now skips during delete operations to prevent resurrection.

**Root cause confirmed**: Auto-import was running in PersistentPreRun before delete executed, causing it to re-import stale JSONL from git and resurrect deleted issues.

**Solution implemented**:
1. Added delete to skip list in main.go PersistentPreRun (alongside import and sync --dry-run)
2. Delete operations now complete atomically without auto-import interference
3. Added comprehensive test (TestBulkDeleteNoResurrection) to prevent regression

**Test verification**:
- Creates 20 issues, deletes 10
- Verifies no resurrection after delete
- Confirms JSONL has correct count (10 remaining)
- All existing tests still pass


Feature with design

This is a description


Refactor duplicate JSONL-from-git parsing code

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

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

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

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


Daemon gets stuck when auto-import blocked by git conflicts

CRITICAL: The daemon enters a corrupt state that breaks RPC commands when auto-import is triggered but git pull fails due to uncommitted changes.

Impact: This is a data integrity and usability issue that could cause users to lose trust in Beads. The daemon silently fails for certain commands while appearing healthy.

Reproduction:
1. Make local changes to issues (creates uncommitted .beads/beads.jsonl)
2. Remote has updates (JSONL newer, triggers auto-import)
3. Daemon tries to pull but fails: 'cannot pull with rebase: You have unstaged changes'
4. Daemon enters bad state - 'bd show' and other commands return EOF
5. 'bd list' still works, daemon process is running, no errors logged

Technical details:
- Auto-import check runs in handleRequest() before processing RPC commands
- When import is blocked, it appears to corrupt daemon state
- Likely: deadlock, unclosed transaction, or storage handle corruption
- Panic recovery (server_lifecycle_conn.go:183) didn't catch anything - not a panic

Required fix:
- Auto-import must not block RPC command execution
- Handle git pull failures gracefully without corrupting state
- Consider: skip auto-import if git is dirty, queue import for later, or use separate goroutine
- Add timeout/circuit breaker for import operations
- Log clear warnings when auto-import is skipped

Without this fix, users in collaborative environments will frequently encounter mysterious EOF errors that require daemon restarts.


CLI accepts both hash IDs and aliases

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


Implement tombstone TTL and expiration logic

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


Latency test


isLikelyHash rejects 3-char all-letter base36 hashes

ExtractIssuePrefix fails for multi-hyphen prefixes when the 3-char hash suffix has no digits.

Example: `xa-adt-bat` returns prefix `xa` instead of `xa-adt`.

Root cause: `isLikelyHash()` requires at least one digit to distinguish hashes from English words, but base36 hashes can be all-letters by chance.

Affected: 20 issues in xa-adapt with suffixes like bat, dev, fbi, oil, etc.


Refactor path canonicalization into helper function

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

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

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

Related to bd-e16b implementation.


bd-hv01: Refactor snapshot management into dedicated module

Problem: Snapshot logic is scattered across deletion_tracking.go. Would benefit from abstraction with SnapshotManager type.

Benefits: cleaner separation of concerns, easier to test in isolation, better encapsulation, could add observability/metrics.

Suggested improvements: add magic constants, track merge statistics, better error messages.

Files: cmd/bd/deletion_tracking.go (refactor into new snapshot_manager.go)


Hash-based IDs with aliasing system

Replace sequential auto-increment IDs (bd-1c63eb84, bd-9063acda) with content-hash based IDs (bd-af78e9a2) plus human-friendly aliases (#1, #2).

## Motivation
Current sequential IDs cause collision problems when multiple clones work offline:
- Non-deterministic convergence in N-way scenarios (bd-cbed9619.1, bd-e6d71828)
- Complex collision resolution logic (~2,100 LOC)
- UNIQUE constraint violations during import
- Requires coordination between workers

Hash-based IDs eliminate collisions entirely while aliases preserve human readability.

## Benefits
- ✅ Collision-free distributed ID generation
- ✅ Eliminates ~2,100 LOC of collision handling code
- ✅ Better git merge behavior (different IDs = different JSONL lines)
- ✅ True offline-first workflows
- ✅ Simpler auto-import (no remapping needed)
- ✅ Enables parallel CI/CD workers without coordination

## Design
- Canonical ID: bd-af78e9a2 (8-char SHA256 prefix of title+desc+timestamp+creator)
- Alias: #42 (auto-increment per workspace, mutable, display-only)
- CLI accepts both: bd show bd-af78e9a2 OR bd show #42
- JSONL stores hash IDs only (aliases reconstructed on import)
- Alias conflicts resolved via content-hash ordering (deterministic)

## Breaking Change
This is a v2.0 feature requiring migration. Provide bd migrate --hash-ids tool.

## Timeline
~9 weeks (Phase 1: Hash IDs 4w, Phase 2: Aliases 3w, Phase 3: Testing 2w)

## Dependencies
Should complete after bd-7c5915ae (cleanup validation) and before bd-710a4916 (CRDT).


worktree: any bd command is slow

in a git worktree any bd command is slow, with a 2-3s pause before any results are shown. The identical command with `--no-daemon` is near instant.


Add internal/ai package for LLM integration

Shared AI client for repair commands.

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

Env vars:
- BEADS_AI_PROVIDER
- BEADS_AI_API_KEY
- BEADS_AI_MODEL

Files: internal/ai/client.go (new)


bd jira sync command

Add a built-in bd command for Jira synchronization.

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

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

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

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

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


Add unit tests for hasJSONLChanged() function

The hasJSONLChanged() function has no unit tests. Should test:
- Normal case: hash matches
- Normal case: hash differs
- Edge case: empty file
- Edge case: missing metadata (first run)
- Edge case: corrupted metadata
- Edge case: file read errors
- Edge case: invalid hash format in metadata

Also add performance benchmarks for large JSONL files.


Add functional tests for FlushManager correctness verification


Add unit tests for parent resurrection

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


Clean up snapshot files after successful merge

After a successful 3-way merge and import during 'bd sync', the snapshot files (beads.base.jsonl, beads.left.jsonl, and their .meta.json files) are left in the .beads/ directory indefinitely.

These files are only needed temporarily during the merge process:
- beads.base.jsonl: snapshot from last successful import
- beads.left.jsonl: snapshot before git pull

Once the merge succeeds and the new JSONL is imported, these files serve no purpose and should be cleaned up.

Current behavior:
- sync.go:269 calls updateBaseSnapshot() after successful import
- UpdateBase() updates beads.base.jsonl to the new state
- beads.left.jsonl is never removed
- Both files accumulate in .beads/ directory

Expected behavior:
- After successful merge and import, clean up both snapshot files
- Only retain snapshots between sync operations (create on export, use during merge, clean up after import)

The cleanup logic exists (SnapshotManager.Cleanup()) but is only called on validation failures (deletion_tracking.go:48), not on success.

Discovered in vc project where stale snapshot files from Nov 8 merge were still present.


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


Log errors from timer-triggered flushes instead of discarding


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)


Test coverage improvement initiative (47.8% → 65%)

Umbrella epic for improving overall test coverage from 47.8% to 65%+.

## Summary

| Package | Current | Target | Issue |
|---------|---------|--------|-------|
| internal/compact | 18.2% | 70% | bd-thgk |
| cmd/bd/doctor/fix | 23.9% | 50% | bd-fx7v |
| cmd/bd | 26.2% | 50% | bd-llfl |
| internal/daemon | 27.3% | 60% | bd-n386 |
| cmd/bd/setup | 28.4% | 50% | bd-sh4c |
| internal/syncbranch | 33.0% | 70% | bd-io8c |
| internal/export | 37.1% | 60% | bd-6sm6 |
| internal/lockfile | 42.0% | 60% | bd-9w3s |
| internal/rpc | 47.5% | 60% | bd-m8ro |
| internal/beads | 48.1% | 70% | bd-tvu3 |
| internal/storage | N/A | basic | bd-a15d |

## Packages with good coverage (no action needed)
- internal/types: 91.0%
- internal/utils: 88.9%
- internal/deletions: 84.3%
- internal/config: 84.2%
- internal/autoimport: 83.5%
- internal/validation: 80.8%
- internal/merge: 78.9%
- internal/routing: 74.3%
- internal/storage/sqlite: 73.1%
- internal/importer: 70.2%

Overall target: 65%+ (from current 47.8%)


Imperator does not know its own mail identity


Audit remaining cmd/bd files for error handling consistency

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


Doctor should exclude merge artifacts from 'multiple JSONL' warning

Doctor command warns about 'multiple JSONL files' when .base.jsonl and .left.jsonl merge artifacts exist. These are expected during/after merge operations and should be excluded from the warning.


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


formatVanishedIssues output order is non-deterministic


Issue with labels

This is a description


Add Claude setup tip with dynamic priority

Add a predefined tip that suggests running `bd setup claude` when Claude Code is detected but not configured. This tip should have higher priority (shown more frequently) until the setup is complete.


convertDeletionToTombstone uses hardcoded default values

The convertDeletionToTombstone helper (importer.go:1114-1130) uses hardcoded defaults:
- Priority: 2
- IssueType: types.TypeTask
- Title: "(deleted)"

These defaults work but could be improved:
1. Consider using Priority: 0 (unset) to distinguish from user-set priority
2. OriginalType is empty string - consider setting IssueType to empty to be consistent
3. Title could include the original ID for debugging: "(deleted: bd-xyz)"

Low priority cleanup for consistency.


wish: a 'continue' or similar cmd/flag which means alter last issue

so many time I create an issue and then have another thought: 'oh, before I did X and it crashed there was ZZZ happening' or 'actually that is P4 not P2'. It would be nice if when `bd {cmd}` is used without a {title} or {id} it just adds or updates the most recently touched issue.


GH#520: Daemon sync-branch commit fails with pre-commit hooks (needs --no-verify)

gitCommitInWorktree in daemon_sync_branch.go missing --no-verify flag, causing commit failures when pre-commit hooks installed. Library function in worktree.go:684 has correct impl. See: https://github.com/steveyegge/beads/issues/520


Phase 2: Implement sandbox auto-detection for GH #353

Implement automatic sandbox detection to improve UX for users in sandboxed environments (e.g., Codex).

**Tasks:**
1. Implement sandbox detection heuristic using syscall.Kill permission checks
2. Auto-enable --sandbox mode when sandbox is detected
3. Display informative message when sandbox is detected

**Implementation:**
- Add isSandboxed() function in cmd/bd/main.go
- Auto-set sandboxMode = true in PersistentPreRun when detected
- Show: 'ℹ  Sandbox detected, using direct mode'

**References:**
- docs/GH353_INVESTIGATION.md (Solution 3, lines 120-160)
- Depends on: Phase 1 (bd-???)

**Acceptance Criteria:**
- Codex users don't need to manually specify --sandbox
- No false positives in normal environments
- Clear messaging when auto-detection triggers


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


Add tests for performFlush error handling in FlushManager

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

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

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

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

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

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


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.


Add performance optimization to hasJSONLChanged with mtime fast-path

hasJSONLChanged() reads entire JSONL file to compute SHA256 hash on every check. For large databases (50MB+), this is expensive and called frequently by daemon.

Optimization: Check mtime first as fast-path. Only compute hash if mtime changed. This catches git operations (mtime changes but content doesn't) while avoiding expensive hash computation in common case (mtime unchanged = content unchanged).

Performance impact: 99% of checks will use fast path, only git operations need slow path.


Update git hooks to be sync.branch aware

## Problem

The pre-push hook blocks pushes when .beads/issues.jsonl has uncommitted changes. But with sync.branch configured, those changes are intentionally NOT committed to main - they go to the sync branch via worktree.

## Current Behavior

1. User configures sync.branch=beads-sync
2. bd sync commits changes to beads-sync via worktree
3. Local .beads/issues.jsonl is updated (needed for import)
4. git push to main triggers pre-push hook
5. Hook sees uncommitted .beads changes and blocks push
6. User must use --no-verify to push

## Expected Behavior

When sync.branch is configured, the pre-push hook should:
1. Check if sync.branch is set (bd config get sync.branch)
2. If set, skip the .beads uncommitted check OR
3. Verify changes are committed to the sync branch instead

## Affected Files

- examples/git-hooks/pre-push
- examples/git-hooks/pre-commit (may also need update)

## Workaround

Use git push --no-verify


Reduce sync rounds in multiclone tests

Analyze and reduce the number of sync rounds in hash multiclone tests.

Current state:
- TestHashIDs_MultiCloneConverge: 1 round of syncs across 3 clones
- TestHashIDs_IdenticalContentDedup: 2 rounds across 2 clones

Investigation needed:
- Profile to see how much time each sync takes
- Determine minimum rounds needed for convergence
- Consider making rounds configurable via env var

File: beads_hash_multiclone_test.go:70, :132


Implement filesystem shims for WASM

WASM needs JS shims for filesystem access. Child of epic bd-44d0.

## Tasks
- [ ] Implement file read/write shims
- [ ] Map WASM syscalls to Node.js fs API
- [ ] Handle .beads/ directory discovery
- [ ] Test with real JSONL files
- [ ] Support both absolute and relative paths

## Technical Notes
- Use Node.js fs module via syscall/js
- Consider MEMFS for in-memory option


Testing event-driven mode!


Migration tool: sequential → hash IDs

Create migration tool to convert existing sequential-ID databases to hash-ID format.

## Command: bd migrate --hash-ids

```bash
bd migrate --hash-ids [--dry-run]
```

## Migration Process

### 1. Backup Database
```bash
cp .beads/beads.db .beads/beads.db.backup-1761798384
echo "✓ Backup created: .beads/beads.db.backup-1234567890"
```

### 2. Generate Hash IDs for Existing Issues
```go
func migrateToHashIDs(db *SQLiteStorage) error {
    // Read all issues
    issues, err := db.ListIssues(ctx, ListOptions{Status: "all"})

    // Generate mapping: old ID → new hash ID
    mapping := make(map[string]string)
    for _, issue := range issues {
        hashID := GenerateHashID(
            issue.Title,
            issue.Description,
            issue.CreatedAt,
            db.workspaceID,
        )
        mapping[issue.ID] = hashID
    }

    // Detect hash collisions (extremely rare)
    if hasCollisions(mapping) {
        return fmt.Errorf("hash collision detected, aborting")
    }

    return nil
}
```

### 3. Update Database Schema
```sql
-- Add alias column
ALTER TABLE issues ADD COLUMN alias INTEGER UNIQUE;

-- Populate aliases from old IDs
UPDATE issues SET alias = CAST(SUBSTR(id, 4) AS INTEGER)
  WHERE id LIKE 'bd-%' AND SUBSTR(id, 4) GLOB '[0-9]*';

-- Create new issues_new table with hash IDs
CREATE TABLE issues_new (
    id TEXT PRIMARY KEY,  -- Hash IDs now
    alias INTEGER UNIQUE,
    title TEXT NOT NULL,
    -- ... rest of schema
);

-- Copy data with ID mapping
INSERT INTO issues_new SELECT
    <new_hash_id>,  -- From mapping
    alias,
    title,
    -- ...
FROM issues;

-- Drop old table, rename new
DROP TABLE issues;
ALTER TABLE issues_new RENAME TO issues;
```

### 4. Update Dependencies
```sql
-- Update depends_on_id using mapping
UPDATE dependencies
SET issue_id = <new_hash_id>,
    depends_on_id = <new_depends_on_hash_id>
FROM mapping;
```

### 5. Update Text References
```go
// Update all text fields that mention old IDs
func updateTextReferences(db *SQLiteStorage, mapping map[string]string) error {
    for oldID, newID := range mapping {
        // Update description, notes, design, acceptance_criteria
        db.Exec(`UPDATE issues SET
            description = REPLACE(description, ?, ?),
            notes = REPLACE(notes, ?, ?),
            design = REPLACE(design, ?, ?),
            acceptance_criteria = REPLACE(acceptance_criteria, ?, ?)
        `, oldID, newID, oldID, newID, oldID, newID, oldID, newID)
    }
}
```

### 6. Export to JSONL
```bash
bd export  # Writes hash IDs to .beads/issues.jsonl
git add .beads/issues.jsonl
git commit -m "Migrate to hash-based IDs"
```

## Output
```bash
$ bd migrate --hash-ids
Migrating to hash-based IDs...
✓ Backup created: .beads/beads.db.backup-1730246400
✓ Generated 150 hash IDs
✓ No hash collisions detected
✓ Updated issues table schema
✓ Updated 150 issue IDs
✓ Updated 87 dependencies
✓ Updated 234 text references
✓ Exported to .beads/issues.jsonl
✓ Migration complete!

Next steps:
  1. Test: bd list, bd show #1, etc.
  2. Commit: git commit -m "Migrate to hash-based IDs"
  3. Push: git push origin main
  4. Notify collaborators to pull and re-init
```

## Dry Run Mode
```bash
$ bd migrate --hash-ids --dry-run
[DRY RUN] Would migrate 150 issues:
  bd-1c63eb84   → bd-af78e9a2  (alias: #1)
  bd-9063acda   → bd-e5f6a7b8  (alias: #2)
  ...
  bd-150 → bd-9a8b7c6d  (alias: #150)

[DRY RUN] Would update:
  - 150 issue IDs
  - 87 dependencies
  - 234 text references in descriptions/notes

No changes made. Run without --dry-run to apply.
```

## Files to Create
- cmd/bd/migrate.go (new command)
- internal/storage/sqlite/migrations/hash_ids.go

## Testing
- Test migration on small database (10 issues)
- Test migration on large database (1000 issues)
- Test hash collision detection (inject collision artificially)
- Test text reference updates
- Test rollback (restore from backup)
- Test migrated database works correctly

## Rollback Procedure
```bash
# If migration fails or has issues
mv .beads/beads.db.backup-1234567890 .beads/beads.db
bd export  # Restore JSONL from backup DB
```

## Multi-Clone Coordination
**Important**: All clones must migrate before syncing:

1. Coordinator sends message: "Migrating to hash IDs on 2025-10-30 at 10:00 UTC"
2. All collaborators pull latest changes
3. All run: `bd migrate --hash-ids`
4. All push changes
5. New work can continue with hash IDs

**Do NOT**:
- Mix sequential and hash IDs in same database
- Sync before all clones migrate


Event-driven daemon architecture

Replace 5-second polling sync loop with event-driven architecture that reacts instantly to changes. Eliminates stale data issues while reducing CPU ~60%. Key components: FileWatcher (fsnotify), Debouncer (500ms), RPC mutation events, optional git hooks. Target latency: <500ms (vs 5000ms). See event_driven_daemon.md for full design.

## Implementation Progress

**Completed:**
1. ✅ Mutation events infrastructure (bd-143 equivalent)
   - MutationEvent channel in RPC server
   - Events emitted for all write operations: create, update, close, label add/remove, dep add/remove, comment add
   - Non-blocking emission with dropped event counter

2. ✅ FileWatcher with fsnotify (bd-b0c7f7ef related)
   - Watches .beads/issues.jsonl and .git/refs/heads
   - 500ms debounce
   - Polling fallback if fsnotify unavailable

3. ✅ Debouncer (bd-144 equivalent)
   - 500ms debounce for both export and import triggers
   - Thread-safe trigger/cancel

4. ✅ Separate export-only and import-only functions
   - createExportFunc(): exports + optional commit/push (no pull/import)
   - createAutoImportFunc(): pull + import (no export)
   - Target latency <500ms achieved by avoiding full sync

5. ✅ Dropped events safety net (bd-eef03e0a related)
   - Atomic counter tracks dropped mutation events
   - 60-second health check triggers export if events were dropped
   - Prevents silent data loss from event storms

**Still Needed:**
- Platform-specific tests (bd-69bce74a)
- Integration test for mutation→export latency (bd-140)
- Unit tests for FileWatcher (bd-b0c7f7ef)
- Unit tests for Debouncer (bd-144)
- Event storm stress test (bd-eef03e0a)
- Documentation update (bd-142)

**Next Steps:**
Add comprehensive test coverage before enabling events mode by default.


Add bd init --contributor wizard

Interactive wizard for OSS contributor setup. Guides user through: fork workflow setup, separate planning repo configuration, auto-detection of fork relationships, examples of common OSS workflows.


Test Agent Mail server failure scenarios

Verify graceful degradation across various failure modes.

Test Cases:
- Server never started
- Server crashes during operation
- Network partition (timeout)
- Server returns 500 error
- Invalid bearer token
- SQLite corruption

Acceptance Criteria:
- Agents continue working in all scenarios
- Clear log messages about degradation
- No crashes or data loss
- Beads JSONL remains consistent

File: tests/integration/test_mail_failures.py


Daemon crashes silently when multiple .db files exist in .beads/

When daemon detects multiple .db files (after filtering out .backup and vc.db files), it writes error details to .beads/daemon-error file before exiting.

The error file is checked when:
1. Daemon discovery fails to connect (internal/daemon/discovery.go)
2. Auto-start fails to yield a running daemon (cmd/bd/main.go)
3. Daemon list shows 'daemon not responding' error

This makes the error immediately visible to users without requiring them to check daemon logs.

File created:  cmd/bd/daemon.go (writes daemon-error on multiple .db detection)
Files modified:
- internal/daemon/discovery.go (reads daemon-error and surfaces in DaemonInfo.Error)
- cmd/bd/main.go (displays daemon-error when auto-start fails)

Testing: Create multiple .db files in .beads/, start daemon, verify error file created and shown in bd daemons list

Root cause: Daemon exits with os.Exit(1) when multiple .db files detected (daemon.go:1381), but error only goes to daemon log file. User sees 'daemon not responding' without knowing why.

Current detection:
- daemon.go filters out .backup and vc.db files
- bd doctor detects multiple databases
- Error message tells user to run 'bd init' or manually remove

Problem: Error is not user-visible unless they check daemon logs.

Proposed fix options:
1. Surface the error in 'bd info' and 'bd daemons list' output
2. Add a hint in error messages to run 'bd doctor' when daemon fails
3. Make daemon write error to a .beads/daemon-error file that gets checked
4. Improve 'bd doctor' to run automatically when daemon is unhealthy


Multi-clone collision resolution testing and documentation

Epic to track improvements to multi-clone collision resolution based on ultrathinking analysis of-3d844c58 and [deleted:bd-71107098].

Current state:
- 2-clone collision resolution is SOUND and working correctly
- Hash-based deterministic collision resolution works
- Test fails due to timestamp comparison, not actual logic issues

Work needed:
1. Fix TestTwoCloneCollision to compare content not timestamps
2. Add TestThreeCloneCollision for regression protection
3. Document 3-clone ID non-determinism as known behavior


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

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

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

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

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

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

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


Investigation & Proof of Concept

Validate that MCP Agent Mail works as expected and delivers promised benefits before committing to full integration.

POC completed successfully:
✅ bd-muls: Server installed and tested
✅ bd-27xm: MCP tool execution issues resolved
✅ [deleted:bd-6hji]: File reservation collision prevention validated
✅ bd-htfk: Latency benchmarking shows 20-50x improvement
✅ bd-pmuu: ADR 002 created documenting integration decision

Results validate Agent Mail benefits:
- Collision prevention works (exclusive file reservations)
- Latency: <100ms (vs 2000-5000ms git sync)
- Lightweight deployment (<50MB memory)
- Optional/non-intrusive integration approach validated

Next: bd-wfmw (Integration Layer Implementation)


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)


GH#510: Document sync-branch worktree behavior

User confused about beads creating worktree on main branch. Need docs explaining sync-branch worktree mechanism. See: https://github.com/steveyegge/beads/issues/510


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%


cmd/bd tests fail without -short flag (parallel test deadlock)


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 integration tests for multi-repo sync

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


Update bd delete to create tombstones

Modify delete command to create tombstone records instead of (or in addition to) deletions.jsonl entries. Phase 1: dual-write mode. Per design bd-dli.


Refactor sqlite.go (2298 lines)

Break down internal/storage/sqlite/sqlite.go into smaller, more focused modules. The file is currently 2298 lines and should be split into logical components.


bd-hv01: Brittle string comparison breaks with JSON field reordering

## Problem
deletion_tracking.go:125 uses string comparison to detect unchanged issues:

```go
if leftLine, existsInLeft := leftIndex[id]; existsInLeft && leftLine == baseLine {
    deletions = append(deletions, id)
}
```

This breaks if:
- JSON field order changes (legal in JSON)
- Timestamps updated by import/export
- Whitespace/formatting changes
- Floating point precision varies

## Example Failure
```json
// baseLine
{"id":"bd-1","priority":1,"status":"open"}
// leftLine (same data, different order)
{"id":"bd-1","status":"open","priority":1}
```
These are semantically identical but string comparison fails.

## Fix
Parse and compare JSON semantically:
```go
func jsonEquals(a, b string) bool {
    var objA, objB map[string]interface{}
    json.Unmarshal([]byte(a), &objA)
    json.Unmarshal([]byte(b), &objB)
    return reflect.DeepEqual(objA, objB)
}
```

## Files Affected
- cmd/bd/deletion_tracking.go:125
- cmd/bd/deletion_tracking.go:134-170 (buildIDToLineMap)


Fix inconsistent error handling in multi-repo deletion tracking

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

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

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

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

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

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

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

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

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


Show dependency status in bd show output

When bd show displays dependencies and dependents, include their status (open/closed/in_progress/blocked) for quick progress tracking. Improves context rebuilding and planning.


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


Import overwrites newer local issues with older remote versions

## Problem

During git pull + import, local issues with newer updated_at timestamps get overwritten by older versions from remote JSONL.

## What Happened

Timeline:
1. 17:52 - Closed bd-df190564 and bd-b501fcc1 locally (updated_at: 2025-10-31)
2. 17:51 - Remote pushed same issues with status=open (updated_at: 2025-10-30)
3. 17:52 - Local sync pulled remote commit and imported JSONL
4. Result: Issues reverted to open despite local version being newer

## Root Cause

DetectCollisions (internal/storage/sqlite/collision.go:67-79) compares fields but doesn't check timestamps:

```go
conflictingFields := compareIssues(existing, incoming)
if len(conflictingFields) == 0 {
    result.ExactMatches = append(result.ExactMatches, incoming.ID)
} else {
    // Same ID, different content - treats as UPDATE
    result.Collisions = append(result.Collisions, &CollisionDetail{...})
}
```

Import applies incoming version regardless of which is newer.

## Expected Behavior

Import should:
1. Compare updated_at timestamps when collision detected
2. Skip update if local version is newer
3. Apply update only if remote version is newer
4. Warn on timestamp conflicts

## Solution

Add timestamp checking to DetectCollisions or importIssues:

```go
if len(conflictingFields) > 0 {
    // Check timestamps
    if !incoming.UpdatedAt.After(existing.UpdatedAt) {
        // Local is newer or same - skip update
        result.ExactMatches = append(result.ExactMatches, incoming.ID)
        continue
    }
    // Remote is newer - apply update
    result.Collisions = append(result.Collisions, &CollisionDetail{...})
}
```

## Files
- internal/storage/sqlite/collision.go
- internal/importer/importer.go

## References
- Discovered during bd-df190564, bd-b501fcc1 re-opening


Feature P1


Missing test for PullResult.SafetyWarnings population


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


Auto-routing and maintainer detection

Implement intelligent routing to automatically send new issues to correct repo based on user's maintainer vs contributor status, with discovered issues inheriting parent's source_repo.


Merge3Way public API does not expose TTL parameter

The public Merge3Way() function in merge.go does not allow callers to configure the tombstone TTL. It hard-codes the default via merge3WayWithTTL(). While merge3WayWithTTL() exists, it is unexported (lowercase). This means the CLI and tests cannot configure TTL at merge time. Use cases: testing with different TTL values, per-repository TTL configuration, debugging with short TTL, supporting --ttl flag in bd merge command (mentioned in design doc bd-zvg). Recommendation: Export Merge3WayWithTTL (rename to uppercase). Files: internal/merge/merge.go:77, 292-298


Centralize BD_DEBUG logging into debug package

The codebase has 43 scattered instances of `if os.Getenv("BD_DEBUG") != ""` debug checks across 6 files. Centralize into a debug logging package.

Current locations:
- `cmd/bd/main.go` - 15 checks
- `cmd/bd/autoflush.go` - 6 checks
- `cmd/bd/nodb.go` - 4 checks
- `internal/rpc/server.go` - 2 checks
- `internal/rpc/client.go` - 5 checks
- `cmd/bd/daemon_autostart.go` - 11 checks

Target structure:
```
internal/debug/
└── debug.go
```

Benefits:
- Centralized debug logging
- Easier to add structured logging later
- Testable (can mock debug output)
- Consistent debug message format

Impact: Removes 43 scattered checks, improves code clarity


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.


Safety check tests use string(rune()) which only works for single digits


Use in-memory filesystem for test git operations

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

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

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


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

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


Test exclusive file reservations with two agents

Simulate two agents racing to claim the same issue and verify that exclusive reservations prevent collision.

Acceptance Criteria:
- Agent A reserves bd-123 → succeeds
- Agent B tries to reserve bd-123 → fails with clear error message
- Agent B can see who has the reservation
- Reservation expires after TTL
- Agent B can claim after expiration

Successfully tested file reservations:
- Agent BrownBear reserved bd-123 → granted
- Agent ChartreuseHill tried same → conflicts returned
- System correctly prevents collision


Multi-repo contributor workflow support

Implement separate repository support for OSS contributors to prevent PR pollution while maintaining git ledger and multi-clone sync. Based on contributor-workflow-analysis.md Solution #4.


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


PruneDeletions iterates map non-deterministically

## Problem

`PruneDeletions` iterates over `loadResult.Records` which is a map. Go maps iterate in random order, so:

1. `result.PrunedIDs` order is non-deterministic
2. `kept` slice order is non-deterministic → `WriteDeletions` output order varies

## Location
`internal/deletions/deletions.go:213`

## Impact
- Git diffs are noisy (file changes order on each prune)
- Tests could be flaky if they depend on order
- Harder to debug/audit

## Fix
Sort by ID or timestamp before iterating:

```go
// Convert map to slice and sort
var records []DeletionRecord
for _, r := range loadResult.Records {
    records = append(records, r)
}
sort.Slice(records, func(i, j int) bool {
    return records[i].ID < records[j].ID
})
```


Investigate bd export/import sync issue - database and JSONL out of sync

Observed in VC repo: database has 1137 issues but beads.jsonl only has 309. Running 'bd export -o .beads/issues.jsonl' doesn't update the file. Running 'bd import' hangs indefinitely.

Reproduction context:
- VC repo: 1137 issues in DB, 309 in JSONL
- Created 4 new issues with bd create
- bd export didn't write them to JSONL
- bd import hung (possibly daemon lock conflict?)

Need to investigate:
1. Why export doesn't update JSONL when DB has more issues
2. Why import hangs
3. Daemon lock interaction with export/import
4. File path handling (issues.jsonl vs beads.jsonl)

## Root Cause Analysis

The issue was NOT about file path handling or export failing to update JSONL. The actual problem was:

**Import/Export hanging when daemon is running** due to SQLite lock contention:

1. When daemon is connected, `PersistentPreRun` in main.go returns early without initializing the `store` variable
2. Import/Export commands then tried to open the database directly with `sqlite.New(dbPath)`
3. This blocked waiting for the database lock that the daemon already holds → **HANGS INDEFINITELY**

## Solution Implemented

Modified both import.go and export.go to:
1. Detect when `daemonClient` is connected
2. Explicitly close the daemon connection before opening direct SQLite access
3. Added debug logging to help diagnose similar issues

This ensures import/export commands always run in direct mode, avoiding lock contention.

## File Path Handling

The file path confusion (issues.jsonl vs beads.jsonl) was a red herring. The code uses `filepath.Glob("*.jsonl")` which correctly finds ANY `.jsonl` file in `.beads/` directory, so both filenames work.


Channel isolation test - beads

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


Add --yes flag to bd doctor --fix

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

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


Add tests for 3-way merge scenarios

Comprehensive test coverage for merge logic.

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

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


bd-hv01: Missing cleanup of .merged temp file on failure

Problem: deletion_tracking.go:49 creates tmpMerged file but does not clean up on failure, causing disk space leak and potential interference with subsequent syncs.

Fix: Add defer os.Remove(tmpMerged) after creating temp file path.

Files: cmd/bd/deletion_tracking.go:38-89


Add test coverage for internal/config package


Consolidate ID generation and validation into ids.go

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


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.


Review and document rarely-used commands

Document use cases or consider deprecation for infrequently-used commands.

**Commands to review:**
1. bd rename-prefix - How often is this used? Document use cases
2. bd detect-pollution - Consider integrating into bd validate
3. bd migrate-hash-ids - One-time migration, keep but document as legacy

**For each command:**
- Document typical use cases
- Add examples to help text
- Consider if it should be a subcommand instead
- Add deprecation warning if appropriate

**Not changing:**
- duplicates ✓ (useful for data quality)
- repair-deps ✓ (useful for fixing broken refs)
- restore ✓ (critical for compacted issues)
- compact ✓ (performance feature)

**Deliverable:**
- Updated help text
- Documentation in ADVANCED.md
- Deprecation plan if needed


Remove unreachable RPC methods

Several RPC server and client methods are unreachable and should be removed:

Server methods (internal/rpc/server.go):
- `Server.GetLastImportTime` (line 2116)
- `Server.SetLastImportTime` (line 2123)
- `Server.findJSONLPath` (line 2255)

Client methods (internal/rpc/client.go):
- `Client.Import` (line 311) - RPC import not used (daemon uses autoimport)

Evidence:
```bash
go run golang.org/x/tools/cmd/deadcode@latest -test ./...
```

Impact: Removes ~80 LOC of unused RPC code


Unit tests for Debouncer

Test debouncer batches multiple triggers into single action. Test timer reset on subsequent triggers. Test cancel during wait. Test thread safety.


bd doctor --json flag not working

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


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.


Add circular dependency detection to bd doctor

Added cycle detection as Check #10 in bd doctor command. Uses same recursive CTE query as DetectCycles() to find circular dependencies. Reports error status with count and fix suggestion if cycles found.


Fix FOREIGN KEY constraint failures in AddComment and ApplyCompaction

The 'CloseIssue', 'UpdateIssueID', and 'RemoveLabel' methods were fixed in PR #348 to prevent foreign key constraint failures when operating on non-existent issues.

However, the Oracle identified two other methods that follow the same problematic pattern:
1. `AddComment` (in `internal/storage/sqlite/events.go`)
2. `ApplyCompaction` (in `internal/storage/sqlite/compact.go`)

These methods attempt to insert an event record after updating the issue, without verifying that the issue update actually affected any rows. This leads to a foreign key constraint failure if the issue does not exist.

We need to:
1. Create reproduction tests for these failure cases
2. Apply the same fix pattern: check `RowsAffected()` after the update, and return a proper "issue not found" error if it is 0, before attempting to insert the event.
3. Standardize the error message format to "issue %s not found" or "issue not found: %s" for consistency.


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)


Fix bd init to bootstrap from sync-branch when configured

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


Exclude tombstones from SearchIssues by default

SearchIssues() returns tombstones if they match search criteria. Per design, users generally shouldn't see deleted issues in search results.

Location: internal/storage/sqlite/queries.go:1155-1311 and transaction.go:887-1044

Options:
1. Add default tombstone exclusion unless explicitly filtering for them
2. Add IncludeTombstones field to IssueFilter

Recommendation: Add 'status != tombstone' to WHERE clause unless filter.Status == StatusTombstone


Add ON DELETE CASCADE to child_counters schema

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


Implement signal-aware context in CLI commands

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

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

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

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

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

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

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


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


Design tombstone TTL and expiration semantics

Design the TTL and expiration behavior for tombstone records. Key decisions: default TTL (recommend 30 days), configuration via config.yaml, expiration semantics (remove vs keep expired), merge behavior when tombstone expires (live issue wins), clock skew handling.


Test npm package installation locally

Verify npm package works before publishing:

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

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

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


Document sync.require_confirmation_on_mass_delete config option


Replace BEADS_DB with BEADS_DIR environment variable

Implement BEADS_DIR as a replacement for BEADS_DB to point to the .beads directory instead of the database file directly.

Rationale:
- With --no-db mode, there's no .db file to point to
- The .beads directory is the logical unit (contains config.yaml, db files, jsonl files)
- More intuitive: point to the beads directory not the database file

Implementation:
1. Add BEADS_DIR environment variable support
2. Maintain backward compatibility with BEADS_DB
3. Priority order: BEADS_DIR > BEADS_DB > auto-discovery
4. If BEADS_DIR is set, look for config.yaml in that directory to find actual database path
5. Update documentation and migration guide

Files to modify:
- beads.go (FindDatabasePath function)
- cmd/bd/main.go (initialization)
- Documentation (CLI_REFERENCE.md, TROUBLESHOOTING.md, etc.)
- MCP integration (integrations/beads-mcp/src/beads_mcp/config.py)

Testing:
- Ensure BEADS_DB still works (backward compatibility)
- Test BEADS_DIR with both db and --no-db modes
- Test priority order when both are set
- Update integration tests

Related to GitHub issue #179


Create shared insert/event/dirty helpers

Create issues.go (insertIssue/insertIssues), events.go (recordCreatedEvent/recordCreatedEvents), dirty.go (markDirty/markDirtyBatch). Refactor single and bulk create paths to use these.


Update AGENTS.md with event-driven mode

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


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


Recurring dirty state after merge conflicts - bd sync keeps failing

## Problem

`bd sync` consistently fails with merge conflicts in `.beads/beads.jsonl`, creating a loop:
1. User runs `bd sync`
2. Git merge conflict occurs
3. User resolves with `git checkout --theirs` (takes remote)
4. Daemon auto-exports database state (which has local changes)
5. JSONL becomes dirty again immediately
6. Repeat

This has been happening for **weeks** and is extremely frustrating.

## Root Cause

The recommended conflict resolution (`git checkout --theirs`) throws away local database state (comments, dependencies, closed issues). The daemon then immediately re-exports, creating a dirty state.

## Current Workaround

Manual `bd export -o .beads/beads.jsonl && git add && git commit && git push` after every failed sync.

## Example Session

```bash
$ bd sync
CONFLICT (content): Merge conflict in .beads/beads.jsonl

$ git checkout --theirs .beads/beads.jsonl && bd import && git add && git commit && git push
# Pushed successfully

$ git status
modified: .beads/beads.jsonl  # DIRTY AGAIN!
```

## Lost Data in Recent Session

- bd-ry1u closure (lost in merge)
- Comments on bd-08fd, bd-23a8, bd-6049, bd-87a0 (lost)
- Dependencies that existed only in local DB

## Potential Solutions

1. **Use beads-merge tool** - Implement proper 3-way JSONL merge (bd-bzfy)
2. **Smarter conflict resolution** - Detect when `--theirs` will lose data, warn user
3. **Sync validation** - Check if JSONL == DB after merge, re-export if needed
4. **Daemon awareness** - Pause auto-export during merge resolution
5. **Transaction log** - Replay local changes after merge instead of losing them

## Related Issues

- bd-bzfy (beads-merge integration)
- Possibly related to daemon auto-export behavior

## Solution Implemented

Fixed the recurring dirty state after merge conflicts by adding **sync validation** before re-exporting.

### Root Cause
Lines 217-237 in `sync.go` unconditionally re-exported DB to JSONL after every import, even when they were already in sync. This created an infinite loop:
1. User runs `bd sync` which pulls and imports remote JSONL
2. Sync unconditionally re-exports DB (which has local changes)
3. JSONL becomes dirty immediately
4. Repeat

### Fix
Added `dbNeedsExport()` function in `integrity.go` that checks:
- If JSONL exists
- If DB modification time is newer than JSONL
- If DB and JSONL issue counts match

Now `bd sync` only re-exports if DB actually has changes that differ from JSONL.

### Changes
- Added `dbNeedsExport()` in `cmd/bd/integrity.go` (lines 228-271)
- Updated `sync.go` lines 217-251 to check before re-exporting
- Added comprehensive tests in `cmd/bd/sync_merge_test.go`

### Testing
All tests pass including 4 new tests:
- `TestDBNeedsExport_InSync` - Verifies no export when synced
- `TestDBNeedsExport_DBNewer` - Detects DB modifications
- `TestDBNeedsExport_CountMismatch` - Catches divergence
- `TestDBNeedsExport_NoJSONL` - Handles missing JSONL

This prevents the weeks-long frustration of merge conflicts causing infinite dirty loops.


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.


Add bd deleted command for audit trail

Parent: bd-imj

Add command to view deletion history.

Usage:
  bd deleted              # Show recent deletions (last 7 days)
  bd deleted --since=30d  # Show deletions in last 30 days
  bd deleted --all        # Show all tracked deletions
  bd deleted bd-xxx       # Show deletion details for specific issue

Output format:
  bd-xxx  2025-11-25 10:00  stevey  duplicate of bd-yyy
  bd-yyy  2025-11-25 10:05  claude  cleanup

Acceptance criteria:
- List deletions with timestamp, actor, reason
- Filter by time range
- Lookup specific issue ID
- JSON output option for scripting


Refactor check-health DB access to avoid repeated path resolution

The runCheckHealth lightweight checks (hintsDisabled, checkVersionMismatch, checkSyncBranchQuick) each have duplicated database path resolution logic. Extract a helper function to DRY this up.


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


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

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

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

**Proposed behavior:**
- `bd sync --squash` accumulates pending exports
- Only commits when explicitly requested or on session end
- Default behavior unchanged (immediate commits for orchestration)

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

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


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


Windows binaries lack SQLite support (GH #253)

Windows users installing via install.ps1 get "sql: unknown driver sqlite" error. Root cause: GoReleaser was building with CGO_ENABLED=0, which excludes SQLite driver.

Fixed by:
1. Enabling CGO in .goreleaser.yml
2. Installing MinGW cross-compiler in release workflow
3. Splitting builds per platform to set correct CC for Windows

Needs new release to fix for users.


Create npm package structure with package.json

Set up initial npm package structure for @beads/bd:

## Files to create
- npm/package.json - Package metadata, dependencies, scripts
- npm/bin/bd - CLI wrapper script that invokes native binary
- npm/.gitignore - Ignore downloaded binaries
- npm/README.md - Installation and usage instructions

## package.json structure
- Name: @beads/bd (scoped package)
- Main: index.js (exports binary path)
- Bin: bin/bd (CLI entry point)
- Scripts: postinstall (download binary)
- Keywords: issue-tracker, cli, beads, bd
- License: MIT

## Bin wrapper
Simple Node.js script that:
- Spawns native binary with child_process.spawn
- Passes through all arguments and stdio
- Exits with binary's exit code


Add unit tests for getLocalSyncBranch

The new getLocalSyncBranch() function in autoimport.go (bd-0is fix) has no dedicated unit tests.

Should test:
- Returns empty string when no config.yaml exists
- Returns empty string when config.yaml has no sync-branch key
- Returns value when sync-branch is set with double quotes
- Returns value when sync-branch is set with single quotes
- Returns value when sync-branch is set without quotes
- Returns BEADS_SYNC_BRANCH env var when set (takes precedence)
- Handles edge cases: empty file, whitespace-only lines, comments

Location: cmd/bd/autoimport_test.go


Update daemon commit logic for separate branch

Modify daemon to use worktree for commits when sync.branch configured.

Tasks:
- Update internal/daemon/server_export_import_auto.go
- Detect sync.branch configuration
- Ensure worktree exists before commit
- Sync JSONL to worktree
- Commit in worktree context
- Push to configured branch
- Fallback to current behavior if sync.branch not set
- Handle git errors (network, permissions, conflicts)

Estimated effort: 3-4 days


Update flake.nix vendorHash after dependency bumps

The Nix flake CI is failing due to stale vendorHash.

Error from CI:
```
error: hash mismatch in fixed-output derivation beads-0.24.4-go-modules.drv
```

Root cause: go.mod/go.sum was updated by dependabot (wazero, anthropic-sdk-go, etc.) but flake.nix vendorHash wasn't updated.

## Immediate Fix
Run `nix build` locally, get the correct hash from the error message, update flake.nix.

## Long-term Fix
Add a GitHub Action that auto-updates vendorHash when go.mod/go.sum changes:
1. Trigger on push to main when go.mod/go.sum changes
2. Run `nix build` to get the new hash from the error
3. Update flake.nix with the correct hash
4. Commit and push


Add optional post-merge git hook example for bd sync

Create example git hook that auto-runs bd sync after git pull/merge.

Add to examples/git-hooks/:
- post-merge hook that checks if .beads/issues.jsonl changed
- If changed: run `bd sync` automatically
- Make it optional/documented (not auto-installed)

Benefits:
- Zero-friction sync after git pull
- Complements auto-detection as belt-and-suspenders

Note: post-merge hook already exists for pre-commit/post-merge. Extend it to support sync.


Fix TestMigrateHashIDs test failure

Test failure in cmd/bd/migrate_hash_ids_test.go:100 - New ID bd-09970281 for bd-1 is not a hash ID. This test is validating the hash ID migration but the generated ID doesn't match the expected format.


Duplicate DefaultRetentionDays constants

## Problem

There are now two constants for the same value:

1. `deletions.DefaultRetentionDays = 7` in `internal/deletions/deletions.go:184`
2. `configfile.DefaultDeletionsRetentionDays = 7` in `internal/configfile/configfile.go:102`

## Impact
- DRY violation
- Risk of values getting out of sync
- Confusing which one to use

## Fix
Remove the constant from `deletions` package and have it import from `configfile`, or create a shared constants package.


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


~/src/beads daemon has 'sql: database is closed' errors - zombie daemon


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.


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

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

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

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

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

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

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


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

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

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

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

## Proposed Architecture

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

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

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

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

## Implementation Phases

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

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

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

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

## Risks & Mitigations

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

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

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

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

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

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

## References
- Automerge: https://automerge.org
- Yjs: https://docs.yjs.dev
- Loro: https://loro.dev
- CRDT theory: Shapiro et al, A comprehensive study of CRDTs
- Related issues: bd-e6d71828, bd-7a2b58fc,-1


bd repair-deps - Orphaned dependency cleaner

Find and fix orphaned dependency references.

Implementation:
- Scan all issues for dependencies pointing to non-existent issues
- Report orphaned refs
- Auto-fix with --fix flag
- Interactive mode with --interactive

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


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

### ValidateState tests
- .beads/ exists → success
- .beads/ missing → appropriate error
- git dirty state detection

### CountImpact tests
- Empty .beads/ → zero counts
- With issues → correct count (open vs closed)
- With tombstones → correct count
- Returns HasUncommitted correctly

### Backup tests
- Creates backup with correct timestamp format
- Preserves all files and permissions
- Returns correct path
- Handles missing .beads/ gracefully
- Errors on pre-existing backup dir

### Git operation tests
- CheckGitState detects dirty, detached, not-a-repo
- GitRemoveBeads removes correct files
- GitCommitReset creates commit with message
- Operations skip gracefully when not in git repo

### Reset tests (with mocks/temp dirs)
- Soft reset removes files, calls init
- Hard reset includes git operations
- Dry run doesn't modify anything
- SkipInit flag prevents re-initialization
- Daemon killall is called
- Backup is created when requested

## Approach
- Can start with interface definitions (TDD style)
- Use testify for assertions
- Create temp directories for isolation
- Mock git operations where needed
- Test completion depends on implementation tasks

## File Location
`internal/reset/reset_test.go`
`internal/reset/backup_test.go`
`internal/reset/git_test.go`


Blocking issue


Test label


Add depth-based batch creation in upsertIssues

Replace single batch creation with depth-level batching (max depth 3). Create issues at depth 0, then 1, then 2, then 3. Prevents parent validation errors when importing hierarchical issues in same batch. File: internal/importer/importer.go:534-546


Add uninstall documentation (GH #445)


Add index on deleted_at for TTL queries

Per bd-2m7 design: 'Index for efficient tombstone filtering' was recommended. The current implementation does NOT add an index on deleted_at.

For TTL cleanup queries like 'SELECT * FROM issues WHERE deleted_at < datetime(now, -30 days)', this will require a full table scan.

Add to migration or new migration:
CREATE INDEX IF NOT EXISTS idx_issues_deleted_at ON issues(deleted_at) WHERE deleted_at IS NOT NULL


Improve test coverage for cmd/bd/doctor/fix (23.9% → 50%)

The doctor/fix package has only 23.9% test coverage. The doctor fix functionality is important for troubleshooting.

Current coverage: 23.9%
Target coverage: 50%


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

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


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


Use stable repo identifiers instead of full paths in metadata keys

## Problem
bd-ar2.2 uses full absolute paths as metadata key suffixes:
```go
updateExportMetadata(exportCtx, store, path, log, path)
// Creates: last_import_hash:/Users/stevey/src/cino/beads/repo1/.beads/issues.jsonl
```

## Issues
1. Absolute paths differ across machines/clones
2. Keys are very long
3. If user moves repo or clones to different path, metadata becomes orphaned
4. Metadata won't be portable across team members

## Better Approach
Use source_repo identifiers from multi-repo config (e.g., ".", "../frontend"):
```go
// Get mapping of jsonl_path -> source_repo identifier
for _, path := range multiRepoPaths {
    repoKey := getRepoKeyForPath(path)  // e.g., ".", "../frontend"
    updateExportMetadata(exportCtx, store, path, log, repoKey)
}
```

This creates stable keys like:
- `last_import_hash:.`
- `last_import_hash:../frontend`

## Related
Depends on bd-ar2.10 (hasJSONLChanged update)


GH#518: Document bd setup command

bd setup (e.g. bd setup cursor) is undocumented. Users only find it by grepping source. Need proper docs. See: https://github.com/steveyegge/beads/issues/518


bd ready taking 5 seconds with 132 issues (89 closed)

User reports bd ready is annoyingly slow on M2 Mac - 5 seconds for 132 issues (89 closed). Started noticing after hash-based IDs update. Need to investigate performance regression. Reported in GH #243.

Root cause identified: Not a query performance issue, but stale daemon locks causing 5s timeout delays.

Fixed in bd-ndyz (closed) via 5 sub-issues:
- bd-expt: Fast-fail socket checks (200ms timeout)
- bd-wgu4: Lock probe before RPC attempts
- bd-1mzt: Self-heal stale artifacts
- bd-vcg5: Panic recovery + socket cleanup
- bd-j7e2: RPC diagnostics (BD_RPC_DEBUG)

All fixes merged. Ready for v0.22.2 release.


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


Add import.orphan_handling config option

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


Test update


Delete collision resolution code

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

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

## Code to Remove from Existing Files

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

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

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

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

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

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

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

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

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

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


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


Fix `bd sync` failure due to daemon auto-export timestamp skew

`bd sync` fails with false-positive "JSONL is newer than database" after daemon auto-export.
Root Cause: Daemon exports local changes to JSONL, updating its timestamp. `bd sync` sees JSONL.mtime > DB.mtime and assumes external changes, blocking export.
Proposed Fixes:
1. `bd sync` auto-imports if timestamp matches but content differs (or just auto-imports).
2. Content-based comparison instead of timestamp only.