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


Crew worker emma in beads - human-managed persistent workspace.

role_type: crew
rig: beads
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


Clean repo cruft: bd_new, @AGENTS.md, state.json

Remove tracked cruft:
- git rm bd_new (31MB old binary)
- git rm @AGENTS.md (duplicate, was 'removed' but still tracked)
- Add state.json to .gitignore, git rm --cached state.json
- Consider moving history/EPHEMERAL_MOLECULES_DESIGN.md to docs/ or removing


Remove legacy autoflush code paths

## Problem

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

## Locations

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

## Current Behavior

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

## Proposed Fix

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

## Risk

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


bd doctor --fix destroys child→parent dependencies without confirmation

GH#740: User reports that `bd doctor --fix` removed their intentional child→parent dependencies, calling them an 'anti-pattern'.

The fix in `cmd/bd/doctor/fix/validation.go` (ChildParentDependencies function) automatically deletes dependencies where a child issue depends on its parent epic.

**Problem**: This assumes all such dependencies are mistakes, but users may intentionally want children blocked by their parent (e.g., 'don't start subtasks until parent is fully scoped').

**Solution options**:
1. Remove this fix entirely (let users manage their own deps)
2. Require explicit opt-in flag like `--fix-child-parent`
3. Add confirmation prompt before destructive action
4. Add `--dry-run` to preview what would be removed
5. Only warn, never auto-fix

**Files involved**:
- `cmd/bd/doctor/validation.go:311-382` (check)
- `cmd/bd/doctor/fix/validation.go:165-227` (fix)


Remove Storage Cache from Server Struct

Eliminate cache fields and use s.storage directly


Bump version in all files

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


Test prefix bd-


BondRef.ProtoID field name is misleading for mol+mol bonds

In bondMolMol, the BondRef.ProtoID field is used to store molecule IDs:

```go
BondedFrom: append(molA.BondedFrom, types.BondRef{
    ProtoID:   molB.ID,  // This is a molecule, not a proto
    ...
})
```

This is semantically confusing since ProtoID suggests it should only hold proto references.

**Options:**
1. Rename ProtoID to SourceID (breaking change, needs migration)
2. Add documentation clarifying ProtoID can hold molecule IDs in bond context
3. Leave as-is, accept the naming is imprecise

Low priority since it's just naming, not functionality.


Read operations should not modify database file

Read-only operations (bd list, bd ready, bd show) modify the database file. Every open sets WAL pragma, runs schema/migrations, and checkpoints on close. Need sqlite.NewReadOnly() that opens with mode=ro, skips WAL/schema/migrations, and skips checkpoint. See GH#804.


Merge: quartz-mjxey2w1

branch: polecat/quartz-mjxey2w1
target: main
source_issue: quartz-mjxey2w1
rig: beads
agent_bead: bd-beads-polecat-quartz


Test pinned issue


Review PR #512: clarify bd ready docs

Review and merge PR #512 from aspiers. This PR clarifies what bd ready does after git pull in README.md. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/512


Split monolithic sqlite.go into focused files

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

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

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

Effort: 6-8 hours


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: topaz-1767083484329

branch: polecat/topaz-1767083484329
target: main
source_issue: topaz-1767083484329
rig: beads


Work on gt-8tmz.34: Expansion var overrides not implement...

Work on gt-8tmz.34: Expansion var overrides not implemented. The ExpandRule.Vars field exists in internal/formula/types.go but is ignored during expansion in internal/formula/expand.go. Implement: 1) Pass rule.Vars to expandStep in ApplyExpansions, 2) Merge vars with formula defaults (rule.Vars wins), 3) Substitute vars in template placeholders, 4) Add test in expand_test.go. When done, commit and push to main.


beads-release

Beads release workflow - from version bump to verified release.

This formula orchestrates a complete release cycle:
1. Preflight checks (clean git, up to date)
2. Documentation updates (CHANGELOG, info.go)
3. Version bump (all components)
4. Git operations (commit, tag, push)
5. CI verification (GitHub Actions)
6. Artifact verification (GitHub, npm, PyPI)
7. Local installation update
8. Daemon restart

## Usage

```bash
bd wisp create beads-release --var version=0.37.0
```

Or assign to a polecat:
```bash
gt sling beads/polecats/p1 --formula beads-release --var version=0.37.0
```


Move relate/unrelate under dep command

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


Installation tracking: .beads/installed.json

Track installed formulas for update checking and provenance.

## File Format
```json
{
  "mol-code-review": {
    "source": "github.com/anthropics/mol-code-review",
    "version": "v1.2.0",
    "installed_at": "2025-12-25T12:00:00Z",
    "formula_hash": "abc123..."
  },
  "mol-polecat-work": {
    "source": "local",
    "version": "1",
    "installed_at": "2025-12-24T10:00:00Z"
  }
}
```

## Commands That Update This
- `bd mol install` - Adds entry
- `bd mol update` - Updates version/timestamp
- `bd cook` - Adds entry with source:local
- `bd mol uninstall` - Removes entry (new command)

## Usage
```bash
bd mol list --installed    # Shows installed with source/version
bd mol outdated            # Shows formulas with available updates
```

## Sync Behavior
- installed.json is gitignored (local state)
- Only formulas/ directory is synced
- Each clone tracks its own installations


Test suite hangs/never finishes

Running 'go test ./... -count=1' hangs indefinitely. The full test suite never completes, making it difficult to verify changes. Need to investigate which tests are hanging and fix or add timeouts.


Wait for GoReleaser

gh run list --workflow=release.yml - wait for success


Cross-project dependency support

Enable tracking dependencies across project boundaries.

## Mechanism
- Producer: `bd ship <capability>` adds `provides:<capability>` label
- Consumer: `blocked_by: external:<project>:<capability>`
- Resolution: `bd ready` checks external deps via config

## Design Doc
See: gastown/docs/cross-project-deps.md

## Children
- bd-eijl: bd ship command
- bd-om4a: external: prefix in blocked_by
- bd-66w1: external_projects config
- bd-zmmy: bd ready resolution


Update hook templates to 0.30.4

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

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

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


Feature: bd swarm list - discover existing swarm molecules

No command to list existing swarm molecules.

Add `bd swarm list` to show all swarm molecules:

```
bd swarm list

Active swarms:
  bd-xyz: Swarm: Feature X (3/10 complete, 2 active)
  bd-abc: Swarm: Bug fixes (0/5 complete)
```

Could also be achieved via `bd list --mol-type=swarm` but dedicated command would show richer info.


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

## Problem

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

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

## Root Cause

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

## Current Workaround

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

## Proposed Fix

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

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


Run bump-version.sh

Update all component versions atomically.

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

This updates:
- cmd/bd/version.go
- .claude-plugin/*.json
- integrations/beads-mcp/pyproject.toml
- integrations/beads-mcp/src/beads_mcp/__init__.py
- npm-package/package.json
- Hook templates
- README.md
- CHANGELOG.md (adds date)


bd sync fails with orphaned issues and duplicate ID conflict

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

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

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

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

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

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

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


gt mq list shows empty when MRs exist


sync branch not match config

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

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


Verify PyPI package

Confirm PyPI package published.

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

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

Should show 0.99.0-test.


Create version-bump template as native Beads

Migrate the version-bump workflow from YAML to a native Beads template:

1. Create epic with template label: Release {{version}}
2. Create child tasks for each step (update version files, changelog, commit, push, publish)
3. Set up dependencies between tasks
4. Add verification commands in task descriptions

This serves as both migration and validation of the new system.


Code smell: Duplicated IssueDetails struct in show.go (4 definitions)

The IssueDetails struct is defined 4 times within show.go at lines 109, 159, 190, and 372 with identical structure but slightly different JSON tag configurations.

**Problem:**
- Violates DRY principle
- Makes maintenance harder - changes must be applied to multiple locations
- Increases risk of inconsistency between definitions
- Takes up ~50 lines of duplicated code

**Acceptance Criteria:**
- [ ] Extract IssueDetails to a single package-level type
- [ ] Update all 4 locations to use the shared type
- [ ] Ensure JSON output remains consistent
- [ ] Tests pass


GH#522: Add --type flag to bd update command

Add --type flag to bd update for changing issue type (task/epic/bug/feature). Storage layer already supports it. See GitHub issue #522.


Review changes since last release

Understand what's being released.

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

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


Merge: garnet-1767142025508

branch: polecat/garnet-1767142025508
target: main
source_issue: garnet-1767142025508
rig: beads


BondRef data model for compound lineage

Add data model support for tracking compound molecule lineage.

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

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

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

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


Update local installations.

Update local installations.

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

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

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

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

Verify: `bd --version` shows 0.37.0

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


Add content-addressable identity to Issue type

**Summary:** Added content-addressable identity to Issue type by implementing a ContentHash field that generates a unique SHA256 fingerprint based on semantic issue content. This resolves issue identification challenges when multiple system instances create issues with identical IDs but different contents.

**Key Decisions:**
- Use SHA256 for content hashing
- Hash excludes ID and timestamps
- Compute hash automatically at creation/import time
- Add database column for hash storage

**Resolution:** Successfully implemented a deterministic content hashing mechanism that enables reliable issue identification across distributed systems, improving data integrity and collision detection.


Release v{{version}}

Clean release workflow for beads. Variables: version (e.g., 0.37.0), date (YYYY-MM-DD)


Update CHANGELOG.md

Write the [Unreleased] section with all changes for 0.39.0.

Format: Keep a Changelog (https://keepachangelog.com)

Sections:
- ### Added
- ### Changed
- ### Fixed
- ### Documentation

The bump script will stamp the date automatically.


Add versionChanges to info.go

Add entry at TOP of versionChanges in cmd/bd/info.go


test pin


Commit and push release

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


Merge: bd-c7y5

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


Move cook to formula subcommand

## Task
Move `bd cook` → `bd formula cook`

## Implementation

### 1. Update formula.go
- Add `cookCmd` as subcommand of `formulaCmd`
- Import necessary packages from cook.go if not already present

### 2. Update cook.go
- Remove `rootCmd.AddCommand(cookCmd)` from init()
- Keep the command implementation as-is

### 3. Create hidden alias for backwards compatibility
- In main.go or aliases file, add:
  ```go
  aliasCmd := &cobra.Command{
    Use:    "cook",
    Hidden: true,
    Run: func(cmd *cobra.Command, args []string) {
      // Forward to formula cook
    },
  }
  ```

### 4. Update docs
- docs/MOLECULES.md - if `bd cook` mentioned
- CHANGELOG.md - add deprecation notice

## Files to modify
- cmd/bd/formula.go (add subcommand)
- cmd/bd/cook.go (change registration)
- cmd/bd/main.go (add hidden alias)


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.


bd update --parent: allow reparenting issues

**Request:** Add `--parent` flag to `bd update` command.

**Use case:** When renaming/migrating an epic (e.g., gt-oki8p → gt-liftoff), need to reparent child issues to the new epic.

**Current workaround:** None clean - must delete and recreate children, or manually edit JSONL.

**Proposed:**
```bash
bd update gt-j0gx2 --parent=gt-liftoff
```

**Note:** `bd create` already has `--parent` flag, so the semantics are established.


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)


Re: Thread Test 2

Great! Thread is working well.


Update CHANGELOG.md with release notes

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


Add daemon-side delete operation tests

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

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

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


Relocate thanks command to bd info --thanks

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

## Implementation

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

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

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

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


Merge: bd-by0d

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


Push release v{{version}} to remote

Push the commit and tag:

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

Verify on GitHub that the tag appears in releases.


Create release tag

Create annotated git tag.

```bash
git tag -a v0.41.0 -m "Release v0.41.0"
```

Verify: `git tag -l | tail -5`


Rebuild local binary

Build and verify: go build -o bd ./cmd/bd && ./bd version


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


Refactor: Extract shared child-fetching logic in swarm.go

Lines 162-181 (analyzeEpicForSwarm) and 630-649 (getSwarmStatus) have identical logic for fetching epic children via parent-child dependencies.

Extract to a shared helper function like:

```go
func getEpicChildren(ctx context.Context, s StorageInterface, epicID string) ([]*types.Issue, error)
```

This will reduce duplication and ensure consistent behavior between validate/status/create commands.


Crew worker fang in beads - human-managed persistent workspace.

role_type: crew
rig: beads
agent_state: idle
hook_bead: null
role_bead: gt-crew-role
cleanup_status: null


refinery Handoff


Test Child Task


Mega-molecule progress support for Hanoi demo

Beads-side work for million-step molecule monitoring.

## Context
Towers of Hanoi with 20 disks = 1,048,575 steps.
Need efficient progress queries and display guards.

## Requirements
1. bd mol progress command (summary without listing all steps)
2. Display guards in bd mol current (detect large molecules)
3. Efficient child count queries (already indexed)


bd swarm status command

Add bd swarm status to show swarm state derived from beads.

## Context
This is the key command that demonstrates 'discovery over tracking'.
Swarm status is COMPUTED from:
- Epic children statuses
- Dependency structure
- Issue assignments

NOT stored separately. If beads changes, status changes.

## Implementation
1. New command: bd swarm status <swarm-mol-id>
2. Find linked epic from swarm molecule
3. Query all children of epic
4. Compute and display:
   - Ready front (unblocked, open issues)
   - In progress (in_progress status)
   - Completed (closed)
   - Blocked (has unmet dependencies)
   - Progress percentage
   - Coordinator
   - Estimated remaining (based on avg session time)

## Output Format
Ready Front Analysis: gt-epic-xxx

Completed:     ✓ gt-001 (2h ago)
Active:        ⟳ gt-002 [nux], ⟳ gt-003 [toast]
Ready:         ○ gt-004, ○ gt-005
Blocked:       ◌ gt-006 (needs 004, 005)

Progress: 1/6 complete, 2/6 active (17%)

## Reference
~/gt/docs/swarm-architecture.md - 'Ready Front Visualization'

## Acceptance
- bd swarm status <mol> shows accurate state
- State reflects current beads (not cached)
- --json output for programmatic use


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.


bd dep add: Auto-resolve cross-rig IDs using routes.jsonl

Currently, adding a dependency to an issue in another rig requires verbose external reference syntax:

```bash
# This fails - can't resolve bd-* from gastown context
bd dep add gt-xyz bd-abc

# This works but is verbose
bd dep add gt-xyz external:beads:bd-abc
```

The town-level routing (~/gt/.beads/routes.jsonl) already knows how to map prefixes to rigs:
```json
{"prefix": "gt-", "path": "gastown/mayor/rig"}
{"prefix": "bd-", "path": "beads/mayor/rig"}
```

Enhancement: When `bd dep add` encounters an ID with a foreign prefix, it should:
1. Check routes.jsonl for the prefix mapping
2. Auto-resolve to external:<project>:<id> internally
3. Allow the simpler `bd dep add gt-xyz bd-abc` syntax

This would make cross-rig dependencies much more ergonomic.


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).


bd template commands fail with daemon mode

The `bd template show` and `bd template instantiate` commands fail with 'Error loading template: no database connection' when daemon is running.

**Reproduction:**
```bash
bd daemon --start
bd template show bd-qqc  # Error: no database connection
bd template show bd-qqc --no-daemon  # Works
```

**Expected:** Template commands should work with daemon like other commands.

**Workaround:** Use `--no-daemon` flag.

**Location:** Likely in cmd/bd/template.go - daemon RPC path not implemented for template operations.


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


Monitor GoReleaser CI job

Watch the GoReleaser action:
https://github.com/steveyegge/beads/actions/workflows/release.yml

Should complete in ~10 minutes and create:
- GitHub Release with binaries for all platforms
- Checksums and signatures

Check status:
```bash
gh run list --workflow=release.yml -L 1
gh run watch  # to monitor live
```

Verify release exists:
```bash
gh release view v0.30.4
```

```verify
gh release view v0.30.4 --json tagName -q .tagName | grep -q 'v0.30.4'
```


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


Phase 1.1: TOON Library Integration - Add gotoon dependency

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

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

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


Update documentation for CLI consolidation

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

## Files requiring updates

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

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

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

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

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


Consolidate migrate-* commands into migrate subcommands

Move migrate-hash-ids, migrate-issues, migrate-sync, migrate-tombstones under 'migrate' as subcommands. Reduces 5 top-level commands to 1.


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

Implement condition checking for each await type.

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

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

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


bd mol run: Only creates partial children from proto

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

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

All 4 should be instantiated.

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

self-review and request-shutdown are missing.

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

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


Implement Step.Condition evaluation in bd cook

## Summary
Implement conditional step execution based on variable values.

## Location
- Schema: `internal/formula/types.go:177-180`
- Implement in: `internal/formula/cook.go` (or new file `condition.go`)

## Current Schema
```go
// Condition makes this step optional based on a variable.
// Format: "{{var}}" (truthy) or "{{var}} == value".
// TODO(bd-7zka): Not yet implemented in bd cook. Filed as future work.
Condition string `json:"condition,omitempty"`
```

## Implementation

### Parsing
Support two formats:
1. `"{{var}}"` - truthy check (non-empty, non-"false", non-"0")
2. `"{{var}} == value"` - equality check

```go
func evaluateCondition(condition string, vars map[string]string) (bool, error) {
    // Check for equality format
    if strings.Contains(condition, "==") {
        parts := strings.SplitN(condition, "==", 2)
        varExpr := strings.TrimSpace(parts[0])
        expected := strings.TrimSpace(parts[1])

        // Extract variable name from {{var}}
        varName := extractVarName(varExpr)
        actual := vars[varName]
        return actual == expected, nil
    }

    // Truthy check
    varName := extractVarName(condition)
    val := vars[varName]
    return val != "" && val != "false" && val != "0", nil
}
```

### Integration with bd cook
In the step processing loop, check condition before creating issue:
```go
if step.Condition != "" {
    include, err := evaluateCondition(step.Condition, vars)
    if err != nil {
        return fmt.Errorf("invalid condition %q: %w", step.Condition, err)
    }
    if !include {
        continue // Skip this step
    }
}
```

## Example Usage
```yaml
steps:
  - title: "Run integration tests"
    condition: "{{run_integration}}"

  - title: "Deploy to staging"
    condition: "{{environment}} == staging"
```

## Testing
- Test truthy evaluation (empty, "false", "0", valid value)
- Test equality check
- Test invalid condition format
- Test missing variable (should be falsy)

## Acceptance Criteria
- [x] Truthy conditions work
- [x] Equality conditions work
- [x] Steps are skipped when condition is false
- [x] Clear error on malformed condition
- [x] Works with variable substitution


Migration invariants should exclude external dependencies from orphan check

## Summary

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

## Location

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

## Current Code (buggy)

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

## Fix

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

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

## Reproduction

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

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

## Files to Modify

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

## Testing

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

# Sync should succeed
bd sync

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

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


Merge: jade-1767138549967

branch: polecat/jade-1767138549967
target: main
source_issue: jade-1767138549967
rig: beads


Update info.go versionChanges

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

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

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

Focus on workflow-impacting changes agents need to know.


bd prime should mention when beads is redirected

## Problem

Agents running in a redirected clone don't know they're sharing beads with other clones. This can cause confusion when molecules or issues seem to 'appear' or 'disappear'.

## Proposed Solution

When `bd prime` runs and detects a redirect, include it in the output:

```
Beads: /Users/stevey/gt/beads/mayor/rig/.beads
       (redirected from crew/dave - you share issues with other clones)
```

## Why

Visibility over magic. If agents can see the redirect, they can reason about it.

## Related

- bd where command (shows this on demand)
- gt redirect following (ensures gt matches bd behavior)


Witness for beads - monitors polecat health and progress.

role_type: witness
rig: beads
agent_state: idle
hook_bead: null
role_bead: gt-witness-role
cleanup_status: null


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


Code review: bd mol bond implementation

Review the mol bond command implementation before shipping.

Focus areas:
1. runMolBond() - polymorphic dispatch logic correctness
2. bondProtoProto() - compound proto creation, dependency wiring
3. bondProtoMol() / bondMolProto() - spawn and attach logic
4. bondMolMol() - joining molecules, lineage tracking
5. BondRef usage - is lineage tracked correctly?
6. Error handling - are all failure modes covered?
7. Edge cases - what could go wrong?

File: cmd/bd/mol.go (lines 485-859)
Commit: 386b513e


Rename wisp prefix from 'eph' to 'wisp'

The wisp system uses 'eph' as the ID prefix segment (e.g., gt-eph-xxx) but should use 'wisp' (e.g., gt-wisp-xxx) for consistency with terminology.

**Location:** cmd/bd/wisp.go:260-261
```go
// Use "eph" prefix for distinct visual recognition
result, err := spawnMolecule(ctx, store, subgraph, vars, "", actor, true, "eph")
```

**Fix:** Change "eph" to "wisp"

**Also update:**
- main_errors.go:100,107 - detection patterns for ephemeral IDs
- Any tests expecting '-eph-' pattern
- Documentation if it references the prefix

**Migration:** Existing gt-eph-* wisps in databases will continue to work (they have ephemeral=true flag). New wisps will use gt-wisp-* prefix.


Default to YES for git hooks and merge driver installation

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


Cache GetGitDir() for additional optimization

Code review finding from bd-7di fix.

GetGitDir() at line 17 is not cached but is called by:
- GetGitHooksDir()
- GetGitRefsDir()
- GetGitHeadPath()

If any of those are called multiple times, we still spawn extra git processes. Consider adding sync.Once caching similar to the other functions.

File: internal/git/gitdir.go


bd-beads-polecat-obsidian

bd-beads-polecat-obsidian

role_type: polecat
rig: beads
agent_state: spawning
hook_bead: null
role_bead: gt-polecat-role
cleanup_status: null
active_mr: bd-1juu
notification_level: null


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


Run bump-version.sh {{version}}

Run ./scripts/bump-version.sh {{version}} to update version in all files


Test comment display


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

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


Add 'tracks' relation type for convoy tracking

Add a new relation type 'tracks' for convoy → issue relationships.

Unlike 'depends_on':
- Non-blocking (tracked issue doesn't block convoy)
- Cross-prefix capable (convoy in hq-* tracks issues in gt-*, bd-*)
- Supports reverse lookup ('what convoys track this issue?')

Used by convoys to track issues across project chains without creating dependencies.

Related: hq-7h8jx (Convoy System epic in town beads)


Add bd state and bd set-state helper commands

Convenience commands for the labels-as-state pattern documented in LABELS.md.

`bd state <role> <dimension>` - Query the current value of a state dimension
Example: bd state witness-alpha patrol → outputs: active

`bd set-state <role> <dimension>=<value> [--reason "..."]` - Atomically creates an event bead and updates the label
Example: bd set-state witness-alpha patrol=muted --reason "Investigating stuck polecat"

This would combine the manual steps:
1. bd create "<reason>" -t event --parent <role>
2. bd label remove <role> <dimension>:<old-value>
3. bd label add <role> <dimension>:<new-value>

Into a single atomic operation.

Discovered from: bd-31ae


bd mail uses git user.name for sender instead of BEADS_AGENT_NAME

When sending mail via `bd mail send`, the sender field in the stored issue uses git config user.name instead of the BEADS_AGENT_NAME environment variable.

Reproduction:
1. Set BEADS_AGENT_NAME=gastown-alpha
2. Run: bd mail send mayor/ -s 'Test' -m 'Body'
3. Check the issue.jsonl: sender is 'Steve Yegge' (git user.name) not 'gastown-alpha'

Expected: The sender field should use BEADS_AGENT_NAME when set.

This breaks the mail system for multi-agent workflows where agents need to identify themselves by their role (polecat, refinery, etc.) rather than the human user's git identity.

Related: gt mail routing integration with Gas Town


Wisp storage: ephemeral molecule tracking

Implement ephemeral molecule storage for patrol cycles.

## Architecture

Wisps are ephemeral molecules stored in `.beads-wisps/` (gitignored).
When squashed, they create digests in permanent `.beads/`.

**Storage is per-rig, not per-role**: Witness and Refinery share mayor/rig's
`.beads-wisps/` since they execute from that context.

## Design Doc
See: gastown/mayor/rig/docs/wisp-architecture.md

## Key Requirements

1. **Ephemeral storage**: `.beads-wisps/` directory, gitignored
2. **Bond with --wisp**: Creates in wisps instead of permanent
3. **Squash**: Deletes wisp, creates digest in permanent beads
4. **Burn**: Deletes wisp, no digest
5. **Wisp commands**: `bd wisp list`, `bd wisp gc`

## Storage Locations

| Context | Location |
|---------|----------|
| Rig (Deacon, Witness, Refinery) | mayor/rig/.beads-wisps/ |
| Polecat (if used) | polecats/<name>/.beads-wisps/ |

## Children (to be created)
- bd mol bond --wisp flag
- .beads-wisps/ storage backend
- bd mol squash handles wisp to permanent
- bd wisp list command
- bd wisp gc command (orphan cleanup)


Routing bypassed in daemon mode

## Bug

Prefix-based routing (routes.jsonl) only works in direct mode. When the daemon is running, it bypasses routing logic because:

1. Daemon mode resolves IDs via RPC (`daemonClient.ResolveID`)
2. Daemon uses its own store, which only knows about local beads
3. The routing logic in `resolveAndGetIssueWithRouting` is only called in direct mode

## Reproduction

```bash
# With daemon running:
cd ~/gt
bd show gt-1py3y  # Fails - daemon can't find it

# With daemon bypassed:
bd --no-daemon show gt-1py3y  # Works - uses routing
```

## Fix Options

### Option A: Client-side routing detection (Recommended)
Before calling daemon RPC, check if the ID prefix matches a route to a different beads dir. If so, use direct mode with routing for that ID.

```go
// In show.go, before daemon mode:
if daemonClient != nil && needsRouting(id) {
    // Fall back to direct mode for this ID
    result, err := resolveAndGetIssueWithRouting(ctx, store, id)
    ...
}
```

### Option B: Daemon-side routing
Add routing support to the daemon RPC server. More complex - daemon would need to:
- Load routes.jsonl on startup
- Open connections to multiple databases
- Route requests based on ID prefix

### Option C: Hybrid
Daemon returns "not found + prefix hint", client retries with routing.

## Recommendation

Option A is simplest. The client already has routing logic; just use it when we detect a routed prefix.


Tier 4: Auto-squash on molecule completion

Automatically squash molecules when they reach terminal state.

## Integration Points

1. Hook into molecule completion handler
2. Detect when all steps are done/failed
3. Trigger squash automatically

## Config

bd config set mol.auto_squash true  # Default: false
bd config set mol.auto_squash_on_success true  # Only on success
bd config set mol.auto_squash_delay '5m'  # Wait before squash

## Implementation Options

### Option A: Post-Completion Hook
In mol completion handler:
- Check if auto_squash enabled
- Call Squash() after terminal state

### Option B: Git Hook
In .beads/hooks/post-commit:
- bd mol squash --auto

### Option C: Daemon Background Task
- Daemon periodically checks for squashable molecules
- Squashes in background

## Acceptance Criteria

- Completed molecules auto-squash without manual intervention
- Configurable delay before squash
- Option to squash only on success vs always
- Works with both daemon and no-daemon modes


Fix or remove TestFindJSONLPathDefault (issue #356)

Code health review found .test-skip permanently skips TestFindJSONLPathDefault.

The test references issue #356 about wrong JSONL filename expectations (issues.jsonl vs beads.jsonl).

Test file: internal/beads/beads_test.go

The underlying migration from beads.jsonl to issues.jsonl may be complete, so either:
1. Fix the test expectations
2. Remove the test if no longer needed
3. Document why it remains skipped


Code Health Review Dec 2025: Technical Debt Cleanup

Epic grouping technical debt identified in the Dec 16, 2025 code health review.

## Overall Health Grade: B (Solid foundation, needs cleanup)

### P1 (High Priority):
- bd-74w1: Consolidate duplicate path-finding utilities
- [deleted:bd-b6xo]: Remove/fix ClearDirtyIssues() race condition
- [deleted:bd-b3og]: Fix TestImportBugIntegration deadlock

### P2 (Medium Priority):
- bd-05a8: Split large files (doctor.go, sync.go)
- bd-qioh: Standardize error handling patterns
- bd-rgyd: Split queries.go (1586 lines)
- bd-9g1z: Fix/remove TestFindJSONLPathDefault

### P3 (Low Priority):
- bd-ork0: Add comments to 30+ ignored errors
- bd-4nqq: Remove dead test code in info_test.go
- bd-dhza: Reduce global state in main.go

## Key Areas:
1. Code duplication in path utilities
2. Large monolithic files (5 files >1000 lines)
3. Global state (25+ variables, 3 deprecated)
4. Silent error suppression (30+ instances)
5. Test gaps and dead test code
6. Atomicity risks in batch operations


Merge: quartz-1767082908528

branch: polecat/quartz-1767082908528
target: main
source_issue: quartz-1767082908528
rig: beads


Tier 5 (Future): JSONL archive rotation

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

## Design

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

## Commands

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

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

## Config

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

## Considerations

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

## Priority

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


gt mail send: support reading message body from stdin

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

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

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

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


Add 'rm' as alias for 'remove' in bd dep subcommand

Cobra supports command aliases. Add 'rm' as a short alias for 'remove' in the dep subcommand for convenience.

Example: bd dep rm issue1 issue2 should work the same as bd dep remove issue1 issue2

Implementation: Add Aliases: []string{"rm"} to the depRemoveCmd definition.


Integrate detect-pollution into bd doctor

## Task
Move `bd detect-pollution` → `bd doctor --check=pollution`

## Current state
- detect-pollution already shows deprecation hint pointing to doctor
- Doctor command exists with multiple checks

## Implementation

### 1. Update doctor.go
- Add pollution check to the doctor checks
- Add `--check=pollution` flag option
- Integrate detect-pollution logic

### 2. Update detect_pollution.go
- Mark as hidden (deprecated)
- Forward to doctor --check=pollution
- Keep for one release cycle

### 3. Update docs
- Remove detect-pollution from any command lists
- Update doctor docs to include pollution check

## Files to modify
- cmd/bd/doctor.go
- cmd/bd/detect_pollution.go


test hash length


Pinned issues: persistent context markers

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

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

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


Merge: obsidian-1767106243818

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


Code smell: 20+ global variables in main.go

main.go lines 63-103 have over 20 global variables managing command-line state, daemon connection, auto-flush, auto-import, and error handling.

**Globals include:**
dbPath, actor, store, jsonOutput, daemonStatus, daemonClient, noDaemon, rootCtx, rootCancel, autoFlushEnabled, flushMutex, storeMutex, storeActive, flushFailureCount, lastFlushError, flushManager, hookRunner, skipFinalFlush, autoImportEnabled, versionUpgradeDetected, previousVersion, upgradeAcknowledged

**Problem:**
- Global state makes testing difficult
- Race conditions possible between goroutines
- Hard to track which functions modify which globals

**Acceptance Criteria:**
- [ ] Create CommandContext struct to hold runtime state
- [ ] Pass context through command execution
- [ ] Reduce global count to essential minimum
- [ ] Tests pass


Test Parent Epic


Unify template.go and mol.go under bd mol

Consolidate the two DAG-template systems into one under the mol command. mol.go (on rictus branch) has the right UX (catalog/show/bond), template.go has the mechanics. Merge them, deprecate bd template commands.


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

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

Current coverage: 23.9%
Target coverage: 50%


Restart beads daemon

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

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

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

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


Fix agent ID parser for hyphenated rig names (GH#868)

Agent ID format <prefix>-<rig>-<role>-<name> fails when rig contains hyphens. Fix: parse from ends - prefix first, name last, role second-to-last (must be crew/polecat/witness/etc), rig is everything in between.


Release v{{version}}

SUPERSEDED by bd-7bs4. Use the new proto for releases.


Update .claude-plugin/plugin.json to 0.30.4

Update version field in .claude-plugin/plugin.json:
```json
"version": "0.30.4"
```

```verify
jq -e '.version == "0.30.4"' .claude-plugin/plugin.json
```


Update CHANGELOG.md

Write the [Unreleased] section with all changes for 0.99.0-test.

Format: Keep a Changelog (https://keepachangelog.com)

Sections:
- ### Added
- ### Changed
- ### Fixed
- ### Documentation

The bump script will stamp the date automatically.