bd ready includes blocked issues (GH#544)

Issues with 'blocks' dependencies still appear in bd ready. The ready query should exclude issues that have unresolved blockers.

Fix in: cmd/bd/ready.go or internal query logic.


Fix auto-import creating duplicates instead of updating issues

ROOT CAUSE: server_export_import_auto.go line 221 uses ResolveCollisions: true for ALL auto-imports. This is wrong.

Problem:
- ResolveCollisions is for branch merges (different issues with same ID)
- Auto-import should UPDATE existing issues, not create duplicates
- Every git pull creates NEW duplicate issues with different IDs
- Two agents ping-pong creating endless duplicates

Evidence:
- 31 duplicate groups found (bd duplicates)
- bd-236-246 are duplicates of bd-224-235
- Both agents keep pulling and creating more duplicates
- JSONL file grows endlessly with duplicates

The Fix:
Change checkAndAutoImportIfStale in server_export_import_auto.go:
- Remove ResolveCollisions: true (line 221)
- Use normal import logic that updates existing issues by ID
- Only use ResolveCollisions for explicit bd import --resolve-collisions

Impact: Critical - makes beads unusable for multi-agent workflows


Add test coverage for internal/validation package


Add cancellation and timeout tests

Add comprehensive tests for context cancellation and timeout behavior.

## Context
Part of context propagation work. Validates that bd-rtp and bd-yb8 work correctly.

## Test Coverage Needed

### 1. Cancellation Tests
- [ ] Import operation cancelled mid-stream
- [ ] Export operation cancelled mid-stream
- [ ] Database query cancelled during long operation
- [ ] No corruption after cancellation
- [ ] Proper cleanup (defers execute, connections closed)

### 2. Timeout Tests
- [ ] Operations respect context deadlines
- [ ] Appropriate error messages on timeout
- [ ] State remains consistent after timeout

### 3. Signal Handling Tests
- [ ] SIGINT (Ctrl+C) triggers cancellation
- [ ] SIGTERM triggers graceful shutdown
- [ ] Multiple signals handled correctly

## Implementation Approach
```go
func TestImportCancellation(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())

    // Start import in goroutine
    go func() {
        err := runImport(ctx, largeFile)
        assert.Error(err, context.Canceled)
    }()

    // Cancel after short delay
    time.Sleep(100 * time.Millisecond)
    cancel()

    // Verify database integrity
    assertDatabaseConsistent(t, store)
}
```

## Files to Create/Update
- cmd/bd/import_test.go - cancellation tests
- cmd/bd/export_test.go - cancellation tests
- internal/storage/sqlite/*_test.go - context timeout tests

## Acceptance Criteria
- [ ] All critical operations have cancellation tests
- [ ] Tests verify database integrity after cancellation
- [ ] Signal handling tested (if feasible)
- [ ] Test coverage >80% for context paths


Upgrade notification persists after running suggested commands

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

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

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

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

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


Parent: bd-imj

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

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

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

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


Dogfood: Migrate beads repo to hash IDs

Final validation: migrate the beads project itself to hash-based IDs.

## Purpose
Dogfooding the migration on beads' own issue database to:
1. Validate migration tool works on real data
2. Discover edge cases
3. Verify all workflows still work
4. Build confidence for users

## Pre-Migration Checklist
- [ ] All bd-f8b764c9 child tasks completed
- [ ] All tests pass: `go test ./...`
- [ ] Migration tool tested on test databases
- [ ] Documentation updated
- [ ] MCP server updated and published
- [ ] Clean git status

## Migration Steps

### 1. Create Backup
```bash
# Backup database
cp -r .beads .beads.backup-1761798568

# Backup JSONL
cp .beads/beads.jsonl .beads/beads.jsonl.backup

# Create git branch for migration
git checkout -b hash-id-migration
git add .beads.backup-*
git commit -m "Pre-migration backup"
```

### 2. Run Migration (Dry Run)
```bash
bd migrate --hash-ids --dry-run > migration-plan.txt
cat migration-plan.txt

# Review:
# - Number of issues to migrate
# - Hash collision check (should be zero)
# - Text reference updates
# - Dependency updates
```

### 3. Run Migration (Real)
```bash
bd migrate --hash-ids 2>&1 | tee migration-log.txt

# Expected output:
# ✓ Backup created: .beads/beads.db.backup-1234567890
# ✓ Generated 150 hash IDs
# ✓ No hash collisions detected
# ✓ Updated issues table schema
# ✓ Updated 150 issue IDs
# ✓ Updated 87 dependencies
# ✓ Updated 234 text references
# ✓ Exported to .beads/beads.jsonl
# ✓ Migration complete!
```

### 4. Validation

#### Database Integrity
```bash
# Check all issues have hash IDs
bd list | grep -v "bd-[a-f0-9]\{8\}" && echo "FAIL: Non-hash IDs found"

# Check all issues have aliases
sqlite3 .beads/beads.db "SELECT COUNT(*) FROM issues WHERE alias IS NULL"
# Should be 0

# Check no alias duplicates
sqlite3 .beads/beads.db "SELECT alias, COUNT(*) FROM issues GROUP BY alias HAVING COUNT(*) > 1"
# Should be empty
```

#### Functionality Tests
```bash
# Test show by hash ID
bd show bd-

# Test show by alias
bd show #1

# Test create new issue
bd create "Test issue after migration" -p 2
# Should get hash ID + alias

# Test update
bd update #1 --priority 1

# Test dependencies
bd dep tree #1

# Test export
bd export
git diff .beads/beads.jsonl
# Should show hash IDs
```

#### Text Reference Validation
```bash
# Check that old IDs were updated in descriptions
grep -r "bd-[0-9]\{1,3\}[^a-f0-9]" .beads/beads.jsonl && echo "FAIL: Old ID format found"

# Verify hash ID references exist
grep -o "bd-[a-f0-9]\{8\}" .beads/beads.jsonl | sort -u | wc -l
# Should match number of hash IDs
```

### 5. Commit Migration
```bash
git add .beads/beads.jsonl .beads/beads.db
git commit -m "Migrate to hash-based IDs (v2.0)

- Migrated 150 issues to hash IDs
- Preserved aliases (#1-#150)
- Updated 87 dependencies
- Updated 234 text references
- Zero hash collisions

Migration log: migration-log.txt"

git push origin hash-id-migration
```

### 6. Create PR
```bash
gh pr create --title "Migrate to hash-based IDs (v2.0)" --body "## Summary
Migrates beads project to hash-based IDs as part of v2.0 release.

## Migration Stats
- Issues migrated: 150
- Dependencies updated: 87
- Text references updated: 234
- Hash collisions: 0
- Aliases assigned: 150

## Validation
- ✅ All tests pass
- ✅ Database integrity verified
- ✅ All workflows tested (show, update, create, deps)
- ✅ Text references updated correctly
- ✅ Export produces valid JSONL

## Files Changed
- `.beads/beads.jsonl` - Hash IDs in all entries
- `.beads/beads.db` - Schema updated with aliases

## Rollback
If issues arise:
\`\`\`bash
mv .beads.backup-1234567890 .beads
bd export
\`\`\`

See migration-log.txt for full details."
```

### 7. Merge and Cleanup
```bash
# After PR approval
git checkout main
git merge hash-id-migration
git push origin main

# Tag release
git tag v2.0.0
git push origin v2.0.0

# Cleanup
rm migration-log.txt migration-plan.txt
git checkout .beads.backup-*  # Keep in git history
```

## Rollback Procedure
If migration fails or has issues:

```bash
# Restore backup
mv .beads .beads.failed-migration
mv .beads.backup-1234567890 .beads

# Regenerate JSONL
bd export

# Verify restoration
bd list
git diff .beads/beads.jsonl

# Cleanup
git checkout hash-id-migration
git reset --hard main
```

## Post-Migration Communication

### GitHub Issue/Discussion
```markdown
## Beads v2.0 Released: Hash-Based IDs

We've migrated beads to hash-based IDs! 🎉

**What changed:**
- Issues now use hash IDs (bd-af78e9a2) instead of sequential (bd-cb64c226.3)
- Human-friendly aliases (#42) for easy reference
- Zero collision risk in distributed workflows

**Action required:**
If you have a local clone, you need to migrate:

\`\`\`bash
git pull origin main
bd migrate --hash-ids
git push origin main
\`\`\`

See MIGRATION.md for details.

**Benefits:**
- ✅ No more ID collisions
- ✅ Work offline without coordination
- ✅ Simpler codebase (-2,100 LOC)

Questions? Reply here or see docs/HASH_IDS.md
```

## Success Criteria
- [ ] Migration completes without errors
- [ ] All validation checks pass
- [ ] PR merged to main
- [ ] v2.0.0 tagged and released
- [ ] Documentation updated
- [ ] Community notified
- [ ] No rollback needed within 1 week

## Files to Create
- migration-log.txt (transient)
- migration-plan.txt (transient)

## Timeline
Execute after all other bd-f8b764c9 tasks complete (estimated: ~8 weeks from start)


Proposal: Custom status states via config

Proposal to add 'custom status states' via `bd config`.
Users could define an optional issue status enum (e.g., awaiting_review, review_in_progress) in the config.
This would enable multi-step pipelines to process issues where each step correlates to a specific status.

Examples:
- awaiting_verification
- awaiting_docs
- awaiting_testing


bd init overwrites existing git hooks without detection or chaining

GH #254: bd init silently overwrites existing git hooks (like pre-commit framework) without detecting them, backing them up, or offering to chain. This breaks workflows and can result in committed code with failing tests.

Fix: Detect existing hooks, prompt user with options to chain/overwrite/skip.


GH#519: bd sync fails when sync.branch is currently checked-out branch


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

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

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

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

## Root Cause Analysis

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

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

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

## Solution Implemented

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

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

## File Path Handling

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


Git history fallback has incorrect logic for detecting deletions

## Problem

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

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

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

## Correct Logic

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

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

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

## Fix Options

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

Option 1 is simpler and correct since caller already filters.


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


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


Document sandbox workarounds for GH #353

Add documentation for sandbox troubleshooting and new flags.

**Tasks:**
1. Create or update TROUBLESHOOTING.md with sandbox section
2. Document new flags in CLI reference
3. Add comment to GH #353 with immediate workarounds

**Content needed:**
- Symptoms of daemon lock issues in sandboxed environments
- Usage guide for --sandbox, --force, and --allow-stale flags
- Step-by-step troubleshooting for Codex users
- Examples of each escape hatch

**Files to update:**
- docs/TROUBLESHOOTING.md (create if needed)
- docs/CLI_REFERENCE.md or README.md
- GitHub issue #353

**References:**
- docs/GH353_INVESTIGATION.md (lines 240-276)


Add mermaid output format for bd dep tree

Add visual dependency graph output using Mermaid format for better visualization of issue relationships.

Example usage:
  bd dep tree --format mermaid <issue-id>
  bd dep tree --format mermaid bd-42 > graph.md

This would output Mermaid syntax that can be rendered in GitHub, documentation sites, or Mermaid live editor.

Implementation notes:
- Add --format flag to dep tree command
- Support 'text' (default) and 'mermaid' formats
- Mermaid graph should show issue IDs, titles, and dependency types
- Consider using flowchart LR or graph TD syntax


Performance Testing Documentation

Create docs/performance-testing.md documenting the performance testing framework.

Sections:
1. Overview - What the framework does, goals
2. Running Benchmarks
   - make bench command
   - Running specific benchmarks
   - Interpreting output (ns/op, allocs/op)
3. Profiling and Analysis
   - Viewing CPU profiles with pprof
   - Reading flamegraphs
   - Memory profiling
   - Finding hotspots
4. User Diagnostics
   - bd doctor --perf usage
   - Sharing profiles with bug reports
   - Understanding the report output
5. Comparing Performance
   - Using benchstat for before/after comparisons
   - Detecting regressions
6. Tips for Optimization
   - Common patterns
   - When to profile vs benchmark

Style:
- Concise, practical examples
- Screenshots/examples of pprof output
- Clear command-line examples
- Focus on workflow, not theory


bd resolve-conflicts - Git merge conflict resolver

Automatically resolve JSONL merge conflicts.

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

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

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


Add mutation channel to internal/rpc/server.go

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


Test lowercase p0


bd init creates nested .beads directories

bd init sometimes creates .beads/.beads/ nested directories, which should never happen. This occurs fairly often and can cause confusion about which .beads directory is active. Need to add validation to detect if already inside a .beads directory and either error or use the parent .beads location.


Create Docker image for Agent Mail

Containerize Agent Mail server for easy deployment.

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

File: deployment/agent-mail/Dockerfile


Add monitoring and alerting

Observability for production Agent Mail server.

Acceptance Criteria:
- Health check endpoint (/health)
- Prometheus metrics export
- Grafana dashboard
- Alerts for server downtime
- Alerts for high error rate
- Log aggregation config

File: deployment/agent-mail/monitoring/


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.


Add tests for performFlush error handling in FlushManager

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

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

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

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

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

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


Baseline quality gate failure: lint

The lint quality gate is failing on the baseline (main branch).

This blocks the executor from claiming work until fixed.

Error: golangci-lint failed: exit status 1

Output:
```
cmd/bd/search.go:39:12: Error return value of `cmd.Help` is not checked (errcheck)
			cmd.Help()
			        ^
cmd/bd/clean.go:118:15: G304: Potential file inclusion via variable (gosec)
	file, err := os.Open(gitignorePath)
	             ^
cmd/bd/doctor/gitignore.go:98:12: G306: Expect WriteFile permissions to be 0600 or less (gosec)
	if err := os.WriteFile(gitignorePath, []byte(GitignoreTemplate), 0644); err != nil {
	          ^
cmd/bd/merge.go:121:16: G204: Subprocess launched with variable (gosec)
			gitRmCmd := exec.Command("git", "rm", "-f", "--quiet", fullPath)
			            ^
cmd/bd/doctor.go:167:20: `cancelled` is a misspelling of `canceled` (misspell)
		fmt.Println("Fix cancelled.")
		                 ^
cmd/bd/flush_manager.go:139:42: `cancelling` is a misspelling of `canceling` (misspell)
		// Send shutdown request FIRST (before cancelling context)
		                                       ^
cmd/bd/flush_manager.go:261:15: `cancelled` is a misspelling of `canceled` (misspell)
			// Context cancelled (shouldn't normally happen)
			           ^
cmd/bd/flush_manager.go:269:55: (*FlushManager).performFlush - result 0 (error) is always nil (unparam)
func (fm *FlushManager) performFlush(fullExport bool) error {
                                                      ^
8 issues:
* errcheck: 1
* gosec: 3
* misspell: 3
* unparam: 1

```


Add tests for internal/utils package

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


bd doctor should check for multiple DBs, multiple JSONLs, daemon health


Jira import/export integration

Add ability to import issues from Jira and export beads issues to Jira. See GitHub discussion #430 for user request.

**Background**: User @kaihendry configured jira.* settings but found bd sync doesn't sync with Jira - only with git. The config namespace exists but the integration doesn't.

**Existing infrastructure**:
- external_ref field for linking to external systems
- Import logic matches by external_ref first (priority over ID matching)
- Config namespace jira.* documented in CONFIG.md
- GitHub import example (examples/github-import/gh2jsonl.py) provides pattern

**Schema mapping challenges**:
- Jira workflows are highly customizable (status mapping)
- Jira has more field types (components, versions, custom fields)
- Two-way sync needs conflict resolution
- Jira Cloud vs Server/Data Center have different APIs

**Approach**: Phased implementation starting with import, then export, then bidirectional sync.


Add integration tests for multi-repo sync

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


Export deduplication breaks when JSONL and export_hashes table diverge

## Problem

The export deduplication feature (timestamp-only skipping) breaks when the JSONL file and export_hashes table get out of sync, causing exports to skip issues that aren't actually in the file.

## Symptoms

- `bd export` reports "Skipped 128 issue(s) with timestamp-only changes"
- JSONL file only has 38 lines but DB has 149 issues
- export_hashes table has 149 entries
- Auto-import doesn't trigger (hash matches despite missing data)
- Two repos on same commit show different issue counts

## Root Cause

shouldSkipExport() in autoflush.go compares current issue hash with stored export_hashes entry. If they match, it skips export assuming the issue is already in the JSONL.

This assumption fails when:
1. Git operations (pull, reset, checkout) change JSONL without clearing export_hashes
2. Manual JSONL edits or corruption
3. Import operations that modify DB but don't update export_hashes
4. Partial exports that update export_hashes but don't complete

## Impact

- **Critical data loss risk**: Issues appear to be tracked but aren't persisted to git
- Breaks multi-repo sync (root cause of today's debugging session)
- Auto-import fails to detect staleness (hash matches despite missing data)
- Silent data corruption (no error messages, just missing issues)

## Reproduction

1. Have DB with 149 issues, all in export_hashes table
2. Truncate JSONL to 38 lines (simulate git reset or corruption)
3. Run `bd export` - it skips 128 issues
4. JSONL still has only 38 lines but export thinks it succeeded

## Current Workaround

```bash
sqlite3 .beads/beads.db "DELETE FROM export_hashes"
bd export -o .beads/beads.jsonl
```

## Proposed Solutions

**Option 1: Verify JSONL integrity before skipping**
- Count lines in JSONL, compare with export_hashes count
- If mismatch, clear export_hashes and force full export
- Safe but adds I/O overhead

**Option 2: Hash-based JSONL validation**
- Store hash of entire JSONL file in metadata
- Before export, check if JSONL hash matches
- If mismatch, clear export_hashes
- More efficient, detects any JSONL corruption

**Option 3: Disable timestamp-only deduplication**
- Remove the feature entirely
- Always export all issues
- Simplest and safest, but creates larger git commits

**Option 4: Clear export_hashes on git operations**
- Add post-merge hook to clear export_hashes
- Clear on any import operation
- Defensive approach but may over-clear

## Recommended Fix

Combination of Options 2 + 4:
1. Store JSONL file hash in metadata after export
2. Check hash before export, clear export_hashes if mismatch
3. Clear export_hashes on import operations
4. Add `bd validate` check for JSONL/export_hashes sync

## Files Involved

- cmd/bd/autoflush.go (shouldSkipExport)
- cmd/bd/export.go (export with deduplication)
- internal/storage/sqlite/metadata.go (export_hashes table)

## Recovery Session (2025-10-29 21:30)

### What Happened
- Created 14 new hash ID issues (bd-f8b764c9 through bd-f8b764c9.1)
- bd sync appeared to succeed
- Canonical repo (~/src/beads): 162 issues in DB + JSONL ✓
- Secondary repo (fred/beads): Only 145 issues vs 162 in canonical ✗
- Both repos on same git commit but different issue counts!

### Bug Manifestation During Recovery

1. **Initial state**: fred/beads had 145 issues, 145 lines in JSONL, 145 export_hashes entries

2. **After git reset --hard origin/main**:
   - JSONL: 162 lines (from git)
   - DB: 150 issues (auto-import partially worked)
   - Auto-import failed with UNIQUE constraint error

3. **After manual import --resolve-collisions**:
   - DB: 160 issues
   - JSONL: Still 162 lines
   - export_hashes: 159 entries

4. **After bd export**:
   - **JSONL reduced to 17 lines!** ← The bug in action
   - export_hashes: 159 entries (skipped exporting 142 issues)
   - Silent data loss - no error message

5. **After clearing export_hashes and re-export**:
   - JSONL: 159 lines (missing 3 issues still)
   - DB: 159 issues
   - Still diverged from canonical

### The Bug Loop
Once export_hashes and JSONL diverge:
- Export skips issues already in export_hashes
- But those issues aren't actually in JSONL
- This creates corrupt JSONL with missing issues
- Auto-import can't detect the problem (file hash matches what was exported)
- Data is lost with no error messages

### Recovery Solution
Couldn't break the loop with export alone. Had to:
1. Copy .beads/beads.db from canonical repo
2. Clear export_hashes
3. Full re-export
4. Finally converged to 162 issues

### Key Learnings

1. **The bug is worse than we thought**: It can create corrupt exports (17 lines instead of 162!)

2. **Auto-import can't save you**: Once export is corrupt, auto-import just imports the corrupt data

3. **Silent failure**: No warnings, no errors, just missing issues

4. **Git operations trigger it**: git reset, git pull, etc. change JSONL without clearing export_hashes

5. **Import operations populate export_hashes**: Even manual imports update export_hashes, setting up future export failures

### Immediate Action Required

**DISABLE EXPORT DEDUPLICATION NOW**

This feature is fundamentally broken and causes data loss. Should be disabled until properly fixed.

Quick fix options:
- Set environment variable to disable feature
- Comment out shouldSkipExport check
- Always clear export_hashes before export
- Add validation that DB count == JSONL line count before allowing export

### Long-term Fix

Need Option 2 + 4 from proposed solutions:
1. Store JSONL file hash after every successful export
2. Before export, verify JSONL hash matches expected
3. If mismatch, log WARNING and clear export_hashes
4. Clear export_hashes on every import operation
5. Add git post-merge hook to clear export_hashes
6. Add `bd validate` command to detect divergence


Extract daemon logger functions to daemon_logger.go


Doctor should exclude merge artifacts from 'multiple JSONL' warning

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


Phase 3: Enhance daemon robustness for GH #353

Improve daemon health checks and metadata refresh to prevent staleness issues.

**Tasks:**
1. Enhance daemon health checks to detect unreachable daemons
2. Add daemon metadata refresh (check disk every 5s)
3. Comprehensive testing in sandbox environments

**Implementation:**
- cmd/bd/main.go: Better health check error handling (lines 300-367)
- cmd/bd/daemon_event_loop.go: Periodic metadata refresh
- cmd/bd/daemon_unix.go: Permission-aware process checks

**References:**
- docs/GH353_INVESTIGATION.md (Solutions 4 & 5, lines 161-209)
- Depends on: Phase 2 (bd-u3t)

**Acceptance Criteria:**
- Daemon detects when it's unreachable and auto-switches to direct mode
- Daemon picks up external import operations without restart
- All edge cases handled gracefully


Replace Windows CI full test suite with smoke tests

## Problem

Windows CI tests consistently timeout at 30 minutes despite multiple optimization attempts:
- Split into parallel jobs (cmd vs internal) - still times out
- RAM disk for temp/cache - marginal improvement (~50%)
- RAM disk for workspace - breaks due to t.Chdir() incompatibility with ImDisk

Root cause: Windows I/O is fundamentally 20x slower than Linux:
- NTFS slow for small file operations
- Windows Defender scans every file access
- Git on Windows is 5-10x slower
- Process spawning is expensive

Linux tests complete in ~1.5 min with race detector. Windows takes 30+ min without.

## Current State

- Windows jobs have continue-on-error: true (don't block PRs)
- Tests timeout before completing, providing zero signal
- RAM disk experiments added complexity without solving the problem

## Solution: Smoke Tests Only

Replace full test suite with minimal smoke tests that verify Windows compatibility in < 2 minutes.

### What to Test
1. Binary builds - go build succeeds (already separate step)
2. Binary executes - bd version works
3. Init works - bd init creates valid database
4. Basic CRUD - create, list, show, update, close
5. Path handling - Windows backslash paths work

### Proposed CI Change

Replace test-windows-cmd and test-windows-internal with single smoke test job:
- Build bd.exe
- Run: bd version
- Run: bd init --quiet --prefix smoke
- Run: bd create --title "Windows smoke test" --type task
- Run: bd list
- Run: bd show smoke-001
- Run: bd update smoke-001 --status in_progress
- Run: bd close smoke-001

### Benefits
- Completes in < 2 minutes vs 30+ minute timeouts
- Provides actual pass/fail signal
- Verifies Windows binary works
- Removes RAM disk complexity
- Single job instead of two parallel jobs

### Files to Change
- .github/workflows/ci.yml - Replace test-windows-cmd and test-windows-internal jobs

### Migration Steps
1. Remove test-windows-cmd job
2. Remove test-windows-internal job
3. Add single test-windows job with smoke tests
4. Remove continue-on-error (tests should now pass reliably)
5. Clean up RAM disk remnants from previous attempts

### Context
- Parent issue: bd-5we (Use RAM disk for Windows CI tests)
- RAM disk approach proved insufficient
- This is the pragmatic solution after extensive experimentation


Extract daemon config functions to daemon_config.go


Test Epic

## Overview

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

## Success Criteria

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

## Background

[Provide context and motivation]

## Scope

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

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


MCP server: "No workspace set" and "chunk longer than limit" errors

Two related errors reported in beads-mcp v0.21:

**Error 1: "No workspace set" after successful set_context**
```
✓ Set beads context
✗ list
  Error calling tool 'list': No workspace set. Either provide workspace_root
  parameter or call set_context() first.
```

Hypothesis: Environment variable persistence issue between MCP tool calls, or ContextVar not being set correctly by @with_workspace decorator.

**Error 2: "Separator is found, but chunk is longer than limit"**
```
✗ list
  Error calling tool 'list': Separator is found, but chunk is longer than limit
```

Hypothesis: MCP protocol output size limit exceeded. Large issue databases may produce JSON output that exceeds MCP stdio buffer limits.

Platform: Fedora 43, using copilot-cli with Sonnet 4.5

Workaround: CLI works fine (`bd list --status open --json`)

## Fixes Implemented

**Issue 1: "No workspace set" after successful set_context** ✅ FIXED

Root cause: os.environ doesn't persist across MCP tool calls. When set_context() set BEADS_WORKING_DIR in os.environ, that change was lost on the next tool call.

Solution:
- Added module-level _workspace_context dict for persistent storage (server.py:51)
- Modified set_context() to store in both persistent dict and os.environ (server.py:265-287)
- Modified with_workspace() decorator to check persistent context first (server.py:129-133)
- Updated where_am_i() to check persistent context (server.py:302-330)

**Issue 2: "chunk longer than limit"** ✅ FIXED

Root cause: MCP stdio protocol has buffer limits. Large issue lists with full dependencies/dependents exceed this.

Solution:
- Reduced default list limit from 50 to 20 (server.py:356, models.py:122)
- Reduced max list limit from 1000 to 100 (models.py:122)
- Strip dependencies/dependents from list() and ready() responses (server.py:343-350, 368-373)
- Full dependency details still available via show() command

## Testing

✅ Python syntax validated with py_compile
✅ Changes are backward compatible
✅ Persistent context falls back to os.environ for compatibility

Users should now be able to call set_context() once and have it persist across all subsequent tool calls. Large databases will no longer cause buffer overflow errors.


Merge timestamp tie-breaker should prefer local (left)

In mergeFieldByUpdatedAt, when timestamps are exactly equal, right wins. For consistency with IssueType (where local/left wins), equal timestamps should prefer left. Minor inconsistency.


Improve clean vs cleanup command naming/documentation

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

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

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

2. Keep names but improve help text and documentation

3. Add prominent warnings in help output

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

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


Implicit deletion cases do not check for tombstones in remaining side

In merge3WayWithTTL(), the implicit deletion cases (lines 426-435) handle when an issue exists in base and one side but not the other. The current logic does not check if the remaining side is a tombstone:

Case: inBase && inLeft && !inRight (deleted implicitly in right)
- If leftIssue is a tombstone, we should keep it (tombstone propagation)
- Currently: continue (drops the tombstone)

Case: inBase && !inLeft && inRight (deleted implicitly in left)
- If rightIssue is a tombstone, we should keep it
- Currently: continue (drops the tombstone)

This could cause tombstones to be lost during merge if one side has a tombstone and the other side implicitly deleted. The tombstone should win since its an explicit deletion record.

Files: internal/merge/merge.go:426-435


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

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

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

Current coverage: 26.2%
Target coverage: 50%


Test numeric 1


Pre-sync integrity check (bd sync --check)

Add bd sync --check flag that detects problematic states before attempting sync: forced pushes on sync branch (via git reflog), prefix mismatches in JSONL, orphaned children with missing parents. Output combined diagnostic without modifying state.


Add internal/ai package for AI-assisted repairs

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

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

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

See repair_commands.md lines 357-425 for design.


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


Refactor duplicate JSONL-from-git parsing code

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

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

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

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


Test parent issue


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

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

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

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

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

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

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

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

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

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

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


Replace getStorageForRequest with Direct Access

Replace all getStorageForRequest(req) calls with s.storage


Use in-memory filesystem for test git operations

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

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

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


Add comprehensive tests for config.yaml issue-prefix migration

The GH #209 config.yaml migration lacks test coverage:

Missing tests:
- config.SetIssuePrefix() edge cases (empty file, comments, malformed YAML)
- config.GetIssuePrefix() with various config states
- MigrateConfigToYAML() automatic migration logic
- bd init writing to config.yaml instead of DB
- bd migrate DB→config.yaml migration path

Test scenarios needed:
1. SetIssuePrefix with empty config.yaml
2. SetIssuePrefix with existing config.yaml (preserves other settings)
3. SetIssuePrefix with commented issue-prefix line
4. SetIssuePrefix atomic write (temp file cleanup)
5. GetIssuePrefix fallback behavior
6. MigrateConfigToYAML when config.yaml missing prefix but DB has it
7. MigrateConfigToYAML when both missing (detect from issues)
8. MigrateConfigToYAML when config.yaml already has prefix (no-op)
9. Integration test: fresh bd init writes to config.yaml only
10. Integration test: upgrade from v0.21 DB migrates to config.yaml

Priority 1 because this is a user-facing migration affecting all users upgrading to v0.22.


Add circular dependency detection to bd doctor

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


Run final validation and cleanup checks

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

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

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

Impact: Confirms all cleanup objectives achieved successfully

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

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


purgeDeletedIssues still writes to deletions.jsonl during git-history-backfill

In purgeDeletedIssues (lines 929-939), when a deletion is recovered from git history, we:
1. Append to deletions.jsonl (backfill)
2. Create a tombstone in DB

With the tombstone approach, writing to deletions.jsonl is redundant since the tombstone will be exported to JSONL. Consider removing the deletions.jsonl backfill once we're confident in the tombstone approach.

This is related to the Phase 2 migration (bd-vw8) - we should stop writing to deletions.jsonl entirely.


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

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

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

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

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

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

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

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

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


Prevent multiple daemons from running on the same repo

Multiple bd daemons running on the same repo clone causes race conditions and data corruption risks.

**Problem:**
- Nothing prevents spawning multiple daemons for the same repository
- Multiple daemons watching the same files can conflict during sync operations
- Observed: 4 daemons running simultaneously caused sync race condition

**Solution:**
Implement daemon singleton enforcement per repo:
1. Use a lock file (e.g., .beads/.daemon.lock) with PID
2. On daemon start, check if lock exists and process is alive
3. If stale lock (dead PID), clean up and acquire lock
4. If active daemon exists, either:
   - Exit with message 'daemon already running (PID xxx)'
   - Or offer --replace flag to kill existing and take over
5. Release lock on graceful shutdown

**Edge cases to handle:**
- Daemon crashes without releasing lock (stale PID detection)
- Multiple repos in same directory tree (each repo gets own lock)
- Race between two daemons starting simultaneously (atomic lock acquisition)


Week 1 task


Add MCP server functions for repair commands

Expose new repair commands via MCP server for agent access:

Functions to add:
- beads_repair_deps()
- beads_detect_pollution()
- beads_validate()
- beads_resolve_conflicts() (when implemented)

Update integrations/beads-mcp/src/beads_mcp/server.py

See repair_commands.md lines 803-884 for design.

Implemented all three MCP server functions:

1. **repair_deps(fix=False)** - Find/fix orphaned dependencies
2. **detect_pollution(clean=False)** - Detect/clean test issues
3. **validate(checks=None, fix_all=False)** - Run comprehensive health checks

Changes:
- Added abstract methods to BdClientBase
- Implemented in BdCliClient (CLI execution)
- Added NotImplementedError stubs in BdDaemonClient (falls back to CLI)
- Created wrapper functions in tools.py
- Registered @mcp.tool decorators in server.py

All commands tested and working with --no-daemon flag.


False positive detection by Kaspersky Antivirus (Trojan)

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


Test issue 2


Verify MCP Server Compatibility

Ensure MCP server works with cache-free daemon


Create shared insert/event/dirty helpers

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


Add test for legacy deletions.jsonl to tombstone conversion

The importer now converts legacy deletions.jsonl entries to tombstones (bd-dve), but there's no dedicated test that:
1. Creates a deletions.jsonl with entries
2. Imports issues (some in deletions, some not)
3. Verifies the converted tombstones have correct fields from the deletion record

The existing TestImportIssues_TombstoneNotFilteredByDeletionsManifest tests that tombstones bypass the filter, but doesn't verify the conversion of legacy deletions to tombstones.

Add a test: TestImportIssues_LegacyDeletionsConvertedToTombstones


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

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


Test child direct


Auto-configure git merge driver during `bd init`

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

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

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


Implement alias conflict resolution

Handle alias conflicts when multiple clones assign same alias to different issues.

## Scenario
```
Clone A: Creates bd-a1b2c3d4, assigns alias #42
Clone B: Creates bd-e5f6a7b8, assigns alias #42
After sync: Conflict! Which issue gets #42?
```

## Resolution Strategy: Content-Hash Ordering
Deterministic, same result on all clones:
```go
func ResolveAliasConflicts(conflicts []AliasConflict) []AliasRemapping {
    for _, conflict := range conflicts {
        // Sort by hash ID (lexicographic)
        sort.Strings(conflict.IssueIDs)

        // Winner: lowest hash ID (arbitrary but deterministic)
        winner := conflict.IssueIDs[0]

        // Losers: reassign to next available aliases
        for _, loser := range conflict.IssueIDs[1:] {
            newAlias := getNextAlias()
            remappings = append(remappings, AliasRemapping{
                IssueID:  loser,
                OldAlias: conflict.Alias,
                NewAlias: newAlias,
            })
        }
    }
    return remappings
}
```

## Detection During Import
File: internal/importer/importer.go
```go
func handleAliasConflicts(imported []Issue, existing []Issue) error {
    // Build alias map from imported issues
    aliasMap := make(map[int][]string)  // alias → issue IDs

    for _, issue := range imported {
        aliasMap[issue.Alias] = append(aliasMap[issue.Alias], issue.ID)
    }

    // Check against existing aliases
    for alias, importedIDs := range aliasMap {
        existingID := storage.GetIssueIDByAlias(alias)
        if existingID != "" {
            // Conflict! Resolve it
            allIDs := append(importedIDs, existingID)
            conflicts = append(conflicts, AliasConflict{
                Alias:    alias,
                IssueIDs: allIDs,
            })
        }
    }

    // Resolve and apply
    remappings := ResolveAliasConflicts(conflicts)
    applyAliasRemappings(remappings)
}
```

## Alternative Strategies (For Future Consideration)

### Priority-Based
```go
// Higher priority keeps alias
if issueA.Priority < issueB.Priority {
    winner = issueA
}
```

### Timestamp-Based (Last-Write-Wins)
```go
// Newer issue keeps alias
if issueA.UpdatedAt.After(issueB.UpdatedAt) {
    winner = issueA
}
```

### Manual Resolution
```bash
bd resolve-aliases --manual
# Interactive prompt for each conflict
```

## User Notification
```bash
$ bd sync
✓ Synced 5 issues
⚠ Alias conflicts resolved:
  - Issue bd-e5f6a7b8: alias changed from #42 to #100
  - Issue bd-9a8b7c6d: alias changed from #15 to #101
```

## Files to Create/Modify
- internal/storage/sqlite/alias_conflicts.go (new)
- internal/importer/importer.go (detect conflicts)
- cmd/bd/sync.go (show conflict notifications)

## Testing
- Test two clones assign same alias to different issues
- Test conflict resolution is deterministic (same on all clones)
- Test loser gets new alias
- Test winner keeps original alias
- Test multiple conflicts resolved in one import


Test SQLite WASM compatibility

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

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

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


GH#540: Add silent quick-capture mode (bd q)

Add bd q alias for quick capture that outputs only issue ID. Useful for piping/scripting. See GitHub issue #540.


Split monolithic sqlite.go into focused files

internal/storage/sqlite/sqlite.go is 1050 lines containing initialization, 20+ CRUD methods, query building, and schema management.

Split into:
- store.go: Store struct & initialization (150 lines)
- bead_queries.go: Bead CRUD (300 lines)
- work_queries.go: Work queries (200 lines)
- stats_queries.go: Statistics (150 lines)
- schema.go: Schema & migrations (150 lines)
- helpers.go: Common utilities (100 lines)

Impact: Impossible to understand at a glance; hard to find specific functionality; high cognitive load

Effort: 6-8 hours


Fix checkExistingBeadsData to be worktree-aware

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


Add integration tests for CompactedResult in MCP server

The CompactedResult model is used for list operations when results exceed COMPACTION_THRESHOLD (20 items), but there are no integration tests verifying the actual MCP server behavior with large result sets.

## What's Missing
- Integration test that mocks a large result set (>20 issues) and verifies CompactedResult is returned
- Verification that preview_count matches PREVIEW_COUNT (5)
- Verification that total_count reflects actual result size
- Test that compaction threshold is configurable (currently hardcoded COMPACTION_THRESHOLD=20)

## Why This Matters
The compaction logic is critical for preventing context overflow with large lists. Without integration tests, we can't verify it works correctly with the actual MCP protocol.

## Suggested Implementation
Create test_mcp_compaction.py with:
1. test_list_with_large_result_set_returns_compacted_result()
2. test_ready_work_respects_compaction_threshold()
3. test_compacted_result_structure_is_valid()


Update LINTING.md with current baseline

After cleanup, document the remaining acceptable baseline in LINTING.md so we can track regression.


Handle unchecked errors (errcheck - 683 issues)

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

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


Extract batch operations to batch_ops.go

Move validateBatchIssues, generateBatchIDs, bulkInsertIssues, bulkRecordEvents, bulkMarkDirty, CreateIssues to batch_ops.go


Invert control for compact: provide tools for agent-driven compaction

Currently compact requires Anthropic API key because bd calls the AI directly. This is backwards - we should provide tools (like all other bd commands) that let an AI agent perform the compaction. The agent decides what to keep/merge, not bd. Related to GH #243 complaint about API key requirement.


Database import silently fails when daemon version != CLI version


Latency test 1


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


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)


Add performance benchmarks for multi-repo hydration

The contributor-workflow-analysis.md asserts sub-second queries (line 702) and describes smart caching via file mtime tracking (Decision #4, lines 584-618), but doesn't provide concrete performance benchmarks.

VC's requirement (from VC feedback section):
- Executor polls GetReadyWork() every 5-10 seconds
- Queries must be sub-second (ideally <100ms)
- Smart caching must avoid re-parsing JSONLs on every query

Suggested performance targets to validate:
- File stat overhead: <1ms per repo
- Hydration (when needed): <500ms for typical JSONL (<25k)
- Query (from cache): <10ms
- Total GetReadyWork(): <100ms (VC's requirement)

Also test at scale:
- N=1 repo (baseline)
- N=3 repos (typical)
- N=10 repos (edge case)

These benchmarks are critical for library consumers like VC that run automated polling loops.


Add docs/architecture/event_driven.md

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


Daemon crashes silently on RPC query after startup

The daemon fails to handle 'show' RPC commands when:
1) JSONL is newer than database (needs import)
2) git pull fails due to uncommitted changes

Symptoms:
- Daemon appears to run (ps shows process)
- 'bd list' and other commands work fine
- 'bd show <id>' returns "failed to read response: EOF"
- No panic or error logged in daemon.log

Root cause likely: auto-import deadlock or state corruption when import is blocked by git conflicts.

Workaround:
- Restart daemon after syncing git state (commit/push changes)
- OR use --no-daemon flag for all commands

The panic recovery added in server_lifecycle_conn.go:183 didn't catch any panics, confirming this isn't a panic-based crash.

Root cause found and fixed: Two bugs - (1) nil pointer check missing in handleShow causing panic, (2) double JSON encoding in show.go ID resolution. Both fixed. bd show now works with daemon.


Sync branch integrity guards

Track sync branch parent commit. If sync branch was force-pushed, warn user and require confirmation before proceeding. Add option to reset to remote if user accepts rebase. Prevents silent corruption from forced pushes.


Improve timestamp comparison readability in import

The timestamp comparison logic uses double-negative which can be confusing:

Current code:
if !incoming.UpdatedAt.After(existing.UpdatedAt) {
    // skip update
}

More readable:
if incoming.UpdatedAt.After(existing.UpdatedAt) {
    // perform update
} else {
    // skip (local is newer)
}

This is a minor refactor for code clarity.

Related: bd-1022
Files: internal/importer/importer.go:411, 488


Migration and onboarding for multi-repo

Create migration tools, wizards, and documentation to help users adopt multi-repo workflow, with special focus on OSS contributor onboarding and team adoption scenarios.


Auto-detect scenarios and prompt users

Detect when user is in fork/contributor scenario and prompt with helpful suggestions. Check: git remote relationships, existing .beads config, repo ownership. Suggest appropriate wizard.


Document CLI + hooks as recommended approach over MCP

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


getLocalSyncBranch YAML parsing is fragile

The getLocalSyncBranch() function in autoimport.go uses a simple line-by-line YAML parser that could fail on:
- Indented sync-branch: (nested in a section)
- Comments containing sync-branch:
- Multi-line values
- Values with special characters

Current implementation at autoimport.go:161-172 does:
```go
for _, line := range strings.Split(string(data), "\n") {
    line = strings.TrimSpace(line)
    if strings.HasPrefix(line, "sync-branch:") {
        value := strings.TrimPrefix(line, "sync-branch:")
        ...
    }
}
```

The reason we can't use viper's cached config is that it's initialized at process startup from the main repo's CWD, not from the current directory at runtime. This causes issues in tests where t.Chdir() changes the directory.

Options:
1. Accept the limitation (sync-branch must be top-level, unquoted)
2. Use a proper YAML library (gopkg.in/yaml.v3) to parse just this value
3. Refactor config package to support re-initialization per directory


sync tries to create worktree in .git file

example: bd sync --no-daemon
→ Exporting pending changes to JSONL...
→ Committing changes to sync branch 'worktree-db-fail'...
Error committing to sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/worktree-db-fail/.git: not a directory


Add fsnotify dependency to go.mod


Git history fallback doesn't escape regex special chars in IDs

## Problem

In `batchCheckGitHistory`, IDs are directly interpolated into a regex pattern:

```go
patterns = append(patterns, fmt.Sprintf(\`"id":"%s"\`, id))
searchPattern := strings.Join(patterns, "|")
cmd := exec.Command("git", "log", "--all", "-G", searchPattern, ...)
```

If an ID contains regex special characters (e.g., `bd-foo.bar` or `bd-test+1`), the pattern will be malformed or match unintended strings.

## Location
`internal/importer/importer.go:923-926`

## Impact
- False positives: IDs with `.` could match any character
- Regex errors: IDs with `[` or `(` could cause git to fail
- Security: potential for regex injection (low risk since IDs are validated)

## Fix
Escape regex special characters:

```go
import "regexp"

escapedID := regexp.QuoteMeta(id)
patterns = append(patterns, fmt.Sprintf(\`"id":"%s"\`, escapedID))
```


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.


Test issue 2


bd doctor --fix hydrates issues that remain in deletions manifest, causing perpetual skip warnings

When bd doctor --fix hydrates issues from git history, it doesn't remove them from the deletions manifest. This causes a conflict where:
1. Issue exists in database as 'open'
2. Issue also exists in deletions manifest
3. Every sync reports 'Skipping bd-xxx (in deletions manifest)'
4. Issue is 'Protected from incorrect sanitization'

**Reproduction:**
1. Run bd doctor --fix (which hydrates issues from git history)
2. Run bd sync
3. Observe conflicting messages about protected issues being skipped

**Affected issues in this session:**
bd-eyto, bd-6rl, bd-y2v, bd-abjw, bd-a0cp, bd-mql4, bd-nl2

**Expected behavior:**
When hydrating an issue, also remove it from the deletions manifest to prevent conflict.

**Workaround:** Manually remove conflicting IDs from deletions.jsonl


Improve Beads test coverage from 46% to 80%


Test worktree sync branch commit


sync branch not match config

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

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


Auto-migrate database on CLI version bump

When CLI is upgraded (e.g., 0.24.0 → 0.24.1), database version becomes stale. Add auto-migration in PersistentPreRun or daemon startup. Check dbVersion != CLIVersion and run bd migrate automatically. Fixes recurring UX issue where bd doctor shows version mismatch after every CLI upgrade.


Add warning when staleness check errors

## Problem

When ensureDatabaseFresh() calls CheckStaleness() and it errors (corrupted metadata, permission issues, etc.), we silently proceed with potentially stale data.

**Location:** cmd/bd/staleness.go:27-32

**Scenarios:**
- Corrupted metadata table
- Database locked by another process
- Permission issues reading JSONL file
- Invalid last_import_time format in DB

## Current Code

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    // If we can't determine staleness, allow operation to proceed
    // (better to show potentially stale data than block user)
    return nil
}
```

## Fix

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    fmt.Fprintf(os.Stderr, "Warning: Could not verify database freshness: %v\n", err)
    fmt.Fprintf(os.Stderr, "Proceeding anyway. Data may be stale.\n\n")
    return nil
}
```

## Impact
Medium - users should know when staleness check fails

## Effort
Easy - 5 minutes


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


YABB: Spurious issue updates during normal operations

Issue bd-627d was updated during config refactoring session without any actual changes to it. Only timestamps and content_hash changed.

Observed: Running various bd commands (list, create, etc.) caused bd-627d updated_at to change from 14:14 to 14:31.

Expected: Issues should only be updated when explicitly modified.

This causes:
- Dirty JSONL after every session
- False conflicts in git
- Confusing git history

Likely culprit: Daemon auto-import/export cycle or database migration touching all issues.

Investigated thoroughly - unable to reproduce. The import logic has IssueDataChanged() checks before calling UpdateIssue (importer/importer.go:458). All tests pass. May have been fixed by recent refactorings. Closing as cannot reproduce - please reopen with specific repro steps if it occurs again.


Add test coverage for internal/git package


Refactor create_test.go to use shared DB setup

Convert TestCreate_* functions to use test suites with shared database setup.

Example transformation:
- Before: 10 separate tests, each with newTestStore()
- After: 1 TestCreate() with 10 t.Run() subtests sharing one DB

Estimated speedup: 10x faster (1 DB setup instead of 10)


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


Remove Storage Cache from Server Struct

Eliminate cache fields and use s.storage directly


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)


Add schema compatibility probe to prevent silent migration failures

Issue #262 revealed a serious bug: migrations may fail silently, causing UNIQUE constraint errors later.

Root cause:
- sqlite.New() runs migrations once on open
- checkVersionMismatch() prints 'database will be upgraded automatically' but only updates metadata
- If migrations fail or daemon runs older version, queries expecting new columns fail with 'no such column'
- Import logic misinterprets this as 'not found' and tries INSERT on existing ID
- Result: UNIQUE constraint failed: issues.id

Fix strategy (minimal):
1. Add schema probe in sqlite.New() after RunMigrations
   - SELECT all expected columns from all tables with LIMIT 0
   - If fails, retry RunMigrations and probe again
   - If still fails, return fatal error with clear message
2. Fix checkVersionMismatch to not claim 'will upgrade' unless probe passes
3. Only update bd_version after successful migration probe
4. Add schema verification before import operations
5. Map 'no such column' errors to clear actionable message

Related: #262


Implement postinstall script for binary download

Create npm/scripts/postinstall.js that downloads platform-specific binaries:

## Platform detection
- Detect os.platform() and os.arch()
- Map to GitHub release asset names:
  - linux-amd64 → bd-linux-amd64
  - linux-arm64 → bd-linux-arm64
  - darwin-amd64 → bd-darwin-amd64
  - darwin-arm64 → bd-darwin-arm64
  - win32-x64 → bd-windows-amd64.exe

## Download logic
- Fetch from GitHub releases: https://github.com/steveyegge/beads/releases/latest/download/${asset}
- Save to npm/bin/bd (or bd.exe on Windows)
- Set executable permissions (chmod +x)
- Handle errors gracefully with helpful messages

## Error handling
- Check for unsupported platforms
- Retry on network failures
- Provide manual download instructions if automated fails
- Skip download if binary already exists (for local development)


Add test files for internal/storage

The internal/storage package has no test files at all. This package provides the storage interface abstraction.

Current coverage: N/A (no test files)
Target: Add basic interface tests


Debug test


Implement core reset package (internal/reset)

Create the core reset logic in internal/reset/ package.

## Responsibilities
- ResetOptions struct with all flag options
- CountImpact() - count issues/tombstones that will be deleted
- ValidateState() - check .beads/ exists, check git dirty state
- ExecuteReset() - main reset logic (without CLI concerns)
- Integrate with daemon killall

## Interface Design
```go
type ResetOptions struct {
    Hard      bool   // Include git operations (git rm, commit)
    Backup    bool   // Create backup before reset
    DryRun    bool   // Preview only, don't execute
    SkipInit  bool   // Don't re-initialize after reset
}

type ResetResult struct {
    IssuesDeleted     int
    TombstonesDeleted int
    BackupPath        string  // if backup was created
    DaemonsKilled     int
}

type ImpactSummary struct {
    IssueCount      int
    OpenCount       int
    ClosedCount     int
    TombstoneCount  int
    HasUncommitted  bool  // git dirty state
}

func Reset(opts ResetOptions) (*ResetResult, error)
func CountImpact() (*ImpactSummary, error)
func ValidateState() error
```

## IMPORTANT: CLI vs Core Separation
- `Force` (skip confirmation) is NOT in ResetOptions - that's a CLI concern
- Core always executes when called; CLI decides whether to prompt first
- Keep CLI-agnostic: no prompts, no colored output, no user interaction
- Return errors for CLI to handle with user-friendly messages
- Unit testable in isolation

## Dependencies
- Uses daemon.KillAllDaemons() from internal/daemon/
- Calls bd init logic after reset (unless SkipInit)


Ready issue


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

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

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

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

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


Updated title


Test parent issue


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)


Proposal workflow (propose/withdraw/accept)

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


Add 'town report' command for aggregated swarm status

## Problem
Getting a full swarm status requires running 6+ commands:
- `town list <rig>` for each rig
- `town mail inbox` as Boss
- `bd list --status=open/in_progress` per rig

This is slow and error-prone for both humans and agents.

## Proposed Solution
Add `town report [RIG]` command that aggregates:
- All rigs with polecat states (running/stopped, awake/asleep)
- Boss inbox summary (unread count, recent senders)
- Aggregate issue counts per rig (open/in_progress/blocked)

Example output:
```
=== beads ===
Polecats: 5 (5 running, 0 stopped)
Issues: 20 open, 0 in_progress, 0 blocked

=== gastown ===
Polecats: 6 (4 running, 2 stopped)
Issues: 0 open, 0 in_progress, 0 blocked

=== Boss Mail ===
Unread: 10 | Total: 22
Recent: rictus (21:19), scrotus (21:14), immortanjoe (21:14)
```

## Acceptance Criteria
- [ ] `town report` shows all rigs
- [ ] `town report <rig>` shows single rig detail
- [ ] Output is concise and scannable
- [ ] Completes in <2 seconds


Auto-detect issue prefix from existing JSONL in 'bd init'

When running `bd init` in a fresh clone with existing JSONL, it should auto-detect the issue prefix from the JSONL file instead of requiring `--prefix`.

Currently you must specify `--prefix ef` manually. But the JSONL file already contains issues like `ef-1it`, `ef-1jp` etc., so the prefix is known.

**Ideal UX**:
```
$ bd init
Detected issue prefix 'ef' from existing JSONL (38 issues).
✓ Database initialized...
```

This would make fresh clone hydration a single command: `bd init` with no flags.


Update documentation for bd prime and Claude integration

Update AGENTS.md, README.md, and QUICKSTART.md to document the new `bd prime` command, `bd setup claude` command, and tip system.


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


Install and test MCP Agent Mail locally

Install MCP Agent Mail on a single development machine and verify basic functionality.

Acceptance Criteria:
- Server installed via one-line installer
- Server running on port 8765
- Can register a project via HTTP
- Can register an agent identity
- Web UI accessible at /mail

Tested local installation. Server runs on port 8765, web UI works. MCP API tool execution has errors - needs debugging. See /tmp/bd-muls-report.md for details.


Document BEADS_DIR pattern for multi-agent workspaces (Gas Town)

Gas Town and similar multi-agent systems need to configure separate beads databases per workspace/rig, distinct from any project-level beads.

## Use Case

In Gas Town:
- Each 'rig' (managed project) has multiple agents (polecats, refinery, witness)
- All agents in a rig should share a single beads database at the rig level
- This should be separate from any .beads/ the project itself uses
- The BEADS_DIR env var enables this

## Documentation Needed

1. Add a section to docs explaining BEADS_DIR for multi-agent setups
2. Example: setting BEADS_DIR in agent startup scripts/hooks
3. Clarify interaction with project-level .beads/ (BEADS_DIR takes precedence)

## Current Support

Already implemented in internal/beads/beads.go:FindDatabasePath():
- BEADS_DIR env var is checked first (preferred)
- BEADS_DB env var still supported (deprecated)
- Falls back to .beads/ search in tree

Just needs documentation for the multi-agent workspace pattern.


GH#403: bd doctor --fix circular error message

bd doctor --fix suggests running bd doctor --fix for deletions manifest issue. Fix to provide actual resolution. See GitHub issue #403.


Improve CheckStaleness error handling

## Problem

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

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

## Edge Cases That Return False

1. **Invalid last_import_time format** (line 259-262)
   - Corrupted metadata returns 'not stale'
   - Could show stale data

2. **No JSONL file found** (line 267-277)
   - If glob fails, falls back to 'issues.jsonl'
   - If that's empty, returns 'not stale'

3. **JSONL stat fails** (line 279-282)
   - Permission denied, file missing
   - Returns 'not stale' even though can't verify

## Current Code

```go
lastImportTime, err := time.Parse(time.RFC3339, lastImportStr)
if err \!= nil {
    return false, nil  // ← Should return error
}

// ...

if jsonlPath == "" {
    return false, nil  // ← Should return error
}

stat, err := os.Stat(jsonlPath)
if err \!= nil {
    return false, nil  // ← Should return error
}
```

## Fix

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

// ...

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

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

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

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

## Dependencies
Requires: bd-n4td (warning on errors)


bd status "Recent Activity" is misleading - should use git history

## Problem

`bd status` shows "Recent Activity (last 7 days)" but the numbers are wrong. It only looks at database timestamps, not git history. Says "141 issues closed in last 7 days" when thousands have actually come and go.

## Issues

1. Only queries database timestamps, not git history
2. 7 days is too long a window
3. Numbers don't reflect actual activity in JSONL git history

## Proposed Fix

Either:
- Query git history of `.beads/beads.jsonl` to get actual activity (last 24-48 hours)
- Remove "Recent Activity" section entirely if not useful
- Make time window configurable and default to 24h

## Example Output (Current)
```
Recent Activity (last 7 days):
Issues Created:    174
Issues Closed:     141
Issues Updated:    37
```
This is misleading when thousands of issues have actually cycled through.


Switch from modernc.org/sqlite to ncruces/go-sqlite3 for WASM support

modernc.org/sqlite depends on modernc.org/libc which has no js/wasm support (platform-specific syscalls). Need to switch to ncruces/go-sqlite3 which wraps a WASM build of SQLite using wazero runtime.

Key differences:
- ncruces/go-sqlite3: Uses WASM build of SQLite + wazero runtime
- modernc.org/sqlite: Pure Go translation, requires libc for syscalls

This is a prerequisite for bd-62a0 (WASM build infrastructure).


Debug test 2


CONFIG.md docs for sync.require_confirmation_on_mass_delete missing threshold detail

The docs say 'if >50% of issues vanish' but the actual code (worktree.go:380) also requires '>5 issues existed before'. The docs should mention both conditions for completeness.


Implement health checks in daemon event loop

Add health checks to checkDaemonHealth() function in daemon_event_loop.go:170:
- Database integrity check
- Disk space check
- Memory usage check

Currently it's just a no-op placeholder.


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.


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.


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.


Implement `bd setup claude` command for Claude Code integration

Create a `bd setup claude` command that installs Claude Code integration files (slash commands and hooks). This is idempotent and safe to run multiple times.


Extract migration functions to migrations.go

Move migrateDirtyIssuesTable, migrateExternalRefColumn, migrateCompositeIndexes, migrateClosedAtConstraint, migrateCompactionColumns, migrateSnapshotsTable, migrateCompactionConfig, migrateCompactedAtCommitColumn, migrateExportHashesTable, migrateContentHashColumn to a separate migrations.go file


Batch test 5


Tests take too long - unacceptable for project size

## Problem

Running `go test ./internal/importer/... -v` takes an unacceptably long time (minutes). For a project this size, tests should complete in seconds.

## Impact

- Slows down development iteration
- AI agents waste time waiting for tests
- Blocks rapid bug fixes and validation
- Poor developer experience

## Investigation Needed

- Profile which tests are slow
- Check for unnecessary sleeps, timeouts, or integration tests
- Look for tests that could be parallelized
- Consider splitting unit vs integration tests

## Goal

Test suite for a single package should complete in <5 seconds, ideally <2 seconds.

## Optimizations Applied

1. **Added t.Parallel() to CLI tests** (13 tests) - allows concurrent execution
2. **Removed unnecessary 200ms sleep** in daemon_autoimport_test.go - Execute() forces auto-import synchronously
3. **Reduced filesystem settle wait** from 100ms → 50ms on non-Windows platforms
4. **Optimized debouncer test sleeps** (9 reductions):
   - Before debounce waits: 30ms → 20ms, 20ms → 10ms
   - After debounce waits: 40ms → 35ms, 30ms → 35ms, etc.
   - Thread safety test: 100ms → 70ms
   - Sequential cycles: 50ms → 40ms (3x)
   - Cancel tests: 70-80ms → 60ms

## Results

### cmd/bd package (main improvement target):
- **Before**: 5+ minutes (timeout)
- **After**: ~18-20 seconds
- **Speedup**: ~15-18x faster

### internal/importer package:
- **After**: <1 second (0.9s)

### Full test suite (with `-short` flag):
- Most packages complete in <2s
- Total runtime constrained by sequential integration tests

## Known Issues

- TestConcurrentExternalRefImports hangs due to :memory: connection pool issue (bd-b121)
- Some sync_branch tests may need sequential execution (git worktree conflicts)


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.


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

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

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

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

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

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

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


Implement TryResurrectParent function

Create internal/storage/sqlite/resurrection.go with TryResurrectParent(ctx, parentID) function. Parse JSONL history to find deleted parent, create tombstone with status=deleted and is_tombstone=true flag. Handle recursive resurrection for multi-level missing parents (bd-abc.1.2 with missing bd-abc and bd-abc.1).


Daemon crash recovery: panic handler + socket cleanup

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

This ensures better diagnostics and cleaner state after crashes.


bd jira sync command

Add a built-in bd command for Jira synchronization.

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

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

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

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

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


TestImportIssues_LegacyDeletionsConvertedToTombstones is failing

The test TestImportIssues_LegacyDeletionsConvertedToTombstones in internal/importer/importer_test.go is failing:

Expected 3 created (1 regular + 2 tombstones from deletions.jsonl), got 2
Expected tombstone for test-deleted2 not found

The test expects legacy deletions.jsonl entries to be converted to tombstones during import, but test-deleted2 is not being converted.

Location: internal/importer/importer_test.go:1344


Enhance bd doctor to suggest reset for broken states

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

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

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

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

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

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


Enforce daemon singleton per workspace with file locking

Agent in ~/src/wyvern discovered 4 simultaneous daemon processes running, causing infinite directory recursion (.beads/.beads/.beads/...). Each daemon used relative paths and created nested .beads/ directories.

Root cause: No singleton enforcement. Multiple `bd daemon` processes can start in same workspace.

Expected: One daemon per workspace (each workspace = separate .beads/ dir with bd.sock)
Actual: Multiple daemons can run simultaneously in same workspace

Note: Separate git clones = separate workspaces = separate daemons (correct). Git worktrees share .beads/ and have known limitations (documented, use --no-daemon).

## Fix Summary

Successfully prevented the nested .beads/.beads/ recursion bug by implementing two safeguards:

1. **Path Canonicalization in FindDatabasePath()** (beads.go):
   - Added filepath.Abs() + filepath.EvalSymlinks() to normalize all database paths
   - Prevents relative path edge cases that create nested directories
   - Ensures all daemons see the same canonical path

2. **Nested Directory Detection** (daemon_lifecycle.go):
   - Added explicit check for ".beads/.beads" pattern in setupDaemonLock()
   - Fails fast with clear error message if nested structure detected
   - Provides user hints about proper usage

## Root Cause

The daemon lock (added Oct 22, 2025) correctly prevents simultaneous daemons in the SAME workspace. However, when BEADS_DB used a relative path (e.g., ".beads/beads.db") from inside the .beads directory, FindDatabasePath() would resolve it to a nested path creating a separate workspace:
- First daemon: /workspace/.beads/beads.db
- Second daemon from .beads/: /workspace/.beads/.beads/beads.db ← Different lock file!

## Testing

All acceptance criteria passed:
✅ 1. Second daemon start fails with "daemon already running" error
✅ 2. Killing daemon releases lock, new daemon can start
✅ 3. No infinite .beads/ recursion possible (tested nested BEADS_DB path)
✅ 4. Works with auto-start mechanism

The fix addresses the edge case while maintaining the existing lock mechanism's correctness.


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

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

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

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

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

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

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

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