Document 3-clone ID non-determinism in collision resolution

Document the known behavior of 3+ way collision resolution where ID assignments may vary based on sync order, even though content always converges correctly.

Updates needed:
- Update bd-71107098 notes to mark 2-clone case as solved
- Document 3-clone ID non-determinism as known limitation
- Add explanation to ADVANCED.md or collision resolution docs
- Explain why this happens (pairwise hash comparison is deterministic, but multi-way ID allocation uses sync-order dependent counters)
- Clarify trade-offs: content convergence ✅ vs ID stability ❌

Key points to document:
- Hash-based resolution is pairwise deterministic
- Content always converges correctly (all issues present with correct data)
- Numeric ID assignments in 3+ way collisions depend on sync order
- This is acceptable for most use cases (content convergence is primary goal)
- Full determinism would require complex multi-way comparison


Add unit tests for parent resurrection

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


Modify CreateIssue to support parent resurrection

Update internal/storage/sqlite/sqlite.go:182-196 to call TryResurrectParent before failing on missing parent. Coordinate with EnsureIDs changes for consistent behavior. Handle edge case where parent never existed in JSONL (fail gracefully).


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


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 MCP tools failing to load in Claude Code (GH#346)

Fix FastMCP schema generation bug by refactoring `Issue` model to avoid recursion.

    - Refactored `Issue` in `models.py` to use `LinkedIssue` for dependencies/dependents.
    - Restored missing `Mail*` models to `models.py`.
    - Fixed `tests/test_mail.py` mock assertion.

    Fixes GH#346.


bd message: Add HTTP client timeout to prevent hangs

HTTP client in `sendAgentMailRequest` uses default http.Post with no timeout.

**Location:** cmd/bd/message.go:181

**Problem:**
- Can hang indefinitely if server is unresponsive
- No way to cancel stuck requests
- Poor UX in flaky networks

**Fix:**
```go
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Post(url, "application/json", bytes.NewReader(reqBody))
```

**Impact:** Production reliability and security issue


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
  }
}
```


Create architecture decision record (ADR)

Document why we chose Agent Mail, alternatives considered, and tradeoffs.

Acceptance Criteria:
- Problem statement (git traffic, no locks)
- Alternatives considered (custom RPC, Redis, etc.)
- Why Agent Mail fits Beads
- Integration principles (optional, graceful degradation)
- Future considerations

File: docs/adr/002-agent-mail-integration.md


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

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

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

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

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

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

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


Consider backwards compatibility for ready() and list() return type change

PR #481 changed the return types of `ready()` and `list()` from `list[Issue]` to `list[IssueMinimal] | CompactedResult`. This is a breaking change for MCP clients.

## Impact Assessment
Breaking change affects:
- Any MCP client expecting `list[Issue]` from ready()
- Any MCP client expecting `list[Issue]` from list()
- Client code that accesses full Issue fields (description, design, acceptance_criteria, timestamps, dependencies, dependents)

## Current Behavior
- ready() returns `list[IssueMinimal] | CompactedResult`
- list() returns `list[IssueMinimal] | CompactedResult`
- show() still returns full `Issue` (good)

## Considerations
**Pros of current approach:**
- Forces clients to use show() for full details (good for context efficiency)
- Simple mental model (always use show for full data)
- Documentation warns about this

**Cons:**
- Clients expecting list[Issue] will break
- No graceful degradation option
- No migration period

## Potential Solutions
1. Add optional parameter `full_details=false` to ready/list (would increase payload)
2. Create separate tools: ready_minimal/list_minimal + ready_full/list_full
3. Accept breaking change and document upgrade path (current approach)
4. Version the MCP server and document migration guide

## Recommendation
Current approach (solution 3) is reasonable if:
- Changelog clearly documents the breaking change
- Migration guide provided to clients
- Error handling is graceful for clients expecting specific fields


Test simple issue


bd detect-pollution - Test pollution detector

Detect test issues that leaked into production DB.

Pattern matching for:
- Titles starting with 'test', 'benchmark', 'sample'
- Sequential numbering (test-1, test-2)
- Generic descriptions
- Created in rapid succession

Optional AI scoring for confidence.

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


Implement hash ID generation in CreateIssue

Replace sequential ID generation with hash-based IDs in CreateIssue function.

## Current Behavior (internal/storage/sqlite/sqlite.go)
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // ID comes from auto-increment counter
    // Collisions possible across clones
}
```

## New Behavior
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // Generate hash ID if not provided
    if issue.ID == "" {
        issue.ID = idgen.GenerateHashID(
            issue.Title,
            issue.Description,
            time.Now(),
            s.workspaceID,
        )
    }

    // Assign next alias
    issue.Alias = s.getNextAlias()

    // Insert with hash ID + alias
    // ...
}
```

## Workspace ID Generation
Add to database initialization:
```go
// Generate stable workspace ID (persisted in .beads/workspace_id)
workspaceID := getOrCreateWorkspaceID()
```

Options for workspace ID:
1. Hostname + random suffix
2. UUID (random)
3. Git remote URL hash (deterministic per repo)

Recommended: Option 3 (git remote hash) for reproducibility

## Hash Collision Detection
```go
// On insert, check for collision (unlikely but possible)
existing, err := s.GetIssue(ctx, issue.ID)
if err == nil {
    // Hash collision! Add random suffix and retry
    issue.ID = issue.ID + "-" + randomSuffix(4)
}
```

## Files to Create/Modify
- internal/types/id_generator.go (new)
- internal/storage/sqlite/sqlite.go (CreateIssue)
- internal/storage/sqlite/workspace.go (new - workspace ID management)
- .beads/workspace_id (new file, git-ignored)

## Testing
- Test hash ID generation is deterministic
- Test collision detection and retry
- Test workspace ID persistence
- Benchmark: hash generation performance (<1μs)


Code Review Sweep: thorough

Perform thorough code review sweep based on accumulated activity.

**AI Reasoning:**
Significant code volume added (150,273 lines) across multiple critical areas, including cmd/bd, internal/storage/sqlite, and internal/rpc. High file change count (616) indicates substantial refactoring or new functionality. The metrics suggest potential for subtle architectural or implementation issues that warrant review.

**Scope:** thorough
**Target Areas:** cmd/bd, internal/storage/sqlite, internal/rpc
**Estimated Files:** 12
**Estimated Cost:** $5

**Task:**
Review files for non-obvious issues that agents miss during focused work:
- Inefficiencies (algorithmic, resource usage)
- Subtle bugs (race conditions, off-by-one, copy-paste)
- Poor patterns (coupling, complexity, duplication)
- Missing best practices (error handling, docs, tests)
- Unnamed anti-patterns

File discovered issues with detailed reasoning and suggestions.


Test lowercase p0


Support separate branch for beads commits

Allow beads to commit to a separate branch (e.g., beads-metadata) using git worktrees to support protected main branch workflows.

Solves GitHub Issue #205 - Users need to protect main branch while maintaining beads workflow.

Key advantages:
- Works on any git platform
- Main branch stays protected
- No disruption to user's working directory
- Backward compatible (opt-in via config)
- Minimal disk overhead (sparse checkout)

Total estimate: 17-24 days (4-6 weeks with parallel work)


Investigate performance issues in VS Code Copilot (Windows)

Beads unusable in Windows 11 VS Code Copilot chat with Sonnet 4.5.
Summary event happens every 3-4 turns, taking 3 minutes.
Copilot summarizes after ~125k tokens despite model supporting 1M.
Large context size of beads might be triggering aggressive summarization.
Need workaround or optimization for context size.


Issue with desc

This is a description


Add tests for internal/utils package

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


Add bd alias command for manual alias control

Add command for users to manually view and reassign aliases.

## Command: bd alias

### Subcommands

#### 1. bd alias list
Show all alias mappings:
```bash
$ bd alias list
#1    → bd-af78e9a2  Fix authentication bug
#2    → bd-e5f6a7b8  Add logging to daemon
#42   → bd-1a2b3c4d  Investigate jujutsu integration
#100  → bd-9a8b7c6d  (reassigned after conflict)
```

#### 2. bd alias set <alias> <hash-id>
Manually assign alias to specific issue:
```bash
$ bd alias set 42 bd-1a2b3c4d
✓ Assigned alias #42 to bd-1a2b3c4d

$ bd alias set 1 bd-af78e9a2
✗ Error: Alias #1 already assigned to bd-e5f6a7b8
Use --force to override
```

#### 3. bd alias compact
Renumber all aliases to fill gaps:
```bash
$ bd alias compact [--dry-run]

Current aliases: #1, #2, #5, #7, #100, #101
After compacting: #1, #2, #3, #4, #5, #6

Renumbering:
  #5   → #3
  #7   → #4
  #100 → #5
  #101 → #6

Apply changes? [y/N]
```

#### 4. bd alias reset
Regenerate all aliases (sequential from 1):
```bash
$ bd alias reset [--sort-by=priority|created|id]

WARNING: This will reassign ALL aliases. Continue? [y/N]

Reassigning 150 issues by priority:
  bd-a1b2c3d4 → #1   (P0: Critical security bug)
  bd-e5f6a7b8 → #2   (P0: Data loss fix)
  bd-1a2b3c4d → #3   (P1: Jujutsu integration)
  ...
```

#### 5. bd alias find <hash-id>
Look up alias for hash ID:
```bash
$ bd alias find bd-af78e9a2
#1

$ bd alias find bd-nonexistent
✗ Error: Issue not found
```

## Use Cases

### 1. Keep Important Issues Low-Numbered
```bash
# After closing many P0 issues, compact to free low numbers
bd alias compact

# Or manually set
bd alias set 1 bd-<critical-bug-hash>
```

### 2. Consistent Aliases Across Clones
```bash
# After migration, coordinator assigns canonical aliases
bd alias reset --sort-by=id
git add .beads/aliases.jsonl
git commit -m "Canonical alias assignments"
git push

# Other clones pull and adopt
git pull
bd import  # Alias conflicts resolved automatically
```

### 3. Debug Alias Conflicts
```bash
# See which aliases were reassigned
bd alias list | grep "#100"
```

## Flags

### Global
- `--dry-run`: Preview changes without applying
- `--force`: Override existing alias assignments

### bd alias reset
- `--sort-by=priority`: Assign by priority (P0 first)
- `--sort-by=created`: Assign by creation time (oldest first)
- `--sort-by=id`: Assign by hash ID (lexicographic)

## Implementation

File: cmd/bd/alias.go
```go
func aliasListCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "list",
        Short: "List all alias mappings",
        Run: func(cmd *cobra.Command, args []string) {
            aliases := storage.GetAllAliases()
            for _, a := range aliases {
                fmt.Printf("#%-4d → %s  %s\n",
                    a.Alias, a.IssueID, a.Title)
            }
        },
    }
}

func aliasSetCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "set <alias> <hash-id>",
        Short: "Manually assign alias to issue",
        Args:  cobra.ExactArgs(2),
        Run: func(cmd *cobra.Command, args []string) {
            alias, _ := strconv.Atoi(args[0])
            hashID := args[1]

            force, _ := cmd.Flags().GetBool("force")
            if err := storage.SetAlias(alias, hashID, force); err != nil {
                fmt.Fprintf(os.Stderr, "Error: %v\n", err)
                os.Exit(1)
            }
            fmt.Printf("✓ Assigned alias #%d to %s\n", alias, hashID)
        },
    }
}
```

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

## Testing
- Test alias list output
- Test alias set with/without force
- Test alias compact removes gaps
- Test alias reset with different sort orders
- Test alias find lookup


File change test issue


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

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

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

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

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


Improve test coverage for internal/rpc (47.5% → 60%)

The RPC package has only 47.5% test coverage. RPC is the communication layer for daemon operations.

Current coverage: 47.5%
Target coverage: 60%


Centralize BD_DEBUG logging into debug package

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

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

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

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

Impact: Removes 43 scattered checks, improves code clarity


worktree: any bd command is slow

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


Add simple cleanup command to delete closed issues

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


GH#486: Claude forgets beads workflow mid-session

Claude progressively reverts to TodoWrite or skips tracking despite CLAUDE.md instructions. Happens after context resets or long sessions. Need stronger prompting or hooks. See: https://github.com/steveyegge/beads/issues/486


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


Test parent issue


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.


Run bd doctor at end of bd init to verify setup

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


AI-supervised database migrations for safer schema evolution

## Problem

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

## Current State

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

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

## Proposal: AI-Supervised Migration Framework

Use AI to supervise migrations through structured verification:

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

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

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

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

## Example Flow

```
$ bd migrate

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

If something goes wrong:
```
$ bd migrate

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

## Implementation Ideas

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

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

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

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

## Benefits

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

## Open Questions

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

## Related Work

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

## Progress

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

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

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

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

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

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

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

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

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


Add multi-repo support to export metadata updates

## Problem
The bd-ymj fix only updates metadata for the main jsonlPath, but the codebase supports multi-repo mode where exports write to multiple JSONL files.

Other daemon_sync operations handle multi-repo correctly:
- Lines 509-518: Snapshots captured for all multi-repo paths
- Lines 578-587: Deletions applied for all multi-repo paths

But metadata updates are missing!

## Investigation Needed
Metadata is stored globally in database (`last_import_hash`, `last_import_mtime`), but multi-repo has per-repo JSONL files. Current schema may not support tracking multiple JSONL files.

Options:
1. Store metadata per-repo (new schema)
2. Store combined hash of all repos
3. Document that multi-repo doesn't need this metadata (why?)

## Solution (if needed)
```go
// After export
if multiRepoPaths := getMultiRepoJSONLPaths(); multiRepoPaths != nil {
    // Multi-repo mode: update metadata for each JSONL
    for _, path := range multiRepoPaths {
        updateExportMetadata(exportCtx, store, path, log)
    }
} else {
    // Single-repo mode: update metadata for main JSONL
    updateExportMetadata(exportCtx, store, jsonlPath, log)
}
```

## Files
- cmd/bd/daemon_sync.go (createExportFunc, createSyncFunc)
- Possibly: internal/storage/sqlite/metadata.go (schema changes)

## Related
Depends on bd-ar2.1 (extract helper function first)


RPC fast-fail: stat socket before dial, cap timeouts to 200ms

Eliminate 5s delay when daemon socket is missing by:
1. Add os.Stat(socketPath) check before dialing in TryConnect
2. Return (nil, nil) immediately if socket doesn't exist
3. Set default dial timeout to 200ms in TryConnect
4. Keep TryConnectWithTimeout for explicit health/status checks (1-2s)

This prevents clients from waiting through full timeout when no daemon is running.


Import validation falsely reports data loss on collision resolution

## Problem

Post-import validation reports 'data loss detected!' when import count reduces due to legitimate collision resolution.

## Example

```
Import complete: 1 created, 8 updated, 142 unchanged, 19 skipped, 1 issues remapped
Post-import validation failed: import reduced issue count: 165 → 164 (data loss detected!)
```

This was actually successful collision resolution (bd-70419816 duplicated → remapped to-70419816), not data loss.

## Impact

- False alarms waste investigation time
- Undermines confidence in import validation
- Confuses users/agents about sync health

## Solution

Improve validation to distinguish:
- Collision-resolution merges (expected count reduction)
- Actual data loss (unexpected disappearance)

Track remapped issue count and adjust expected post-import count accordingly.


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


Update documentation for beads-merge integration

Document the integrated merge functionality.

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

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


bd delete fails to find closed issues

## Steps to Reproduce
1. Close some issues with `bd close`
2. Try to delete them with `bd delete <ids> --force`
3. Get error "issues not found"

## Expected Behavior
Should delete the closed issues

## Actual Behavior
```
Error: issues not found: bd-74ee, bd-9b13, bd-72w, bd-149, bd-5iv, bd-78w
```

But `bd list --status closed --json` shows they exist.

## Root Cause
Likely the delete command is only looking for open issues, or there's a race condition with auto-import.


Frontend task


Add command injection prevention tests for git rm in merge command

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

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

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

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

This is a critical security path preventing arbitrary command execution.

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


## 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 should not 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).

## Files to Modify

- cmd/bd/cleanup.go - Add call to pruneExpiredTombstones after deleteBatch

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


Stealth mode global gitignore should use absolute project path

**GitHub Issue:** #538

**Problem:**
`bd init --stealth` adds `.beads/` to the global gitignore, which ignores ALL `.beads/` folders across all repositories. Users who want stealth mode in one project but open beads usage in others are blocked.

**Solution:**
Change stealth mode to use absolute paths instead of generic patterns:

**Before (current):**
```
# Beads stealth mode configuration (added by bd init --stealth)
.beads/
.claude/settings.local.json
```

**After (proposed):**
```
# Beads stealth mode: /Users/foo/work-project (added by bd init --stealth)
/Users/foo/work-project/.beads/
/Users/foo/work-project/.claude/settings.local.json
```

**Implementation:**
1. Modify `setupGlobalGitIgnore()` in `cmd/bd/init.go`
2. Get current working directory (absolute path)
3. Use absolute path patterns instead of generic ones
4. Update comment to show which project the entry is for

**Tradeoffs:**
- If project directory moves, gitignore entry becomes stale (acceptable - user can re-run `bd init --stealth`)
- Multiple stealth projects = multiple entries (works correctly)

**Testing:**
- Verify absolute path is added to global gitignore
- Verify other projects' .beads/ folders are NOT ignored
- Test with existing global gitignore file
- Test creating new global gitignore file


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

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

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

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


Add optional post-merge git hook example for bd sync

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

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

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

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


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


Create tip system infrastructure for contextual hints

Implement a tip/hint system that shows helpful contextual messages after successful commands. This is different from the existing error-path "Hint:" messages - tips appear on success paths to educate users about features they might not know about.


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

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

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

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

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


Latency test 1

Resetting stale in_progress status from old executor run (yesterday)


Add unit tests for hasJSONLChanged() function

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

Also add performance benchmarks for large JSONL files.


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


Add edge case tests for export metadata updates

## Current Coverage
TestExportUpdatesMetadata tests basic happy path only.

## Missing Test Cases

### 1. Multi-Repo Mode
- Export with multi-repo configuration
- Verify metadata updated for ALL JSONL files
- Critical gap (related to bd-ar2.2)

### 2. computeJSONLHash Failure
- JSONL file deleted/inaccessible after export
- Should log warning, not crash
- Verify behavior on next export

### 3. SetMetadata Failures
- Database write-protected
- Disk full
- Should log warnings, continue

### 4. os.Stat Failure
- JSONL file deleted after export, before stat
- Should handle gracefully (mtime just not updated)

### 5. Concurrent Exports
- Two daemon instances export simultaneously
- Verify metadata doesn't get corrupted
- Race condition test

### 6. Clock Skew
- System time goes backward between exports
- Verify mtime handling still works

### 7. Partial Export Failure
- Multi-repo mode: some exports succeed, some fail
- What metadata should be updated?

### 8. Large JSONL Files
- Performance test with large files (10k+ issues)
- Verify hash computation doesn't timeout

## Files
- cmd/bd/daemon_sync_test.go

## Implementation Priority
1. Multi-repo test (P1 - related to critical issue)
2. Failure handling tests (P2)
3. Performance/edge cases (P3)


Test coverage improvement initiative (47.8% → 65%)

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

## Summary

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

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

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


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

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

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

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

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

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


Add merge helper commands (bd sync --merge)

Add commands to merge beads branch back to main.

Tasks:
- Implement bd sync --merge command
- Implement bd sync --status command
- Implement bd sync --auto-merge (optional, for automation)
- Detect merge conflicts and provide guidance
- Show commit diff between branches
- Verify main branch is clean before merge
- Push merged changes to remote

Estimated effort: 2-3 days


Audit and fix JSONL filename mismatches across all repo clones

## Problem

Multiple clones of repos are configured with different JSONL filenames (issues.jsonl vs beads.jsonl), causing:
1. JSONL files to be resurrected after deletion (one clone pushes issues.jsonl, another pushes beads.jsonl)
2. Agents unable to see issues filed by other agents after sync
3. Merge conflicts and data inconsistencies

## Root Cause

When repos were "bd doctored" or initialized at different times, some got issues.jsonl (old default) and others got beads.jsonl (Beads repo specific). These clones push their respective files, creating duplicates.

## Task

Scan all repo clones under ~/src/ (1-2 levels deep) and standardize their JSONL configuration.

### Step 1: Find all beads-enabled repos

```bash
# Find all directories named 'beads' at levels 1-2 under ~/src/
find ~/src -maxdepth 2 -type d -name beads
```

### Step 2: For each repo found, check configuration

For each directory from Step 1, check:
- Does `.beads/metadata.json` exist?
- What is the `jsonl_export` value?
- What JSONL files actually exist in `.beads/`?
- Are there multiple JSONL files (problem!)?

### Step 3: Create audit report

Generate a report showing:
```
Repo Path                           | Config        | Actual Files           | Status
----------------------------------- | ------------- | ---------------------- | --------
~/src/beads                         | beads.jsonl   | beads.jsonl            | OK
~/src/dave/beads                    | issues.jsonl  | issues.jsonl           | MISMATCH
~/src/emma/beads                    | issues.jsonl  | issues.jsonl, beads.jsonl | DUPLICATE!
```

### Step 4: Determine canonical name for each repo

For repos that are the SAME git repository (check `git remote -v`):
- Group them together
- Determine which JSONL filename should be canonical (majority wins, or beads.jsonl for the beads repo itself)
- List which clones need to be updated

### Step 5: Generate fix script

Create a script that for each mismatched clone:
1. Updates `.beads/metadata.json` to use the canonical name
2. If JSONL file needs renaming: `git mv .beads/old.jsonl .beads/new.jsonl`
3. Removes any duplicate JSONL files: `git rm .beads/duplicate.jsonl`
4. Commits the change
5. Syncs: `bd sync`

### Expected Output

1. Audit report showing all repos and their config status
2. List of repos grouped by git remote (same repository)
3. Fix script or manual instructions for standardizing each repo
4. Verification that after fixes, all clones of the same repo use the same JSONL filename

### Edge Cases

- Handle repos without metadata.json (use default discovery)
- Handle repos with no git remote (standalone/local)
- Handle repos that are not git repositories
- Don't modify repos with uncommitted changes (warn instead)

### Success Criteria

- All clones of the same git repository use the same JSONL filename
- No duplicate JSONL files in any repo
- All configurations documented in metadata.json
- bd doctor passes on all repos


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


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

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

Uses embeddings by default for >100 issues.

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


Add is_tombstone flag to schema

Optionally add is_tombstone boolean field to issues table. Marks resurrected parents that were deleted. Allows distinguishing tombstones from normal deleted issues. Update schema.go and create migration.


Add MCP functions for repair commands

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

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


Add validation and documentation for metadata key format

## Issues

### 1. No validation that keySuffix doesn't contain separator
```go
if keySuffix != "" {
    hashKey += ":" + keySuffix  // ❌ What if keySuffix contains ":"?
}
```

Add validation:
```go
if strings.Contains(keySuffix, ":") {
    return fmt.Errorf("keySuffix cannot contain ':' separator")
}
```

### 2. Inconsistent keySuffix semantics
Empty string means "global", non-empty means "scoped". Better to always pass explicit key:
```go
const (
    SingleRepoKey = "."
    // Multi-repo uses source_repo identifier
)
```

### 3. Metadata key format not documented
Need to document:
- `last_import_hash` - single-repo mode
- `last_import_hash:<repo_key>` - multi-repo mode
- `last_import_time` - when last import occurred
- `last_import_mtime` - fast-path optimization

Add to internal/storage/sqlite/schema.go or docs/


Test npm package installation locally

Verify npm package works before publishing:

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

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

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


Fix autoimport tests for content-hash collision scoring

## Overview
Three autoimport tests are failing after [deleted:bd-cbed9619.4] because they expect behavior based on the old reference-counting collision resolution, but the system now uses deterministic content-hash scoring.

## Failing Tests
1. `TestAutoImportMultipleCollisionsRemapped` - expects local versions preserved
2. `TestAutoImportAllCollisionsRemapped` - expects local versions preserved
3. `TestAutoImportCollisionRemapMultipleFields` - expects specific collision resolution behavior

## Root Cause
These tests were written when ScoreCollisions used reference counting to determine which version to keep. Now it uses content-hash comparison (introduced in commit 2e87329), which produces different but deterministic results.

## Example
Old behavior: Issue with more references would be kept
New behavior: Issue with lexicographically lower content hash is kept

## Solution
Update each test to:
1. Verify the new content-hash based behavior is correct
2. Check that the remapped issue (not necessarily local/remote) has the expected content
3. Ensure dependencies are preserved on the correct remapped issue

## Acceptance Criteria
- All three autoimport tests pass
- Tests verify content-hash determinism (same collision always resolves the same way)
- Tests check dependency preservation on remapped issues
- Test documentation explains content-hash scoring expectations

## Files to Modify
- `cmd/bd/autoimport_collision_test.go`

## Testing
Run: `go test ./cmd/bd -run "TestAutoImport.*Collision" -v`


Test RPC mutation event


Test parent issue


Add test coverage for internal/validation package


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.


Make MCP compaction settings configurable (COMPACTION_THRESHOLD, PREVIEW_COUNT)

Currently COMPACTION_THRESHOLD (20) and PREVIEW_COUNT (5) are hardcoded constants at the module level. This prevents users from tuning context engineering behavior.

## Current State
```python
COMPACTION_THRESHOLD = 20  # Compact results with more than 20 issues
PREVIEW_COUNT = 5          # Show first 5 issues in preview
```

## Problems
1. No way for MCP clients to request different preview sizes
2. No way to disable compaction for debugging
3. No way to adapt to different context window sizes
4. Values are not documented in tool schema

## Recommended Solution
Add environment variables or MCP tool parameters:
- `BEADS_MCP_COMPACTION_THRESHOLD` env var (default: 20)
- `BEADS_MCP_PREVIEW_COUNT` env var (default: 5)
- Optional `compact_threshold` and `preview_size` parameters to list/ready tools

## Implementation Notes
- Keep current defaults for backward compatibility
- Read from environment at server initialization
- Document in CONTEXT_ENGINEERING.md
- Add tests verifying env var behavior


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.


Code Review Sweep: thorough

Perform thorough code review sweep based on accumulated activity.

**AI Reasoning:**
Significant code activity with 7608 lines added and 120 files changed indicates substantial modifications. Multiple high-churn areas (cmd/bd, internal/rpc) suggest potential for subtle issues and emerging patterns that warrant review.

**Scope:** thorough
**Target Areas:** cmd/bd, internal/rpc, .beads
**Estimated Files:** 12
**Estimated Cost:** $5

**Task:**
Review files for non-obvious issues that agents miss during focused work:
- Inefficiencies (algorithmic, resource usage)
- Subtle bugs (race conditions, off-by-one, copy-paste)
- Poor patterns (coupling, complexity, duplication)
- Missing best practices (error handling, docs, tests)
- Unnamed anti-patterns

File discovered issues with detailed reasoning and suggestions.


Investigate jujutsu VCS as potential solution for conflict-free merging

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

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

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

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

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

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


Make two-clone workflow actually work (no hacks)

TestTwoCloneCollision proves beads CANNOT handle two independent clones filing issues simultaneously. This is the basic collaborative workflow and it must work cleanly.

Test location: beads_twoclone_test.go

The test creates two git clones, both file issues with same ID (test-1), --resolve-collisions remaps clone B's to test-2, but after sync:
- Clone A has test-1="Issue from clone A", test-2="Issue from clone B"
- Clone B has test-1="Issue from clone B", test-2="Issue from clone A"

The TITLES are swapped! Both clones have 2 issues but with opposite title assignments.

We've tried many fixes (per-project daemons, auto-sync, lamport hashing, precommit hooks) but nothing has made the test pass.

Goal: Make the test pass WITHOUT hacks. The two clones should converge to identical state after sync.

**Major progress achieved!** The two-clone workflow now converges correctly.

**What was fixed:**
--3d844c58: Implemented content-hash based rename detection
- bd-64c05d00.1: Fixed test to compare content not timestamps
- Both clones now converge to identical issue databases
- test-1 and test-2 have correct titles in both clones
- No more title swapping!

**Current status (VERIFIED):**
✅ Acceptance criteria 1: TestTwoCloneCollision passes (confirmed Oct 28)
✅ Acceptance criteria 2: Both clones converge to identical issue database (content matches)
✅ Acceptance criteria 3: No manual conflict resolution required (automatic)
✅ Acceptance criteria 4: Git status clean
✅ Acceptance criteria 5: bd ready output identical (timestamps are expected difference)

**ALL ACCEPTANCE CRITERIA MET!** This issue is complete and can be closed.


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.


Add richer query capabilities to bd list

Current bd list filters are limited to basic field matching (status, priority, type, assignee, label). This forces users to resort to piping through jq for common queries.

Missing query capabilities:
- Pattern matching: --title-contains, --desc-contains
- Date ranges: --created-after, --updated-before, --closed-after
- Empty/null checks: --empty-description, --no-assignee, --no-labels
- Numeric ranges: --priority-min, --priority-max
- Complex boolean logic: --and, --or operators
- Full-text search: --search across all text fields
- Negation: --not-status, --exclude-label

Example use cases:
- Find issues with empty descriptions
- Find stale issues not updated in 30 days
- Find high-priority bugs with no assignee
- Search for keyword across title/description/notes

Implementation approach:
- Add query builder pattern to storage layer
- Support --query DSL for complex queries
- Keep simple flags for common cases
- Add --json output for programmatic use

## Progress Update

**Completed:**
- ✅ Extended IssueFilter struct with new fields (pattern matching, date ranges, empty/null checks, priority ranges)
- ✅ Updated SQLite SearchIssues implementation
- ✅ Added CLI flags to list.go
- ✅ Added parseTimeFlag helper
- ✅ Comprehensive tests added - all passing

**Remaining:**
- ⚠ RPC layer needs updating (internal/rpc/protocol.go ListArgs)
- ⚠ Daemon handler needs to forward new filters
- ⚠ End-to-end testing with daemon mode
- 📝 Documentation updates

**Files Modified:**
- internal/types/types.go
- internal/storage/sqlite/sqlite.go
- cmd/bd/list.go
- cmd/bd/list_test.go

**Next Steps:**
1. Update RPC protocol
2. Update daemon handler
3. Test with daemon mode
4. Update docs


Extract daemon logger functions to daemon_logger.go


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.


Benchmark Helper Functions

Extend existing benchmark helpers in internal/storage/sqlite/bench_helpers_test.go (or create if organizing separately).

Existing helper (in compact_bench_test.go):
- setupBenchDB(tb) - Creates temp SQLite database with basic config
  * Used by compact and cycle benchmarks
  * Returns (*SQLiteStorage, cleanup func())

New helpers to add:
- setupLargeBenchDB(b *testing.B) storage.Storage
  * Creates 10K issue database using LargeSQLite fixture
  * Returns configured storage instance

- setupXLargeBenchDB(b *testing.B) storage.Storage
  * Creates 20K issue database using XLargeSQLite fixture
  * Returns configured storage instance

Implementation options:
1. Add to existing compact_bench_test.go (co-located with setupBenchDB)
2. Create new bench_helpers_test.go for organization

Both approaches:
- Build tag: //go:build bench
- Uses fixture generator from internal/testutil/fixtures
- Follows existing setupBenchDB() pattern
- Handles database cleanup

These helpers reduce duplication across new benchmark functions and provide consistent large-scale database setup.


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

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


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

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

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


Add error handling guidance for export metadata update failures

## Problem
The bd-ymj fix treats all metadata update failures as warnings:

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

But if metadata updates fail, the NEXT export will fail with "JSONL content has changed since last import".

## Questions
1. Should metadata failures be critical (return error)?
2. Should we implement retry logic?
3. Is warning acceptable (safe, just requires import before next export)?

## Current Behavior
- Metadata failure is silent (only logged)
- User won't know until next export fails
- Next export requires import first (safe but annoying)

## Recommendation
Add clear comment explaining the trade-off:

```go
// Note: Metadata update failures are treated as warnings, not errors.
// This is acceptable because the worst case is the next export will
// require an import first, which is safe and prevents data loss.
// Alternative: Make this critical and fail the export if metadata
// updates fail (but this makes exports more fragile).
if err := store.SetMetadata(ctx, "last_import_hash", currentHash); err != nil {
    log.log("Warning: failed to update last_import_hash: %v", err)
    log.log("Next export may require running 'bd import' first")
}
```

## Files
- cmd/bd/daemon_sync.go

## Decision Needed
Choose approach based on failure mode preferences


Audit all cmd/bd tests and group into suites

Analyze all 279 tests in cmd/bd and identify:
1. Which tests can share DB setup (most of them\!)
2. Which tests actually need isolation (export/import, git ops)
3. Optimal grouping into test suites

Create a mapping document showing:
- Current: 279 individual test functions
- Proposed: ~10-15 test suites with subtests
- Expected speedup per suite

Blocks all refactoring work.


Testing event-driven mode!


Test graceful degradation when server unavailable

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

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

Success Metric: Zero functional difference when Agent Mail unavailable


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.


Remove unreachable RPC methods

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

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

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

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

Impact: Removes ~80 LOC of unused RPC code


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.


Publish @beads/bd package to npm registry

Publish the npm package to the public npm registry:

## Prerequisites
- npm account created
- Organization @beads created (or use different namespace)
- npm login completed locally
- Package tested locally (bd-f282 completed)

## Publishing steps
1. Verify package.json version matches current bd version
2. Run npm pack and inspect tarball contents
3. Test installation from tarball one more time
4. Run npm publish --access public
5. Verify package appears on https://www.npmjs.com/package/@beads/bd
6. Test installation from registry: npm install -g @beads/bd

## Post-publish
- Add npm badge to README.md
- Update CHANGELOG.md with npm package release
- Announce in release notes

## Note
- May need to choose different name if @beads namespace unavailable
- Alternative: beads-cli, bd-cli, or unscoped beads-issue-tracker


Implement bd resolve-conflicts (git merge conflicts in JSONL)

Automatically detect and resolve git merge conflicts in .beads/issues.jsonl file.

Features:
- Detect conflict markers in JSONL
- Parse conflicting issues from HEAD and BASE
- Provide mechanical resolution (remap duplicate IDs)
- Support AI-assisted resolution (requires internal/ai package)

See repair_commands.md lines 125-353 for design.


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


Add template support for issue creation

Support creating issues from predefined templates to streamline common workflows like epics, bug reports, or feature proposals.

Example usage:
  bd create --from-template epic "Phase 3 Features"
  bd create --from-template bug "Login failure"
  bd template list
  bd template create epic

Templates should include:
- Pre-filled description structure
- Suggested priority and type
- Common labels
- Design/acceptance criteria sections

Implementation notes:
- Store templates in .beads/templates/ directory
- Support YAML or JSON format
- Ship with built-in templates (epic, bug, feature)
- Allow custom project-specific templates


bd message: Improve type safety with typed parameter structs


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.


Add test coverage for internal/config package


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


Add TestNWayCollision for 5+ clones

## Overview
Add comprehensive tests for N-way (5+) collision resolution to verify the solution scales beyond 3 clones.

## Purpose
While TestThreeCloneCollision validates the basic N-way case, we need to verify:
1. Solution scales to arbitrary N
2. Performance is acceptable with more clones
3. Convergence time is bounded
4. No edge cases in larger collision groups

## Implementation Tasks

### 1. Create TestFiveCloneCollision
File: beads_twoclone_test.go (or new beads_nway_test.go)

```go
func TestFiveCloneCollision(t *testing.T) {
    // Test with 5 clones creating same ID with different content
    // Verify all 5 clones converge after sync rounds

    t.Run("SequentialSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "A", "B", "C", "D", "E")
    })

    t.Run("ReverseSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "E", "D", "C", "B", "A")
    })

    t.Run("RandomSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "C", "A", "E", "B", "D")
    })
}
```

### 2. Implement generalized testNCloneCollision
Generalize the 3-clone test to handle arbitrary N:

```go
func testNCloneCollision(t *testing.T, numClones int, syncOrder ...string) {
    t.Helper()

    if len(syncOrder) != numClones {
        t.Fatalf("syncOrder length (%d) must match numClones (%d)",
            len(syncOrder), numClones)
    }

    tmpDir := t.TempDir()

    // Setup remote and N clones
    remoteDir := setupBareRepo(t, tmpDir)
    cloneDirs := make(map[string]string)

    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        cloneDirs[name] = setupClone(t, tmpDir, remoteDir, name)
    }

    // Each clone creates issue with same ID but different content
    for name, dir := range cloneDirs {
        createIssue(t, dir, fmt.Sprintf("Issue from clone %s", name))
    }

    // Sync in specified order
    for _, name := range syncOrder {
        syncClone(t, cloneDirs[name], name)
    }

    // Final pull for convergence
    for name, dir := range cloneDirs {
        finalPull(t, dir, name)
    }

    // Verify all clones have all N issues
    expectedTitles := make(map[string]bool)
    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        expectedTitles[fmt.Sprintf("Issue from clone %s", name)] = true
    }

    for name, dir := range cloneDirs {
        titles := getTitles(t, dir)
        if !compareTitleSets(titles, expectedTitles) {
            t.Errorf("Clone %s missing issues: expected %v, got %v",
                name, expectedTitles, titles)
        }
    }

    t.Log("✓ All", numClones, "clones converged successfully")
}
```

### 3. Add performance benchmarks
Test convergence time and memory usage:

```go
func BenchmarkNWayCollision(b *testing.B) {
    for _, n := range []int{3, 5, 10, 20} {
        b.Run(fmt.Sprintf("N=%d", n), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                // Run N-way collision and measure time
                testNCloneCollisionBench(b, n)
            }
        })
    }
}
```

### 4. Add convergence time tests
Verify bounded convergence:

```go
func TestConvergenceTime(t *testing.T) {
    // Test that convergence happens within expected rounds
    // For N clones, should converge in at most N-1 sync rounds

    for n := 3; n <= 10; n++ {
        t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
            rounds := measureConvergenceRounds(t, n)
            maxExpected := n - 1
            if rounds > maxExpected {
                t.Errorf("Convergence took %d rounds, expected ≤ %d",
                    rounds, maxExpected)
            }
        })
    }
}
```

### 5. Add edge case tests
Test boundary conditions:
- All N clones have identical content (dedup works)
- N-1 clones have same content, 1 differs
- All N clones have unique content
- Mix of collisions and non-collisions

## Acceptance Criteria
- TestFiveCloneCollision passes with all sync orders
- All 5 clones converge to identical content
- Performance is acceptable (< 5 seconds for 5 clones)
- Convergence time is bounded (≤ N-1 rounds)
- Edge cases handled correctly
- Benchmarks show scalability to 10+ clones

## Files to Create/Modify
- beads_twoclone_test.go or beads_nway_test.go
- Add helper functions for N-clone setup

## Testing Strategy

### Test Matrix
| N Clones | Sync Orders | Expected Result |
|----------|-------------|-----------------|
| 3        | A→B→C       | Pass            |
| 3        | C→B→A       | Pass            |
| 5        | A→B→C→D→E   | Pass            |
| 5        | E→D→C→B→A   | Pass            |
| 5        | Random      | Pass            |
| 10       | Sequential  | Pass            |

### Performance Targets
- 3 clones: < 2 seconds
- 5 clones: < 5 seconds
- 10 clones: < 15 seconds

## Dependencies
- Requires bd-cbed9619.5, bd-cbed9619.4, bd-cbed9619.3, bd-cbed9619.2 to be completed
- TestThreeCloneCollision must pass first

## Success Metrics
- All tests pass for N ∈ {3, 5, 10}
- Convergence time scales linearly (O(N))
- Memory usage reasonable (< 100MB for 10 clones)
- No data corruption or loss in any scenario


Test issue 1


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


Unit tests for Debouncer

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


Update AGENTS.md with event-driven mode

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


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


Extract Daemon struct and config into internal/daemonrunner

Create internal/daemonrunner with Config struct and Daemon struct. Move daemon runtime logic from cmd/bd/daemon.go Run function into Daemon.Start/Stop methods.

Refactoring complete! Created internal/daemonrunner package with:
- Config struct (config.go)
- Daemon struct with Start/Stop methods (daemon.go)
- RPC server lifecycle (rpc.go)
- Sync loop implementation (sync.go)
- Git operations (git.go)
- Process management (process.go, flock_*.go)
- Logger setup (logger.go)
- Platform-specific signal handling (signals_*.go)
- Database fingerprint validation (fingerprint.go)

Build succeeds and most daemon tests pass. Import functionality still delegated to cmd/bd (marked with TODO(bd-b5a3)).


Add concurrent import race condition tests

Currently no tests verify behavior when multiple clones import simultaneously with external_ref matching.

Scenarios to test:
1. Two clones import same external_ref update at same time
2. Clone A imports while Clone B updates same issue
3. Verify transaction isolation prevents corruption
4. Document expected behavior (last-write-wins vs timestamp-based)

Related: bd-1022
Files: internal/importer/external_ref_test.go


Remove Cache Configuration Docs

Remove documentation of deprecated cache env vars


Add depth-based batch creation in upsertIssues

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


Fix PR #319: Performance Improvements - CI failures and lint errors

PR #319 (Performance Improvements) has excellent performance optimizations but is blocked by CI failures.

## The PR
- URL: https://github.com/steveyegge/beads/pull/319
- Author: @rsnodgrass (Ryan)
- Claimed improvements: bd ready 20.5x faster (752ms → 36.6ms), startup 10.5x faster

## CI Failures

### Lint Errors (8 total)
1. cmd/bd/deletion_tracking.go:57 - unchecked os.Remove
2. cmd/bd/import.go:548 - unchecked os.RemoveAll
3. cmd/bd/message.go:205 - unchecked resp.Body.Close
4. cmd/bd/migrate_issues.go:633 - unchecked fmt.Scanln
5. cmd/bd/migrate_issues.go:701 - unchecked MarkFlagRequired
6. cmd/bd/migrate_issues.go:702 - unchecked MarkFlagRequired
7. cmd/bd/show.go:610 - gosec G104 unhandled error
8. cmd/bd/show.go:614 - gosec G104 unhandled error

### Test Failures
All syncbranch_test.go tests failing with:
"migration external_ref_column failed: failed to create index on external_ref: sqlite3: SQL logic error: no such table: main.issues"

This suggests the PR branch needs rebasing on current main.

## Required Work

### 1. Fix Lint Errors
Add proper error handling for all 8 flagged locations. Most can use _ = or log warnings.

### 2. Rebase on Current Main
The migration test failures indicate the branch is out of sync. Need to:
- git fetch upstream
- git rebase upstream/main
- Resolve any conflicts
- Verify tests pass locally

### 3. Verify CI Passes
- All lint checks green
- All tests pass (Linux, Windows, Nix)

## Optional Improvements
- Consider splitting into smaller PRs (core index, WASM cache, testing infra)
- Add documentation for benchmark usage
- Extract helper functions in doctor/perf.go for better testability

## Value
This PR delivers real performance improvements. The index optimization alone is worth merging quickly once CI is fixed.


Improve Beads test coverage from 46% to 80%


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.


Implement fs bridge layer for WASM (Go syscall/js to Node.js fs)

Go's os package in WASM returns 'not implemented on js' for mkdir and other file operations. Need to create a bridge layer that:

1. Detects WASM environment (GOOS=js)
2. Uses syscall/js to call Node.js fs module functions
3. Implements wrappers for:
   - os.MkdirAll
   - os.ReadFile / os.WriteFile
   - os.Open / os.Create
   - os.Stat / os.Lstat
   - filepath operations

Approach:
- Create internal/wasm/fs_bridge.go with //go:build js && wasm
- Export Node.js fs functions to Go using global.readFileSync, global.writeFileSync, etc.
- Wrap in Go API that matches os package signatures
- Update beads.go and storage layer to use bridge when in WASM

This unblocks bd-4462 (basic WASM testing) and [deleted:bd-5bbf] (feature parity testing).


Add TestNWayCollision for 5+ clones

## Overview
Add comprehensive tests for N-way (5+) collision resolution to verify the solution scales beyond 3 clones.

## Purpose
While TestThreeCloneCollision validates the basic N-way case, we need to verify:
1. Solution scales to arbitrary N
2. Performance is acceptable with more clones
3. Convergence time is bounded
4. No edge cases in larger collision groups

## Implementation Tasks

### 1. Create TestFiveCloneCollision
File: beads_twoclone_test.go (or new beads_nway_test.go)

```go
func TestFiveCloneCollision(t *testing.T) {
    // Test with 5 clones creating same ID with different content
    // Verify all 5 clones converge after sync rounds

    t.Run("SequentialSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "A", "B", "C", "D", "E")
    })

    t.Run("ReverseSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "E", "D", "C", "B", "A")
    })

    t.Run("RandomSync", func(t *testing.T) {
        testNCloneCollision(t, 5, "C", "A", "E", "B", "D")
    })
}
```

### 2. Implement generalized testNCloneCollision
Generalize the 3-clone test to handle arbitrary N:

```go
func testNCloneCollision(t *testing.T, numClones int, syncOrder ...string) {
    t.Helper()

    if len(syncOrder) != numClones {
        t.Fatalf("syncOrder length (%d) must match numClones (%d)",
            len(syncOrder), numClones)
    }

    tmpDir := t.TempDir()

    // Setup remote and N clones
    remoteDir := setupBareRepo(t, tmpDir)
    cloneDirs := make(map[string]string)

    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        cloneDirs[name] = setupClone(t, tmpDir, remoteDir, name)
    }

    // Each clone creates issue with same ID but different content
    for name, dir := range cloneDirs {
        createIssue(t, dir, fmt.Sprintf("Issue from clone %s", name))
    }

    // Sync in specified order
    for _, name := range syncOrder {
        syncClone(t, cloneDirs[name], name)
    }

    // Final pull for convergence
    for name, dir := range cloneDirs {
        finalPull(t, dir, name)
    }

    // Verify all clones have all N issues
    expectedTitles := make(map[string]bool)
    for i := 0; i < numClones; i++ {
        name := string(rune('A' + i))
        expectedTitles[fmt.Sprintf("Issue from clone %s", name)] = true
    }

    for name, dir := range cloneDirs {
        titles := getTitles(t, dir)
        if !compareTitleSets(titles, expectedTitles) {
            t.Errorf("Clone %s missing issues: expected %v, got %v",
                name, expectedTitles, titles)
        }
    }

    t.Log("✓ All", numClones, "clones converged successfully")
}
```

### 3. Add performance benchmarks
Test convergence time and memory usage:

```go
func BenchmarkNWayCollision(b *testing.B) {
    for _, n := range []int{3, 5, 10, 20} {
        b.Run(fmt.Sprintf("N=%d", n), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                // Run N-way collision and measure time
                testNCloneCollisionBench(b, n)
            }
        })
    }
}
```

### 4. Add convergence time tests
Verify bounded convergence:

```go
func TestConvergenceTime(t *testing.T) {
    // Test that convergence happens within expected rounds
    // For N clones, should converge in at most N-1 sync rounds

    for n := 3; n <= 10; n++ {
        t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
            rounds := measureConvergenceRounds(t, n)
            maxExpected := n - 1
            if rounds > maxExpected {
                t.Errorf("Convergence took %d rounds, expected ≤ %d",
                    rounds, maxExpected)
            }
        })
    }
}
```

### 5. Add edge case tests
Test boundary conditions:
- All N clones have identical content (dedup works)
- N-1 clones have same content, 1 differs
- All N clones have unique content
- Mix of collisions and non-collisions

## Acceptance Criteria
- TestFiveCloneCollision passes with all sync orders
- All 5 clones converge to identical content
- Performance is acceptable (< 5 seconds for 5 clones)
- Convergence time is bounded (≤ N-1 rounds)
- Edge cases handled correctly
- Benchmarks show scalability to 10+ clones

## Files to Create/Modify
- beads_twoclone_test.go or beads_nway_test.go
- Add helper functions for N-clone setup

## Testing Strategy

### Test Matrix
| N Clones | Sync Orders | Expected Result |
|----------|-------------|-----------------|
| 3        | A→B→C       | Pass            |
| 3        | C→B→A       | Pass            |
| 5        | A→B→C→D→E   | Pass            |
| 5        | E→D→C→B→A   | Pass            |
| 5        | Random      | Pass            |
| 10       | Sequential  | Pass            |

### Performance Targets
- 3 clones: < 2 seconds
- 5 clones: < 5 seconds
- 10 clones: < 15 seconds

## Dependencies
- Requires bd-cbed9619.5, bd-cbed9619.4, bd-0dcea000, bd-4d7fca8a to be completed
- TestThreeCloneCollision must pass first

## Success Metrics
- All tests pass for N ∈ {3, 5, 10}
- Convergence time scales linearly (O(N))
- Memory usage reasonable (< 100MB for 10 clones)
- No data corruption or loss in any scenario


Separate process/lock/PID concerns into process.go

Create internal/daemonrunner/process.go with: acquireDaemonLock, PID file read/write, stopDaemon, isDaemonRunning, getPIDFilePath, socket path helpers, version check.


Add security tests for WriteFile permissions in doctor command

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

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

In cmd/bd/doctor/gitignore.go:98, os.WriteFile uses 0644 permissions, flagged by gosec G306 as potentially too permissive.

Add tests to verify:
- File is created with appropriate permissions (0600 or less)
- Existing file permissions are not loosened
- File ownership is correct
- Sensitive data handling if .gitignore contains secrets

This ensures .gitignore files are created with secure permissions to prevent unauthorized access.

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


Document external_ref in content hash behavior

The content hash includes external_ref, which has implications that should be documented.

Current behavior:
- external_ref is included in content hash calculation (collision.go:158-160)
- Changing external_ref changes content hash
- This means: local issue → add external_ref → different hash

Implications:
- Local issue + external_ref addition = looks like 'new content'
- May not match by content hash in some scenarios
- Generally correct behavior, but subtle

Action items:
- Document in code comments
- Add to ARCHITECTURE.md or similar
- Add test demonstrating this behavior
- Consider if this is desired long-term

Related: bd-1022
Files: internal/storage/sqlite/collision.go:158-160


Publish bd-wasm to npm

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

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

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


Wire config to import pipeline

Connect import.orphan_handling config to importer.go and sqlite validation functions. Pass mode flag through call chain. Implement all four modes (strict/resurrect/skip/allow) with proper error messages and logging.


Fix linting error in migrate.go

Linter reports error:
```
cmd/bd/migrate.go:647:37: cleanupWALFiles - result 0 (error) is always nil (unparam)
```

The `cleanupWALFiles` function always returns nil, so the error return type should be removed or the function should actually return errors when appropriate.


Add tests for internal/importer package

Currently 0.0% coverage. Need tests for JSONL import logic including collision detection and resolution.


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


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.


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 {


GH#516: Automate 'landing the plane' setup in AGENTS.md

bd init or /beads:init should auto-add landing-the-plane instructions to AGENTS.md (and @AGENTS.md for web Claude). Reduces manual setup. See: https://github.com/steveyegge/beads/issues/516


Rapid 5


bd export should detect and prevent stale database exports

## Problem

When `bd export` is run with a stale database (older than issues.jsonl), it silently overwrites the JSONL file with stale data, causing data loss.

## What Happened (vc project)

1. Agent A created 4 new issues and exported to issues.jsonl (commit 99a9d58)
2. Agent A closed an issue and exported again (commit 58b4613) - JSONL now has 4 epics
3. Agent B had stale database (from before step 1)
4. Agent B worked on unrelated issue and exported (commit 0609233)
5. Agent B's export **overwrote issues.jsonl**, removing the 4 epics created by Agent A
6. Required manual recovery by re-exporting from Agent A's correct database

## Expected Behavior

`bd export` should detect that the database is stale and either:
- **Refuse to export** with error message explaining the issue
- **Warn prominently** and require explicit --force flag to override
- **Auto-import first** to sync database before exporting

## How to Detect Staleness

Compare modification times (similar to VC's ValidateDatabaseFreshness):
1. Check .db, .db-wal, .db-shm timestamps (use newest for WAL mode)
2. Check issues.jsonl timestamp
3. If JSONL is newer by >1 second: database is stale

## Suggested Fix

Add staleness check in `bd export`:

```go
func Export(dbPath, jsonlPath string, force bool) error {
    // Check if database is stale
    if !force {
        if err := checkDatabaseFreshness(dbPath, jsonlPath); err != nil {
            return fmt.Errorf("database is stale: %w\n" +
                "Run 'bd import %s' first to sync, or use --force to override",
                err, jsonlPath)
        }
    }

    // Proceed with export...
}
```

## Impact

- **Severity**: High (silent data loss)
- **Frequency**: Happens in multi-agent workflows when agents don't sync
- **Workaround**: Manual recovery (re-export from correct database)

## References

- VC issue tracker: commits 58b4613 -> 0609233 -> c41c638
- VC has similar check: `storage.ValidateDatabaseFreshness()`
- Tolerance: 1 second (handles filesystem timestamp precision)

Fixed with ID-based comparison instead of just count. Now detects:
1. DB has fewer issues than JSONL (count check)
2. DB has different issues than JSONL (ID comparison)

Both scenarios now properly refuse export unless --force is used.

Implementation uses getIssueIDsFromJSONL() to build a set of IDs from JSONL, then checks if any JSONL IDs are missing from DB. Shows specific missing issue IDs in error message.


Add functional tests for FlushManager correctness verification


Fixture Generator for Realistic Test Data

Create internal/testutil/fixtures/fixtures.go with functions to generate realistic test data at scale.

Functions:
- LargeSQLite(storage) - 10K issues, native SQLite
- XLargeSQLite(storage) - 20K issues, native SQLite
- LargeFromJSONL(storage) - 10K issues imported from JSONL
- XLargeFromJSONL(storage) - 20K issues imported from JSONL

Data characteristics:
- Epic hierarchies (depth 4): Epic → Feature → Task → Subtask
- Cross-linked dependencies (tasks blocking across epics)
- Realistic status/priority/label distribution
- Representative assignees and temporal data

Implementation:
- Single file: internal/testutil/fixtures/fixtures.go
- No config structs, simple direct functions
- Seeded RNG for reproducibility
- Reusable by both benchmarks and tests


Measure notification latency vs git sync

Benchmark end-to-end latency for status updates to propagate between agents using both methods.

Acceptance Criteria:
- Measure git sync latency (commit → push → pull → import)
- Measure Agent Mail latency (send_message → fetch_inbox)
- Document latency distribution (p50, p95, p99)
- Verify <100ms claim for Agent Mail
- Compare against 1-5s baseline for git

Success Metric: Agent Mail latency < 100ms, git sync latency > 1000ms

Latency benchmark completed. Results documented in latency_results.md:
- Git sync: 2000-5000ms (full cycle with network)
- Agent Mail: <100ms (HTTP API round-trip)
- Confirms 20-50x latency reduction claim


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


Add database maintenance commands section to QUICKSTART.md

**Problem:**
Users don't discover `bd compact` or `bd cleanup` commands until their database grows large. These maintenance commands aren't mentioned in quickstart documentation.

Related to issue #349 item #4.

**Current state:**
docs/QUICKSTART.md ends at line 217 with "See README.md for full documentation" but has no mention of maintenance operations.

**Proposed addition:**
Add a "Database Maintenance" section after line 140 (before "Advanced: Agent Mail" section) covering:
- When database grows (many closed issues)
- How to view compaction statistics
- How to compact old issues
- How to delete closed issues
- Warning about permanence

**Example content:**
```markdown
## Database Maintenance

As your project accumulates closed issues, the database grows. Use these commands to manage size:

```bash
# View compaction statistics
bd compact --stats

# Preview compaction candidates (30+ days closed)
bd compact --analyze --json

# Apply agent-generated summary
bd compact --apply --id bd-42 --summary summary.txt

# Immediately delete closed issues (use with caution!)
bd cleanup --force
```

**When to compact:**
- Database file > 10MB with many old closed issues
- After major project milestones
- Before archiving a project phase
```

**Files to modify:**
- docs/QUICKSTART.md (add section after line 140)


Investigate and optimize test suite performance

Test suite is taking very long to run (>45s for cmd/bd tests, full suite timing unknown but was cancelled).

This impacts development velocity and CI/CD performance.

Investigation needed:
- Profile which tests are slowest
- Identify bottlenecks (disk I/O, network, excessive setup/teardown?)
- Consider parallelization opportunities
- Look for redundant test cases
- Check if integration tests can be optimized


bd doctor doesn't detect version mismatches or stale daemons


Fix silent error handling in RPC response writing

Marshal and write errors silently ignored in writeResponse, can send partial JSON and hang clients.

Location: internal/rpc/server_lifecycle_conn.go:228-232

Problem:
- json.Marshal error ignored - cyclic reference sends corrupt JSON
- Write error ignored - connection closed, no indication to caller
- WriteByte error ignored - client hangs waiting for newline
- Flush error ignored - partial data buffered

Current code:
func (s *Server) writeResponse(writer *bufio.Writer, resp Response) {
    data, _ := json.Marshal(resp)  // Ignored!
    _, _ = writer.Write(data)      // Ignored!
    _ = writer.WriteByte('\n')     // Ignored!
    _ = writer.Flush()             // Ignored!
}

Solution: Return errors, handle in caller, close connection on error

Impact: Client hangs waiting for response; corrupt JSON sent

Effort: 1 hour


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`


bd config set succeeds but doesn't persist to config.toml

Commands like `bd config set daemon.auto_push true` return "Set daemon.auto_push = true" but the config file is never created and `bd info --json | jq '.config'` returns null.

**Steps to reproduce:**
1. Run `bd config set daemon.auto_push true`
2. See success message: "Set daemon.auto_push = true"
3. Check `cat .beads/config.toml` → file doesn't exist
4. Check `bd info --json | jq '.config'` → returns null

**Expected:**
- .beads/config.toml should be created with the setting
- bd info should show the config value

**Impact:**
Users can't enable auto-push/auto-commit via CLI as documented in AGENTS.md


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


Issue 1


Fix bd doctor to recommend issues.jsonl as canonical (not beads.jsonl)


Fix bd doctor hash ID detection for short all-numeric hashes

bd doctor incorrectly flags hash-based IDs as sequential when they are short (3-4 chars) and all-numeric (e.g., pf-158).

Root cause: isHashID() in cmd/bd/migrate_hash_ids.go:328-358 uses faulty heuristic:
- For IDs < 5 chars, only returns true if contains letters
- But base36 hash IDs can be 3+ chars and all-numeric (MinLength: 3)
- Example: pf-158 is valid hash ID but flagged as sequential

Fix: Check multiple IDs (10-20 samples) instead of single-ID pattern matching:
- Sample IDs across database
- Check majority pattern (sequential vs hash format)
- Sequential: 1-4 digits (bd-1, bd-2...)
- Hash: 3-8 chars base36 (pf-158, pf-3s9...)

Impact: False positive warnings in bd doctor output


Stress test: event storm handling

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


Add UNIQUE constraint on external_ref column

The external_ref column should have a UNIQUE constraint to prevent multiple issues from having the same external reference. This ensures data integrity when syncing from external systems (Jira, GitHub, Linear).

Current behavior:
- Multiple issues can have the same external_ref
- GetIssueByExternalRef returns first match (non-deterministic with duplicates)

Proposed solution:
- Add UNIQUE constraint to external_ref column
- Add migration to check for and resolve existing duplicates
- Update tests to verify constraint enforcement

Related: bd-1022


Feature with design

This is a description


Test P1 priority


npm package for bd with native binaries

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

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

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

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

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


Comprehensive testing for separate branch workflow including unit tests, integration tests, and performance testing.

Tasks:
- Unit tests for worktree management
- Unit tests for config parsing
- Integration tests: create/update/close → beads branch
- Integration test: merge beads → main
- Integration test: protected branch scenario
- Integration test: network failure recovery
- Integration test: config change handling
- Manual testing guide
- Performance testing (worktree overhead)

Test scenarios: fresh setup, issue operations, merge workflow, protected branch, error handling, migration, multiple workspaces, sparse checkout

Estimated effort: 4-5 days

Completed comprehensive test coverage. Added 4 new integration tests: config change handling, multiple concurrent clones (3-way), performance testing (avg 77ms < 150ms target), and network failure recovery. All tests pass. Coverage includes fresh setup, issue ops, error handling, multiple workspaces, sparse checkout, config changes, network failures, and performance.


Test label