Make merge requests ephemeral wisps instead of permanent issues

## Problem

Merge requests (MRs) are currently created as regular beads issues (type: merge-request). This means they:
- Sync to JSONL and propagate via git
- Accumulate in the issue database indefinitely
- Clutter `bd list` output with closed MRs
- Create permanent records for inherently transient artifacts

MRs are process artifacts, not work products. They exist briefly while code awaits merge, then their purpose is fulfilled. The git merge commit and GitHub PR (if applicable) provide the permanent audit trail - the beads MR is redundant.

## Proposed Solution

Make MRs ephemeral wisps that exist only during the merge process:

1. **Create MRs as wisps**: When a polecat completes work and requests merge, create the MR in `.beads-wisp/` instead of `.beads/`

2. **Refinery visibility**: This works because all clones within a rig share the same database:
   ```
   beads/              ← Rig root
   ├── .beads/         ← Permanent issues (synced to JSONL)
   ├── .beads-wisp/    ← Ephemeral wisps (NOT synced)
   ├── crew/dave/      ← Uses rig's shared DB
   ├── polecats/*/     ← Uses rig's shared DB
   └── refinery/       ← Uses rig's shared DB
   ```
   The refinery can see wisp MRs immediately - same SQLite database.

3. **On merge completion**: Burn the wisp (delete without digest). The git merge commit IS the permanent record. No digest needed since:
   - Digest wouldn't be smaller than the MR itself (~200-300 bytes either way)
   - Git history provides complete audit trail
   - GitHub PR (if used) provides discussion/approval record

4. **On merge rejection/abandonment**: Burn the wisp. Optionally notify the source polecat via mail.

## Benefits

- **Clean JSONL**: MRs never pollute the permanent issue history
- **No accumulation**: Wisps are burned on completion, no cleanup needed
- **Correct semantics**: Wisps are for "operational ephemera" - MRs fit perfectly
- **Reduced sync churn**: Fewer JSONL updates, faster `bd sync`
- **Cleaner queries**: `bd list` shows work items, not process artifacts

## Implementation Notes

### Where MRs are created

Currently MRs are created by the witness or polecat when work is ready for merge. This code needs to:
- Set `wisp: true` on the MR issue
- Or use a dedicated wisp creation path

### Refinery changes

The refinery queries for pending MRs to process. It needs to:
- Query wisp storage as well as (or instead of) permanent storage
- Use `bd mol burn` or equivalent to delete processed MRs

### What about cross-rig MRs?

If an MR needs to be visible outside the rig (e.g., external collaborators):
- They would see the GitHub PR anyway
- Or we could create a permanent "merge completed" notification issue
- But this is likely unnecessary - MRs are internal coordination

### Migration

Existing MRs in permanent storage:
- Can be cleaned up with `bd cleanup` or manual deletion
- Or left to age out naturally
- No migration of open MRs needed (they'll complete under old system

## Alternatives Considered

1. **Auto-cleanup of closed MRs**: Keep MRs as permanent issues but auto-delete after 24h. Simpler but still creates sync churn and temporary JSONL pollution.

2. **MRs as mail only**: Polecat sends mail to refinery with merge details, no MR issue at all. Loses queryability (bd-801b [P2] [merge-request] closed - Merge: bd-bqcc
bd-pvu0 [P2] [merge-request] closed - Merge: bd-4opy
bd-i0rx [P2] [merge-request] closed - Merge: bd-ao0s
bd-u0sb [P2] [merge-request] closed - Merge: bd-uqfn
bd-8e0q [P2] [merge-request] closed - Merge: beads-ocs
bd-hvng [P2] [merge-request] closed - Merge: bd-w193
bd-4sfl [P2] [merge-request] closed - Merge: bd-14ie
bd-sumr [P2] [merge-request] closed - Merge: bd-t4sb
bd-3x9o [P2] [merge-request] closed - Merge: bd-by0d
bd-whgv [P2] [merge-request] closed - Merge: bd-401h
bd-f3ll [P2] [merge-request] closed - Merge: bd-ot0w
bd-fmdy [P3] [merge-request] closed - Merge: bd-kzda).

3. **Separate merge queue**: Refinery maintains internal state for pending merges, not in beads at all. Clean but requires new infrastructure.

Wisps are the cleanest solution - they already exist, have the right semantics, and require minimal changes.

## Related

- Wisp architecture:
- Current MR creation: witness/refinery code paths
- bd-pvu0, bd-801b: Example MRs currently in permanent storage
EOF
)


Code review: bd mol bond --ref and bd activity (bd-xo1o work)

Review dave's recent commits for bd-xo1o (Dynamic Molecule Bonding):

## Commits to Review
- ee04b1ea: feat: add dynamic molecule bonding with --ref flag (bd-xo1o.1)
- be520d90: feat: add bd activity command for real-time state feed (bd-xo1o.3)

## Review Focus
1. Code quality and correctness
2. Error handling
3. Edge cases
4. Test coverage
5. Documentation

## Deliverables
- File beads for any issues found
- Note any concerns or suggestions
- Verify the implementation matches the bd-xo1o epic requirements


bd mol bond --ephemeral flag

Add --ephemeral flag to bd mol bond command.

## Behavior
- `bd mol bond <proto> --ephemeral` creates molecule in .beads-ephemeral/
- Without flag, creates in .beads/ (current behavior)
- Ephemeral molecules have `ephemeral: true` in their issue record

## Implementation
- Add --ephemeral bool flag to mol bond command
- Route to EphemeralStore when flag set
- Set ephemeral:true on created issue

## Testing
- Test mol bond creates in correct location
- Test ephemeral flag is persisted
- Test regular mol bond still works


Add git operations for --hard reset

Implement git integration for hard reset mode.

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

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

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

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

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

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


Add .beads-backup-* pattern to gitignore template

Update the gitignore template in doctor package to include backup directories.

## Change
Add `.beads-backup-*/` to the GitignoreTemplate in `cmd/bd/doctor/gitignore.go`

## Why
Backup directories created by `bd reset --backup` should not be committed to git.
They are local-only recovery tools.

## File
`cmd/bd/doctor/gitignore.go` - look for GitignoreTemplate constant


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)


Fix TestImportBugIntegration deadlock in importer_test.go

Code health review found internal/importer/importer_test.go has TestImportBugIntegration skipped with:

TODO: Test hangs due to database deadlock - needs investigation

This indicates a potential unresolved concurrency issue in the importer. The test has been skipped for an unknown duration.

Fix: Investigate the deadlock, fix the underlying issue, and re-enable the test.


Merge: bd-qioh

branch: polecat/Errata
target: main
source_issue: bd-qioh
rig: beads


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

Code health review found several oversized files:

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

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

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

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


bd validate - Comprehensive health check

Run all validation checks in one command.

Checks:
- Duplicates
- Orphaned dependencies
- Test pollution
- Git conflicts

Supports --fix-all for auto-repair.

Depends on bd-cbed9619.1, bd-0dcea000, bd-31aab707, bd-9826b69a.

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


Implement bd close --continue flag

Auto-advance to next step in molecule when closing an issue. Referenced by gt-um6q, gt-lz13. Needed for molecule navigation workflow.


Delete server_cache_storage.go

Remove the entire cache implementation file (~286 lines)


Pinned issues: persistent context markers

Add ability to pin issues so they remain visible and are excluded from work-finding commands. Pinned issues serve as persistent context markers (handoffs, architectural notes, recovery instructions) that should not be claimed as work items.

Use Cases:
1. Handoff messages - Pin session handoffs so new agents always see them
2. Architecture decisions - Pin ADRs or design notes for reference
3. Recovery context - Pin amnesia-cure notes that help agents orient

Core commands: bd pin, bd unpin, bd list --pinned/--no-pinned


Test message

This is a test message body


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

## Merged PRs Summary

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

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

---

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

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

---

## Overall Assessment

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

No critical bugs or design issues found.

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


Add daemon RPC support for gate commands.

## Current State
Gate commands require --no-daemon flag because they use direct SQLite access:
- Gate create needs to write await_type, await_id, timeout_ns, waiters fields
- Gate wait needs to update waiters JSON array
- Daemon RPC doesnt have methods for these operations

## Implementation

### 1. Add RPC methods to internal/rpc/protocol.go

```go
// Gate operations
type GateCreateArgs struct {
    Title     string        \`json:"title"\`
    AwaitType string        \`json:"await_type"\`
    AwaitID   string        \`json:"await_id"\`
    Timeout   time.Duration \`json:"timeout"\`
    Waiters   []string      \`json:"waiters"\`
}

type GateCreateResult struct {
    Issue *types.Issue \`json:"issue"\`
}

type GateListArgs struct {
    All bool \`json:"all"\` // Include closed gates
}

type GateListResult struct {
    Gates []*types.Issue \`json:"gates"\`
}

type GateWaitArgs struct {
    GateID  string   \`json:"gate_id"\`
    Waiters []string \`json:"waiters"\` // Additional waiters to add
}

type GateWaitResult struct {
    Gate        *types.Issue \`json:"gate"\`
    AddedCount  int          \`json:"added_count"\`
}
```

### 2. Add handler methods to internal/daemon/rpc_handler.go

```go
func (h *RPCHandler) GateCreate(ctx context.Context, args *rpc.GateCreateArgs) (*rpc.GateCreateResult, error) {
    now := time.Now()
    gate := &types.Issue{
        Title:     args.Title,
        IssueType: types.TypeGate,
        Status:    types.StatusOpen,
        Priority:  1,
        Assignee:  "deacon/",
        Wisp:      true,
        AwaitType: args.AwaitType,
        AwaitID:   args.AwaitID,
        Timeout:   args.Timeout,
        Waiters:   args.Waiters,
        CreatedAt: now,
        UpdatedAt: now,
    }
    gate.ContentHash = gate.ComputeContentHash()

    if err := h.store.CreateIssue(ctx, gate, h.actor); err != nil {
        return nil, err
    }

    return &rpc.GateCreateResult{Issue: gate}, nil
}

func (h *RPCHandler) GateList(ctx context.Context, args *rpc.GateListArgs) (*rpc.GateListResult, error) {
    gateType := types.TypeGate
    filter := types.IssueFilter{IssueType: &gateType}
    if !args.All {
        openStatus := types.StatusOpen
        filter.Status = &openStatus
    }

    gates, err := h.store.SearchIssues(ctx, "", filter)
    if err != nil {
        return nil, err
    }

    return &rpc.GateListResult{Gates: gates}, nil
}

func (h *RPCHandler) GateWait(ctx context.Context, args *rpc.GateWaitArgs) (*rpc.GateWaitResult, error) {
    gate, err := h.store.GetIssue(ctx, args.GateID)
    if err != nil {
        return nil, err
    }
    if gate.IssueType != types.TypeGate {
        return nil, fmt.Errorf("%s is not a gate", args.GateID)
    }

    // Merge waiters (dedupe)
    waiterSet := make(map[string]bool)
    for _, w := range gate.Waiters {
        waiterSet[w] = true
    }
    added := 0
    for _, w := range args.Waiters {
        if !waiterSet[w] {
            gate.Waiters = append(gate.Waiters, w)
            waiterSet[w] = true
            added++
        }
    }

    if added > 0 {
        // Update via store
        updates := map[string]interface{}{
            "waiters": gate.Waiters,
        }
        if err := h.store.UpdateIssue(ctx, args.GateID, updates, h.actor); err != nil {
            return nil, err
        }
    }

    return &rpc.GateWaitResult{Gate: gate, AddedCount: added}, nil
}
```

### 3. Register methods in daemon

In internal/daemon/server.go, register the new methods:
```go
rpc.RegisterMethod("gate.create", h.GateCreate)
rpc.RegisterMethod("gate.list", h.GateList)
rpc.RegisterMethod("gate.wait", h.GateWait)
```

### 4. Add client methods to internal/rpc/client.go

```go
func (c *Client) GateCreate(ctx context.Context, args *GateCreateArgs) (*GateCreateResult, error) {
    var result GateCreateResult
    err := c.Call(ctx, "gate.create", args, &result)
    return &result, err
}

func (c *Client) GateList(ctx context.Context, args *GateListArgs) (*GateListResult, error) {
    var result GateListResult
    err := c.Call(ctx, "gate.list", args, &result)
    return &result, err
}

func (c *Client) GateWait(ctx context.Context, args *GateWaitArgs) (*GateWaitResult, error) {
    var result GateWaitResult
    err := c.Call(ctx, "gate.wait", args, &result)
    return &result, err
}
```

### 5. Update cmd/bd/gate.go to use daemon

```go
// In gateCreateCmd Run:
if daemonClient != nil {
    result, err := daemonClient.GateCreate(ctx, &rpc.GateCreateArgs{
        Title:     title,
        AwaitType: awaitType,
        AwaitID:   awaitID,
        Timeout:   timeout,
        Waiters:   notifyAddrs,
    })
    if err != nil {
        FatalError("gate create: %v", err)
    }
    gate = result.Issue
} else {
    // Existing direct store code
}
```

## Files to Modify

1. **internal/rpc/protocol.go** - Add Gate*Args/Result types
2. **internal/daemon/rpc_handler.go** - Add handler methods
3. **internal/daemon/server.go** - Register methods
4. **internal/rpc/client.go** - Add client methods
5. **cmd/bd/gate.go** - Use daemon client when available

## Testing

```bash
# Start daemon
bd daemon start

# Test via daemon (should work without --no-daemon)
bd gate create --await timer:5m --notify beads/dave
bd gate list
bd gate wait <id> --notify beads/alice

# Verify daemon handled it
bd daemons logs . | grep gate
```

## Success Criteria
- All gate commands work without --no-daemon
- Same behavior in daemon vs direct mode
- Waiters array updates correctly via RPC
- Tests pass for RPC gate operations


Fix TestRunSync_Timeout failing on macOS

The hooks timeout test fails because exec.CommandContext doesn't properly terminate child processes of shell scripts on macOS. The test creates a hook that runs 'sleep 60' with a 500ms timeout, but it waits the full 60 seconds.

Options to fix:
- Use SysProcAttr{Setpgid: true} to create process group and kill the group
- Skip test on darwin with build tag
- Use a different approach for timeout testing

Location: internal/hooks/hooks_test.go:220-253


Gate: timer:1m


Update info.go versionChanges

Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for 0.30.7


WaitsFor directive: Fanout gate for dynamic children

Implement WaitsFor directive for molecules that spawn dynamic children.

## Syntax
```markdown
## Step: aggregate
Collect outcomes from all dynamically-bonded children.
WaitsFor: all-children
Needs: survey-workers
```

## Behavior
1. Parse WaitsFor directive during molecule step parsing
2. Track which steps spawn dynamic children (the spawner)
3. Gate step waits until ALL children of the spawner complete
4. Works with bd ready - gate step not ready until children done

## Gate Types
- `WaitsFor: all-children` - Wait for all dynamic children
- `WaitsFor: any-children` - Proceed when first child completes (future)
- `WaitsFor: <step-ref>.children` - Wait for specific spawner's children

## Integration
- bd ready should skip gate steps until children complete
- bd show <mol> should display gate status and child count


Optimization: Tombstones still synced, adding overhead

Tombstoned (deleted) issues are still processed during sync, adding overhead.

## Evidence
```
Import complete: 0 created, 0 updated, 407 unchanged, 100 skipped
```
Those 100 skipped are tombstones - they're read from JSONL, parsed, then filtered out.

## Current State (Beads repo)
- 408 total issues
- 99 tombstones (24% of database)
- Every sync reads and skips these 99 entries

## Impact
- Sync time increases with tombstone count
- JSONL file size grows indefinitely
- Git history accumulates tombstone churn

## Proposed Solutions

### 1. JSONL Compaction (`bd compact`)
Periodically rewrite JSONL without tombstones:
```bash
bd compact  # Removes tombstones, rewrites issues.jsonl
```
Trade-off: Loses delete history, but that's in git anyway.

### 2. Tombstone TTL
Auto-remove tombstones older than N days during sync:
```go
if issue.Deleted && time.Since(issue.UpdatedAt) > 7*24*time.Hour {
    // Skip writing to new JSONL
}
```

### 3. Archive File
Move old closed issues to `issues-archive.jsonl`:
- Not synced regularly
- Available for historical queries
- Main JSONL stays small

### 4. Lazy Tombstone Handling
Don't write tombstones to JSONL at all - just remove the line:
- Simpler, but loses cross-clone delete propagation
- Would need different delete propagation mechanism

## Recommendation
Start with `bd compact` command - simple, explicit, user-controlled.

## Related
- gt-tnss: Analysis - Beads database size and hygiene strategy
- gt-ox67: Maintenance - Regular cleanup of closed MR/gate beads


Commit and push release

git add -A && git commit && git push to trigger CI


Update CHANGELOG.md for {{version}}

In CHANGELOG.md:

1. Change `## [Unreleased]` section header to `## [{{version}}] - {{date}}`
2. Add new empty `## [Unreleased]` section above it
3. Review and clean up the changes list

Format:
```markdown
## [Unreleased]

## [{{version}}] - {{date}}

### Added
- ...

### Changed
- ...

### Fixed
- ...
```


Improve version bump molecule with missing steps

During v0.32.1 release, discovered missing steps in the release molecule:

**Missing from molecule:**
1. Rebuild ~/go/bin/bd (only did ~/.local/bin/bd)
2. Install beads-mcp from local source: `uv tool install --reinstall ./integrations/beads-mcp`
3. Restart daemons with `bd daemons killall`
4. (Optional) Publish beads-mcp to PyPI

**Current molecule steps (bd-6s61):**
1. Update CHANGELOG.md
2. Update info.go versionChanges
3. Run bump-version.sh
4. Run tests and linting
5. Update local installation
6. Commit and push release
7. Wait for CI
8. Verify release artifacts

**Proposed additions:**
- After "Update local installation": rebuild BOTH ~/.local/bin/bd AND ~/go/bin/bd
- Add: "Install beads-mcp from source" step
- Add: "Restart daemons" step
- Add: "Verify all versions match" step that checks all artifacts

**Also learned:**
- Must run from mayor/rig to avoid git conflicts with bd sync (already documented in bump-version.sh)

CORRECTION: npm publishing IS automated and working!

**Package naming:**
- OLD: `beads` (npm) - deprecated, stuck at 0.2.1
- CURRENT: `@beads/bd` (npm) - scoped package, auto-published by CI

**How it works:**
- CI uses OIDC trusted publishing (no token needed)
- Workflow: .github/workflows/release.yml → publish-npm job
- Permissions: `id-token: write` enables GitHub OIDC
- To install: `npm install -g @beads/bd` (not `npm install beads`)

**All publishing is automated on tag push:**
1. GitHub Release - goreleaser ✓
2. PyPI - publish-pypi job ✓
3. Homebrew - update-homebrew job ✓
4. npm (@beads/bd) - publish-npm job ✓

**Remaining molecule improvements (local steps only):**
- Rebuild BOTH ~/.local/bin/bd AND ~/go/bin/bd
- Install beads-mcp from source: `uv tool install --reinstall ./integrations/beads-mcp`
- Restart daemons: `bd daemons killall`
- Run from mayor/rig to avoid git conflicts with bd sync
- Final verification step to check all local versions match


Update go install bd to {{version}}

Rebuild and install bd to ~/go/bin:

```bash
go install ./cmd/bd
~/go/bin/bd version  # Verify shows {{version}}
```

Note: If ~/go/bin is in PATH before /opt/homebrew/bin, this is the version that runs by default.


refinery Handoff

attached_molecule: bd-ndye
attached_at: 2025-12-23T12:35:07Z


bd preflight: PR readiness checks for contributors

## Vision

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

## Problem Statement

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

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

## Why beads?

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

## Proposed Interface

### Tier 1: Checklist Mode (v1)

    $ bd preflight
    PR Readiness Checklist:

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

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

### Tier 2: Check Mode (v2)

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

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

### Tier 3: Fix Mode (v3)

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

## Checks to Implement

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

## Future: Configuration

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

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

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

## Implementation Phases

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

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

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

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

## Dependencies

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

## Success Metrics

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


sync branch not match config

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

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


Add GetWorkerStatus RPC endpoint

New RPC endpoint to get all workers and their current molecule/step in one call. Returns: assignee, moleculeID, moleculeTitle, currentStep, totalSteps, stepTitle, lastActivity, status. Enables activity feed TUI to show worker state without multiple round trips.


Auto-migrate database on CLI version bump

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


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.


Improve clean vs cleanup command naming/documentation

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

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

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

2. Keep names but improve help text and documentation

3. Add prominent warnings in help output

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

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


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


Add gate timeout tracking and notification

Implement timeout and notification logic for gates.

## Timeout Behavior
1. Gate created with timeout (e.g., 30m)
2. Deacon tracks elapsed time during patrol
3. If timeout reached:
   - Notify all waiters: "Gate timed out"
   - Close gate with timeout reason
   - Waiter can retry, escalate, or fail gracefully

## Notification
- Use gt mail send to notify waiters
- Include gate ID, await type, and reason in message
- Support multiple waiters notification

## Escalation Path
- Witness sees stuck worker, nudges them
- Worker can escalate to human if needed


Merge: bd-thgk

branch: polecat/Compactor
target: main
source_issue: bd-thgk
rig: beads


Add tests for daemon functionality

Critical daemon functions have 0% test coverage including daemon lifecycle, health checks, and RPC server functionality. These are essential for system reliability and need comprehensive test coverage.


Add creator field to Issue struct

Add Creator *EntityRef field to Issue. Tracks who created the issue. Optional, omitted if nil in JSONL. This enables CV chain tracking - every piece of work is attributed to its creator.


Push version bump to GitHub

git push origin main - triggers CI but no release yet.


bd init UX Improvements

bd init leaves users with incomplete setup, requiring manual bd doctor --fix. Issues found: (1) git hooks not installed if user declines prompt, (2) no auto-migration when CLI is upgraded, (3) stale merge driver configs from old versions. Fix by making bd init more robust with better defaults and auto-migration.


Merge: bd-au0.5

branch: polecat/Searcher
target: main
source_issue: bd-au0.5
rig: beads


Phase 2.2: Switch bdt storage to TOON format

Currently bdt stores issues in JSONL format in issues.toon file. Phase 2.2 must implement actual TOON format storage - this is the fundamental goal of the bdtoon project.

## Current State (Phase 2.1)
- issues.toon stores JSONL (intermediate format)
- --toon flag allows output in TOON format for LLM consumption
- Problem: We're not actually using TOON as the fundamental storage format

## Required Work (Phase 2.2)
1. Switch issue file I/O to write TOON format instead of JSONL
   - Update cmd/bdt/storage.go to use EncodeTOON for writing
   - Update cmd/bdt/storage.go to decode TOON (currently decodes JSON)
   - Ensure round-trip: write TOON → read TOON → write TOON is byte-identical

2. Update command implementations
   - cmd/bdt/create.go: Write newly created issues to TOON format
   - cmd/bdt/list.go: Read issues from TOON format
   - cmd/bdt/show.go: Read from TOON format
   - cmd/bdt/import.go: Convert imported JSONL to TOON
   - cmd/bdt/export.go: Export TOON to JSONL (for bd compatibility)

3. Implement TOON parser that handles gotoon's encoder-only limitation
   - Since gotoon doesn't decode TOON, need custom TOON→JSON decoder
   - OR continue storing TOON but decoding via intermediate JSON conversion

4. Git merge driver optimization
   - TOON is line-oriented, better for 3-way merges than binary formats
   - Configure git merge driver for .toon files

5. Comprehensive testing
   - Round-trip tests: Issue → TOON → storage → read → Issue
   - Merge conflict resolution tests with TOON format
   - Large issue set performance tests

## Success Criteria
- issues.toon stores actual TOON format (not JSONL)
- bdt list reads from TOON file
- bdt create writes to TOON file
- Round-trip: create issue → list → show returns identical data
- All 65+ tests still passing
- Performance comparable to JSONL storage


Upgrade beads-mcp to 0.33.2

Upgrade the MCP server via pip:

```bash
pip install --upgrade beads-mcp
pip show beads-mcp | grep Version  # Verify 0.33.2
```

Note: Restart Claude Code or MCP session to use new version.


Merge: bd-dxtc

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


Fix checkExistingBeadsData to be worktree-aware

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


Run bump-version.sh 0.32.1

Execute ./scripts/bump-version.sh 0.32.1 to update all version references


Pinned field available in beads v0.37.0

Hey max,

Heads up on your mail overhaul work:

1. **Pinned field is available** - beads v0.37.0 (released by dave earlier) includes the pinned field on issues. You'll want to add this to BeadsMessage in types.go.

2. **Database migration** - Check if existing .beads databases need migration to support the pinned field. Run `bd doctor` to see if it flags anything.

3. **Sorting task** - Once you have the pinned field, gt-ngu1 (pinned beads first in mail inbox) needs implementing. Since messages now come from `bd list --type=message`, you'll need to either:
   - Sort in listBeads() after fetching, or
   - Ensure bd list returns pinned items first (may already do this?)

Check what version of bd you're building against.

-- Mayor


Add empty tasks validation in workflow create

workflow.go:321 will panic if wf.Tasks is empty. Add validation that len(wf.Tasks) > 0 before accessing wf.Tasks[0].


Add close_reason field to CLI schema and documentation

PR #551 persists close_reason, but the CLI documentation may not mention this field as part of the issue schema.

## Current State
- close_reason is now persisted in database
- `bd show --json` will return close_reason in JSON output
- Documentation may not reflect this new field

## What's Missing
- CLI reference documentation for close_reason field
- Schema documentation showing close_reason is a top-level issue field
- Example output showing close_reason in bd show --json
- bd close command documentation should mention close_reason parameter is optional

## Suggested Action
1. Update README.md or CLI reference docs to list close_reason as an issue field
2. Add example to bd close documentation
3. Update any type definitions or schema specs
4. Consider adding close_reason to verbose list output (bd list --verbose)


bd pin: pinned field overwritten by subsequent bd commands

## Summary

The `bd pin` command correctly sets `pinned=1` in SQLite, but any subsequent `bd` command (including read-only commands like `bd show`) resets `pinned` to 0.

## Reproduction Steps

```bash
bd --no-daemon pin <issue-id> --for=max
sqlite3 .beads/beads.db "SELECT id, pinned FROM issues WHERE id=\"<issue-id>\""
# Shows pinned=1 ✓

bd --no-daemon show <issue-id> --json
sqlite3 .beads/beads.db "SELECT id, pinned FROM issues WHERE id=\"<issue-id>\""
# Shows pinned=0 ✗ WRONG
```

## Root Cause Investigation

### Prime Suspects

1. **JSONL import overwrites DB** - The `pinned` field has `omitempty` so false values arent in JSONL. When JSONL is imported, it overwrites the DB pinned=1 with default pinned=0.

2. **Files to check:**
   - `internal/importer/importer.go` - ImportIssue() may unconditionally set all fields
   - `internal/storage/sqlite/issues.go` - UpsertIssue() may not preserve pinned
   - `cmd/bd/main.go` - ensureStoreActive() may trigger import

### Debug Steps

```bash
# Add debug logging to track what is writing pinned=0
grep -rn "pinned" internal/storage/sqlite/*.go
grep -rn "Pinned" internal/importer/*.go
```

## Likely Fix

In `internal/importer/importer.go` or `internal/storage/sqlite/issues.go`:

```go
// When upserting from JSONL, preserve pinned field if already set
func (s *SQLiteStorage) UpsertIssue(ctx context.Context, issue *types.Issue) error {
    // Check if issue exists and is pinned
    existing, _ := s.GetIssue(ctx, issue.ID)
    if existing != nil && existing.Pinned && !issue.Pinned {
        // Preserve existing pinned status
        issue.Pinned = existing.Pinned
    }
    // ... rest of upsert
}
```

OR the import should skip fields that are omitempty and not present in JSONL:

```go
// In importer, only update fields that are explicitly set in JSONL
// Pinned with omitempty means absent = dont change, not absent = false
```

## Testing

```bash
# After fix:
bd --no-daemon pin <issue-id> --for=max
bd --no-daemon show <issue-id> --json  # Should not reset pinned
bd list --pinned  # Should show the pinned issue
bd hook --agent max  # Should show pinned work
```

## Files to Modify

1. **internal/importer/importer.go** - Preserve pinned on import
2. **internal/storage/sqlite/issues.go** - UpsertIssue preserve pinned
3. **Add test** in internal/importer/importer_test.go

## Success Criteria
- `bd pin` survives subsequent bd commands
- `bd list --pinned` shows pinned issues
- `bd hook --agent X` shows pinned work
- Existing tests still pass


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

Approaches:
1. Mechanical: Exact title/description matching
2. Embeddings: Cosine similarity (cheap, scalable)
3. AI: LLM-based semantic comparison (expensive, accurate)

Uses embeddings by default for >100 issues.

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


🤝 HANDOFF: v0.31.0 released, molecules discussion

Session completed 0.31.0 release and had important molecules discussion.

## Completed
- v0.31.0 released (deferred status, audit trail, directory labels, etc.)
- Fixed lint issues, hook version markers, codesigning
- All CI green, artifacts verified

## Filed Issues
- bd-usro: Rename template instantiate → bd mol bond
- bd-y8bj: Auto-detect identity for bd mail (P1 bug)
- gt-975: Molecule execution support for polecats/crew
- gt-976: Crew lifecycle support in Deacon

## Key Insight
Molecules are the future - TodoWrite is ephemeral, molecules are persistent institutional memory on the world chain. I tried to use TodoWrite for version bump and missed steps (codesigning, MCP verification). Molecules would have caught this.

## Next Steps
- bd mol bond implementation is priority
- Max has gt-976 for crew lifecycle (enables automated refresh mid-molecule)

Check bd ready and gt-975/976 status.


Investigate TestRoutingIntegration failure

TestRoutingIntegration/maintainer_with_SSH_remote failed during pre-commit check with "expected role maintainer, got contributor".
This occurred while running `go test -short ./...` on darwin/arm64.
The failure appears unrelated to storage/sqlite changes.
Need to investigate if this is a flaky test or environmental issue.


Create and push git tag v{{version}}

Create the release tag and push it:

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

This triggers the GoReleaser GitHub Action to build release binaries.


Protect pinned issues from bd cleanup/compact

Update bd cleanup and bd compact to never delete pinned issues, even if they are closed. Pinned issues should persist indefinitely as reference material.


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

## Patrol Integration
```go
func (d *Deacon) checkGates(ctx context.Context) {
    gates, _ := d.store.ListOpenGates(ctx)

    for _, gate := range gates {
        // Check timeout
        if time.Since(gate.CreatedAt) > gate.Timeout {
            d.notifyWaiters(gate, "timeout")
            d.closeGate(gate, "timed out")
            continue
        }

        // Check condition
        if d.checkCondition(gate.AwaitType, gate.AwaitID) {
            d.notifyWaiters(gate, "cleared")
            d.closeGate(gate, "condition met")
        }
    }
}
```

## Note
This task is in Gas Town (gt), not beads. May need to be moved there.


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.


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


Update hook templates to 0.30.4

Update bd-hooks-version comment in all 4 hook templates:
- cmd/bd/templates/hooks/pre-commit
- cmd/bd/templates/hooks/post-merge
- cmd/bd/templates/hooks/pre-push
- cmd/bd/templates/hooks/post-checkout

Each should have:
```bash
# bd-hooks-version: 0.30.4
```

```verify
grep -l 'bd-hooks-version: 0.30.4' cmd/bd/templates/hooks/* | wc -l | grep -q '4'
```


Add Windows code signing for bd.exe releases

## Context

Go binaries (including bd.exe) are commonly flagged by antivirus software as false positives due to heuristic detection. See docs/ANTIVIRUS.md for full details.

## Problem

Kaspersky and other AV software flag bd.exe as PDM:Trojan.Win32.Generic, causing it to be quarantined or deleted.

## Solution

Implement code signing for Windows releases using:
1. An EV (Extended Validation) code certificate
2. Integration with GoReleaser to sign Windows binaries during release

## Benefits

- Reduces false positive rates over time as the certificate builds reputation
- Provides tamper verification for users
- Improves SmartScreen trust rating on Windows
- Professional appearance for enterprise users

## Implementation Steps

1. Acquire EV code signing certificate (annual cost ~$300-500)
2. Set up signtool or osslsigncode in release pipeline
3. Update .goreleaser.yml to sign Windows binaries
4. Update checksums to include signed binary hashes
5. Document signing verification in ANTIVIRUS.md

## References

- docs/ANTIVIRUS.md - Current documentation
- bd-t4u1 - Original Kaspersky false positive report
- https://github.com/golang/go/issues/16292 - Go project discussion


Test pinned for stats


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


Add covering index (label, issue_id) for label queries

GetIssuesByLabel joins labels table but requires table lookup after using idx_labels_label.

**Query (labels.go:165):**
```sql
SELECT ... FROM issues i
JOIN labels l ON i.id = l.issue_id
WHERE l.label = ?
```

**Problem:** Current idx_labels_label index doesn't cover issue_id, requiring row lookup.

**Solution:** Add migration:
```sql
CREATE INDEX IF NOT EXISTS idx_labels_label_issue ON labels(label, issue_id);
```

This is a covering index - query can be satisfied entirely from the index without touching the labels table rows.


bd graph crashes with --no-daemon on closed issues

The `bd graph` command panics with nil pointer dereference when using `--no-daemon` flag on an issue with closed children.

**Reproduction:**
```bash
bd graph bd-qqc --no-daemon
# panic: runtime error: invalid memory address or nil pointer dereference
# in main.computeDependencyCounts
```

**Stack trace:**
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x20 pc=0x1010bdfb0]

goroutine 1 [running]:
main.computeDependencyCounts(...)
    /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:428
main.renderGraph(0x1400033bb80, 0x0)
    /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:307 +0x300
```

**Location:** cmd/bd/graph.go:428 - computeDependencyCounts() not handling nil case


Commit and push release

git add -A && git commit && git push to trigger CI


bd mol burn deletes ephemeral without digest

Update bd mol burn to handle ephemeral molecules.

## Behavior for Ephemeral Molecules
- Delete wisp from .beads-ephemeral/
- NO digest created (unlike squash)
- Used for abandoned/crashed cycles

## Difference from Squash
| Command | Ephemeral Behavior |
|---------|-------------------|
| squash | Delete wisp, create digest |
| burn | Delete wisp, no trace |

## Implementation
- Detect if molecule is ephemeral
- Delete from ephemeral store
- Skip digest creation


Merge: bd-m8ro

branch: polecat/max
target: main
source_issue: bd-m8ro
rig: beads


Merge: bd-uqfn

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


Edge Schema Consolidation: Unify all edges in dependencies table

Consolidate all edge types into the dependency table per decision 004.

## Changes
- Add metadata column to dependencies table
- Add thread_id column for conversation grouping
- Remove redundant Issue fields: replies_to, relates_to, duplicate_of, superseded_by
- Update all code to use dependencies API
- Migration script for existing data
- JSONL format change (breaking)

Reference: ~/gt/hop/decisions/004-edge-schema-consolidation.md


Run bump-version.sh test-squash

Run ./scripts/bump-version.sh test-squash to update version in all files


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


Migrate sort.Slice to slices.SortFunc

Go 1.21+ provides slices.SortFunc which is cleaner and slightly faster than sort.Slice.

Found 15+ instances of sort.Slice in:
- cmd/bd/autoflush.go
- cmd/bd/count.go
- cmd/bd/daemon_sync.go
- cmd/bd/doctor.go
- cmd/bd/export.go
- cmd/bd/import.go
- cmd/bd/integrity.go
- cmd/bd/jira.go
- cmd/bd/list.go
- cmd/bd/migrate_hash_ids.go
- cmd/bd/rename_prefix.go
- cmd/bd/show.go

Example migration:
```go
// Before
sort.Slice(issues, func(i, j int) bool {
    return issues[i].Priority < issues[j].Priority
})

// After
slices.SortFunc(issues, func(a, b *types.Issue) int {
    return cmp.Compare(a.Priority, b.Priority)
})
```

Benefits:
- Cleaner 3-way comparison
- Slightly better performance
- Modern idiomatic Go


Merge: bd-indn

branch: polecat/rictus
target: main
source_issue: bd-indn
rig: beads


Merge: bd-io8c

branch: polecat/Syncer
target: main
source_issue: bd-io8c
rig: beads


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


Push release v0.33.2 to remote

Push the commit and tag:

```bash
git push && git push --tags
```

Verify on GitHub that the tag appears in releases.


Add comments to 30+ silently ignored errors or fix them

Code health review found 30+ instances of error suppression using blank identifier without explanation:

Good examples (with comments):
- merge.go: _ = gitRmCmd.Run() // Ignore errors
- daemon_watcher.go: _ = watcher.Add(...) // Ignore error

Bad examples (no context):
- create.go:213: dbPrefix, _ = store.GetConfig(ctx, "issue_prefix")
- daemon_sync_branch.go: _ = daemonClient.Close()
- migrate_hash_ids.go, version_tracking.go: _ = store.Close()

Fix: Add comments explaining WHY errors are ignored, or handle them properly.


Issue lifecycle hooks (on-close, on-complete)

Add hooks that fire on issue state transitions, enabling automation like closing linked GitHub issues.

## Problem

We have `external_ref` to link beads issues to external systems (GitHub, Linear, Jira), but no mechanism to trigger actions when issues close. Currently:

```
bd-u2sc (external_ref: gh-692) closes → nothing happens
```

## Proposed Solution

### Phase 1: Shell Hooks

Add `.beads-hooks/on-close.sh` (and similar lifecycle hooks):

```bash
# .beads-hooks/on-close.sh
# Called by bd close with issue JSON on stdin
#\!/bin/bash
issue=$(cat)
external_ref=$(echo "$issue" | jq -r '.external_ref // empty')
if [[ "$external_ref" == gh-* ]]; then
    number="${external_ref#gh-}"
    gh issue close "$number" --repo steveyegge/beads \
      --comment "Completed via beads epic $(echo $issue | jq -r .id)"
fi
```

### Lifecycle Events

| Event | Trigger | Use Cases |
|-------|---------|-----------|
| `on-close` | Issue closed | Close external refs, notify, archive |
| `on-complete` | Epic children all done | Roll-up completion, close parent refs |
| `on-status-change` | Any status transition | Sync to external systems |

### Phase 2: Molecule Completion Handlers

Molecules could define completion actions:

```yaml
name: github-issue-tracker
on_complete:
  - action: shell
    command: gh issue close {{external_ref}} --repo {{repo}}
  - action: mail
    to: mayor/
    subject: "Epic {{id}} completed"
```

### Phase 3: Gas Town Integration

For full Gas Town deployments:
- Witness observes closures via beads events
- Routes to integration agents via mail
- Agents handle external system interactions

## Implementation Notes

- Hooks should be async (don't block bd close)
- Pass full issue JSON to hook via stdin
- Support hook timeout and failure handling
- Consider `--no-hooks` flag for bulk operations

## Related

- `external_ref` field already exists (GH#142)
- Cross-project deps: bd-h807, bd-d9mu
- Git hooks: .beads-hooks/ pattern established

## Use Cases

1. **GitHub integration**: Close GH issues when beads epic completes
2. **Linear sync**: Update Linear status when beads status changes
3. **Notifications**: Send mail/Slack when high-priority issues close
4. **Audit**: Log all closures to external system


Graph Link: supersedes for version chains

Implement supersedes link type for version tracking.

New command:
- bd supersede <id> --with <new> - marks id as superseded by new
- Auto-closes the superseded issue

Query support:
- bd show <id> shows 'Superseded by: <new>'
- bd show <new> shows 'Supersedes: <id>'
- bd list --superseded shows version chains

Storage:
- superseded_by column pointing to replacement issue

Useful for design docs, specs, and evolving artifacts.


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


Restart running daemons

Kill and restart any running bd daemons to pick up new version: pkill -f 'bd daemon' && bd daemon --start


wish: list & ready show issues as hierarchy tree

`bd ready` and `bd list` just show a flat list, and it's up to the reader to parse which ones are dependent or sub-issues of others. It would be much easier to understand if they were shown in a tree format


test hash length


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


Graph Link: duplicates for deduplication

Implement duplicates link type for marking issues as duplicates.

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

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

Storage:
- duplicates column pointing to canonical issue ID

Essential for large issue databases with many similar reports.


activity.go: Missing test coverage

The new activity.go command (from bd-xo1o.3) has no test coverage. At minimum, tests should cover:
- parseDurationString() for various formats (5m, 1h, 2d, invalid)
- filterEvents() for --mol and --type filtering
- formatEvent() and getEventDisplay() for all mutation types

Discovered during code review of bd-xo1o implementation.


Respond to GitHub issue #479 with solution

Once bd reset is implemented and released, respond to GitHub issue #479.

## Response should include
- Announce the new bd reset command
- Show basic usage examples
- Link to any documentation
- Thank the user for the feedback

## Example response
```
Thanks for raising this! We've added a `bd reset` command to handle this case.

Usage:
- `bd reset` - Reset to clean state (prompts for confirmation)
- `bd reset --backup` - Create backup first
- `bd reset --hard` - Also clean up git history

This is available in version X.Y.Z.
```

## Notes
- Wait until feature is merged and released
- Consider if issue should be closed or left for user confirmation


test pin issue


Migration invariants should exclude external dependencies from orphan check

## Summary

The `checkForeignKeys` function in `migration_invariants.go` flags external dependencies as orphaned because they dont exist in the local issues table.

## Location

`internal/storage/sqlite/migration_invariants.go` around line 150-162

## Current Code (buggy)

```go
// Check for orphaned dependencies
var orphanCount int
err = db.QueryRowContext(ctx, \`
    SELECT COUNT(*)
    FROM dependencies d
    WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = d.depends_on_id)
\`).Scan(&orphanCount)
```

## Fix

Exclude external references (format: `external:<project>:<capability>`):

```go
// Check for orphaned dependencies (excluding external refs)
var orphanCount int
err = db.QueryRowContext(ctx, \`
    SELECT COUNT(*)
    FROM dependencies d
    WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = d.depends_on_id)
      AND d.depends_on_id NOT LIKE 'external:%'
\`).Scan(&orphanCount)
```

## Reproduction

```bash
# Add external dependency
bd dep add bd-xxx external:other-project:some-capability

# Try to sync - fails
bd sync
# Error: found 1 orphaned dependencies
```

## Files to Modify

1. **internal/storage/sqlite/migration_invariants.go** - Add WHERE clause

## Testing

```bash
# Create issue with external dep
bd create "Test external deps" -t task
bd dep add bd-xxx external:beads:release-workflow

# Sync should succeed
bd sync

# Verify dep exists
bd show bd-xxx --json | jq .dependencies
```

## Success Criteria
- External dependencies dont trigger orphan check
- `bd sync` succeeds with external deps
- Regular orphan detection still works for internal deps


Commit release v{{version}}

Stage and commit the version bump:

```bash
git add cmd/bd/version.go cmd/bd/info.go CHANGELOG.md
git commit -m "release: v{{version}}"
```

Do NOT push yet - tag first.


Add integration tests for bd reset command

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

## Test Scenarios

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

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

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

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

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

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


Fix daemon/direct mode inconsistency in relate and duplicate commands

The relate.go and duplicate.go commands have inconsistent daemon/direct mode handling:

When daemonClient is connected, they resolve IDs via RPC but then perform updates directly via store.UpdateIssue(), bypassing the daemon.

Affected locations:
- relate.go:125-139 (runRelate update)
- relate.go:235-246 (runUnrelate update)
- duplicate.go:120 (runDuplicate update)
- duplicate.go:207 (runSupersede update)

Should either use RPC for updates when daemon is running, or document why direct access is intentional.


Tier 1: Ephemeral repo routing

Simplified: Make mol spawn set ephemeral=true on spawned issues.

## The Fix

Modify cloneSubgraph() in template.go to set Ephemeral: true:

```go
// template.go:474
newIssue := &types.Issue{
    Title:      substituteVariables(oldIssue.Title, vars),
    // ... existing fields ...
    Ephemeral:  true,  // ADD THIS LINE
}
```

## Optional: Add --persistent flag

Add flag to bd mol spawn for when you want spawned issues to persist:

```bash
bd mol spawn mol-code-review --var pr=123  # ephemeral (default)
bd mol spawn mol-code-review --var pr=123 --persistent  # not ephemeral
```

## Why This Is Simpler Than Original Design

Original design proposed separate ephemeral repo routing. After code review:

- Ephemeral field already exists in schema
- bd cleanup --ephemeral already works
- No new config needed
- No multi-repo complexity

## Acceptance Criteria

- bd mol spawn creates issues with ephemeral=true
- bd cleanup --ephemeral -f deletes them after closing
- --persistent flag opts out of ephemeral
- Existing molecules continue to work


GH#518: Document bd setup command

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


Merge: bd-likt

branch: polecat/Gater
target: main
source_issue: bd-likt
rig: beads


Async Gates for Agent Coordination

Agents need an async primitive for waiting on external events (CI completion, API responses, human approval). Gates are wisp issues that block until external conditions are met, managed by the Deacon.

## Core Concepts

**Gate** = wisp issue that blocks until external condition is met
- Type: gate
- Phase: wisp (never synced, ephemeral)
- Assignee: deacon/ (Deacon monitors it)
- Fields: await_type, await_id, timeout, waiters[]

**Await Types:**
- gh:run:<id> - GitHub Actions run completion
- gh:pr:<id> - PR merged/closed
- timer:<duration> - Simple delay
- human:<prompt> - Human approval required
- mail:<pattern> - Wait for mail matching pattern

## Open Questions
- Should gates live in wisp storage or main storage with wisp flag?
- Do we need a gate catalog (like molecule catalog)?
- Should waits-for dep type work with gates?


Work on beads-tip: Fix broken Claude integration link in ...

Work on beads-tip: Fix broken Claude integration link in bd doctor (GH#623). Update URL that doesn't exist. When done, submit MR (not PR) to integration branch for Refinery.


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


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#517: Claude sets priority wrong on new install

Claude uses 'medium/high/low' for priority instead of P0-P4. Update bd prime/onboard output to be clearer about priority syntax. See GitHub issue #517.


Add metadata and thread_id columns to dependencies table

Schema changes:
- ALTER TABLE dependencies ADD COLUMN metadata TEXT DEFAULT '{}'
- ALTER TABLE dependencies ADD COLUMN thread_id TEXT DEFAULT ''
- CREATE INDEX idx_dependencies_thread ON dependencies(thread_id) WHERE thread_id != ''


gt spawn/crew setup should create .beads/redirect for worktrees

Crew clones and polecats need a .beads/redirect file pointing to the shared beads database (../../mayor/rig/.beads). Currently:

- redirect files can get deleted by git clean
- not auto-created during gt spawn or worktree setup
- missing redirects cause 'no beads database found' errors

Found missing in: gastown/joe, beads/zoey (after git clean)

Fix options:
1. gt spawn creates redirect during worktree setup
2. gt prime regenerates missing redirects
3. bd commands auto-detect worktree and find shared beads

This should be standard Gas Town rig configuration.


Cleanup: --ephemeral flag

Update bd cleanup to handle ephemeral issues.

New flag:
- bd cleanup --ephemeral - deletes all CLOSED issues with ephemeral=true

Behavior:
- Only deletes if status=closed AND ephemeral=true
- Respects --dry-run flag
- Reports count of deleted ephemeral issues

This allows swarm cleanup to remove transient messages without affecting permanent issues.


bd merge omits priority:0 due to omitempty JSON tag

GitHub issue #671. The merge code in internal/merge/merge.go uses 'omitempty' on the Priority field, which causes priority:0 (P0/critical) to be dropped from JSON output since 0 is Go's zero value for int. Fix: either remove omitempty from Priority field or use a pointer (*int). This affects the git merge driver and causes P0 issues to lose their priority.


False positive detection by Kaspersky Antivirus (Trojan)

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


Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1186

Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1186:2. Error: rows.Close()


Remove unreachable utility functions

Several small utility functions are unreachable:

Files to clean:
1. `internal/storage/sqlite/hash.go` - `computeIssueContentHash` (line 17)
   - Check if entire file can be deleted if only contains this function

2. `internal/config/config.go` - `FileUsed` (line 151)
   - Delete unused config helper

3. `cmd/bd/git_sync_test.go` - `verifyIssueOpen` (line 300)
   - Delete dead test helper

4. `internal/compact/haiku.go` - `HaikuClient.SummarizeTier2` (line 81)
   - Tier 2 summarization not implemented
   - Options: implement feature OR delete method

Impact: Removes 50-100 LOC depending on decisions


Integration test: agent waits for CI via gate

End-to-end test of the gate workflow.

## Test Scenario
1. Agent creates gate: bd gate create --await gh:run:123 --timeout 5m --notify beads/dave
2. Agent writes handoff and exits
3. Deacon patrol checks gate condition
4. (Mock) GitHub run completes
5. Deacon notifies waiter and closes gate
6. New agent session reads mail and resumes

## Test Requirements
- Mock GitHub API responses
- Test timeout path
- Test multiple waiters
- Verify mail notifications sent


Tier 5 (Future): JSONL archive rotation

Periodic rotation of issues.jsonl for long-running repos.

## Design

.beads/
├── issues.jsonl           # Current (hot)
├── archive/
│   ├── issues-2025-12.jsonl.gz  # Archived (cold)
│   └── ...
└── index.jsonl            # Merged index for queries

## Commands

bd archive rotate [flags]
  --older-than N    Archive issues closed > N days
  --compress        Gzip archives
  --dry-run         Preview

bd archive list           # Show archived periods
bd archive restore <period>  # Restore from archive

## Config

bd config set archive.enabled true
bd config set archive.rotate_days 90
bd config set archive.compress true
bd config set archive.path '.beads/archive'

## Considerations

- Archives can be gitignored (local only) or committed (shared)
- Query layer must check index, hydrate from archive
- Cold storage tiering (S3/GCS) for enterprise
- Merkle proofs preserved for audit

## Priority

This is post-1.0 work. Current focus is on squash (removes ephemeral).
Archive helps with long-term history but is less critical.


Create git tag v{{version}}

Create the release tag:

```bash
git tag v{{version}}
```

Verify: `git tag | grep {{version}}`


Remove molecule.go (simple instantiation)

molecule.go uses is_template field for simple single-issue cloning. This is too simple for what molecules should be - full DAG orchestration. The use case is covered by bd mol bond with a single-issue molecule. Delete molecule.go and its commands.


Improve test coverage for internal/daemon (27.3% → 60%)

The daemon package has only 27.3% test coverage. The daemon is critical for background operations and reliability.

Key areas needing tests:
- Daemon autostart logic
- Socket handling
- PID file management
- Health checks

Current coverage: 27.3%
Target coverage: 60%


Split large cmd/bd files into logical modules for maintainability.

## Current State
| File | Lines | Status |
|------|-------|--------|
| sync.go | 2203 | Too large |
| init.go | 1742 | Too large |
| show.go | 1419 | Too large |
| compact.go | 1190 | Borderline |

## Proposed Splits

### 1. sync.go (2203 lines) → 4 files

**sync.go** (~500 lines) - Main command and coordination
```go
var syncCmd = &cobra.Command{...}
func init() {...}
func doSync(...) {...}
```

**sync_export.go** (~400 lines) - Export operations
```go
func exportToJSONL(...)
func markDirtyAndScheduleFlush(...)
func flushPendingChanges(...)
```

**sync_import.go** (~500 lines) - Import operations
```go
func importFromJSONL(...)
func handleImportConflicts(...)
func mergeImportedIssues(...)
```

**sync_branch.go** (~400 lines) - Git branch operations
```go
func commitToSyncBranch(...)
func pullFromSyncBranch(...)
func handleSyncBranchDivergence(...)
```

### 2. init.go (1742 lines) → 3 files

**init.go** (~400 lines) - Main init command
```go
var initCmd = &cobra.Command{...}
func runInit(...)
func determinePrefix(...)
```

**init_wizard.go** (~500 lines) - Interactive setup
```go
func runContributorWizard(...)
func runTeamWizard(...)
func promptForConfig(...)
```

**init_hooks.go** (~400 lines) - Git hooks setup
```go
func installGitHooks(...)
func configureAutosync(...)
func setupMergeDriver(...)
```

### 3. show.go (1419 lines) → 3 files

**show.go** (~400 lines) - Main show command
```go
var showCmd = &cobra.Command{...}
func showIssue(...)
func showMultipleIssues(...)
```

**show_format.go** (~400 lines) - Output formatting
```go
func formatIssueText(...)
func formatIssueMarkdown(...)
func formatDependencyTree(...)
```

**show_threads.go** (~300 lines) - Thread/conversation display
```go
func showThread(...)
func formatThreadMessages(...)
```

### 4. compact.go (1190 lines) → 2 files

**compact.go** (~600 lines) - Main compact command
```go
var compactCmd = &cobra.Command{...}
func runCompact(...)
```

**compact_tiers.go** (~400 lines) - Tier-specific logic
```go
func compactTier1(...)
func compactTier2(...)
func squashWisps(...)
```

## Implementation Steps

1. **Start with sync.go** (largest file)
2. **Create new files** with same package declaration
3. **Move functions** maintaining related code together
4. **Update any file-local variables** that need to be shared
5. **Run tests** after each split:
   ```bash
   go test -short ./cmd/bd/...
   ```

## File Organization After Split
```
cmd/bd/
├── sync.go           (~500 lines)
├── sync_export.go    (~400 lines)
├── sync_import.go    (~500 lines)
├── sync_branch.go    (~400 lines)
├── init.go           (~400 lines)
├── init_wizard.go    (~500 lines)
├── init_hooks.go     (~400 lines)
├── show.go           (~400 lines)
├── show_format.go    (~400 lines)
├── show_threads.go   (~300 lines)
├── compact.go        (~600 lines)
└── compact_tiers.go  (~400 lines)
```

## Success Criteria
- No file > 600 lines
- All tests pass
- Related code stays together
- Clear file naming indicates purpose


Update local installation

Run install script or brew upgrade to get new version locally: curl -fsSL .../install.sh | bash


Upgrade local Homebrew installation

Upgrade bd via Homebrew:

```bash
brew update
brew upgrade bd
/opt/homebrew/bin/bd version  # Verify shows {{version}}
```

**Note**: If `brew upgrade` fails with CLT (Command Line Tools) errors on bleeding-edge macOS versions (e.g., Tahoe 26.x):

```bash
# Reinstall CLT
sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install
# Wait for GUI installer to complete, then retry brew upgrade
```

Alternative: Skip Homebrew and use go install:
```bash
go install ./cmd/bd
~/go/bin/bd version  # Verify shows {{version}}
```


Merge: bd-by0d

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


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.


Add daemon-side delete operation tests

Follow-up epic for PR #626: Add comprehensive test coverage for delete operations at the daemon/RPC layer. PR #626 successfully added storage layer tests but identified gaps in daemon-side delete operations and RPC integration testing.

## Scope
Tests needed for:
1. deleteViaDaemon (cmd/bd/delete.go:21) - RPC client-side deletion command
2. Daemon RPC delete handler - Server-side deletion via daemon
3. createTombstone wrapper (cmd/bd/delete.go:335) - Tombstone creation wrapper
4. deleteIssue wrapper (cmd/bd/delete.go:349) - Direct deletion wrapper

## Coverage targets
- Delete via RPC daemon (both success and error paths)
- Cascade deletion through daemon
- Force deletion through daemon
- Dry-run mode validation
- Tombstone creation and verification
- Error handling and edge cases


Add tests for dependency management

Key dependency functions like mergeBidirectionalTrees, GetDependencyTree, and DetectCycles have low or no coverage. These are essential for maintaining data integrity in the dependency graph.


Add test files for internal/storage

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

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


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


Install 0.30.4 Go binary locally

Rebuild and install the Go binary:
```bash
go install ./cmd/bd
# OR
make install
```

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

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


Improve test coverage for internal/syncbranch (33.0% → 70%)

Improve test coverage for internal/syncbranch package from 27% to 70%.

## Current State
- Coverage: 27.0%
- Files: syncbranch.go, worktree.go
- Tests: syncbranch_test.go (basic tests exist)

## Functions Needing Tests

### syncbranch.go (config management)
- [x] ValidateBranchName - has tests
- [ ] Get - needs store mock tests
- [ ] GetFromYAML - needs YAML parsing tests
- [ ] IsConfigured - needs file system tests
- [ ] IsConfiguredWithDB - needs DB path tests
- [ ] Set - needs store mock tests
- [ ] Unset - needs store mock tests

### worktree.go (git operations) - PRIORITY
- [ ] CommitToSyncBranch - needs git repo fixture tests
- [ ] PullFromSyncBranch - needs merge scenario tests
- [ ] CheckDivergence - needs ahead/behind tests
- [ ] ResetToRemote - needs reset scenario tests
- [ ] performContentMerge - needs 3-way merge tests
- [ ] extractJSONLFromCommit - needs git show tests
- [ ] hasChangesInWorktree - needs dirty state tests
- [ ] commitInWorktree - needs commit scenario tests

## Implementation Guide

1. **Use testutil fixtures:**
   ```go
   import "github.com/steveyegge/beads/internal/testutil/fixtures"

   func TestCommitToSyncBranch(t *testing.T) {
       repo := fixtures.NewGitRepo(t)
       defer repo.Cleanup()
       // ... test scenarios
   }
   ```

2. **Test scenarios for worktree.go:**
   - Clean commit (no conflicts)
   - Non-fast-forward push (diverged)
   - Merge conflict resolution
   - Empty changes (nothing to commit)

3. **Mock storage for syncbranch.go:**
   ```go
   store := memory.New()
   // Set up test config
   syncbranch.Set(ctx, store, "beads-sync")
   ```

## Success Criteria
- Coverage ≥ 70%
- All public functions have at least one test
- Edge cases covered for git operations
- Tests pass with `go test -race ./internal/syncbranch`

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


Add missing updated_at index on issues table

GetStaleIssues queries filter by updated_at but there's no index on this column.

**Current query (ready.go:253-254):**
```sql
WHERE status != 'closed'
  AND datetime(updated_at) < datetime('now', '-' || ? || ' days')
```

**Problem:** Full table scan when filtering stale issues.

**Solution:** Add migration to create:
```sql
CREATE INDEX IF NOT EXISTS idx_issues_updated_at ON issues(updated_at);
```

**Note:** The datetime() function wrapper may prevent index usage. Consider also storing updated_at as INTEGER (unix timestamp) for better index efficiency, or test if SQLite can use the index despite the function.


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.


.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


Upgrade beads-mcp to {{version}}

Upgrade the MCP server via pip:

```bash
pip install --upgrade beads-mcp
pip show beads-mcp | grep Version  # Verify {{version}}
```

Note: Restart Claude Code or MCP session to use new version.


wish: a 'continue' or similar cmd/flag which means alter last issue

so many time I create an issue and then have another thought: 'oh, before I did X and it crashed there was ZZZ happening' or 'actually that is P4 not P2'. It would be nice if when `bd {cmd}` is used without a {title} or {id} it just adds or updates the most recently touched issue.


Split monolithic sqlite.go into focused files

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

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

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

Effort: 6-8 hours


Merge: bd-49kw

branch: polecat/nux
target: main
source_issue: bd-49kw
rig: beads


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.


Add Validation type and validations field to Issue

Add Validation struct (Validator *EntityRef, Outcome string, Timestamp time.Time, Score *float32) and Validations []Validation field to Issue. Tracks who validated/approved work completion. Core to HOP proof-of-stake concept - validators stake reputation on approvals.


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

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

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

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

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

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


Implement await type handlers (gh:run, gh:pr, timer, human, mail)

Implement condition checking for each await type.

## Handlers Needed
- gh:run:<id> - Check GitHub Actions run status via gh CLI
- gh:pr:<id> - Check PR merged/closed status via gh CLI
- timer:<duration> - Simple elapsed time check
- human:<prompt> - Check for human approval (via mail?)
- mail:<pattern> - Check for mail matching pattern

## Implementation Location
This is Deacon logic, so likely in Gas Town (gt) not beads.

## Interface
```go
type AwaitHandler interface {
    Check(awaitID string) (completed bool, result string, err error)
}
```


Bump version in all files

Run ./scripts/bump-version.sh {{version}} to update 10 version files. Then run with --commit after info.go is updated.


Create git tag v0.33.2

Create the release tag:

```bash
git tag v0.33.2
```

Verify: `git tag | grep 0.33.2`


Merge: bd-phtv

branch: polecat/Pinner
target: main
source_issue: bd-phtv
rig: beads


bd wisp gc command

Add bd wisp gc command to garbage collect orphaned wisps.

## Usage
```bash
bd wisp gc                # Clean orphaned wisps
bd wisp gc --dry-run      # Show what would be cleaned
bd wisp gc --age 1h       # Custom orphan threshold (default: 1h)
```

## Orphan Detection
A wisp is orphaned if:
- process_id field exists AND process is dead
- OR updated_at older than threshold AND not complete
- AND molecule status is not complete/abandoned

## Behavior
- Delete orphaned wisps (no digest created)
- Report count of cleaned wisps
- --dry-run shows candidates without deleting

## Implementation
- Add 'gc' subcommand to wisp group
- Process detection via os.FindProcess or /proc
- Configurable age threshold


Merge: bd-ao0s

branch: polecat/rictus
target: main
source_issue: bd-ao0s
rig: beads


Update go install bd to 0.33.2

Rebuild and install bd to ~/go/bin:

```bash
go install ./cmd/bd
~/go/bin/bd version  # Verify shows 0.33.2
```

Note: If ~/go/bin is in PATH before /opt/homebrew/bin, this is the version that runs by default.


Update cmd/bd/version.go to 0.30.4

Update the Version constant in cmd/bd/version.go:
```go
Version = "0.30.4"
```

```verify
grep -q 'Version = "0.30.4"' cmd/bd/version.go
```


Test Molecule

A test molecule


Thread Test 2

Testing direct mode


Verify release artifacts

Check GitHub releases page - binaries for darwin/linux/windows should be available


Code review findings: mol squash deletion bypasses tombstones


Merge: bd-d28c

branch: polecat/capable
target: main
source_issue: bd-d28c
rig: beads


Tier 2: Basic bd mol squash command

Add bd mol squash command for basic molecule execution compression.

## Command

bd mol squash <molecule-id> [flags]
  --dry-run         Preview what would be squashed
  --keep-children   Don't delete ephemeral children after squash
  --json            JSON output

## Implementation

1. Find all ephemeral children of molecule (parent-child deps)
2. Concatenate child descriptions/notes into digest
3. Create digest issue in main repo with:
   - Title: 'Molecule Execution Summary: <original-title>'
   - digest_of: [list of squashed child IDs]
   - ephemeral: false (digest is permanent)
4. Delete ephemeral children (unless --keep-children)
5. Link digest to parent work item

## Schema Changes

Add to Issue struct:
- SquashedAt *time.Time
- SquashDigest string (ID of digest)
- DigestOf []string (IDs of squashed children)

## Acceptance Criteria

- bd mol squash <id> creates digest, removes children
- --dry-run shows preview
- Digest has proper metadata linking


Code Review: PR #481 - Context Engineering Optimizations

Comprehensive code review of the merged context engineering PR (PR #481) that reduces MCP context usage by 80-90%.

## Summary
The PR successfully implements lazy tool schema loading and minimal issue models to reduce context window usage. Overall implementation is solid and well-tested.

## Positive Findings
✅ Well-designed models (IssueMinimal, CompactedResult)
✅ Comprehensive test coverage (28 tests, all passing)
✅ Clear documentation and comments
✅ Backward compatibility preserved (show() still returns full Issue)
✅ Sensible defaults (COMPACTION_THRESHOLD=20, PREVIEW_COUNT=5)
✅ Tool catalog complete with all 15 tools documented

## Issues Identified
See linked issues for specific followup tasks.

## Context Engineering Architecture
- discover_tools(): List tool names only (~500 bytes vs ~15KB)
- get_tool_info(name): Get specific tool details on-demand
- IssueMinimal: Lightweight model for list views (~80 bytes vs ~400 bytes)
- CompactedResult: Auto-compacts results with >20 issues
- _to_minimal(): Conversion function (efficient, no N+1 issues)


Phase 1.3: Basic TOON read/write operations

Add basic TOON read/write operations to bdt executable. Implement create, list, and show commands that use the internal/toon package for encoding/decoding to TOON format.

## Subtasks
1. Implement bdt create command - Create issues and serialize to TOON format
2. Implement bdt list command - Read issues.toon and display all issues
3. Implement bdt show command - Display single issue by ID
4. Add file I/O operations for issues.toon
5. Integrate internal/toon package (EncodeTOON, DecodeJSON)
6. Write tests for create, list, show operations

## Files to Create/Modify
- cmd/bdt/create.go - Create command
- cmd/bdt/list.go - List command
- cmd/bdt/show.go - Show command
- cmd/bdt/storage.go - File I/O helper

## Success Criteria
- bdt create "Issue title" creates and saves to issues.toon
- bdt list displays all issues in human-readable format
- bdt list --json shows JSON output
- bdt show <id> displays single issue
- Issues round-trip correctly: create → list → show
- All tests passing with >80% coverage


Update CHANGELOG.md with release notes

Add meaningful release notes to CHANGELOG.md describing what changed in 0.30.7


bd delete --from-file should add deleted issues to deletions manifest

When using bd delete --from-file to bulk delete issues, the deleted issue IDs are not being added to the deletions.jsonl manifest.

This causes those issues to be resurrected during bd sync when git history scanning finds them in old commits.

Expected: All deleted issues should be added to deletions.jsonl so they wont be reimported from git history.

Workaround: Manually add deletion records to deletions.jsonl.


bd create -f crashes with nil pointer dereference

GitHub issue #674. The markdown import feature crashes at markdown.go:338 because global variables (store, ctx, actor) aren't initialized when createIssuesFromMarkdown is called. The function uses globals set by cobra command framework but is being called before they're ready. Need to either initialize globals at start of function or pass them as parameters.


GH#444: Fix inconsistent status naming in_progress vs in-progress

Documentation uses in-progress (hyphen) but code expects in_progress (underscore). Update all docs to use canonical in_progress. See GitHub issue #444.