Improve test coverage for internal/beads (48.1% → 70%)

Improve test coverage for internal/beads package from 48% to 70%.

## Current State
- Coverage: 48.4%
- Files: beads.go, fingerprint.go
- Tests: beads_test.go (moderate coverage)

## Functions Needing Tests

### beads.go (database discovery)
- [ ] followRedirect - needs redirect file tests
- [ ] findDatabaseInBeadsDir - needs various dir structures
- [x] NewSQLiteStorage - likely covered
- [ ] FindDatabasePath - needs BEADS_DB env var tests
- [ ] hasBeadsProjectFiles - needs file existence tests
- [ ] FindBeadsDir - needs directory traversal tests
- [ ] FindJSONLPath - needs path derivation tests
- [ ] findGitRoot - needs git repo tests
- [ ] findDatabaseInTree - needs nested directory tests
- [ ] FindAllDatabases - needs multi-database tests
- [ ] FindWispDir - needs wisp directory tests
- [ ] FindWispDatabasePath - needs wisp path tests
- [ ] NewWispStorage - needs wisp storage tests
- [ ] EnsureWispGitignore - needs gitignore creation tests
- [ ] IsWispDatabase - needs path classification tests

### fingerprint.go (repo identification)
- [ ] ComputeRepoID - needs various remote URL tests
- [ ] canonicalizeGitURL - needs URL normalization tests
- [ ] GetCloneID - needs clone identification tests

## Implementation Guide

1. **Use temp directories:**
   ```go
   func TestFindBeadsDir(t *testing.T) {
       tmpDir := t.TempDir()
       beadsDir := filepath.Join(tmpDir, ".beads")
       os.MkdirAll(beadsDir, 0755)

       // Create test files
       os.WriteFile(filepath.Join(beadsDir, "beads.db"), []byte{}, 0644)

       // Change to tmpDir and test
       oldWd, _ := os.Getwd()
       os.Chdir(tmpDir)
       defer os.Chdir(oldWd)

       result := FindBeadsDir()
       assert.Equal(t, beadsDir, result)
   }
   ```

2. **Test scenarios:**
   - BEADS_DB environment variable set
   - .beads/ in current directory
   - .beads/ in parent directory
   - Redirect file pointing elsewhere
   - No beads directory found
   - Wisp directory alongside main beads

3. **Git remote URL tests:**
   ```go
   tests := []struct{
       input    string
       expected string
   }{
       {"git@github.com:user/repo.git", "github.com/user/repo"},
       {"https://github.com/user/repo", "github.com/user/repo"},
       {"ssh://git@github.com/user/repo.git", "github.com/user/repo"},
   }
   ```

## Success Criteria
- Coverage ≥ 70%
- All FindXxx functions have tests
- Environment variable handling tested
- Edge cases (missing dirs, redirects) covered

## Run Tests
```bash
go test -v -cover ./internal/beads
go test -race ./internal/beads
```


GH#405: Prefix parsing with hyphens treats first segment as prefix

Prefix me-py-toolkit gets parsed as just me- when detecting mismatches. Fix prefix parsing to handle multi-hyphen prefixes. See GitHub issue #405.


Version Bump: 0.32.1

Release checklist for version 0.32.1. Patch release with MCP output control params and pin field fix.


Run tests

TMPDIR=/tmp go test -short ./...


Thread Test

Testing the thread feature


Review PR #513: fix hooks install docs

Review and merge PR #513 from aspiers. This PR fixes incorrect docs for how to install git hooks - updates README to use bd hooks install instead of removed install.sh. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/513


Rename 'bond' to 'spawn' for instantiation

Rename the bd mol bond command to bd mol spawn for instantiating protos.

- Rename molBondCmd to molSpawnCmd
- Update command Use/Short/Long descriptions
- Keep 'bond' available for the new bonding feature
- Update all documentation references
- Add 'protomolecule' as easter egg alias for 'proto'


bd mol reset: Reset molecule to initial state for looping

Implement bd mol reset for patrol loop behavior.

## Command

```bash
bd mol reset <mol-id>
```

## Behavior

1. Load molecule state from .beads/molecules/<mol-id>.state.yaml
2. Reset all step states to pending
3. Clear current_step (or set to first step)
4. Preserve: bonded_at, bonded_by, variables
5. Update: reset_count++, last_reset_at

## Use Case: Deacon Patrol Loop

```
inbox-check → spawn-work → self-inspect → mol-reset → (loop)
```

The Deacon runs its patrol, then resets the molecule to start fresh.
No gt handoff needed unless context is high.

## State After Reset

```yaml
id: mol-deacon-patrol
status: running
current_step: inbox-check  # Back to first
reset_count: 5
last_reset_at: 2025-12-24T...
steps:
  inbox-check: pending      # All reset
  spawn-work: pending
  self-inspect: pending
```

## Files

- cmd/bd/mol_reset.go
- internal/mol/state.go (add Reset method)


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

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

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

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

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

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

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

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


bd cook: Compile formula to proto

Implement the 'bd cook' command that transforms formulas into protos:

Usage: bd cook <formula-file> [options]
       bd cook .beads/formulas/*.formula.yaml

Process:
1. Parse formula YAML (uses formula parser)
2. Resolve extends (formula inheritance)
3. Expand macros (expansion rules)
4. Apply aspects (cross-cutting concerns)
5. Generate flat proto bead

Output:
- Creates proto bead in .beads/ with mol-prefix
- Proto has all steps pre-expanded
- Proto is ready for pour/wisp instantiation

Example:
  bd cook .beads/formulas/mol-deacon-patrol.formula.yaml
  → Creates mol-deacon-patrol proto bead

Gas Town tracking: gt-8tmz.13


Auto-recovery mode (bd sync --auto-recover)

Add bd sync --auto-recover flag that: detects problematic sync state, backs up .beads/issues.db with timestamp, rebuilds DB from JSONL atomically, verifies consistency, reports what was fixed. Provides safety valve when sync integrity fails.


.beads-ephemeral/ storage backend

Implement ephemeral storage layer for wisps.

## Requirements
- New storage location: .beads-ephemeral/issues.jsonl (sibling to .beads/)
- Gitignored by default (add to .beads/.gitignore)
- Same JSONL format as regular beads
- Config option: ephemeral.directory (relative path)
- ephemeral.enabled config flag

## Storage Behavior
- Ephemeral issues have ephemeral: true field
- No sync to remote (local only)
- No daemon tracking needed (transient)

## Implementation
- Add EphemeralStore in storage package
- Initialize on demand when --ephemeral flag used
- Share Issue struct, just different storage path


Audit and fix JSONL filename mismatches across all repo clones

## Problem

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

## Root Cause

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

## Task

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

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

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

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

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

### Step 3: Create audit report

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

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

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

### Step 5: Generate fix script

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

### Expected Output

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

### Edge Cases

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

### Success Criteria

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


Merge: bd-05a8

branch: polecat/valkyrie
target: main
source_issue: bd-05a8
rig: beads


Refactor mol.go: split 1200+ line file into subcommands

## Problem

cmd/bd/mol.go has grown to 1200+ lines with all molecule subcommands in one file.

## Current State
- mol.go: 1218 lines (bond, spawn, run, distill, catalog, show, etc.)
- Hard to navigate, review, and maintain

## Proposed Structure
Split into separate files by subcommand:
```
cmd/bd/
├── mol.go           # Root command, shared helpers
├── mol_bond.go      # bd mol bond
├── mol_spawn.go     # bd mol spawn
├── mol_run.go       # bd mol run
├── mol_distill.go   # bd mol distill
├── mol_catalog.go   # bd mol catalog
├── mol_show.go      # bd mol show
└── mol_test.go      # Tests (already separate)
```

## Benefits
- Easier code review
- Better separation of concerns
- Simpler navigation
- Each subcommand self-contained


Tier 1: Ephemeral repo routing

Add routing.ephemeral config option to route ephemeral=true issues to separate location.

## Changes Required

1. Add `routing.ephemeral` config option (default: empty = disabled)
2. Update routing logic in `determineRepo()` to check ephemeral flag
3. Update `bd create` to respect ephemeral routing
4. Update import/export for multi-location support
5. Ephemeral repo can be:
   - Separate git repo (~/.beads-ephemeral)
   - Non-git directory (just filesystem)
   - Same repo, different branch (future)

## Config

```bash
bd config set routing.ephemeral "~/.beads-ephemeral"
```

## Acceptance Criteria

- `bd create "test" --ephemeral` creates in ephemeral repo when configured
- `bd list` shows issues from both repos
- Ephemeral repo never synced to remote


Verify MCP Server Compatibility

Ensure MCP server works with cache-free daemon


Merge: bd-gqxd

branch: polecat/furiosa
target: main
source_issue: bd-gqxd
rig: beads


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

bd sync tries to create worktree for sync.branch even when already on that branch. Should commit directly instead. See GitHub issue #519.


Merge: bd-dxtc

branch: polecat/cheedo
target: main
source_issue: bd-dxtc
rig: beads


Add date and priority filters to bd search for parity with bd list.

## Current State
bd search supports: --status, --type, --assignee, --label, --limit
bd list supports: all of the above PLUS date ranges and priority filters

## Filters to Add

### Priority Filters
```bash
bd search "query" --priority 1           # Exact priority
bd search "query" --priority-min 0       # P0 and above (higher priority)
bd search "query" --priority-max 2       # P2 and below (lower priority)
```

### Date Filters
```bash
bd search "query" --created-after 2025-01-01
bd search "query" --created-before 2025-12-31
bd search "query" --updated-after 2025-01-01
bd search "query" --closed-after 2025-01-01
```

### Content Filters
```bash
bd search "query" --desc-contains "bug"
bd search "query" --notes-contains "todo"
bd search "query" --empty-description    # Issues with no description
bd search "query" --no-assignee          # Unassigned issues
bd search "query" --no-labels            # Issues without labels
```

## Files to Modify

### 1. cmd/bd/search.go
Add flag definitions in init():
```go
searchCmd.Flags().IntP("priority", "p", -1, "Filter by exact priority (0-4)")
searchCmd.Flags().Int("priority-min", -1, "Filter by minimum priority")
searchCmd.Flags().Int("priority-max", -1, "Filter by maximum priority")
searchCmd.Flags().String("created-after", "", "Filter by creation date (YYYY-MM-DD)")
searchCmd.Flags().String("created-before", "", "Filter by creation date")
searchCmd.Flags().String("updated-after", "", "Filter by update date")
searchCmd.Flags().String("updated-before", "", "Filter by update date")
searchCmd.Flags().String("closed-after", "", "Filter by close date")
searchCmd.Flags().String("closed-before", "", "Filter by close date")
searchCmd.Flags().String("desc-contains", "", "Filter by description content")
searchCmd.Flags().String("notes-contains", "", "Filter by notes content")
searchCmd.Flags().Bool("empty-description", false, "Filter issues with empty description")
searchCmd.Flags().Bool("no-assignee", false, "Filter unassigned issues")
searchCmd.Flags().Bool("no-labels", false, "Filter issues without labels")
```

### 2. internal/rpc/protocol.go
Update SearchArgs struct:
```go
type SearchArgs struct {
    Query             string
    Filter            types.IssueFilter
    // Already has most fields via IssueFilter
}
```

Note: types.IssueFilter already has these fields - just need to wire them up!

### 3. cmd/bd/search.go Run function
Parse flags and populate filter:
```go
if priority, _ := cmd.Flags().GetInt("priority"); priority >= 0 {
    filter.Priority = &priority
}
if createdAfter, _ := cmd.Flags().GetString("created-after"); createdAfter != "" {
    t, err := time.Parse("2006-01-02", createdAfter)
    if err != nil {
        FatalError("invalid date format for --created-after: %v", err)
    }
    filter.CreatedAfter = &t
}
// ... similar for other flags
```

## Implementation Steps

1. **Check types.IssueFilter** - verify all needed fields exist
2. **Add flags to search.go** init()
3. **Parse flags** in Run function
4. **Pass to SearchIssues** via filter
5. **Test all combinations**

## Testing
```bash
# Create test issues
bd create "Test P1" -p 1
bd create "Test P2" -p 2 --description "Has description"

# Test filters
bd search "" --priority 1
bd search "" --priority-min 0 --priority-max 1
bd search "" --empty-description
bd search "" --desc-contains "description"
```

## Success Criteria
- All filters work in both direct and daemon mode
- Date parsing handles YYYY-MM-DD format
- --json output includes filtered results
- Help text documents all new flags


Widespread double JSON encoding bug in daemon mode RPC calls

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

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

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

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

All commands now tested and working correctly with daemon mode.


Restart beads daemon

Kill any running daemons so they pick up the new version:
```bash
bd daemons killall
```

Start fresh daemon:
```bash
bd list  # triggers daemon start
```

Verify daemon version:
```bash
bd version --daemon
```

```verify
bd version --daemon 2>&1 | grep -q '0.30.4'
```


Ephemeral issue cleanup and history compaction

## Problem

Beads history grows without bound. Every message, handoff, work assignment
stays in issues.jsonl forever. Enterprise users will balk at "git as database."

## Solution: Two-Tier Cleanup

### Tier 1: Ephemeral Cleanup (v1)

bd cleanup --ephemeral --closed

- Deletes closed issues where ephemeral=true from issues.jsonl
- Safe: only removes explicitly marked ephemeral + closed
- Preserves git history (commits still exist)
- Run after swarm completion

### Tier 2: History Compaction (v2)

bd compact --squash

- Rewrites issues.jsonl to remove tombstones
- Optionally squashes git history (interactive rebase equivalent)
- Preserves Merkle proofs for deleted items
- Advanced: cold storage tiering

## HOP Context

| Layer | HOP Role | Persistence |
|-------|----------|-------------|
| Execution trace | None | Ephemeral |
| Work scaffolding | None | Summarizable |
| Work outcome | CV entry | Permanent |
| Validation record | Stake proof | Permanent |

"Execution is ephemeral. Outcomes are permanent. You can't squash your CV."

## Success Criteria

- After cleanup --ephemeral: issues.jsonl only contains persistent work
- Work outcomes preserved (CV entries)
- Validation records preserved (stake proofs)
- Execution scaffolding removed (transient coordination)

## Implementation Plan (REVISED after code review)

See history/EPHEMERAL_MOLECULES_DESIGN.md for comprehensive design + review.

## Key Simplification

After code review, Tier 1 is MUCH simpler than originally designed:

- **Original**: Separate ephemeral repo with routing.ephemeral config
- **Revised**: Just set Wisp: true in cloneSubgraph()

The wisp field and bd cleanup --wisp already exist\!

## Child Tasks (in dependency order)

1. **bd-2vh3.2**: Tier 1 - Ephemeral spawning (SIMPLIFIED) [READY]
   - Just add Wisp: true to template.go:474
   - Add --persistent flag to opt out
2. **bd-2vh3.3**: Tier 2 - Basic bd mol squash command
3. **bd-2vh3.4**: Tier 3 - AI-powered squash summarization
4. **bd-2vh3.5**: Tier 4 - Auto-squash on molecule completion
5. **bd-2vh3.6**: Tier 5 - JSONL archive rotation (DEFERRED: post-1.0)

## What Already Exists

| Component | Location |
|-----------|----------|
| Ephemeral field | internal/types/types.go:45 |
| bd cleanup --wisp | cmd/bd/cleanup.go:72 |
| cloneSubgraph() | cmd/bd/template.go:456 |
| loadTemplateSubgraph() | cmd/bd/template.go |

## HOP Alignment

'Execution is ephemeral. Outcomes are permanent. You can't squash your CV.'


Auto-repair stale merge driver configs with invalid placeholders

Old bd versions (<0.24.0) installed merge driver with invalid placeholders %L %R instead of %A %B. Add detection to bd doctor --fix: check if git config merge.beads.driver contains %L or %R, auto-repair to 'bd merge %A %O %A %B'. One-time migration for users who initialized with old versions.


Exclude pinned issues from bd blocked

Update bd blocked to exclude pinned issues. Pinned issues are context markers and should not appear in the blocked work list.


Improve test coverage for internal/compact (18.2% → 70%)

Improve test coverage for internal/compact package from 17% to 70%.

## Current State
- Coverage: 17.3%
- Files: compactor.go, git.go, haiku.go
- Tests: compactor_test.go (minimal tests)

## Functions Needing Tests

### compactor.go (core compaction)
- [ ] New - needs config validation tests
- [ ] CompactTier1 - needs single issue compaction tests
- [ ] CompactTier1Batch - needs batch processing tests
- [ ] compactSingleWithResult - internal, test via public API

### git.go
- [ ] GetCurrentCommitHash - needs git repo fixture tests

### haiku.go (AI summarization) - MOCK REQUIRED
- [ ] NewHaikuClient - needs API key validation tests
- [ ] SummarizeTier1 - needs mock API response tests
- [ ] callWithRetry - needs retry logic tests
- [ ] isRetryable - needs error classification tests
- [ ] renderTier1Prompt - needs template rendering tests

## Implementation Guide

1. **Mock the Anthropic API:**
   ```go
   // Create mock HTTP server
   server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
       json.NewEncoder(w).Encode(map[string]interface{}{
           "content": []map[string]string{{"text": "Summarized content"}},
       })
   }))
   defer server.Close()

   // Point client at mock
   client.baseURL = server.URL
   ```

2. **Test scenarios:**
   - Successful compaction with AI summary
   - API failure with retry
   - Rate limit handling
   - Empty issue handling
   - Large issue truncation

3. **Use test database:**
   ```go
   store, cleanup := testutil.NewTestStore(t)
   defer cleanup()
   ```

## Success Criteria
- Coverage ≥ 70%
- AI calls properly mocked (no real API calls in tests)
- Retry logic verified
- Error paths covered

## Run Tests
```bash
go test -v -cover ./internal/compact
go test -race ./internal/compact
```


Fix JSON errors in comments.go

Replace direct stderr writes with FatalErrorRespectJSON() in comments.go.

Commands affected:
- commentsCmd (list)
- commentsAddCmd


Commit release

git add -A && git commit -m 'release: v{{version}}'


GH#518: Document bd setup command

bd setup is undiscoverable. Add to README/docs. Currently only findable by grepping source. See GitHub issue #518.


GH#402: Add --parent flag documentation to bd onboard

bd onboard output is missing --parent flag for epic subtasks. Agents guess wrong syntax (--deps parent:). See GitHub issue #402.


Code review findings: mol squash deletion bypasses tombstones


Review SQLite indexes and scaling bottlenecks

Audit the beads SQLite schema for:

## Index Review
- Are all frequently-queried columns indexed?
- Are compound indexes needed for common query patterns?
- Any missing indexes on foreign keys or filter columns?

## Scaling Bottlenecks
- How does performance degrade with 10k, 100k, 1M issues?
- Full table scans in hot paths?
- JSONL export/import performance at scale
- Transaction contention in multi-agent scenarios

## Common Query Patterns to Optimize
- bd ready (status + blocked_by resolution)
- bd list with filters (status, type, priority, labels)
- bd show with dependency graph traversal
- bd sync import/export

## Deliverables
- Document current indexes
- Identify missing indexes
- Benchmark key operations at scale
- Recommend schema improvements


Tier 3: AI-powered squash summarization

## Design: Agent-Provided Summarization (Inversion of Control)

bd is a tool FOR agents, not an agent itself. The calling agent provides
the summary; bd just stores it.

### API

```bash
# Agent generates summary, passes to bd
bd mol squash bd-xxx --summary "Agent-generated summary here"

# Without --summary, falls back to basic concatenation
bd mol squash bd-xxx
```

### Gas Town Integration Pattern

```go
// In polecat completion handler or witness
raw := exec.Command("bd", "mol", "show", molID, "--json").Output()
summary := callHaiku(buildSummaryPrompt(raw))  // agent's job
exec.Command("bd", "mol", "squash", molID, "--summary", summary).Run()
```

### Why This Design

| Concern | bd's job | Agent's job |
|---------|----------|-------------|
| Store data | ✅ | |
| Query data | ✅ | |
| Generate summaries | | ✅ |
| Call LLMs | | ✅ |
| Manage API keys | | ✅ |

### Implementation Status

- [x] --summary flag added to bd mol squash
- [x] Tests for agent-provided summary
- [ ] Gas Town integration (separate task)

### Acceptance Criteria

- ✅ bd mol squash --summary uses provided text
- ✅ Without --summary, falls back to concatenation
- ✅ No LLM calls in bd itself


Add tests for import/export functionality

Import/export functions like ImportIssues, exportToJSONLWithStore, and AutoImportIfNewer have low coverage. These are critical for data integrity and multi-repo synchronization.


Protos should have distinct prefix (e.g., proto-)

Currently protos use the same bd- prefix as regular issues, making them visually indistinguishable. When you see bd-7bs4 it looks like work to squash, not a reusable template.

**Problem:**
- Protos look like regular issues
- No visual signal that something is a template vs active work
- Easy to accidentally close/modify protos thinking they're issues

**Proposed Solution:**
- Protos should have their own prefix (e.g., `proto-`, `mol-`, or `tpl-`)
- Could be a separate database/storage for the proto library
- Or a convention where proto IDs are generated differently

**Alternatives:**
1. Separate proto database with `proto-` prefix
2. Special ID format for protos (e.g., `bd:proto:release`)
3. Visual indicator in bd list/show output (already have labels, but not enough)

**Current workaround:** Label with `template` and `molecule`, but IDs still look like regular issues.


Merge: bd-rgyd

branch: polecat/Splitter
target: main
source_issue: bd-rgyd
rig: beads


Exclude pinned issues from bd ready

Update bd ready to exclude pinned issues. Pinned issues are context markers, not work items, and should never appear in the ready-to-work list.


Consider consolidating config.yaml parsing into shared utility

Multiple places parse config.yaml with custom structs:

1. **autoimport.go:148** - `localConfig{SyncBranch}`
2. **main.go:310** - strings.Contains for no-db (fragile, see bd-r6k2)
3. **doctor.go:863** - strings.Contains for no-db (fragile, see bd-r6k2)
4. **internal/config/config.go** - Uses viper (but caches at startup, problematic for tests)

Consider creating a shared utility in `internal/configfile/` or extending the viper config:

```go
// internal/configfile/yaml.go
type YAMLConfig struct {
    SyncBranch string `yaml:"sync-branch"`
    NoDb       bool   `yaml:"no-db"`
    IssuePrefix string `yaml:"issue-prefix"`
    Author     string `yaml:"author"`
}

func LoadYAML(beadsDir string) (*YAMLConfig, error) {
    // Parse config.yaml with proper YAML library
}
```

Benefits:
- Single source of truth for config.yaml structure
- Proper YAML parsing everywhere
- Easier to add new config fields

Trade-off: May add complexity for simple one-off reads.


BondRef data model for compound lineage

Add data model support for tracking compound molecule lineage.

NEW FIELDS on Issue:
  bonded_from: []BondRef  // For compounds: constituent protos

NEW TYPE:
  type BondRef struct {
      ProtoID   string  // Source proto ID
      BondType  string  // sequential, parallel, conditional
      BondPoint string  // Attachment site (issue ID or empty for root)
  }

JSONL SERIALIZATION:
  {
    "id": "proto-feature-tested",
    "title": "Feature with tests",
    "bonded_from": [
      {"proto_id": "proto-feature", "bond_type": "root"},
      {"proto_id": "proto-testing", "bond_type": "sequential"}
    ],
    ...
  }

QUERIES:
- GetCompoundConstituents(id) → []BondRef
- IsCompound(id) → bool
- GetCompoundsUsing(protoID) → []Issue  // Reverse lookup


Timer beads for scheduled agent callbacks

## Problem

Agents frequently need to wait for external events (CI completion, PR reviews, artifact builds) but have no good mechanism:
- `sleep N` blocks and is unreliable (often times out at 8+ minutes)
- Polling wastes context and is easy to forget
- No way to survive session restarts

## Proposal: Timer Beads

A new bead type or field that represents a scheduled callback:

### Creating timers
```bash
bd timer create --in 30s --callback "Check CI run 12345" --issue bd-xyz
bd timer create --at "2025-12-20T08:00:00" --callback "Morning standup"
bd timer create --in 5m --on-expire "tmux send-keys -t dave 'bd show bd-xyz'"
```

### Timer storage
- Store in beads (survives restarts)
- Fields: `expires_at`, `callback_description`, `on_expire_command`, `linked_issue`
- Status: pending, fired, cancelled

### Deacon integration
The Deacon daemon monitors timer beads:
1. Wakes on next timer expiry
2. Executes `on_expire` command (e.g., tmux send-keys to interrupt agent)
3. Marks timer as fired
4. Optionally updates linked issue

### Use cases
- CI monitoring: "ping me when build completes"
- PR reviews: "check back in 1 hour"
- Scheduled tasks: "remind me at EOD to sync"
- Blocking waits: agent registers callback instead of sleeping

## Acceptance criteria
- [ ] Timer bead type or field design
- [ ] `bd timer create/list/cancel` commands
- [ ] Deacon timer monitoring loop
- [ ] tmux integration for agent interrupts
- [ ] Survives daemon restarts


Fix TestFallbackToDirectModeEnablesFlush deadlock causing 10min test timeout

## Problem

TestFallbackToDirectModeEnablesFlush in direct_mode_test.go deadlocks for 9m59s before timing out, causing the entire test suite to take 10+ minutes instead of <10 seconds.

## Root Cause

Database lock contention between test cleanup and flushToJSONL():
- Test cleanup (line 36) tries to close DB via defer
- flushToJSONL() (line 132) is still accessing DB
- Results in deadlock: database/sql.(*DB).Close() waits for mutex while GetJSONLFileHash() holds it

## Stack Trace Evidence

```
goroutine 512 [sync.Mutex.Lock, 9 minutes]:
database/sql.(*DB).Close(0x14000643790)
  .../database/sql/sql.go:927 +0x84
github.com/steveyegge/beads/cmd/bd.TestFallbackToDirectModeEnablesFlush.func1()
  .../direct_mode_test.go:36 +0xf4

While goroutine running flushToJSONL() holds DB connection via GetJSONLFileHash()
```

## Impact

- Test suite: 10+ minutes → should be <10 seconds
- ALL other tests pass in ~4 seconds
- This ONE test accounts for 99.9% of test runtime

## Related

This is the EXACT same issue documented in MAIN_TEST_REFACTOR_NOTES.md for why main_test.go refactoring was deferred - global state manipulation + DB cleanup = deadlock.

## Fix Approaches

1. **Add proper cleanup sequencing** - stop flush goroutines BEFORE closing DB
2. **Use test-specific DB lifecycle** - ensure flush completes before cleanup
3. **Mock the flush mechanism** - avoid real DB for testing this code path
4. **Add explicit timeout handling** - fail fast with clear error instead of hanging

## Files

- cmd/bd/direct_mode_test.go:36-132
- cmd/bd/autoflush.go:353 (validateJSONLIntegrity)
- cmd/bd/autoflush.go:508 (flushToJSONLWithState)

## Acceptance

- Test passes without timeout
- Test suite completes in <10 seconds
- No deadlock between cleanup and flush operations


rpc: Rich mutation events not emitted

The activity command (activity.go) references rich mutation event types (MutationBonded, MutationSquashed, MutationBurned, MutationStatus) that include metadata like OldStatus, NewStatus, ParentID, and StepCount.

However, the emitMutation() function in server_core.go:141 only accepts (eventType, issueID) and only populates Type, IssueID, and Timestamp. The additional metadata fields are never set.

Need to either:
1. Add an emitRichMutation() function that accepts the additional metadata
2. Update call sites (close, bond, squash, burn operations) to emit rich events

Without this fix, the activity feed will never show:
- Status transitions (in_progress -> closed)
- Bonded events with step counts
- Parent molecule relationships

Discovered during code review of bd-xo1o implementation.


Add bd reset command for clean slate restart

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

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

## Design

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

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

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

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

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

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


Design contributor namespace isolation for beads pollution prevention

## Problem

When contributors work on beads-the-project using beads-the-tool, their personal work-tracking issues leak into PRs. The .beads/issues.jsonl is intentionally tracked (it's the project's issue database), but contributors' local issues pollute the diff.

This is a recursion problem unique to self-hosting projects.

## Possible Solutions to Explore

1. **Contributor namespaces** - Each contributor gets a private prefix (e.g., `bd-steve-xxxx`) that's gitignored or filtered
2. **Separate database** - Contributors use BEADS_DIR pointing elsewhere for personal tracking
3. **Issue ownership/visibility flags** - Mark issues as "local-only" vs "project"
4. **Prefix-based filtering** - Configure which prefixes are committed vs ignored

## Design Considerations

- Should be zero-friction for contributors (no manual setup)
- Must not break existing workflows
- Needs to work with sync/collaboration features
- Consider: what if a "personal" issue graduates to "project" issue?

## Expansion Needed

This is a placeholder. Needs detailed design exploration before implementation.


bd mol install: Install formula from GitHub

Download and cook a formula from a GitHub repository.

## Usage
```bash
# Direct GitHub URL
bd mol install github.com/anthropics/mol-code-review

# With version tag
bd mol install github.com/anthropics/mol-code-review@v1.2.0

# Shorthand (future: via registry lookup)
bd mol install @anthropic/mol-code-review
```

## Implementation
1. Parse URL: extract org, repo, optional version tag
2. Construct raw URL: `https://raw.githubusercontent.com/org/repo/[tag]/formula.json`
3. Fetch formula.json via HTTP
4. Validate formula structure
5. Save to .beads/formulas/
6. Run cook to create local proto
7. Record in .beads/installed.json for update tracking

## installed.json Format
```json
{
  "mol-code-review": {
    "source": "github.com/anthropics/mol-code-review",
    "version": "v1.2.0",
    "installed_at": "2025-12-25T12:00:00Z"
  }
}
```

## Dependencies
- Requires network access
- Uses `gh` CLI or direct HTTP for auth (private repos)


Mail Commands: bd mail send/inbox/read/ack

Implement core mail commands in cmd/bd/mail.go

Commands:
- bd mail send <recipient> -s 'Subject' -m 'Body' [--urgent]
  - Creates issue with type=message, sender=identity, assignee=recipient
  - --urgent sets priority=0

- bd mail inbox [--from <sender>] [--priority <n>]
  - Lists open messages where assignee=my identity
  - Sorted by priority, then date

- bd mail read <id>
  - Shows full message content (subject, body, sender, timestamp)
  - Does NOT close (separate from ack)

- bd mail ack <id>
  - Marks message as read by closing it
  - Can ack multiple: bd mail ack <id1> <id2> ...

Requires: Identity configuration (bd-kwro.7)


Add tests for helper functions (GetDirtyIssueHash, GetAllDependencyRecords, export hashes)

Several utility functions have 0% coverage:
- GetDirtyIssueHash (dirty.go)
- GetAllDependencyRecords (dependencies.go)
- GetExportHash, SetExportHash, ClearAllExportHashes (hash.go)

These are lower priority but should have basic coverage.


Merge3Way public API does not expose TTL parameter

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


Add close_reason to JSONL export format documentation

PR #551 now persists close_reason to the database, but there's a question about whether this field should be exported to JSONL format.

## Current State
- close_reason is stored in issues.close_reason column
- close_reason is also stored in events table (audit trail)
- The JSONL export format may or may not include close_reason

## Questions
1. Should close_reason be exported to JSONL format?
2. If yes, where should it go (root level or nested in events)?
3. Should there be any special handling to avoid duplication?
4. How should close_reason be handled during JSONL import?

## Why This Matters
- JSONL is the git-friendly sync format
- Other beads instances import from JSONL
- close_reason is meaningful data that should be preserved across clones

## Suggested Action
- Check if close_reason is currently exported in JSONL
- If not, add it to the export schema
- Document the field in JSONL format spec
- Add tests for round-trip (export -> import -> verify close_reason)


Implement or remove ExpectExit/ExpectStdout verification fields

The Verification struct in internal/types/workflow.go has ExpectExit and ExpectStdout fields that are never used by workflowVerifyCmd. Either implement the functionality or remove the dead fields.


Digest: Version Bump: test-squash

## Molecule Execution Summary

**Molecule**: Version Bump: test-squash
**Steps**: 8

**Completed**: 0/8

---

### Steps

1. **[open]** Verify release artifacts
   Check GitHub releases page - binaries for darwin/linux/windows should be available

2. **[open]** Commit and push release
   git add -A && git commit && git push to trigger CI

3. **[open]** Update CHANGELOG.md with release notes
   Add meaningful release notes to CHANGELOG.md describing what changed in test-squash

4. **[open]** Wait for CI to pass
   Monitor GitHub Actions - all checks must pass before release artifacts are built

5. **[open]** Restart running daemons
   Kill and restart any running bd daemons to pick up new version: pkill -f 'bd daemon' && bd daemon --start

6. **[open]** Update local installation
   Run install script or brew upgrade to get new version locally: curl -fsSL .../install.sh | bash

7. **[open]** Run bump-version.sh test-squash
   Run ./scripts/bump-version.sh test-squash to update version in all files

8. **[open]** Update info.go versionChanges
   Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for test-squash


mol-beads-release

Release checklist for beads version {{version}}.

This molecule ensures all release steps are completed properly.
Variable: {{version}} - target version (e.g., 0.35.0)

## Step: update-release-notes
Update cmd/bd/info.go with release notes for {{version}}.

Add a new VersionChange entry at the top of versionChanges slice:
```go
{
    Version: "{{version}}",
    Date:    "YYYY-MM-DD",
    Changes: []string{
        "NEW: Feature description",
        "FIX: Bug fix description",
        "IMPROVED: Enhancement description",
    },
},
```

Run `git log --oneline v<previous>..HEAD` to see what changed.

## Step: update-changelog
Update CHANGELOG.md with detailed release notes.

Add a new section after [Unreleased]:
```markdown
## [{{version}}] - YYYY-MM-DD

### Added
- **Feature name** (issue-id) - Description

### Changed
- **Change description** (issue-id)

### Fixed
- **Bug fix** (issue-id) - Description
```

Sort by importance, not chronologically.
Needs: update-release-notes

## Step: bump-version
Run the version bump script.

```bash
./scripts/bump-version.sh {{version}}
```

This updates version in all files:
- cmd/bd/version.go
- .claude-plugin/*.json
- integrations/beads-mcp/pyproject.toml
- npm-package/package.json
- Hook templates

Needs: update-changelog

## Step: run-tests
Run tests and verify lint passes.

```bash
go test -short ./...
```

CI will run full lint, but fix any obvious issues first.
Needs: bump-version

## Step: commit-release
Commit the release changes.

```bash
git add -A
git commit -m "chore: bump version to v{{version}}"
```

Needs: run-tests

## Step: push-and-tag
Push commit and create release tag.

```bash
git push origin main
git tag v{{version}}
git push origin v{{version}}
```

This triggers GitHub Actions release workflow.
Needs: commit-release

## Step: wait-for-ci
Wait for GitHub Actions to complete.

Monitor: https://github.com/steveyegge/beads/actions

CI will:
- Build binaries via GoReleaser
- Create GitHub Release with assets
- Publish to npm (@beads/bd)
- Publish to PyPI (beads-mcp)
- Update Homebrew tap

Wait until all jobs succeed (~5-10 min).
Needs: push-and-tag

## Step: verify-release
Verify the release is complete.

```bash
# Check GitHub release
gh release view v{{version}}

# Check Homebrew
brew update && brew info steveyegge/beads/bd

# Check npm
npm view @beads/bd version

# Check PyPI
pip index versions beads-mcp
```

Needs: wait-for-ci

## Step: update-local
Update local installations.

```bash
# Upgrade Homebrew
brew upgrade steveyegge/beads/bd

# Or install from source
./scripts/bump-version.sh {{version}} --install

# Install MCP locally
pip install -e integrations/beads-mcp

# Restart daemons
pkill -f "bd daemon" || true
```

Verify: `bd --version` shows {{version}}
Needs: verify-release

## Step: manual-publish
(Optional) Manual publish if CI failed.

```bash
# npm (requires npm login)
./scripts/bump-version.sh {{version}} --publish-npm

# PyPI (requires TWINE credentials)
./scripts/bump-version.sh {{version}} --publish-pypi

# Or both
./scripts/bump-version.sh {{version}} --publish-all
```

Only needed if CI publishing failed.
Needs: wait-for-ci


Consider caching external dep resolution results

Each call to GetReadyWork re-checks all external dependencies by:
1. Querying for external deps in the local database
2. Opening each external project's database
3. Querying for closed issues with provides: labels

For workloads with many external deps or slow external databases, this adds latency on every bd ready call.

Potential optimizations:
- In-memory TTL cache for external dep status (e.g., 60 second TTL)
- Store resolved status in a local cache table with timestamp
- Batch resolution of common project/capability pairs

This is not urgent - current implementation is correct and performant for typical workloads. Only becomes an issue with many external deps across many projects.


Add stale molecule check to bd doctor

Extend bd doctor to detect stale molecules.

**New check:**
- Name: 'Stale Molecules'
- Category: Workflow
- Severity: Warning (don't fail overall check)

**Detection:**
Reuse logic from bd mol stale command:
- Find mols where Completed >= Total but root is open
- Filter to orphaned (not assigned, not pinned)
- Extra weight if blocking other work

**Output:**
```
⚠ Stale Molecules
  Found 2 complete-but-unclosed molecules:
  - bd-xyz: Version bump v0.36.0 (blocking 1 issue)
  - bd-uvw: Old patrol (not blocking)
  Fix: bd close <id> or bd mol squash <id>
```

**--fix behavior:**
- Auto-close stale mols (with reason 'Auto-closed by bd doctor')
- Or prompt interactively with -i flag

Depends on: bd mol stale command


Document sync.branch 'always dirty' working tree behavior

## Context

When sync.branch is configured, the .beads/issues.jsonl file in main's working tree is ALWAYS dirty. This is by design:

1. bd sync commits to beads-sync branch (via worktree)
2. bd sync copies JSONL to main's working tree (so CLI commands work)
3. This copy is NOT committed to main (to reduce commit noise)

Contributors who watch main branch history pushed for sync.branch to avoid constant beads commit noise. But users need to understand the trade-off.

## Documentation Needed

Update README.md sync.branch section with:

1. **Clear explanation** of why .beads/ is always dirty on main
2. **"Be Zen about it"** - this is expected, not a bug
3. **Workflow options:**
   - Accept dirty state, use `bd sync --merge` periodically to snapshot to main
   - Or disable sync.branch if clean working tree is more important
4. **Shell alias tip** to hide beads from git status:
   ```bash
   alias gs='git status -- ":!.beads/"'
   ```
5. **When to merge**: releases, milestones, or periodic snapshots

## Related

- bd-7b7h: Fix that allows bd sync --merge to work with dirty .beads/
- bd-elqd: Investigation that identified this as expected behavior


Fix G304 potential file inclusion in cmd/bd/tips.go:259

Linting issue: G304: Potential file inclusion via variable (gosec) at cmd/bd/tips.go:259:18. Error: if data, err := os.ReadFile(settingsPath); err == nil {


Add pinned field to issue schema

Add boolean 'pinned' field to the issue schema. When true, the issue is marked as a persistent context marker that should not be treated as a work item.


gt mail send: support reading message body from stdin

Currently gt mail send -m requires the message as a command-line argument, which causes shell escaping issues with backticks, quotes, and special characters.

Add support for reading message body from stdin:
- gt mail send addr -s 'Subject' --stdin  # Read body from stdin
- echo 'body' | gt mail send addr -s 'Subject' -m -  # Convention: -m - means stdin

This would allow:
cat <<'EOF' | gt mail send addr -s 'Subject' --stdin
Message with `backticks` and 'quotes' safely
EOF

Without this, agents struggle to send handoff messages containing code snippets.


Cross-rig external dependency support

Support dependencies on issues in other rigs/repos.

## Use Case

Gas Town issues often depend on Beads issues (and vice versa). Currently we use labels like `external:beads/bd-xxx` as documentation, but:
- `bd blocked` doesn't recognize external deps
- `bd ready` doesn't filter them out
- No way to query cross-rig status

## Proposed UX

### Adding external deps
```bash
# New syntax for bd dep add
bd dep add gt-a07f external:beads:bd-kwjh

# Or maybe cleaner
bd dep add gt-a07f --external beads:bd-kwjh
```

### Showing blocked status
```bash
bd blocked
# → gt-a07f blocked by external:beads:bd-kwjh (unverified)

# With optional cross-rig query
bd blocked --resolve-external
# → gt-a07f blocked by external:beads:bd-kwjh (closed) ← unblocked!
```

### Storage
Could use:
- Special dependency type: `type: external`
- Label convention: `external:rig:id`
- New field: `external_deps: ["beads:bd-kwjh"]`

## Implementation Notes

Cross-rig queries would need:
- Known rig locations (config or discovery)
- Read-only beads access to external rigs
- Caching to avoid repeated queries

For MVP, just recognizing external deps and marking them as 'unverified' blockers would be valuable.


Phase 1.1: TOON Library Integration - Add gotoon dependency

Add gotoon (github.com/alpkeskin/gotoon) to go.mod and create internal/toon wrapper package for TOON encoding/decoding. This enables bdtoon to encode Issue structs to TOON format and decode TOON back to issues.

## Subtasks
1. Add gotoon dependency: go get github.com/alpkeskin/gotoon
2. Create internal/toon package with wrapper functions
3. Write encode tests for Issue struct round-trip conversion
4. Write decode tests for TOON to Issue conversion
5. Add gotoon API options to wrapper (indent, delimiter, length markers)

## Success Criteria
- go.mod includes gotoon dependency
- internal/toon/encode.go exports EncodeTOON(issues) ([]byte, error)
- internal/toon/decode.go exports DecodeTOON(data []byte) ([]Issue, error)
- Round-trip tests verify Issue → TOON → Issue produces identical data
- Tests pass with: go test ./internal/toon -v


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

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


Add bd doctor --fix flag to automatically repair issues

Add bd doctor --fix flag to automatically repair detected issues.

## Current State
- Doctor checks return issues with "Fix" field containing fix instructions
- No automatic fix execution
- User must manually follow fix instructions

## Implementation

### 1. Add --fix flag to doctor.go
```go
// In cmd/bd/doctor.go init()
doctorCmd.Flags().Bool("fix", false, "Automatically fix detected issues")
doctorCmd.Flags().Bool("yes", false, "Skip confirmation prompts (use with --fix)")
```

### 2. Create fix registry (cmd/bd/doctor/fix/registry.go)
```go
package fix

// Fixer can automatically repair an issue
type Fixer interface {
    // CanFix returns true if this fixer handles the given check name
    CanFix(checkName string) bool
    // Fix attempts to repair the issue, returns error if failed
    Fix(ctx context.Context, issue *CheckResult) error
    // Description returns human-readable description of what will be fixed
    Description() string
}

var registry []Fixer

func Register(f Fixer) {
    registry = append(registry, f)
}

func GetFixer(checkName string) Fixer {
    for _, f := range registry {
        if f.CanFix(checkName) {
            return f
        }
    }
    return nil
}
```

### 3. Implement fixers (one per file)

**cmd/bd/doctor/fix/hooks.go**
```go
type HooksFixer struct{}

func (f *HooksFixer) CanFix(name string) bool {
    return name == "git-hooks" || name == "hooks-outdated"
}

func (f *HooksFixer) Fix(ctx context.Context, issue *CheckResult) error {
    return hooks.Install(".", true) // force reinstall
}

func (f *HooksFixer) Description() string {
    return "Reinstall git hooks"
}

func init() {
    Register(&HooksFixer{})
}
```

**cmd/bd/doctor/fix/sync_branch.go**
```go
type SyncBranchFixer struct{}

func (f *SyncBranchFixer) CanFix(name string) bool {
    return name == "sync-branch-missing" || name == "sync-branch-diverged"
}

func (f *SyncBranchFixer) Fix(ctx context.Context, issue *CheckResult) error {
    // Reset to remote or create branch
    return syncbranch.ResetToRemote(ctx, repoRoot, branch, jsonlPath)
}
```

**cmd/bd/doctor/fix/merge_driver.go**
```go
type MergeDriverFixer struct{}

func (f *MergeDriverFixer) CanFix(name string) bool {
    return name == "merge-driver-missing" || name == "merge-driver-outdated"
}

func (f *MergeDriverFixer) Fix(ctx context.Context, issue *CheckResult) error {
    return setupMergeDriver()
}
```

### 4. Update doctor run logic

```go
func runDoctor(cmd *cobra.Command, args []string) {
    issues := runAllChecks()

    if \!fixFlag {
        // Existing behavior - just display issues
        displayIssues(issues)
        return
    }

    // Collect fixable issues
    var fixable []FixableIssue
    for _, issue := range issues {
        if fixer := fix.GetFixer(issue.CheckName); fixer \!= nil {
            fixable = append(fixable, FixableIssue{issue, fixer})
        }
    }

    if len(fixable) == 0 {
        fmt.Println("No automatically fixable issues found")
        return
    }

    // Show what will be fixed
    fmt.Printf("Found %d fixable issues:\n", len(fixable))
    for i, f := range fixable {
        fmt.Printf("  %d. [%s] %s\n", i+1, f.Issue.CheckName, f.Fixer.Description())
    }

    // Confirm unless --yes
    if \!yesFlag {
        fmt.Print("\nProceed with fixes? [Y/n] ")
        // ... read confirmation
    }

    // Apply fixes
    for _, f := range fixable {
        fmt.Printf("Fixing %s... ", f.Issue.CheckName)
        if err := f.Fixer.Fix(ctx, f.Issue); err \!= nil {
            fmt.Printf("FAILED: %v\n", err)
        } else {
            fmt.Println("OK")
        }
    }
}
```

## Fixable Checks (Initial Set)

| Check | Fixer | Action |
|-------|-------|--------|
| git-hooks | HooksFixer | Reinstall hooks |
| hooks-outdated | HooksFixer | Update hooks |
| merge-driver-missing | MergeDriverFixer | Configure merge driver |
| sync-branch-diverged | SyncBranchFixer | Reset to remote |
| daemon-version-mismatch | DaemonFixer | Restart daemon |

## Testing
```bash
# Test with broken hooks
rm .git/hooks/pre-commit
bd doctor --fix --yes

# Verify fix applied
bd doctor  # Should pass now
```

## Success Criteria
- --fix flag triggers automatic repair
- User prompted for confirmation (unless --yes)
- Clear output showing what was fixed
- Graceful handling of fix failures
- At least 5 common issues auto-fixable


Remove legacy autoflush code paths

## Problem

The autoflush system has dual code paths - an old timer-based approach and a new FlushManager. Both are actively used based on whether flushManager is nil.

## Locations

- main.go:78-81: isDirty, needsFullExport, flushTimer marked 'used by legacy code'
- autoflush.go:291-369: Functions with 'Legacy path for backward compatibility with tests'

## Current Behavior

```go
// In markDirtyAndScheduleFlush():
if flushManager != nil {
    flushManager.MarkDirty(false)
    return
}
// Legacy path for backward compatibility with tests
```

## Proposed Fix

1. Ensure flushManager is always initialized (even in tests)
2. Remove the legacy timer-based code paths
3. Remove isDirty, needsFullExport, flushTimer globals
4. Update tests to use FlushManager

## Risk

Low - the FlushManager is the production path. Legacy code only runs when flushManager is nil (test scenarios).


Beads commands operate on stale database without warning

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

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

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

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

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

## Implementation Complete

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

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

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

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

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


Phase 2.3: TOON deletion tracking

Implement deletion tracking in TOON format.

## Overview
Phase 2.2 switched storage to TOON format. Phase 2.3 adds deletion tracking in TOON format for propagating deletions across clones.

## Required Work

### 2.3.1 Deletion Tracking (TOON Format)
- [ ] Implement deletions.toon file (tracking deleted issue records)
- [ ] Add DeleteTracker struct to record deleted issue IDs and metadata
- [ ] Update bdt delete command to record in deletions.toon
- [ ] Design deletion record format (ID, timestamp, reason, hash)
- [ ] Implement auto-prune of old deletion records (configurable TTL)

### 2.3.2 Sync Propagation
- [ ] Load deletions.toon during import
- [ ] Remove deleted issues from local database when imported from remote
- [ ] Handle edge cases (delete same issue in multiple clones)
- [ ] Deletion ordering and conflict resolution

### 2.3.3 Testing
- [ ] Unit tests for deletion tracking
- [ ] Integration tests for deletion propagation
- [ ] Multi-clone deletion scenarios
- [ ] TTL expiration tests

## Success Criteria
- deletions.toon stores deletion records in TOON format
- Deletions propagate across clones via git sync
- Old records auto-prune after TTL
- All 70+ tests still passing
- bdt delete command works seamlessly


bd formula add: Import formula to local catalog

Import a formula file to the local catalog (search path).

**Replaces**: "bd mol promote" (proto-to-proto concept is obsolete with ephemeral protos)

## Usage
```bash
# Add a formula file to project catalog
bd formula add my-workflow.formula.json

# Add to user-level catalog
bd formula add my-workflow.formula.json --scope user

# Add from URL
bd formula add https://example.com/workflow.formula.json
```

## Implementation
1. Parse the formula file (validate JSON structure)
2. Determine target directory based on scope:
   - project: .beads/formulas/
   - user: ~/.beads/formulas/
   - town: ~/gt/.beads/formulas/
3. Copy/download formula to target
4. Verify it is loadable: bd formula show <name>

## Flags
- `--scope <level>` - Where to add (project|user|town, default: project)
- `--name <name>` - Override formula name (default: from file)

## Note
This is for manually adding formulas. For GitHub-hosted formulas, use:
  bd mol install github.com/org/formula-name


Remove all deletions.jsonl code except migration

There's deletions manifest code spread across the entire codebase that should have been removed after tombstone migration:

Files with deletions code (non-migration):
- internal/deletions/ - entire package
- cmd/bd/sync.go - 25+ references, auto-compact, sanitize
- cmd/bd/delete.go - dual-writes to deletions.jsonl
- internal/importer/importer.go - checks deletions manifest
- internal/syncbranch/worktree.go - merges deletions.jsonl
- cmd/bd/doctor/fix/sync.go - cleanupDeletionsManifest
- cmd/bd/doctor/fix/deletions.go - HydrateDeletionsManifest
- cmd/bd/integrity.go - checks deletions for data loss
- cmd/bd/deleted.go - entire command
- cmd/bd/compact.go - pruneDeletionsManifest
- cmd/bd/doctor.go - checkDeletionsManifest
- Plus many more

Action: Aggressively remove all non-migration deletions code. Tombstones are the only deletion mechanism now.


Kill running daemons

Stop all bd daemons before release:

```bash
pkill -f 'bd.*daemon' || true
sleep 1
pgrep -lf 'bd.*daemon'  # Should show nothing
```


worktree: any bd command is slow

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


bd cleanup --hard should skip tombstone creation for true permanent deletion

## Problem

When using bd cleanup --hard --older-than N --force, the command:
1. Deletes closed issues older than N days (converting them to tombstones with NOW timestamp)
2. Then tries to prune tombstones older than N days (finds none because they were just created)

This leaves the database bloated with fresh tombstones that will not be pruned.

## Expected Behavior

In --hard mode, the deletion should be permanent without creating tombstones, since the user explicitly requested bypassing sync safety.

## Workaround

Manually delete from database: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE status=tombstone'

## Fix Options

1. In --hard mode, use a different delete path that does not create tombstones
2. After deleting, immediately prune the just-created tombstones regardless of age
3. Pass a skip_tombstone flag to the delete operation

Option 1 is cleanest - --hard should mean permanent delete without tombstone.


Add --body-file flag to bd create for reading descriptions from files

## Problem

Creating issues with long/complex descriptions via CLI requires shell escaping gymnastics:

```bash
# Current workaround - awkward heredoc quoting
bd create --title="..." --description="$(cat <<'EOF'
...markdown...
EOF
)"

# Often fails with quote escaping errors in eval context
# Agents resort to writing temp files then reading them
```

## Proposed Solution

Add `--body-file` and `--description-file` flags to read description from a file, matching `gh` CLI pattern.

```bash
# Natural pattern that aligns with training data
cat > /tmp/desc.md << 'EOF'
...markdown content...
EOF

bd create --title="..." --body-file=/tmp/desc.md
```

## Implementation

### 1. Add new flags to `bd create`

```go
createCmd.Flags().String("body-file", "", "Read description from file (use - for stdin)")
createCmd.Flags().String("description-file", "", "Alias for --body-file")
```

### 2. Flag precedence

- If `--body-file` or `--description-file` is provided, read from file
- If value is `-`, read from stdin
- Otherwise fall back to `--body` or `--description` flag
- If neither provided, description is empty (current behavior)

### 3. Error handling

- File doesn't exist → clear error message
- File not readable → clear error message
- stdin specified but not available → clear error message

## Benefits

✅ **Matches training data**: `gh issue create --body-file file.txt` is a common pattern
✅ **No shell escaping issues**: File content is read directly
✅ **Works with any content**: Markdown, special characters, quotes, etc.
✅ **Agent-friendly**: Agents already write complex content to temp files
✅ **User-friendly**: Easier for humans too when pasting long descriptions

## Related Commands

Consider adding similar support to:
- `bd update --body-file` (for updating descriptions)
- `bd comment --body-file` (if/when we add comments)

## Examples

```bash
# From file
bd create --title="Add new feature" --body-file=feature.md

# From stdin
echo "Quick description" | bd create --title="Bug fix" --body-file=-

# With other flags
bd create \
  --title="Security issue" \
  --type=bug \
  --priority=0 \
  --body-file=security-report.md \
  --label=security
```

## Testing

- Test with normal files
- Test with stdin (`-`)
- Test with non-existent files (error handling)
- Test with binary files (should handle gracefully)
- Test with empty files (valid - empty description)
- Test that `--description-file` and `--body-file` are equivalent aliases


bd close --continue: auto-advance to next molecule step

Add --continue flag to bd close for seamless molecule step transitions.

## Usage

bd close <step-id> --continue [--no-auto]

## Behavior

1. Closes the specified step
2. Finds next ready step in same molecule (sibling/child)
3. By default, marks it in_progress (--no-auto to skip)
4. Outputs the transition

## Output

[done] Closed gt-abc.3: Implement feature

Next ready in molecule:
  gt-abc.4: Write tests

[arrow] Marked in_progress (use --no-auto to skip)

## If no next step

[done] Closed gt-abc.6: Exit decision

Molecule gt-abc complete! All steps closed.
Consider: bd mol squash gt-abc --summary '...'

## Key behaviors
- Detects parent molecule from closed step
- Finds next unblocked sibling
- Auto-claims by default (propulsion principle)
- Graceful handling when molecule is complete

## Gas Town integration
- gt-lz13: Update templates with nav workflow
- gt-um6q: Update docs with nav workflow


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

Wire up the reset command with Cobra CLI.

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

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

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

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

Continue? [y/N] y

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

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

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

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


GH#505: Add bd reset/wipe command

Add command to cleanly reset/wipe beads database. User reports painful manual process to start fresh. See GitHub issue #505.