Critical bug


Add PostgreSQL backend

Implement PostgreSQL storage backend as alternative to SQLite for larger teams


parallel_test_10


parallel_test_3


parallel_test_8


parallel_test_9


parallel_26


parallel_31


parallel_32


parallel_33


parallel_28


parallel_29


Test issue to verify fix

This should be bd-11 if the fix works


parallel_27


parallel_30


parallel_35


parallel_34


stress_3


stress_5


stress_10


stress_2


stress_8


stress_13


Implement collision detection in import

Create collision.go with detectCollisions() function. Compare incoming JSONL issues against DB state. Distinguish between: (1) exact match (idempotent), (2) ID match but different content (collision), (3) new issue. Return list of colliding issues.


stress_14


stress_1


stress_7


stress_17


stress_6


stress_19


stress_20


stress_15


stress_24


stress_18


Implement reference scoring algorithm

Count references for each colliding issue: text mentions in descriptions/notes/design fields + dependency references. Sort collisions by score ascending (fewest refs first). This minimizes total updates during renumbering.


stress_22


stress_28


stress_25


stress_29


stress_26


stress_9


stress_30


stress_32


stress_16


stress_27


Implement ID remapping with reference updates

Allocate new IDs for colliding issues. Update all text field references using word-boundary regex (\bbd-10\b). Update dependency records. Build id_mapping for reporting. Handle chain dependencies properly.


stress_31


stress_35


stress_37


stress_34


stress_36


stress_21


stress_38


stress_42


stress_43


stress_39


Add --resolve-collisions flag and user reporting

Add import flags: --resolve-collisions (auto-fix) and --dry-run (preview). Display clear report: collisions detected, remappings applied (old→new with scores), reference counts updated. Default behavior: fail on collision (safe).


stress_45


stress_46


stress_48


stress_44


stress_40


stress_41


stress_12


stress_47


stress_49


stress_50


Write comprehensive collision resolution tests

Test cases: simple collision, multiple collisions, dependency updates, text reference updates, chain dependencies, edge cases (partial ID matches, case sensitivity, triple merges). Add to import_test.go and collision_test.go.


stress_33


stress_23


stress_11


stress_4


Add visual indicators for nodes with multiple parents in dep tree

When a node appears in the dependency tree via multiple paths (diamond dependencies), add a visual indicator like (*) or (multiple parents) to help users understand the graph structure. This would make it clear when deduplication has occurred. Example: 'bd-503: Shared dependency (*) [P1] (open)'


Add --show-all-paths flag to bd dep tree

Currently bd dep tree deduplicates nodes when multiple paths exist (diamond dependencies). Add optional --show-all-paths flag to display the full graph with all paths, showing duplicates. Useful for debugging complex dependency structures and understanding all relationships.


Make maxDepth configurable in bd dep tree command

Currently maxDepth is hardcoded to 50 in GetDependencyTree. Add --max-depth flag to bd dep tree command to allow users to control recursion depth. Default should remain 50 for safety, but users with very deep trees or wanting shallow views should be able to configure it.


Test issue with --deps flag


Another test with multiple deps


Fix: bd init --prefix test -q flag not recognized

The init command doesn't recognize the -q flag. When running 'bd init --prefix test -q', it fails silently or behaves unexpectedly. The flag should either be implemented for quiet mode or removed from documentation if not supported.


Update documentation for collision resolution

Update README.md with collision resolution section. Update CLAUDE.md with new workflow. Document --resolve-collisions and --dry-run flags. Add example scenarios showing branch merge workflows.


Add OAuth2 support

Implement OAuth2 authentication flow with support for Google and GitHub providers.

- Create OAuth2 provider interface
- Implement Google provider
- Implement GitHub provider
- Add callback handler
- Store tokens securely

- Users can authenticate with Google
- Users can authenticate with GitHub
- Tokens are stored securely in database
- Token refresh works automatically


Add rate limiting to auth endpoints

Auth endpoints are vulnerable to brute force attacks. Need to add rate limiting.


Improve session management

Current session management is basic. Need to improve with better expiration handling.


Refactor parseMarkdownFile to reduce cyclomatic complexity

The parseMarkdownFile function in cmd/bd/markdown.go has a cyclomatic complexity of 38, which exceeds the recommended threshold of 30. This makes the function harder to understand, test, and maintain.

Split the function into smaller, focused units:

1. parseMarkdownFile(filepath) - Main entry point, handles file I/O
2. parseMarkdownContent(scanner) - Core parsing logic
3. processIssueSection(issue, section, content) - Handle section finalization (current switch statement)
4. parseLabels(content) []string - Extract labels from content
5. parseDependencies(content) []string - Extract dependencies from content
6. parsePriority(content) int - Parse and validate priority

Benefits:
- Each function has a single responsibility
- Easier to test individual components
- Lower cognitive load when reading code
- Better encapsulation of parsing logic

- parseMarkdownFile complexity < 15
- New helper functions each have complexity < 10
- All existing tests still pass
- No change in functionality or behavior
- Code coverage maintained or improved


Add OAuth2 support


Add rate limiting to auth endpoints


Improve session management


Add design/notes/acceptance_criteria fields to update command

Currently bd update only supports status, priority, title, assignee. Add support for --design, --notes, --acceptance-criteria flags. This makes it easier to add detailed designs to issues after creation.


Investigate vector/semantic search for issue discovery

From GH issue #2 RFC discussion: Evaluate if vector/semantic search over issues would provide value for beads.

**Use case:** Find semantically related issues (e.g., 'login broken' finds 'authentication failure', 'session expired').

**Questions to answer:**
1. What workflows would this enable that we can't do now?
2. Is dataset size (typically 50-200 issues) large enough to benefit?
3. Do structured features (deps, tags, types) already provide better relationships?
4. What's the maintenance cost (embeddings, storage, recomputation)?

**Alternatives to consider:**
- Improve 'bd list' filtering with regex/boolean queries
- Add 'bd related <id>' showing deps + mentions + same tags
- Export to JSON and pipe to external AI tools

**Decision:** Only implement if clear use case emerges. Don't add complexity for theoretical benefits.

**Context:** Part of evaluating Turso RFC ideas (GH #2). Vector search was proposed but unclear if needed for typical beads usage.


Implement storage driver interface for pluggable backends

Create abstraction layer for storage to support multiple backends (SQLite, Postgres, Turso, in-memory testing, etc.).

**Current state:** All storage logic hardcoded to SQLite in internal/storage/sqlite/sqlite.go

**Proposed design:**

```go
// internal/storage/storage.go
type Store interface {
    // Issue CRUD
    CreateIssue(issue *Issue) error
    GetIssue(id string) (*Issue, error)
    UpdateIssue(id string, updates *Issue) error
    DeleteIssue(id string) error
    ListIssues(filter *Filter) ([]*Issue, error)

    // Dependencies
    AddDependency(from, to string, depType DependencyType) error
    RemoveDependency(from, to string, depType DependencyType) error
    GetDependencies(id string) ([]*Dependency, error)

    // Counters, stats
    GetNextID(prefix string) (string, error)
    GetStats() (*Stats, error)

    Close() error
}
```

**Benefits:**
- Better testing (mock/in-memory stores)
- Future flexibility (Postgres, cloud APIs, etc.)
- Clean architecture (business logic decoupled from storage)
- Enable Turso or other backends without refactoring everything

**Implementation steps:**
1. Define Store interface in internal/storage/storage.go
2. Refactor SQLiteStore to implement interface
3. Update all commands to use interface, not concrete type
4. Add MemoryStore for testing
5. Add driver selection via config (storage.driver = sqlite|turso|postgres)
6. Update tests to use interface

**Note:** This is valuable even without adopting Turso. Good architecture practice.

**Context:** From GH issue #2 RFC evaluation. Driver interface is low-cost, high-value regardless of whether we add alternative backends.


Investigate auto-export debounce not triggering

Auto-export to JSONL did not trigger automatically after creating bd-180 and bd-181. Had to manually run 'bd export' to sync.

**Expected behavior:** Auto-export should trigger ~5 seconds after CRUD operations (per CLAUDE.md documentation).

**Actual behavior:** Issues bd-180 and bd-181 were created but JSONL was not updated until manual 'bd export' was run.

**Investigation needed:**
1. Check if auto-flush goroutine is running
2. Verify debounce timer is being triggered on CreateIssue()
3. Check for errors/panics in background export
4. Verify auto-flush is enabled by default
5. Check if there's a race condition with shutdown

**Impact:** HIGH - Data loss risk if users create issues and don't realize they haven't synced to Git.

**Testing:**
```bash
# Create issue and wait 10 seconds
bd create "Test" -p 4
sleep 10
grep "Test" .beads/issues.jsonl  # Should find it
```

**Workaround:** Manually run 'bd export' after CRUD operations.

**Context:** Discovered during GH issue #2 RFC evaluation while creating bd-180 and bd-181.

**Investigation Results:**

The auto-export feature IS working correctly in the current codebase (v0.9.5). Testing shows:

✅ Storage layer marks issues dirty correctly (lines 415-423, 593-601, 633-641 in sqlite.go)
✅ CLI calls markDirtyAndScheduleFlush() after CRUD operations (lines 766, 1000, 1055 in main.go)
✅ Debounce timer (5s) triggers and exports to JSONL successfully
✅ Tested: Created bd-196, auto-exported within 6 seconds

**Root Cause:**
The issue was caused by running an **old bd binary** that didn't have the complete auto-export implementation. After rebuilding (`go build -o bd ./cmd/bd`), auto-export works perfectly.

**Resolution:**
Users experiencing this issue need to:
1. Rebuild bd: `go build -o bd ./cmd/bd`
2. Install updated binary: `sudo mv bd /usr/local/bin/` (or appropriate path)
3. Verify: `bd version` should show 0.9.5+

**Additional Notes:**
- Auto-export uses 5-second debounce (configurable at main.go:36)
- Exports are atomic (writes to .tmp, then renames)
- Only dirty issues are exported (incremental updates)
- Works correctly in production use

**Follow-up:**
Consider adding a version check warning if user's binary is outdated (bd-408).


Plugin manifest has invalid 'engines' field causing marketplace install failure

The plugin.json manifest includes an 'engines' field that Claude Code doesn't recognize. This causes the plugin to fail validation when installed via /plugin marketplace add.

Error: Plugin beads has an invalid manifest file at .claude-plugin/plugin.json. Validation errors: : Unrecognized key(s) in object: 'engines'

Fix: Remove the 'engines' field from plugin.json

The engines field was added to specify minimum beads version requirements, but Claude Code's plugin manifest schema doesn't support it. We should document version requirements in PLUGIN.md instead.


race_test_4


race_test_8


race_test_5


race_test_16


race_test_17


race_test_10


Fix import zero-value field handling

Import uses zero-value checks (Priority != 0) to determine field updates. This prevents setting priority to 0 or clearing string fields. Export/import round-trip not fully idempotent for zero values. Consider JSON presence detection or explicit preserve-existing semantics. Location: cmd/bd/import.go:95-106


race_test_11


final_test_10


final_test_7


stress_test_5


stress_test_6


stress_test_1


Test2 auto-export


Add version mismatch warning for outdated binaries

When bd detects that the database was created/modified by a newer version, warn the user that they may be running an outdated binary.

**Problem:** Users may run old bd binaries without realizing it, leading to confusing behavior (e.g., missing features like auto-export).

**Solution:** Store schema version or bd version in metadata table, check on startup, warn if mismatch.

**Implementation:**
1. Add `schema_version` or `bd_version` to metadata table during init
2. Check version in PersistentPreRun (cmd/bd/main.go)
3. Warn if binary version < DB version
4. Suggest: 'Your bd binary (v0.9.3) is older than this database (v0.9.5). Rebuild: go build -o bd ./cmd/bd'

**Edge cases:**
- Dev builds (show commit hash?)
- Backwards compatibility (older DBs should work with newer binaries)
- Don't warn on every command (maybe once per session?)

**Related:** Fixes confusion from bd-182 (auto-export not working with old binary)

**Implementation Complete:**

✅ Added version metadata storage during `bd init` (init.go:59-63)
✅ Added `checkVersionMismatch()` function (main.go:301-345)
✅ Integrated version check into PersistentPreRun (main.go:98-99)
✅ Tested both scenarios:
   - Outdated binary: Clear warning with rebuild instructions
   - Newer binary: Info message that DB will be auto-upgraded
✅ No warnings on subsequent runs (version updated automatically)

**How it works:**
1. On `bd init`: Stores current version in metadata table
2. On every command: Checks stored version vs binary version
3. If mismatch:
   - Binary < DB version → Warn: outdated binary
   - Binary > DB version → Info: auto-upgrading DB
4. Always updates stored version to current (future-proof)

**Files modified:**
- cmd/bd/init.go: Store version on init
- cmd/bd/main.go: checkVersionMismatch() + integration

**Testing:**
```bash
# Simulate old binary
sqlite3 .beads/default.db "UPDATE metadata SET value='0.9.99' WHERE key='bd_version';"
bd ready  # Shows warning

# Normal use (versions match)
bd ready  # No warning
```

**Edge cases handled:**
- Empty version (old DB): Silently upgrade
- Metadata errors: Skip check (defensive)
- Dev builds: String comparison (works for 0.9.5 vs 0.9.6)

Fixes bd-182 confusion (users won't run outdated binaries unknowingly).


Add label management commands to CLI

Currently labels can only be managed programmatically via the storage API. Add CLI commands to add, remove, and filter by labels.

Can add/remove/list labels via CLI, can filter issues by label, labels persist to JSONL


Investigate and fix import timeout with 208 issues

bd import times out after 2 minutes when importing 208 issues from JSONL. This is unacceptable for a tool designed to scale to 100k+ issues.

## Reproduction
```bash
cd ~/src/beads
bd import .beads/issues.jsonl  # Hangs/times out after 2 minutes
```

Current database state:
- 208 issues in bd.db (2MB)
- 208 lines in issues.jsonl (100KB)
- WAL mode enabled

## Symptoms
- Import starts but never completes
- No error message, just hangs
- Timeout after 2 minutes (artificially imposed in testing)
- Happens even when database already contains the issues (idempotent import)

## Likely Causes
1. **Transaction size** - All 208 issues in one transaction?
2. **Lock contention** - WAL checkpoint blocking?
3. **N+1 queries** - Dependency checking for each issue?
4. **Missing indexes** - Slow lookups during collision detection?
5. **Auto-flush interaction** - Background flush goroutine conflicting?

## Performance Target
Should handle:
- 1000 issues in < 5 seconds
- 10,000 issues in < 30 seconds
- 100,000 issues in < 5 minutes

## Investigation Steps
1. Add timing instrumentation to import phases
2. Profile the import operation
3. Check for lock contention in WAL mode
4. Review collision detection performance
5. Test with PRAGMA synchronous = NORMAL
6. Consider batching imports (e.g., 100 issues per transaction)

- Can import 208 issues in < 5 seconds
- Can import 1000 issues in < 30 seconds
- Add performance logging to identify bottlenecks
- Add --batch-size flag for tuning
- Document performance characteristics
- Integration test with large import

## Profiling Results (2025-10-15)

Created comprehensive profiling tests in cmd/bd/import_profile_test.go.

### Key Findings

Import performance is actually **very good** for the core phases:
- 1000 issues: 883ms total (1,132 issues/sec)
- 208 issues collision detection: 3.2ms (64,915 issues/sec)

### Bottleneck Identified

**Create/Update phase takes 95-98% of total time:**
- 100 issues: 58ms (87.4%)
- 500 issues: 214ms (95.8%)
- 1000 issues: 866ms (98.1%)

This phase does N*2 individual queries (GetIssue + CreateIssue/UpdateIssue) with no transaction batching.

### Next Steps

1. Profile dependency import phase (not yet measured)
2. Check for auto-flush lock contention
3. Investigate WAL checkpoint blocking
4. Consider transaction batching for bulk imports

### Test Command

```bash
go test -v -run TestImportPerformance ./cmd/bd/
```

See test file for detailed instrumentation.


Verify auto-export works


Add --strict flag for dependency import failures

Currently dependency import errors are warnings (logged to stderr, execution continues). Missing targets or cycles may indicate JSONL corruption. Add --strict flag to fail on any dependency errors for data integrity validation. Location: cmd/bd/import.go:159-164


race_test_4


race_test_5


race_test_2


race_test_7


race_test_14


race_test_9


race_test_8


race_test_11


race_test_19


race_test_16


Simplify getNextID SQL query parameters

Query passes prefix four times to same SQL query. Works but fragile if query changes. Consider simplifying SQL to require fewer parameters. Location: internal/storage/sqlite/sqlite.go:73-78


race_test_18


race_test_20


race_test_6


race_test_3


race_test_12


race_test_15


race_test_13


race_test_10


race_test_17


verification_


Add validation/warning for malformed issue IDs

getNextID silently ignores non-numeric ID suffixes (e.g., bd-foo). CAST returns NULL for invalid strings. Consider detecting and warning about malformed IDs in database. Location: internal/storage/sqlite/sqlite.go:79-82


final_review_test_


P0: Transaction state corruption in first fix attempt

First attempt at fixing bd-89 had a critical flaw: used 'ROLLBACK; BEGIN IMMEDIATE' which executed as two separate statements. After ROLLBACK, the Go tx object was invalid but continued to be used, causing undefined behavior.

Root cause: database/sql connection pooling. Without acquiring a dedicated connection, subsequent queries could use different connections from the pool, breaking the transaction.

Correct fix: Use conn := s.db.Conn(ctx) to acquire a dedicated connection, then execute BEGIN IMMEDIATE, all operations, and COMMIT on that single connection.

This bug was caught during code review and fixed before merging.


P2: Consider batching API for bulk issue creation

Current CreateIssue acquires a dedicated connection for each call. For bulk imports or agent workflows creating many issues, a batched API could improve performance:

CreateIssues(ctx, issues []*Issue, actor string) error

This would:
- Acquire one connection
- Use one IMMEDIATE transaction
- Insert all issues atomically
- Reduce connection overhead

Not urgent - current approach is correct and fast enough for typical use.


P3: Consider early context check in CreateIssue

CreateIssue starts an IMMEDIATE transaction before checking if context is cancelled. If a client cancels early, we've already acquired the write lock.

Consider adding:
  if err := ctx.Err(); err != nil {
    return err
  }

Between validation (line 318) and acquiring connection (line 331).

Low priority - the busy_timeout and deferred cleanup handle this gracefully, but an early check would be slightly more efficient.


Data model allows inconsistent status/closed_at states

Issue bd-89 demonstrates a data model inconsistency: an issue can have status='open' but also have a closed_at timestamp set. This creates a liminal state that violates the expected invariant that closed_at should only be set when status='closed'.

Root causes:
1. Import (bd import) updates status field independently from closed_at field
2. UpdateIssue allows status changes without managing closed_at
3. No database constraint enforcing the invariant
4. Export includes both fields independently in JSONL

Current behavior:
- bd close: Sets status='closed' AND closed_at (correct)
- bd update --status open: Sets status='open' but leaves closed_at unchanged (creates inconsistency)
- bd import: Can import inconsistent data from JSONL

Impact:
- 'bd ready' shows issues that appear closed (have closed_at)
- Confusing for users and downstream tools
- Stats may be inaccurate

Potential solutions:
A) Add CHECK constraint: (status = 'closed') = (closed_at IS NOT NULL)
B) Update import/update logic to enforce invariant in application code
C) Add a 'reopened' event that explicitly clears closed_at
D) Remove closed_at field entirely (calculate from events or use status only)

See bd-89 for concrete example.


Ultrathink: Choose solution for status/closed_at inconsistency (bd-224)

Deep analysis of solution options for bd-224 data model inconsistency issue.

Context:
- Target users: individual devs and small teams
- Future: swarms of agent workers
- Brand-new codebase with few users (can break things)
- Issue: status='open' with closed_at!=NULL creates liminal state

Options to evaluate:
A) Database CHECK constraint
B) Application-level enforcement
C) Add explicit reopened event
D) Remove closed_at field entirely

Analysis framework:
1. Simplicity (mental model for devs)
2. Robustness (hard to break, especially for agent swarms)
3. Migration cost (schema changes, data cleanup)
4. Future extensibility
5. Performance
6. Consistency guarantees

Need to determine best approach given tradeoffs.

Analysis complete. Recommendation: Hybrid approach with DB CHECK constraint + smart UpdateIssue + import enforcement + reopen command.

Key insights:
- DB constraint provides defense-in-depth perfect for agent swarms
- Statistics calculation currently uses 'closed_at IS NOT NULL' which is BROKEN by inconsistent data
- UpdateIssue and Import don't manage the invariant
- EventReopened exists but is unused

See ULTRATHINK_BD224.md for full analysis.


Epic: Fix status/closed_at inconsistency (bd-224 solution)

Implement hybrid solution to enforce status/closed_at invariant:
- Database CHECK constraint
- Smart UpdateIssue logic
- Import enforcement
- Reopen command

This is a data integrity issue that affects statistics and will cause problems for agent swarms. The hybrid approach provides defense-in-depth.

See ULTRATHINK_BD224.md for full analysis and rationale.

Parent of all implementation tasks for this fix.


Audit and document all inconsistent issues in database

Before we add the constraint, we need to know what data is inconsistent.

Steps:
1. Query database for status/closed_at mismatches
2. Document each case (how many, which issues, patterns)
3. Decide on cleanup strategy (trust status vs trust closed_at)
4. Create SQL script for cleanup

Output: Document with counts and cleanup SQL ready to review.

This unblocks the migration work.


Critical: Auto-import silently overwrites local changes without collision detection

## Problem

Auto-import is silently overwriting local changes when pulling from Git, without using collision detection or warning the user.

## Evidence

Issue bd-89 timeline:
1. 08:07:31 - Manual update: set status=in_progress
2. 08:13:08 - Manual close: set status=closed, closed_at set
3. 08:33:25 - **Auto-import silently overwrote to status=open** (discarded local change)
4. 08:57:59 - Auto-import overwrote again
5. 08:58:30 - Auto-import overwrote again

Query shows 10+ issues with multiple auto-import overwrites, including bd-198, bd-199, bd-200-206.

## Root Cause Hypothesis

When two clones both modify issue N:
- Clone A: modifies bd-89, exports to JSONL, commits
- Clone B: also modifies bd-89 differently, pulls from A
- Auto-import uses UpdateIssue which **unconditionally overwrites**
- Clone B's local changes are silently lost
- No collision detection warning
- References and work context corrupted

## Impact

- **Data loss**: Local changes silently discarded
- **Corruption**: Issue state doesn't reflect actual work done
- **Lost references**: Comments/deps referencing lost state are now wrong
- **Trust**: Can't trust the database reflects reality
- **Agent swarms**: Catastrophic for parallel workers

## Expected Behavior

Auto-import should:
1. Detect collisions (same ID, different content)
2. Warn user or fail import
3. Offer resolution options (--resolve-collisions)
4. Never silently overwrite without user confirmation

## Current Behavior

Auto-import silently overwrites using UpdateIssue, no collision detection.

## Reproduction

1. Clone A: bd update bd-1 --status in_progress
2. Clone A: git add .beads/issues.jsonl && git commit && git push
3. Clone B: bd update bd-1 --status closed
4. Clone B: git pull (triggers auto-import)
5. Result: bd-1 status silently reverted to in_progress, closed state lost

## Files Involved

- cmd/bd/main.go: auto-import trigger logic
- cmd/bd/import.go: import without collision detection
- internal/storage/sqlite/sqlite.go: UpdateIssue unconditionally overwrites


Investigate data recovery for issues overwritten by auto-import bug

Auto-import bug (bd-228) has been silently overwriting local changes since the feature was introduced. We need to investigate if any important data was lost and if it can be recovered.

## Data Sources for Recovery

1. **Git history of .beads/issues.jsonl** - May contain older versions with lost changes
2. **Events table** - Has full audit trail with old_value/new_value
3. **Commit messages** - May reference work that was done but later overwritten

## Investigation Steps

1. Check events table for patterns:
   - Look for auto-import events that changed status from closed → open
   - Look for manual updates followed by auto-import reverts
   - Identify issues with suspicious event sequences

2. Git archaeology:
   - Check git log for .beads/issues.jsonl
   - Look for commits where status=closed was changed to status=open
   - Compare git history vs current database state

3. Document lost work:
   - Create list of issues that were closed but reverted to open
   - Identify any critical work that was marked done but lost
   - Check if any issue references are now broken

## Recovery Actions

If significant data loss is found:
- Create script to reconstruct lost state from events table
- Manually review and restore critical closed issues
- Document lessons learned for preventing future data loss

## Example Query

```sql
-- Find issues where status was manually changed then reverted by auto-import
SELECT
  issue_id,
  GROUP_CONCAT(event_type || ':' || actor, ' -> ') as event_chain
FROM events
WHERE issue_id IN (
  SELECT DISTINCT issue_id
  FROM events
  WHERE actor = 'auto-import' AND event_type = 'status_changed'
)
GROUP BY issue_id
HAVING event_chain LIKE '%stevey%auto-import%';
```

Investigation complete. Successfully recovered 3 valuable issues (bd-232, bd-233) from 22 total lost. Historical context preserved in bd-89 notes. Auto-import collision detection (bd-228) now prevents future data loss.


Optimize export dependency queries (N+1 problem)

Export triggers separate GetDependencyRecords() per issue. For large DBs (1000+ issues), this is N+1 queries. Add GetAllDependencyRecords() to fetch all dependencies in one query. Location: cmd/bd/export.go:52-59, import.go:138-142


Code review: Auto-import collision detection fix (bd-228)

Request thorough code review of the auto-import collision detection implementation.

## Files Changed
- cmd/bd/main.go: autoImportIfNewer() and new autoImportWithoutCollisionDetection()

## Review Focus Areas

### 1. Correctness
- Does collision detection properly identify conflicts?
- Are colliding issues correctly filtered from import?
- Is the fallback function correct for non-SQLite backends?

### 2. Edge Cases
- What happens if DetectCollisions() fails?
- What if all issues are collisions?
- What if JSONL is malformed?
- Race conditions with concurrent auto-imports?

### 3. User Experience
- Is the warning message clear and actionable?
- Should we log to a file instead of/in addition to stderr?
- Should there be a --auto-resolve flag?

### 4. Performance
- Does collision detection add significant latency?
- Will this work with 1000+ issues?
- Any unnecessary N+1 queries?

### 5. Testing Gaps
- Do we need integration tests for collision scenarios?
- Should we test the warning output?
- Test autoImportWithoutCollisionDetection() fallback?

## Questions for Reviewer
1. Should auto-import be more aggressive (auto-resolve) or more conservative (fail)?
2. Should we add a counter for how many times collisions occurred?
3. Should there be a config option to disable collision detection?
4. Is the warning too verbose for typical workflows?

## Current Behavior
- Skips colliding issues (preserves local)
- Prints warning to stderr
- Suggests manual resolution command
- Continues with non-colliding issues


Add integration tests for auto-import collision detection

The auto-import collision detection fix (bd-228) needs comprehensive integration tests.

## Test Scenarios Needed

### 1. Basic Collision Detection
```go
TestAutoImportWithCollisions
- Setup: Create issue in DB with status=closed
- Simulate: Git pull with JSONL containing same issue with status=open
- Verify: Local changes preserved, warning printed, issue still closed
```

### 2. Multiple Collisions
```go
TestAutoImportWithMultipleCollisions
- Setup: 5 issues with local modifications
- Simulate: JSONL with 3 colliding, 2 matching
- Verify: 3 skipped, 2 updated, appropriate warnings
```

### 3. No Collisions (Happy Path)
```go
TestAutoImportWithoutCollisions
- Setup: Clean database
- Simulate: JSONL with new issues + exact matches
- Verify: All imported successfully, no warnings
```

### 4. All Collisions
```go
TestAutoImportAllCollisions
- Setup: Every issue has local modifications
- Simulate: JSONL with conflicting versions
- Verify: All skipped, warning lists all issues
```

### 5. Collision Detection Failure
```go
TestAutoImportCollisionDetectionError
- Setup: Mock DetectCollisions() to return error
- Verify: Import skipped entirely (safe failure mode)
```

### 6. Hash-Based Skip
```go
TestAutoImportHashUnchanged
- Setup: JSONL hash matches last_import_hash
- Verify: No collision detection runs, fast path
```

### 7. Fallback for Non-SQLite
```go
TestAutoImportWithoutCollisionDetection
- Setup: Mock non-SQLite storage backend
- Verify: Falls back to old behavior, no collision detection
```

## Test Infrastructure Needed

1. **Helper functions:**
   - createTestDBWithIssues()
   - writeJSONLFile()
   - captureStderrWarnings()
   - simulateGitPull()

2. **Fixtures:**
   - sample-collisions.jsonl
   - sample-exact-matches.jsonl
   - sample-mixed.jsonl

3. **Assertions:**
   - assertIssueState(id, expectedStatus)
   - assertWarningContains(text)
   - assertCollisionCount(n)

## Acceptance Criteria
- All 7 test scenarios implemented
- Tests pass consistently
- Code coverage >80% for autoImportIfNewer()
- Tests run in <5 seconds total
- Clear test names and documentation


Consider batching API for bulk issue creation (recovered from bd-222)

Current CreateIssue acquires a dedicated connection for each call. For bulk imports or agent workflows creating many issues, a batched API could improve performance:

CreateIssues(ctx, issues []*Issue, actor string) error

This would:
- Acquire one connection
- Use one IMMEDIATE transaction
- Insert all issues atomically
- Reduce connection overhead

Not urgent - current approach is correct and fast enough for typical use.

**Recovered from:** bd-222 (lost in auto-import bug, see LOST_ISSUES_RECOVERY.md)


Consider early context check in CreateIssue (recovered from bd-223)

CreateIssue starts an IMMEDIATE transaction before checking if context is cancelled. If a client cancels early, we've already acquired the write lock.

Consider adding:
  if err := ctx.Err(); err != nil {
    return err
  }

Between validation (line 318) and acquiring connection (line 331).

Low priority - the busy_timeout and deferred cleanup handle this gracefully, but an early check would be slightly more efficient.

**Recovered from:** bd-223 (lost in auto-import bug, see LOST_ISSUES_RECOVERY.md)


Add database CHECK constraint for status/closed_at invariant

Add database-level constraint to enforce: (status = 'closed') = (closed_at IS NOT NULL)

Prerequisites: Clean existing inconsistent data first

Changes:
- Add migration to schema.go
- Constraint prevents ANY code path from creating inconsistent state
- Foundation for robust agent-proof system

Migration SQL:
```sql
-- First clean up existing inconsistent data
UPDATE issues SET closed_at = NULL
WHERE status != 'closed' AND closed_at IS NOT NULL;

UPDATE issues SET closed_at = CURRENT_TIMESTAMP
WHERE status = 'closed' AND closed_at IS NULL;

-- Add the constraint
ALTER TABLE issues ADD CONSTRAINT chk_closed_at_status
  CHECK ((status = 'closed') = (closed_at IS NOT NULL));
```

Implementation in schema.go migrations.


Add 'bd reopen' command for explicit issue reopening

Create cmd/bd/reopen.go with explicit reopen command that:
- Sets status to 'open'
- Automatically clears closed_at (via UpdateIssue)
- Emits EventReopened
- Optional --reason flag for explanation

Makes reopening more explicit than 'bd update --status open'

```go
var reopenCmd = &cobra.Command{
    Use:   "reopen [id...]",
    Short: "Reopen one or more closed issues",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        ctx := context.Background()
        reason, _ := cmd.Flags().GetString("reason")

        for _, id := range args {
            updates := map[string]interface{}{
                "status": "open",
            }
            if err := store.UpdateIssue(ctx, id, updates, getUser()); err != nil {
                fmt.Fprintf(os.Stderr, "Error reopening %s: %v\n", id, err)
                continue
            }

            if reason != "" {
                store.AddComment(ctx, id, getUser(), reason)
            }
        }

        markDirtyAndScheduleFlush()
    },
}
```


Add tests for status/closed_at invariant enforcement

Comprehensive test suite for bd-224 invariant:
- Test DB constraint rejects bad data
- Test UpdateIssue auto-manages closed_at
- Test Import normalizes data correctly
- Test reopen command clears closed_at
- Test Issue.Validate() catches errors


Add Issue.Validate() closed_at invariant check

Add validation logic to types.Issue.Validate() to check closed_at invariant before database operations.

This provides application-level validation that complements the database constraint.

```go
func (i *Issue) Validate() error {
    // ... existing validation ...

    // Enforce closed_at invariant (bd-224)
    if i.Status == StatusClosed && i.ClosedAt == nil {
        return fmt.Errorf("closed issues must have closed_at timestamp")
    }
    if i.Status != StatusClosed && i.ClosedAt != nil {
        return fmt.Errorf("non-closed issues cannot have closed_at timestamp")
    }

    return nil
}
```


Fix UpdateIssue to auto-manage closed_at on status changes

Modify internal/storage/sqlite/sqlite.go UpdateIssue to automatically manage closed_at when status changes.

When status changes TO closed: set closed_at = now()
When status changes FROM closed: set closed_at = nil and emit EventReopened

```go
func (s *SQLiteStorage) UpdateIssue(ctx context.Context, id string, updates map[string]interface{}, actor string) error {
    // ... existing validation ...

    // Smart closed_at management based on status changes
    if statusVal, ok := updates["status"]; ok {
        newStatus := statusVal.(string)

        if newStatus == string(types.StatusClosed) {
            // Changing to closed: ensure closed_at is set
            if _, hasClosedAt := updates["closed_at"]; !hasClosedAt {
                updates["closed_at"] = time.Now()
            }
        } else {
            // Changing from closed to something else: clear closed_at
            if oldIssue.Status == types.StatusClosed {
                updates["closed_at"] = nil
                eventType = types.EventReopened
            }
        }
    }

    // ... rest of existing code ...
}
```


Fix import.go to normalize closed_at before creating issues

Modify cmd/bd/import.go to enforce closed_at invariant before calling CreateIssue/CreateIssues.

Normalize data: if status=closed, ensure closed_at is set; if status!=closed, ensure closed_at is nil.

```go
if _, ok := rawData["status"]; ok {
    updates["status"] = issue.Status

    // Enforce closed_at invariant
    if issue.Status == types.StatusClosed {
        // Status is closed: ensure closed_at is set
        if issue.ClosedAt == nil {
            now := time.Now()
            updates["closed_at"] = now
        } else {
            updates["closed_at"] = *issue.ClosedAt
        }
    } else {
        // Status is not closed: ensure closed_at is NULL
        updates["closed_at"] = nil
    }
}
```


Support ID space partitioning for parallel worker agents

Enable external orchestrators (like AI worker swarms) to control issue ID assignment. Add --id flag to 'bd create' for explicit ID specification. Optionally support 'bd config set next_id N' to set the starting point for auto-increment. Storage layer already supports pre-assigned IDs (sqlite.go:52-71), just need CLI wiring. This keeps beads simple while letting orchestrators implement their own ID partitioning strategies to minimize merge conflicts. Complementary to bd-9's collision resolution.


Add CreateIssues interface method to Storage

Add CreateIssues to the Storage interface in storage/storage.go

Non-breaking addition to interface for batch issue creation.

```go
// storage/storage.go
type Storage interface {
    CreateIssue(ctx context.Context, issue *types.Issue, actor string) error
    CreateIssues(ctx context.Context, issues []*types.Issue, actor string) error  // NEW
    // ... rest unchanged
}
```


Add comprehensive unit tests for CreateIssues

Test coverage for CreateIssues:
- Empty batch
- Single issue
- Multiple issues
- Mixed ID assignment (explicit + auto-generated)
- Validation errors
- Duplicate ID errors
- Rollback on error
- Verify closed_at invariant enforced


Update import.go to use CreateIssues for bulk imports

Modify cmd/bd/import.go to use CreateIssues instead of CreateIssue loop.

After bd-224, import already normalizes closed_at, so this is straightforward:
1. Normalize all issues in batch (closed_at handling)
2. Call CreateIssues once with full batch
3. Much simpler than current loop

```go
// After normalizing all issues
for _, issue := range issues {
    if issue.Status == types.StatusClosed {
        if issue.ClosedAt == nil {
            now := time.Now()
            issue.ClosedAt = &now
        }
    } else {
        issue.ClosedAt = nil
    }
}

// Single batch call (5-15x faster!)
return store.CreateIssues(ctx, issues, "import")
```

Completed: Replaced CreateIssue loop with single CreateIssues batch call. Added in-memory de-duplication for duplicate IDs within same import (last one wins). Fixed skip-existing logic to only apply to DB issues, not batch duplicates. All 14 import tests pass including performance, collisions, dependencies, and labels.


Document CreateIssues API and update EXTENDING.md

Documentation updates:
- Godoc for CreateIssues with usage guidance
- Add batch import examples
- Update EXTENDING.md with batch usage patterns
- Performance notes in README.md
- When to use CreateIssue vs CreateIssues


Implement SQLiteStorage.CreateIssues with atomic ID range reservation

Core implementation of CreateIssues in internal/storage/sqlite/sqlite.go

Key optimizations:
- Single connection + transaction
- Atomic ID range reservation (generate N IDs in one counter update)
- Prepared statement for bulk inserts
- All-or-nothing atomicity

Expected 5-10x speedup for N>10 issues.

Implementation phases per ULTRATHINK_BD222.md:

1. **Validation**: Pre-validate all issues (calls Issue.Validate() which enforces closed_at invariant from bd-224)
2. **Connection & Transaction**: BEGIN IMMEDIATE (same as CreateIssue)
3. **Batch ID Generation**: Reserve range [nextID, nextID+N) in single counter update
4. **Bulk Insert**: Prepared statement loop (defer multi-VALUE INSERT optimization)
5. **Bulk Events**: Record creation events for all issues
6. **Bulk Dirty**: Mark all issues dirty for export
7. **Commit**: All-or-nothing transaction commit

See ULTRATHINK_BD222.md lines 344-541 for full implementation details.


Add concurrency tests for CreateIssues

Concurrent testing:
- Multiple goroutines creating batches in parallel
- Verify no ID collisions
- Mix CreateIssue and CreateIssues calls
- Verify all issues created correctly


Add performance benchmarks for CreateIssues

Benchmark suite comparing CreateIssue loop vs CreateIssues batch:
- 10, 100, 1000 issues
- Expected: 5-10x speedup for N>10
- Measure connection, transaction, and insert overhead

Target: 100 issues in <130ms (vs 900ms sequential)


Test reopen command


Add transaction support to storage layer for atomic multi-operation workflows

Currently each storage method (CreateIssue, UpdateIssue, etc.) starts its own transaction. This makes it impossible to perform atomic multi-step operations like collision resolution. Add support for passing *sql.Tx through the storage interface, or create transaction-aware versions of methods. This would make remapCollisions and other batch operations truly atomic.


Implement --format flag for bd list (from PR #46)

PR #46 by tmc adds --format flag with Go template support for bd list, including presets for 'digraph' and 'dot' (Graphviz) output with status-based color coding. Unfortunately the PR is based on old main and would delete labels, reopen, and storage tests. Need to reimplement the feature atop current main.

Features to implement:
- --format flag for bd list
- 'digraph' preset: basic 'from to' format for golang.org/x/tools/cmd/digraph
- 'dot' preset: Graphviz compatible output with color-coded statuses
- Custom Go template support with vars: IssueID, DependsOnID, Type, Issue, Dependency
- Status-based colors: open=white, in_progress=lightyellow, blocked=lightcoral, closed=lightgray

Examples:
- bd list --format=digraph | digraph nodes
- bd list --format=dot | dot -Tsvg -o deps.svg
- bd list --format='{{.IssueID}} -> {{.DependsOnID}} [{{.Type}}]'

Original PR: https://github.com/steveyegge/beads/pull/46


Epic: Add intelligent database compaction with Claude Haiku

Implement multi-tier database compaction using Claude Haiku to semantically compress old, closed issues. This keeps the database lightweight and agent-friendly while preserving essential context.

Goals:
- 70-95% space reduction for eligible issues
- Full restore capability via snapshots
- Opt-in with dry-run safety
- ~$1 per 1,000 issues compacted

- Schema migration with snapshots table
- Haiku integration for summarization
- Two-tier compaction (30d, 90d)
- CLI with dry-run, restore, stats
- Full test coverage
- Documentation complete


Add compaction schema and migrations

Add database schema support for issue compaction tracking and snapshot storage.

Add three columns to `issues` table:
- `compaction_level INTEGER DEFAULT 0` - 0=original, 1=tier1, 2=tier2
- `compacted_at DATETIME` - when last compacted
- `original_size INTEGER` - bytes before first compaction

Create `issue_snapshots` table:
```sql
CREATE TABLE issue_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    issue_id TEXT NOT NULL,
    snapshot_time DATETIME NOT NULL,
    compaction_level INTEGER NOT NULL,
    original_size INTEGER NOT NULL,
    compressed_size INTEGER NOT NULL,
    original_content TEXT NOT NULL,  -- JSON blob
    archived_events TEXT,
    FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
);
```

Add indexes:
- `idx_snapshots_issue` on `issue_id`
- `idx_snapshots_level` on `compaction_level`

Add migration functions in `internal/storage/sqlite/sqlite.go`:
- `migrateCompactionColumns(db *sql.DB) error`
- `migrateSnapshotsTable(db *sql.DB) error`

- Existing databases migrate automatically
- New databases include columns by default
- Migration is idempotent (safe to run multiple times)
- No data loss during migration
- Tests verify migration on fresh and existing DBs


Add compaction configuration keys

Add configuration keys for compaction behavior with sensible defaults.

Add to `internal/storage/sqlite/schema.go` initial config:
```sql
INSERT OR IGNORE INTO config (key, value) VALUES
    ('compact_tier1_days', '30'),
    ('compact_tier1_dep_levels', '2'),
    ('compact_tier2_days', '90'),
    ('compact_tier2_dep_levels', '5'),
    ('compact_tier2_commits', '100'),
    ('compact_model', 'claude-3-5-haiku-20241022'),
    ('compact_batch_size', '50'),
    ('compact_parallel_workers', '5'),
    ('auto_compact_enabled', 'false');
```

Add helper functions for loading config into typed struct.

- Config keys created on init
- Existing DBs get defaults on migration
- `bd config get/set` works with all keys
- Type validation (days=int, enabled=bool)
- Documentation in README.md


Implement candidate identification queries

Write SQL queries to identify issues eligible for Tier 1 and Tier 2 compaction based on closure time and dependency status.

Create `internal/storage/sqlite/compact.go` with:

```go
type CompactionCandidate struct {
    IssueID       string
    ClosedAt      time.Time
    OriginalSize  int
    EstimatedSize int
    DependentCount int
}

func (s *SQLiteStorage) GetTier1Candidates(ctx context.Context) ([]*CompactionCandidate, error)
func (s *SQLiteStorage) GetTier2Candidates(ctx context.Context) ([]*CompactionCandidate, error)
func (s *SQLiteStorage) CheckEligibility(ctx context.Context, issueID string, tier int) (bool, string, error)
```

Use recursive CTE for dependency depth checking (similar to ready_issues view).

- Tier 1 query filters by days and dependency depth
- Tier 2 query includes commit/issue count checks
- Dependency checking handles circular deps gracefully
- Performance: <100ms for 10,000 issue database
- Tests cover edge cases (no deps, circular deps, mixed status)


Create Haiku client and prompt templates

Implement Claude Haiku API client with template-based prompts for Tier 1 and Tier 2 summarization.

Create `internal/compact/haiku.go`:

```go
type HaikuClient struct {
    client *anthropic.Client
    model  string
}

func NewHaikuClient(apiKey string) (*HaikuClient, error)
func (h *HaikuClient) SummarizeTier1(ctx context.Context, issue *types.Issue) (string, error)
func (h *HaikuClient) SummarizeTier2(ctx context.Context, issue *types.Issue) (string, error)
```

Use text/template for prompt rendering.

Tier 1 output format:
```
**Summary:** [2-3 sentences]
**Key Decisions:** [bullet points]
**Resolution:** [outcome]
```

Tier 2 output format:
```
Single paragraph ≤150 words covering what was built, why it mattered, lasting impact.
```

- API key from env var or config (env takes precedence)
- Prompts render correctly with templates
- Rate limiting handled gracefully (exponential backoff)
- Network errors retry up to 3 times
- Mock tests for API calls


Implement snapshot creation and restoration

Implement snapshot creation before compaction and restoration capability to undo compaction.

Add to `internal/storage/sqlite/compact.go`:

```go
func (s *SQLiteStorage) CreateSnapshot(ctx context.Context, issue *types.Issue, level int) error
func (s *SQLiteStorage) RestoreFromSnapshot(ctx context.Context, issueID string, level int) error
func (s *SQLiteStorage) GetSnapshots(ctx context.Context, issueID string) ([]*Snapshot, error)
```

Snapshot JSON structure:
```json
{
  "description": "...",
  "design": "...",
  "notes": "...",
  "acceptance_criteria": "...",
  "title": "..."
}
```

- Snapshot created atomically with compaction
- Restore returns exact original content
- Multiple snapshots per issue supported (Tier 1 → Tier 2)
- JSON encoding handles UTF-8 and special characters
- Size calculation is accurate (UTF-8 bytes)


Implement Tier 1 compaction logic

Implement the core Tier 1 compaction process: snapshot → summarize → update.

Add to `internal/compact/compactor.go`:

```go
type Compactor struct {
    store  storage.Storage
    haiku  *HaikuClient
    config *CompactConfig
}

func New(store storage.Storage, apiKey string, config *CompactConfig) (*Compactor, error)
func (c *Compactor) CompactTier1(ctx context.Context, issueID string) error
func (c *Compactor) CompactTier1Batch(ctx context.Context, issueIDs []string) error
```

Process:
1. Verify eligibility
2. Calculate original size
3. Create snapshot
4. Call Haiku for summary
5. Update issue (description=summary, clear design/notes/criteria)
6. Set compaction_level=1, compacted_at=now, original_size
7. Record EventCompacted
8. Mark dirty for export

- Single issue compaction works end-to-end
- Batch processing with parallel workers (5 concurrent)
- Errors don't corrupt database (transaction rollback)
- EventCompacted includes size savings
- Dry-run mode (identify + size estimate only, no API calls)


Implement Tier 2 compaction logic

Implement Tier 2 ultra-compression: more aggressive summarization and optional event pruning.

Add to `internal/compact/compactor.go`:

```go
func (c *Compactor) CompactTier2(ctx context.Context, issueID string) error
func (c *Compactor) CompactTier2Batch(ctx context.Context, issueIDs []string) error
```

Process:
1. Verify issue is at compaction_level = 1
2. Check Tier 2 eligibility (days, deps, commits/issues)
3. Create Tier 2 snapshot
4. Call Haiku with ultra-compression prompt
5. Update issue (description = single paragraph, clear all other fields)
6. Set compaction_level = 2
7. Optionally prune events (keep created/closed, archive rest to snapshot)

- Requires existing Tier 1 compaction
- Git commit counting works (with fallback to issue counter)
- Events optionally pruned (config: compact_events_enabled)
- Archived events stored in snapshot JSON
- Size reduction 90-95%


Add `bd compact` CLI command

Implement the `bd compact` command with dry-run, batch processing, and progress reporting.

Create `cmd/bd/compact.go`:

```go
var compactCmd = &cobra.Command{
    Use:   "compact",
    Short: "Compact old closed issues to save space",
}

Flags:
  --dry-run              Preview without compacting
  --tier int            Compaction tier (1 or 2, default: 1)
  --all                 Process all candidates
  --id string           Compact specific issue
  --force               Force compact (bypass checks, requires --id)
  --batch-size int      Issues per batch
  --workers int         Parallel workers
  --json                JSON output
```

- `--dry-run` shows accurate preview with size estimates
- `--all` processes all candidates
- `--id` compacts single issue
- `--force` bypasses eligibility checks (only with --id)
- Progress bar for batches (e.g., [████████] 47/47)
- JSON output with `--json`
- Exit codes: 0=success, 1=error
- Shows summary: count, size saved, cost, time


Optimize reference updates to avoid loading all issues into memory

In updateReferences(), we call SearchIssues with no filter to get ALL issues for updating references. For large databases (10k+ issues), this loads everything into memory. Options: 1) Use batched processing with LIMIT/OFFSET, 2) Use SQL UPDATE with REPLACE() directly, 3) Stream results instead of loading all at once. Located in collision.go:266


Add `bd compact --restore` functionality

Implement restore command to undo compaction from snapshots.

Add to `cmd/bd/compact.go`:

```go
var compactRestore string

compactCmd.Flags().StringVar(&compactRestore, "restore", "", "Restore issue from snapshot")
```

Process:
1. Load snapshot for issue
2. Parse JSON content
3. Update issue with original content
4. Set compaction_level = 0, compacted_at = NULL, original_size = NULL
5. Record event (EventRestored or EventUpdated)
6. Mark dirty for export

- Restores exact original content
- Handles multiple snapshots (use latest by default)
- `--level` flag to choose specific snapshot
- Updates compaction_level correctly
- Exports restored content to JSONL
- Shows before/after in output

Won't fix - snapshots defeat the purpose of compaction


Add `bd compact --stats` command

Add statistics command showing compaction status and potential savings.

```go
var compactStats bool

compactCmd.Flags().BoolVar(&compactStats, "stats", false, "Show compaction statistics")
```

Output:
- Total issues, by compaction level (0, 1, 2)
- Current DB size vs estimated uncompacted size
- Space savings (KB/MB and %)
- Candidates for each tier with size estimates
- Estimated API cost (Haiku pricing)

- Accurate counts by compaction_level
- Size calculations include all text fields (UTF-8 bytes)
- Shows candidates with eligibility reasons
- Cost estimation based on current Haiku pricing
- JSON output supported
- Clear, readable table format


Add EventCompacted to event system

Add new event type for tracking compaction in audit trail.

1. Add to `internal/types/types.go`:
```go
const EventCompacted EventType = "compacted"
```

2. Record event during compaction:
```go
eventData := map[string]interface{}{
    "tier": tier,
    "original_size": originalSize,
    "compressed_size": compressedSize,
    "reduction_pct": (1 - float64(compressedSize)/float64(originalSize)) * 100,
}
```

3. Update event display in `bd show`.

- Event includes tier, original_size, compressed_size, reduction_pct
- Shows in event history (`bd events <id>`)
- Exports to JSONL correctly
- `bd show` displays compaction status and marker


Add compaction indicator to `bd show`

Update `bd show` command to display compaction status prominently.

Add to issue display:
```
bd-42: Fix authentication bug [CLOSED] 🗜

Status: closed (compacted L1)
...

---
💾 Restore: bd compact --restore bd-42
📊 Original: 2,341 bytes | Compressed: 468 bytes (80% reduction)
🗜 Compacted: 2025-10-15 (Tier 1)
```

Emoji indicators:
- Tier 1: 🗜
- Tier 2: 📦

- Compaction status visible in title line
- Footer shows size savings when compacted
- Restore command shown for compacted issues
- Works with `--json` output (includes compaction fields)
- Emoji optional (controlled by config or terminal detection)


Write compaction tests

Comprehensive test suite for compaction functionality.

Test coverage:

1. **Candidate Identification:**
   - Eligibility by time
   - Dependency depth checking
   - Mixed status dependents
   - Edge cases (no deps, circular deps)

2. **Snapshots:**
   - Create and restore
   - Multiple snapshots per issue
   - Content integrity (UTF-8, special chars)

3. **Tier 1 Compaction:**
   - Single issue compaction
   - Batch processing
   - Error handling (API failures)

4. **Tier 2 Compaction:**
   - Requires Tier 1
   - Events pruning
   - Commit counting fallback

5. **CLI:**
   - All flag combinations
   - Dry-run accuracy
   - JSON output parsing

6. **Integration:**
   - End-to-end flow
   - JSONL export/import
   - Restore verification

- Test coverage >80%
- All edge cases covered
- Mock Haiku API in tests (no real API calls)
- Integration tests pass
- `go test ./...` passes
- Benchmarks for performance-critical paths


Add compaction documentation

Document compaction feature in README and create detailed COMPACTION.md guide.

**Update README.md:**
- Add to Features section
- CLI examples (dry-run, compact, restore, stats)
- Configuration guide
- Cost analysis

**Create COMPACTION.md:**
- How compaction works (architecture overview)
- When to use each tier
- Detailed cost analysis with examples
- Safety mechanisms (snapshots, restore, dry-run)
- Troubleshooting guide
- FAQ

**Create examples/compaction/:**
- `workflow.sh` - Example monthly compaction workflow
- `cron-compact.sh` - Cron job setup
- `auto-compact.sh` - Auto-compaction script

- README.md updated with compaction section
- COMPACTION.md comprehensive and clear
- Examples work as documented (tested)
- Screenshots or ASCII examples included
- API key setup documented (env var vs config)
- Covers common questions and issues


Optional: Implement auto-compaction

Implement automatic compaction triggered by certain operations when enabled via config.

Trigger points (when `auto_compact_enabled = true`):
1. `bd stats` - check and compact if candidates exist
2. `bd export` - before exporting
3. Configurable: on any read operation after N candidates accumulate

Add:
```go
func (s *SQLiteStorage) AutoCompact(ctx context.Context) error {
    enabled, _ := s.GetConfig(ctx, "auto_compact_enabled")
    if enabled != "true" {
        return nil
    }

    // Run Tier 1 compaction on all candidates
    // Limit to batch_size to avoid long operations
    // Log activity for transparency
}
```

- Respects auto_compact_enabled config (default: false)
- Limits batch size to avoid blocking operations
- Logs compaction activity (visible with --verbose)
- Can be disabled per-command with `--no-auto-compact` flag
- Only compacts Tier 1 (Tier 2 remains manual)
- Doesn't run more than once per hour (rate limiting)


Optional: Add git commit counting

Implement git commit counting for "project time" measurement as alternative to calendar time for Tier 2 eligibility.

```go
func getCommitsSince(closedAt time.Time) (int, error) {
    cmd := exec.Command("git", "rev-list", "--count",
        fmt.Sprintf("--since=%s", closedAt.Format(time.RFC3339)), "HEAD")
    output, err := cmd.Output()
    if err != nil {
        return 0, err  // Not in git repo or git not available
    }
    return strconv.Atoi(strings.TrimSpace(string(output)))
}
```

Fallback strategies:
1. Git commit count (preferred)
2. Issue counter delta (store counter at close time, compare later)
3. Pure time-based (90 days)

- Counts commits since closed_at timestamp
- Handles git not available gracefully (falls back)
- Fallback to issue counter delta works
- Configurable via compact_tier2_commits config key
- Tested with real git repo
- Works in non-git environments


Explore in-memory embedded SQL alternatives to SQLite

Investigate lightweight in-memory embedded SQL databases as alternative backends for environments where SQLite is problematic or considered too heavyweight. This would provide flexibility for different deployment scenarios.

Research options:
- modernc.org/sqlite (pure Go SQLite implementation, no cgo)
- rqlite (distributed SQLite with Raft)
- go-memdb (in-memory database by HashiCorp)
- badger (embedded key-value store, would need SQL layer)
- bbolt (embedded key-value store)
- duckdb (lightweight analytical database)

Evaluate on:
- Memory footprint vs SQLite
- cgo dependency (pure Go preferred)
- SQL compatibility level
- Transaction support
- Performance characteristics
- Maintenance/community status
- Migration complexity from SQLite

Consider creating a storage abstraction layer to support multiple backends.

- Document comparison of at least 3 alternatives
- Benchmark memory usage and performance vs SQLite
- Assess migration effort for each option
- Recommendation on whether to support alternatives
- If yes, prototype storage interface abstraction

Worth noting: modernc.org/sqlite is a pure Go implementation (no cgo) that might already address the "heavyweight" concern, since much of SQLite's overhead comes from cgo calls. Should evaluate this first before exploring completely different database technologies.


Add label and title filtering to bd list

Add --label and --title flags to bd list command for better filtering. Labels backend already exists, just need CLI exposure. Title search needs both backend and CLI implementation.


Cache compiled regexes in replaceIDReferences for performance

replaceIDReferences() compiles the same regex patterns on every call. With 100 issues and 10 ID mappings, that's 1000 regex compilations. Pre-compile regexes once and reuse. Can use a struct with compiled regex, placeholder, and newID. Located in collision.go:329. Estimated performance improvement: 10-100x for large batches.


Detect and handle Git merge conflicts in JSONL auto-import

**Problem**: Git merge conflicts in .beads/issues.jsonl break auto-import silently. When JSONL contains conflict markers (<<<<<<< HEAD, =======, >>>>>>>), JSON parser fails and entire import is skipped. System only shows parse error but doesn't identify it as a merge conflict.

**Impact**: Critical - blocks all auto-import until manually resolved.

**Solution**:
1. Add pre-parse merge conflict detection in autoImportIfNewer()
2. Check for conflict markers before JSON parsing
3. Provide helpful error message with resolution instructions
4. Consider auto-resolution strategies:
   - Parse each section separately (HEAD vs incoming)
   - Detect collisions between sections
   - Auto-resolve with --resolve-collisions logic
   - OR abort with clear merge resolution instructions

**Code locations**:
- cmd/bd/main.go:autoImportIfNewer() (lines 164-398)
- cmd/bd/import.go (manual import with better error handling)
- internal/storage/sqlite/collision.go (collision detection)

- Pre-parse detection catches merge conflict markers
- Clear error message identifies it as merge conflict
- Error message includes resolution steps
- Test case with synthetic merge conflict
- Consider: automatic merge resolution (stretch goal)


Epic: Git-Based Auto-Sync for Multi-Device Support

Implement git-based auto-sync to enable multi-device workflows while maintaining beads' local-first, lightweight philosophy. Build on existing JSONL export/import infrastructure and collision detection.

See DESIGN-GIT-SYNC.md for comprehensive architectural design.

Core principle: Git + JSONL remains source of truth. No required server. Optional lightweight sync gateway for users who want push-button multi-device support.

- Phase 1: Enhanced git-based sync (no server) completed
- Phase 2: Optional bd serve gateway implemented
- Phase 3: Client gateway integration working
- Phase 4: Production-ready with CRDT conflict resolution
- All existing functionality preserved (backward compatible)
- Documentation complete

---


Phase 1: Implement bd sync command

Create bd sync command to manually synchronize with git remote in a single operation.

Command should:
1. Export pending changes to JSONL
2. Commit changes to git
3. Pull from remote (with conflict resolution)
4. Push local commits to remote

Wraps the entire sync workflow in one command for better UX.

- bd sync command implemented
- Exports dirty changes to JSONL automatically
- Commits to git with descriptive message
- Pulls and handles merge conflicts
- Auto-imports updated JSONL
- Pushes to remote
- Error handling for git failures
- Tests cover success and failure scenarios
- Documentation in README.md

---


Phase 1: Implement bd daemon command

Create bd daemon command to run background sync process with configurable interval.

Daemon should:
- Run in background (fork or systemd service)
- Poll for changes at configurable interval (default: 5 minutes)
- Export dirty issues to JSONL
- Auto-commit if --auto-commit flag set
- Auto-push if --auto-push flag set
- Pull remote changes periodically
- Auto-import when remote changes detected
- Log sync activity

Command flags:
- --interval=5m (sync check interval)
- --auto-commit (automatically commit changes)
- --auto-push (automatically push commits)
- --stop (stop running daemon)
- --status (show daemon status)

- bd daemon command implemented
- Background process management working
- Configurable sync interval
- Auto-commit functionality
- Auto-push functionality
- PID file for process management
- Graceful shutdown on SIGTERM
- Logging to file or syslog
- Status command shows daemon state
- Tests for daemon lifecycle
- Documentation in README.md

---


Phase 1: Create enhanced git hooks examples

Create comprehensive git hooks in examples/git-hooks/ for immediate sync without daemon.

Hooks to create:
1. post-commit: Force immediate export, amend commit with JSONL changes
2. post-merge: Auto-import with collision resolution
3. pre-push: Ensure JSONL is up-to-date
4. post-checkout: Auto-import when switching branches

Installation script: examples/git-hooks/install.sh

- post-commit hook that auto-exports
- post-merge hook that auto-imports with --resolve-collisions
- pre-push hook validates JSONL is synced
- post-checkout hook imports branch changes
- install.sh script copies hooks to .git/hooks/
- Documentation in examples/git-hooks/README.md
- Hooks are executable
- Error handling in all hooks
- Tests verify hook behavior

---


Phase 1: Implement three-way merge for conflicts

Implement three-way merge algorithm to intelligently resolve conflicts by comparing base, local, and remote versions.

Three-way merge logic:
- Get base version from git history (last common ancestor)
- Compare local vs base: if same, use remote (remote changed)
- Compare remote vs base: if same, use local (local changed)
- If both changed: mark as conflict for manual resolution

Field-by-field merging for Issue type:
- title, description, design, acceptance_criteria, notes (text fields)
- status, priority, issue_type (enum fields)
- labels (set merge - union)
- dependencies (set merge - union)

Return MergeResult with:
- MergedVersion (auto-merged fields)
- Conflicts (list of fields requiring manual resolution)

- ThreeWayMerge function implemented
- GetLastSyncedVersion retrieves git base version
- Field-by-field merge logic for all Issue fields
- Union merge for labels and dependencies
- Conflict detection for incompatible changes
- MergeResult type defined
- Integration with import collision detection
- Tests cover all merge scenarios
- Documentation in DESIGN-GIT-SYNC.md

---


Phase 1: Add version vectors for causal ordering

Add optional version vector metadata to Issues for detecting causal relationships and enabling better conflict resolution.

Add sync_metadata to Issue JSON:
```json
{
  "sync_metadata": {
    "version": 3,
    "last_modified_by": "device-a",
    "version_vector": {
      "device-a": 2,
      "device-b": 1
    }
  }
}
```

Version vector tracks logical clock per device:
- Incremented on each update
- Used to determine causality (happened-before)
- Enables automatic conflict resolution for concurrent edits

Optional field - backward compatible with existing JSONL.

- SyncMetadata type defined
- Version vector in Issue struct (optional)
- Device ID generation and storage
- Increment version on update
- Version vector comparison functions
- Causal ordering detection
- Integration with collision detection
- Backward compatible JSONL parsing
- Tests for version vector logic
- Documentation in DESIGN-GIT-SYNC.md

---


Phase 1: Improve collision UX with interactive resolution

Enhance collision handling UX with interactive prompts for users to choose resolution strategy.

When collision detected during sync, prompt:
```
⚠ Collision detected on bd-10 (conflicting fields: priority, title)

Options:
  1. Keep local change
  2. Accept remote change
  3. Remap remote to new ID (recommended)
  4. Resolve manually

Choose [1-4]:
```

Add bd resolve command for manual resolution:
- Opens editor with three-way diff
- User edits merged version
- Validates and applies resolution

- Interactive collision prompt implemented
- User can choose resolution strategy
- bd resolve command implemented
- Three-way diff display in editor
- Manual edit and apply workflow
- Validation of resolved version
- Tests for all resolution paths
- Documentation in README.md

---


Phase 1: Add sync state machine tracking

Implement sync state tracking to show users current sync status and guide next actions.

Sync states:
- clean: DB = JSONL = Git (all synced)
- dirty: DB ≠ JSONL (pending flush)
- conflict: JSONL ≠ DB (collision detected)
- ahead: Local commits not pushed
- behind: Remote commits not pulled
- diverged: Both ahead and behind

Add CheckSyncState() function that:
- Checks dirty issues count
- Runs git status to check ahead/behind
- Compares JSONL hash with DB
- Returns SyncState

Display in bd status command.

- SyncState enum defined
- CheckSyncState() implemented
- Git status parsing (ahead/behind commits)
- Dirty issues detection
- JSONL hash comparison
- bd status shows sync state
- Color-coded status display
- Guidance on next action
- Tests for all states
- Documentation in README.md

---


Phase 1: Add tests for concurrent edit scenarios

Create comprehensive test suite for concurrent editing scenarios across multiple devices.

Test scenarios:
1. Two devices edit different fields (should merge cleanly)
2. Two devices edit same field (collision)
3. One device edits closed issue (local wins)
4. Race condition: simultaneous push (git handles)
5. Three-way merge with base version
6. Version vector causal ordering
7. Union merge for labels
8. Dependency conflict resolution

Use test fixtures with pre-created git history.

- Test suite in cmd/bd/sync_test.go
- All 8+ concurrent edit scenarios covered
- Test fixtures with git history
- Mock git operations where needed
- Tests verify correct merge behavior
- Tests verify collision detection
- Tests run in CI
- Documentation in test comments

---


Improve error handling in dependency removal during remapping

In updateDependencyReferences(), RemoveDependency errors are caught and ignored with continue (line 392). Comment says 'if dependency doesn't exist' but this catches ALL errors including real failures. Should check error type with errors.Is(err, ErrDependencyNotFound) and only ignore not-found errors, returning other errors properly.


Phase 1: Document git-based sync workflow

Add comprehensive documentation for git-based sync workflow to README.md and create dedicated SYNC.md guide.

Documentation should cover:
- Manual git workflow (current)
- bd sync command usage
- bd daemon for background sync
- Git hooks installation
- Conflict resolution strategies
- Troubleshooting common issues
- Best practices for multi-device work

Create SYNC.md with:
- Quick start guide
- Detailed workflow examples
- Conflict resolution guide
- FAQ section

- README.md updated with sync overview
- SYNC.md created with detailed guide
- Examples for all sync workflows
- Conflict resolution examples
- Troubleshooting section
- FAQ section
- Diagrams for workflows (optional)
- Links from AGENTS.md

---


Phase 2: Implement bd serve command

Create bd serve command to start HTTP API gateway server that wraps bd CLI commands.

Gateway server provides:
- REST API wrapping bd commands
- WebSocket for real-time notifications
- API key authentication
- Auto-commit on changes
- Periodic git pull
- Webhook notifications to clients

Command flags:
- --port=8080 (HTTP port)
- --api-key=<key> (auth key, auto-generated if not provided)
- --auto-commit (commit changes immediately)
- --auto-pull=5m (pull interval)
- --allowed-ips=<list> (IP whitelist)

Architecture: Thin wrapper that executes bd CLI commands and manages git operations.

- bd serve command implemented
- HTTP server with route handlers
- API key authentication
- Auto-commit on CRUD operations
- Background git pull task
- WebSocket server for notifications
- Graceful shutdown on SIGTERM
- Logging of all API requests
- Tests for server lifecycle
- Documentation in README.md

---


Phase 2: Implement REST API endpoints

Implement REST API endpoints that wrap bd CLI commands for remote access.

API endpoints:
- GET /api/health - server status
- GET /api/sync/status - git sync state
- POST /api/sync/pull - trigger pull + import
- POST /api/sync/push - trigger export + push
- GET /api/issues - list issues
- POST /api/issues - create issue
- GET /api/issues/:id - get issue
- PATCH /api/issues/:id - update issue
- DELETE /api/issues/:id - close issue
- POST /api/issues/:id/labels - add label
- DELETE /api/issues/:id/labels/:l - remove label
- POST /api/issues/:id/deps - add dependency
- DELETE /api/issues/:id/deps/:d - remove dependency

All endpoints return JSON.
All mutations require authentication.
All responses include proper HTTP status codes.

- All API endpoints implemented
- Handlers wrap bd CLI commands
- JSON request/response format
- Proper HTTP status codes
- Error responses with details
- Authentication on mutation endpoints
- CORS headers for web clients
- Rate limiting (optional)
- API tests for all endpoints
- OpenAPI/Swagger spec
- Documentation in API.md

---


Phase 2: Implement WebSocket notifications

Implement WebSocket server for real-time push notifications to connected clients.

WebSocket protocol:
- Client subscribes to events (issue.created, issue.updated, etc.)
- Server pushes notifications when events occur
- Clients can filter by status, priority, labels
- Heartbeat to detect disconnections

Events:
- issue.created
- issue.updated
- issue.closed
- sync.required
- sync.complete

Message format: JSON with type, timestamp, data.

- WebSocket server at /api/subscribe
- Client subscription with filters
- Event notification on CRUD ops
- Heartbeat/ping-pong
- Client disconnect handling
- Reconnection support
- Multiple concurrent clients
- Tests for WebSocket protocol
- Client library example (Go)
- Documentation in API.md

---


Phase 2: Create Docker packaging for gateway

Create Dockerfile and docker-compose.yml for easy gateway deployment.

Multi-stage Dockerfile:
1. Builder stage: compile Go binary
2. Runtime stage: Alpine with git, sqlite
3. Initialize git config
4. Expose port 8080
5. Volume mount for /data

docker-compose.yml:
- Service: beads-gateway
- Environment variables for config
- Volume mount for persistence
- Restart policy

Include .dockerignore for efficient builds.

- Dockerfile with multi-stage build
- docker-compose.yml with config
- .dockerignore file
- Build script (scripts/docker-build.sh)
- Environment variable configuration
- Volume mount for data persistence
- README.md with Docker instructions
- Published to Docker Hub (optional)
- Tests for Docker deployment
- Documentation in DEPLOYMENT.md

---


Phase 2: Implement API key authentication

Implement secure API key authentication for gateway API access.

Authentication:
- Bearer token in Authorization header
- Single API key for personal use
- Optional IP whitelist for additional security
- Generate secure random key on first start
- Store hashed key in config file

API key generation:
- 32 bytes of crypto/rand
- Base64 encoded
- Display once on generation

Config storage:
- ~/.beads/gateway-config.json
- Contains hashed API key
- Optional allowed_ips list

- API key generation function
- Secure storage in config file
- Bearer token authentication
- Middleware for auth checking
- IP whitelist support (optional)
- 401 response for invalid auth
- Key rotation support
- Tests for authentication
- Documentation in SECURITY.md

---


Phase 2: Add gateway stress tests

Create stress tests to validate gateway performance under load and ensure it can handle production traffic.

Test scenarios:
1. Concurrent request handling (100+ clients)
2. Large issue database (10,000+ issues)
3. WebSocket connection limits
4. Git lock contention
5. Memory usage under load
6. Response time percentiles (p50, p95, p99)

Use Go testing tools:
- testing.B for benchmarks
- net/http/httptest for server testing
- goroutines for concurrent clients

- Stress test suite in cmd/bd/serve_stress_test.go
- Concurrent client test (100+ requests/sec)
- Large database test (10k+ issues)
- WebSocket stress test
- Memory profiling test
- Response time benchmarks
- Git operation serialization test
- Performance regression detection
- Documentation in PERFORMANCE.md

---


Phase 2: Create API client library (Go)

Create Go client library for accessing beads gateway API.

Client library should provide:
- Type-safe API calls
- Authentication handling
- Error handling and retries
- WebSocket subscription
- Automatic reconnection
- Context support for cancellation

Package: github.com/steveyegge/beads/client

Example usage:
```go
client := beads.NewClient("http://gateway:8080", "api-key")
issue, err := client.GetIssue(ctx, "bd-10")
```

- Go client library package
- All API endpoints wrapped
- Type-safe request/response
- Authentication handling
- Retry logic for transient errors
- WebSocket client support
- Context support
- Example usage in examples/
- Tests for client library
- Documentation in CLIENT.md

---


Phase 2: Document gateway API

Create comprehensive API documentation for gateway with examples and OpenAPI spec.

Documentation should include:
- Authentication guide
- All endpoint descriptions
- Request/response examples
- Error codes and handling
- WebSocket protocol
- Rate limits
- Best practices

Create OpenAPI 3.0 specification for:
- Automated client generation
- API testing tools
- Documentation generation

- API.md with full documentation
- OpenAPI 3.0 spec (api-spec.yaml)
- Request/response examples for all endpoints
- Authentication examples
- WebSocket protocol documentation
- Error response documentation
- Code examples in multiple languages
- Postman collection (optional)
- Link from README.md

---


Phase 3: Implement bd config command for gateway

Create bd config command to manage gateway configuration for transparent client-side gateway use.

Config commands:
- bd config set gateway <url>
- bd config set api_key <key>
- bd config get gateway
- bd config get api_key
- bd config unset gateway
- bd config list

Config storage: ~/.beads/config.json

Config keys:
- gateway_url: URL of gateway server
- api_key: Authentication key
- auto_sync: Enable background sync
- sync_interval: Sync check interval

- bd config command implemented
- Set/get/unset/list subcommands
- Config file at ~/.beads/config.json
- Validation of config values
- Secure storage of API key
- Tests for config operations
- Documentation in README.md

---


Use safer placeholder pattern in replaceIDReferences

Currently uses __PLACEHOLDER_0__ which could theoretically collide with user text. Use a truly unique placeholder like null bytes: \x00REMAP\x00_0_\x00 which are unlikely to appear in normal text. Located in collision.go:324. Very low probability issue but worth fixing for completeness.


Phase 3: Add transparent gateway detection

Modify bd commands to transparently use gateway API when configured, with automatic fallback to local storage.

Update storage layer to:
1. Check for gateway config
2. If gateway configured, use GatewayClient
3. If gateway unavailable, fall back to local SQLite
4. Cache gateway responses for offline use

Transparent for these commands:
- bd create
- bd update
- bd close
- bd list
- bd show
- bd label
- bd dep

No changes to command interface - works identically.

- Gateway detection in storage layer
- GatewayClient wraps API calls
- Automatic fallback to local on error
- Response caching for offline mode
- All CRUD commands work via gateway
- Offline mode preserves functionality
- Tests for gateway mode
- Tests for fallback behavior
- Documentation in README.md

---


Phase 3: Implement client-side sync daemon

Create client-side background sync daemon for automatic synchronization with gateway.

Sync daemon:
- Runs in background
- Polls gateway for updates every 30s
- Pushes local changes to gateway
- Handles sync conflicts
- Logs sync activity

Commands:
- bd sync-daemon start
- bd sync-daemon stop
- bd sync-daemon status

Uses gateway WebSocket for real-time updates if available.

- Client sync daemon implemented
- Background process management
- Polling gateway for changes
- Push local changes to gateway
- WebSocket subscription for real-time
- Conflict resolution
- PID file management
- Status command
- Tests for sync daemon
- Documentation in README.md

---


Phase 3: Add WebSocket subscription client

Implement WebSocket client for subscribing to gateway notifications and receiving real-time updates.

WebSocket client:
- Connects to gateway /api/subscribe
- Sends subscription request with filters
- Receives event notifications
- Handles disconnection and reconnection
- Updates local DB on events

Integration with sync daemon:
- Daemon subscribes to all events
- On notification, triggers immediate sync
- Reduces polling overhead

- WebSocket client implementation
- Subscription with event filters
- Event handler callbacks
- Auto-reconnection logic
- Integration with sync daemon
- Local DB update on events
- Connection state management
- Tests for WebSocket client
- Documentation in CLIENT.md

---


Phase 3: Add offline mode with local cache

Implement offline mode that caches gateway responses locally for continued operation when gateway is unavailable.

Offline cache:
- SQLite cache of gateway responses
- Automatic cache population on sync
- Serve reads from cache when offline
- Queue writes for later sync
- Conflict detection on reconnect

Cache strategy:
- Read-through: Check cache, then gateway
- Write-behind: Queue, sync on reconnect
- TTL for cache entries
- Manual cache clear command

- Local cache database
- Read-through caching
- Write queue for offline writes
- Sync queued writes on reconnect
- Conflict detection after offline
- Cache TTL and expiration
- bd cache command (clear, status)
- Tests for offline scenarios
- Documentation in README.md

---


Phase 3: Create gateway integration tests

Create end-to-end integration tests that verify gateway and client working together.

Integration test scenarios:
1. Client connects to gateway
2. Create issue via gateway
3. List issues via gateway
4. Update issue via gateway
5. WebSocket notification received
6. Sync between multiple clients
7. Offline mode and reconnect
8. Conflict resolution

Use Docker Compose to spin up gateway for tests.

- Integration test suite
- Docker Compose for test environment
- All CRUD operations tested
- WebSocket notification test
- Multi-client sync test
- Offline/reconnect test
- Conflict resolution test
- Tests run in CI
- Documentation in TESTING.md

---


Phase 3: Document client gateway setup

Create comprehensive documentation for setting up and using gateway mode.

Documentation should cover:
- Gateway deployment (Docker)
- Client configuration (bd config)
- Transparent gateway mode
- Sync daemon setup
- Offline mode
- Troubleshooting
- Best practices

Create GATEWAY.md with:
- Quick start guide
- Deployment options
- Configuration reference
- Usage examples
- FAQ

- GATEWAY.md created
- Quick start guide
- Deployment instructions (Docker, binary)
- Client setup instructions
- Sync daemon guide
- Offline mode documentation
- Troubleshooting section
- FAQ section
- Links from README.md

---


Phase 4: Implement CRDT for automatic conflict resolution

Implement Conflict-free Replicated Data Type (CRDT) semantics for automatic merge of concurrent edits without conflicts.

CRDT strategies per field type:
- Single-value fields (title, status): Last-Write-Wins with version vectors
- Set fields (labels): G-Set (grow-only, union merge)
- List fields (events): RGA (Replicated Growable Array)
- Text fields: Operational transforms or delta-based

Add CRDT metadata:
- Version vector per device
- Logical clock per update
- Tombstones for deletions

Requires sync_metadata in JSONL (optional, backward compatible).

- CRDT logic for all field types
- Last-Write-Wins for single-value
- Union merge for sets
- RGA for lists
- Version vector comparison
- Automatic conflict resolution
- No manual resolution needed
- Tests for concurrent updates
- Documentation in DESIGN-GIT-SYNC.md

---


Phase 4: Add operational transforms for text fields

Implement operational transforms (OT) for text fields to enable real-time collaborative editing of descriptions and notes.

OT for text fields:
- description
- design
- acceptance_criteria
- notes

Operations:
- Insert(pos, text)
- Delete(pos, len)
- Transform operations for concurrent edits

Requires real-time sync via WebSocket.

Alternative: Use simpler delta-based approach with three-way merge.

- OT implementation for text fields
- Insert/Delete operations
- Transform function for concurrent ops
- Integration with WebSocket sync
- Tests for concurrent text edits
- Performance acceptable for large text
- Documentation in DESIGN-GIT-SYNC.md

---


Phase 4: Add sync status to bd status command

Enhance bd status command to show detailed sync status including gateway connection, sync state, and conflicts.

bd status should display:
- Sync state (clean/dirty/ahead/behind/diverged/conflict)
- Gateway connection status (connected/offline)
- Last sync timestamp
- Pending changes count
- Conflicts count
- Sync daemon status (running/stopped)

Format:
```
Sync Status:
  State: clean
  Gateway: connected (http://gateway:8080)
  Last sync: 2 minutes ago
  Pending: 0 issues
  Conflicts: 0
  Daemon: running (PID 12345)
```

- Sync status in bd status output
- Gateway connection check
- Sync state display
- Pending changes count
- Conflicts list
- Daemon status
- Color-coded output
- Tests for status display
- Documentation in README.md

---


Phase 4: Add metrics and monitoring

Add metrics collection and monitoring for gateway operations and sync performance.

Metrics to collect:
- API request count and latency
- Sync operation count and duration
- Conflict resolution count
- WebSocket connection count
- Git operation duration
- Database query performance

Expose metrics:
- Prometheus format at /metrics
- JSON format at /api/metrics

Optional: Grafana dashboard template.

- Metrics collection implemented
- Prometheus exposition format
- Key metrics covered (requests, latency, sync)
- /metrics endpoint
- /api/metrics JSON endpoint
- Grafana dashboard template
- Documentation in MONITORING.md

---


Normal task


Remove unused issueMap in scoreCollisions

scoreCollisions() creates issueMap and populates it (lines 135-138) but never uses it. Either remove it or add a TODO comment explaining future use. Located in collision.go:135-138. Cosmetic cleanup.


Git-based restoration for compacted issues

Store git commit hash at compaction time to enable restoration of full issue history from version control. When issues are compacted, record the current git commit hash so users can restore the original uncompacted issue from git history.


Add compacted_at_commit field to Issue type

Add optional compacted_at_commit string field to store git commit hash when issue is compacted


Record git commit hash during compaction

Update compact command to capture current git HEAD commit hash and store in compacted_at_commit field


Implement bd restore command

Create new restore command that checks out git commit from compacted_at_commit, reads issue from JSONL, and displays full history


Add tests for git-based restoration

Test compaction stores commit hash, restore command works, handles missing git repo gracefully


Document git-based restoration feature

Update COMPACTION.md and README.md with restoration workflow and examples


Concurrent bd commands collide on shared .tmp filename

When multiple bd commands run concurrently (e.g., different agents), they all write to .beads/issues.jsonl.tmp causing race conditions. One command's rename succeeds, consuming the .tmp file, then other commands fail with 'no such file or directory'. Solution: use unique temp filenames with PID or random suffix (e.g., issues.jsonl.tmp.12345).


Fix circular dependency detection and prevention

Implement robust detection, prevention, and user guidance for circular dependencies that cross edge types. Current system only prevents cycles within "blocks" type dependencies, allowing cross-type cycles (e.g., A blocks B, B parent-child A) that can hide work from ready list and confuse dependency visualization.

Analysis shows that cycle prevention is type-specific (blocks only) while detection is type-agnostic. All current operations are safe from infinite loops (depth-limited), but semantic confusion and future maintenance hazards exist. Need validation, warnings, and potentially full cross-type prevention.

- Cross-type cycles are prevented or warned about
- Semantic validation prevents child→parent dependencies
- Users receive clear diagnostic messages
- Documentation explains cycle handling behavior
- All existing safe operations remain protected


Add semantic validation for parent-child dependency direction

Prevent backwards parent-child dependencies where child tasks depend on their parent epics. This is semantically incorrect - parents should depend on children completing, not the reverse.

Add validation in AddDependency that checks if dep.Type == DepParentChild and validates the direction is correct (parent depends on child, not child on parent). Reject with clear error message if direction is backwards.

- Child→parent dependencies are rejected with clear error
- Parent→child dependencies work as expected
- Error message explains correct direction
- Tests cover both valid and invalid cases


Add diagnostic warnings when cycles are detected after dep add

Run DetectCycles() after adding dependencies and warn users if cycles exist. Provide clear, actionable messages about which issues form the cycle and potential impact on ready work visibility.

In bd dep add command, after successful dependency addition, call DetectCycles(). If cycles found, print warning to stderr showing cycle path and explanation. Include suggestion to run 'bd dep cycles' for full analysis.

- Warnings appear after dep add when cycles exist
- Warning shows cycle path clearly
- Explains impact on ready work calculation
- Suggests next steps for user
- Does not block operation (warning only)


Test issue for design field

Testing the new update flags

## Design Plan\n- Add flags to update command\n- Test thoroughly\n- Document changes

- All three fields (design, notes, acceptance-criteria) can be updated\n- Changes persist in database\n- bd show displays the fields correctly

Implementation complete. All tests passing.


Document cycle handling behavior and limitations

Add comprehensive documentation explaining which dependency types are checked for cycles, why cross-type cycles matter, what operations are safe, and how users should think about dependency design.

Add comments to AddDependency explaining cycle checking logic. Update README.md with section on cycles. Add to DESIGN.md explaining technical decisions. Include examples of problematic and correct dependency patterns.

- Code comments explain cycle checking
- README has user-facing cycle guidance
- DESIGN.md has technical rationale
- Examples show good and bad patterns
- Explains ready work implications


Benchmark cycle detection performance on large dependency graphs

Measure performance impact of cross-type cycle checking on large graphs (1000+ issues). Determine if optimization needed before enabling full prevention. Test various graph structures (sparse, dense, deeply nested).

Create benchmark test with synthetic dependency graphs of varying sizes and structures. Measure AddDependency performance with type-specific vs all-types cycle checking. Document findings and optimization recommendations if needed.

- Benchmark test covers 100, 1000, 5000 issue graphs
- Tests sparse and dense dependency patterns
- Measures time for both checking approaches
- Documents results in comments or DESIGN.md
- Recommends optimization if >100ms impact found


Implement cross-type cycle prevention in AddDependency

Expand cycle detection to check ALL dependency types, not just "blocks" type. Currently A blocks B, B parent-child A is allowed but creates a cross-type cycle that hides work from ready list.

Modify cycle check in AddDependency to traverse all dependency types using recursive CTE without type filter. Check if adding new dependency would create cycle across any combination of types. Include depth limit (100) to prevent infinite traversal.

- Cross-type cycles are prevented
- Error message indicates cycle would be created
- All dependency types are checked in traversal
- Performance is acceptable (add benchmark)
- Tests cover various cross-type cycle scenarios

Implementation complete with oracle feedback addressed:
- Removed type filter from cycle detection (checks all types)
- Extracted maxDependencyDepth=100 constant shared by AddDependency and DetectCycles
- Moved cycle check before INSERT to avoid unnecessary write on failure
- Added index verification (idx_dependencies_issue exists)
- Added 6 comprehensive tests: self-dependency, related cycles (2-node, mixed), cross-type cycles (2-node, 3-node, discovered-from)
All tests pass.


stress_test_6


final_test_10


parallel_test_8


parallel_test_9


bd should auto-detect .beads/*.db in current directory

When bd is run without --db flag, it defaults to beads' own database instead of looking for a .beads/*.db file in the current working directory. This causes confusion when working on other projects that use beads for issue tracking (like vc).

Expected behavior: bd should search for .beads/*.db in cwd and use that if found, before falling back to default beads database.

Example: Running 'bd ready' in /Users/stevey/src/vc/vc/ should automatically find and use .beads/vc.db without requiring --db flag every time.


Document or automate JSONL sync workflow for git collaboration

When using beads across multiple machines/environments via git, there's a workflow gap:

1. Machine A: Create issues → stored in .beads/project.db
2. Machine A: bd export -o .beads/issues.jsonl
3. Machine A: git add .beads/issues.jsonl && git commit && git push
4. Machine B: git pull
5. Machine B: ??? issues.jsonl exists but project.db is empty/stale

The missing step is: bd import --db .beads/project.db -i .beads/issues.jsonl

This needs to be either:
a) Documented clearly in workflow docs
b) Automated (e.g., git hook, or bd auto-imports if jsonl is newer than db)
c) Both

Real-world impact: User had Claude Code on GCP VM create vc issues from BOOTSTRAP.md. They were exported to issues.jsonl and committed. But on local machine, vc.db was empty until manual import was run.


Implement reserved database name _.db

Auto-detection now skips .beads/_.db to prevent pollution when beads dogfoods itself. This allows beads to use its own issue tracker without interfering with other projects using beads in the same directory tree. Implementation includes filtering in findDatabase(), stopping directory walk when .beads/ is found, and documentation in README.md and CLAUDE.md.


Auto-flush JSONL on CRUD operations with 5-second debounce

Implemented automatic write-through from SQLite to JSONL with 5-second debouncing. After any CRUD operation (create, update, close, dep add/remove), changes are scheduled to flush to JSONL after 5 seconds of inactivity. On process exit, any pending changes are flushed immediately. This prevents .db and .jsonl from getting out of sync, solving the workflow gap where agents forget to run 'bd export'. Can be disabled with --no-auto-flush flag. Addresses bd-33.


Handle missing JSONL directory in findJSONLPath

findJSONLPath() assumes the database directory exists. If someone runs bd init to create a new database but the .beads directory doesn't exist yet, the glob operations might fail silently. Add os.MkdirAll(dbDir, 0755) to ensure directory exists before globbing. Located in cmd/bd/main.go:188-201.


Refactor duplicate flush logic in PersistentPostRun

PersistentPostRun contains a complete copy of the flush logic instead of calling flushToJSONL(). This violates DRY principle and makes maintenance harder. Refactor to use flushToJSONL() with a force parameter to bypass isDirty check, or extract shared logic into a helper function. Located in cmd/bd/main.go:104-138.


Add visibility for auto-flush failures

flushToJSONL() writes warnings to stderr when flush fails, but calling code has no way to know if flush succeeded or failed. This means a command could return success even though JSONL is now out of sync. Consider maintaining a 'last flush status' variable or counter for failed flushes, and warn user after multiple consecutive failures (e.g., 3+). Located in cmd/bd/main.go:227-307.


Optimize auto-flush to use incremental updates

Every flush exports ALL issues and ALL dependencies, even if only one issue changed. For large projects (1000+ issues), this could be expensive. Current approach guarantees consistency, which is fine for MVP, but future optimization could track which issues changed and use incremental updates. Located in cmd/bd/main.go:255-276.


final_test_7


race_test_5


Low priority chore


Make auto-flush debounce duration configurable

flushDebounce is hardcoded to 5 seconds. Make it configurable via environment variable BEADS_FLUSH_DEBOUNCE (e.g., '500ms', '10s'). Current 5-second value is reasonable for interactive use, but CI/automated scenarios might want faster flush. Add getDebounceDuration() helper function. Located in cmd/bd/main.go:31.


race_test_4


race_test_8


final_test_10


race_test_16


race_test_17


race_test_10


parallel_test_3


race_test_11


GH-3: Debug zsh killed error on bd init

User reports 'zsh: killed bd init' when running bd init or just bd command. Likely a crash or signal. Need to reproduce and investigate cause.

Awaiting user feedback - cannot reproduce locally, waiting for user to provide more details about environment and error message


stress_test_5


Add godoc comments for auto-flush functions

Add comprehensive godoc comments for findJSONLPath(), markDirtyAndScheduleFlush(), and flushToJSONL() explaining behavior, concurrency considerations, and error handling. Include notes about debouncing behavior (timer resets on each write, flush occurs 5s after LAST operation) and flush-on-exit guarantees. Located in cmd/bd/main.go:188-307.


stress_test_6


stress_test_1


parallel_test_7