Graph Link: duplicates for deduplication

Implement duplicates link type for marking issues as duplicates.

New command:
- bd duplicate <id> --of <canonical> - marks id as duplicate of canonical
- Auto-closes the duplicate issue

Query support:
- bd show <id> shows 'Duplicate of: <canonical>'
- bd list --duplicates shows all duplicate pairs

Storage:
- duplicates column pointing to canonical issue ID

Essential for large issue databases with many similar reports.


Git history fallback assumes .beads is direct child of repo root

## Problem

`checkGitHistoryForDeletions` assumes the repo structure:

```go
repoRoot := filepath.Dir(beadsDir)  // Assumes .beads is in repo root
jsonlPath := filepath.Join(".beads", "beads.jsonl")
```

But `.beads` could be in a subdirectory (monorepo, nested project), and the actual JSONL filename could be different (configured via `metadata.json`).

## Location
`internal/importer/importer.go:865-869`

## Impact
- Git search will fail silently for repos with non-standard structure
- Monorepo users won't get deletion propagation

## Fix
1. Use `git rev-parse --show-toplevel` to find actual repo root
2. Compute relative path from repo root to JSONL
3. Or use `git -C <dir>` to run from beadsDir directly


Code Health Review Dec 2025: Technical Debt Cleanup

Epic grouping technical debt identified in the Dec 16, 2025 code health review.

## Overall Health Grade: B (Solid foundation, needs cleanup)

### P1 (High Priority):
- bd-74w1: Consolidate duplicate path-finding utilities
- bd-b6xo: Remove/fix ClearDirtyIssues() race condition
- bd-b3og: Fix TestImportBugIntegration deadlock

### P2 (Medium Priority):
- bd-05a8: Split large files (doctor.go, sync.go)
- bd-qioh: Standardize error handling patterns
- bd-rgyd: Split queries.go (1586 lines)
- bd-9g1z: Fix/remove TestFindJSONLPathDefault

### P3 (Low Priority):
- bd-ork0: Add comments to 30+ ignored errors
- bd-4nqq: Remove dead test code in info_test.go
- bd-dhza: Reduce global state in main.go

## Key Areas:
1. Code duplication in path utilities
2. Large monolithic files (5 files >1000 lines)
3. Global state (25+ variables, 3 deprecated)
4. Silent error suppression (30+ instances)
5. Test gaps and dead test code
6. Atomicity risks in batch operations


Add tests for 3-way merge scenarios

Comprehensive test coverage for merge logic.

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

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


SearchIssues N+1 query causes context timeout with GetLabels

scanIssues() calls GetLabels in a loop for every issue, causing N+1 queries and context deadline exceeded errors when used with short timeouts or in-memory databases. This is especially problematic since SearchIssues already supports label filtering via SQL WHERE clauses.


Split test suites: fast vs. integration

Reorganize tests into separate packages/files for fast unit tests vs slow integration tests.

Benefits:
- Clear separation of concerns
- Easier to run just fast tests during development
- Can parallelize CI jobs better

Files to organize:
- beads_hash_multiclone_test.go (slow integration tests)
- beads_integration_test.go (medium-speed integration tests)
- Other test files (fast unit tests)


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


bd init creates nested .beads directories

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


Test dependency parent


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.


Add orphan detection migration

Create migration to detect orphaned children in existing databases. Query: SELECT id FROM issues WHERE id LIKE '%.%' AND substr(id, 1, instr(id || '.', '.') - 1) NOT IN (SELECT id FROM issues). Log results, let user decide action (delete orphans or convert to top-level).


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.


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

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

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

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

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

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


Q4 Platform Improvements

## Overview

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

## Success Criteria

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

## Background

[Provide context and motivation]

## Scope

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

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


bd cleanup fails with CHECK constraint on status/closed_at mismatch

## Problem

Running `bd cleanup --force` fails with:

```
Error: failed to create tombstone for bd-okh: sqlite3: constraint failed: CHECK constraint failed: (status = 'closed') = (closed_at IS NOT NULL)
```

## Root Cause

The database has a CHECK constraint ensuring closed issues have `closed_at` set and non-closed issues don't. When `bd cleanup` tries to convert a closed issue to a tombstone, this constraint fails if:
1. The issue has status='closed' but closed_at is NULL, OR
2. The issue is being converted but the tombstone creation doesn't properly handle the closed_at field

## Impact

- `bd cleanup --force` cannot complete
- 722 closed issues cannot be cleaned up
- Blocks routine maintenance

## Investigation Needed

1. Check the bd-okh record in the database:
   ```sql
   SELECT id, status, closed_at, deleted_at FROM issues WHERE id = 'bd-okh';
   ```
2. Determine if this is data corruption or a bug in tombstone creation

## Proposed Solutions

1. **If data corruption**: Add a `bd doctor --fix` check that repairs status/closed_at mismatches
2. **If code bug**: Fix the tombstone creation to properly handle the constraint

## Files to Investigate

- `internal/storage/sqlite/sqlite.go` - DeleteIssue / tombstone creation
- Schema CHECK constraint definition

## Acceptance Criteria

- [ ] Identify root cause (data vs code bug)
- [ ] `bd cleanup --force` completes successfully
- [ ] `bd doctor` detects status/closed_at mismatches


Add t.Parallel() to slow hash multiclone tests

Add t.Parallel() to TestHashIDs_MultiCloneConverge and TestHashIDs_IdenticalContentDedup so they run concurrently.

Expected savings: ~10 seconds (from 20s to ~11s)

Implementation:
- Add t.Parallel() call at start of each test function
- Verify tests don't share resources that would cause conflicts
- Run tests to confirm they work in parallel

File: beads_hash_multiclone_test.go:34, :101


Update bash-agent example with Agent Mail integration

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

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

File: examples/bash-agent/agent.sh


GH#509: bd fails to find .beads from nested worktrees

findDatabaseInTree stops at worktree git root, missing .beads in parent repo. Should check git-common-dir and search past worktree root. See: https://github.com/steveyegge/beads/issues/509


Debug MCP Agent Mail tool execution errors

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

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

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

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

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

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

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


Add internal/ai package for LLM integration

Shared AI client for repair commands.

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

Env vars:
- BEADS_AI_PROVIDER
- BEADS_AI_API_KEY
- BEADS_AI_MODEL

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


bd info whats-new missing releases

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


Fix bd sync race condition with daemon causing dirty working directory

After bd sync completes with sync.branch mode, subsequent bd commands or daemon file watcher would see a hash mismatch and trigger auto-import, which then schedules re-export, dirtying the working directory.

**Root cause:**
1. bd sync exports JSONL with NEW content (hash H1)
2. bd sync updates jsonl_content_hash = H1 in DB
3. bd sync restores JSONL from HEAD (OLD content, hash H0)
4. Now: file hash = H0, DB hash = H1 (MISMATCH)
5. Daemon or next CLI command sees mismatch, imports from OLD JSONL
6. Import triggers re-export → file is dirty

**Fix:**
After restoreBeadsDirFromBranch(), update jsonl_content_hash to match the restored file's hash. This ensures daemon and CLI see file hash = DB hash → no spurious import/export cycle.

Related: bd-c83r (multiple daemon prevention)


False positive detection by Kaspersky Antivirus (Trojan)

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


BG's issue to reopen


Create shared insert/event/dirty helpers

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


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


Remove Cache Configuration Docs

Remove documentation of deprecated cache env vars


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 bd daemon --stop-all command to kill all daemon processes

Currently there's no easy way to stop all running bd daemon processes. Users must resort to pkill -f 'bd daemon' or similar shell commands.

Add a --stop-all flag to bd daemon that:
1. Finds all running bd daemon processes (not just the current repo's daemon)
2. Gracefully stops them all
3. Reports how many were stopped

This is useful when:
- Multiple daemons are running and causing race conditions
- User wants a clean slate before running bd sync
- Debugging daemon-related issues


Add bd deleted command for audit trail

Parent: bd-imj

Add command to view deletion history.

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

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

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


Extract batch operations to batch_ops.go

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


Schema: Add message type and new fields

Add to internal/storage/sqlite/schema.go and models:

New issue_type value:
- message

New optional fields on Issue struct:
- Sender string (who sent this)
- Ephemeral bool (can be bulk-deleted)
- RepliesTo string (issue ID for threading)
- RelatesTo []string (issue IDs for knowledge graph)
- Duplicates string (canonical issue ID)
- SupersededBy string (replacement issue ID)

Update:
- internal/storage/sqlite/schema.go - add columns
- internal/models/issue.go - add fields to struct
- internal/storage/sqlite/sqlite.go - CRUD operations
- Create migration from v0.30.1

Ensure backward compatibility - all new fields optional.


bd-hv01: Storage backend extensibility broken by type assertion

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

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

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


Add comprehensive filters to bd export

Enhance bd export with filtering options for selective exports.

**Currently only has:**
- --status

**Add filters:**
- --label, --label-any
- --assignee
- --type
- --priority, --priority-min, --priority-max
- --created-after, --created-before
- --updated-after, --updated-before

**Use case:**
- Export only open issues: bd export --status open
- Export high-priority bugs: bd export --type bug --priority-max 1
- Export recent issues: bd export --created-after 2025-01-01

**Files to modify:**
- cmd/bd/export.go
- Reuse filter logic from list.go


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


GH#483: Pre-commit hook fails unnecessarily when .beads removed

Pre-commit hook fails on bd sync when .beads directory exists but user is on branch without beads. Should exit gracefully. See GitHub issue #483.


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)


Fix transaction conflict in TryResurrectParent

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

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

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

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

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


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

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

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

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

Commit: d46177d


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

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

Current coverage: 28.4%
Target coverage: 50%


Documentation for messaging and graph links

Document all new features.

Files to update:
- README.md - brief mention of messaging capability
- AGENTS.md - update for AI agents using bd mail
- docs/messaging.md (new) - full messaging reference
- docs/graph-links.md (new) - graph link reference
- CHANGELOG.md - v0.30.2 release notes

Topics to cover:
- Mail commands with examples
- Graph link types and use cases
- Identity configuration
- Hooks setup for notifications
- Migration notes


Issue to reopen with reason


Add template support for issue creation

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

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

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

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


Channel isolation test - beads

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


Create Python adapter library

Create beads_mail_adapter.py library that wraps Agent Mail HTTP calls with health checks and graceful degradation.

Acceptance Criteria:
- AgentMailAdapter class with health check on init
- enabled flag auto-disables if server unreachable
- All methods wrapped in try/catch (non-blocking failures)
- Methods: reserve_issue(), release_issue(), notify(), check_inbox()
- Environment-based configuration (AGENT_MAIL_URL, AGENT_MAIL_TOKEN)
- Unit tests for enabled/disabled modes

File: lib/beads_mail_adapter.py


Optimize test suite performance - cut runtime by 50%+

## Problem
Test suite takes ~20.8 seconds, with 95% of time spent in just 2 tests:
- TestHashIDs_MultiCloneConverge: 11.08s (53%)
- TestHashIDs_IdenticalContentDedup: 8.78s (42%)

Both tests in beads_hash_multiclone_test.go perform extensive Git operations (bare repos, multiple clones, sync rounds).

## Goal
Cut total test time by at least 50% (to ~10 seconds or less).

## Analysis
Tests already have some optimizations:
- --shared --depth=1 --no-tags for fast cloning
- Disabled hooks, gc, fsync
- Support -short flag

## Impact
- Faster development feedback loop
- Reduced CI costs and time
- Better developer experience


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


Add performance benchmarks for multi-repo hydration

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

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

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

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

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


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

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

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

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

## Root Cause Analysis

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

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

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

## Solution Implemented

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

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

## File Path Handling

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


Extract daemon server functions to daemon_server.go


bd show --json doesn't include dependency type field

Fix GitHub issue #202. The JSON output from bd show and bd list commands should include the dependency type field (and optionally created_at, created_by) to match internal storage format and enable better tooling integration.

PR #203 updated with cleaner implementation: https://github.com/steveyegge/beads/pull/203

## Final Implementation

Cleanest possible approach - no internal helper methods needed:

**Design:**
- `GetDependenciesWithMetadata()` / `GetDependentsWithMetadata()` - canonical implementations with full SQL query
- `GetDependencies()` / `GetDependents()` - thin wrappers that strip metadata for backward compat
- `scanIssuesWithDependencyType()` - shared helper for scanning rows with dependency type

**Benefits:**
- Single source of truth - the `...WithMetadata()` methods ARE the implementation
- Eliminated ~139 lines of duplicated SQL and scanning code
- All tests passing (14 dependency-related tests)
- Backward compatible
- dependency_type field appears correctly in JSON output

**Note on scan helpers:**
The duplication between `scanIssues()` and `scanIssuesWithDependencyType()` is necessary because they handle different SQL result shapes (16 vs 17 columns). This is justified as they serve fundamentally different purposes based on query structure.


Optimize GetReadyWork performance - 752ms on 10K database (target: <50ms)

# Performance Analysis (10K Issue Database)

Analyzed using CPU profiles from benchmark suite on Apple M2 Pro.

## Operation Performance

| Operation                        | Time    | Allocations | Memory |
|----------------------------------|---------|-------------|--------|
| bd ready (GetReadyWork)          | ~752ms  | 167,466     | 16MB   |
| bd list (SearchIssues no filter) | ~11.6ms | 89,214      | 5.8MB  |
| bd list (SearchIssues filtered)  | ~9.2ms  | 62,365      | 3.5MB  |
| bd create (CreateIssue)          | ~2.6ms  | 146         | 8.6KB  |
| bd update (UpdateIssue)          | ~0.32ms | 364         | 15KB   |
| bd close (UpdateIssue)           | ~0.32ms | 364         | 15KB   |

**Target: <50ms for all operations on 10K database**

**Current issue: GetReadyWork is 15x over target (752ms vs 50ms)**

## Root Cause

GetReadyWork (internal/storage/sqlite/ready.go:90-128) uses recursive CTE to propagate blocking:
- 65x slower than SearchIssues
- Recalculates entire blocked issue tree on every call
- Algorithm:
  1. Find directly blocked issues via 'blocks' dependencies
  2. Recursively propagate blockage to descendants (max depth: 50)
  3. Exclude all blocked issues from results

## CPU Profile Analysis

- Database syscalls (pthread_cond_signal, syscall6): ~75%
- SQLite engine overhead: inherent to recursive CTE
- Application code (query construction): <1%

**Bottleneck is the recursive CTE query execution, not application code.**

## Optimization Recommendations

### High Impact (Likely to achieve <50ms target)

1. **Cache blocked issue calculation**
   - Add `blocked_issues` table updated on dependency changes
   - Trade write complexity for read speed (ready called >> dependency changes)
   - Eliminates recursive CTE on every read

2. **Add/verify database indexes**
   ```sql
   CREATE INDEX IF NOT EXISTS idx_dependencies_blocked
     ON dependencies(issue_id, type, depends_on_id);
   CREATE INDEX IF NOT EXISTS idx_issues_status
     ON issues(status);
   ```

### Medium Impact

3. **Reduce allocations** (167K allocations for GetReadyWork)
   - Profile `scanIssues()` for object pooling opportunities
   - Reuse slice capacity for repeated calls

### Low Impact (Not recommended)
- Query optimization for CRUD operations (already <3ms)
- Connection pooling tuning (not showing in profiles)

## Verification

Run benchmarks to validate optimization:
```bash
make bench-quick
go tool pprof -http=:8080 internal/storage/sqlite/bench-cpu-*.prof
```

Profile files automatically generated in `internal/storage/sqlite/`.


bd message: Add input validation for --importance flag

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

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

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

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


troubleshoot GH#278 daemon exits every few secs


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


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

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


Investigate jujutsu integration for beads

Research and document how beads could integrate with jujutsu (jj), the next-generation VCS. Key areas to explore:
- How jj's operation model differs from git (immutable operations, working-copy-as-commit)
- JSONL sync strategy with jj's conflict resolution model
- Daemon compatibility with jj's more frequent rewrites
- Whether auto-import/export needs changes for jj workflows
- Example configurations and documentation updates needed


bd init should stop running daemon to avoid stale cache

When running bd init, any running daemon continues with stale cached data, causing bd stats and other commands to show old counts.

Repro:
1. Have daemon running with 788 issues cached
2. Clean JSONL to 128 issues, delete db, run bd init
3. bd stats still shows 788 (daemon cache)
4. Must manually run bd daemon --stop

Fix: bd init should automatically stop any running daemon before reinitializing.


GH#523: Fix closed issues missing closed_at timestamp during db upgrade

Old beads databases may have closed issues without closed_at timestamps, causing 'closed issues must have closed_at timestamp' validation errors on upgrade to 0.29.0. Need defensive handling during import. See: https://github.com/steveyegge/beads/issues/523


Code Review: PR #551 - Persist close_reason to issues table

Code review of PR #551 which fixes close_reason persistence bug.

## Summary
The PR correctly fixes a bug where close_reason was only stored in the events table, not in the issues.close_reason column. This caused `bd show --json` to return empty close_reason.

## What Was Fixed
- ✅ CloseIssue now updates both close_reason and closed_at
- ✅ ReOpenIssue clears both close_reason and closed_at
- ✅ Comprehensive tests added for both storage and CLI layers
- ✅ Clear documentation in queries.go about dual storage strategy

## Quality Assessment
✅ Tests cover both storage layer and CLI JSON output
✅ Handles reopen case (clearing close_reason)
✅ Good comments explaining dual-storage design
✅ No known issues

## Potential Followups
See linked issues for suggestions.


Reduce global state in cmd/bd/main.go (25+ variables)

Code health review found main.go has 25+ global variables (lines 57-112):

- dbPath, actor, store, jsonOutput, daemonClient, noDaemon
- rootCtx, rootCancel, autoFlushEnabled
- isDirty (marked 'USED BY LEGACY CODE')
- needsFullExport (marked 'USED BY LEGACY CODE')
- flushTimer (marked 'DEPRECATED')
- flushMutex, storeMutex, storeActive
- flushFailureCount, lastFlushError, flushManager
- skipFinalFlush, autoImportEnabled
- versionUpgradeDetected, previousVersion, upgradeAcknowledged

Impact:
- Hard to test individual commands
- Race conditions possible
- State leakage between commands

Fix: Move toward dependency injection. Remove deprecated variables. Consider cmd/bd/internal package.


Implement auto-merge functionality in duplicates command

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


Improve sync branch divergence recovery UX

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

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

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

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

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


Split large cmd/bd files: doctor.go (2948 lines), sync.go (2121 lines)

Code health review found several oversized files:

1. doctor.go - 2948 lines, 48 functions mixed together
   - Should split into doctor/checks/*.go for individual diagnostics
   - applyFixes() and previewFixes() are nearly identical

2. sync.go - 2121 lines
   - ZFC (Zero Flush Check) logic embedded inline (lines 213-247)
   - Multiple mode handlers should be extracted

3. init.go - 1732 lines
4. compact.go - 1097 lines
5. show.go - 1069 lines

Recommendation: Extract into focused sub-packages or split into logical files.


TestHooksCheckGitHooks failing - version mismatch (0.23.0 vs 0.23.1)

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

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

This is blocking the landing of GH #274 fix.


bd doctor should recommend bd prime migration for existing repos

bd doctor should detect old beads integration patterns and recommend migrating to bd prime approach.

## Current behavior
- bd doctor checks if Claude hooks are installed globally
- Doesn't check project-level integration (AGENTS.md, CLAUDE.md)
- Doesn't recommend migration for repos using old patterns

## Desired behavior
bd doctor should detect and suggest:

1. **Old slash command pattern detected**
   - Check for /beads:* references in AGENTS.md, CLAUDE.md
   - Suggest: These slash commands are deprecated, use bd prime hooks instead

2. **No agent documentation**
   - Check if AGENTS.md or CLAUDE.md exists
   - Suggest: Run 'bd onboard' or 'bd setup claude' to document workflow

3. **Old MCP-only pattern**
   - Check for instructions to use MCP tools but no bd prime hooks
   - Suggest: Add bd prime hooks for better token efficiency

4. **Migration path**
   - Show: 'Run bd setup claude to add SessionStart/PreCompact hooks'
   - Show: 'Update AGENTS.md to reference bd prime instead of slash commands'

## Example output

⚠ Warning: Old beads integration detected in CLAUDE.md
  Found: /beads:* slash command references (deprecated)
  Recommend: Migrate to bd prime hooks for better token efficiency
  Fix: Run 'bd setup claude' and update CLAUDE.md

💡 Tip: bd prime + hooks reduces token usage by 80-99% vs slash commands
  MCP mode: ~50 tokens vs ~10.5k for full MCP scan
  CLI mode: ~1-2k tokens with automatic context recovery

## Benefits
- Helps existing repos adopt new best practices
- Clear migration path for users
- Better token efficiency messaging


Prevent test pollution in production database

The bd-vxdr cleanup revealed test issues were created during manual testing in the production workspace (Nov 2-4, template feature development).

**Root cause:** Manual testing with `./bd create "Test issue"` pollutes the production .beads database.

**Prevention strategies:**
1. Use TEST_DB environment variable for manual testing
2. Add warning when creating issues with "Test" prefix
3. Improve developer docs about testing workflow
4. Consider adding `bd test-mode` command for isolated testing

**Implementation completed:**

1. ✅ Added warning when creating issues with "Test" prefix in production database
   - Shows yellow warning with ⚠ symbol
   - Suggests using BEADS_DB for isolated testing
   - Warning appears in create.go after title validation

2. ✅ Documented BEADS_DB testing workflow in AGENTS.md
   - Added "Testing Workflow" section in Development Guidelines
   - Includes manual testing examples with BEADS_DB
   - Includes automated testing examples with t.TempDir()
   - Clear warning about not polluting production database

3. ⚠ Decided against bd test-mode command
   - BEADS_DB already provides simple, flexible isolation
   - Additional command would add complexity without much benefit
   - Current approach follows Unix philosophy (env vars for config)

**Files modified:**
- cmd/bd/create.go - Added Test prefix warning
- AGENTS.md - Added Testing Workflow section

**Testing:**
- Verified warning appears when creating "Test" prefix issues
- Verified BEADS_DB isolation works correctly
- Built successfully with `go build`


Remove obsolete stale.go command (executor tables never implemented)


Beads commands operate on stale database without warning

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

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

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

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

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

## Implementation Complete

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

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

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

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

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


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


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.


Enhance `bd doctor` to verify Claude Code integration

Add checks to `bd doctor` that verify Claude Code integration is properly set up when .claude/ directory or Claude environment is detected.


Rapid 3


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

```


Consolidate ID generation and validation into ids.go

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


bd repair-deps - Orphaned dependency cleaner

Find and fix orphaned dependency references.

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

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


Create npm package structure for bd-wasm

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


Test update


Manual task


Blocking issue


Refactor sqlite.go into focused modules

Split sqlite.go (2,298 lines) into focused modules: migrations.go, ids.go, issues.go, events.go, dirty.go, db.go. This will improve maintainability and reduce cognitive load.


Hash-based IDs with aliasing system

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

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

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

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

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

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

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

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


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

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

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

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

## Proposed Architecture

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

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

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

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

## Implementation Phases

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

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

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

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

## Risks & Mitigations

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

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

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

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

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

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

## References
- Automerge: https://automerge.org
- Yjs: https://docs.yjs.dev
- Loro: https://loro.dev
- CRDT theory: Shapiro et al, A comprehensive study of CRDTs
- Related issues: bd-0dcea000, bd-4d7fca8a, bd-6221bdcd


bd message: Refactor duplicated error messages


Add bd doctor --fix flag to automatically repair issues

Implement a --fix flag for bd doctor that can automatically repair detected issues.

Requirements:
- Add --fix flag to bd doctor command
- Show all fixable issues and prompt for confirmation before applying fixes
- Organize fix implementations under doctor/fix/<type_of_fix>.go
- Each fix type should have its own file (e.g., doctor/fix/hooks.go, doctor/fix/sync.go)
- Display what will be fixed and ask user to confirm (Y/n) before proceeding
- Support fixing issues like:
  - Missing or broken git hooks
  - Sync problems with remote
  - File permission issues
  - Any other auto-repairable issues doctor detects

Implementation notes:
- Maintain separation between detection (existing doctor code) and repair (new fix code)
- Each fix should be idempotent and safe to run multiple times
- Provide clear output about what was fixed
- Log any fixes that fail with actionable error messages


Update AGENTS.md with event-driven mode

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


Identity Configuration

Implement identity system for sender field.

Configuration sources (in priority order):
1. --identity flag on commands
2. BEADS_IDENTITY environment variable
3. .beads/config.json: {"identity": "worker-name"}
4. Default: git user.name or hostname

New config file support:
- .beads/config.json for per-repo settings
- identity field for messaging

Helper function:
- GetIdentity() string - resolves identity from sources

Update bd mail send to use GetIdentity() for sender field.


Update docs for import bug fix

Update AGENTS.md, README.md, TROUBLESHOOTING.md with import.orphan_handling config documentation. Document resurrection behavior, tombstones, config modes. Add troubleshooting section for import failures with deleted parents.


Sync validation false positive: legitimate deletions trigger 'data loss detected'

## Problem
`bd sync` fails with false positive data loss detection when legitimate deletions occur:
```
Post-import validation failed: import reduced issue count: 26 → 25 (data loss detected!)
```

## Root Cause
The validation in `sync.go:329-340` counts DB issues BEFORE import, but `purgeDeletedIssues()` in `importer.go:159` legitimately removes issues DURING import. The validation doesn't account for expected deletions.

**The Flow:**
```
sync.go:293     → beforeCount = countDBIssues() = 26
sync.go:310-319 → sanitizeJSONL removes deleted issues from JSONL (RemovedCount=N)
sync.go:323     → importFromJSONL() runs subprocess
                  └→ importer.go:159 purgeDeletedIssues() removes issues from DB
sync.go:331     → afterCount = countDBIssues() = 25
sync.go:335     → validatePostImport(26, 25) → ERROR
```

## Fix
Pass `sanitizeResult.RemovedCount` to validation and account for expected deletions:

```go
// sync.go around line 335
expectedDecrease := 0
if sanitizeResult != nil {
    expectedDecrease = sanitizeResult.RemovedCount
}
if err := validatePostImportWithDeletions(beforeCount, afterCount, expectedDecrease); err != nil {
    // ...
}
```

```go
// integrity.go - new or modified function
func validatePostImportWithDeletions(before, after, expectedDeletions int) error {
    if after < before - expectedDeletions {
        return fmt.Errorf("unexpected data loss: %d → %d (expected max decrease: %d)",
            before, after, expectedDeletions)
    }
    // ...
}
```

## Files
- cmd/bd/sync.go:329-340
- cmd/bd/integrity.go:289-301


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


Review multi-repo deletion tracking implementation

Thoroughly review the multi-repo deletion tracking fix (bd-4oob):

Files changed:
- cmd/bd/deletion_tracking.go: Added getMultiRepoJSONLPaths() helper
- cmd/bd/daemon_sync.go: Updated snapshot capture/update logic for multi-repo
- cmd/bd/deletion_tracking_test.go: Added 2 new tests (287 lines)

Review focus areas:
1. Correctness: Does getMultiRepoJSONLPaths() handle all edge cases?
2. Performance: Calling getMultiRepoJSONLPaths() 3x per sync (snapshot capture, merge, base update) - should we cache?
3. Error handling: What if some repos fail snapshot operations but others succeed?
4. Race conditions: Multiple daemons in different repos?
5. Test coverage: Are TestMultiRepoDeletionTracking and TestMultiRepoSnapshotIsolation sufficient?
6. Path handling: Absolute vs relative paths, tilde expansion

This is fresh code - needs careful review before considering deletion tracking production-ready.

Code review completed. Overall assessment: Core deletion tracking logic is sound, but error handling and path handling issues make this not yet production-ready for multi-repo scenarios.

Key findings:

CRITICAL ISSUES (Priority 1):
1. Inconsistent error handling in daemon_sync.go - snapshot/merge fail hard but base update warns. Can leave DB in inconsistent state with no rollback. See bd-sjmr.
2. No path normalization in getMultiRepoJSONLPaths() - tilde expansion, relative paths, duplicates not handled. See bd-iye7.

SHOULD FIX (Priority 2):
3. Missing test coverage for edge cases - empty paths, duplicates, partial failures. See bd-kdoh.
4. Performance - getMultiRepoJSONLPaths() called 3x per sync (minor issue). See bd-we4p.

WHAT WORKS WELL:
- Atomic file operations with PID-based temp files
- Good snapshot isolation between repos
- Race condition protection via exclusive locks
- Solid test coverage for happy path scenarios

VERDICT: Address bd-iye7 and bd-sjmr before considering deletion tracking production-ready for multi-repo mode.

Detailed review notes available in conversation history.


Add 'bd message send' command for Agent Mail messaging

Agent Mail server supports messaging between agents, but bd CLI only uses it for file reservations. Add commands for inter-agent messaging.

## Background
- Agent Mail server running at http://127.0.0.1:8765
- 12 workspaces configured across 3 channels (beads.dev, vc.dev, wyvern.dev)
- Current integration: file reservations only
- Gap: no way to send messages from bd CLI

## Proposed Commands

```bash
# Send message to another agent
bd message send <to-agent> <message> [options]
  --subject <subject>
  --thread-id <thread-id>     # Optional - group related messages
  --project-id <project>      # Defaults to BEADS_PROJECT_ID

# List inbox messages
bd message inbox [options]
  --limit <N>
  --unread-only

# Read specific message
bd message read <message-id>

# Mark message as acknowledged
bd message ack <message-id>
```

## Example Usage

```bash
# Send message to agent in same channel
bd message send cino-beads-stevey-macbook "Working on bd-z0yn, need your review" \
  --subject "Review request" \
  --thread-id bd-z0yn

# Check inbox
bd message inbox --unread-only

# Read and acknowledge
bd message read msg-abc123
bd message ack msg-abc123
```

## Design Notes
- Use same env vars (BEADS_AGENT_MAIL_URL, BEADS_AGENT_NAME, BEADS_PROJECT_ID)
- Graceful degradation if Agent Mail unavailable
- JSON output support for all commands
- Consider integrating with bd update/close (auto-notify on status changes)

## References
- Agent Mail README: ~/src/mcp_agent_mail/README.md
- Beads integration docs: docs/AGENT_MAIL.md

## Implementation Summary

Added four new commands to bd CLI for Agent Mail messaging:

1. `bd message send <to-agent> <message>` - Send messages to other agents
   - Flags: --subject, --thread-id, --importance, --ack-required
   - Supports markdown content
   - Thread conversations by issue ID

2. `bd message inbox` - List inbox messages
   - Flags: --limit, --unread-only, --urgent-only, --json
   - Shows subject, sender, age, importance
   - Highlights unread and ACK-required messages

3. `bd message read <message-id>` - Read and mark message as read
   - Automatically marks message as read
   - Shows message content

4. `bd message ack <message-id>` - Acknowledge a message
   - Marks message as acknowledged
   - Also marks as read if not already

## Implementation Details

- Uses JSON-RPC over HTTP to communicate with Agent Mail server
- Configuration via environment variables (BEADS_AGENT_MAIL_URL, BEADS_AGENT_NAME, BEADS_PROJECT_ID)
- Graceful error messages when Agent Mail not configured
- Full JSON output support for programmatic use
- Follows same patterns as existing bd commands

## Documentation

Updated:
- docs/AGENT_MAIL.md - Added "Messaging Commands" section with examples and best practices
- README.md - Added "Messaging (Agent Mail)" section in Usage

## Testing

- Compiles successfully
- Help output works correctly
- Ready for integration testing with Agent Mail server


UX nightmare: multiple ways daemon can fail with misleading messages


Add MCP server functions for repair commands

**Summary:** Added MCP server repair functions for agent dependency management, system validation, and pollution detection. Implemented across BdClientBase, BdCliClient, and daemon clients to enhance system diagnostics and self-healing capabilities.

**Key Decisions:**
- Expose repair_deps(), detect_pollution(), validate() via MCP server
- Create abstract method stubs with fallback to CLI execution
- Use @mcp.tool decorators for function registration

**Resolution:** Successfully implemented comprehensive repair command infrastructure, enabling more robust system health monitoring and automated remediation with full CLI and daemon support.

Implemented all three MCP server functions:

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

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

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


Document sandbox workarounds for GH #353

Add documentation for sandbox troubleshooting and new flags.

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

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

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

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


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

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

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

See repair_commands.md lines 125-353 for design.


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

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

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

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

All fixes merged. Ready for v0.22.2 release.


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.


Use external_ref as primary matching key for import updates

Enable re-syncing from external systems (Jira, GitHub, Linear) by using external_ref as the primary matching key during imports. Currently imports treat any content change as a collision, making it impossible to sync updates from external systems without creating duplicates.

See GH #142 for detailed proposal and implementation plan.

Key changes needed:
1. Add findByExternalRef() query function
2. Update DetectCollisions() to match by external_ref first
3. Update import_shared.go to update existing issues when external_ref matches
4. Add index on external_ref for performance
5. Preserve local issues (no external_ref) from being overwritten

This enables hybrid workflows: import external backlog, break down with local tasks, re-sync anytime.

## Code Review Complete ✅

**Overall Assessment**: EXCELLENT - Production ready

### Implementation Quality
- ✓ Clean architecture with proper interface extension
- ✓ Dual backend support (SQLite + Memory)
- ✓ Smart matching priority: external_ref → ID → content hash
- ✓ O(1) lookups with database index
- ✓ Timestamp-based conflict resolution
- ✓ Comprehensive test coverage (11 test cases)

### Follow-up Issues Filed
High Priority (P2):
- bd-897a: Add UNIQUE constraint on external_ref column
- bd-7315: Add validation for duplicate external_ref in batch imports

Medium Priority (P3):
- bd-f9a1: Add index usage verification test
- bd-3f6a: Add concurrent import race condition tests

Low Priority (P4):
- bd-e166: Improve timestamp comparison readability
- bd-9e23: Optimize Memory backend with index
- bd-537e: Add external_ref change tracking
- bd-df11: Add import metrics
- bd-9f4a: Document external_ref in content hash

### Key Features
✅ External systems (Jira, GitHub, Linear) can re-sync without duplicates
✅ Hybrid workflows: import external backlog, add local tasks, re-sync anytime
✅ Local issues protected from being overwritten
✅ Timestamp checking ensures only newer updates applied
✅ Performance optimized with database index

**Confidence Level**: 95% - Ship it! 🚀


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%


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

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

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

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

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

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

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


Beads Messaging & Knowledge Graph (v0.30.2)

Add messaging semantics and extended graph links to Beads, enabling it to serve as
the universal substrate for knowledge work - issues, messages, documents, and threads
as nodes in a queryable graph.

## Motivation

Gas Town (GGT) needs inter-agent communication. Rather than a separate mail system,
collapse messaging into Beads - one system, one sync, one query interface, all in git.

This also positions Beads as a foundation for:
- Company-wide issue tracking (like Notion)
- Threaded conversations (like Reddit/Slack)
- Knowledge graphs with loose associations
- Arbitrary workflow UIs built on top

## New Issue Type

**message** - ephemeral communication between workers
- sender: who sent it
- assignee: recipient
- priority: P0 (urgent) to P4 (routine)
- status: open (unread) -> closed (read)
- ephemeral: true = can be bulk-deleted after swarm

## New Graph Links

**replies_to** - conversation threading
- Messages reply to messages
- Enables Reddit-style nested threads
- Different from parent_id (not hierarchy, its conversation flow)

**relates_to** - loose see also associations
- Bidirectional knowledge graph edges
- Not blocking, not hierarchical, just related
- Enables discovery and traversal

**duplicates** - deduplication at scale
- Mark issue B as duplicate of canonical issue A
- Close B, link to A
- Essential for large issue databases

**supersedes** - version chains
- Design Doc v2 supersedes Design Doc v1
- Track evolution of artifacts

## New Fields (optional, any issue type)

- sender (string) - who created this (for messages)
- ephemeral (boolean) - can be bulk-deleted when closed

## New Commands

Messaging:
- bd mail send <recipient> -s Subject -m Body
- bd mail inbox (list open messages for me)
- bd mail read <id> (show message content)
- bd mail ack <id> (mark as read/close)
- bd mail reply <id> -m Response (reply to thread)

Graph links:
- bd relate <id1> <id2> (create relates_to link)
- bd duplicate <id> --of <canonical> (mark as duplicate)
- bd supersede <id> --with <new> (mark superseded)

Cleanup:
- bd cleanup --ephemeral (delete closed ephemeral issues)

## Identity Configuration

Workers need identity for sender field:
- BEADS_IDENTITY env var
- Or .beads/config.json: identity field

## Hooks (for GGT integration)

Beads as platform - extensible without knowing about GGT.
Hook files in .beads/hooks/:
- on_create (runs after bd create)
- on_update (runs after bd update)
- on_close (runs after bd close)
- on_message (runs after bd mail send)

GGT registers hooks to notify daemons of new messages.

## Schema Changes (Migration Required)

Add to issue schema:
- type: message (new valid type)
- sender: string (optional)
- ephemeral: boolean (optional)
- replies_to: string (issue ID, optional)
- relates_to: []string (issue IDs, optional)
- duplicates: string (canonical issue ID, optional)
- superseded_by: string (new issue ID, optional)

Migration adds fields as optional - existing beads unchanged.

## Success Criteria

1. bd mail send/inbox/read/ack/reply work end-to-end
2. replies_to creates proper thread structure
3. relates_to, duplicates, supersedes links queryable
4. Hooks fire on create/update/close/message
5. Identity configurable via env or config
6. Migration preserves all existing data
7. All new features have tests


Add integration tests for multi-repo sync

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


test hash length


Add MCP functions for repair commands

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

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


bd init should refuse when JSONL already has issues (safety guard)

When running `bd init` in a directory with an existing JSONL containing issues, bd should refuse and suggest the correct action instead of proceeding.

## The Problem

Current behavior when database is missing but JSONL exists:
```
$ bd create "test"
Error: no beads database found
Hint: run 'bd init' to create a database...
```

This leads users (and AI agents) to reflexively run `bd init`, which can cause:
- Prefix mismatch if wrong prefix specified
- Data corruption if JSONL is damaged
- Confusion about what actually happened

## Proposed Behavior

```
$ bd init --prefix bd

⚠ Found existing .beads/issues.jsonl with 76 issues.

This appears to be a fresh clone, not a new project.

To hydrate the database from existing JSONL:
  bd doctor --fix

To force re-initialization (may cause data loss):
  bd init --prefix bd --force

Aborting.
```

## Trigger Conditions

- `.beads/issues.jsonl` or `.beads/beads.jsonl` exists
- File contains > 0 valid issue lines
- No `--force` flag provided

## Edge Cases

- Empty JSONL (0 issues) → allow init (new project)
- Corrupted JSONL → warn but allow with confirmation
- Existing `.db` file → definitely refuse (weird state)

## Related

- bd-dmb: Fresh clone should suggest hydration (better error messages)
- bd-4ew: bd doctor should detect fresh clone

This issue is about the safety guard on `bd init` itself, not the error messages from other commands.


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 topological sort utility functions

Create internal/importer/sort.go with utilities for depth-based sorting of issues. Functions: GetHierarchyDepth(id), SortByDepth(issues), GroupByDepth(issues). Include stable sorting for same-depth issues.


Add docs/architecture/event_driven.md

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


Refactor duplicate JSONL-from-git parsing code

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

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

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

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


Implement transaction retry logic for SQLITE_BUSY

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

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

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

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

Impact: Spurious failures under concurrent write load

Effort: 3 hours


Refactor daemon.go into internal/daemonrunner

Extract daemon runtime from daemon.go (1,565 lines) into internal/daemonrunner with focused modules: config.go, daemon.go, process.go, rpc_server.go, sync.go, git.go. Keep cobra command thin.


Complete and commit beads-mcp type safety improvements

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


Run final validation and cleanup checks

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

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

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

Impact: Confirms all cleanup objectives achieved successfully

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

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


Database pollution in ~/src/dave/vc: 895 issues vs canonical 310

~/src/dave/vc/.beads/beads.db has 895 total issues (675 open, 149 closed), but canonical ~/src/vc/.beads/vc.db has only 310 issues (230 open). This is 585 extra issues - likely pollution from other repositories.

Need to:
1. Identify which issues are polluted (use detect-pollution)
2. Compare issue IDs between dave/vc and canonical vc databases
3. Determine pollution source (beads repo? other repos?)
4. Clean up polluted database
5. Root cause: why did pollution occur?

Investigation findings so far:
- Polluted DB (~/src/dave/vc/.beads/beads.db): 241 issues (180 open, 43 closed)
- Canonical DB (~/src/vc/.beads/vc.db): 310 issues (230 open, 62 closed)
- Contradiction: Polluted has FEWER issues, not more (241 < 310, diff of 69)
- Only 1 unique ID in polluted: vc-55fi
- All source_repo fields are set to "." in both databases
- Issue description claims 895 issues in polluted vs 310 canonical - numbers don't match current state
- Possible: Pollution was already partially cleaned, or issue description refers to different database?


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


Split monolithic sqlite.go into focused files

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

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

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

Effort: 6-8 hours


Workaround for FastMCP outputSchema bug in Claude Code

The beads MCP server (v0.23.1) successfully connects to Claude Code, but all tools fail to load with a schema validation error due to a bug in FastMCP 2.13.1.

Error: "Invalid literal value, expected \"object\"" in outputSchema.

Root Cause: FastMCP generates outputSchema with $ref at root level without "type": "object" for self-referential models (Issue).

Workaround: Use slash commands (/beads:ready) or wait for FastMCP fix.


Address gosec security warnings (102 issues)

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


Add richer query capabilities to bd list

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

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

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

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

## Progress Update

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

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

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

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


Add TestNWayCollision for 5+ clones

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

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

## Implementation Tasks

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

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

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

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

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

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

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

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

    tmpDir := t.TempDir()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Testing Strategy

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

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

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

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


[CRITICAL] Sync validation false positive - legitimate deletions trigger 'data loss detected'

Sync preflight validation incorrectly detects 'data loss' when legitimate deletions occur. This blocks all syncs and is the highest priority fix.


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.


Add index usage verification test for external_ref lookups

Currently we test that idx_issues_external_ref index exists, but we don't verify that it's actually being used by the query planner.

Proposed solution:
- Add test using EXPLAIN QUERY PLAN
- Verify that 'SEARCH TABLE issues USING INDEX idx_issues_external_ref' appears in plan
- Ensures O(1) lookup performance is maintained

Related: bd-1022
Files: internal/storage/sqlite/external_ref_test.go:260


Document CompactedResult response format in CONTEXT_ENGINEERING.md

The CompactedResult is a new response format that MCP clients need to understand, but it's not documented in CONTEXT_ENGINEERING.md.

## What's Missing
- Example CompactedResult JSON response
- How to detect if a result is compacted
- How to request full results (disable compaction or increase limit)
- Guidance on handling both list[IssueMinimal] and CompactedResult return types
- Migration guide for clients expecting list[Issue] from ready() and list()

## Documentation Gaps
Currently CONTEXT_ENGINEERING.md explains the optimization strategy but doesn't show:
1. Sample CompactedResult response
2. Client-side code examples for handling compaction
3. When compaction is triggered (>20 results)
4. How to get all results if needed

## Suggested Additions
Add new section \"Handling Large Result Sets\" with:
- CompactedResult schema documentation
- Python client example for handling both response types
- Guidance on re-issuing queries with better filters


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

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

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

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

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

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


bd sync --import-only doesn't update DB mtime causing bd doctor false warning


Add edge case tests for export metadata updates

## Current Coverage
TestExportUpdatesMetadata tests basic happy path only.

## Missing Test Cases

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

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

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

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

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

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

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

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

## Files
- cmd/bd/daemon_sync_test.go

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


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.


Final validation test


Add tests for daemon sync module


Fix CI failures before 0.21.3 release

CI is failing on multiple jobs:
1. Nix flake: Tests fail due to missing git in build environment
2. Windows tests: Need to check what's failing
3. Linux tests: Need to check what's failing
4. Linter errors: Many unchecked errors need fixing

Need to fix before tagging v0.21.3 release.

Fixed linter errors (errcheck, misspell), Nix flake git dependency, and import database discovery bug. Tests still failing - need to investigate further.


Remove unused global daemon infrastructure (internal/daemonrunner/)

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

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

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

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

Total cleanup: ~1,500 LOC


Extract duplicated metadata update code in daemon_sync.go

## Problem
The same 22-line metadata update block appears identically in both:
- createExportFunc (lines 309-328)
- createSyncFunc (lines 520-539)

This violates DRY principle and makes maintenance harder.

## Solution
Extract to helper function:

```go
// updateExportMetadata updates last_import_hash and related metadata after a successful export.
// This prevents "JSONL content has changed since last import" errors on subsequent exports (bd-ymj fix).
func updateExportMetadata(ctx context.Context, store storage.Storage, jsonlPath string, log daemonLogger) {
    currentHash, err := computeJSONLHash(jsonlPath)
    if err != nil {
        log.log("Warning: failed to compute JSONL hash for metadata update: %v", err)
        return
    }

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

    exportTime := time.Now().Format(time.RFC3339)
    if err := store.SetMetadata(ctx, "last_import_time", exportTime); err != nil {
        log.log("Warning: failed to update last_import_time: %v", err)
    }

    // Store mtime for fast-path optimization
    if jsonlInfo, statErr := os.Stat(jsonlPath); statErr == nil {
        mtimeStr := fmt.Sprintf("%d", jsonlInfo.ModTime().Unix())
        if err := store.SetMetadata(ctx, "last_import_mtime", mtimeStr); err != nil {
            log.log("Warning: failed to update last_import_mtime: %v", err)
        }
    }
}
```

## Files
- cmd/bd/daemon_sync.go

## Benefits
- Easier maintenance
- Single source of truth
- Consistent behavior


Add 'town report' command for aggregated swarm status

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

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

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

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

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

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

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


bd detect-pollution - Test pollution detector

Detect test issues that leaked into production DB.

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

Optional AI scoring for confidence.

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


Merge3Way public API does not expose TTL parameter

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


Frontend task


Test parent issue


Add import.orphan_handling config option

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


GH#497: bd onboard copilot-instructions.md is beads-specific, not generic

bd onboard outputs beads repo specific content (Go/SQLite/Cobra) for copilot-instructions.md instead of a generic template. AGENTS.md output is correct. See: https://github.com/steveyegge/beads/issues/497


Extract Daemon struct and config into internal/daemonrunner

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

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

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


Extract daemon logger functions to daemon_logger.go


Separate process/lock/PID concerns into process.go

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


GH#509: bd doesn't find .beads when running from nested worktrees

When worktrees are nested under main repo (.worktrees/feature/), bd stops at worktree git root instead of continuing to find .beads in parent. See GitHub issue #509 for detailed fix suggestion.