Standardize error handling: replace direct fmt.Fprintf+os.Exit with FatalError

Standardize error handling in cmd/bd/ using FatalError pattern.

## Current State
~200+ instances of direct `fmt.Fprintf(os.Stderr, ...) + os.Exit(1)` pattern scattered across cmd/bd/*.go files.

## Target Pattern

Use existing FatalError helper (or create if missing):

```go
// In cmd/bd/helpers.go or similar
func FatalError(format string, args ...interface{}) {
    fmt.Fprintf(os.Stderr, "Error: "+format+"\n", args...)
    os.Exit(1)
}

func FatalErrorf(err error, context string) {
    fmt.Fprintf(os.Stderr, "Error: %s: %v\n", context, err)
    os.Exit(1)
}
```

## Transformation

```go
// Before
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)

// After
FatalError("%v", err)

// Before
fmt.Fprintf(os.Stderr, "Error: invalid --since duration: %v\n", err)
os.Exit(1)

// After
FatalError("invalid --since duration: %v", err)
```

## Files to Update (by occurrence count)
Run: `grep -c "os.Exit(1)" cmd/bd/*.go | sort -t: -k2 -rn | head -20`

Priority files (highest occurrence):
- close.go
- show.go
- sync.go
- init.go
- list.go
- create.go
- update.go

## Implementation Steps

1. **Check if FatalError exists** in cmd/bd/helpers.go or create it
2. **Create migration script** or use sed:
   ```bash
   # Find patterns to replace
   grep -rn "fmt.Fprintf(os.Stderr.*Error.*\n.*os.Exit(1)" cmd/bd/
   ```
3. **Replace systematically** file by file
4. **Run tests** after each file to verify behavior unchanged
5. **Run linter** to catch any missed patterns

## Verification
```bash
# Count remaining direct exits (should be near zero)
grep -c "os.Exit(1)" cmd/bd/*.go | awk -F: "{sum+=\$2} END {print sum}"

# Run tests
go test -short ./cmd/bd/...
```

## Success Criteria
- All error exits use FatalError/FatalErrorf
- Consistent "Error: " prefix on all error messages
- Tests pass
- No behavior changes (exit codes remain 1)

## Progress (Dec 2025)

Standardized error handling in 3 major files:
- compact.go: All 48 os.Exit(1) calls converted to FatalError
- sync.go: All error patterns converted (kept 1 valid summary exit)
- migrate.go: 4 patterns converted

## Remaining Work
~326 fmt.Fprintf(os.Stderr, "Error:") patterns remain across ~30 files.
High-count files remaining: show.go (16), dep.go (14), gate.go (28), init.go (11).

## Verification
- Build compiles successfully
- Tests pass


Consolidate git context into single cached struct

Code review finding from bd-7di fix.

Currently there are 3 separate caches:
- isWorktreeOnce/isWorktreeResult
- mainRepoRootOnce/mainRepoRootResult/mainRepoRootErr
- repoRootOnce/repoRootResult

These have redundant git calls - e.g. both isWorktreeUncached() and getMainRepoRootUncached() call getGitDirNoError("--git-common-dir").

Could consolidate into a single cached struct:
```go
type gitContext struct {
    gitDir     string
    commonDir  string
    repoRoot   string
    isWorktree bool
}
```

This would reduce git calls further and simplify ResetCaches().

File: internal/git/gitdir.go


Default bd list to non-closed issues

Change bd list default from showing all issues to showing only actionable issues (open, in_progress, blocked, deferred). This addresses GH#788 where agents get context blown out by closed issues.

Current: bd list shows everything including closed
Proposed: bd list shows non-closed by default

This is the core fix - other enhancements depend on this change.

## Implementation Plan

### Code Changes (cmd/bd/list.go)

1. **Add new filter logic in Run function** (~line 393-399):
```go
// Default to non-closed if no status filter specified
if status == "" && \!cmd.Flags().Changed("all") {
    // Show actionable issues by default (GH#788)
    filter.ExcludeStatus = []types.Status{"closed"}
}
```

2. **Need to add ExcludeStatus to IssueFilter** (internal/types/types.go):
```go
type IssueFilter struct {
    // ... existing fields ...
    ExcludeStatus []Status // Exclude these statuses
}
```

3. **Update SearchIssues query** (internal/storage/sqlite/issues.go):
```go
if len(filter.ExcludeStatus) > 0 {
    placeholders := make([]string, len(filter.ExcludeStatus))
    for i, s := range filter.ExcludeStatus {
        placeholders[i] = "?"
        args = append(args, string(s))
    }
    conditions = append(conditions,
        fmt.Sprintf("status NOT IN (%s)", strings.Join(placeholders, ",")))
}
```

### Testing
- Verify `bd list` shows open, in_progress, blocked, deferred (not closed)
- Verify `bd list --status=closed` still works
- Verify `bd list --all` shows everything (requires bd-34q1)

### Backwards Compatibility
- Scripts using `bd list | grep` may get fewer results
- Document in changelog: "bd list now defaults to non-closed issues"


Test actor direct


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.


bd mol search: Find formulas using GitHub API

Search for formulas in the Mol Mall using GitHub's search API.

## Usage
```bash
bd mol search "code review"           # Free text search
bd mol search --topic molecule         # By GitHub topic
bd mol search --org anthropics         # By organization
bd mol search --stars ">10"            # By popularity
```

## Implementation
1. Use GitHub Search API: `/search/repositories`
2. Filter by topic:mol-formula or naming convention
3. Parse results, show name/description/stars/version
4. Support pagination for large result sets

## Output
```
NAME                              STARS  DESCRIPTION
github.com/anthropics/mol-code-review  42  AI-assisted code review workflow
github.com/gastown/mol-polecat-work    12  Standard polecat work lifecycle
```

## Discovery Convention
Repos should have:
- Topic: `mol-formula` or `beads-molecule`
- Name starting with `mol-`
- formula.json in root


Merge: bd-rgyd

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


Move relate/unrelate under dep command

Make relate and unrelate subcommands of dep (dep relate, dep unrelate). They're dependency operations and belong there.


Graph Link: replies_to for conversation threading

Implement replies_to link type for message threading.

New command:
- bd mail reply <id> -m 'Response' creates a message with replies_to set

Query support:
- bd show <id> --thread shows full conversation thread
- Thread traversal in storage layer

Storage:
- replies_to column in issues table
- Index for efficient thread queries

This enables Reddit-style nested threads where messages reply to other messages.


Commit and push release

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


Ephemeral patrol molecules leaking into beads

## Problem

Ephemeral patrol orchestration molecules (e.g., mol-deacon-patrol, wisp step issues) keep appearing in `bd ready` output in gastown beads.

These are ephemeral/wisp issues that should:
1. Never be synced to the beads-sync branch
2. Live only in .beads-wisp/ (ephemeral storage)
3. Be squashed to digests, not persisted as regular beads

## Examples found

- gt-wisp-6ue: mol-deacon-patrol
- gt-pacdm: mol-deacon-patrol
- gt-wisp-mpm: Check own context limit
- gt-wisp-lkc: Clean dead sessions

## Investigation needed

1. Where are these being created? (gt mol bond? manual bd create?)
2. Why are they using the gt- prefix instead of staying ephemeral?
3. Is the wisp storage (.beads-wisp/) being used correctly?
4. Is bd sync accidentally picking up ephemeral issues?

## Expected behavior

Patrol molecules and their steps should be ephemeral and never appear in `bd ready` or `bd list`.


Verify release artifacts

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


Investigate incremental blocked_issues_cache updates at scale

Current blocked_issues_cache strategy does full DELETE + INSERT on every dependency/status change.

**Problem at scale:**
- 10K issues: ~50ms rebuild (acceptable)
- 100K issues: ~500ms rebuild (noticeable)
- 1M issues: multi-second rebuilds (problematic)

**Current implementation (blocked_cache.go:104-154):**
- DELETE FROM blocked_issues_cache
- INSERT with recursive CTE

**Potential optimizations:**
1. **Incremental updates:** Only add/remove affected issue IDs instead of full rebuild
2. **Dirty tracking:** Skip rebuild if cache is already valid
3. **Async rebuild:** Rebuild in background, serve stale cache briefly
4. **Partial invalidation:** Only invalidate affected subtree

**Decision needed:** Is this premature optimization? Current target is <100K issues.

**Benchmark:** Add benchmark for cache rebuild at 100K scale to measure actual impact.


Code Review Round 2: Beads Refactoring

Second pass code review of Beads codebase. Found 10 HIGH/MEDIUM priority issues.

**HIGH (P2):**
- bd-6dnt: Duplicated IssueDetails struct (4 definitions)
- bd-vqh9: init.go is 1928 lines
- bd-it19: show.go is 1592 lines

**MEDIUM (P3):**
- bd-qobn: 20+ global variables in main.go
- bd-0e02: Inconsistent error handling patterns
- bd-g6m5: Mixed daemon/direct mode logic
- bd-qe7j: Incomplete TODOs in formula/types.go
- bd-4sxh: Issue struct ~100 fields
- bd-yuxq: 262 uses of interface{}
- bd-jbqx: Repeated validation patterns


Merge: bd-g4b4

branch: polecat/Hooker
target: main
source_issue: bd-g4b4
rig: beads


Merge: bd-qioh

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


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 show --short flag for compact output

## Request
Add a `--short` flag to `bd show` for compact, one-line output per issue.

## Use Case
When checking multiple issues quickly:
```bash
bd show bd-nkqx --short && bd show bd-oxgi --short
```

Currently fails with "unknown flag: --short"

## Expected Output
Something like:
```
bd-nkqx [closed] P1 task: Validate swarm schema design
bd-oxgi [closed] P1 task: Add mol_type schema field to beads
```

## Format Suggestion
`<id> [<status>] <priority> <type>: <title>`

Optional: include blocker count if any, e.g. `(blocked by 2)`


Code smell: init.go is 1928 lines - needs splitting

cmd/bd/init.go is 1928 lines covering multiple init modes, contributor/team setup, hooks setup, and stealth mode.

**Problem:**
- File is too large to understand and maintain
- Multiple responsibilities within single file
- Harder to test individual components

**Acceptance Criteria:**
- [ ] Split into logical files (init_setup.go, init_hooks.go, init_stealth.go, etc.)
- [ ] Each file under 500 lines
- [ ] No functionality changes
- [ ] Tests pass


mol-beads-release

Release checklist for beads version 0.99.0.

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

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

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

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

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

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

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

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

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

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

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

```bash
./scripts/bump-version.sh 0.99.0
```

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

Needs: update-changelog

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

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

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

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

```bash
git add -A
git commit -m "chore: bump version to v0.99.0"
```

Needs: run-tests

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

```bash
git push origin main
git tag v0.99.0
git push origin v0.99.0
```

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

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

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

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

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

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

```bash
# Check GitHub release
gh release view v0.99.0

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

# Check npm
npm view @beads/bd version

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

Needs: wait-for-ci

## Step: update-local
Update local installations.

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

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

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

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

Verify: `bd --version` shows 0.99.0
Needs: verify-release

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

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

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

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

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


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.


bd mol bond --pour should set Wisp=false

In mol_bond.go bondProtoMol(), opts.Wisp is hardcoded to true (line 392). This ignores the --pour flag. When user specifies --pour to make an issue persistent, the Wisp field should be false so the issue is not marked for bulk deletion.

Current behavior:
- --pour flag correctly selects regular storage (not wisp storage)
- But opts.Wisp=true means spawned issues are still marked for cleanup when closed

Expected behavior:
- --pour should set Wisp=false so persistent issues are not auto-cleaned

Comparison with mol_spawn.go (line 204):
  wisp := !pour  // Correctly respects --pour flag
  result, err := spawnMolecule(ctx, store, subgraph, vars, assignee, actor, wisp)

Fix: Pass pour flag to bondProtoMol and set opts.Wisp = !pour


Widespread double JSON encoding bug in daemon mode RPC calls

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

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

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

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

All commands now tested and working correctly with daemon mode.


Merge: bd-u2sc.4

branch: polecat/Logger
target: main
source_issue: bd-u2sc.4
rig: beads


Code smell: Issue struct is a God Object (50+ fields)

attached_args: Refactor Issue struct God Object

The Issue struct in internal/types/types.go has 50+ fields covering many different concerns:

- Basic issue tracking (ID, Title, Description, Status, Priority)
- Messaging/communication (Sender, Ephemeral, etc.)
- Agent identity (HookBead, RoleBead, AgentState, RoleType, Rig)
- Gate/async coordination (AwaitType, AwaitID, Timeout, Waiters)
- Source tracing (SourceFormula, SourceLocation)
- HOP validation/entities (Creator, Validations)
- Compaction (CompactionLevel, CompactedAt, OriginalSize)
- Tombstones (DeletedAt, DeletedBy, DeleteReason)

Consider:
1. Extracting field groups into embedded structs (e.g., AgentFields, GateFields)
2. Using composition pattern to make the struct more modular
3. At minimum, grouping related fields together with section comments

Location: internal/types/types.go:12-82


refinery Handoff


Update documentation for CLI consolidation

## Task
Update all documentation to reflect CLI consolidation changes.

## Files requiring updates

### Primary docs
- docs/CLI_REFERENCE.md - Major updates
  - Add admin section (cleanup, compact, reset)
  - Update migrate section (subcommands)
  - Update formula section (add cook)
  - Remove/update standalone command references

### Secondary docs
- docs/DELETIONS.md - migrate-tombstones → migrate tombstones, compact → admin compact
- docs/FAQ.md - compact → admin compact
- docs/QUICKSTART.md - compact, cleanup → admin compact, admin cleanup
- docs/TROUBLESHOOTING.md - compact → admin compact
- docs/CONFIG.md - compact → admin compact
- docs/INSTALLING.md - quickstart reference
- docs/EXTENDING.md - quickstart reference

### Skills docs
- skills/beads/SKILL.md - compact, quickstart references
- skills/beads/references/CLI_REFERENCE.md - mirror main CLI_REFERENCE.md

### Other docs
- commands/quickstart.md - update or remove
- commands/compact.md - update to admin compact
- examples/compaction/README.md - update examples
- README.md - quickstart reference
- CHANGELOG.md - add consolidation changes section

## Implementation notes
- Keep backwards compatibility notices where aliases exist
- Update command examples to use new paths
- Consider adding deprecation warnings section


rpc: Rich mutation events not emitted

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

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

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

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

Discovered during code review of bd-xo1o implementation.


Remove Storage Cache from Server Struct

Eliminate cache fields and use s.storage directly


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


Merge: bd-kzda

branch: polecat/toast
target: main
source_issue: bd-kzda
rig: beads


Remove dead test code in info_test.go

Code health review found cmd/bd/info_test.go has two tests permanently skipped:

- TestInfoCommand
- TestInfoCommandNoDaemon

Both skip with: 'Manual test - bd info command is working, see manual testing'

These are essentially dead code. Either automate them or remove them entirely.


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


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


bd mol run: Support proto lookup by title

`bd mol run` only accepts proto ID (e.g., gt-lwuu), not proto title (e.g., mol-polecat-work).

## Current Behavior
```bash
bd mol run mol-polecat-work --var issue=gt-xxx
# Error: no issue found matching "mol-polecat-work"

bd mol run gt-lwuu --var issue=gt-xxx
# Works
```

## Desired Behavior
Both should work - lookup by ID or by title.

## Why This Matters
- Proto titles are human-readable and memorable
- Proto IDs are opaque (gt-lwuu vs mol-polecat-work)
- Hardcoding IDs in code is fragile (IDs change across databases)
- `bd mol catalog` shows both: `gt-lwuu: mol-polecat-work`

## Workaround
Currently using hardcoded proto ID in gt spawn, which is brittle.


Test hooked styling


Merge: bd-ao0s

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


Thread Test 2

Testing direct mode


Test epic for duplicate swarm


Update beads_mcp/__init__.py to 0.30.4

Update __version__ in integrations/beads-mcp/src/beads_mcp/__init__.py:
```python
__version__ = "0.30.4"
```

```verify
grep -q '__version__ = "0.30.4"' integrations/beads-mcp/src/beads_mcp/__init__.py
```


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


Update info.go versionChanges

Add entry to versionChanges in cmd/bd/info.go.

This powers `bd info --whats-new` for agents.

```go
"0.39.0": {
    "summary": "Brief description",
    "changes": []string{
        "Key change 1",
        "Key change 2",
    },
},
```

Focus on workflow-impacting changes agents need to know.


Update molecular chemistry docs with discoveries

Document recent discoveries about molecule/epic relationship and lifecycle:

**Layer Cake (corrected order):**
```
Formulas (YAML compile-time macros)
    ↓
Protos (template label, read-only)
    ↓
Molecules (pour, bond, squash, burn)
    ↓
Epics (parent-child, dependencies) ← DATA PLANE
    ↓
Issues (JSONL, git-backed) ← STORAGE
```

**Key insight:** Molecules work without protos - you can create ad-hoc molecules.
Protos are templates FOR molecules, not the other way around.

**New concepts to document:**
1. Orphan vs Stale matrix (blocking × assigned dimensions)
2. Graph pressure as staleness indicator (not time)
3. Parallelism via absence of depends_on (default parallel, opt-in sequential)
4. progress.Completed/Total are computed, not stored
5. WaitsFor: all-children for dynamic fanout gates

**Common pitfalls for agents:**
- Thinking phases/steps imply sequence (NO - deps do)
- Confusing protos with molecules
- Temporal language inverting dependencies
- Forgetting to squash/burn wisps

**Consider splitting docs:**
- molecules.md → conceptual overview
- formulas.md → YAML syntax reference
- lifecycle.md → pour/squash/burn/bond operations
- patterns.md → Christmas Ornament, factory assembly, etc.


bd cook: needs field not converted to step dependencies

When cooking a formula with `needs` fields on steps, the dependencies are not created between sibling steps.

## Expected

Formula:
```yaml
steps:
  - id: inbox-check
    title: Check inbox
  - id: trigger-spawns
    title: Nudge polecats
    needs: [inbox-check]
```

Should create:
- mol-foo.inbox-check (no deps)
- mol-foo.trigger-spawns → depends on → mol-foo.inbox-check

## Actual

Both steps only depend on the parent proto:
- mol-foo.inbox-check → depends on → mol-foo
- mol-foo.trigger-spawns → depends on → mol-foo

The `needs` field is ignored.

## Impact

This breaks the step execution order. Steps that should wait for predecessors will run in parallel or out of order.

## Reproduction

```bash
bd cook mol-deacon-patrol.formula.yaml
bd show mol-deacon-patrol.trigger-pending-spawns
# Shows: Depends on mol-deacon-patrol (parent only)
# Should show: Depends on mol-deacon-patrol.inbox-check
```


Update CHANGELOG header

Change [Unreleased] to [{{version}}] - {{date}} and add new empty [Unreleased] section above it.


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)


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.


Update CHANGELOG.md for 0.33.2

In CHANGELOG.md:

1. Change `## [Unreleased]` section header to `## [0.33.2] - 2025-12-21`
2. Add new empty `## [Unreleased]` section above it
3. Review and clean up the changes list

Format:
```markdown
## [Unreleased]

## [0.33.2] - 2025-12-21

### Added
- ...

### Changed
- ...

### Fixed
- ...
```


Test event


Update Metrics and Health Endpoints

Remove cache-related metrics from health/metrics endpoints


Fix JSON errors in label.go (label commands)

Replace ~19 direct stderr writes with FatalErrorRespectJSON() in label.go.

Commands affected:
- labelAddCmd
- labelRemoveCmd
- labelListCmd
- labelListAllCmd


Add bd template list command

Add a convenience command to list available templates:

bd template list

This is equivalent to 'bd list --label=template' but more discoverable.
Could also show variable placeholders found in each template.


bd swarm validate command

Add bd swarm validate to check epic structure for swarming.

## Context
Before running a swarm, we need to validate the epic structure:
- Are dependencies correct (not inverted)?
- Are there orphaned issues?
- Is the DAG well-formed?

The Ready Front model (not phases!) means dependencies define execution order.
Common mistake: 'Phase 1 before Phase 2' → inverted deps.
Correct thinking: 'Phase 2 needs Phase 1' → bd dep add phase2 phase1

## Implementation
1. New command: bd swarm validate <epic-id>
2. Walk the dependency DAG from epic children
3. Detect issues:
   - Root issues with no dependents (orphans?)
   - Leaf issues depending on nothing (missing deps?)
   - Cycles (impossible)
   - Disconnected subgraphs
4. Report:
   - Ready fronts (waves of parallel work)
   - Estimated worker-sessions
   - Max parallelism
   - Warnings for potential issues

## Reference
~/gt/docs/swarm-architecture.md - 'The Ready Front Model' section

## Acceptance
- bd swarm validate <epic> runs
- Reports ready fronts accurately
- Warns about structural issues
- Returns success/failure exit code


Default to YES for git hooks and merge driver installation

Currently bd init prompts user to install git hooks and merge driver, but setup is incomplete if user declines. Change to install by default unless --skip-hooks or --skip-merge-driver flags are passed. Better safe defaults. If installation fails, warn user and suggest bd doctor --fix.


Workaround for FastMCP outputSchema bug in Claude Code

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

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

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

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


Verify version consistency

Confirm all versions match 0.39.0.

```bash
grep 'Version = ' cmd/bd/version.go
jq -r '.version' .claude-plugin/plugin.json
jq -r '.version' npm-package/package.json
grep 'version = ' integrations/beads-mcp/pyproject.toml
```

All should show 0.39.0.


Review jordanhubbard chaos testing PR #752

FINDINGS:
- Implementation quality: HIGH
- Recommendation: MERGE WITH MODIFICATIONS
- Mods: No hard coverage threshold, chaos tests on releases only

DECISION: User agrees. Next steps:
1. Add chaos tests to release-bump formula
2. Merge PR #752

Review doc: docs/pr-752-chaos-testing-review.md


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

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

Current coverage: 23.9%
Target coverage: 50%


Restart running daemons

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


Add external_projects to config schema

Add external_projects mapping to .beads/config.yaml:

```yaml
external_projects:
  beads: ../beads
  gastown: ../gastown
  other: /absolute/path/to/project
```

Used by bd ready and other commands to resolve external: references.

Part of cross-project dependency system.
See: gastown/docs/cross-project-deps.md


Verify npm package

Confirm npm package published.

```bash
npm show @beads/bd version
```

Should show 0.41.0.

Also check: https://www.npmjs.com/package/@beads/bd


Add pre-migration orphan cleanup to avoid chicken-and-egg fix failure

## Problem

When the database has orphaned foreign key references (dependencies or labels pointing to non-existent issues), the migration invariant check fails, preventing the database from opening. This creates a chicken-and-egg problem:

1. `bd doctor --fix` tries to open the database
2. Opening triggers migrations with invariant checks
3. Invariant check fails due to orphaned refs
4. Fix never runs because database won't open

## Root Cause

Aggressive tombstone deletion (e.g., deleting ~1000 old tombstones) left behind orphaned:
- 218+ dependencies where issue_id not in issues
- 222+ dependencies where depends_on_id not in issues
- 30+ labels where issue_id not in issues

## Current Workaround

Direct SQL to clean up before bd can run:
```sql
DELETE FROM dependencies WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = issue_id);
DELETE FROM dependencies WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = depends_on_id) AND depends_on_id NOT LIKE 'external:%';
DELETE FROM labels WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = issue_id);
```

## Proposed Fix

Add a pre-migration cleanup step that runs BEFORE invariant checks:
1. Option A: Run orphan cleanup as part of migrations (before invariant check)
2. Option B: Add `bd doctor --force-fix` that bypasses invariant checks
3. Option C: Have invariant check auto-clean orphans instead of failing

Option A is cleanest - orphan cleanup should be part of normal migration.


Code Review: Beads Refactoring Sprint

Epic for code smell fixes identified during Beads code review.

## Scope
11 issues covering:
- P2: Duplicated code in cook.go (2 issues)
- P3: Long functions and God Objects (5 issues)
- P4: Technical debt and cleanup (4 issues)

## Approach
Swarm with polecats, each taking 1-2 issues. All changes should:
1. Pass existing tests
2. Not change external behavior
3. Improve code maintainability

## Success Criteria
All 11 child issues closed with passing CI.


Code cleanup: Search for leftover debugging code

Scan the beads codebase for debugging artifacts that should be removed before release:

## Patterns to search for:
- `fmt.Printf` debugging statements (vs proper logging)
- `// DEBUG`, `// TODO: remove`, `// TEMP` comments
- `log.Printf` that should use structured logging
- Hardcoded test values or paths
- `panic()` calls that should be proper error handling
- Commented-out code blocks
- `time.Sleep` debugging delays

## Files to check:
- cmd/bd/*.go
- internal/**/*.go

## Acceptance:
- Remove or convert all debugging artifacts
- Ensure no test pollution in production code


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


Dead code: fetchAndRebaseInWorktree() marked DEPRECATED but still exists

attached_args: Remove dead code fetchAndRebaseInWorktree

The function fetchAndRebaseInWorktree() in internal/syncbranch/worktree.go (lines 811-830) is marked as DEPRECATED with a comment:

```go
// fetchAndRebaseInWorktree is DEPRECATED - kept for reference only.
// Use contentMergeRecovery instead to avoid tombstone resurrection.
```

Since contentMergeRecovery() is the replacement and is being used, this dead code should be removed to reduce maintenance burden.

Location: internal/syncbranch/worktree.go:811-830


Implement core reset package (internal/reset)

Create the core reset logic in internal/reset/ package.

## Responsibilities
- ResetOptions struct with all flag options
- CountImpact() - count issues/tombstones that will be deleted
- ValidateState() - check .beads/ exists, check git dirty state
- ExecuteReset() - main reset logic (without CLI concerns)
- Integrate with daemon killall

## Interface Design
```go
type ResetOptions struct {
    Hard      bool   // Include git operations (git rm, commit)
    Backup    bool   // Create backup before reset
    DryRun    bool   // Preview only, don't execute
    SkipInit  bool   // Don't re-initialize after reset
}

type ResetResult struct {
    IssuesDeleted     int
    TombstonesDeleted int
    BackupPath        string  // if backup was created
    DaemonsKilled     int
}

type ImpactSummary struct {
    IssueCount      int
    OpenCount       int
    ClosedCount     int
    TombstoneCount  int
    HasUncommitted  bool  // git dirty state
}

func Reset(opts ResetOptions) (*ResetResult, error)
func CountImpact() (*ImpactSummary, error)
func ValidateState() error
```

## IMPORTANT: CLI vs Core Separation
- `Force` (skip confirmation) is NOT in ResetOptions - that's a CLI concern
- Core always executes when called; CLI decides whether to prompt first
- Keep CLI-agnostic: no prompts, no colored output, no user interaction
- Return errors for CLI to handle with user-friendly messages
- Unit testable in isolation

## Dependencies
- Uses daemon.KillAllDaemons() from internal/daemon/
- Calls bd init logic after reset (unless SkipInit)


.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


Update info.go versionChanges

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


Test Parent Epic


Merge: bd-49kw

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


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


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.


Push release tag

Push the version tag to trigger CI release.

```bash
git push origin v0.99.0-test
```

This triggers GitHub Actions to build artifacts and publish.


Introduce slog for structured daemon logging.

## Current State
Daemon uses fmt.Fprintf for logging:
```go
fmt.Fprintf(os.Stderr, "Warning: failed to detect user role: %v\n", err)
fmt.Fprintf(logFile, "[%s] %s\n", time.Now().Format(time.RFC3339), msg)
```

This produces unstructured, hard-to-parse logs.

## Target State
Use Go 1.21+ slog for structured logging:
```go
slog.Warn("failed to detect user role", "error", err)
slog.Info("sync completed", "created", 5, "updated", 3, "duration_ms", 150)
```

## Implementation

### 1. Create logger setup (internal/daemon/logger.go)

```go
package daemon

import (
    "io"
    "log/slog"
    "os"
)

// SetupLogger configures the daemon logger.
// Returns a cleanup function to close the log file.
func SetupLogger(logPath string, jsonFormat bool, level slog.Level) (func(), error) {
    var w io.Writer = os.Stderr
    var cleanup func()

    if logPath \!= "" {
        f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
        if err \!= nil {
            return nil, err
        }
        w = io.MultiWriter(os.Stderr, f)
        cleanup = func() { f.Close() }
    }

    var handler slog.Handler
    opts := &slog.HandlerOptions{Level: level}

    if jsonFormat {
        handler = slog.NewJSONHandler(w, opts)
    } else {
        handler = slog.NewTextHandler(w, opts)
    }

    slog.SetDefault(slog.New(handler))

    return cleanup, nil
}
```

### 2. Add log level flag

In cmd/bd/daemon.go:
```go
daemonCmd.Flags().String("log-level", "info", "Log level (debug, info, warn, error)")
daemonCmd.Flags().Bool("log-json", false, "Output logs in JSON format")
```

### 3. Replace fmt.Fprintf with slog calls

**Pattern 1: Simple messages**
```go
// Before
fmt.Fprintf(os.Stderr, "Starting daemon on %s\n", socketPath)

// After
slog.Info("starting daemon", "socket", socketPath)
```

**Pattern 2: Errors**
```go
// Before
fmt.Fprintf(os.Stderr, "Error: failed to connect: %v\n", err)

// After
slog.Error("failed to connect", "error", err)
```

**Pattern 3: Debug info**
```go
// Before
if debug {
    fmt.Fprintf(os.Stderr, "Received request: %s\n", method)
}

// After
slog.Debug("received request", "method", method)
```

**Pattern 4: Structured data**
```go
// Before
fmt.Fprintf(logFile, "Import: %d created, %d updated\n", created, updated)

// After
slog.Info("import completed",
    "created", created,
    "updated", updated,
    "unchanged", unchanged,
    "duration_ms", duration.Milliseconds())
```

### 4. Files to Update

| File | Changes |
|------|---------|
| cmd/bd/daemon.go | Add log flags, call SetupLogger |
| internal/daemon/server.go | Replace fmt with slog |
| internal/daemon/rpc_handler.go | Replace fmt with slog |
| internal/daemon/sync.go | Replace fmt with slog |
| internal/daemon/autoflush.go | Replace fmt with slog |
| internal/daemon/logger.go | New file |

### 5. Log output examples

**Text format (default):**
```
time=2025-12-23T12:30:00Z level=INFO msg="daemon started" socket=/tmp/bd.sock pid=12345
time=2025-12-23T12:30:01Z level=INFO msg="import completed" created=5 updated=3 duration_ms=150
time=2025-12-23T12:30:05Z level=WARN msg="sync branch diverged" local_ahead=2 remote_ahead=1
```

**JSON format (--log-json):**
```json
{"time":"2025-12-23T12:30:00Z","level":"INFO","msg":"daemon started","socket":"/tmp/bd.sock","pid":12345}
{"time":"2025-12-23T12:30:01Z","level":"INFO","msg":"import completed","created":5,"updated":3,"duration_ms":150}
```

## Migration Strategy

1. **Add logger.go** with SetupLogger
2. **Update daemon startup** to initialize slog
3. **Convert one file at a time** (start with server.go)
4. **Test after each file**
5. **Remove old logging code** once all converted

## Testing

```bash
# Start daemon with debug logging
bd daemon start --log-level debug

# Check logs
bd daemons logs . | head -20

# Test JSON output
bd daemon start --log-json --log-level debug
bd daemons logs . | jq .
```

## Success Criteria
- All daemon logging uses slog
- --log-level controls verbosity
- --log-json produces machine-parseable output
- Log entries have consistent structure
- No fmt.Fprintf to stderr in daemon code


Update cmd/bd/info.go with release notes for 0.37.0.

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

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

instantiated_from: bd-mol-xga
step: update-release-notes


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.


Update info.go versionChanges

Add 0.32.1 entry to versionChanges map in cmd/bd/info.go


Commit and push release

git add -A && git commit -m 'chore: bump version to 0.32.1' && git push


Add EntityRef type for structured entity references

Create EntityRef struct with Name, Platform, Org, ID fields. This is the foundation for HOP entity tracking. Can render as entity://hop/<platform>/<org>/<id> when needed. Add to internal/types/types.go.


Relocate thanks command to bd info --thanks

## Task
Move `bd thanks` → `bd info --thanks`

## Implementation

### 1. Update info.go
- Add `--thanks` flag
- Import thanks.go display logic
- When --thanks flag set, call thanks display function

### 2. Update thanks.go
- Export the display function (rename to public)
- Remove rootCmd.AddCommand()
- Optionally keep as hidden alias

### 3. Update docs
- README.md if mentioned
- CHANGELOG.md - deprecation notice

## Files to modify
- cmd/bd/info.go
- cmd/bd/thanks.go


Update local installations.

Update local installations.

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

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

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

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

Verify: `bd --version` shows 0.37.0

instantiated_from: bd-mol-xga
step: update-local


Full event test


mergeDependencies uses union instead of 3-way merge

## Critical Bug

The `mergeDependencies` function in internal/merge/merge.go performs a UNION of left and right dependencies instead of a proper 3-way merge. This causes removed dependencies to be resurrected.

### Root Cause

```go
// Current code (lines 795-816):
func mergeDependencies(left, right []Dependency) []Dependency {
    // Just unions left + right
    // NEVER REMOVES anything
    // Doesn't even look at base!
}
```

And `mergeIssue` (line 579) doesn't pass `base`:
```go
result.Dependencies = mergeDependencies(left.Dependencies, right.Dependencies)
```

### Impact

If:
- Base has dependency D
- Left removes D (intentional)
- Right still has D (stale)

Current: D is in result (resurrection!)
Correct: Left removed it, D should NOT be in result

This breaks Gas Town's workflow and data integrity. Closed means closed.

### Fix

Change `mergeDependencies` to take `base` and do proper 3-way merge:
- If dep was in base and removed by left → exclude (left wins)
- If dep was in base and removed by right → exclude (right wins)
- If dep wasn't in base and added by either → include
- If dep was in base and both still have it → include

Key principle: **REMOVALS ARE AUTHORITATIVE**

### Files to Change

1. internal/merge/merge.go:
   - `mergeDependencies(left, right)` → `mergeDependencies(base, left, right)`
   - `mergeIssue` line 579: pass `base.Dependencies`

### Related

This also explains why `ProtectLocalExportIDs` in importer is defined but never used - the protection was never actually implemented.


Exclude pinned issues from bd blocked

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


mol-beads-release

Release checklist for beads version {{version}}.

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

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

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

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

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

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

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

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

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

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

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

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

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

Needs: update-changelog

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

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

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

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

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

Needs: run-tests

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

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

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

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

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

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

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

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

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

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

# Check npm
npm view @beads/bd version

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

Needs: wait-for-ci

## Step: update-local
Update local installations with proper codesigning (macOS).

```bash
# Build from source in mayor/rig (canonical build location)
cd ~/gt/beads/mayor/rig
git pull
go build -o bd ./cmd/bd

# Sign and install (macOS requires codesigning to avoid "Killed: 9")
# Uses fix-gt script which handles both gt and bd binaries
fix-gt

# Or manually sign if fix-gt not available:
# xattr -cr bd && codesign -f -s - bd
# cp bd ~/go/bin/bd && codesign -f -s - ~/go/bin/bd
# cp bd ~/.local/bin/bd && codesign -f -s - ~/.local/bin/bd

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

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

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

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

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

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

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

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


Kill running daemons

Stop all bd daemons before release:

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


Add bd update --parent flag to change issue parent

Allow changing an issue's parent with bd update --parent <new-parent-id>. Useful for reorganizing tasks under different epics or moving issues between hierarchies. Should update the parent-child dependency relationship.


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.


Wait for CI

Monitor GitHub Actions for release completion.

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

Expected time: 5-10 minutes

Watch for:
- Build artifacts (all platforms)
- Test suite pass
- npm publish
- PyPI publish


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.


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


Commit and push release

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


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


Priority: refactor mol.go then bd squash

Two tasks:

1. bd-cnwx - Refactor mol.go (1200+ lines, split by subcommand)
2. bd-2vh3 - Ephemeral cleanup (bd cleanup --ephemeral)

Refactor first - smaller, unblocks easier review of future mol work.

- Mayor


Code review findings: mol squash deletion bypasses tombstones


Verify npm package

Confirm npm package published.

```bash
npm show @beads/bd version
```

Should show 0.99.0-test.

Also check: https://www.npmjs.com/package/@beads/bd


Bump version in all files

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


bd sync deleted non-.beads files (skills/beads/*)

On 2025-12-23, commit 7b671662 (bd sync) inadvertently deleted 5 files from skills/beads/ that were added by PR #722 (commit e432fcc8). These files are outside .beads/ and should never be touched by bd sync.

Files deleted:
- skills/beads/README.md
- skills/beads/references/INTEGRATION_PATTERNS.md
- skills/beads/references/MOLECULES.md
- skills/beads/references/PATTERNS.md
- skills/beads/references/TROUBLESHOOTING.md

Investigation needed:
1. How did bd sync stage/commit files outside .beads/?
2. Was this a one-time accident or is there a systematic bug?
3. Add safeguards to prevent bd sync from touching non-.beads paths

Reported via GH#738.


Auto-detect identity from directory context for bd mail

Currently bd mail inbox defaults to git user name, requiring --identity flag with exact format.

## Problem
- Mail sent to `gastown/crew/max`
- Max runs `bd mail inbox` → defaults to 'Steve Yegge' (git user)
- Max must know to use `--identity 'gastown/crew/max'` with exact slashes

## Proposed Fix
Auto-detect identity from directory context when in a Gas Town workspace:
- In `/Users/stevey/gt/gastown/crew/max`, infer identity = `gastown/crew/max`
- Pattern: `<town>/<rig>/<role>/<name>` → `<rig>/<role>/<name>`

## Additional Improvements
1. Support GT_IDENTITY env var (set by gt crew at / session spawning)
2. Support identity in .beads/config.yaml
3. Normalize format: accept both slashes and dashes as equivalent

## Context
Discovered during crew-to-crew work assignment. Max couldn't see mail despite correct nudge because identity defaulted wrong.


Merge: bd-d28c

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


Remove all deletions.jsonl code except migration

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

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

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


Merge: bd-9usz

branch: polecat/slit
target: main
source_issue: bd-9usz
rig: beads


Code smell: Repeated validation patterns could be centralized

Similar validation patterns appear in multiple commands (create.go, show.go, update.go, etc.):
- Check if resource exists
- Check if resource is in valid state
- Validate user input
- Check permissions

**Problem:**
- Duplicated validation logic
- Inconsistent validation between commands
- Hard to add new validation rules consistently

**Acceptance Criteria:**
- [ ] Create internal/validation package
- [ ] Centralize common validation logic
- [ ] Use composition for validation chains
- [ ] Update commands to use shared validators
- [ ] Tests pass


Run check-versions.sh - all must pass

Run the version consistency check:
```bash
./scripts/check-versions.sh
```

All versions must match 0.30.4.

```verify
./scripts/check-versions.sh
```


Test prefix beads


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


Support --reason flag in daemon mode for reopen command

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


bd mol reset: Reset molecule to initial state for looping

Implement bd mol reset for patrol loop behavior.

## Command

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

## Behavior

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

## Use Case: Deacon Patrol Loop

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

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

## State After Reset

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

## Files

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


Merge: bd-au0.6

branch: polecat/furiosa
target: main
source_issue: bd-au0.6
rig: beads


Design auto-squash behavior for wisps

Explore the design space for automatic wisp squashing.

**Context:**
Wisps are ephemeral molecules that should be squashed (digest) or burned (no trace)
when complete. Currently this is manual. Should it be automatic?

**Questions to answer:**
1. When should auto-squash trigger?
   - On molecule completion?
   - On session end/handoff?
   - On patrol detection?

2. What's the default summary for auto-squash?
   - Generic: 'Auto-squashed on completion'
   - Step-based: List closed steps
   - AI-generated: Require agent to provide

3. Should this be configurable?
   - Per-molecule setting in formula?
   - Global config: auto_squash: true/false
   - Per-wisp flag at creation time?

4. Who decides - Beads or Gas Town?
   - Beads: Provides operators (squash, burn)
   - Gas Town: Makes policy decisions
   - Proposal: GT patrol molecules call bd mol squash

**Constraints:**
- Don't lose important context (summary matters)
- Don't create noise in digest history
- Respect agent's intent (some wisps should burn, not squash)

**Recommendation:**
Gas Town patrol molecules should have explicit squash/burn steps.
Beads provides primitives, GT makes policy decisions.
Auto-squash at Beads level is probably wrong layer.


Remove legacy autoflush code paths

## Problem

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

## Locations

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

## Current Behavior

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

## Proposed Fix

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

## Risk

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


Update info.go versionChanges

Add entry to versionChanges in cmd/bd/info.go.

This powers `bd info --whats-new` for agents.

```go
"0.99.0-test": {
    "summary": "Brief description",
    "changes": []string{
        "Key change 1",
        "Key change 2",
    },
},
```

Focus on workflow-impacting changes agents need to know.


bd init should stop running daemon to avoid stale cache

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

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

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


Verify PyPI package

Confirm PyPI package published.

```bash
pip index versions beads-mcp 2>/dev/null | head -3
```

Or check: https://pypi.org/project/beads-mcp/

Should show 0.41.0.


Review changes since last release

Understand what's being released.

```bash
git log $(git describe --tags --abbrev=0)..HEAD --oneline
```

Categorize changes:
- Features (feat:)
- Fixes (fix:)
- Breaking changes
- Documentation


Merge: obsidian-1767106243818

branch: polecat/obsidian-1767106243818
target: main
source_issue: obsidian-1767106243818
rig: beads


Document sync.branch 'always dirty' working tree behavior

## Context

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

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

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

## Documentation Needed

Update README.md sync.branch section with:

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

## Related

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


bd mol bond: Dynamic bond with variable substitution

Implement dynamic molecule bonding with runtime variable substitution.

## Command
```bash
bd mol bond <proto-id> <parent-wisp-id> --var key=value --var key2=value2
```

## Behavior
1. Parse proto molecule template
2. Substitute {{key}} placeholders with provided values
3. Create wisp children under the parent molecule
4. Child IDs follow pattern: parent-id.child-ref (e.g., patrol-x7k.arm-ace)
5. Nested children: parent-id.child-ref.step-ref (e.g., patrol-x7k.arm-ace.capture)

## Variable Substitution
- In step titles: "Inspect {{polecat_name}}" -> "Inspect ace"
- In descriptions: Full template substitution
- In Needs directives: Allow referencing parent steps

## Output
```
✓ Bonded mol-polecat-arm to patrol-x7k
  Created: patrol-x7k.arm-ace (5 steps)
```


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

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

## Filters to Add

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

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

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

## Files to Modify

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

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

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

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

## Implementation Steps

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

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

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

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


Wait for CI

Monitor GitHub Actions for release completion.

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

Expected time: 5-10 minutes

Watch for:
- Build artifacts (all platforms)
- Test suite pass
- npm publish
- PyPI publish


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.


Phase 2.4: Git merge driver optimization for TOON format

Optimize git 3-way merge for TOON line-oriented format.

## Overview
TOON is line-oriented (unlike binary formats), enabling smarter git merge strategies. Implement custom merge driver to handle TOON-specific merge patterns.

## Required Work

### 2.4.1 TOON Merge Driver
- [ ] Create .git/info/attributes entry for *.toon files
- [ ] Implement custom merge driver script/command
- [ ] Handle tabular format row merges (line-based 3-way)
- [ ] Handle YAML-style format merges
- [ ] Conflict markers for unsolvable conflicts

### 2.4.2 Merge Patterns
- [ ] Row addition: both branches add different rows → union
- [ ] Row deletion: one branch deletes, other modifies → conflict (manual review)
- [ ] Row modification: concurrent field changes → intelligent merge or conflict
- [ ] Field ordering changes: ignore (TOON format resilient to order)

### 2.4.3 Testing & Documentation
- [ ] Unit tests for merge scenarios (3-way merge logic)
- [ ] Integration tests with actual git merges
- [ ] Conflict scenario testing
- [ ] Documentation of merge strategy

## Success Criteria
- Git merge handles TOON conflicts intelligently
- Fewer manual merge conflicts than JSONL
- Round-trip preserved through merges
- All 70+ tests still passing
- Git history stays clean (minimal conflict markers)


Thread Test

Testing the thread feature


Merge: obsidian-1767083466920

branch: polecat/obsidian-1767083466920
target: main
source_issue: obsidian-1767083466920
rig: beads


bd wisp create doesn't set Wisp flag on spawned issues

When running 'bd wisp create <proto>', the spawned issues don't have Wisp: true set. They show up in 'bd ready' and get synced to JSONL.

Example: gt-lqc6m and gt-wisp-ry9 are mol-deacon-patrol issues that should be wisps but have wisp: null.

This causes patrol cycles to pollute the issue database and show up as ready work.


Gate await fields cleared by --no-daemon CLI access (not multi-repo)


Verify version consistency

Confirm all versions match 0.99.0-test.

```bash
grep 'Version = ' cmd/bd/version.go
jq -r '.version' .claude-plugin/plugin.json
jq -r '.version' npm-package/package.json
grep 'version = ' integrations/beads-mcp/pyproject.toml
```

All should show 0.99.0-test.


Beads commands operate on stale database without warning

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

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

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

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

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

## Implementation Complete

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

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

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

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

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


Implement bd mol current command

Show what molecule the agent should currently be working on. Referenced by gt-um6q, gt-lz13. Needed for molecule navigation workflow in templates.


gt handoff deadlock at handoff.go:125

When running 'gt handoff -m "message"' after successful MR submit, go panics with 'fatal error: all goroutines are asleep - deadlock\!' at handoff.go:125. The shutdown request still appears to be sent successfully but the command crashes. Stack trace shows issue is in runHandoff select statement.


Refactor duplicate JSONL-from-git parsing code

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

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

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

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


HANDOFF: Molecule bonding - spawn done, bond next

## Context

Continuing work on bd-o5xe (Molecule bonding epic).

## Completed This Session

- bd-mh4w: Renamed bond to spawn in mol.go
- bd-rnnr: Added BondRef data model to types.go

## Now Unblocked

1. bd-o91r: Polymorphic bond command [P1]
2. bd-iw4z: Compound visualization [P2]
3. bd-iq19: Distill command [P2]

## Key Files

- cmd/bd/mol.go
- internal/types/types.go

## Next Step

Start with bd-o91r. Run bd show bd-o5xe for context.


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


Auto-detect non-TTY and adjust output

Detect when stdout is not a TTY (piped, redirected, or agent context) and adjust output:

- Disable ANSI colors
- Disable pager
- Disable emoji decorations
- Use plain text format

Uses golang.org/x/term or similar for TTY detection.

Needs pager support to be meaningful (otherwise nothing to disable).

## Implementation Plan

### Dependencies
```go
import "golang.org/x/term"
// or: github.com/mattn/go-isatty
```

### Code Changes

1. **Add TTY detection helper** (internal/ui/terminal.go):
```go
func IsTerminal() bool {
    return term.IsTerminal(int(os.Stdout.Fd()))
}

func ShouldUseColor() bool {
    // Respect NO_COLOR standard
    if os.Getenv("NO_COLOR") \!= "" {
        return false
    }
    // Respect CLICOLOR=0
    if os.Getenv("CLICOLOR") == "0" {
        return false
    }
    // Force color with CLICOLOR_FORCE
    if os.Getenv("CLICOLOR_FORCE") \!= "" {
        return true
    }
    return IsTerminal()
}
```

2. **Apply in lipgloss setup** (internal/ui/styles.go):
```go
func init() {
    if \!ShouldUseColor() {
        lipgloss.SetColorProfile(termenv.Ascii)
    }
}
```

3. **Disable pager for non-TTY** (uses bd-jdz3 infrastructure):
```go
// In pager.go ToPager():
if \!IsTerminal() {
    fmt.Print(content)
    return nil
}
```

### Standards Followed
- NO_COLOR: https://no-color.org/
- CLICOLOR/CLICOLOR_FORCE: de facto BSD standard
- gh cli uses same conventions

### Testing
- `bd list | cat` outputs no ANSI codes
- `NO_COLOR=1 bd list` outputs no colors
- `CLICOLOR_FORCE=1 bd list | cat` keeps colors
- Verify emoji handling in non-TTY mode


Bug: bd swarm create allows duplicate swarm molecules

Running `bd swarm create <epic>` twice creates two swarm molecules for the same epic.

Should check for existing swarm molecule linked to the epic and either:
1. Error with 'swarm already exists: <id>'
2. Warn and return existing swarm ID
3. Add --force flag to allow creating another

Check by querying for molecules with mol_type=swarm that have a relates-to dependency to the epic.


Create git tag v0.33.2

Create the release tag:

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

Verify: `git tag | grep 0.33.2`


Push release tag

Push the version tag to trigger CI release.

```bash
git push origin v0.41.0
```

This triggers GitHub Actions to build artifacts and publish.


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.


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


Merge: bd-05a8

branch: polecat/valkyrie
target: main
source_issue: bd-05a8
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


Merge: bd-likt

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


bd mol run: Only creates partial children from proto

When running `bd mol run gt-lwuu --var issue=gt-xxx`, only 2 of 4 proto children are created.

## Expected
Proto gt-lwuu has 4 children:
- gt-lwuu.1: load-context
- gt-lwuu.2: implement
- gt-lwuu.3: self-review
- gt-lwuu.8: request-shutdown

All 4 should be instantiated.

## Actual
Only 2 children created:
- load-context
- implement

self-review and request-shutdown are missing.

## Repro
```bash
bd --no-daemon mol run gt-lwuu --var issue=gt-xxx --json
bd list --parent=<created-id>  # Only shows 2 children
```

## Impact
Polecats spawned with mol-polecat-work get incomplete workflow.


bd mol burn should work on mols, not just wisps

Currently `bd mol burn` only works on wisps (Ephemeral=true):

```
$ bd mol burn bd-mol-p7i
Error: molecule bd-mol-p7i is not a wisp (Ephemeral=false)
Hint: mol burn only works with wisp molecules
      Use 'bd delete' to remove non-wisp issues
```

The name 'burn' implies destruction - it should work on any molecule, not just wisps. When called on a regular mol, it should convert to `bd delete --cascade`.

**Proposed behavior:**
- If wisp: current behavior (delete without tombstones)
- If mol: `bd delete <id> --cascade` (delete with tombstones)

This makes the metaphor consistent: burn = destroy a molecule, regardless of phase.


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


Test pinned issue


bd create --rig flag for cross-rig issue filing

## Problem

To create an issue in a different rig, you must cd to that rig's directory. This is clunky when coordinating across rigs.

## Proposed Solution

Add a --rig flag to bd create that leverages existing routes.jsonl:

```bash
# From anywhere in town
bd create --rig beads --title="bug report" --type=bug

# Equivalent to:
cd ~/gt/beads/mayor/rig && bd create --title="..."
```

## How it works

1. Parse --rig flag (rig name, not prefix)
2. Look up rig in routes.jsonl to find beads location
3. Determine prefix from rig config
4. Create issue in that location with auto-generated ID

## Why elegant

- Leverages existing routing infrastructure
- Rig names are human-memorable (not cryptic prefixes)
- Works from anywhere in town
- Prefix auto-determined from rig config
- Reads naturally: "create in beads"

## Alternative

Also consider --prefix for power users who know prefixes:
```bash
bd create --prefix bd "Title here"
```


bd sync fails with orphaned issues and duplicate ID conflict

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

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

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

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

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

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

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


Digest: Release v0.34.0 @ 2025-12-22 12:16

Released v0.34.0: wisp commands, chemistry UX, cross-project deps