Investigate incremental blocked_issues_cache updates at scale

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

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

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

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

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

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


Create release tag

Create annotated git tag.

```bash
git tag -a v0.99.0-test -m "Release v0.99.0-test"
```

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


GetBlockedIssues shows external deps as blocking even when satisfied

GetBlockedIssues (ready.go:385-493) shows external:* refs in the blocked_by list but doesn't check if they're actually satisfied using CheckExternalDep.

This can be confusing - an issue shows as blocked by external:project:capability even if that capability has been shipped (closed issue with provides: label exists).

Options:
1. Call CheckExternalDep for each external ref and filter satisfied ones from blocked_by
2. Add a note in output indicating external deps need lazy resolution
3. Document this is expected behavior (bd blocked shows all deps, bd ready shows resolved state)

Related: GetReadyWork correctly filters by external deps, but GetBlockedIssues doesn't.


Update Homebrew formula

./scripts/update-homebrew.sh {{version}}


Cross-rig external dependency support

Support dependencies on issues in other rigs/repos.

## Use Case

Gas Town issues often depend on Beads issues (and vice versa). Currently we use labels like `external:beads/bd-xxx` as documentation, but:
- `bd blocked` doesn't recognize external deps
- `bd ready` doesn't filter them out
- No way to query cross-rig status

## Proposed UX

### Adding external deps
```bash
# New syntax for bd dep add
bd dep add gt-a07f external:beads:bd-kwjh

# Or maybe cleaner
bd dep add gt-a07f --external beads:bd-kwjh
```

### Showing blocked status
```bash
bd blocked
# → gt-a07f blocked by external:beads:bd-kwjh (unverified)

# With optional cross-rig query
bd blocked --resolve-external
# → gt-a07f blocked by external:beads:bd-kwjh (closed) ← unblocked!
```

### Storage
Could use:
- Special dependency type: `type: external`
- Label convention: `external:rig:id`
- New field: `external_deps: ["beads:bd-kwjh"]`

## Implementation Notes

Cross-rig queries would need:
- Known rig locations (config or discovery)
- Read-only beads access to external rigs
- Caching to avoid repeated queries

For MVP, just recognizing external deps and marking them as 'unverified' blockers would be valuable.


GH#519: bd sync fails when sync.branch is currently checked-out branch


Introduce slog for structured daemon logging.

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

This produces unstructured, hard-to-parse logs.

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

## Implementation

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

```go
package daemon

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

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

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

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

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

    slog.SetDefault(slog.New(handler))

    return cleanup, nil
}
```

### 2. Add log level flag

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

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

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

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

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

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

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

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

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

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

### 4. Files to Update

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

### 5. Log output examples

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

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

## Migration Strategy

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

## Testing

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

# Check logs
bd daemons logs . | head -20

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

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


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.


Wait for CI

Monitor GitHub Actions for release completion.

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

Expected time: 5-10 minutes

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


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


Test hooked styling


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


Run bump-version.sh

Update all component versions atomically.

```bash
./scripts/bump-version.sh 0.41.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)


Implement bd pin/unpin commands

Add 'bd pin <id>' and 'bd unpin <id>' commands to toggle the pinned status of issues. Should support multiple IDs like other bd commands.


bd mol run: Support proto lookup by title

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

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

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

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

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

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


test hash length


Code Review: PR #481 - Context Engineering Optimizations

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

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

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

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

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


Run tests

TMPDIR=/tmp go test -short ./...


bd cook: Compile formula to proto

Implement the 'bd cook' command that transforms formulas into protos:

Usage: bd cook <formula-file> [options]
       bd cook .beads/formulas/*.formula.yaml

Process:
1. Parse formula YAML (uses formula parser)
2. Resolve extends (formula inheritance)
3. Expand macros (expansion rules)
4. Apply aspects (cross-cutting concerns)
5. Generate flat proto bead

Output:
- Creates proto bead in .beads/ with mol-prefix
- Proto has all steps pre-expanded
- Proto is ready for pour/wisp instantiation

Example:
  bd cook .beads/formulas/mol-deacon-patrol.formula.yaml
  → Creates mol-deacon-patrol proto bead

Gas Town tracking: gt-8tmz.13


Add close_reason field to CLI schema and documentation

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

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

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

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


Merge: quartz-1767106247671

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


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


Merge: bd-au0.7

branch: polecat/dementus
target: main
source_issue: bd-au0.7
rig: beads


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

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

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

## Functions Needing Tests

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

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

## Implementation Guide

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

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

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

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

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

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


Mol Mall: Formula marketplace using GitHub as backend

Create a marketplace for sharing molecule formulas using GitHub repos as the hosting backend.

## Why GitHub?

GitHub solves multiple problems at once:
- **Hosting**: Raw file URLs for formula.json
- **Versioning**: Git tags (v1.0.0, v1.2.0)
- **Auth**: GitHub tokens for private formulas
- **Discovery**: GitHub search, topics, stars
- **Collaboration**: PRs for contributions, issues for bugs
- **Organizations**: Natural scoping (@anthropic/, @gastown/)

## URL Scheme

```bash
# Direct GitHub URL
bd mol install github.com/anthropics/mol-code-review

# With version tag
bd mol install github.com/anthropics/mol-code-review@v1.2.0

# Shorthand (via registry lookup)
bd mol install @anthropic/mol-code-review
```

## Architecture

### Distributed Model (like Go modules)
Each formula lives in its own repo:
```
github.com/anthropics/mol-code-review/
├── formula.json      # The formula
├── README.md         # Documentation
└── CHANGELOG.md      # Version history
```

### Optional Registry (for discovery)
```
github.com/anthropics/mol-mall/
└── registry.json     # Index pointing to formula repos
```

## Child Issues

1. `bd mol export` - Proto → Formula file
2. `bd mol promote` - Distilled proto → Catalog proto (one step)
3. `bd mol install` - GitHub → Local proto
4. `bd mol update` - Check for newer versions
5. `bd mol search` - Find formulas (GitHub API)
6. `bd mol publish` - Push formula to GitHub repo
7. Formula versioning - Version field + git tag integration
8. Installation tracking - .beads/installed.json

## ID Namespace Design

| Entity | ID Format | Example |
|--------|-----------|---------|
| Formula (GitHub) | `github.com/org/repo` | `github.com/anthropics/mol-code-review` |
| Catalog proto (local) | `mol-name` | `mol-code-review` |
| Distilled proto | `<db>-proto-xxx` | `bd-proto-a7f` |
| Poured instance | `<db>-mol-xxx` | `gt-mol-b8c` |
| Wisp instance | `<db>-wisp-xxx` | `hq-wisp-d9e` |


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`


Run bump-version.sh test-squash

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


Add --pinned and --no-pinned flags to bd list

Add filtering flags to bd list: --pinned shows only pinned issues, --no-pinned excludes pinned issues. Default behavior shows all issues with a pin indicator.


Push version bump to GitHub

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


bd mol burn should work on mols, not just wisps

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

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

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

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

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


Remove deprecated daemon logger function

In cmd/bd/daemon_logger.go:131, there's a TODO to remove a deprecated function once all callers are updated to use the new signature. Need to audit callers and clean up.

File: cmd/bd/daemon_logger.go:131


bd repair command: fix orphaned foreign key refs


CLI API Audit for OSS Launch

Comprehensive CLI API audit before OSS launch.

## Tasks
1. Review gt command groupings - propose consolidation
2. Review bd command groupings
3. Document gt vs bd ownership boundaries
4. Identify naming inconsistencies
5. Document flag vs subcommand criteria

## Context
- Pre-OSS launch cleanup
- Goal: clean, consistent, discoverable CLI surface


Add 0.33.2 to versionChanges in info.go

Add new entry at the TOP of versionChanges array in cmd/bd/info.go:

```go
{
    Version: "0.33.2",
    Date:    "2025-12-21",
    Changes: []string{
        // Add notable changes here
    },
},
```

Copy changes from CHANGELOG.md [Unreleased] section.


Gate await fields cleared by auto-flush/auto-import cycle

## Problem

When creating a gate with `--await` fields (e.g., `gh:run:123`), the await_type and await_id are initially stored correctly in the SQLite database. However, they get cleared (set to empty strings) during subsequent bd commands due to the auto-flush/auto-import cycle.

## Reproduction Steps

1. Create a gate with await:
   ```
   bd gate create --molecule test-mol --await gh:run:20517738002
   ```
   Output shows correct `await_type: gh:run, await_id: 20517738002`

2. Check database - fields are present

3. Run any bd command (e.g., `bd list`)

4. Check database again - await_type and await_id are now empty

## Root Cause

Gates are wisps (ephemeral issues). During the auto-flush cycle:
1. Auto-flush exports issues to JSONL (filtering out wisps)
2. Auto-import reads JSONL and updates DB rows
3. The update clears fields that weren't in the JSONL (including await fields)

## Workaround

Using `--no-auto-import --no-auto-flush` flags preserves the fields, but this isn't practical for normal use.

## Impact

GitHub gate evaluation (`bd gate eval`) cannot work because by the time eval runs, the await fields have been cleared.

## Suggested Fix

Either:
1. Don't clear await fields during auto-import if they weren't in the source
2. Store wisp fields separately from the main JSONL export
3. Include wisps in export but filter them at sync time


Clean up orphaned child issues (bd-cb64c226.*, bd-cbed9619.*)

## Problem

Every bd command shows warnings about 12 orphaned child issues:
- bd-cb64c226.1, .6, .8, .9, .10, .12, .13
- bd-cbed9619.1, .2, .3, .4, .5

These are hierarchical IDs (parent.child format) where the parent issues no longer exist.

## Impact

- Clutters output of every bd command
- Confusing for users
- Indicates incomplete cleanup of deleted parent issues

## Proposed Solution

1. Delete the orphaned issues since their parents no longer exist:
   ```bash
   bd delete bd-cb64c226.1 bd-cb64c226.6 bd-cb64c226.8 ...
   ```

2. Or convert them to top-level issues if they contain useful content

## Investigation Needed

- What were the parent issues bd-cb64c226 and bd-cbed9619?
- Why were they deleted without their children?
- Should bd delete cascade to children automatically?


Update docs for import bug fix

Update AGENTS.md, README.md, TROUBLESHOOTING.md with import.orphan_handling config documentation. Document resurrection behavior, tombstones, config modes. Add troubleshooting section for import failures with deleted parents.


Update documentation for template system

Update AGENTS.md and help text to document the new template system:

- How to create a template (epic + template label + child issues)
- How to define variables (just use {{name}} placeholders)
- How to instantiate (bd template instantiate)
- Migration from YAML workflows (if any users had custom ones)


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.


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.


Test child task


Code smell: Duplicated issue type switch in cook.go

The same switch statement for mapping step type to IssueType appears twice in cook.go:

Location 1 (line 425):
```go
switch step.Type {
case "task":
    issueType = types.TypeTask
case "bug":
    issueType = types.TypeBug
// ...
}
```

Location 2 (line 764): Identical switch statement.

Should extract to a helper function:
```go
func stepTypeToIssueType(stepType string) types.IssueType {
    switch stepType {
    case "task": return types.TypeTask
    case "bug": return types.TypeBug
    // ...
    default: return types.TypeTask
    }
}
```

Location: cmd/bd/cook.go:425 and cmd/bd/cook.go:764


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

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

## Proposed Splits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Implementation Steps

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

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

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


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


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

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


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

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

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

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

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


Test cross-rig issue creation

Testing --rig flag from town root


MCP plugin: _find_beads_db_in_tree() should follow .beads/redirect files

## Problem
The `_find_beads_db_in_tree()` function in `integrations/beads-mcp/src/beads_mcp/tools.py` does not follow `.beads/redirect` files.

## Impact
MCP beads tools fail from polecat/crew directories that use redirect files to share a beads database.

## Root Cause
When auto-detecting workspace, the MCP plugin looks for `.beads/*.db` files directly but doesn't check for or follow `redirect` files:

```python
beads_dir = os.path.join(current, '.beads')
if os.path.isdir(beads_dir):
    db_files = glob.glob(os.path.join(beads_dir, '*.db'))  # Only looks for .db files
    valid_dbs = [f for f in db_files if '.backup' not in os.path.basename(f)]
    if valid_dbs:
        return current
```

Polecat directories have `.beads/redirect` files (e.g., containing `../../mayor/rig/.beads`) but no `.db` files, so `_find_beads_db_in_tree()` doesn't find them.

## Expected Behavior
The function should:
1. Check for `.beads/redirect` file first
2. If found, follow the redirect to the actual beads location
3. Verify the redirected location has a valid database

## Reference
The Go implementation handles this correctly in `ResolveBeadsDir()` - see gastown `internal/beads/beads.go:23-60`.

## Related
- gt-tnw: MCP beads tools fail from polecat directories


Wait for GoReleaser

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


Merge: bd-9usz

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


Add bd mol run command (bond + assign + pin)

bd mol run = bond + assign root to caller + pin to startup mail. This is the Gas Town integration point. When agent restarts, check startup mail, find pinned molecule root, query bd ready for next step. Makes molecules immortal.


Blocking logic: Include 'hooked' in blocking statuses

Update the blocked_cache.go SQL to treat 'hooked' as a blocking status.

## Current Logic (line ~163)

```sql
blocker.status IN ('open', 'in_progress', 'blocked', 'deferred')
```

## Updated Logic

```sql
blocker.status IN ('open', 'in_progress', 'blocked', 'deferred', 'hooked')
```

## Files to Update

- internal/storage/sqlite/blocked_cache.go

## Test Cases

1. Issue A (hooked) blocks Issue B → B should NOT appear in bd ready
2. Issue A (pinned, type=agent) → B should appear in bd ready
3. Issue A (hooked) closed → B should appear in bd ready


Merge: onyx-1767106262992

branch: polecat/onyx-1767106262992
target: main
source_issue: onyx-1767106262992
rig: beads


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


Verify the release is complete.

Verify the release is complete.

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

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

# Check npm
npm view @beads/bd version

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

instantiated_from: bd-mol-xga
step: verify-release


Tier 2: Basic bd mol squash command

Add bd mol squash command for basic molecule execution compression.

## Command

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

## Implementation

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

## Schema Changes

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

## Acceptance Criteria

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


Commit release v{{version}}

Stage and commit the version bump:

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

Do NOT push yet - tag first.


Add GetWorkerStatus RPC endpoint

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


Update info.go versionChanges

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


Implement formula features: repeat, for_each, branch, gate, compose.split

Multiple formula features in internal/formula/types.go are marked as "Not yet implemented":

IMPLEMENTED (TODOs removed):
- Step.Expand: Inline expansion - implemented in ApplyInlineExpansions (expand.go)
- Step.ExpandVars: Variable overrides for expansion - implemented alongside Expand

STILL NOT IMPLEMENTED (TODOs remain):
- Step.Condition: Make step optional based on variable (types.go:177-180)
- Step.Gate: Async wait condition for step (types.go:185-187)
- Gate struct: Schema defined but not processed in bd cook (types.go:209-210)

File: internal/formula/types.go


Add type: event for state transitions

Events capture operational state changes as immutable records.

Fields needed:
- event_type: string (namespaced category like patrol.muted, agent.started)
- actor: string (entity URI who caused this)
- target: string (entity URI or bead ID affected)
- payload: object (event-specific JSON data)

Example:
  id: evt-x7k2
  type: event
  event_type: patrol.muted
  actor: human:overseer
  target: agent:deacon
  payload: {reason: fixing deadlock, until: gt-abc1}

This enables:
- bd activity --follow showing events
- bd list --type=event --target=agent:deacon
- Full audit trail for operational state
- HOP-compatible transaction records


Run bump-version.sh 0.32.1

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


mol-beads-release

attached_args: Release v0.38.0 - execute each step in order, verify before proceeding to next

Release checklist for beads version 0.38.0.

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

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

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

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

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

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

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

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

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

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

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

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

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

Needs: update-changelog

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

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

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

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

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

Needs: run-tests

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

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

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

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

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

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

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

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

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

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

# Check npm
npm view @beads/bd version

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

Needs: wait-for-ci

## Step: update-local
Update local installations.

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

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

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

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

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

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

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

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

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

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


Add .beads-backup-* pattern to gitignore template

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

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

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

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


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 pin


GH#540: Add silent quick-capture mode (bd q)

Add bd q alias for quick capture that outputs only issue ID. Useful for piping/scripting. See GitHub issue #540.


Move relate/unrelate under dep command

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


Work on gt-8tmz.36: Validate expanded step IDs are unique...

Work on gt-8tmz.36: Validate expanded step IDs are unique. In internal/formula/, add validation during cooking that checks for duplicate step IDs after expansion. When done: 1) bd close gt-8tmz.36, 2) bd sync, 3) git push, 4) gt mq submit


Graph Link: supersedes for version chains

Implement supersedes link type for version tracking.

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

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

Storage:
- superseded_by column pointing to replacement issue

Useful for design docs, specs, and evolving artifacts.


Add 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


Release complete

Release v0.39.0 is complete!

Summary:
- All version files updated
- Git tag pushed
- CI artifacts built
- npm and PyPI packages published
- Local installation updated
- Daemons restarted

Optional next steps:
- Announce on social media
- Update documentation site
- Close related milestone


Merge: bd-4qfb

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


Add stale molecule check to bd doctor

Extend bd doctor to detect stale molecules.

**New check:**
- Name: 'Stale Molecules'
- Category: Workflow
- Severity: Warning (don't fail overall check)

**Detection:**
Reuse logic from bd mol stale command:
- Find mols where Completed >= Total but root is open
- Filter to orphaned (not assigned, not pinned)
- Extra weight if blocking other work

**Output:**
```
⚠ Stale Molecules
  Found 2 complete-but-unclosed molecules:
  - bd-xyz: Version bump v0.36.0 (blocking 1 issue)
  - bd-uvw: Old patrol (not blocking)
  Fix: bd close <id> or bd mol squash <id>
```

**--fix behavior:**
- Auto-close stale mols (with reason 'Auto-closed by bd doctor')
- Or prompt interactively with -i flag

Depends on: bd mol stale command


Update local installation

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


gt polecat done should auto-stop running session

Currently 'gt polecat done' fails if session is running, requiring a separate 'gt session stop' first. This is unnecessary friction - done should just stop the session automatically since that's always what you want.


Merge: bd-u2sc.4

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


Test pinned for stats


Run bump-version.sh 0.30.7

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


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 distill: Extract formula from mol/epic

Extract a formula from completed work (mol, wisp, or epic).

**Key change**: Distill works on execution artifacts (mols/wisps/epics), not protos.
Protos are ephemeral - they don't persist. Distillation extracts patterns from
actual executed work.

## Usage
```bash
bd distill bd-mol-xyz -o my-workflow.formula.json
bd distill bd-epic-abc -o feature-workflow.formula.json
```

## Use Cases
- **Emergent patterns**: Structured work manually, want to templatize it
- **Modified execution**: Poured a formula, added custom steps, want to capture
- **Learning from success**: Extract what made a complex mol succeed

## Implementation
1. Load mol/wisp/epic subgraph (root + all children)
2. Convert to formula JSON structure
3. Extract variables from patterns (titles, descriptions)
4. Generate step IDs from issue titles (slugify)
5. Write .formula.json file

## Output Format
```json
{
  "formula": "my-workflow",
  "description": "...",
  "version": 1,
  "vars": { ... },
  "steps": [ ... ]
}
```

## Architecture Note
This closes the formula lifecycle loop:
  Formulas ──cook──→ Mols ──distill──→ Formulas

All sharing happens via formulas. Mols contain execution context and aren't shared.


Merge: amber-1767138546434

branch: polecat/amber-1767138546434
target: main
source_issue: amber-1767138546434
rig: beads


GH#402: Add --parent flag documentation to bd onboard

bd onboard output is missing --parent flag for epic subtasks. Agents guess wrong syntax (--deps parent:). See GitHub issue #402.


Swarm status doesn't update after task completions

During swarm bd-784c, the swarm status remained at '9% (1/11 tasks merged)' even after 5 tasks were completed and closed.

**Observed:**
```
gt swarm status bd-784c
Progress: 9% (1/11 tasks merged)
```

But bd list showed 4+ issues closed:
- bd-j3dj, bd-kkka, bd-1tkd, bd-9btu all closed

**Expected:**
Swarm status should reflect actual completion count, either by:
1. Polling issue status from beads
2. Updating when polecats close issues
3. Updating when commits are pushed to integration branch

**Impact:**
Coordinator (me) had to manually check bd list to know actual progress.


Merge: bd-ykd9

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


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


Graph Link: replies_to for conversation threading

Implement replies_to link type for message threading.

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

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

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

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


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 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[]


hq- prefix routing fails: '.' resolves relative to cwd, not town root

## Problem
The hq- prefix in routes.jsonl maps to '.' which is interpreted relative to the current working directory, not the town root.

## Reproduction
```bash
cd ~/gt && bd show hq-2cchm    # Works
cd ~/gt/beads/crew/emma && bd show hq-2cchm  # Fails: 'no issue found'
```

## Root Cause
routes.jsonl has: {"prefix": "hq-", "path": "."}

The '.' is resolved relative to cwd, so from crew/emma it looks for ~/gt/beads/crew/emma/.beads/ instead of ~/gt/.beads/

## Fix
The routing logic should resolve '.' as the town root, or the route should use an absolute path or a sentinel like 'TOWN_ROOT'.


getLocalSyncBranch silently ignores YAML parse errors

In autoimport.go:170-172, YAML parsing errors are silently ignored. If a user has malformed YAML in config.yaml, sync-branch will just silently be empty with no feedback.

Recommendation: Add debug logging since this function is only called during auto-import, and debugging silent failures is painful.

Add: debug.Logf("Warning: failed to parse config.yaml: %v", err)


Swarm: Code Review: Swarm commands consolidation

Swarm molecule orchestrating epic bd-2ubv.

Epic: bd-2ubv
Coordinator:


Merge: bd-c7y5

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


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)


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.


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)


gt nudge doesn't work with crew addresses

## Bug

`gt nudge beads/crew/dave "message"` fails because it uses the polecat session manager which produces wrong session names.

## Expected
Session name: `gt-beads-crew-dave` (hyphen)

## Actual
Session name: `gt-beads-crew/dave` (slash, from polecat manager)

## Root Cause

In nudge.go line 46-57:
```go
rigName, polecatName, err := parseAddress(target)  // beads, crew/dave
sessionName := mgr.SessionName(polecatName)        // gt-beads-crew/dave (WRONG)
```

Should detect `crew/` prefix in polecatName and use `crewSessionName(rigName, crewName)` instead.

## Fix

```go
if strings.HasPrefix(polecatName, "crew/") {
    crewName := strings.TrimPrefix(polecatName, "crew/")
    sessionName = crewSessionName(rigName, crewName)
} else {
    sessionName = mgr.SessionName(polecatName)
}
```


Agent bead prefix mismatch: gt- vs bd- for beads polecat agents

When spawning polecats in beads rig, gt sling tries to create agent beads with gt-beads-polecat-* prefix, but beads requires bd- prefix. Warning: 'issue ID gt-beads-polecat-obsidian does not match configured prefix bd'. Agent beads should use the rig's configured prefix.


Work on gt-8tmz.36: Validate expanded step IDs are unique...

Work on gt-8tmz.36: Validate expanded step IDs are unique. After ApplyExpansions in internal/formula/expand.go, new steps can be created that might have duplicate IDs. Add validation in ApplyExpansions to check for duplicate step IDs after expansion. If duplicates found, return an error with the duplicate IDs. Add test in expand_test.go. When done, commit and push to main.


bd mol bond: Dynamic bond with variable substitution

Implement dynamic molecule bonding with runtime variable substitution.

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

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

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

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


swarm status: Handle external dependencies and error cases

Three issues: (1) Silent error swallowing - GetDependencyRecords errors are silently continued, but analyzeEpicForSwarm returns errors. Inconsistent. (2) External dependencies ignored - if issue depends on something outside epic thats still open, we mark it Ready incorrectly. (3) Command usage string says [epic-id] (optional) but requires exactly 1 arg. File: cmd/bd/swarm.go


Merge: bd-n386

branch: polecat/immortan
target: main
source_issue: bd-n386
rig: beads


Code smell: Multiple CLI command files exceed 1000 lines

Several CLI command files are very large and could benefit from splitting:

| File | Lines | Notes |
|------|-------|-------|
| init.go | 1928 | Multiple init modes, contributor/team setup |
| show.go | 1592 | Display formatting, tree views, output modes |
| doctor.go | 1295 | Many health checks |
| sync.go | 1201 | Sync branch operations |
| compact.go | 1199 | Compaction logic |
| linear.go | 1190 | Linear integration |
| main.go | 1148 | Entry point and globals |

Consider:
1. Splitting init.go into init_core.go, init_contributor.go (already exists), init_team.go (already exists)
2. Moving show.go formatters to internal/ui package
3. Doctor checks could be individual files under doctor/ subpackage (already started)

Location: cmd/bd/*.go


bd mol search: Find formulas using GitHub API

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

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

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

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

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


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 coordinator display


> bd sync --no-daemon
→ Exporting pending changes to JSONL...
→ Committing changes to sync branch 'beads-sync'...
Error committing to sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory

**Problem Diagnosed**: The `bd sync` command was failing with "mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory" because it was being executed from the wrong directory.

**Root Cause**: The command was run from `/var/home/matt/dev/beads` (where the `fix-ci` worktree exists) instead of the main repository directory `/var/home/matt/dev/beads/main`. Since `fix-ci` is a git worktree with a `.git` file (not directory), the worktree creation logic failed when trying to create `<current_dir>/.git/beads-worktrees/<branch>`.

**Solution Verified**: Execute `bd sync` from the main repository directory:
```bash
cd main && bd sync --dry-run
```


Work on beads-1ra: Add molecules.jsonl as separate catalo...

Work on beads-1ra: Add molecules.jsonl as separate catalog file for template molecules


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


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.


Support --reason flag in daemon mode for reopen command

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


Verify PyPI package

Confirm PyPI package published.

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

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

Should show 0.41.0.


Version Bump: {{version}}

Release checklist for version {{version}}. This molecule ensures all release steps are completed properly.


Show pin indicator in bd list output

Add a visual indicator (e.g., pin emoji or [P] marker) for pinned issues in bd list output so users can easily identify them.


Push release tag

Push the version tag to trigger CI release.

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

This triggers GitHub Actions to build artifacts and publish.


Push to main

Push the release commit to origin.

```bash
git push origin main
```

If rejected, someone else pushed. Pull, rebase, try again.


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


Fix multi-round convergence for N-way collisions

**Summary:** Multi-round collision resolution was identified as a critical issue preventing complete synchronization across distributed clones. The problem stemmed from incomplete final pulls that didn't fully propagate all changes between system instances.

**Key Decisions:**
- Implement multi-round sync mechanism
- Ensure bounded convergence (≤N rounds)
- Guarantee idempotent import without data loss

**Resolution:** Developed a sync strategy that ensures all clones converge to the same complete set of issues, unblocking the bd-cbed9619 epic and improving distributed system reliability.


bd hook: detect agent from cwd instead of defaulting to $USER

**Problem:**
`bd hook` without `--agent` defaults to `$USER` (e.g., "stevey") instead of detecting the agent identity from the current working directory.

**Expected behavior:**
When running from `/Users/stevey/gt/beads/crew/dave`, the agent should be detected as `beads/crew/dave`.

**Current behavior:**
```bash
$ pwd
/Users/stevey/gt/beads/crew/dave
$ bd hook
Hook: stevey
  (empty)

$ bd hook --agent beads/crew/dave
Hook: beads/crew/dave
  📌 bd-hobo (mol) - open
```

**Fix:**
bd hook should use the same cwd-based agent detection that other commands use (similar to how `gt mail` determines identity from cwd).


bd mol bond --ephemeral flag

Add --ephemeral flag to bd mol bond command.

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

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

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


bd cleanup --hard should skip tombstone creation for true permanent deletion

## Problem

When using bd cleanup --hard --older-than N --force, the command:
1. Deletes closed issues older than N days (converting them to tombstones with NOW timestamp)
2. Then tries to prune tombstones older than N days (finds none because they were just created)

This leaves the database bloated with fresh tombstones that will not be pruned.

## Expected Behavior

In --hard mode, the deletion should be permanent without creating tombstones, since the user explicitly requested bypassing sync safety.

## Workaround

Manually delete from database: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE status=tombstone'

## Fix Options

1. In --hard mode, use a different delete path that does not create tombstones
2. After deleting, immediately prune the just-created tombstones regardless of age
3. Pass a skip_tombstone flag to the delete operation

Option 1 is cleanest - --hard should mean permanent delete without tombstone.


Execute molecule bd-mol-xga: Release beads 0.37.0


Commit the release changes.

Commit the release changes.

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

instantiated_from: bd-mol-xga
step: commit-release


Update Homebrew formula

Update the Homebrew tap with new version:

```bash
./scripts/update-homebrew.sh {{version}}
```

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

After running, verify the formula with:

```bash
brew info steveyegge/beads/bd
```


Add composite index (issue_id, event_type) on events table

GetCloseReason and GetCloseReasonsForIssues filter by both issue_id and event_type.

**Query (queries.go:355-358):**
```sql
SELECT comment FROM events
WHERE issue_id = ? AND event_type = ?
ORDER BY created_at DESC LIMIT 1
```

**Problem:** Currently uses idx_events_issue but must filter event_type in memory.

**Solution:** Add migration:
```sql
CREATE INDEX IF NOT EXISTS idx_events_issue_type ON events(issue_id, event_type);
```

**Priority:** Low - events table is typically small relative to issues.


Add versionChanges to info.go

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


Create and push git tag v{{version}}

Create the release tag and push it:

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

This triggers the GoReleaser GitHub Action to build release binaries.


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


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


Fix JSON errors in epic.go

Replace direct stderr writes with FatalErrorRespectJSON() in epic.go.

Commands affected:
- epicStatusCmd
- epicCloseEligibleCmd


🤝 HANDOFF: Review remaining beads PRs

## Current State
Just merged PR #653 (doctor refactor) and added tests to restore coverage.

## Remaining Open PRs to Review
Run `gh pr list --repo steveyegge/beads` to see current list. As of handoff:

1. #655 - feat: Linear Integration (jblwilliams)
2. #651 - feat(audit): agent audit trail (dchichkov)
3. #648 - Stop init creating redundant @AGENTS.md (maphew)
4. #646 - fix(unix): handle Statfs field types (jordanhubbard)
5. #645 - feat: /plan-to-beads Claude Code command (petebytes)
6. #642, #641, #640 - sync branch fixes (cpdata)

## Review Checklist
- Check CI status with `gh pr checks <num> --repo steveyegge/beads`
- Verify no .beads/ data leaking (we have a hook now)
- Review code quality
- Merge good ones, request changes on problematic ones

## Notes
- User wants us to be proactive about merging good PRs
- Can add tests ourselves if coverage drops


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.


Update local installation

Update local bd to the new version.

Option 1 - Homebrew:
```bash
brew upgrade bd
```

Option 2 - Install script:
```bash
curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
```

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

Should show 0.39.0.


Test Second Parent


bd merge omits priority:0 due to omitempty JSON tag

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


Merge: beads-ocs

branch: polecat/valkyrie
target: main
source_issue: beads-ocs
rig: beads


Fix JSON errors in dep.go (dependency commands)

Replace ~41 direct stderr writes with FatalErrorRespectJSON() in dep.go.

Commands affected:
- depAddCmd
- depRemoveCmd
- depTreeCmd
- depCyclesCmd

Key error paths:
- ID resolution errors
- Cycle detection errors
- External reference validation


Build and verify

go build -o bd ./cmd/bd && ./bd version


Update Homebrew formula

After GoReleaser completes, the Homebrew tap should be auto-updated.

If manual update needed:
```bash
./scripts/update-homebrew.sh v0.30.4
```

Or manually update steveyegge/homebrew-beads with new SHA256.

Verify:
```bash
brew update
brew info beads
```


Code smell: Repeated validation patterns could be centralized

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

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

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


Merge: bd-g4b4

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


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.


Add tests for helper functions (GetDirtyIssueHash, GetAllDependencyRecords, export hashes)

Several utility functions have 0% coverage:
- GetDirtyIssueHash (dirty.go)
- GetAllDependencyRecords (dependencies.go)
- GetExportHash, SetExportHash, ClearAllExportHashes (hash.go)

These are lower priority but should have basic coverage.


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


Preflight: Check git status

Ensure working tree is clean before starting release.

```bash
git status
```

If there are uncommitted changes, either:
- Commit them first
- Stash them: `git stash`
- Abort and resolve


Merge: onyx-1767083477016

branch: polecat/onyx-1767083477016
target: main
source_issue: onyx-1767083477016
rig: beads


Polecats should spawn with auto-accept mode enabled

During swarm bd-784c, polecats (Toast, Nux) were spawned without --dangerously-skip-permissions or equivalent auto-accept mode.

**Problem:**
Every edit, bash command, and tool use required manual confirmation via tmux send-keys. This defeats the purpose of autonomous polecat operation.

**Expected:**
Polecats in a swarm should run with permissions bypassed so they can work autonomously.

**Workaround used:**
- Manually sent Enter keys via tmux to accept prompts
- Eventually polecats entered 'bypass permissions on' mode after restart

**Suggestion:**
- gt sling should pass --dangerously-skip-permissions by default for polecats
- Or polecats should have a .claude/settings.local.json pre-configured for auto-accept


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


Release complete

Release v0.41.0 is complete!

Summary:
- All version files updated
- Git tag pushed
- CI artifacts built
- npm and PyPI packages published
- Local installation updated
- Daemons restarted

Optional next steps:
- Announce on social media
- Update documentation site
- Close related milestone


Design auto-squash behavior for wisps

Explore the design space for automatic wisp squashing.

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

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

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

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

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

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

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


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.


Simplify wisp architecture: single DB with ephemeral flag

## Problem

The current wisp architecture uses a separate .beads-wisp/ directory with its own database. This creates unnecessary complexity:

- Separate directory structure and database initialization
- Parallel routing logic in both beads and gastown
- Merge queries from two stores for inbox/list operations
- All this for what is essentially a boolean flag

## Current State

Gastown mail code has to:
- resolveWispDir() vs resolveBeadsDir()
- Query both, merge results
- Route deletes to correct store

## Proposed Solution

Single database with an ephemeral boolean field on Issue. Behavior:
- bd create --ephemeral sets the flag
- JSONL export SKIPS ephemeral issues (they never sync)
- bd list shows all by default, --ephemeral / --persistent filters
- bd mol squash clears the flag (promotes to permanent, now exports)
- bd wisp gc deletes old ephemeral issues from the db
- No separate directory, no separate routing

## Migration

1. Update beads to support ephemeral flag in main db
2. Update bd wisp commands to work with flag instead of separate dir
3. Update gastown mail to use simple --ephemeral flag (remove all dual-routing)
4. Deprecate .beads-wisp/ directory pattern

## Acceptance Criteria

- Single database for all issues (ephemeral and persistent)
- ephemeral field on Issue type
- JSONL export skips ephemeral issues
- bd create --ephemeral works
- bd mol squash promotes ephemeral to persistent
- Gastown mail uses simple flag, no dual-routing


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


Verify version consistency

Confirm all versions match 0.39.0.

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

All should show 0.39.0.


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


Code smell: Issue struct still has ~100 fields - consider composition

internal/types/types.go Issue struct (lines 14-100) still has approximately 100 fields across many categories despite prior cleanup:

- Core identification (ID, ContentHash)
- Content (Title, Description, Design)
- Status & Workflow
- Assignment, Timestamps
- External integration
- Compaction metadata
- Relational data (Labels, Dependencies, Comments)
- Tombstone, Messaging, Context markers
- Bonding, HOP, Gate, Source tracing, Agent identity fields

**Problem:**
- Very large struct is difficult to understand
- Hard to identify required vs optional fields
- High change risk

**Acceptance Criteria:**
- [ ] Group related fields into embedded structs (IssueMetadata, IssueCompaction, IssueAgent, etc.)
- [ ] Use composition instead of single flat struct
- [ ] Maintain backward compatibility for serialization
- [ ] Tests pass


Monitor npm publish

Watch the npm publish action:
https://github.com/steveyegge/beads/actions/workflows/npm-publish.yml

Verify at: https://www.npmjs.com/package/@anthropics/claude-code-beads-plugin/v/0.30.4

Check:
```bash
npm view @anthropics/claude-code-beads-plugin@0.30.4 version
```

```verify
npm view @anthropics/claude-code-beads-plugin@0.30.4 version 2>/dev/null | grep -q '0.30.4'
```


Add global verbosity flags (--verbose, --quiet)

Add consistent verbosity controls across all commands.

**Current state:**
- bd init has --quiet flag
- No other commands have verbosity controls
- Debug output controlled by BD_VERBOSE env var

**Proposal:**
Add persistent flags:
- --verbose / -v: Enable debug output
- --quiet / -q: Suppress non-essential output

**Implementation:**
- Add to rootCmd.PersistentFlags()
- Replace BD_VERBOSE checks with flag checks
- Standardize output levels:
  * Quiet: Errors only
  * Normal: Errors + success messages
  * Verbose: Errors + success + debug info

**Files to modify:**
- cmd/bd/main.go (add flags)
- internal/debug/debug.go (respect flags)
- Update all commands to respect quiet mode

**Testing:**
- Verify --verbose shows debug output
- Verify --quiet suppresses normal output
- Ensure errors always show regardless of mode


Verify CI is green

Check that CI is passing before starting release:
```bash
gh run list --limit 3
```
All recent runs should show 'success'. If not, fix issues first.


Commit release

Stage and commit all version changes.

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

Review the commit to ensure all expected files are included.


Time-dependent worktree detection tests may be flaky


Update CHANGELOG.md with release notes

Add meaningful release notes to CHANGELOG.md describing what changed in test-squash


Kill running daemons

Stop all bd daemons before release:

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


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


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'


Run go install

go install ./cmd/bd


Code cleanup: Search for leftover debugging code

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

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

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

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


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


Fix N+1 query pattern in swarm status blocked check

In getSwarmStatus, when checking if an issue is blocked, we query each dependency individually with GetIssue. With 100 issues and 5 deps each, thats 500 extra queries. Fix: Build a status map upfront from the already-fetched childIssues slice. File: cmd/bd/swarm.go:706-710


Merge: bd-au0.6

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


Git merge driver uses invalid placeholders (%L, %R instead of %A, %B)

## Problem

The beads git merge driver is configured with invalid Git placeholders:

```
git config merge.beads.driver "bd merge %A %O %L %R"
```

Git doesn't recognize `%L` or `%R` as valid merge driver placeholders. The valid placeholders are:
- `%O` = base (common ancestor)
- `%A` = current version (ours)
- `%B` = other version (theirs)

## Impact

- Affects ALL users when they have `.beads/beads.jsonl` merge conflicts
- Automatic JSONL merge fails with error: "error reading left file: failed to open file: open 7: no such file or directory"
- Users must manually resolve conflicts instead of getting automatic merge

## Root Cause

The `bd init` command (or wherever the merge driver is configured) is using non-standard placeholders. When Git encounters `%L` and `%R`, it either passes them literally or interprets them incorrectly.

## Fix

Update the merge driver configuration to:
```
git config merge.beads.driver "bd merge %A %O %A %B"
```

Where:
- 1st `%A` = output file (current file, will be overwritten)
- `%O` = base (common ancestor)
- 2nd `%A` = left/current version
- `%B` = right/other version

## Action Items

1. Fix `bd init` (or equivalent setup command) to use correct placeholders
2. Add migration/warning for existing users with misconfigured merge driver
3. Update documentation with correct merge driver setup
4. Consider adding validation when `bd init` is run


Test body-file

This is a test description from a file.

It has multiple lines.


Merge: topaz-1767106269860

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


Update CHANGELOG.md for 0.30.4

1. Change `## [Unreleased]` to `## [0.30.4] - 2025-12-17`
2. Add new empty `## [Unreleased]` section at top
3. Ensure all changes since 0.30.3 are documented


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


Merge: quartz-1767083470444

branch: polecat/quartz-1767083470444
target: main
source_issue: quartz-1767083470444
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


Add missing updated_at index on issues table

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

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

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

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

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


Implement subgraph cloning with variable substitution

Core implementation of template instantiation:

1. Add `bd template instantiate <template-id> [--var key=value]...` command
2. Implement subgraph loading:
   - Load template epic
   - Recursively load all children (and their children)
   - Load all dependencies between issues in the subgraph
3. Implement variable substitution:
   - Scan titles and descriptions for `{{name}}` patterns
   - Replace with provided values
   - Error on missing required variables (or prompt interactively)
4. Implement cloning:
   - Generate new IDs for all issues
   - Create cloned issues with substituted text
   - Remap and create dependencies
5. Return the new epic ID

Consider adding `--dry-run` flag to preview what would be created.


Investigate Claude Code crash logging improvements

## Problem

Claude Code doesn't leave useful crash logs when it terminates unexpectedly. Investigation of a crash on 2025-12-26 showed:

- Debug logs in ~/.claude/debug/ just stop mid-stream with no error/exit message
- No signal handlers appear to log SIGTERM/SIGKILL/SIGINT
- No dedicated crash log file exists
- When Node.js crashes hard or gets killed, there's no record of why

## What we found

- Session debug log (02080b1a-...) stopped at 22:58:40 UTC mid-operation
- No 'exit', 'error', 'crash', 'signal' entries at end of file
- macOS DiagnosticReports showed Chrome crashes but no Node crashes
- System logs showed no relevant kill/OOM events

## Desired improvements

1. Exit handlers that log graceful shutdown
2. Signal handlers that log SIGTERM/SIGINT before exiting
3. A dedicated crash log or at least a 'last known state' file
4. Possibly CLI flags to enable verbose crash debugging

## Investigation paths

- Check if Claude Code has --debug or similar flags
- Look at Node.js crash handling best practices
- Consider if we can wrap claude invocations to capture crashes

## Related

This came up while investigating why a crew worker session crashed at end of a code review task.


Upgrade beads-mcp

pip3 install --upgrade beads-mcp


Merge: bd-u2sc.3

branch: polecat/Modular
target: main
source_issue: bd-u2sc.3
rig: beads


Test agent bead

Testing new agent type


bd validate - Comprehensive health check

Run all validation checks in one command.

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

Supports --fix-all for auto-repair.

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

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


Auto-detect non-TTY and adjust output

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

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

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

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

## Implementation Plan

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

### Code Changes

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

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

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

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

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

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


Implement global N-way collision resolution algorithm

**Summary:** Replaced pairwise collision resolution with a global N-way algorithm that deterministically resolves issue ID conflicts across multiple clones. The new approach groups collisions, deduplicates by content hash, and assigns sequential IDs to ensure consistent synchronization.

**Key Decisions:**
- Use content hash for global, stable sorting
- Group collisions by base ID
- Assign sequential IDs based on sorted unique versions
- Eliminate order-dependent remapping logic

**Resolution:** Implemented ResolveNWayCollisions function that guarantees deterministic issue ID assignment across multiple synchronization scenarios, solving the core challenge of maintaining consistency in distributed systems with potential conflicts.


Install 0.30.4 MCP server locally

Upgrade the MCP server (after PyPI publish):
```bash
pip install --upgrade beads-mcp
# OR if using uv:
uv tool upgrade beads-mcp
```

Verify:
```bash
pip show beads-mcp | grep Version
```

```verify
pip show beads-mcp 2>/dev/null | grep -q 'Version: 0.30.4'
```


Merge: bd-49kw

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