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.


Replace BEADS_DB with BEADS_DIR environment variable

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

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

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

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

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

Related to GitHub issue #179


Fix multi-round convergence for N-way collisions

## Problem

N-way collision resolution is working (IDs get remapped correctly), but clones don't fully converge after a single final pull. Each clone is missing some issues that other clones have.

From TestFiveCloneCollision results:
- Clone A has: A, B
- Clone B has: A, B
- Clone C has: A, B, C
- Clone D has: A, B, C, D
- Clone E has: A, B, C, E

**Expected**: All clones should have A, B, C, D, E after final pull.

## Root Cause

The current sync workflow does:
1. Each clone syncs in order (resolving collisions locally)
2. Final pull to get all changes

But the final pull itself may need import with collision resolution, which creates new commits. These new commits aren't propagated to other clones, so they remain incomplete.

## Proposed Solution

**Option 1: Multi-round final sync**
- After final pull, do additional sync rounds until all clones converge
- Check convergence by comparing issue counts or content hashes
- Maximum N rounds for N clones

**Option 2: Iterative pull-import-push**
- Each clone: pull → import with --resolve-collisions → push
- Repeat until no new changes
- Guaranteed convergence but may create commit spam

**Option 3: Fix auto-import to be truly idempotent**
- Ensure importing same JSONL multiple times produces no new commits
- May require smarter content-based deduplication

## Acceptance Criteria

- TestFiveCloneCollision passes without t.Skip
- All N clones have all N issues after convergence
- Convergence happens in bounded rounds (≤ N)
- No data loss or duplication
- Works for arbitrary N (tested with 5, 10 clones)

## Impact

This is the final blocker for bd-cbed9619 epic completion.


Daemon performAutoImport should update jsonl_content_hash after import

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

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


Audit and standardize JSON output across all commands

Ensure consistent JSON format and error handling when --json flag is used.

**Scope:**
1. Verify all commands respect --json flag
2. Standardize success response format
3. Standardize error response format
4. Document JSON schemas

**Commands to audit:**
- Core CRUD: create, update, delete, show, list, search ✓
- Queries: ready, blocked, stale, count, stats, status
- Deps: dep add/remove/tree/cycles
- Labels: label commands
- Comments: comments add/list/delete
- Epics: epic status/close-eligible
- Export/import: already support --json ✓

**Testing:**
- Success cases return valid JSON
- Error cases return valid JSON (not plain text)
- Consistent field naming (snake_case vs camelCase)
- Array vs object wrapping consistency


Add sync.branch configuration

Add configuration layer to support sync.branch setting via config file, environment variable, or CLI flag.

Tasks:
- Add sync.branch field to config schema
- Add BEADS_SYNC_BRANCH environment variable
- Add --branch flag to bd init
- Add bd config get/set sync.branch commands
- Validation (branch name format, conflicts)
- Config migration for existing users

Estimated effort: 1-2 days


Issue 2 to reopen


bd hooks install fails in git worktrees

When bd is used in a git worktree, bd hooks install fails with 'mkdir .git: not a directory' because .git is a file (gitdir pointer) not a directory. Beads should detect and follow the .git gitdir pointer to install hooks in the correct location. This blocks normal worktree workflows.

## Symptoms of this bug:
- Git hooks don't install automatically
- Auto-sync doesn't run (5-second debounce)
- Hash mismatch warnings in bd output
- Daemon fails to start with 'auto_start_failed'

## Workaround:
Use `git rev-parse --git-dir` to find the actual hooks directory and copy hooks manually:
```bash
mkdir -p $(git rev-parse --git-dir)/hooks
cp -r .beads-hooks/* $(git rev-parse --git-dir)/hooks/
```


Manual task


Feature with design

This is a description


Fix import failure on missing parent issues

Import process fails atomically when JSONL references deleted parent issues. Implement hybrid solution: topological sorting + parent resurrection to handle deleted parents gracefully while maintaining referential integrity. See docs/import-bug-analysis-bd-3xq.md for full analysis.


Add unit tests for getLocalSyncBranch

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

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

Location: cmd/bd/autoimport_test.go


string(rune()) pattern in other test files still only works for single digits

The bd-c5m fix only addressed worktree_divergence_test.go, but several other test files have the same issue:

- cmd/bd/stale_test.go:218, 234
- cmd/bd/export_integrity_integration_test.go:45-47, 279-280
- internal/storage/sqlite/comments_test.go:146, 165
- internal/storage/sqlite/blocked_cache_test.go:292
- internal/storage/sqlite/compact_bench_test.go:117
- internal/storage/sqlite/cycle_detection_test.go:418

These use string(rune('0'+i)) which produces wrong characters when i >= 10.
Should use strconv.Itoa(i) instead.


Duplicate DefaultRetentionDays constants

## Problem

There are now two constants for the same value:

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

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

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


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


Refactor check-health DB access to avoid repeated path resolution

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


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

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

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

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

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


bd doctor: detect and fix stale beads-sync branch

## Problem

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

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

## Proposed bd doctor checks

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

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

## Proposed fixes (--fix)

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

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


Test parent issue


Add index on deleted_at for TTL queries

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

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

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


bd sync fails with orphaned issues and duplicate ID conflict

After fixing the deleted_at TEXT column scanning bug (commit 18b1eb2), bd sync still fails with two issues:

1. Orphan Detection Warning: 12 orphaned child issues whose parents no longer exist (bd-cb64c226.* and bd-cbed9619.*)

2. Import Failure: UNIQUE constraint failed for bd-360 - this tombstone exists in both DB and JSONL

Error: "Import failed: error creating depth-0 issues: bulk insert issues: failed to insert issue bd-360: sqlite3: constraint failed: UNIQUE constraint failed: issues.id"

Fix options:
- Delete orphaned child issues with bd delete
- Resolve bd-360 duplicate (in deletions.jsonl vs tombstone in DB)
- Reset sync branch: git branch -f beads-sync main && git push --force-with-lease origin beads-sync

Fixed tombstone constraint violation bug. When deleting closed issues, the CHECK constraint (status = 'closed') = (closed_at IS NOT NULL) was violated because CreateTombstone didn't clear closed_at. Fix: set closed_at = NULL in tombstone creation SQL.

The sync data corruption (orphaned issues in beads-sync branch) requires manual cleanup: reset sync branch with 'git branch -f beads-sync main && git push --force-with-lease origin beads-sync'


Short tests taking 13+ minutes (performance regression)


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.


Daemon gets stuck when auto-import blocked by git conflicts

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

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

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

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

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

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


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

## Merged PRs Summary

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

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

---

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

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

---

## Overall Assessment

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

No critical bugs or design issues found.

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


Create cmd/bd/daemon_watcher.go (~150 LOC)

Implement FileWatcher using fsnotify to watch JSONL file and git refs. Handle platform differences (inotify/FSEvents/ReadDirectoryChangesW). Include edge case handling for file rename, event storm, watcher failure.


GH#486: Claude forgets beads workflow mid-session

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


Add integration tests for adapter library

Test suite for beads_mail_adapter.py covering all scenarios.

Acceptance Criteria:
- Test enabled mode (server available)
- Test disabled mode (server unavailable)
- Test graceful degradation (server dies mid-operation)
- Test reservation conflicts
- Test message sending/receiving
- Mock HTTP server for testing
- 90%+ code coverage

File: lib/test_beads_mail_adapter.py

Test suite completed with 29 comprehensive tests covering:
- Enabled mode (server available): 10 tests
- Disabled mode (server unavailable): 2 tests
- Graceful degradation: 4 tests
- Reservation conflicts: 2 tests
- Configuration: 5 tests
- Health check scenarios: 3 tests
- HTTP error handling: 3 tests

**Performance**: All tests run in 10ms (fast!)

**Coverage highlights**:
✅ Server health checks (ok, degraded, error, timeout)
✅ All API operations (reserve, release, notify, check_inbox, get_reservations)
✅ HTTP errors (404, 409 conflict, 500, 503)
✅ Network errors (timeout, connection refused)
✅ Malformed responses (bad JSON, empty body, plain text errors)
✅ Environment variable configuration
✅ Graceful degradation when server dies mid-operation
✅ Conflict handling with both JSON and plain text errors
✅ Dict wrapper responses ({"messages": [...]} and {"reservations": [...]})
✅ Custom TTL for reservations
✅ Default agent name fallback

No external dependencies, no slow integration tests, just fast unit tests with mocks.


Integration test: mutation to export latency

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

Test added to daemon_test.go as TestMutationToExportLatency().

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

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

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


Add command injection prevention tests for git rm in merge command

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

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

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

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

This is a critical security path preventing arbitrary command execution.

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


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.


Add tests for merge driver auto-config in bd init

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

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

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


Add security tests for file path validation in clean command

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

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

In cmd/bd/clean.go:118, os.Open(gitignorePath) is flagged by gosec G304 for potential file inclusion via variable without validation.

Add tests covering:
- Path traversal attempts (../../etc/passwd)
- Absolute paths outside project directory
- Symlink following behavior
- Non-existent file handling
- Validation that only .gitignore files in valid locations are opened

This is a security-sensitive code path that needs validation to prevent unauthorized file access.

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


Implement CLI command (cmd/bd/reset.go)

Wire up the reset command with Cobra CLI.

## Responsibilities
- Define command and all flags
- User confirmation prompt (unless --force)
- Display impact summary before confirmation
- Colored output and progress indicators
- Call core reset package
- Handle errors with user-friendly messages
- Register command with rootCmd in init()

## Flags
```go
--hard       bool   "Also remove from git and commit"
--force      bool   "Skip confirmation prompt"
--backup     bool   "Create backup before reset"
--dry-run    bool   "Preview what would happen"
--skip-init  bool   "Do not re-initialize after reset"
--verbose    bool   "Show detailed progress output"
```

## Output Format
```
⚠  This will reset beads to a clean state.

Will be deleted:
  • 47 issues (23 open, 24 closed)
  • 12 tombstones

Continue? [y/N] y

→ Stopping daemons... ✓
→ Removing .beads/... ✓
→ Initializing fresh... ✓

✓ Reset complete. Run 'bd onboard' to set up hooks.
```

## Implementation Notes
- Confirmation logic lives HERE, not in core package
- Use color package (github.com/fatih/color) for output
- Follow patterns from other commands (init.go, doctor.go)
- Add to rootCmd in init() function

## File Location
`cmd/bd/reset.go`


Add performance optimization to hasJSONLChanged with mtime fast-path

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

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

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


Help agents understand version changes with bd info --whats-new

**Problem** (from GH Discussion #239 by @maphew):
Weekly major versions mean agents need to adapt workflows, but currently there's no efficient way to communicate "what changed that affects you."

**Proposed solutions:**

1. **bd info --whats-new** - Show agent-actionable changes since last version
   ```
   Since v0.20.1:
   • Hash IDs eliminate collisions - remove ID coordination workarounds
   • Event-driven daemon (opt-in) - add BEADS_DAEMON_MODE=events
   • Merge driver auto-configured - conflicts rarer
   ```

2. **Version-aware bd onboard** - Detect version changes and show diff of agent-relevant changes

3. **AGENTS.md top section** - "🆕 Recent Changes (Last 3 Versions)" with workflow impacts

**Why agents need this:**
- Raw CHANGELOG is token-heavy and buried in release details
- Full bd onboard re-run wasteful if only 2-3 things changed
- Currently requires user to manually explain updates

**Related:** https://github.com/steveyegge/beads/discussions/239


Update AGENTS.md with event-driven mode

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


Design tombstone storage format

Define the JSON schema for tombstone records. Options: minimal (id, deleted_at, deleted_by, expires_at) vs full preservation (keep all original fields). Trade-offs: storage size vs audit trail. Consider: should tombstones be distinguishable from regular issues by status field or separate record type?


Add external_ref change tracking and auditing

Currently we don't track when external_ref is added, removed, or changed. This would be useful for debugging and auditing.

Proposed features:
- Log event when external_ref changes
- Track in events table with old/new values
- Add query to find issues where external_ref changed
- Add metrics: issues with external_ref vs without

Use cases:
- Debugging import issues
- Understanding which issues are externally managed
- Auditing external system linkage

Related: bd-1022


Investigation & Proof of Concept

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

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

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

Next: bd-wfmw (Integration Layer Implementation)


Review PR #512: clarify bd ready docs

Review and merge PR #512 from aspiers. This PR clarifies what bd ready does after git pull in README.md. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/512


Test basic bd commands in WASM (init, create, list)

Compile and verify basic bd functionality works in WASM:
- Test bd init --quiet
- Test bd create with simple issue
- Test bd list --json output
- Verify SQLite database creation and queries work
- Document any runtime issues or workarounds needed


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.


Add bd reset command for clean slate restart

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

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

## Design

### Command Interface
```
bd reset [--hard] [--force] [--backup] [--dry-run] [--no-init]
```

| Flag | Effect |
|------|--------|
| `--hard` | Also remove from git index and commit |
| `--force` | Skip confirmation prompt |
| `--backup` | Create `.beads-backup-{timestamp}/` first |
| `--dry-run` | Preview what would happen |
| `--no-init` | Don't re-initialize after clearing |

### Reset Levels
1. **Soft Reset (default)** - Kill daemons, clear .beads/, re-init. Git history unchanged.
2. **Hard Reset (`--hard`)** - Also git rm and commit the removal, then commit fresh state.

### Implementation Flow
1. Validate .beads/ exists
2. If not --force: show impact summary, prompt confirmation
3. If --backup: copy .beads/ to .beads-backup-{timestamp}/
4. Kill daemons
5. If --hard: git rm + commit
6. rm -rf .beads/*
7. If not --no-init: bd init (and git add+commit if --hard)
8. Print summary

### Safety Mechanisms
- Confirmation prompt (skip with --force)
- Impact summary (issue/tombstone counts)
- Backup option
- Dry-run preview
- Git dirty check warning

### Code Structure
- `cmd/bd/reset.go` - CLI command
- `internal/reset/` - Core logic package


Modify EnsureIDs to support parent resurrection

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


GH#517: Fix Claude setting wrong priority syntax on new install

Claude uses 'medium' instead of P2/2 for priority, causing infinite error loops. bd prime hook or docs not clear enough. See: https://github.com/steveyegge/beads/issues/517


mergePriority: negative priorities not handled consistently


Design tombstone TTL and expiration semantics

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


bd-hv01: Deletion tracking production readiness

Epic to track all improvements and fixes needed to make the deletion tracking implementation ([deleted:bd-hv01]) production-ready.

The core 3-way merge algorithm is sound, but there are critical issues around atomicity, error handling, and edge cases that need to be addressed before this can be safely used in production.

Critical path (P1):
- Non-atomic snapshot operations
- Brittle JSON string comparison
- Silent partial deletion failures
- Race conditions in concurrent scenarios

Follow-up work (P2-P3):
- Test coverage for edge cases and multi-repo mode
- Performance optimizations
- Code refactoring and observability

Related commit: 708a81c


Multi-repo hydration layer

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


Create backup and restore procedures

Disaster recovery procedures for Agent Mail data.

Acceptance Criteria:
- Automated daily snapshots (GCP persistent disk)
- SQLite backup script
- Git repository backup
- Restore procedure documentation
- Test restore from backup

File: deployment/agent-mail/backup.sh


Add tests for daemon lifecycle module


Fix row iterator resource leak in recursive dependency queries

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

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

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

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

Impact: Connection exhaustion during dependency traversal


Test child issue


Add integration tests for bd reset command

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

## Test Scenarios

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

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

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

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

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

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


Add git operations for --hard reset

Implement git integration for hard reset mode.

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

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

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

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

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

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


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

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

Current coverage: 23.9%
Target coverage: 50%


Add test coverage for internal/autoimport package


Clarify exclusive lock protocol compatibility with multi-repo

The contributor-workflow-analysis.md proposes per-repo file locking (Decision #7) using flock on JSONL files. However, VC (a downstream library consumer) uses an exclusive lock protocol (vc-195, requires Beads v0.17.3+) that allows bd daemon and VC executor to coexist.

Need to clarify:
- Does the proposed per-repo file locking work with VC's existing exclusive lock protocol?
- Do library consumers like VC need to adapt their locking logic?
- Can multiple repos be locked atomically for cross-repo operations?

Context: contributor-workflow-analysis.md lines 662-681


bd doctor should detect fresh clone and recommend 'bd init'

When running `bd doctor` on a fresh clone (JSONL exists, no .db file), it should:

1. Detect this is a fresh clone situation
2. Recommend `bd init --prefix <detected-prefix>` as the fix
3. Show the prefix detected from the JSONL file

Currently it shows various warnings (git hooks, merge driver, etc.) but doesn't address the fundamental issue: the database needs to be hydrated.


Investigate and upgrade to modernc.org/sqlite 1.39.1+

We had to pin modernc.org/sqlite to v1.38.2 due to a FOREIGN KEY constraint regression in v1.39.1 (SQLite 3.50.4).

**Issue:** [deleted:bd-47], GH #144

**Symptom:** CloseIssue fails with "FOREIGN KEY constraint failed (787)" when called via MCP/daemon, but works fine via CLI.

**Root Cause:** Unknown - likely stricter FK enforcement in SQLite 3.50.4 or modernc.org wrapper changes.

**Workaround:** Pinned to v1.38.2 (SQLite 3.49.x)

**TODO:**
1. Monitor modernc.org/sqlite releases for fixes
2. Check SQLite 3.50.5+ changelogs for FK-related fixes
3. Investigate why daemon mode fails but CLI succeeds (connection reuse? transaction isolation?)
4. Consider filing upstream issue with reproducible test case
5. Upgrade when safe


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.


Add security tests for WriteFile permissions in doctor command

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

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

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

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

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

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


worktree: any bd command is slow

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


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

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

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

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

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

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


Support separate branch for beads commits

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

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

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

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


Add migration guide for library consumers

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

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

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

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

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


Add content-addressable identity to Issue type

## Overview
Phase 1: Add content hashing to enable global identification of issues regardless of their assigned IDs.

## Current Problem
The system identifies issues only by ID (e.g., test-1, test-2). When multiple clones create the same ID with different content, there's no way to identify that these are semantically different issues without comparing all fields.

## Solution
Add a ContentHash field to the Issue type that represents the canonical content fingerprint.

## Implementation Tasks

### 1. Add ContentHash field to Issue type
File: internal/types/types.go
```go
type Issue struct {
    ID          string
    ContentHash string  // SHA256 of canonical content
    // ... existing fields
}
```

### 2. Add content hash computation method
Use existing hashIssueContent from collision.go:186 as foundation:
```go
func (i *Issue) ComputeContentHash() string {
    return hashIssueContent(i)
}
```

### 3. Compute hash at creation time
- Modify CreateIssue to compute and store ContentHash
- Modify CreateIssues (batch) to compute hashes

### 4. Compute hash at import time
- Modify ImportIssues to compute ContentHash for all incoming issues
- Store hash in database

### 5. Add database column
- Add migration to add content_hash column to issues table
- Update SELECT/INSERT statements to include content_hash
- Index on content_hash for fast lookups

### 6. Populate existing issues
- Add migration step to compute ContentHash for all existing issues
- Use hashIssueContent function

## Acceptance Criteria
- Issue type has ContentHash field
- Hash is computed automatically at creation time
- Hash is computed for imported issues
- Database stores content_hash column
- All existing issues have non-empty ContentHash
- Hash is deterministic (same content → same hash)
- Hash excludes ID, timestamps (only semantic content)

## Files to Modify
- internal/types/types.go
- internal/storage/sqlite/sqlite.go (schema, CreateIssue, CreateIssues)
- internal/storage/sqlite/migrations.go (new migration)
- internal/importer/importer.go (compute hash during import)
- cmd/bd/create.go (compute hash at creation)

## Testing
- Unit test: same content produces same hash
- Unit test: different content produces different hash
- Unit test: hash excludes ID and timestamps
- Integration test: hash persists in database
- Migration test: existing issues get hashes populated


deletions.jsonl gets corrupted with full issue objects instead of deletion records

## Bug Description

The deletions.jsonl file was found to contain full issue objects (like issues.jsonl) instead of deletion records.

### Expected Format (DeletionRecord)
```json
{"id":"bd-xxx","timestamp":"2025-...","actor":"user","reason":"deleted"}
```

### Actual Content Found
```json
{"id":"bd-03r","title":"Document deletions manifest...","description":"...","status":"closed",...}
```

## Impact
- bd sync sanitization step reads deletions.jsonl and removes any matching IDs from issues.jsonl
- With 60 full issue objects in deletions.jsonl, ALL 60 issues were incorrectly removed during sync
- This caused complete data loss of the issue database

## Root Cause (suspected)
Something wrote issues.jsonl content to deletions.jsonl. Possible causes:
- Export writing to wrong file
- File path confusion during sync
- Race condition between export and deletion tracking

## Related Issues
- bd-0b2: --no-git-history flag (just fixed)
- bd-4pv: export outputs only 1 issue after corruption
- bd-4t7: auto-import runs during --no-auto-import

## Reproduction
Unknown - discovered during bd sync session on 2025-11-26

## Fix
Need to investigate what code path could write issue objects to deletions.jsonl


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.


Update MCP server for hash IDs

Update beads-mcp server to support hash IDs and aliases.

## Changes Needed

### 1. MCP Function Signatures (No Change)
Functions already use issue IDs as strings, so they work with hash IDs:

```python
# These already work!
beads_show(issue_id: str)           # Accepts bd-af78e9a2 or #42
beads_update(issue_id: str, ...)    # Accepts both formats
beads_close(issue_ids: List[str])   # Accepts both formats
```

### 2. Add Alias Resolution Helper
File: integrations/beads-mcp/src/beads_mcp/server.py

```python
def resolve_issue_id(issue_id: str) -> str:
    """Resolve alias to hash ID if needed."""
    # Hash ID: pass through
    if issue_id.startswith('bd-') and len(issue_id) == 11:
        return issue_id

    # Alias: #42 or 42
    alias_str = issue_id.lstrip('#')
    try:
        alias = int(alias_str)
        # Call bd to resolve
        result = subprocess.run(
            ['bd', 'alias', 'find', f'bd-{alias}'],
            capture_output=True, text=True
        )
        if result.returncode == 0:
            return result.stdout.strip()
    except ValueError:
        pass

    # Invalid format
    raise ValueError(f"Invalid issue ID: {issue_id}")
```

### 3. Update Response Formatting
Show aliases in responses:

```python
@server.call_tool()
async def beads_show(issue_id: str) -> List[TextContent]:
    resolved_id = resolve_issue_id(issue_id)

    result = subprocess.run(['bd', 'show', resolved_id], ...)

    # Parse response and add alias info
    # Format: "bd-af78e9a2 (alias: #42)"
    ...
```

### 4. Add beads_alias_* Functions

```python
@server.call_tool()
async def beads_alias_list() -> List[TextContent]:
    """List all alias mappings."""
    result = subprocess.run(['bd', 'alias', 'list'], ...)
    return [TextContent(type="text", text=result.stdout)]

@server.call_tool()
async def beads_alias_set(alias: int, issue_id: str) -> List[TextContent]:
    """Manually assign alias to issue."""
    result = subprocess.run(['bd', 'alias', 'set', str(alias), issue_id], ...)
    return [TextContent(type="text", text=result.stdout)]

@server.call_tool()
async def beads_alias_compact() -> List[TextContent]:
    """Compact aliases to fill gaps."""
    result = subprocess.run(['bd', 'alias', 'compact'], ...)
    return [TextContent(type="text", text=result.stdout)]
```

### 5. Update Documentation
File: integrations/beads-mcp/README.md

```markdown
## Issue IDs (v2.0+)

The MCP server accepts both hash IDs and aliases:

```python
# Using hash IDs
await beads_show(issue_id="bd-af78e9a2")

# Using aliases
await beads_show(issue_id="#42")
await beads_show(issue_id="42")  # Shorthand
```

## Alias Management

New functions for alias control:

- `beads_alias_list()` - List all alias mappings
- `beads_alias_set(alias, issue_id)` - Manually assign alias
- `beads_alias_compact()` - Compact aliases to fill gaps

## Migration

After migrating to hash IDs:
1. Update beads-mcp: `pip install --upgrade beads-mcp`
2. Restart MCP server
3. All existing workflows continue to work
```

### 6. Version Compatibility
Detect and handle both v1.x and v2.0 formats:

```python
def detect_beads_version() -> str:
    """Detect if beads is using sequential or hash IDs."""
    result = subprocess.run(['bd', 'list', '-n', '1'], ...)
    first_id = parse_first_issue_id(result.stdout)

    if first_id.startswith('bd-') and len(first_id) > 11:
        return '2.0'  # Hash ID
    else:
        return '1.x'  # Sequential ID

# On startup
beads_version = detect_beads_version()
logger.info(f"Detected beads version: {beads_version}")
```

## Testing

### Unit Tests
File: integrations/beads-mcp/tests/test_hash_ids.py

```python
def test_resolve_hash_id():
    """Hash IDs pass through unchanged."""
    assert resolve_issue_id("bd-af78e9a2") == "bd-af78e9a2"

def test_resolve_alias():
    """Aliases resolve to hash IDs."""
    # Mock bd alias find command
    assert resolve_issue_id("#42") == "bd-af78e9a2"
    assert resolve_issue_id("42") == "bd-af78e9a2"

def test_invalid_id():
    """Invalid IDs raise ValueError."""
    with pytest.raises(ValueError):
        resolve_issue_id("invalid")
```

### Integration Tests
```python
async def test_show_with_hash_id(mcp_server):
    result = await mcp_server.beads_show(issue_id="bd-af78e9a2")
    assert "bd-af78e9a2" in result[0].text

async def test_show_with_alias(mcp_server):
    result = await mcp_server.beads_show(issue_id="#42")
    assert "bd-af78e9a2" in result[0].text  # Resolved
```

## Backward Compatibility
The MCP server should work with both:
- Beads v1.x (sequential IDs)
- Beads v2.0+ (hash IDs)

Detection happens at runtime based on issue ID format.

## Files to Modify
- integrations/beads-mcp/src/beads_mcp/server.py
- integrations/beads-mcp/README.md
- integrations/beads-mcp/tests/test_hash_ids.py (new)
- integrations/beads-mcp/pyproject.toml (bump version)

## Deployment
```bash
cd integrations/beads-mcp
# Bump version to 2.0.0
poetry version 2.0.0
# Publish to PyPI
poetry publish --build
```


Improve sync error messages

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


Test label update feature


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

Linting issue: checkHooksQuick - path is unused (unparam) at cmd/bd/doctor.go:541:22. Error: func checkHooksQuick(path string) string {


Support --reason flag in daemon mode for reopen command

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


Import overwrites newer local issues with older remote versions

## Problem

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

## What Happened

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

## Root Cause

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

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

Import applies incoming version regardless of which is newer.

## Expected Behavior

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

## Solution

Add timestamp checking to DetectCollisions or importIssues:

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

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

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


Test dependency parent


bd preflight: PR readiness checks for contributors

## Vision

Encode project-specific institutional knowledge into executable checks. CONTRIBUTING.md is documentation that's read once and forgotten; `bd preflight` is documentation that runs at exactly the right moment.

## Problem Statement

Contributors face a "last mile" problem - they do the work but stumble on project-specific gotchas at PR time:
- Nix vendorHash gets stale when go.sum changes
- Beads artifacts leak into PRs (see bd-umbf for namespace solution)
- Version mismatches between version.go and default.nix
- Tests/lint not run locally before pushing
- Other project-specific checks that only surface when CI fails

These are too obscure to remember, exist in docs nobody reads end-to-end, and waste CI round-trips.

## Why beads?

Beads already has a foothold in the contributor workflow. It knows:
- Git state (staged files, branch, dirty status)
- Project structure
- The specific issue being worked on
- Project-specific configuration

## Proposed Interface

### Tier 1: Checklist Mode (v1)

    $ bd preflight
    PR Readiness Checklist:

    [ ] Tests pass: go test -short ./...
    [ ] Lint passes: golangci-lint run ./...
    [ ] No beads pollution: check .beads/issues.jsonl diff
    [ ] Nix hash current: go.sum unchanged or vendorHash updated
    [ ] Version sync: version.go matches default.nix

    Run 'bd preflight --check' to validate automatically.

### Tier 2: Check Mode (v2)

    $ bd preflight --check
    ✓ Tests pass
    ✓ Lint passes
    ⚠ Beads pollution: 3 issues in diff - are these project issues or personal?
    ✗ Nix hash stale: go.sum changed, vendorHash needs update
      Fix: sha256-KRR6dXzsSw8OmEHGBEVDBOoIgfoZ2p0541T9ayjGHlI=
    ✓ Version sync

    1 error, 1 warning. Run 'bd preflight --fix' to auto-fix where possible.

### Tier 3: Fix Mode (v3)

    $ bd preflight --fix
    ✓ Updated vendorHash in default.nix
    ⚠ Cannot auto-fix beads pollution - manual review needed

## Checks to Implement

| Check | Description | Auto-fixable |
|-------|-------------|--------------|
| tests | Run go test -short ./... | No |
| lint | Run golangci-lint | Partial (gofmt) |
| beads-pollution | Detect personal issues in diff | No (see bd-umbf) |
| nix-hash | Detect stale vendorHash | Yes (if nix available) |
| version-sync | version.go matches default.nix | Yes |
| no-debug | No TODO/FIXME/console.log | Warn only |
| clean-stage | No unintended files staged | Warn only |

## Future: Configuration

Make checks configurable per-project via .beads/preflight.yaml:

    preflight:
      checks:
        - name: tests
          run: go test -short ./...
          required: true
        - name: no-secrets
          pattern: "**/*.env"
          staged: deny
        - name: custom-check
          run: ./scripts/validate.sh

This lets any project using beads define their own preflight checks.

## Implementation Phases

### Phase 1: Static Checklist
- Implement bd preflight with hardcoded checklist for beads
- No execution, just prints what to check
- Update CONTRIBUTING.md to reference it

### Phase 2: Automated Checks
- Implement bd preflight --check
- Run tests, lint, detect stale hashes
- Clear pass/fail/warn output

### Phase 3: Auto-fix
- Implement bd preflight --fix
- Fix vendorHash, version sync
- Integrate with bd-umbf solution for pollution

### Phase 4: Configuration
- .beads/preflight.yaml support
- Make it useful for other projects using beads
- Plugin/hook system for custom checks

## Dependencies

- bd-umbf: Namespace isolation for beads pollution (blocking for full solution)

## Success Metrics

- Fewer CI failures on first PR push
- Reduced "fix nix hash" commits
- Contributors report preflight caught issues before CI


Extract daemon config functions to daemon_config.go


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.


Second worktree test


Update JSONL format to use hash IDs

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

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

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

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

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

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

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

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

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

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

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

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

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


AI-supervised database migrations for safer schema evolution

## Problem

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

## Current State

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

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

## Proposal: AI-Supervised Migration Framework

Use AI to supervise migrations through structured verification:

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

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

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

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

## Example Flow

```
$ bd migrate

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

If something goes wrong:
```
$ bd migrate

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

## Implementation Ideas

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

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

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

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

## Benefits

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

## Open Questions

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

## Related Work

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

## Progress

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

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

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

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

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

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

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

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

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


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)


sync.branch config lost on clone - should be in config.yaml

## Problem

The sync.branch configuration is stored in the SQLite database, which is gitignored. When contributors clone a repo that uses beads:

1. They get .beads/issues.jsonl (tracked)
2. They don't get .beads/beads.db (gitignored)
3. They lose the sync.branch config

When they run 'bd init' or 'bd doctor --fix', sync.branch gets auto-set to the current branch (usually 'main'), but if the project uses a dedicated sync branch like 'beads-sync', they'll be committing to the wrong branch.

## Root Cause

syncbranch.Get() only checks:
1. BEADS_SYNC_BRANCH env var
2. Database config (store.GetConfig)

It never reads from config.yaml, which IS tracked in git and persists across clones.

## Proposed Solution

Add sync-branch support to config.yaml with this precedence:
1. BEADS_SYNC_BRANCH env var (highest)
2. config.yaml sync-branch setting
3. Database config (legacy/local override)
4. Auto-detect from current branch (lowest)

Changes needed:
1. Update syncbranch.Get() to check config.GetString('sync-branch') between env and database
2. Update config.yaml template in init.go to include sync-branch
3. Update docs to recommend setting sync-branch in config.yaml for team projects
4. Consider: bd init --branch should also write to config.yaml, not just database


Remove Cache Configuration Docs

Remove documentation of deprecated cache env vars


Extract validation functions to validators.go

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


Extract daemon logger functions to daemon_logger.go


Test auto-start in fred


Consider using types.Status in merge package for type safety

The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521


Remove Daemon Storage Cache

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


Update documentation after code health cleanup

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

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

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

Impact: Keeps documentation in sync with code


Rapid 1


Implement "bd daemons logs" subcommand

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


Consider extracting error handling helpers

Evaluate creating FatalError() and WarnError() helpers as suggested in ERROR_HANDLING.md to reduce boilerplate and enforce consistency. Prototype in a few files first to validate the approach.


Integration Layer Implementation

Build the adapter layer that makes Agent Mail optional and non-intrusive.

Progress: bd-m9th (Python adapter library) completed with full test coverage. Next: bd-fzbg (update python-agent example with Agent Mail integration).


Complete and commit beads-mcp type safety improvements

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


Remove Cache-Related Tests

Delete or update tests that assume multi-repo caching


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.


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

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

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

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

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

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


Add tests for bd init --team wizard

Write integration tests for the team wizard:
- Test branch detection
- Test sync branch creation
- Test protected branch workflow
- Test auto-sync configuration


Update 3-way merge to handle tombstones

Modify internal/merge/merge.go to detect tombstones, implement tombstone-wins-unless-expired logic, add TTL parameter to merge functions. Per design bd-zvg.


convertDeletionToTombstone uses hardcoded default values

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

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

Low priority cleanup for consistency.


Add export test verifying tombstones are included in JSONL output

The export now includes tombstones (bd-dve) but there's no test that:
1. Creates issues in DB including a tombstone
2. Runs export
3. Verifies the JSONL contains the tombstone with all fields

Add a test: TestExportIncludesTombstones


Test child daemon mode


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


Channel isolation test - beads

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


Reduce sync rounds in multiclone tests

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

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

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

File: beads_hash_multiclone_test.go:70, :132


Add date and priority filters to bd search

Add filter parity with bd list for consistent querying.

**Missing filters to add:**
- --priority, --priority-min, --priority-max
- --created-after, --created-before
- --updated-after, --updated-before
- --closed-after, --closed-before
- --empty-description, --no-assignee, --no-labels
- --desc-contains, --notes-contains

**Files to modify:**
- cmd/bd/search.go
- internal/rpc/protocol.go (SearchArgs)
- internal/storage/storage.go (Search method)

**Testing:**
- All filter combinations
- Date format parsing
- Daemon and direct mode


Publish bd-wasm to npm

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

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

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


Optimize --sandbox mode: skip FlushManager

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

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

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

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

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

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

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

Part of bd-olc1


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

```


Run final validation and cleanup checks

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

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

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

Impact: Confirms all cleanup objectives achieved successfully

## Validation Results

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

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

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

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


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

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

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

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

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

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

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


Fix Homebrew warning about Ruby file location

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


Document -short flag in testing guide

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


Multi-clone collision resolution testing and documentation

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

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

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


bd doctor: per-fix confirmation mode

Add an interactive mode where users can approve/reject each fix individually rather than all-or-nothing. Currently --fix prompts once for all fixes. This would give users more control over which repairs to apply, especially useful when some fixes may have side effects.


Add TTL to deletions manifest entries

Deletions manifest entries should have a TTL (time-to-live) to automatically expire after a certain period. Stale deletion entries can poison beads installations when an agent gets out of sync, causing issues to remain deleted even when they shouldn't be. Adding expiration would limit the blast radius of stale deletions.


Issue with labels

This is a description


Add fsnotify dependency to go.mod


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

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

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

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

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

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

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

## Progress Update

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

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

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

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

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

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

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


bd-hv01: Refactor snapshot management into dedicated module

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

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

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

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


Fix transaction conflict in TryResurrectParent

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

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

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

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

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


Test child direct


Time-dependent tests may be flaky near TTL boundary

Several tombstone merge tests use time.Now() to create test data: time.Now().Add(-24 * time.Hour), time.Now().Add(-60 * 24 * time.Hour), etc. While these work reliably in practice (24h vs 30d TTL has large margin), they could theoretically be flaky if: 1) Tests run slowly, 2) System clock changes during test, 3) TTL constants change. Recommendation: Consider using a fixed reference time or time injection for deterministic tests. Lower priority since current margin is large. Files: internal/merge/merge_test.go:1337-1338, 1352-1353, 1548-1549, 1590-1591


Blocked issue


Implement content-hash based collision resolution for deterministic convergence

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

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

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

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

Rename detection successfully implemented and tested!

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

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

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

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


Platform tests: Linux, macOS, Windows

Test event-driven mode on all platforms. Verify inotify (Linux), FSEvents (macOS), ReadDirectoryChangesW (Windows). Test fallback behavior on each.


bd import silently succeeds when database matches working tree but not git HEAD

**Critical**: bd import reports '0 created, 0 updated' when database matches working tree JSONL, even when working tree is ahead of git HEAD. This gives false confidence that everything is synced with the source of truth.

## Reproduction

1. Start with database synced to working tree .beads/issues.jsonl (376 issues)
2. Git HEAD has older version of .beads/issues.jsonl (354 issues)
3. Run: bd import .beads/issues.jsonl
4. Output: 'Import complete: 0 created, 0 updated'

## Problem

User expects 'bd import' after 'git pull' to sync database with committed state, but:
- Command silently succeeds because DB already matches working tree
- No warning that working tree has uncommitted changes
- User falsely believes everything is synced with git
- Violates 'JSONL in git is source of truth' principle

## Expected Behavior

When .beads/issues.jsonl differs from git HEAD, bd import should:
1. Detect uncommitted changes: git diff --quiet HEAD .beads/issues.jsonl
2. Warn user: 'Warning: .beads/issues.jsonl has uncommitted changes (376 lines vs 354 in HEAD)'
3. Clarify status: 'Import complete: 0 created, 0 updated (already synced with working tree)'
4. Recommend: 'Run git diff .beads/issues.jsonl to review uncommitted work'

## Impact

- Users can't trust 'bd import' status messages
- Silent data loss risk if user assumes synced and runs git checkout
- Breaks mental model of 'JSONL in git = source of truth'
- Critical for VC's landing-the-plane workflow


Fix inconsistent error handling in multi-repo deletion tracking

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

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

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

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

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

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

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

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

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


bd doctor: export diagnostics history for debugging

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


Stealth mode global gitignore should use absolute project path

**GitHub Issue:** #538

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

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

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

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

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

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

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


Widespread double JSON encoding bug in daemon mode RPC calls

Multiple CLI commands had the same double JSON encoding bug found in bd-1048. All commands that called ResolveID via RPC used string(resp.Data) instead of properly unmarshaling the JSON response. This caused IDs to retain JSON quotes ("bd-1048" instead of bd-1048), which then got double-encoded when passed to subsequent RPC calls.

Affected commands:
- bd show (3 instances)
- bd dep add/remove/tree (5 instances)
- bd label add/remove/list (3 instances)
- bd reopen (1 instance)

Root cause: resp.Data is json.RawMessage (already JSON-encoded), so string() conversion preserves quotes.

Fix: Replace all string(resp.Data) with json.Unmarshal(resp.Data, &id) for proper deserialization.

All commands now tested and working correctly with daemon mode.


Tombstone export includes tombstones even with --status filter

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

  bd export --status=open

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

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


Benchmark git traffic reduction

Automated benchmark comparing git operations with/without Agent Mail.

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

File: tests/benchmarks/git_traffic.py

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

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

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

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


Update LINTING.md with current baseline

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


Extract normalizeLabels to shared utility package

The `normalizeLabels` function appears in multiple locations with identical implementation. Extract to a shared utility package.

Current locations:
- `internal/rpc/server.go:37` (53 lines) - full implementation
- `cmd/bd/list.go:50-52` - uses the server version (needs to use new shared version)

Function purpose:
- Trims whitespace from labels
- Removes empty strings
- Deduplicates labels
- Preserves order

Target structure:
```
internal/util/
├── strings.go         # String utilities
    └── NormalizeLabels([]string) []string
```

Impact: DRY principle, single source of truth, easier to test


Production Readiness (Optional)

Enable multi-machine deployments with containerization and monitoring.


Missing test for IsExpiredTombstone edge cases

## Problem

The `IsExpiredTombstone()` function has several edge cases that aren't directly tested:

1. **Invalid DeletedAt timestamp** - Returns false (safety), but not tested
2. **ttl=0 fallback to DefaultTombstoneTTL** - Not tested directly
3. **Tombstone with empty DeletedAt** - Returns false, tested implicitly but not explicitly
4. **Clock skew grace period effect** - Not tested (would need time mocking)

## Current Test Coverage

The existing tests use `time.Now()` which makes them non-deterministic:
```go
DeletedAt: time.Now().Add(-24 * time.Hour).Format(time.RFC3339)
```

This works but doesn't test the expiration boundary precisely.

## Recommendation

Add explicit unit tests for `IsExpiredTombstone()`:

```go
func TestIsExpiredTombstone(t *testing.T) {
    tests := []struct {
        name      string
        status    string
        deletedAt string
        ttl       time.Duration
        expected  bool
    }{
        {"non-tombstone returns false", "open", "2024-01-01T00:00:00Z", 0, false},
        {"empty deleted_at returns false", "tombstone", "", 0, false},
        {"invalid timestamp returns false", "tombstone", "not-a-date", 0, false},
        {"within TTL returns false", "tombstone", time.Now().Add(-1*time.Hour).Format(time.RFC3339), 24*time.Hour, false},
        {"beyond TTL returns true", "tombstone", time.Now().Add(-48*time.Hour).Format(time.RFC3339), 24*time.Hour, true},
    }
    // ...
}
```

## Files
- internal/merge/merge.go:255-290
- internal/merge/merge_test.go (add TestIsExpiredTombstone)


Document bd edit command and verify MCP exclusion

Follow-up from PR #152:
1. Add "bd edit" to AGENTS.md with "Humans only" note
2. Verify MCP server doesn't expose bd edit command
3. Consider adding test for command registration


VCS-agnostic sync support

Make bd sync work with multiple VCS types (git, jujutsu, mercurial, sapling) by detecting VCS per repo and using appropriate sync commands, supporting mixed-VCS multi-repo configs.


Tombstone fields not merged in mergeIssue function

The mergeIssue() function in merge.go does not handle tombstone fields (DeletedAt, DeletedBy, DeleteReason, OriginalType) when merging live issues. While tombstones are handled separately in merge3WayWithTTL(), if a tombstone ever reaches mergeIssue() via the mergeStatus safety fallback, the tombstone fields would be lost. The mergeIssue() function should either: 1) Copy tombstone fields when result.Status becomes tombstone, or 2) Assert that tombstones never reach mergeIssue() (defensive check). Files: internal/merge/merge.go:458-499


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


Issue 1 to reopen


Test child issue