Upgrade beads-mcp to {{version}}

Upgrade the MCP server via pip:

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

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


Workaround for FastMCP outputSchema bug in Claude Code

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

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

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

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


Merge: jasper-1767142011373

branch: polecat/jasper-1767142011373
target: main
source_issue: jasper-1767142011373
rig: beads


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)


Merge: basalt-mjtme50i

branch: polecat/basalt-mjtme50i
target: main
source_issue: basalt-mjtme50i
rig: beads


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

## Merged PRs Summary

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

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

---

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

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

---

## Overall Assessment

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

No critical bugs or design issues found.

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


Polecat sessions terminate unexpectedly during work

During swarm bd-784c, polecat sessions (Toast, Nux) terminated mid-task without completing their work.

**Observed:**
- Polecats were actively working (edits in progress, tests running)
- Sessions suddenly showed 'not running' in gt polecat status
- tmux sessions existed but were empty/reset
- Work was partially complete (some commits made, issue still open)

**Timeline example:**
1. Toast working on bd-1tkd, making edits
2. Check status 30s later - session empty, state shows 'idle'
3. Had to re-sling to restart

**Impact:**
- Lost work progress
- Required manual intervention to restart
- Unclear if work was saved/committed

**Possible causes:**
- Session timeout?
- Claude Code crash?
- Resource limit?

**Suggestion:**
- Add session health monitoring
- Auto-restart on unexpected termination
- Log session exit reasons


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.


Integrate migration detection into bd doctor

Multiple migration commands (migrate_hash_ids.go, migrate_issues.go, migrate_sync.go, migrate_tombstones.go, migrate.go) have TODOs suggesting integration into 'bd doctor' for automatic migration detection. This would allow bd doctor to detect when migrations are needed and suggest running them.

Files:
- cmd/bd/migrate_hash_ids.go:24
- cmd/bd/migrate_issues.go:15
- cmd/bd/migrate_sync.go:16
- cmd/bd/migrate_tombstones.go:72
- cmd/bd/migrate.go:22


Commit release

Stage and commit all version changes.

```bash
git add -A
git commit -m "chore: Bump version to 0.99.0-test"
```

Review the commit to ensure all expected files are included.


Code smell: Storage interface has 50+ methods - consider interface segregation

The Storage interface in internal/storage/storage.go has 50+ methods covering:

- Issue CRUD (CreateIssue, GetIssue, UpdateIssue, DeleteIssue, SearchIssues)
- Dependencies (AddDependency, RemoveDependency, GetDependencies, GetDependents, etc.)
- Labels (AddLabel, RemoveLabel, GetLabels, etc.)
- Ready work (GetReadyWork, GetBlockedIssues, etc.)
- Events/Comments
- Statistics
- Dirty tracking
- Export hash tracking
- ID generation
- Config/Metadata
- Transactions
- Lifecycle

While this is a valid design for a storage abstraction, consider the Interface Segregation Principle:

1. Split into smaller interfaces: IssueStorage, DependencyStorage, LabelStorage, etc.
2. Compose them: `type Storage interface { IssueStorage; DependencyStorage; ... }`
3. This allows more focused testing and clearer API contracts

Location: internal/storage/storage.go:79-197


Merge: bd-likt

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


Remove bd mol spawn - use pour/wisp only

Remove the spawn command from bd mol. Replace with:

- bd pour <proto> - Instantiate as mol (liquid, persistent)
- bd wisp <proto> - Instantiate as wisp (vapor, ephemeral)

Rationale:
- 'spawn' doesn't fit the chemistry metaphor
- Two phase transitions (pour/wisp) are clearer than one command with flags
- Avoids confusion about defaults

Implementation:
1. Add bd pour command (currently bd mol spawn --pour)
2. Add bd wisp command (currently bd mol spawn without --pour)
3. Remove bd mol spawn
4. Update all docs/tests

Gas Town tracking: gt-ingm.6


Review changes since last release

Understand what's being released.

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

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


bd mol squash handles wisp→digest

Update bd mol squash to handle ephemeral molecules.

## Behavior for Ephemeral Molecules
1. Delete wisp from .beads-ephemeral/
2. Create digest issue in .beads/ (permanent)
3. Digest has type:digest and squashed_from field

## Digest Format
```json
{
  "id": "<parent>.digest-NNN",
  "type": "digest",
  "title": "<proto> cycle @ <timestamp>",
  "description": "<summary from --summary flag>",
  "parent": "<proto-id>",
  "squashed_from": "<wisp-id>"
}
```

## Implementation
- Detect if molecule is ephemeral (check storage location or flag)
- Delete from ephemeral store
- Create digest in permanent store
- Return digest ID

## Testing
- Test squash of ephemeral mol creates digest
- Test wisp is deleted after squash
- Test digest is queryable


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 mol advance: Step through molecule execution

Implement bd mol advance for stepping through molecules.

## Command

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

## Behavior

1. Load molecule state from .beads/molecules/<mol-id>.state.yaml
2. Find current step
3. Mark current step completed
4. Find next available step (respecting needs/deps)
5. Update state file
6. Output next step info (or COMPLETE if done)

## Step State Machine

```
pending → in_progress → completed
```

When advancing:
- Current step: in_progress → completed
- Next step: pending → in_progress

## Output

```bash
$ bd mol advance mol-deacon-patrol
✓ Step inbox-check completed
→ Next step: spawn-work

Title: Spawn polecat for ready work
Description: ...
```

Or if complete:
```bash
$ bd mol advance mol-deacon-patrol
✓ Step self-inspect completed
✓ Molecule COMPLETE

Run bd mol reset to loop, or gt handoff to cycle.
```

## Files

- cmd/bd/mol_advance.go
- internal/mol/state.go


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.


Upgrade local Homebrew installation

Upgrade bd via Homebrew:

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

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

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

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


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.


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

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


Add daemon RPC support for gate commands.

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

## Implementation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### 3. Register methods in daemon

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

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

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

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

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

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

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

## Files to Modify

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

## Testing

```bash
# Start daemon
bd daemon start

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

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

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


Test parent task


Edge Schema Consolidation: Unify all edges in dependencies table

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

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

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


CLI cleanup: Rename bd mol catalog to bd formula list

## Problem
`bd mol catalog` shows formulas, not molecules. This is confusing.

Current state:
- `bd formula list` - lists formulas
- `bd mol catalog` - ALSO lists formulas (duplicate!)

The "mol" namespace should be for molecules (instantiated work), not formulas (templates).

## Proposed Change
- Remove `bd mol catalog`
- Keep `bd formula list` as the canonical command
- Update docs to use `bd formula list`

## Alternative
If we want a "catalog" concept separate from "list", make it `bd formula catalog`.


Implement backup functionality for reset

Add backup capability that can be used by reset command.

## Functionality
- Copy .beads/ to .beads-backup-{timestamp}/
- Timestamp format: YYYYMMDD-HHMMSS
- Preserve file permissions
- Return backup path for user feedback

## Location
`internal/reset/backup.go` - keep with reset package for now (YAGNI)

## Interface
```go
func CreateBackup(beadsDir string) (backupPath string, err error)
```

## Notes
- Simple recursive file copy, no compression needed
- Error if backup dir already exists (unlikely with timestamp)
- Backup directories SHOULD be gitignored
- Add `.beads-backup-*/` pattern to .beads/.gitignore template in doctor package
- Consider: ListBackups() for future `bd backup list` command (not for this PR)


Push commit and tag to origin

```bash
git push origin main
git push origin v0.30.4
```

This triggers GitHub Actions:
- GoReleaser build
- PyPI publish
- npm publish

```verify
git ls-remote origin refs/tags/v0.30.4 | grep -q 'v0.30.4'
```


Update go install bd to {{version}}

Rebuild and install bd to ~/go/bin:

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

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


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.


Implement Gate evaluation in bd cook

## Summary
Implement async wait conditions (gates) for formula steps.

## Location
- Schema: `internal/formula/types.go:185-187, 209-220`
- Implement in: `internal/formula/cook.go` or new `gate.go`

## Current Schema
```go
// Gate defines an async wait condition for this step.
// TODO(bd-7zka): Not yet implemented in bd cook. Will integrate with bd-udsi gates.
Gate *Gate `json:"gate,omitempty"`

// Gate defines an async wait condition (integrates with bd-udsi).
// TODO(bd-7zka): Not yet implemented in bd cook. Schema defined for future use.
type Gate struct {
    Type    string `json:"type"`     // gh:run, gh:pr, timer, human, mail
    ID      string `json:"id,omitempty"`
    Timeout string `json:"timeout,omitempty"`
}
```

## Implementation

### Phase 1: Gate Schema to Issue Metadata
When cooking a step with a gate, store gate info in the issue:
```go
if step.Gate != nil {
    issue.Gate = step.Gate  // Need to add Gate field to Issue type
    issue.Status = "gated"  // New status? Or use blocked?
}
```

### Phase 2: Gate Evaluation (separate from cook)
Gate types to support:
- `gh:run` - Wait for GitHub Actions workflow
- `gh:pr` - Wait for PR state (merged, closed)
- `timer` - Wait for duration
- `human` - Wait for manual approval
- `mail` - Wait for mail response

This likely needs a separate `bd gate` command or daemon integration.

## Design Decision Needed
Should gates be:
1. Stored as issue metadata and evaluated separately?
2. Processed during `bd cook` (blocking)?
3. Create blocked dependency until gate clears?

Recommend option 1: Store gate info, let separate process handle evaluation.

## Example Usage
```yaml
steps:
  - title: "Wait for CI"
    gate:
      type: gh:run
      id: "test.yml"
      timeout: "1h"

  - title: "Deploy after approval"
    gate:
      type: human
      timeout: "24h"
```

## Dependencies
- May need coordination with bd-udsi (gate system)
- Requires Issue type extension for gate metadata

## Acceptance Criteria
- [x] Gate info preserved from formula to issue
- [x] Clear error if gate type is unknown
- [x] Timeout parsed and stored
- [x] Gate evaluation mechanism defined (even if not fully implemented)