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


Perpetual 'JSONL file hash mismatch detected (bd-160)' warning

After cleanup operations, every bd command shows:
```
⚠  WARNING: JSONL file hash mismatch detected (bd-160)
  This indicates JSONL and export_hashes are out of sync.
  Clearing export_hashes to force full re-export.
```

The warning appears repeatedly even after the hash is supposedly cleared. This suggests either:
1. The clear isn't persisting
2. Something keeps causing the mismatch
3. The detection logic has a bug

**Impact:** Warning fatigue - users ignore it, may miss real issues.

**Expected behavior:** After clearing export_hashes once, subsequent operations should not show the warning unless there's a new mismatch.


Fix G304 potential file inclusion in cmd/bd/tips.go:259

Linting issue: G304: Potential file inclusion via variable (gosec) at cmd/bd/tips.go:259:18. Error: if data, err := os.ReadFile(settingsPath); err == nil {


bd doctor: handle missing daemon registry gracefully

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


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


bd import fails on duplicate external_ref with no resolution options

When JSONL contains duplicate external_ref values (e.g., two issues both have external_ref='BS-170'), bd import fails entirely with no resolution options.

User must manually edit JSONL to remove duplicates, which is error-prone.

Example error:
```
batch import contains duplicate external_ref values:
external_ref 'BS-170' appears in issues: [opal-39 opal-43]
```

Should handle this similar to duplicate issue detection - offer to merge, pick one, or clear duplicates.


Add label-based filtering to bd ready command

Allow filtering ready work by labels to help organize work by sprint, week, or category.

Example usage:
  bd ready --label week1-2
  bd ready --label frontend,high-priority

This helps teams organize work into batches and makes it easier for agents to focus on specific categories of work.

Implementation notes:
- Add --label flag to ready command
- Support comma-separated labels (AND logic)
- Should work with existing ready work logic (unblocked issues)


Test database naming


Imperator lacks mail hooks for automatic notification


Add integration tests for bd reset command

End-to-end integration tests for the reset command.

## Test Scenarios

### Basic reset
1. Init beads, create some issues
2. Run bd reset --force
3. Verify .beads/ is fresh, issues gone

### Hard reset
1. Init beads, create issues, commit
2. Run bd reset --hard --force
3. Verify git history has reset commits

### Backup functionality
1. Init beads, create issues
2. Run bd reset --backup --force
3. Verify backup exists with correct contents
4. Verify main .beads/ is reset

### Dry run
1. Init beads, create issues
2. Run bd reset --dry-run
3. Verify nothing changed

### Confirmation prompt
1. Init beads
2. Run bd reset (no --force)
3. Verify prompts for confirmation
4. Test both y and n responses

## Location
tests/integration/reset_test.go or similar


Fix multi-round convergence for N-way collisions

**Summary:** Multi-round collision resolution was identified as a critical issue preventing complete synchronization across distributed clones. The problem stemmed from incomplete final pulls that didn't fully propagate all changes between system instances.

**Key Decisions:**
- Implement multi-round sync mechanism
- Ensure bounded convergence (≤N rounds)
- Guarantee idempotent import without data loss

**Resolution:** Developed a sync strategy that ensures all clones converge to the same complete set of issues, unblocking the bd-cbed9619 epic and improving distributed system reliability.


Update bd compact to prune expired tombstones

Modify compaction to identify and remove expired tombstones from issues.jsonl. Report count of removed tombstones. Per design bd-1r5.


Fix failing integration tests in beads-mcp

The `beads-mcp` test suite has failures in `tests/test_bd_client_integration.py` (assertion error in `test_init_creates_beads_directory`) and errors in `tests/test_worktree_separate_dbs.py` (setup failures finding database). These need to be investigated and fixed to ensure a reliable CI baseline.


Add BEADS_DAEMON_MODE flag handling

Add environment variable BEADS_DAEMON_MODE (values: poll, events). Default to 'poll' for Phase 1. Wire into daemon startup to select runEventLoop vs runEventDrivenLoop.


Test issue 2


Fix row iterator resource leak in recursive dependency queries

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

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

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

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

Impact: Connection exhaustion during dependency traversal


Fix dependency naming inconsistencies (GH #440)

Parent-child dependency documentation is backwards and UI labels are confusing.\n\nProblems:\n1. DEPENDENCIES.md says 'bd dep add PARENT CHILD' but this is rejected\n2. bd show displays epic children under 'Blocks' instead of 'Children'\n3. bd dep tree EPIC shows nothing (need --direction=up)\n4. Inconsistent with 'bd epic status' which uses 'children'\n\nSee: https://github.com/steveyegge/beads/issues/440


Add unit tests for import ordering

Test topological sort: import [child, parent] should succeed, import [parent.1.2, parent, parent.1] should sort correctly. Verify depth-based batching works. Test max depth limits.


Remove Daemon Storage Cache

The daemon's multi-repo storage cache is the root cause of stale data bugs. Since global daemon is deprecated, we only ever serve one repository, making the cache unnecessary complexity. This epic removes the cache entirely for simpler, more reliable direct storage access.


Add --parent flag to bd onboard output

bd onboard didn't document --parent flag for epic subtasks, causing AI agents to guess wrong syntax. Added --parent example and CLI help section pointing to bd <cmd> --help.

Fixes: https://github.com/steveyegge/beads/issues/402


Sync protection mechanism resurrects deleted issues

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

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

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

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

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

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

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


CRITICAL: bd sync exports before pull, allowing stale DB to corrupt JSONL statuses

## Root Cause

The fix in bd-53c (reverse ZFC check) only checks COUNTS, not content. The real corruption happens when:

1. Polecat A has stale DB with old status values (e.g., status=closed for issues that are now open on remote)
2. Polecat A runs bd sync:
   - **Export FIRST**: DB (status=closed) → JSONL (overwrites correct status=open)
   - Commit: Stale JSONL committed
   - Pull: 3-way merge with remote
   - Merge uses 'closed wins' rule → status stays closed
3. Polecat A pushes → Remote now corrupted with status=closed

## Why bd-53c didn't fix it

The reverse ZFC check compares COUNTS:
```go
if jsonlCount > dbCount  // Only catches count mismatch
```

But in the corruption scenario:
- JSONL count = 5, DB count = 5 (same count!)
- Only the STATUS field differs

## The Real Fix

**PULL BEFORE EXPORT**. The sync order must be:
1. Pull from remote (get latest state)
2. Import merged JSONL to DB
3. THEN export DB changes (if any)

Current order is: Export → Commit → Pull → Import → Push

This is a fundamental architecture change to the sync flow.

## Workaround

Use --no-auto-flush and manually control the sync order, or disable daemon auto-export.

## Evidence

From user investigation:
- At 595b7943 (13:20:30): 5 open issues
- At 10239812 (13:28:39): 0 open issues
- All 5 issues had their status changed from open to closed
- Count stayed at 5 (not a deletion issue)


Add uninstall documentation (GH #445)


Implement bd migrate command

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


Test P1 priority


Optimize --sandbox mode: skip FlushManager

When `--sandbox` mode is enabled, skip FlushManager creation entirely.

## Current Behavior
`main.go:561` always creates FlushManager:
```go
flushManager = NewFlushManager(autoFlushEnabled, getDebounceDuration())
```

Even with `--sandbox` (which sets `noAutoFlush=true`), the FlushManager goroutine is still running.

## Proposed Change
```go
// Only create FlushManager if we might actually need it
if !sandboxMode && autoFlushEnabled {
    flushManager = NewFlushManager(true, getDebounceDuration())
}
```

## Handle nil FlushManager
Update PersistentPostRun to handle nil:
```go
if flushManager != nil && !skipFinalFlush {
    if err := flushManager.Shutdown(); err != nil {
        // ...
    }
}
```

## Synchronous Export Fallback
When sandbox mode + write operation occurs, do synchronous export:
```go
if sandboxMode && isDirty {
    performSynchronousExport()
}
```

## Also: Default lock-timeout to 100ms in sandbox mode
In PersistentPreRun, when sandboxMode is detected:
```go
if sandboxMode {
    noDaemon = true
    noAutoFlush = true
    noAutoImport = true
    if lockTimeout == 30*time.Second { // Not explicitly set
        lockTimeout = 100 * time.Millisecond
    }
}
```

Part of bd-olc1


Q4 Platform Improvements

## Overview

[Describe the high-level goal and scope of this epic]

## Success Criteria

- [ ] Criteria 1
- [ ] Criteria 2
- [ ] Criteria 3

## Background

[Provide context and motivation]

## Scope

**In Scope:**
- Item 1
- Item 2

**Out of Scope:**
- Item 1
- Item 2


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.


mergeTombstones does not handle empty DeletedAt timestamps

The mergeTombstones() function uses isTimeAfter() to compare deleted_at timestamps, but does not handle the edge case where one or both tombstones have empty DeletedAt fields. Current behavior with isTimeAfter() semantics: empty string returns false when compared, which means if left.DeletedAt is empty, right wins. If both are empty, behavior depends on isTimeAfter tie-breaker. This should be explicitly handled with a comment or validation. Recommendation: Add explicit handling or documentation for invalid tombstones. Files: internal/merge/merge.go:448-456


Implement content-hash based collision resolution for deterministic convergence

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

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

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

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

Rename detection successfully implemented and tested!

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

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

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

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


Test issue 0


Improve test coverage for internal/lockfile (42.0% → 60%)

The lockfile package has only 42.0% test coverage. Lock file handling is critical for preventing data corruption in concurrent scenarios.

Current coverage: 42.0%
Target coverage: 60%


Command Set Standardization & Flag Consistency

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

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

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

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


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


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


Improve test coverage for cmd/bd/setup (28.4% → 50%)

The setup package has only 28.4% test coverage. Setup commands are critical for first-time user experience.

Current coverage: 28.4%
Target coverage: 50%


Improve test coverage for internal/beads (48.1% → 70%)

The beads package is a core package with 48.1% coverage. As the primary package for issue management, it should have at least 70% coverage.

Current coverage: 48.1%
Target coverage: 70%


Implement "bd daemons logs" subcommand

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


Test child daemon mode


Add git operations for --hard reset

Implement git integration for hard reset mode.

## Operations Needed
1. `git rm -rf .beads/*.jsonl` - remove data files from index
2. `git commit -m 'beads: reset to clean state'` - commit removal
3. After re-init: `git add .beads/` and commit fresh state

## Edge Cases to Handle
- Uncommitted changes in .beads/ - warn or error
- Detached HEAD state - warn, maybe block
- Git not initialized - skip git ops, warn
- Git operations fail mid-way - clear error messaging

## Interface
```go
type GitState struct {
    IsRepo       bool
    IsDirty      bool   // uncommitted changes in .beads/
    IsDetached   bool   // detached HEAD
    Branch       string // current branch name
}

func CheckGitState(beadsDir string) (*GitState, error)
func GitRemoveBeads(beadsDir string) error
func GitCommitReset(message string) error
func GitAddAndCommit(beadsDir, message string) error
```

## Location
`internal/reset/git.go` - keep with reset package for now

Note: Codebase has no central git package. internal/compact/git.go is compact-specific.
Future refactoring could extract shared git utilities, but YAGNI for now.


Use -short flag in CI for PR checks

Update CI configuration to use -short flag for PR checks, run full tests nightly.

The slow tests already support testing.Short() and will be skipped.

Expected savings: ~20 seconds for PR checks (fast tests only)

Implementation:
- Update .github/workflows/ci.yml to add -short flag for PR tests
- Create/update nightly workflow for full test runs
- Update README/docs about test strategy

File: .github/workflows/ci.yml:30


Consolidate check-health DB opens into single connection

The --check-health flag opens the database 3 separate times (once per quick check). Consolidate into a single DB open for better performance, especially on slower filesystems.


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


Issue 1 to reopen


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


Test sync branch setup


Fix GH #227: bd edit broken pipe errors

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

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

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


Deletion propagation via deletions manifest

## Problem

When `bd cleanup -f` or `bd delete` removes issues in one clone, those deletions don't propagate to other clones. The import logic only creates/updates, never deletes. This causes "resurrection" where deleted issues reappear.

## Root Cause

Import sees DB issues not in JSONL and assumes they're "local unpushed work" rather than "intentionally deleted upstream."

## Solution: Deletions Manifest

Add `.beads/deletions.jsonl` - an append-only log of deleted issue IDs with metadata.

### Format
```jsonl
{"id":"bd-xxx","ts":"2025-11-25T10:00:00Z","by":"stevey"}
{"id":"bd-yyy","ts":"2025-11-25T10:05:00Z","by":"claude","reason":"duplicate of bd-zzz"}
```

### Fields
- `id`: Issue ID (required)
- `ts`: ISO 8601 UTC timestamp (required)
- `by`: Actor who deleted (required)
- `reason`: Optional context ("cleanup", "duplicate of X", etc.)

### Import Logic
```
For each DB issue not in JSONL:
  1. Check deletions manifest → if found, delete from DB
  2. Fallback: check git history → if found, delete + backfill manifest
  3. Neither → keep (local unpushed work)
```

### Conflict Resolution
Simultaneous deletions from multiple clones are handled naturally:
- Append-only design means both clones append their deletion records
- On merge, file contains duplicate entries (same ID, different timestamps)
- `LoadDeletions` deduplicates by ID (keeps any/first entry)
- Result: deletion propagates correctly, duplicates are harmless

### Pruning Policy
- Default retention: 7 days (configurable via `deletions.retention_days`)
- Auto-compact during `bd sync` is **opt-in** (disabled by default)
- Hard cap: `deletions.max_entries` (default 50000)
- Git fallback handles pruned entries (self-healing)

### Self-Healing
When git fallback catches a resurrection (pruned entry), it backfills the manifest. One-time git scan cost, then fast again.

### Size Estimates
- ~80 bytes/entry
- 7-day retention with 100 deletions/day = ~56KB
- Git compressed: ~10KB

## Benefits
- ✅ Deletions propagate across clones
- ✅ O(1) lookup (no git scan in normal case)
- ✅ Works in shallow clones
- ✅ Survives history rewrite
- ✅ Audit trail (who deleted what when)
- ✅ Self-healing via git fallback
- ✅ Bounded size via time-based pruning

## References
- Investigation session: 2025-11-25
- Related: bd-2q6d (stale database warnings)


Issue 1


Wrap git operations in GitClient interface

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


GH#505: Add bd reset/wipe command

Add command to cleanly reset/wipe beads database. User reports painful manual process to start fresh. See GitHub issue #505.


Optimize Memory backend GetIssueByExternalRef with index

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

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

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

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


Polecats get stuck on interactive shell prompts (cp/mv/rm -i)

During swarm operations, polecats frequently get stuck waiting for interactive prompts from shell commands like:
- cp prompting 'overwrite file? (y/n)'
- mv prompting 'overwrite file? (y/n)'
- rm prompting 'remove file?'

This happens because macOS aliases or shell configs may have -i flags set by default.

Root cause: Claude Code runs commands that trigger interactive confirmation prompts, but cannot respond to them, causing the agent to hang indefinitely.

Observed in: Multiple polecats during GH issues swarm (Dec 2024)
- Derrick, Roustabout, Prospector, Warboy all got stuck on y/n prompts

Suggested fixes:
1. AGENTS.md should instruct agents to always use -f flag with cp/mv/rm
2. Polecat startup could set shell aliases to use non-interactive versions
3. bd prime hook could include guidance about non-interactive commands
4. Consider detecting stuck prompts and auto-recovering


Add bd reset command for clean slate restart

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

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

**Current workaround** (from maphew):
```bash
bd daemons killall
git rm .beads/*.jsonl
git commit -m 'remove old issues'
rm .beads/*
bd init
bd onboard
```

**Desired:** A proper `bd reset` command that handles this cleanly and safely.


Integrate beads-merge tool by @neongreen

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

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

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

**Integration options**:

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

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

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

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

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

Next: Wait for response before proceeding with integration.

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

Next: Vendor beads-merge with full attribution


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


Create cmd/bd/daemon_event_loop.go (~200 LOC)

Implement runEventDrivenLoop to replace polling ticker. Coordinate FileWatcher, mutation events, debouncer. Include health check ticker (60s) for daemon validation.


Add close_reason to JSONL export format documentation

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

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

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

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

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


Auth tokens expire too quickly

## Summary

[Brief description of the bug]

## Steps to Reproduce

1. Step 1
2. Step 2
3. Step 3

## Expected Behavior

[What should happen]

## Actual Behavior

[What actually happens]

## Environment

- OS: [e.g., macOS 15.7.1]
- Version: [e.g., bd 0.20.1]
- Additional context: [any relevant details]

## Additional Context

[Screenshots, logs, or other relevant information]


bd message: Add input validation for --importance flag

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

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

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

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


Remove unused global daemon infrastructure (internal/daemonrunner/)

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

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

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

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

Total cleanup: ~1,500 LOC


Add tests for getMultiRepoJSONLPaths() edge cases

From bd-xo6b code review: Missing test coverage for getMultiRepoJSONLPaths() edge cases.

Current test gaps:
- No tests for empty paths in config
- No tests for duplicate paths
- No tests for tilde expansion
- No tests for relative paths
- No tests for symlinks
- No tests for paths with spaces
- No tests for invalid/non-existent paths

Test cases needed:

1. Empty path handling:
   Primary = empty, Additional = [empty]
   Expected: Should either use . as default or error gracefully

2. Duplicate detection:
   Primary = ., Additional = [., ./]
   Expected: Should return unique paths only

3. Path normalization:
   Primary = ~/repos/main, Additional = [../other, ./foo/../bar]
   Expected: Should expand to absolute canonical paths

4. Partial failure scenarios:
   What if snapshot capture succeeds for repos 1-2 but fails on repo 3?
   Test that system does not end up in inconsistent state

Files:
- cmd/bd/deletion_tracking_test.go (add new tests)

Dependencies:
Depends on fixing getMultiRepoJSONLPaths() path normalization first.


getLocalSyncBranch silently ignores YAML parse errors

In autoimport.go:170-172, YAML parsing errors are silently ignored. If a user has malformed YAML in config.yaml, sync-branch will just silently be empty with no feedback.

Recommendation: Add debug logging since this function is only called during auto-import, and debugging silent failures is painful.

Add: debug.Logf("Warning: failed to parse config.yaml: %v", err)


Beads commands operate on stale database without warning

All beads read operations should validate database is in sync with JSONL before proceeding.

**Current Behavior:**
- Commands can query/read from stale database
- Only mutation operations (like 'bd sync') check if JSONL is newer
- User gets incorrect results without realizing database is out of sync

**Expected Behavior:**
- All beads commands should have pre-flight check for database freshness
- If JSONL is newer than database, refuse to operate with error: "Database out of sync. Run 'bd import' first."
- Same safety check that exists for 'bd sync' should apply to ALL operations

**Impact:**
- Users make decisions based on incomplete/outdated data
- Silent failures lead to confusion (e.g., thinking issues don't exist when they do)
- Similar to running git commands on stale repo without being warned to pull

**Example:**
- Searched for bd-g9eu issue file: not found
- Issue exists in .beads/issues.jsonl (in git)
- Database was stale, but no warning was given
- Led to incorrect conclusion that issue was already closed/deleted

## Implementation Complete

**Phase 1: Created staleness check (cmd/bd/staleness.go)**
- ensureDatabaseFresh() function checks JSONL mtime vs last_import_time
- Returns error with helpful message when database is stale
- Auto-skips in daemon mode (daemon has auto-import)

**Phase 2: Added to all read commands**
- list, show, ready, status, stale, info, duplicates, validate
- Check runs before database queries in direct mode
- Daemon mode already protected via checkAndAutoImportIfStale()

**Phase 3: Code Review Findings**
See follow-up issues:
- bd-XXXX: Add warning when staleness check errors
- bd-YYYY: Improve CheckStaleness error handling
- bd-ZZZZ: Refactor redundant daemon checks (low priority)

**Testing:**
- Build successful: go build ./cmd/bd
- Binary works: ./bd --version
- Ready for manual testing

**Next Steps:**
1. Test with stale database scenario
2. Implement review improvements
3. Close issue when tests pass


Improve sync branch divergence recovery UX

When the sync branch diverges significantly from remote (e.g., 89 local vs 36 remote commits), bd sync fails with a confusing rebase conflict error.

**Current behavior:**
```
Error committing to sync branch: failed to push from worktree: push failed and recovery rebase also failed
error: could not apply 8712f35... bd sync: 2025-12-13 07:30:04
CONFLICT (content): Merge conflict in .beads/issues.jsonl
```

User is left in a broken state with no clear recovery path.

**Expected behavior:**
1. Detect significant divergence before attempting sync
2. Offer options: 'Sync branch diverged. Choose: (1) Reset to remote (2) Force push local (3) Manual merge'
3. Provide clear recovery commands if automatic recovery fails

**Context:** This happened after multiple clones synced different states. The workaround was manually resetting the sync branch worktree to remote.


Update bash-agent example with Agent Mail integration

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

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

File: examples/bash-agent/agent.sh


bd message: Fix inefficient client-side filtering for --unread-only


Add test coverage for internal/config package


Test parent issue


bd info whats-new missing releases

Current release is v0.25.1 but `bd info --whats-new` stops at v0.23.0, indicating something is missing in the create new release workflow.


Extract daemon sync functions to daemon_sync.go


Debug MCP Agent Mail tool execution errors

**EXTERNAL WORK**: Debug the standalone MCP Agent Mail server (separate from beads integration).

The Agent Mail server runs as an independent service at ~/src/mcp_agent_mail. This is NOT beads code - it's a separate GitHub project we're evaluating for optional coordination features.

Current Issue:
- MCP API endpoint returns errors when calling ensure_project tool
- Error: "Server encountered an unexpected error while executing tool"
- Core HTTP server works, web UI functional, but tool wrapper layer fails

Server Details:
- Location: ~/src/mcp_agent_mail (separate repo)
- Repository: https://github.com/Dicklesworthstone/mcp_agent_mail
- Runs on: http://127.0.0.1:8765
- Bearer token: In .env file

Investigation Steps:
1. Check tool execution logs for full stack trace
2. Verify Git storage initialization at ~/.mcp_agent_mail_git_mailbox_repo
3. Review database setup (storage.sqlite3)
4. Test with simpler MCP tools if available
5. Compare with working test cases in tests/

Why This Matters:
- Blocks [deleted:bd-6hji] (testing file reservations)
- Need working MCP API to validate Agent Mail benefits
- Proof of concept for lightweight beads integration later

Note: The actual beads integration (bd-wfmw) will be lightweight HTTP client code only.


Support --reason flag in daemon mode for reopen command

The reopen.go command has a TODO at line 61 to add reason as a comment once RPC supports AddComment. Currently --reason flag is ignored in daemon mode with a warning.


Plugin should provide hooks instead of requiring global install


Integration test: mutation to export latency

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


bd doctor: add --dry-run flag to preview fixes without applying

Currently bd doctor --fix applies all fixes. Add a --dry-run flag to preview what would be fixed without making changes. This helps users understand the impact before committing to repairs.


Clarify JSONL size bounds with multi-repo

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

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

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

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


Add TestThreeCloneCollision for regression protection

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

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

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

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


Vendor beads-merge Go code into internal/merge/

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

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

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


Configure database connection pool limits for daemon mode

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

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

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

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

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

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

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

Effort: 1 hour


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


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

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

Current coverage: 24.8% (improved from 20.2%)

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

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

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

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

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


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

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

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

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

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


GH#522: Add --type flag to bd update command

Allow changing issue type (task/epic/bug/feature) via bd update --type. Storage layer already supports it. Needed for TUI tools like Abacus. See: https://github.com/steveyegge/beads/issues/522


Issue 2 to reopen


Implement auto-merge functionality in duplicates command

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


No logging/debugging for tombstone resurrection events

Per the design document bd-zvg Open Question 1: Should resurrection log a warning? Recommendation was Yes. Currently, when an expired tombstone loses to a live issue (resurrection), there is no logging or debugging output. This makes it hard to understand why an issue reappeared. Recommendation: Add optional debug logging when resurrection occurs, e.g., Issue bd-abc resurrected (tombstone expired). Files: internal/merge/merge.go:359-366, 371-378, 400-405, 410-415


Clean up merge conflict artifacts in .beads directory

After resolving merge conflicts in .beads/beads.jsonl, leftover artifacts remain as untracked files:
- .beads/beads.base.jsonl
- .beads/beads.left.jsonl

These appear to be temporary files created during merge conflict resolution.

Options to fix:
1. Add these patterns to .beads/.gitignore automatically
2. Clean up these files after successful merge resolution
3. Document that users should delete them manually
4. Add a check in 'bd sync' or 'bd doctor' to detect and remove stale merge artifacts

Preferred solution: Add *.base.jsonl and *.left.jsonl patterns to .beads/.gitignore during 'bd init', and optionally clean them up automatically after successful import.


bd export only outputs 1 issue after auto-import corrupts database

When auto-import runs and purges issues (due to git history backfill bug), subsequent 'bd export' only exports 1 issue even though the database should have many.

Reproduction:
1. Have issues.jsonl with 55 issues
2. Auto-import triggers and purges all issues via git history backfill
3. Run 'bd export' - only exports 1 issue (the last one created before corruption)

The database gets into an inconsistent state where most issues are purged but export doesn't realize this.

Workaround: Rebuild database from scratch with 'rm .beads/beads.db && bd init --prefix bd'


Unit tests for FileWatcher

Test watcher detects JSONL changes. Test git ref changes trigger import. Test debounce integration. Test watcher recovery from file removal/rename.


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


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


Add validation for duplicate external_ref in batch imports

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

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

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

Related: bd-1022


Prefix detection treats embedded hyphens as prefix delimiters

The prefix detection logic in bd import incorrectly identifies issues like 'vc-baseline-test' and 'vc-92cl-gate-test' as having different prefixes ('vc-baseline-' and 'vc-92cl-gate-') instead of recognizing them as having the standard 'vc-' prefix with hyphenated suffixes.

This breaks import with error: 'prefix mismatch detected: database uses vc- but found issues with prefixes: [vc-92cl-gate- (1 issues) vc-baseline- (1 issues)]'

The prefix should be determined by the pattern: prefix is everything up to and including the first hyphen. The suffix can contain hyphens without being treated as part of the prefix.

Example problematic IDs:
- vc-baseline-test (detected as prefix: vc-baseline-)
- vc-92cl-gate-test (detected as prefix: vc-92cl-gate-)
- vc-test (correctly detected as prefix: vc-)

Impact: Users cannot use descriptive multi-part IDs without triggering false prefix mismatch errors.


bd cleanup should also prune expired tombstones

## Problem

`bd cleanup` deletes closed issues (converting them to tombstones) but does NOT prune expired tombstones. Users expect 'cleanup' to do comprehensive cleanup.

## Current Behavior

1. `bd cleanup --force` → converts closed issues to tombstones
2. Expired tombstones (>30 days) remain in issues.jsonl
3. User must separately run `bd compact` to prune tombstones
4. `bd doctor` warns about expired tombstones: 'Run bd compact to prune'

## Expected Behavior

`bd cleanup` should also prune expired tombstones from issues.jsonl.

## Impact

With v0.30.0 making tombstones the default migration path, this UX gap becomes more visible. Users cleaning up their database shouldn't need to know about a separate `bd compact` command.

## Proposed Solution

Call `pruneExpiredTombstones()` at the end of the cleanup command (same function used by compact). Report results:

```
✓ Deleted 15 closed issue(s)
✓ Pruned 3 expired tombstone(s) (older than 30 days)
```

## Files to Modify

- `cmd/bd/cleanup.go` - Add call to pruneExpiredTombstones after deleteBatch
- May need to move pruneExpiredTombstones to shared location if not already accessible

## Acceptance Criteria

- [ ] `bd cleanup --force` prunes expired tombstones after deleting closed issues
- [ ] `bd cleanup --dry-run` shows what tombstones would be pruned
- [ ] JSON output includes tombstone prune results


formatVanishedIssues output order is non-deterministic


Modify EnsureIDs to support parent resurrection

Update internal/storage/sqlite/ids.go:189-202 to call TryResurrectParent before failing on missing parent. Add resurrection mode flag, log resurrected parents for transparency. Maintain backwards compatibility with strict validation mode.


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

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

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

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

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

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

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


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.


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

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

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


Create multi-agent race condition test

Automated test that runs 2+ agents simultaneously to verify collision prevention.

Acceptance Criteria:
- Script spawns 2 agents in parallel
- Both try to claim same issue
- Only one succeeds (via reservation)
- Other agent skips to different work
- Verify in JSONL that no duplicate claims
- Test with Agent Mail enabled/disabled

File: tests/integration/test_agent_race.py

Success Metric: Zero duplicate claims with Agent Mail, collisions without it


Add migration guide for library consumers

The contributor-workflow-analysis.md has excellent migration examples for CLI users (lines 508-549) but lacks examples for library consumers like VC that use beadsLib in Go/TypeScript code.

Library consumers need to know:
- Whether their existing code continues to work unchanged (backward compatibility)
- How config.toml is automatically read (transparent hydration)
- When and how to use explicit multi-repo configuration
- What happens if config.toml doesn't exist (defaults)

Example needed:
```go
// Before (v0.17.3)
store, err := beadsLib.NewSQLiteStorage(".beads/vc.db")

// After (v0.18.0 with multi-repo) - still works!
store, err := beadsLib.NewSQLiteStorage(".beads/vc.db")
// Automatically reads .beads/config.toml if present

// Explicit multi-repo (if needed)
cfg := beadsLib.Config{
    Primary: ".beads/vc.db",
    Additional: []string{"~/.beads-planning"},
}
store, err := beadsLib.NewStorageWithConfig(cfg)
```


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.


bd resolve-conflicts - Git merge conflict resolver

Automatically resolve JSONL merge conflicts.

Modes:
- Mechanical: ID remapping (no AI)
- AI-assisted: Smart merge/keep decisions
- Interactive: Review each conflict

Handles <<<<<<< conflict markers in .beads/beads.jsonl

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


Add guidance for self-hosting projects

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

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

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

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


Batch test 4


TestHooksCheckGitHooks failing - version mismatch (0.23.0 vs 0.23.1)

The test is checking embedded hook versions and expecting 0.23.1, but got 0.23.0. This appears to be a version consistency issue that needs investigation.

Test output:
```
Hook pre-commit version mismatch: got 0.23.0, want 0.23.1
Hook post-merge version mismatch: got 0.23.0, want 0.23.1
Hook pre-push version mismatch: got 0.23.0, want 0.23.1
```

This is blocking the landing of GH #274 fix.


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.


Sync should cleanup snapshot files after completion

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


Test Issue


bd doctor: export diagnostics history for debugging

Add ability to save doctor diagnostics to a file (e.g., diagnostics.json) for historical analysis and bug reporting. This would help track intermittent issues and provide context when filing bug reports. Could integrate with --perf output for comprehensive system snapshots.


Create Go adapter library (optional)

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

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

File: pkg/agentmail/client.go

Note: Lower priority - can shell out to curl initially


User Diagnostics (bd doctor --perf)

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

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

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

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

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

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


Auto-resolve all field conflicts in merge.go

Eliminate manual conflict resolution by adding deterministic auto-resolve rules for all fields:

- Priority: higher priority wins (lower number = more urgent)
- IssueType: local wins
- Notes: concatenate both sides with separator
- Title: side with latest updated_at on the issue wins
- Description: side with latest updated_at on the issue wins

Currently true conflicts (both sides changed same field to different values) fail sync. With this change, NO conflicts ever require manual resolution.

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


Implement content-hash based collision resolution for deterministic convergence

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

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

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

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


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.


Centralize error handling patterns in storage layer

80+ instances of inconsistent error handling across sqlite.go with mix of %w, %v, and no wrapping.

Location: internal/storage/sqlite/sqlite.go (throughout)

Problem:
- Some use fmt.Errorf("op failed: %w", err) - correct wrapping
- Some use fmt.Errorf("op failed: %v", err) - loses error chain
- Some return err directly - no context
- Hard to debug production issues
- Can't distinguish error types

Solution: Create internal/storage/sqlite/errors.go:
- Define sentinel errors (ErrNotFound, ErrInvalidID, etc.)
- Create wrapDBError(op string, err error) helper
- Convert sql.ErrNoRows to ErrNotFound
- Always wrap with operation context

Impact: Lost error context; inconsistent messages; hard to debug

Effort: 5-7 hours


Improve test coverage for internal/syncbranch (33.0% → 70%)

The syncbranch package has only 33.0% test coverage. This package handles git sync operations and is critical for data integrity.

Current coverage: 33.0%
Target coverage: 70%


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.


Improve internal/daemon test coverage (currently 22.5%)

Daemon functionality needs better coverage:
- Auto-start behavior
- Lock file management
- Discovery mechanisms
- Connection handling
- Error recovery

Current coverage: 58.3% (improved from 22.5% as of Nov 2025)


GH#524: Package for Windows (winget)

Request to publish beads on winget for easy Windows installation. See: https://github.com/steveyegge/beads/issues/524


Rapid 3


Add bd hooks install command to embed git hooks in binary

Currently git hooks are installed via `examples/git-hooks/install.sh`, which only exists in the beads source repo. Users who install bd via installer/homebrew/npm can't easily install hooks.

**Proposal:**
Add `bd hooks install` command that:
- Embeds hook scripts in the bd binary (using go:embed)
- Installs them to .git/hooks/ in current repo
- Backs up existing hooks
- Makes them executable

**Commands:**
- `bd hooks install` - Install all hooks
- `bd hooks uninstall` - Remove hooks
- `bd hooks list` - Show installed hooks status

**Benefits:**
- Works for all bd users, not just source repo users
- More discoverable (shows in bd --help)
- Consistent with bd workflow
- Can version hooks with bd releases


Audit Current Cache Usage

Understand exactly what code depends on the storage cache

Audit complete. See CACHE_AUDIT.md for full findings.

Summary:
- Cache was already removed in commit 322ab63 (2025-10-28)
- server_cache_storage.go deleted (~286 lines)
- All getStorageForRequest calls replaced with s.storage
- All environment variables removed
- MCP multi-repo works via per-project daemon architecture
- All tests updated and passing
- Only stale comment in server.go needed fixing


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.


Blocked issue


Daemons don't exit when parent process dies, causing accumulation and race conditions

Multiple daemon processes accumulate over time because daemons don't automatically stop when their parent process (e.g., coding agent) is killed. This causes:

1. Race conditions: 8+ daemons watching same .beads/beads.db, each with own 30s debounce timer
2. Git conflicts: Multiple daemons racing to commit/push .beads/issues.jsonl
3. Resource waste: Orphaned daemons from sessions days/hours old still running

Example: User had 8 daemons from multiple sessions (12:37AM, 7:20PM, 7:22PM, 7:47PM, 9:19PM yesterday + 9:54AM, 10:55AM today).

Solutions to consider:
1. Track parent PID and exit when parent dies
2. Use single global daemon instead of per-session
3. Document manual cleanup: pkill -f "bd daemon"
4. Add daemon lifecycle management (auto-cleanup of stale daemons)

Implementation complete:

1. Added ParentPID field to DaemonLockInfo struct (stored in daemon.lock JSON)
2. Daemon now tracks parent PID via os.Getppid() at startup
3. Both event loops (polling and event-driven) check parent process every 10 seconds
4. Daemon gracefully exits if parent process dies (detected via isProcessRunning check)
5. Handles edge cases:
   - ParentPID=0: Older daemons without tracking (ignored)
   - ParentPID=1: Adopted by init means parent died (exits)
   - Otherwise checks if parent process is still running

The fix prevents daemon accumulation by ensuring orphaned daemons automatically exit within 10 seconds of parent death.


Fix transaction conflict in TryResurrectParent

Integration test TestImportWithDeletedParent fails with 'database is locked' error when resurrection happens inside CreateIssue.

Root cause: TryResurrectParent calls conn.Get() and insertIssue() which conflicts with existing transaction in CreateIssue.

Error: failed to create tombstone for parent bd-parent: failed to insert issue: sqlite3: database is locked

Solution: Refactor resurrection to accept optional transaction parameter, use existing transaction when available instead of creating new connection.

Impact: Blocks resurrection from working in CreateIssue flow, only works in EnsureIDs (which may not have active transaction).


Create npm package structure for bd-wasm

Set up npm package for distribution:
- Create package.json with bd-wasm name
- Bundle bd.wasm + wasm_exec.js
- Create CLI wrapper (bin/bd) that invokes WASM
- Add installation scripts if needed
- Configure package for Claude Code Web sandbox compatibility


Fragile no-db detection uses strings.Contains instead of YAML parsing

Two locations use `strings.Contains(string(configData), "no-db: true")` to detect JSONL-only mode:

1. **cmd/bd/main.go:310** - Auto-enabling no-db mode
2. **cmd/bd/doctor.go:863** - Database status check

This is fragile because it could match:
- Comments: `# no-db: true for testing`
- Nested keys: `settings:\n  no-db: true`
- String values: `note: "set no-db: true to disable"`

Should use proper YAML parsing like we did for sync-branch in bd-0rh:

```go
type noDbConfig struct {
    NoDb bool `yaml:"no-db"`
}

var cfg noDbConfig
if err := yaml.Unmarshal(configData, &cfg); err == nil {
    isNoDbMode = cfg.NoDb
}
```

Related: bd-0rh (fixed similar issue for sync-branch)


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

Implement CLI command to invoke beads-merge functionality.

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

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

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


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


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

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

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

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

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

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

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

## Progress Update

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

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

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

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

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

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

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


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.


Jira export script (jsonl2jira.py)

Create a Python script to push beads issues to Jira.

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

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

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

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

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


Address gosec security warnings (102 issues)

Security linter warnings: file permissions (0755 should be 0750), G304 file inclusion via variable, G204 subprocess launches. Many are false positives but should be reviewed.


Tombstone export includes tombstones even with --status filter

In export.go, we set IncludeTombstones: true unconditionally. However, if a user runs:

  bd export --status=open

They might not expect tombstones in the output. The IncludeTombstones flag should probably only be set when no status filter is specified, or when --status=tombstone is explicitly requested.

Current behavior: exports tombstones regardless of status filter
Expected behavior: respect status filter, only include tombstones when explicitly requested or no filter


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


Add 'When to use daemon mode' decision tree to daemon.md

**Problem:**
Users (especially local-only developers) see daemon-related messages but don't understand:
- What daemon mode does (git sync automation)
- Whether they need it
- Why they see "daemon_unsupported" messages

Related to issue #349 item #3.

**Current state:**
commands/daemon.md explains WHAT daemon does but not WHEN to use it.

**Proposed addition:**
Add a "When to Use Daemon Mode" section after line 20 in commands/daemon.md with clear decision criteria:

**✅ You SHOULD use daemon mode if:**
- Working in a team with git remote sync
- Want automatic commit/push of issue changes
- Need background auto-sync (5-second debounce)
- Making frequent bd commands (performance benefit)

**❌ You DON'T need daemon mode if:**
- Solo developer with local-only tracking
- Working in git worktrees (use --no-daemon)
- Running one-off commands/scripts
- Debugging database issues

**Local-only users:** Direct mode is perfectly fine. Daemon mainly helps with git sync automation. You can use `bd sync` manually when needed.

**Files to modify:**
- commands/daemon.md (add section after line 20)


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.


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.


Test dependency child


Isolate RPC server startup into rpc_server.go

Create internal/daemonrunner/rpc_server.go with StartRPC function. Move startRPCServer logic here and return typed handle.


Fix Windows-specific test failures in CI

Several tests are failing on Windows but passing on Linux:

**Failing tests:**
- TestFindDatabasePathEnvVar
- TestHashIDs_MultiCloneConverge
- TestHashIDs_IdenticalContentDedup
- TestDatabaseReinitialization (all 5 subtests):
  - fresh_clone_auto_import
  - database_removal_scenario
  - legacy_filename_support
  - precedence_test
  - init_safety_check
- TestFindBeadsDir_NotFound
- TestMetricsSnapshot/uptime (in internal/rpc)

**CI Run:** https://github.com/steveyegge/beads/actions/runs/19015638968

These are likely path separator or filesystem behavior differences between Windows and Linux.

Fixed all Windows path issues:
1. TestFindDatabasePathEnvVar - expects canonicalized paths ✅
2. TestHashIDs tests - use platform-specific bd.exe command ✅
3. TestMetricsSnapshot/uptime - enforce minimum 1 second uptime ✅
4. TestFindBeadsDir_NotFound - allow finding .beads in parent dirs ✅
5. TestDatabaseReinitialization - fix git path conversion on Windows (git returns /c/Users/... but filepath needs C:\Users\...) ✅

CI run in progress to verify all fixes.


Remove unreachable utility functions

Several small utility functions are unreachable:

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

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

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

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

Impact: Removes 50-100 LOC depending on decisions


Add functional tests for FlushManager correctness verification


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.


Duplicate safety check message when confirmation required


Implement transaction retry logic for SQLITE_BUSY

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

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

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

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

Impact: Spurious failures under concurrent write load

Effort: 3 hours


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

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


Complete remaining sync branch daemon tests

4 remaining test scenarios in daemon_sync_branch_test.go need completion:

⚠ MINOR FIXES (apply same pattern as TestSyncBranchCommitAndPush_Success):
1. TestSyncBranchCommitAndPush_NoChanges
   - Reorder: call initMainBranch() BEFORE creating JSONL
   - Pattern: init branch → create issue → export JSONL → test

2. TestSyncBranchCommitAndPush_WorktreeHealthCheck
   - Same reordering needed
   - Verify worktree corruption detection and auto-repair

🔧 MORE WORK NEEDED (remote branch setup):
3. TestSyncBranchPull_Success
   - Issue: remote doesn't have sync branch after push
   - Need to verify branch is pushed to remote correctly
   - Then test pull from clone2

4. TestSyncBranchIntegration_EndToEnd
   - Full workflow: Agent A commits → Agent B pulls → Agent B commits → Agent A pulls
   - Same remote branch issue

Pattern to apply (from TestSyncBranchCommitAndPush_Success):
- Call initMainBranch(t, dir) BEFORE creating issues/JSONL
- This ensures sync branch worktree has changes to commit

Acceptance:
- All 7 tests pass
- go test -v -run TestSyncBranch ./cmd/bd/ succeeds


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

## Problem

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

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

## Current Behavior

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

## Expected Behavior

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

## Impact

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

## Fix

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

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

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


Switch from deletions manifest to inline tombstones

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

## Problem Statement

The current deletions manifest approach has several issues:

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

## Proposed Solution: Inline Tombstones

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

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

### Benefits

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

### Design Decisions Needed

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

### Migration Strategy

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

## Related Issues

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


bd doctor: add SQLite integrity check (PRAGMA integrity_check)

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


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.


bd-hv01: Storage backend extensibility broken by type assertion

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

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

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


Fix SyncJSONLToWorktree to merge instead of overwrite

SyncJSONLToWorktree blindly copies local JSONL to worktree, overwriting remote issues.

## Root Cause (GitHub #464)
When Computer B syncs with fewer issues than remote:
1. preemptiveFetchAndFastForward updates worktree (10 issues)
2. SyncJSONLToWorktree overwrites with local (1 issue)
3. Commit records deletion of 9 issues
4. 3-way merge sees "deletion in left" → deletion wins

## Fix
Before copying, check if worktree has more issues. If so:
- Option A: Merge local into worktree (preferred)
- Option B: Fail with helpful error message
- Option C: Warn and require --force flag

## Location
internal/syncbranch/worktree.go:107 - SyncJSONLToWorktree call

## Test Case
1. Clone repo with sync-branch containing issues
2. bd init (starts empty since JSONL on sync-branch)
3. bd create "test"
4. bd sync
5. Verify all issues preserved, not just "test"


Deletions pruning doesn't include results in JSON output

## Problem

When `bd compact --json` runs with deletions pruning, the prune results are silently discarded:

```go
// Only report if there were deletions to prune
if result.PrunedCount > 0 {
    if jsonOutput {
        // JSON output will be included in the main response
        return  // <-- BUG: results are NOT included anywhere
    }
    ...
}
```

## Location
`cmd/bd/compact.go:925-929`

## Impact
- JSON consumers don't know deletions were pruned
- No way to audit pruning via automation

## Fix
Return prune results and include in JSON output structure:

```json
{
  "success": true,
  "compacted": {...},
  "deletions_pruned": {
    "count": 5,
    "retention_days": 7
  }
}
```


Fix TestFallbackToDirectModeEnablesFlush deadlock causing 10min test timeout

## Problem

TestFallbackToDirectModeEnablesFlush in direct_mode_test.go deadlocks for 9m59s before timing out, causing the entire test suite to take 10+ minutes instead of <10 seconds.

## Root Cause

Database lock contention between test cleanup and flushToJSONL():
- Test cleanup (line 36) tries to close DB via defer
- flushToJSONL() (line 132) is still accessing DB
- Results in deadlock: database/sql.(*DB).Close() waits for mutex while GetJSONLFileHash() holds it

## Stack Trace Evidence

```
goroutine 512 [sync.Mutex.Lock, 9 minutes]:
database/sql.(*DB).Close(0x14000643790)
  .../database/sql/sql.go:927 +0x84
github.com/steveyegge/beads/cmd/bd.TestFallbackToDirectModeEnablesFlush.func1()
  .../direct_mode_test.go:36 +0xf4

While goroutine running flushToJSONL() holds DB connection via GetJSONLFileHash()
```

## Impact

- Test suite: 10+ minutes → should be <10 seconds
- ALL other tests pass in ~4 seconds
- This ONE test accounts for 99.9% of test runtime

## Related

This is the EXACT same issue documented in MAIN_TEST_REFACTOR_NOTES.md for why main_test.go refactoring was deferred - global state manipulation + DB cleanup = deadlock.

## Fix Approaches

1. **Add proper cleanup sequencing** - stop flush goroutines BEFORE closing DB
2. **Use test-specific DB lifecycle** - ensure flush completes before cleanup
3. **Mock the flush mechanism** - avoid real DB for testing this code path
4. **Add explicit timeout handling** - fail fast with clear error instead of hanging

## Files

- cmd/bd/direct_mode_test.go:36-132
- cmd/bd/autoflush.go:353 (validateJSONLIntegrity)
- cmd/bd/autoflush.go:508 (flushToJSONLWithState)

## Acceptance

- Test passes without timeout
- Test suite completes in <10 seconds
- No deadlock between cleanup and flush operations