Improve bd doctor output formatting for better readability.

## Current State
Doctor output is a wall of text with:
- All checks shown (even passing ones)
- No visual hierarchy
- Hard to spot failures in long output

## Target Output

```
$ bd doctor

bd doctor v0.35.0

Summary: 24 checks passed, 1 warning, 0 errors

─────────────────────────────────────────────────
⚠ Warnings (1)
─────────────────────────────────────────────────

[hooks] Git hooks outdated
  Current version: 0.34.0
  Latest version: 0.35.0
  Fix: bd hooks install

─────────────────────────────────────────────────
✓ Passed (24)  [use --verbose to show details]
─────────────────────────────────────────────────
```

With --verbose:
```
$ bd doctor --verbose

bd doctor v0.35.0

Summary: 24 checks passed, 1 warning, 0 errors

─────────────────────────────────────────────────
⚠ Warnings (1)
─────────────────────────────────────────────────

[hooks] Git hooks outdated
  ...

─────────────────────────────────────────────────
✓ Passed (24)
─────────────────────────────────────────────────

  Database
    ✓ Database exists
    ✓ Database readable
    ✓ Schema up to date

  Git Hooks
    ✓ Pre-commit hook installed
    ✓ Post-merge hook installed
    ⚠ Hooks version mismatch (see above)

  Sync
    ✓ Sync branch configured
    ✓ Remote accessible
    ...
```

## Implementation

### 1. Add check categories (cmd/bd/doctor/categories.go)

```go
type Category string

const (
    CatDatabase  Category = "Database"
    CatHooks     Category = "Git Hooks"
    CatSync      Category = "Sync"
    CatDaemon    Category = "Daemon"
    CatConfig    Category = "Configuration"
    CatIntegrity Category = "Data Integrity"
)

// Assign categories to checks
var checkCategories = map[string]Category{
    "database-exists":     CatDatabase,
    "database-readable":   CatDatabase,
    "schema-version":      CatDatabase,
    "pre-commit-hook":     CatHooks,
    "post-merge-hook":     CatHooks,
    "hooks-version":       CatHooks,
    "sync-branch":         CatSync,
    "remote-access":       CatSync,
    // ... etc
}
```

### 2. Add --verbose flag

```go
// In cmd/bd/doctor.go init()
doctorCmd.Flags().BoolP("verbose", "v", false, "Show all checks including passed")
```

### 3. Create formatter (cmd/bd/doctor/format.go)

```go
type Formatter struct {
    verbose bool
    noColor bool
}

func (f *Formatter) Format(results []CheckResult) string {
    var buf strings.Builder

    // Count by status
    passed, warnings, errors := countByStatus(results)

    // Header
    buf.WriteString(fmt.Sprintf("bd doctor v%s\n\n", version.Version))
    buf.WriteString(fmt.Sprintf("Summary: %d passed, %d warnings, %d errors\n\n",
        passed, warnings, errors))

    // Errors section (always show)
    if errors > 0 {
        f.writeSection(&buf, "✗ Errors", filterByStatus(results, StatusError))
    }

    // Warnings section (always show)
    if warnings > 0 {
        f.writeSection(&buf, "⚠ Warnings", filterByStatus(results, StatusWarning))
    }

    // Passed section (only with --verbose)
    if f.verbose && passed > 0 {
        f.writePassedSection(&buf, filterByStatus(results, StatusPassed))
    } else if passed > 0 {
        buf.WriteString(fmt.Sprintf("✓ Passed (%d)  [use --verbose to show details]\n", passed))
    }

    return buf.String()
}

func (f *Formatter) writeSection(buf *strings.Builder, title string, results []CheckResult) {
    buf.WriteString("─────────────────────────────────────────────────\n")
    buf.WriteString(title + "\n")
    buf.WriteString("─────────────────────────────────────────────────\n\n")

    for _, r := range results {
        buf.WriteString(fmt.Sprintf("[%s] %s\n", r.CheckName, r.Message))
        if r.Details != "" {
            buf.WriteString(fmt.Sprintf("  %s\n", r.Details))
        }
        if r.Fix != "" {
            buf.WriteString(fmt.Sprintf("  Fix: %s\n", r.Fix))
        }
        buf.WriteString("\n")
    }
}

func (f *Formatter) writePassedSection(buf *strings.Builder, results []CheckResult) {
    // Group by category
    byCategory := groupByCategory(results)

    buf.WriteString("─────────────────────────────────────────────────\n")
    buf.WriteString(fmt.Sprintf("✓ Passed (%d)\n", len(results)))
    buf.WriteString("─────────────────────────────────────────────────\n\n")

    for _, cat := range categoryOrder {
        if checks, ok := byCategory[cat]; ok {
            buf.WriteString(fmt.Sprintf("  %s\n", cat))
            for _, r := range checks {
                buf.WriteString(fmt.Sprintf("    ✓ %s\n", r.Message))
            }
            buf.WriteString("\n")
        }
    }
}
```

### 4. Update run function

```go
func runDoctor(cmd *cobra.Command, args []string) {
    verbose, _ := cmd.Flags().GetBool("verbose")
    noColor, _ := cmd.Flags().GetBool("no-color")

    results := runAllChecks()

    formatter := &Formatter{verbose: verbose, noColor: noColor}
    fmt.Print(formatter.Format(results))

    // Exit code based on results
    if hasErrors(results) {
        os.Exit(1)
    }
}
```

## Files to Modify

1. **cmd/bd/doctor.go** - Add --verbose flag, update run function
2. **cmd/bd/doctor/format.go** - New file for formatting logic
3. **cmd/bd/doctor/categories.go** - New file for check categorization
4. **cmd/bd/doctor/common.go** - Add Status field to CheckResult if missing

## Testing

```bash
# Default output (concise)
bd doctor

# Verbose output
bd doctor --verbose

# JSON output (should still work)
bd doctor --json
```

## Success Criteria
- Summary line at top with counts
- Only failures/warnings shown by default
- --verbose shows grouped passed checks
- Visual separators between sections
- Exit code 1 if errors, 0 otherwise


Runtime bonding: bd mol attach

Attach a molecule to an already-running workflow.

COMMAND: bd mol attach <epic-id> <proto> [--after <issue-id>]

BEHAVIOR:
- Resolve running epic and proto
- Spawn proto as new subtree
- Wire to specified attachment point (or epic root)
- Handle in-progress issues: new work doesn't block completed work

USE CASES:
- Discovered need for docs while implementing feature
- Hotfix needs attaching to release workflow
- Additional testing scope identified mid-flight

FLAGS:
- --after ISSUE: Specific attachment point within epic
- --type: sequential (default) or parallel
- --var: Variables for the attached proto

CONSIDERATIONS:
- What if epic is already closed? Error or reopen?
- What if attachment point issue is closed? Attach as ready-to-work?


Fix TestFallbackToDirectModeEnablesFlush deadlock causing 10min test timeout

## Problem

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

## Root Cause

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

## Stack Trace Evidence

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

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

## Impact

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

## Related

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

## Fix Approaches

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

## Files

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

## Acceptance

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


Fix resolvePartialID stub in workflow.go

The resolvePartialID function at workflow.go:921-925 is a stub that just returns the ID unchanged. Should use utils.ResolvePartialID for proper partial ID resolution in direct mode (non-daemon).


bd sync --merge fails due to chicken-and-egg: .beads/ always dirty

## Problem

When sync.branch is configured (e.g., beads-sync), the bd sync workflow creates a chicken-and-egg problem:

1. `bd sync` commits changes to beads-sync via worktree
2. `bd sync` copies JSONL to main working dir via `copyJSONLToMainRepo()` (sync.go line 364, worktree.go line 678-685)
3. The copy is NOT committed to main - it just updates the working tree
4. `bd sync --merge` checks for clean working dir (sync.go line 1547-1548)
5. `bd sync --merge` FAILS because .beads/issues.jsonl is uncommitted!

## Impact

- sync.branch workflow is fundamentally broken
- Users cannot periodically merge beads-sync → main
- Main branch always shows as dirty
- Creates confusion about git state

## Root Cause

sync.go:1547-1548:
```go
if len(strings.TrimSpace(string(statusOutput))) > 0 {
    return fmt.Errorf("main branch has uncommitted changes, please commit or stash them first")
}
```

This check blocks merge when ANY uncommitted changes exist, including the .beads/ changes that `bd sync` itself created.

## Proposed Fix

Option A: Exclude .beads/ from the clean check in `mergeSyncBranch`:
```go
// Check if there are non-beads uncommitted changes
statusCmd := exec.CommandContext(ctx, "git", "status", "--porcelain", "--", ":!.beads/")
```

Option B: Auto-stash .beads/ changes before merge, restore after

Option C: Change the workflow - do not copy JSONL to main working dir, instead always read from worktree

## Files to Modify

- cmd/bd/sync.go:1540-1549 (mergeSyncBranch function)
- Possibly internal/syncbranch/worktree.go (copyJSONLToMainRepo)

## Fix Implemented

Modified cmd/bd/sync.go mergeSyncBranch function:

1. **Exclude .beads/ from dirty check** (line 1543):
   Changed `git status --porcelain` to `git status --porcelain -- :!.beads/`
   This allows merge to proceed when only .beads/ has uncommitted changes.

2. **Restore .beads/ to HEAD before merge** (lines 1553-1561):
   Added `git checkout HEAD -- .beads/` before merge to prevent
   "Your local changes would be overwritten by merge" errors.
   The .beads/ changes are redundant since they came FROM beads-sync.

## Testing

- All cmd/bd sync/merge tests pass
- All internal/syncbranch tests pass
- Manual verification needed for full workflow


Support redirect files in .beads/ directory

Gas Town creates polecat worktrees with .beads/redirect files that point to a shared beads database. The bd CLI should:

1. When finding a .beads/ directory, check if it contains a 'redirect' file
2. If redirect exists, read the relative path and use that as the beads directory
3. This allows multiple git worktrees to share a single beads database

Example:
- polecats/alpha/.beads/redirect contains '../../mayor/rig/.beads'
- bd commands from alpha should use mayor/rig/.beads

Currently bd ignores redirect files and either uses the local .beads/ or walks up to find a parent .beads/.

Related: gt-nriy (test message that can't be retrieved due to missing redirect support)


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

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


Merge: bd-au0.6

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


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

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

Current coverage: 28.4%
Target coverage: 50%


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.


Add metadata and thread_id columns to dependencies table

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


bd ready resolves external dependencies

Extend bd ready to check external blocked_by references:

1. Parse external:<project>:<capability> from blocked_by
2. Look up project path from external_projects config
3. Check if target project has provides:<capability> label on a closed issue
4. If not satisfied, issue is blocked

Example output:
```bash
bd ready
# gt-xyz: blocked by external:beads:mol-run-assignee (not provided)
# gt-abc: ready
```

Depends on: bd-om4a (external: prefix), bd-66w1 (config)

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


Rename 'template instantiate' to 'mol bond'

Rename the template instantiation command to match molecule metaphor.

Current: bd template instantiate <id> --var key=value
Target:  bd mol bond <id> --var key=value

Changes needed:
- Add 'mol' command group (or extend existing)
- Add 'bond' subcommand that wraps template instantiate logic
- Keep 'template instantiate' as deprecated alias for backward compat
- Update help text and docs to use molecule terminology

The 'bond' verb captures:
1. Chemistry metaphor (molecules bond to form structures)
2. Dependency linking (child issues bonded in a DAG)
3. Short and active

See also: molecule execution model in Gas Town


bd ship command for publishing capabilities

Add `bd ship <capability>` command that:

1. Finds issue with `export:<capability>` label
2. Validates issue is closed (or --force to override)
3. Adds `provides:<capability>` label
4. Protects `provides:*` namespace (only bd ship can add these labels)

Example:
```bash
bd ship mol-run-assignee
# Output: Shipped mol-run-assignee (bd-xyz)
```

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


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.


GH#532: bd sync circular error (suggests running bd sync)

bd sync error message recommends running bd sync to fix the bd sync error. Fix error handling to provide useful guidance. See GitHub issue #532.


Consider consolidating config.yaml parsing into shared utility

Multiple places parse config.yaml with custom structs:

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

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

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

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

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

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


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


Release v0.30.4

## Version Bump Workflow

Coordinating release from 0.30.3 to 0.30.4.

### Components Updated
- Go CLI (cmd/bd/version.go)
- Claude Plugin (.claude-plugin/*.json)
- MCP Server (integrations/beads-mcp/)
- npm Package (npm-package/package.json)
- Git hooks (cmd/bd/templates/hooks/)

### Release Channels
- GitHub Releases (GoReleaser)
- PyPI (beads-mcp)
- npm (@beads/cli)
- Homebrew (homebrew-beads tap)


Merge: bd-kzda

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


Async Gates for Agent Coordination

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

## Core Concepts

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

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

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


Fix checkExistingBeadsData to be worktree-aware

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


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

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

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

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

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

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

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

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


Push version bump to GitHub

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


Prevent multiple daemons from running on the same repo

Multiple bd daemons running on the same repo clone causes race conditions and data corruption risks.

**Problem:**
- Nothing prevents spawning multiple daemons for the same repository
- Multiple daemons watching the same files can conflict during sync operations
- Observed: 4 daemons running simultaneously caused sync race condition

**Solution:**
Implement daemon singleton enforcement per repo:
1. Use a lock file (e.g., .beads/.daemon.lock) with PID
2. On daemon start, check if lock exists and process is alive
3. If stale lock (dead PID), clean up and acquire lock
4. If active daemon exists, either:
   - Exit with message 'daemon already running (PID xxx)'
   - Or offer --replace flag to kill existing and take over
5. Release lock on graceful shutdown

**Edge cases to handle:**
- Daemon crashes without releasing lock (stale PID detection)
- Multiple repos in same directory tree (each repo gets own lock)
- Race between two daemons starting simultaneously (atomic lock acquisition)


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

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

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

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

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

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

Requires: Identity configuration (bd-kwro.7)


No logging/debugging for tombstone resurrection events

Per the design document bd-zvg Open Question 1: Should resurrection log a warning? Recommendation was Yes. Currently, when an expired tombstone loses to a live issue (resurrection), there is no logging or debugging output. This makes it hard to understand why an issue reappeared. Recommendation: Add optional debug logging when resurrection occurs, e.g., Issue bd-abc resurrected (tombstone expired). Files: internal/merge/merge.go:359-366, 371-378, 400-405, 410-415


Add 0.30.4 to info.go release notes

Update cmd/bd/info.go versionChanges map with release notes for 0.30.4.
Include any workflow-impacting changes for --whats-new output.


Improve test coverage for internal/rpc (47.5% → 60%)

The RPC package has only 47.5% test coverage. RPC is the communication layer for daemon operations.

Current coverage: 47.5%
Target coverage: 60%


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

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

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

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

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

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


Implement bd gate create/show/list/close/wait commands

Implement the gate CLI commands.

## Commands
```bash
# Create gate (returns gate ID)
bd gate create --await <type>:<id> --timeout <duration> --notify <addr>

# Check gate status
bd gate show <id>

# List open gates
bd gate list

# Close gate (usually done by Deacon)
bd gate close <id> --reason "completed"

# Add waiter to existing gate
bd gate wait <id> --notify <addr>
```

## Implementation
- Add cmd/bd/gate.go with subcommands
- Gate create creates wisp issue of type gate
- Gate list filters for open gates
- Gate wait adds to waiters[] array
- All support --json output


Add sync resilience guardrails for forced pushes and prefix mismatches

Beads can get into unrecoverable sync states when remote forces pushes occur (e.g., rebases) combined with prefix mismatches from multi-worker scenarios. Add detection, prevention, and auto-recovery features to handle this gracefully.


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


bd preflight: PR readiness checks for contributors

## Vision

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

## Problem Statement

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

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

## Why beads?

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

## Proposed Interface

### Tier 1: Checklist Mode (v1)

    $ bd preflight
    PR Readiness Checklist:

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

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

### Tier 2: Check Mode (v2)

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

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

### Tier 3: Fix Mode (v3)

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

## Checks to Implement

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

## Future: Configuration

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

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

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

## Implementation Phases

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

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

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

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

## Dependencies

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

## Success Metrics

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


Commit and push release

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


Merge: bd-io8c

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


Update CHANGELOG.md with release notes

Add release notes for 0.32.1: MCP output control params (#667), pin field fix


GH#692: Code quality and refactoring improvements

Epic for implementing refactoring suggestions from GitHub issue #692 (rsnodgrass). These are code quality improvements that don't change functionality but improve maintainability, type safety, and performance.

Original issue: https://github.com/steveyegge/beads/issues/692

High priority items:
1. Replace map[string]interface{} with typed structs for JSON output
2. Adopt slices.SortFunc instead of sort.Slice (Go 1.21+)
3. Split large files (sync.go, init.go, show.go)
4. Introduce slog for structured logging in daemon

Lower priority:
5. Further CLI helper extraction
6. Preallocate slices in hot paths
7. Polish items (error wrapping, table-driven parsing)


Commit release v{{version}}

Stage and commit the version bump:

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

Do NOT push yet - tag first.


Merge: bd-dtl8

branch: polecat/dag
target: main
source_issue: bd-dtl8
rig: beads


Performance Validation

Confirm no performance regression from cache removal


Add integration tests for RPC server and event loops

After adding basic unit tests for daemon utilities, the complex daemon functions still need integration tests:

Core daemon lifecycle:
- startRPCServer: Initializes and starts RPC server with proper error handling
- runEventLoop: Polling-based sync loop with parent monitoring and signal handling
- runDaemonLoop: Main daemon initialization and setup

Health checking:
- isDaemonHealthy: Checks daemon responsiveness and health metrics
- checkDaemonHealth: Periodic health verification

These require more complex test infrastructure (mock RPC, test contexts, signal handling) and should be tackled after the unit test foundation is in place.


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

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


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

## Problem

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

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

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

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


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

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


bd pin: pinned field overwritten by subsequent bd commands

## Summary

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

## Reproduction Steps

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

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

## Root Cause Investigation

### Prime Suspects

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

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

### Debug Steps

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

## Likely Fix

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

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

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

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

## Testing

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

## Files to Modify

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

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


Add test coverage for external dep edge cases

During code review of bd-zmmy, identified missing test coverage:

1. RemoveDependency with external ref target (will fail - see bd-a3sj)
2. GetBlockedIssues with mix of local and external blockers
3. GetDependencyTree with external deps
4. AddDependency cycle detection with external refs (should be skipped?)
5. External dep resolution with WAL mode database
6. External dep resolution when target project has no .beads directory
7. External dep resolution with invalid external: format variations

Priority 2 because bd-a3sj is a real bug that tests would catch.


Add gate issue type to beads

Add 'gate' as a new issue type for async coordination.

## Changes Needed
- Add 'gate' to IssueType enum in internal/types/types.go
- Update validation to accept gate type
- Update CLI help text and completion

## Gate Type Semantics
- Gates are ephemeral (live in wisp storage)
- Managed by Deacon patrol
- Have special fields: await_type, await_id, timeout, waiters[]


Add test files for internal/storage

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

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


Tests for messaging and graph links

Comprehensive test coverage for all new features.

Test files:
- cmd/bd/mail_test.go - mail command tests
- internal/storage/sqlite/graph_links_test.go - graph link tests
- internal/hooks/hooks_test.go - hook execution tests

Test cases:
- Mail send/inbox/read/ack lifecycle
- Thread creation and traversal (replies_to)
- Bidirectional relates_to
- Duplicate marking and queries
- Supersedes chains
- Ephemeral cleanup
- Identity resolution priority
- Hook execution (mock hooks)
- Schema migration preserves data

Target: >80% coverage on new code


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.


bd sync --from-main fails: unknown flag --no-git-history


Fix bd sync race condition with daemon causing dirty working directory

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

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

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

Related: bd-c83r (multiple daemon prevention)


Remove or fix ClearDirtyIssues() - race condition risk (bd-52)

Code health review found internal/storage/sqlite/dirty.go still exposes old ClearDirtyIssues() method (lines 103-108) which clears ALL dirty issues without checking what was actually exported.

Data loss risk: If export fails after some issues written to JSONL but before ClearDirtyIssues called, changes to remaining dirty issues will be lost.

The safer ClearDirtyIssuesByID() (lines 113-132) exists and clears only exported issues.

Fix: Either remove old method or mark it deprecated and ensure no code paths use it.


Schema: Add message type and new fields

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

New issue_type value:
- message

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

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

Ensure backward compatibility - all new fields optional.


Add missing hook calls in mail reply and ack

The mail commands are missing hook calls:

1. runMailReply (mail.go:525-672) creates a message but doesn't call hookRunner.Run(hooks.EventMessage, ...) after creating the reply in direct mode (around line 640)

2. runMailAck (mail.go:432-523) closes messages but doesn't call hookRunner.Run(hooks.EventClose, ...) after closing each message (around line 487 for daemon mode, 493 for direct mode)

This means GGT hooks won't fire for replies or message acknowledgments.


Work on beads-d8h: Fix prefix mismatch false positive wit...

Work on beads-d8h: Fix prefix mismatch false positive with multi-hyphen prefixes like 'asianops-audit-' (GH#422). When done, submit MR (not PR) to integration branch for Refinery.


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

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


Fix daemon/direct mode inconsistency in relate and duplicate commands

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

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

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

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


Update CHANGELOG.md with release notes

Add meaningful release notes to CHANGELOG.md describing what changed in {{version}}


Time-dependent tests may be flaky near TTL boundary

Several tombstone merge tests use time.Now() to create test data: time.Now().Add(-24 * time.Hour), time.Now().Add(-60 * 24 * time.Hour), etc. While these work reliably in practice (24h vs 30d TTL has large margin), they could theoretically be flaky if: 1) Tests run slowly, 2) System clock changes during test, 3) TTL constants change. Recommendation: Consider using a fixed reference time or time injection for deterministic tests. Lower priority since current margin is large. Files: internal/merge/merge_test.go:1337-1338, 1352-1353, 1548-1549, 1590-1591


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

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


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.


Work on beads-eub: Consolidated context tool for MCP serv...

Work on beads-eub: Consolidated context tool for MCP server (GH#636). Merge set_context, where_am_i, init into single 'context' tool. When done, submit MR (not PR) to integration branch for Refinery.


Merge: bd-tvu3

branch: polecat/Beader
target: main
source_issue: bd-tvu3
rig: beads


Split internal/storage/sqlite/queries.go (1586 lines)

Split internal/storage/sqlite/queries.go (1704 lines) into logical modules.

## Current State
queries.go is 1704 lines with mixed responsibilities:
- Issue CRUD operations
- Search/filter operations
- Delete operations (complex cascade logic)
- Helper functions (parsing, formatting)

## Proposed Split

### 1. queries.go (keep ~400 lines) - Core CRUD
```go
// Core issue operations
func (s *SQLiteStorage) CreateIssue(...)
func (s *SQLiteStorage) GetIssue(...)
func (s *SQLiteStorage) UpdateIssue(...)
func (s *SQLiteStorage) CloseIssue(...)
```

### 2. queries_search.go (~300 lines) - Search/Filter
```go
// Search and filtering
func (s *SQLiteStorage) SearchIssues(...)
func (s *SQLiteStorage) GetIssueByExternalRef(...)
func (s *SQLiteStorage) GetCloseReason(...)
func (s *SQLiteStorage) GetCloseReasonsForIssues(...)
```

### 3. queries_delete.go (~400 lines) - Delete Operations
```go
// Delete operations with cascade logic
func (s *SQLiteStorage) CreateTombstone(...)
func (s *SQLiteStorage) DeleteIssue(...)
func (s *SQLiteStorage) DeleteIssues(...)
func (s *SQLiteStorage) resolveDeleteSet(...)
func (s *SQLiteStorage) expandWithDependents(...)
func (s *SQLiteStorage) validateNoDependents(...)
func (s *SQLiteStorage) checkSingleIssueValidation(...)
func (s *SQLiteStorage) trackOrphanedIssues(...)
func (s *SQLiteStorage) collectOrphansForID(...)
func (s *SQLiteStorage) populateDeleteStats(...)
func (s *SQLiteStorage) executeDelete(...)
func (s *SQLiteStorage) findAllDependentsRecursive(...)
```

### 4. queries_helpers.go (~100 lines) - Utilities
```go
// Helper functions (already at top of file)
func parseNullableTimeString(...)
func parseJSONStringArray(...)
func formatJSONStringArray(...)
```

### 5. queries_rename.go (~100 lines) - ID/Prefix Operations
```go
// ID and prefix management
func (s *SQLiteStorage) UpdateIssueID(...)
func (s *SQLiteStorage) RenameDependencyPrefix(...)
func (s *SQLiteStorage) RenameCounterPrefix(...)
func (s *SQLiteStorage) ResetCounter(...)
```

## Implementation Steps

1. **Create new files** with package declaration:
   ```go
   // queries_delete.go
   package sqlite

   import (...)
   ```

2. **Move functions** - cut/paste, maintaining order within each file

3. **Update imports** - each file needs its own imports

4. **Run tests** after each file split:
   ```bash
   go test ./internal/storage/sqlite/...
   ```

5. **Run linter** to catch any issues:
   ```bash
   golangci-lint run ./internal/storage/sqlite/...
   ```

## File Organization
```
internal/storage/sqlite/
├── queries.go          # Core CRUD (~400 lines)
├── queries_search.go   # Search/filter (~300 lines)
├── queries_delete.go   # Delete cascade (~400 lines)
├── queries_helpers.go  # Utilities (~100 lines)
└── queries_rename.go   # ID operations (~100 lines)
```

## Success Criteria
- No file > 500 lines
- All tests pass
- No functionality changes
- Clear separation of concerns


Update info.go versionChanges

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


Update info.go versionChanges

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


Make merge requests ephemeral wisps instead of permanent issues

## Problem

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

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

## Proposed Solution

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

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

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

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

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

## Benefits

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

## Implementation Notes

### Where MRs are created

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

### Refinery changes

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

### What about cross-rig MRs?

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

### Migration

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

## Alternatives Considered

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

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

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

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

## Related

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


HOP-compatible schema additions

Add optional fields to Beads schema to enable future HOP integration.
All fields are backwards-compatible (optional, omitted if empty).

## Reference
See ~/gt/docs/hop/BEADS-SCHEMA-CHANGES.md for full specification.

## P1 Changes (Must Have Before Launch)

### 1. EntityRef type
Structured entity reference that can become HOP URI:
```go
type EntityRef struct {
    Name     string // "polecat/Nux"
    Platform string // "gastown"
    Org      string // "steveyegge"
    ID       string // "polecat-nux"
}
```

### 2. creator field
Every issue tracks who created it (EntityRef).

### 3. assignee_ref field
Structured form alongside existing string assignee.

### 4. validations array
Track who validated work completion:
```go
type Validation struct {
    Validator *EntityRef
    Outcome   string    // accepted, rejected, revision_requested
    Timestamp time.Time
    Score     *float32  // Future
}
```

## P2 Changes (Should Have)

### 5. work_type field
"mutex" (default) or "open_competition"

### 6. crystallizes field
Boolean - does this work compound (true) or evaporate (false)?

### 7. cross_refs field
Array of URIs to beads in other repos:
- "beads://github/anthropics/claude-code/bd-xyz"

## P3 Changes (Nice to Have)

### 8. skill_vector placeholder
Reserved for future embeddings: []float32

## Implementation Notes
- All fields optional in JSONL serialization
- Empty/null fields omit from output
- No migration needed for existing data
- CLI additions: --creator, --validated-by filters

Scope reduced after review. P1 only: EntityRef type, creator field, validations array. Deferred: assignee_ref, work_type, crystallizes, cross_refs, skill_vector (YAGNI - semantics unclear, can add later when needed).


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

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

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

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

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

## Potential Followups
See linked issues for suggestions.


Design molecule step hooks

Hooks that fire between molecule steps. When a bead in a molecule closes, trigger hook that can spawn agent attention to prompts/requests. This enables reactive orchestration - the molecule drives, hooks respond. Gas Town feature built on Beads data plane.


Update git hooks

Install the updated hooks:
```bash
bd hooks install
```

Verify hook version:
```bash
grep 'bd-hooks-version' .git/hooks/pre-commit
```

```verify
grep -q 'bd-hooks-version: 0.30.4' .git/hooks/pre-commit 2>/dev/null || echo 'Hooks may not be installed - verify manually'
```


test pin issue


Fix --as flag: uses title instead of ID in mol bond

In bondProtoProto, the --as flag is documented as 'Custom ID for compound proto' but the implementation uses it as the title, not the issue ID.

**Current behavior (mol.go:637-638):**
```go
if customID != '' {
    compoundTitle = customID  // Used as title, not ID
}
```

**Options:**
1. Change flag description to say 'Custom title' (documentation fix)
2. Actually use it as a custom ID prefix or full ID (feature change)
3. Add separate --title flag and make --as actually set ID

Recommend option 1 for simplest fix - change 'Custom ID' to 'Custom title' in the flag description.


Release v0.33.2

Version bump workflow for beads release 0.33.2.

## Variables
- `0.33.2` - The new version number (e.g., 0.31.0)
- `2025-12-21` - Release date (YYYY-MM-DD format)

## Workflow Steps
1. Kill running daemons
2. Run tests and linting
3. Bump version in all files (10 files total)
4. Update cmd/bd/info.go with release notes
5. Commit and push version bump
6. Create and push git tag
7. Update Homebrew formula
8. Upgrade local Homebrew installation
9. Verify installation

## Files Updated by bump-version.sh
- cmd/bd/version.go
- .claude-plugin/plugin.json
- .claude-plugin/marketplace.json
- integrations/beads-mcp/pyproject.toml
- integrations/beads-mcp/src/beads_mcp/__init__.py
- README.md
- npm-package/package.json
- cmd/bd/templates/hooks/* (4 files)
- CHANGELOG.md

## Manual Step Required
- cmd/bd/info.go - Add versionChanges entry with release notes


Review PR #514: fix plugin install docs

Review and merge PR #514 from aspiers. This PR fixes incorrect docs for installing Claude Code plugin from source in docs/PLUGIN.md. Clarifies shell vs Claude Code commands and fixes the . vs ./beads argument issue. URL: https://github.com/anthropics/beads/pull/514


Design contributor namespace isolation for beads pollution prevention

## Problem

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

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

## Possible Solutions to Explore

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

## Design Considerations

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

## Expansion Needed

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


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.


Deferred HOP schema additions (P2/P3)

Deferred from bd-7pwh after review. Add when semantics are clearer and actually needed:

- assignee_ref: Structured EntityRef alongside string assignee
- work_type: 'mutex' vs 'open_competition' (everything is mutex in v0.1)
- crystallizes: bool for work that compounds vs evaporates (can derive from issue_type)
- cross_refs: URIs to beads in other repos (needs federation first)
- skill_vector: []float32 embeddings placeholder (YAGNI)

These can be added later without breaking changes (all optional fields).


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


Test issue 2


Merge: bd-m8ro

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


Merge: bd-ykd9

branch: polecat/Doctor
target: main
source_issue: bd-ykd9
rig: beads


.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


Consider caching external dep resolution results

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

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

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

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


Phase 1.3: Basic TOON read/write operations

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

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

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

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


Work on beads-399: Add omitempty to JSONL fields for smal...

Work on beads-399: Add omitempty to JSONL fields for smaller notifications. When done, submit MR (not PR) to integration branch for Refinery.


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

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


Identity Configuration

Implement identity system for sender field.

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

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

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

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


Test daemon RPC delete handler

Add tests for the daemon-side RPC delete handler that processes delete requests from clients.

## What needs testing
- Daemon's Delete RPC handler implementation
- Processing delete requests from RPC clients
- Cascade deletion at daemon level
- Force deletion at daemon level
- Dry-run mode validation
- Error responses to clients
- Dependency validation before deletion
- Tombstone creation via daemon

## Test scenarios
1. Delete single issue via RPC
2. Delete multiple issues via RPC
3. Cascade deletion of dependents
4. Force delete with orphaned dependents
5. Dry-run returns what would be deleted without actual deletion
6. Error: invalid issue IDs
7. Error: insufficient permissions
8. Error: dependency blocks deletion (without force/cascade)

## Related
- Parent epic: bd-kyll
- Original issue: bd-7z4


Fix update-homebrew.sh awk script corrupts formula

The awk script in scripts/update-homebrew.sh incorrectly removes platform conditionals (on_macos do, on_linux do, if Hardware::CPU.arm?, etc.) when updating SHA256 hashes. This corrupts the Homebrew formula.

The issue is the awk script uses 'next' to skip lines containing platform conditionals but never reconstructs them, resulting in a syntax-invalid formula.

Found during v0.34.0 release - had to manually fix the formula.

Fix options:
1. Rewrite awk script to properly preserve structure while updating sha256 lines only
2. Use sed instead with targeted sha256 replacements
3. Template approach - store formula template and fill in version/hashes


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.


Add orphan detection migration

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


Merge: bd-4qfb

branch: polecat/Polish
target: main
source_issue: bd-4qfb
rig: beads


Create git tag v0.33.2

Create the release tag:

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

Verify: `git tag | grep 0.33.2`


Fix TestImportBugIntegration deadlock in importer_test.go

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

TODO: Test hangs due to database deadlock - needs investigation

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

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


Update version to 0.33.2 in version.go

Edit cmd/bd/version.go line 17:

```go
Version = "0.33.2"
```

Verify with: `grep 'Version =' cmd/bd/version.go`


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

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

## Usage

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

## Behavior

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

## Output

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

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

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

## If no next step

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

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

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

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


Compound visualization in bd mol show

Enhance bd mol show to display compound structure.

ENHANCEMENTS:
- Show constituent protos and how they're bonded
- Display bond type (sequential/parallel) between components
- Indicate attachment points
- Show combined variable requirements across all protos

EXAMPLE OUTPUT:

  Compound: proto-feature-with-tests
  Bonded from:
    └─ proto-feature (root)
       └─ proto-testing (sequential, after completion)

  Variables: {{name}}, {{version}}, {{test_suite}}

  Structure:
    proto-feature-with-tests
    ├─ Design feature {{name}}
    ├─ Implement core
    ├─ Write unit tests      ← from proto-testing
    └─ Run test suite {{test_suite}}  ← from proto-testing


bd close hooks: context check and notifications

Add hook system to bd close for notifications and custom actions.

## Scope (MVP)

Implement **command hooks only** for bd close. Deferred: notify, webhook types.

## Implementation

### 1. Config Schema

Add to internal/configfile/config.go:

```go
type HooksConfig struct {
    OnClose []HookEntry `yaml:"on_close,omitempty"`
}

type HookEntry struct {
    Command string `yaml:"command"`           // Shell command to run
    Name    string `yaml:"name,omitempty"`    // Optional display name
}
```

Add `Hooks HooksConfig` field to Config struct.

### 2. Hook Execution

Create internal/hooks/close_hooks.go:

```go
func RunCloseHooks(ctx context.Context, cfg *configfile.Config, issue *types.Issue) error {
    for _, hook := range cfg.Hooks.OnClose {
        cmd := exec.CommandContext(ctx, "sh", "-c", hook.Command)
        cmd.Env = append(os.Environ(),
            "BEAD_ID="+issue.ID,
            "BEAD_TITLE="+issue.Title,
            "BEAD_TYPE="+string(issue.IssueType),
            "BEAD_PRIORITY="+strconv.Itoa(issue.Priority),
            "BEAD_CLOSE_REASON="+issue.CloseReason,
        )
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        if err := cmd.Run(); err \!= nil {
            // Log warning but dont fail the close
            fmt.Fprintf(os.Stderr, "Warning: close hook %q failed: %v\n", hook.Name, err)
        }
    }
    return nil
}
```

### 3. Integration Point

In cmd/bd/close.go, after successful close:

```go
// Run close hooks
if cfg := configfile.Load(); cfg \!= nil {
    hooks.RunCloseHooks(ctx, cfg, closedIssue)
}
```

### 4. Example Config

```yaml
# .beads/config.yaml
hooks:
  on_close:
    - name: show-next
      command: bd ready --limit 1
    - name: context-check
      command: echo "Issue $BEAD_ID closed. Check context if nearing limit."
```

## Environment Variables

| Variable | Description |
|----------|-------------|
| BEAD_ID | Issue ID (e.g., bd-abc1) |
| BEAD_TITLE | Issue title |
| BEAD_TYPE | Issue type (task, bug, feature, etc.) |
| BEAD_PRIORITY | Priority (0-4) |
| BEAD_CLOSE_REASON | Close reason if provided |

## Testing

Add test in internal/hooks/close_hooks_test.go:
- Test hook execution with mock config
- Test env vars are set correctly
- Test hook failure doesnt block close

## Files to Create/Modify

1. **Create:** internal/hooks/close_hooks.go
2. **Create:** internal/hooks/close_hooks_test.go
3. **Modify:** internal/configfile/config.go (add HooksConfig)
4. **Modify:** cmd/bd/close.go (call RunCloseHooks)
5. **Modify:** docs/CONFIG.md (document hooks config)

## Out of Scope (Future)

- notify hook type (gt mail integration)
- webhook type (HTTP POST)
- on_create, on_update hooks
- Hook timeout configuration
- Parallel hook execution


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

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

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

## Functions Needing Tests

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

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

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

## Implementation Guide

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

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

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

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

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

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


Improve test coverage for internal/export (37.1% → 60%)

The export package has only 37.1% test coverage. Export functionality needs good coverage to ensure data integrity.

Current coverage: 37.1%
Target coverage: 60%


Redesign workflow system: templates as native Beads

## Problem

The current workflow system (YAML templates in cmd/bd/templates/workflows/) is architecturally flawed:

1. **Out-of-band data plane** - YAML files are a parallel system outside Beads itself
2. **Heavyweight DSL** - YAML is gross; even TOML would have been better, but neither is ideal
3. **Not graph-native** - Beads IS already a dependency graph with priorities, so why reinvent it?
4. **Can't use bd commands on templates** - They're opaque YAML, not viewable/editable Beads

## The Right Design

**Templates should be Beads themselves.**

A "workflow template" should be:
- An epic marked as a template (via label, type, or prefix like `tpl-`)
- Child issues with dependencies between them (using normal bd dep)
- Titles and descriptions containing `{{variable}}` placeholders
- Normal priorities that control serialization order

"Instantiation" becomes:
1. Clone the template subgraph (epic + children + dependencies)
2. Substitute variables in titles/descriptions
3. Generate new IDs for all cloned issues
4. Return the new epic ID

## Benefits

- **No YAML** - Templates are just Beads
- **Use existing tools** - `bd show`, `bd edit`, `bd dep` work on templates
- **Graph-native** - Dependencies are real Beads dependencies
- **Simpler codebase** - Remove all the YAML parsing/workflow code
- **Composable** - Templates can reference other templates

## Tasks

1. Delete the YAML workflow system code (revert recent push + remove existing workflow code)
2. Design template marking convention (label? type? id prefix?)
3. Implement `bd template create` or `bd clone --as-template`
4. Implement `bd template instantiate <template-id> --var key=value`
5. Migrate version-bump workflow to native Beads template
6. Update documentation


Merge: bd-by0d

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


Update LINTING.md with current baseline

After cleanup, document the remaining acceptable baseline in LINTING.md so we can track regression.


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

Linting issue: checkHooksQuick - path is unused (unparam) at cmd/bd/doctor.go:541:22. Error: func checkHooksQuick(path string) string {


Consider using types.Status in merge package for type safety

The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521


Merge: bd-t4sb

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


Daemon should be singleton and aggressively kill stale instances

Found 2 bd daemons running (PIDs 76868, 77515) during shutdown. The daemon should:

1. Be a singleton - only one instance per rig allowed
2. On startup, check for existing daemon and kill it before starting
3. Use a PID file or lock file to enforce this

Currently stale daemons can accumulate, causing confusion and resource waste.

**Investigation 2025-12-21:**

The singleton mechanism is already implemented and working correctly:

1. **daemon.lock** uses flock (exclusive non-blocking) to prevent duplicate daemons
2. **bd.sock.startlock** coordinates concurrent auto-starts via O_CREATE|O_EXCL
3. **Registry** tracks all daemons globally in ~/.beads/registry.json

Testing shows:
- Trying to start a second daemon gives: 'Error: daemon already running (PID X)'
- Multiple daemons for *different* rigs is expected/correct behavior

The original report ('Found 2 bd daemons running PIDs 76868, 77515') was likely:
1. Two daemons for different rigs (expected), OR
2. An edge case that's since been fixed

Consider closing as RESOLVED or clarifying the original scenario.


Add bd daemon --stop-all command to kill all daemon processes

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

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

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


Add pinned field to issue schema

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


Document BEADS_DIR pattern for multi-agent workspaces (Gas Town)

Gas Town and similar multi-agent systems need to configure separate beads databases per workspace/rig, distinct from any project-level beads.

## Use Case

In Gas Town:
- Each 'rig' (managed project) has multiple agents (polecats, refinery, witness)
- All agents in a rig should share a single beads database at the rig level
- This should be separate from any .beads/ the project itself uses
- The BEADS_DIR env var enables this

## Documentation Needed

1. Add a section to docs explaining BEADS_DIR for multi-agent setups
2. Example: setting BEADS_DIR in agent startup scripts/hooks
3. Clarify interaction with project-level .beads/ (BEADS_DIR takes precedence)

## Current Support

Already implemented in internal/beads/beads.go:FindDatabasePath():
- BEADS_DIR env var is checked first (preferred)
- BEADS_DB env var still supported (deprecated)
- Falls back to .beads/ search in tree

Just needs documentation for the multi-agent workspace pattern.


Merge: bd-dxtc

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


bd mol current: soft cursor showing current/next step

Add bd mol current command for molecule navigation orientation.

## Usage

bd mol current [mol-id]

If mol-id given, show status for that molecule.
If not given, infer from in_progress issues assigned to current agent.

## Output

You're working on molecule gt-abc (Feature X)

  [done] gt-abc.1: Design
  [done] gt-abc.2: Scaffold
  [done] gt-abc.3: Implement
  [current] gt-abc.4: Write tests [in_progress] <- YOU ARE HERE
  [pending] gt-abc.5: Documentation
  [pending] gt-abc.6: Exit decision

Progress: 3/6 steps complete

## Key behaviors
- Shows full molecule structure with status indicators
- Highlights current in_progress step
- If no in_progress, highlights first ready step
- Works without explicit cursor tracking (inferred from state)

## Implementation notes
- Query children of mol-id
- Sort by dependency order
- Find first in_progress or first ready
- Format with status indicators

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


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)


Enrich MutationEvent with title and assignee

Current MutationEvent only has IssueID, no context. Add Title and Assignee fields so activity feeds can display meaningful info without extra lookups. Emit these fields when creating mutation events in server_core.go.


Phase 2.3: TOON deletion tracking

Implement deletion tracking in TOON format.

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

## Required Work

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

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

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

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


Test deleteViaDaemon RPC client integration

Add comprehensive tests for the deleteViaDaemon function (cmd/bd/delete.go:21) which handles client-side RPC deletion calls.

## Function under test
- deleteViaDaemon: CLI command handler that sends delete requests to daemon via RPC

## Test scenarios needed
1. Successful deletion via daemon
2. Cascade deletion through daemon
3. Force deletion through daemon
4. Dry-run mode (no actual deletion)
5. Error handling:
   - Daemon unavailable
   - Invalid issue IDs
   - Dependency conflicts
6. JSON output validation
7. Human-readable output formatting

## Coverage target
Current: 0%
Target: >80%

## Related
- Parent epic: bd-kyll
- Original issue: bd-7z4


Work on beads-2nh: Fix gt spawn --issue to find issues in...

Work on beads-2nh: Fix gt spawn --issue to find issues in rig's beads database. When done, submit MR (not PR) to integration branch for Refinery.


doctor --fix should export when DB has more issues than JSONL

When 'bd doctor' detects a count mismatch (DB has more issues than JSONL), it currently recommends 'bd sync --import-only', which imports JSONL into DB. But JSONL is the source of truth, not the DB.

**Current behavior:**
- Doctor detects: DB has 355 issues, JSONL has 292
- Recommends: 'bd sync --import-only'
- User runs it: Returns '0 created, 0 updated' (no-op, because JSONL hasn't changed)
- User is stuck

**Root cause:**
The doctor fix is one-directional (JSONL→DB) when it should be bidirectional. If DB has MORE issues, they haven't been exported yet - the fix should be 'bd export' (DB→JSONL), not import.

**Desired fix:**
In fix.DBJSONLSync(), detect which has more data:
- If DB > JSONL: Run 'bd export' to sync JSONL (since DB is the working copy)
- If JSONL > DB: Run 'bd sync --import-only' to import (JSONL is source of truth)
- If equal but timestamps differ: Detect based on file mtime

This makes 'bd doctor --fix' actually fix the problem instead of being a no-op.


bd activity: Real-time molecule state feed

Implement activity feed command for watching molecule state transitions.

## Commands
```bash
bd activity --follow              # Real-time streaming
bd activity --mol <id>            # Activity for specific molecule
bd activity --since 5m            # Last 5 minutes
bd activity --type step           # Only step transitions
```

## Output Format
```
[14:32:01] ✓ patrol-x7k.inbox-check completed
[14:32:03] ✓ patrol-x7k.check-refinery completed
[14:32:08] + patrol-x7k.arm-ace bonded (5 steps)
[14:32:09] → patrol-x7k.arm-ace.capture in_progress
[14:32:10] ✓ patrol-x7k.arm-ace.capture completed
[14:32:14] ✓ patrol-x7k.arm-ace.decide completed (action: nudge-1)
[14:32:17] ✓ patrol-x7k.arm-ace COMPLETE
[14:32:23] ✓ patrol-x7k SQUASHED → digest-x7k
```

## Event Types
- `+` bonded - New molecule/step created
- `→` in_progress - Step started
- `✓` completed - Step/molecule finished
- `✗` failed - Step failed
- `⊘` burned - Wisp discarded
- `◉` squashed - Wisp condensed to digest

## Implementation
- Could use SQLite triggers or polling
- --follow uses OS file watching or polling
- Filter by mol ID, type, time range


Rename 'bond' to 'spawn' for instantiation

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

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


Update Homebrew formula

Update the Homebrew tap with new version:

```bash
./scripts/update-homebrew.sh 0.33.2
```

This script waits for GitHub Actions to complete (~5 min), then updates the formula with new SHA256 hashes.


Improve test coverage

The test suite reports less than 45% code coverage. Identify the specific uncovered areas of the codebase, including modules, functions, or features. Rank them by potential impact on system reliability and business value, from most to least, and provide actionable recommendations for improving coverage in each area.


Add creator field to Issue struct

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


Final release verification

Verify all release artifacts are accessible:

- [ ] `bd --version` shows 0.30.4
- [ ] `bd version --daemon` shows 0.30.4
- [ ] GitHub release exists: https://github.com/steveyegge/beads/releases/tag/v0.30.4
- [ ] `brew upgrade beads && bd --version` shows 0.30.4 (if using Homebrew)
- [ ] `pip show beads-mcp` shows 0.30.4
- [ ] npm package available at 0.30.4
- [ ] `bd info --whats-new` shows 0.30.4 notes

Run final checks:
```bash
bd --version
bd version --daemon
pip show beads-mcp | grep Version
bd info --whats-new
```


False positive detection by Kaspersky Antivirus (Trojan)

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


Version Bump: test-squash

Release checklist for version test-squash. This molecule ensures all release steps are completed properly.


Gate: timer:1m


Migrate sort.Slice to slices.SortFunc

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

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

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

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

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


GH#409: bd init uses issues.jsonl but docs say beads.jsonl

bd init creates config referencing issues.jsonl but README/docs reference beads.jsonl as canonical. Standardize naming. See GitHub issue #409.


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)


bd doctor: detect issues referenced in commits but still open

Add a doctor check that finds 'orphaned' issues - ones referenced in git commit messages (e.g., 'fix bug (bd-xxx)') but still marked as open in beads.

**Detection logic:**
1. Get all open issue IDs from beads
2. Parse git log for issue ID references matching pattern \(prefix-[a-z0-9.]+\)
3. Report issues that appear in commits but are still open

**Output:**
⚠ Warning: N issues referenced in commits but still open
  bd-xxx: 'Issue title' (commit abc123)
  bd-yyy: 'Issue title' (commit def456)

  These may be implemented but not closed. Run 'bd show <id>' to check.

**Implementation:**
- Add check to doctor/checks.go
- Use git log parsing (already have git utilities)
- Match against configured issue_prefix


bd wisp gc command

Add bd wisp gc command to garbage collect orphaned wisps.

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

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

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

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


Add composite index (issue_id, type) on dependencies table

GetBlockedIssues uses EXISTS clauses that filter by issue_id AND type together.

**Query pattern (ready.go:427-429):**
```sql
EXISTS (
  SELECT 1 FROM dependencies d2
  WHERE d2.issue_id = i.id AND d2.type = 'blocks'
)
```

**Problem:** Only idx_dependencies_issue exists. SQLite must filter type after index lookup.

**Solution:** Add migration:
```sql
CREATE INDEX IF NOT EXISTS idx_dependencies_issue_type ON dependencies(issue_id, type);
```

**Note:** This complements the existing idx_dependencies_depends_on_type for the reverse direction.


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

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

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

Current coverage: 27.3%
Target coverage: 60%


Test body-file

This is a test description from a file.

It has multiple lines.


Spawn-time bonding: --attach flag

Add --attach flag to bd mol spawn for on-the-fly composition.

COMMAND: bd mol spawn proto-feature --attach proto-docs --attach proto-testing

BEHAVIOR:
- Spawn the primary proto as normal
- For each --attach: spawn that proto and wire to primary
- Attachments become children of primary's root epic
- Dependencies wired based on bond type (default: sequential)

FLAGS:
- --attach PROTO: Attach a proto (can repeat)
- --attach-type TYPE: sequential (default) or parallel for all attachments
- --after ISSUE: Attachment point for attached protos

VARIABLE HANDLING:
- All attached protos share variable namespace
- Warn on variable name conflicts
- All --var flags apply to all protos

DESIGN NOTE: This is syntactic sugar. Equivalent to:
  bd mol spawn proto-A
  bd mol bond $new_epic_id proto-B

Keeping as separate task because it's a common UX pattern worth optimizing.


Implement content-first idempotent import

**Summary:** Refactored issue import to be content-first and idempotent, ensuring consistent data synchronization across multiple import rounds by prioritizing content hash matching over ID-based updates.

**Key Decisions:**
- Implement content hash as primary matching mechanism
- Create global collision resolution algorithm
- Ensure importing same data multiple times results in no-op

**Resolution:** The new import strategy guarantees predictable convergence across distributed systems, solving rename detection and collision handling while maintaining data integrity during multi-stage imports.


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


Auto-migrate database on CLI version bump

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


refinery Handoff

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


Upgrade beads-mcp to 0.33.2

Upgrade the MCP server via pip:

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

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


Tier 3: AI-powered squash summarization

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

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

### API

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

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

### Gas Town Integration Pattern

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

### Why This Design

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

### Implementation Status

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

### Acceptance Criteria

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


Phase 2.5: TOON-based daemon sync

Implement TOON-native daemon sync (replaces JSONL sync machinery).

## Overview
Daemon sync is the final integration point. Replace export/import/merge machinery with TOON-native sync, building on deletion tracking (2.3) and merge optimization (2.4).

## Required Work

### 2.5.1 TOON-based Daemon Sync
- [ ] Understand current JSONL sync machinery (export.go, import.go, merge.go)
- [ ] Replace export step with TOON encoding (EncodeTOON)
- [ ] Replace import step with TOON decoding (DecodeTOON)
- [ ] Replace merge step with TOON-aware 3-way merge
- [ ] Update daemon auto-sync to read/write TOON
- [ ] Verify 5-second debounce still works

### 2.5.2 Deletion Sync Integration
- [ ] Load deletions.toon during import phase
- [ ] Apply deletions after merging issues
- [ ] Ensure deletion TTL respects daemon schedule

### 2.5.3 Testing
- [ ] Unit tests for daemon sync with TOON
- [ ] Integration tests with actual daemon operations
- [ ] Multi-clone sync scenarios with concurrent edits
- [ ] Performance comparison with JSONL sync
- [ ] Long-running daemon stability tests

## Success Criteria
- Daemon reads/writes TOON format (not JSONL)
- Sync latency comparable to JSONL (<100ms)
- All 70+ tests passing
- bdt commands work seamlessly with daemon
- Multi-clone sync scenarios work correctly


Run bump-version.sh 0.32.1

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


bd mol burn deletes ephemeral without digest

Update bd mol burn to handle ephemeral molecules.

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

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

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


Test Molecule

A test molecule