Code Health Review Dec 2025: Technical Debt Cleanup

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

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

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

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

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

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


Code smell: runCook function is ~275 lines

attached_args: Refactor runCook function (~275 lines)

The runCook() function in cmd/bd/cook.go (lines 83-357) is ~275 lines handling:

1. Flag parsing and validation
2. Mode determination (compile vs runtime)
3. Formula parsing and resolution
4. Control flow, advice, and expansion application
5. Dry-run output
6. Ephemeral mode JSON output
7. Persist mode database operations
8. Result display

Consider extracting:
- parseAndValidateFlags() - flag handling
- loadAndResolveFormula() - parse, resolve, apply transformations
- outputDryRun() - dry run display logic
- outputEphemeral() - ephemeral JSON output
- persistFormula() - database persistence logic

The function has multiple exit points and nested conditionals that make it hard to follow.

Location: cmd/bd/cook.go:83-357


Phase 1.2: Separate bdt executable - Initial structure

Create minimal bdt command structure completely separate from bd. Must not share code, config, or database.

## Subtasks
1. Create cmd/bdt/ directory with main.go
2. Implement bdt version, help, and status commands
3. Configure separate database location: $HOME/.bdt/ (not $HOME/.beads/)
4. Create separate issues file: issues.toon (not issues.jsonl)
5. Update build system:
   - Makefile: Add bdt target
   - .goreleaser.yml: Add bdt binary config

## Files to Create
- cmd/bdt/main.go - Entry point
- cmd/bdt/version.go - Version handling
- cmd/bdt/help.go - Help text (separate from bd)

## Success Criteria
- `make build` produces both `bd` and `bdt` executables
- `bdt version` shows distinct version output from bd
- `bdt --help` shows distinct help text
- bdt uses $HOME/.bdt/ directory (verify with `bdt info`)
- bd and bdt completely isolated (no shared imports beyond stdlib)


Review changes since last release

Understand what's being released.

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

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


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

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


Add 'hooked' status for GUPP work assignment

## Summary

Separate the semantics of `pinned` (identity records) from work-on-hook (GUPP assignment).

**Current problem:** `pinned` is overloaded to mean both:
1. Agent identity beads (gt-mayor, gt-crew-gastown-max) - should never block
2. Work attached to an agent's hook - should block until closed

The blocking logic excludes `pinned` from blocking, which means work on hooks doesn't block its dependents.

## Semantic Split

| Status | Meaning | Blocks dependents? |
|--------|---------|-------------------|
| `pinned` | Domain table / identity record (agents, roles) | No |
| `hooked` | Work on an agent's hook (GUPP invoked) | Yes |

**Terminology:**
- **Pin** (verb): Establish a permanent identity record
- **Hook** (verb): Attach work to an agent's hook
- **Sling** (verb): The act of hooking work to an agent

## Lifecycles

**Molecule** (the work):
  open -> hooked -> closed

**Persistent agent** (Mayor, Witness, Crew):
  idle <-> working (cycles)
Agent bead stays pinned. Operational state derived from hook slot occupancy.

**Polecat** (ephemeral worker):
  spawned-with-work -> working -> despawning
Polecats never idle - spawned for one molecule, nuked after.

The `hooked` status belongs to the **molecule**, not the agent. Agent identity beads remain `pinned` throughout their existence.

## in_progress vs hooked

Both remain valid statuses with different semantics:
- `in_progress` = Soft claim, cooperative (humans juggling multiple issues)
- `hooked` = Hard claim, GUPP-driven (molecule on agent's hook)

Gas Town molecules use `hooked`. Humans/non-Gas Town workflows may use `in_progress`.

## Success Criteria

1. `hooked` is a valid status in beads schema
2. Blocking logic treats `hooked` as blocking (like `in_progress`)
3. gt sling sets status=hooked on the molecule (not pinned)
4. Existing pinned work items migrated to hooked where appropriate
5. Agent/role beads remain pinned and non-blocking


Test stdin 2

Description from stdin test


Add backwards-compatible aliases and deprecation warnings

## Task
Ensure backwards compatibility by adding hidden aliases for all moved commands.

## Implementation

### Create aliases.go
New file to centralize alias management:
```go
package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

// Deprecated command aliases for backwards compatibility
// These will be removed in v0.XX.0

func init() {
    // migrate-* → migrate *
    addDeprecatedAlias("migrate-hash-ids", "migrate hash-ids")
    addDeprecatedAlias("migrate-issues", "migrate issues")
    addDeprecatedAlias("migrate-sync", "migrate sync")
    addDeprecatedAlias("migrate-tombstones", "migrate tombstones")

    // top-level → admin *
    addDeprecatedAlias("cleanup", "admin cleanup")
    addDeprecatedAlias("compact", "admin compact")
    addDeprecatedAlias("reset", "admin reset")

    // top-level → formula *
    addDeprecatedAlias("cook", "formula cook")
}

func addDeprecatedAlias(old, new string) {
    cmd := &cobra.Command{
        Use:    old,
        Hidden: true,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Fprintf(os.Stderr, "⚠  '%s' is deprecated, use '%s' instead\n", old, new)
            // Forward execution to new command
        },
    }
    rootCmd.AddCommand(cmd)
}
```

### Deprecation behavior
1. First release: Show warning, execute command
2. One release later: Show warning, still execute
3. Third release: Remove aliases entirely

## Files to create
- cmd/bd/aliases.go

## Files to modify
- cmd/bd/main.go (ensure aliases loaded)


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

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


bd wisp list command

Add bd wisp list command to show ephemeral molecules.

## Usage
```bash
bd wisp list              # List all wisps in current context
bd wisp list --json       # JSON output
bd wisp list --all        # Include orphaned wisps
```

## Output
- Shows in-progress ephemeral molecules
- Columns: ID, Title, Started, Last Update, Status
- Warns about orphaned wisps (old updated_at)

## Implementation
- New 'wisp' command group
- Read from .beads-ephemeral/issues.jsonl
- Filter to ephemeral:true issues


Merge: quartz-1767083470444

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


beads-release

Beads release workflow - from version bump to verified release.

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

## Usage

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

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


Update CHANGELOG.md

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

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

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

The bump script will stamp the date automatically.


Add bd template list command

Add a convenience command to list available templates:

bd template list

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


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


Commit release v{{version}}

Stage and commit the version bump:

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

Do NOT push yet - tag first.


Merge: bd-ykd9

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


Merge: bd-ot0w

branch: polecat/dementus
target: main
source_issue: bd-ot0w
rig: beads


Implement bd mol current command

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


Rebuild local binary

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


bd mol run: panic slice bounds out of range in mol_run.go:130

## Problem
bd mol run panics after successfully creating the molecule:

```
✓ Molecule running: created 9 issues
  Root issue: gt-i4lo (pinned, in_progress)
  Assignee: stevey

Next steps:
  bd ready                # Find unblocked work in this molecule
panic: runtime error: slice bounds out of range [:8] with length 7

goroutine 1 [running]:
main.runMolRun(0x1014fc0c0, {0x140001e0f80, 0x1, 0x10089daad?})
  /Users/stevey/gt/beads/crew/dave/cmd/bd/mol_run.go:130 +0xc38
```

## Reproduction
```bash
bd --no-daemon mol run gt-lwuu --var issue=gt-test123
```
Where gt-lwuu is a mol-polecat-work proto with 8 child steps.

## Impact
The molecule IS created successfully - the panic happens after creation when formatting the "Next steps" output.


Run bump-version.sh 0.30.7

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


Add mol_type schema field to beads

Add mol_type field to distinguish molecule types (swarm/patrol/work).

## Context
Swarms need to be distinguishable from regular work molecules. The mol_type field enables:
- Filtering: bd list --type=swarm
- Queries: Find all active swarm molecules
- Coordinator logic: Different handling for different mol types

## Implementation
1. Add mol_type column to SQLite schema (nullable, default null = work)
2. Add mol_type to JSONL format
3. Update create command to accept --mol-type
4. Update list/ready commands to filter by mol_type
5. Values: 'swarm', 'patrol', 'work', null (null = work)

## Reference
~/gt/docs/swarm-architecture.md - 'Data Model' section

## Acceptance
- bd create --mol-type=swarm works
- bd list --mol-type=swarm filters correctly
- Existing molecules work unchanged (backward compatible)


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.


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


Run bump-version.sh

Update all component versions atomically.

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

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


Re: Thread Test 2

Got your message. Testing reply feature.


witness Handoff


Add warning when staleness check errors

## Problem

When ensureDatabaseFresh() calls CheckStaleness() and it errors (corrupted metadata, permission issues, etc.), we silently proceed with potentially stale data.

**Location:** cmd/bd/staleness.go:27-32

**Scenarios:**
- Corrupted metadata table
- Database locked by another process
- Permission issues reading JSONL file
- Invalid last_import_time format in DB

## Current Code

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    // If we can't determine staleness, allow operation to proceed
    // (better to show potentially stale data than block user)
    return nil
}
```

## Fix

```go
isStale, err := autoimport.CheckStaleness(ctx, store, dbPath)
if err \!= nil {
    fmt.Fprintf(os.Stderr, "Warning: Could not verify database freshness: %v\n", err)
    fmt.Fprintf(os.Stderr, "Proceeding anyway. Data may be stale.\n\n")
    return nil
}
```

## Impact
Medium - users should know when staleness check fails

## Effort
Easy - 5 minutes


Add composite index (issue_id, type) on dependencies table

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

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

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

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

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


Commit release v0.33.2

Stage and commit the version bump:

```bash
git add cmd/bd/version.go cmd/bd/info.go CHANGELOG.md
git commit -m "release: v0.33.2"
```

Do NOT push yet - tag first.


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.


Integrate migration detection into bd doctor

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

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


Identity Configuration

Implement identity system for sender field.

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

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

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

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


Test pinned for stats


GH#405: Fix prefix parsing with hyphens - multi-hyphen prefixes parsed incorrectly

Fixed: ExtractIssuePrefix was falling back to first-hyphen for word-like suffixes, breaking multi-hyphen prefixes like 'hacker-news' and 'me-py-toolkit'.


Add --pretty flag to bd ready

bd list has --pretty but bd ready doesn't. Should support same tree format with status/priority symbols for consistency.


Emit issue title with dep/label mutation events

When dependencies or labels are added, mutations are emitted without title/assignee:

```go
s.emitMutation(MutationUpdate, depArgs.FromID, "", "")  // server_labels_deps_comments.go:68
s.emitMutation(MutationUpdate, issueID, "", "")         // server_labels_deps_comments.go:101
```

This causes bd activity (and gt feed) to show less informative entries like:
```
[20:42:17] → gt-4ntnq updated
```

Instead of:
```
[20:42:17] → gt-4ntnq updated · Pipeline Reliability: Fix work loss...
```

**Fix**: Look up issue title in dep/label handlers before emitting mutation. This adds one extra DB read per dep/label operation, but these are infrequent enough that it's acceptable.

**Related**: gt feed now deduplicates rapid updates in gastown commit (to be pushed).


Run bump-version.sh 0.32.1

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


Refinery Patrol

Merge queue processor patrol loop with verification gates.


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

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

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

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

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

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

Requires: Identity configuration (bd-kwro.7)


Add comprehensive filters to bd export

Enhance bd export with filtering options for selective exports.

**Currently only has:**
- --status

**Add filters:**
- --label, --label-any
- --assignee
- --type
- --priority, --priority-min, --priority-max
- --created-after, --created-before
- --updated-after, --updated-before

**Use case:**
- Export only open issues: bd export --status open
- Export high-priority bugs: bd export --type bug --priority-max 1
- Export recent issues: bd export --created-after 2025-01-01

**Files to modify:**
- cmd/bd/export.go
- Reuse filter logic from list.go


Run tests and linting

Run the full test suite and linter:

```bash
TMPDIR=/tmp go test -short ./...
golangci-lint run ./...
```

Fix any failures. Linting warnings acceptable (see LINTING.md).


Merge: jade-mjtlsejo

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


Code smell: Duplicated step collection functions in cook.go

attached_args: Fix duplicated step collection functions in cook.go

Two pairs of nearly identical functions exist in cook.go:

1. collectStepsRecursive() (line 754) vs collectStepsToSubgraph() (line 415)
   - Both iterate over steps and create issues
   - Main difference: one writes to DB, one builds in-memory subgraph

2. collectDependencies() (line 833) vs collectDependenciesToSubgraph() (line 502)
   - Both collect dependencies from steps
   - Nearly identical logic

Consider:
1. Extract common step/dependency processing logic
2. Use a strategy pattern or callback for the DB vs in-memory difference
3. Create a single function that returns data, then have callers decide how to persist

Location: cmd/bd/cook.go:415-567 and cmd/bd/cook.go:754-898


Rename 'template instantiate' to 'mol bond'

Rename the template instantiation command to match molecule metaphor.

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

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

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

See also: molecule execution model in Gas Town


Improve bd doctor output formatting for better readability.

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

## Target Output

```
$ bd doctor

bd doctor v0.35.0

Summary: 24 checks passed, 1 warning, 0 errors

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

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

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

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

bd doctor v0.35.0

Summary: 24 checks passed, 1 warning, 0 errors

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

[hooks] Git hooks outdated
  ...

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

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

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

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

## Implementation

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

```go
type Category string

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

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

### 2. Add --verbose flag

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

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

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

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

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

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

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

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

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

    return buf.String()
}

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

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

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

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

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

### 4. Update run function

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

    results := runAllChecks()

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

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

## Files to Modify

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

## Testing

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

# Verbose output
bd doctor --verbose

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

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


Test role bead


Fix resolvePartialID stub in workflow.go

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


Preflight: Pull latest

Ensure we're up to date with origin.

```bash
git pull --rebase
```

Resolve any conflicts before proceeding.


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

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


Update .claude-plugin/marketplace.json to 0.30.4

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

```verify
jq -e '.plugins[0].version == "0.30.4"' .claude-plugin/marketplace.json
```


Merge: jasper-1767106250817

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


Code smell: Long function flushToJSONLWithState (~280 lines)

The flushToJSONLWithState() function in cmd/bd/autoflush.go (lines 490-772) is ~280 lines with:

1. Multiple nested conditionals
2. Inline helper function definitions (recordFailure, recordSuccess)
3. Complex multi-repo filtering logic
4. Mixed concerns (validation, export, error handling)

Consider breaking into:
- validateJSONLIntegrity() - already exists but could absorb more
- getIssuesToExport(fullExport bool) - determines what to export
- mergeWithExistingJSONL(issueMap, dirtyIDs) - handles incremental merge
- filterByPrefix(issues) - multi-repo prefix filtering
- performExport(issues, jsonlPath) - the actual write
- updateMetadataAfterExport() - hash/timestamp updates

Location: cmd/bd/autoflush.go:490-772


bd sync fails when .beads/redirect points to different repo


Merge: bd-u2sc.3

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


Investigate JSONL hash mismatch warnings (bd-160)

During v0.39.1 release, saw repeated warnings:

WARNING: JSONL file hash mismatch detected (bd-160)
  This indicates JSONL and export_hashes are out of sync.
  Clearing export_hashes to force full re-export.

This appeared multiple times during wisp close operations. Questions:
1. What causes export_hashes to drift from actual JSONL content?
2. Is this expected during wisp operations (since wisps do not export)?
3. Should wisp operations skip the hash check entirely?
4. Is there actual data integrity risk or just noise?

The warning references bd-160 - may need to check that issue for context.


Fix TestImportBugIntegration deadlock in importer_test.go

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

TODO: Test hangs due to database deadlock - needs investigation

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

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


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

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

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

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

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


Make merge requests ephemeral wisps instead of permanent issues

## Problem

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

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

## Proposed Solution

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

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

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

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

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

## Benefits

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

## Implementation Notes

### Where MRs are created

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

### Refinery changes

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

### What about cross-rig MRs?

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

### Migration

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

## Alternatives Considered

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

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

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

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

## Related

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


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


Sync race: rebase-based divergence recovery resurrects tombstones

## Problem
When two repos sync simultaneously, tombstones can be resurrected:

1. Repo A deletes issue (creates tombstone), pushes to sync branch
2. Repo B (with 'closed' status) exports and tries to push
3. Push fails (non-fast-forward)
4. fetchAndRebaseInWorktree does git rebase
5. Git rebase applies B's 'closed' patch on top of A's 'tombstone'
6. TEXT-level rebase doesn't invoke beads merge driver
7. 'closed' overwrites 'tombstone' = resurrection

## Root Cause
CommitToSyncBranch uses git rebase for divergence recovery, but rebase is text-level, not content-level. The proper content-level merge in PullFromSyncBranch handles tombstones correctly, but it runs AFTER the problematic push.

## Proposed Fix
Option 1: Don't push in CommitToSyncBranch - let PullFromSyncBranch handle merge+push
Option 2: Replace git rebase with content-level merge in fetchAndRebaseInWorktree
Option 3: Reorder sync steps: Export → Pull/Merge → Commit → Push

## Workaround Applied
Excluded tombstones from orphan detection warnings (commit 1e97d9cc).

See also: bd-3852 (Add orphan detection migration)


Update info.go versionChanges

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

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

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

Focus on workflow-impacting changes agents need to know.


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


Support ephemeral protos: cook inline for pour/wisp/bond

Gas Town moving to ephemeral protos (gt-4v1eo). Protos should be in-memory, not persisted beads. Changes: (1) bd cook outputs proto JSON to stdout by default, add --persist flag for old behavior. (2) bd pour/wisp cook inline from formula name. (3) bd mol bond accepts formula names, cooks inline. The low-level cookFormula() machinery stays for --persist use cases. See gt-4v1eo for full design. Related: bd-ia3g (rename ProtoID).


Update info.go versionChanges

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


Create and push git tag v0.33.2

Create the release tag and push it:

```bash
git tag v0.33.2
git push origin v0.33.2
```

This triggers the GoReleaser GitHub Action to build release binaries.


Preflight: Pull latest

Ensure we're up to date with origin.

```bash
git pull --rebase
```

Resolve any conflicts before proceeding.


Add display guards for large molecules in bd mol current

When molecule has >100 steps, show summary instead of list.

Changes:
1. Count children first
2. If count > 100: show summary + pointer to bd mol progress
3. Add --range flag for viewing specific step ranges
4. Add --limit flag for capping output


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


Distill: promote ad-hoc epic to proto

Extract a reusable proto from an existing ad-hoc epic.

COMMAND: bd mol distill <epic-id> [--as <proto-name>]

BEHAVIOR:
- Clone the epic and all children as a new proto
- Set is_template=true on all cloned issues
- Replace concrete values with {{variable}} placeholders (interactive or --var flags)
- Add to proto catalog

FLAGS:
- --as NAME: Custom proto ID (default: proto-<epic-id>)
- --var field=placeholder: Replace value with variable placeholder
- --interactive: Prompt for each field that looks parameterizable
- --dry-run: Preview the proto structure

EXAMPLE:
  bd mol distill bd-o5xe --as proto-feature-workflow \
    --var title=feature_name \
    --var assignee=worker

USE CASES:
- Team develops good workflow organically, wants to reuse it
- Capture tribal knowledge as executable templates
- Create starting point for similar future work

The reverse of spawn: instead of proto → molecule, it's molecule → proto.


Time-dependent worktree detection tests may be flaky


bd doctor --deep: validate full graph integrity

Add a deep validation mode to bd doctor that checks the entire issue graph.

Current doctor checks:
- Orphan sessions
- Orphan processes
- Wisp GC
- Sync status

Proposed --deep checks:
- **Parent consistency**: Every issue with parent field points to existing issue
- **Dependency symmetry**: If A depends on B, B should show A in blocks
- **Orphan issues**: Issues mentioned in commits but never closed
- **Circular dependencies**: Detect dependency cycles
- **Epic completeness**: Epics with all children closed should be closeable
- **Agent bead integrity**: Agent beads have required fields (role_bead, state)
- **Mail thread integrity**: Messages point to valid threads
- **Molecule state**: Molecules have valid current_step pointing to existing step

Use case: After complex operations (reparenting, migrations, bulk updates), run `bd doctor --deep` to catch graph inconsistencies before they cause problems.

Performance: May be slow on large databases - warn user and show progress.


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.


Version Bump: 0.32.1

Release checklist for version 0.32.1. Patch release with MCP output control params and pin field fix.


Fix JSON errors in epic.go

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

Commands affected:
- epicStatusCmd
- epicCloseEligibleCmd


Relocate quickstart to bd help quickstart or docs

## Task
Move `bd quickstart` → `bd help quickstart` or convert to documentation.

## Options

### Option A: Move to bd help quickstart
- Create a custom help topic
- Cobra supports additional help topics

### Option B: Convert to docs only
- Keep content in docs/QUICKSTART.md
- Remove the command entirely
- Update references to point to docs

## Recommendation
Option B - quickstart is essentially documentation, not a functional command.
The command just prints formatted text that could live in docs.

## Implementation (Option B)
1. Ensure docs/QUICKSTART.md has equivalent content
2. Add hidden alias that prints "See docs/QUICKSTART.md or bd help"
3. Update all references:
   - README.md
   - docs/INSTALLING.md
   - docs/EXTENDING.md
   - npm-package/INTEGRATION_GUIDE.md
   - .beads/README.md

## Files to modify
- cmd/bd/quickstart.go (hide or remove)
- Multiple docs (update references)


activity --follow: Silent error handling

In activity.go:175-179, when the daemon is down or errors occur during polling in --follow mode, errors are silently ignored:

```go
newEvents, err := fetchMutations(lastPoll)
if err != nil {
    // Daemon might be down, continue trying
    continue
}
```

This means:
- Users won't know if the daemon is unreachable
- Could appear frozen when actually failing
- No indication of lost events

Should at least show a warning after N consecutive failures, or show '...' indicator to show polling status.

Discovered during code review of bd-xo1o implementation.


Add GetMoleculeProgress RPC endpoint

New RPC endpoint to get detailed progress for a specific molecule. Returns: moleculeID, title, assignee, and list of steps with their status (done/current/ready/blocked), start/close times. Used when user expands a worker in the activity feed TUI.


Integrate detect-pollution into bd doctor

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

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

## Implementation

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

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

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

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


Push commit and create release tag.

Push commit and create release tag.

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

This triggers GitHub Actions release workflow.

instantiated_from: bd-mol-xga
step: push-and-tag


Merge: opal-1767142018955

branch: polecat/opal-1767142018955
target: main
source_issue: opal-1767142018955
rig: beads


Implement `bd setup cursor` for Cursor IDE integration

Create a `bd setup cursor` command that integrates Beads workflow into Cursor IDE via .cursorrules file. Unlike Claude Code (which has hooks), Cursor uses a static rules file to provide context to its AI.


Merge: bd-gqxd

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


Implement backup functionality for reset

Add backup capability that can be used by reset command.

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

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

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

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


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.


Exclude pinned issues from bd ready

Update bd ready to exclude pinned issues. Pinned issues are context markers, not work items, and should never appear in the ready-to-work list.


Merge: quartz-1767106247671

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


Run check-versions.sh - all must pass

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

All versions must match 0.30.4.

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


Cross-project dependency support

Enable tracking dependencies across project boundaries.

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

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

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


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


Widespread double JSON encoding bug in daemon mode RPC calls

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

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

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

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

All commands now tested and working correctly with daemon mode.


Update local installation

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


Full event test


Add 'bd status' command for issue database overview

Implement a bd status command that provides a quick snapshot of the issue database state, similar to how git status shows working tree state.

Expected output: Show summary including counts by state (open, in-progress, blocked, closed), recent activity (last 7 days), and quick overview without needing multiple queries.

Example output showing issue counts, recent activity stats, and pointer to bd list for details.

Proposed options: --all (show all issues), --assigned (show issues assigned to current user), --json (JSON format output)

Use cases: Quick project health check, onboarding for new contributors, integration with shell prompts or CI/CD, daily standup reference


Update CHANGELOG.md with detailed release notes.

Add a new section after [Unreleased]:
```markdown
## [0.37.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.

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


Update CHANGELOG.md for {{version}}

In CHANGELOG.md:

1. Change `## [Unreleased]` section header to `## [{{version}}] - {{date}}`
2. Add new empty `## [Unreleased]` section above it
3. Review and clean up the changes list

Format:
```markdown
## [Unreleased]

## [{{version}}] - {{date}}

### Added
- ...

### Changed
- ...

### Fixed
- ...
```


Merge3Way public API does not expose TTL parameter

The public Merge3Way() function in merge.go does not allow callers to configure the tombstone TTL. It hard-codes the default via merge3WayWithTTL(). While merge3WayWithTTL() exists, it is unexported (lowercase). This means the CLI and tests cannot configure TTL at merge time. Use cases: testing with different TTL values, per-repository TTL configuration, debugging with short TTL, supporting --ttl flag in bd merge command (mentioned in design doc bd-zvg). Recommendation: Export Merge3WayWithTTL (rename to uppercase). Files: internal/merge/merge.go:77, 292-298


Code Review Round 2: Beads Refactoring

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

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

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


Fix bd sync race condition with daemon causing dirty working directory

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

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

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

Related: bd-c83r (multiple daemon prevention)


Test coordinator display


Code review: bd mol bond --ref and bd activity (bd-xo1o work)

Review dave's recent commits for bd-xo1o (Dynamic Molecule Bonding):

## Commits to Review
- ee04b1ea: feat: add dynamic molecule bonding with --ref flag (bd-xo1o.1)
- be520d90: feat: add bd activity command for real-time state feed (bd-xo1o.3)

## Review Focus
1. Code quality and correctness
2. Error handling
3. Edge cases
4. Test coverage
5. Documentation

## Deliverables
- File beads for any issues found
- Note any concerns or suggestions
- Verify the implementation matches the bd-xo1o epic requirements


WaitsFor directive: Fanout gate for dynamic children

Implement WaitsFor directive for molecules that spawn dynamic children.

## Syntax
```markdown
## Step: aggregate
Collect outcomes from all dynamically-bonded children.
WaitsFor: all-children
Needs: survey-workers
```

## Behavior
1. Parse WaitsFor directive during molecule step parsing
2. Track which steps spawn dynamic children (the spawner)
3. Gate step waits until ALL children of the spawner complete
4. Works with bd ready - gate step not ready until children done

## Gate Types
- `WaitsFor: all-children` - Wait for all dynamic children
- `WaitsFor: any-children` - Proceed when first child completes (future)
- `WaitsFor: <step-ref>.children` - Wait for specific spawner's children

## Integration
- bd ready should skip gate steps until children complete
- bd show <mol> should display gate status and child count


Upgrade beads-mcp to 0.33.2

Upgrade the MCP server via pip:

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

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


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.


Unify template.go and mol.go under bd mol

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


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.


Work on beads-wkt: Output control parameters for MCP tool...

Work on beads-wkt: Output control parameters for MCP tools (GH#622). Add brief, fields, max_description_length params to ready/list/show. When done, submit MR (not PR) to integration branch for Refinery.


Test child task


gt sling --create fails to create polecat before resolving pane

gt sling --create tries to resolve pane BEFORE creating the polecat, causing failure.

Expected: gt sling gt-xyz gastown/newcat --create should:
1. Create polecat if not exists
2. Start session
3. Hook work
4. GUPP triggers autonomous execution

Actual: Fails with 'getting pane for gt-gastown-newcat: exit status 1'

Location: internal/cmd/sling.go - need to reorder operations


Merge: jasper-1767138512869

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


Test refile command

Testing bd refile


Enrich MutationEvent with title and assignee

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


test pin issue


bd formula add: Import formula to local catalog

Import a formula file to the local catalog (search path).

**Replaces**: "bd mol promote" (proto-to-proto concept is obsolete with ephemeral protos)

## Usage
```bash
# Add a formula file to project catalog
bd formula add my-workflow.formula.json

# Add to user-level catalog
bd formula add my-workflow.formula.json --scope user

# Add from URL
bd formula add https://example.com/workflow.formula.json
```

## Implementation
1. Parse the formula file (validate JSON structure)
2. Determine target directory based on scope:
   - project: .beads/formulas/
   - user: ~/.beads/formulas/
   - town: ~/gt/.beads/formulas/
3. Copy/download formula to target
4. Verify it is loadable: bd formula show <name>

## Flags
- `--scope <level>` - Where to add (project|user|town, default: project)
- `--name <name>` - Override formula name (default: from file)

## Note
This is for manually adding formulas. For GitHub-hosted formulas, use:
  bd mol install github.com/org/formula-name


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


Monitor GoReleaser CI job

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

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

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

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

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


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.


mol-beads-release

Release checklist for beads version 0.99.0.

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

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

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

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

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

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

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

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

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

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

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

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

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

Needs: update-changelog

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

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

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

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

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

Needs: run-tests

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

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

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

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

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

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

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

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

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

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

# Check npm
npm view @beads/bd version

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

Needs: wait-for-ci

## Step: update-local
Update local installations.

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

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

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

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

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

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

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

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

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

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


Agent beads: structured labels for filtering

## Agent Beads Need Queryable Labels

Currently agent beads have role_type/rig in description text, not as labels. This breaks @group resolution in gt mail.

## Current State

```json
{
  "id": "gt-gastown-witness",
  "issue_type": "agent",
  "description": "...\\nrole_type: witness\\nrig: gastown\\n..."
}
```

Cannot query: `bd list --type=agent --label=role_type:witness` returns nothing.

## Required

Agent bead creation should add labels:
- `role_type:<type>` (witness, refinery, crew, polecat, dog, mayor, deacon)
- `rig:<rig>` (gastown, beads, or "town" for town-level)

## Where to Fix

gt polecat/crew/agent creation commands should add labels:
```bash
bd create --type=agent ... --labels="role_type:witness,rig:gastown"
```

## Queries Enabled

```bash
bd list --type=agent --label=role_type:witness    # All witnesses
bd list --type=agent --label=rig:gastown          # All gastown agents
bd list --type=agent --label=role_type:dog        # All dogs
```

## Acceptance
- New agent beads created with role_type/rig labels
- Existing agent beads backfilled (one-time script)
- @group patterns work in gt mail router


Add composite index (status, priority) for common list queries

SearchIssues and GetReadyWork frequently filter by status and sort by priority. Currently uses two separate indexes.

**Common query pattern (queries.go:1646-1647):**
```sql
WHERE status = ?
ORDER BY priority ASC, created_at DESC
```

**Problem:** Index merge or full scan when both columns are used.

**Solution:** Add migration:
```sql
CREATE INDEX IF NOT EXISTS idx_issues_status_priority ON issues(status, priority);
```

**Expected impact:** Faster bd list, bd ready with filters. Particularly noticeable at 10K+ issues.


Add 'bd where' command to show active beads location

## Problem

When using redirects, it's unclear which beads database is actually being used. This caused debugging confusion during the v0.39.0 release.

## Proposed Solution

Add `bd where` command:

```bash
bd where
# → /Users/stevey/gt/beads/mayor/rig/.beads (via redirect from crew/dave/.beads)

bd where --json
# → {"path": "...", "redirected_from": "...", "prefix": "bd"}
```

## Alternatives Considered

- `bd info --beads-path` - but `bd where` is more discoverable
- Just fix the UX so it's never confusing - defense in depth is better

## Desire Paths

This supports the 'desire paths' design principle: make the system's behavior visible so agents can debug and understand it.


Test prefix bd-


bd list --parent: filter by parent issue

Add --parent flag to bd list to filter issues by parent.

Example:
```bash
bd list --parent=gt-h5n --status=open
```

Would show all open children of gt-h5n.

Useful for:
- Checking epic progress
- Finding swarmable work within an epic
- Molecule step listing


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

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

Extract to a shared helper function like:

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

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


Add default limit to bd list (50 issues)

Add a sensible default limit of 50 issues to bd list output, matching patterns from gh cli (30) and jira-cli (50).

- Default to 50 when no --limit specified
- --limit 0 means unlimited (explicit override)
- Protects both agents and humans from overwhelming output

This is independent of the filter change but complementary.

## Implementation Plan

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

1. **Change default limit** (init function, ~line 887):
```go
// Currently: listCmd.Flags().IntP("limit", "n", 0, "Limit results")
// Change to:
listCmd.Flags().IntP("limit", "n", 50, "Limit results (0 for unlimited)")
```

2. **Update help text**:
```go
"Limit results (default: 50, use 0 for unlimited)"
```

3. **Handle explicit 0** in Run function:
```go
// --limit 0 means unlimited
if cmd.Flags().Changed("limit") && limit == 0 {
    filter.Limit = 0 // No limit applied
} else if limit > 0 {
    filter.Limit = limit
} else {
    filter.Limit = 50 // Default
}
```

### Testing
- `bd list` shows max 50 issues
- `bd list --limit 10` shows max 10
- `bd list --limit 0` shows unlimited
- Verify output indicates truncation: "Showing 50 of 499 issues (use --limit 0 for all)"

### Documentation
- Note in help: default changed from unlimited to 50


Document pinned status in CLI_REFERENCE.md

## Task

The `pinned` status is a valid issue status but is not documented in CLI_REFERENCE.md.

## Current Status Values (from types.go:295-301)

- open
- in_progress
- blocked
- deferred
- closed
- tombstone
- **pinned** ← undocumented

## Definition

`StatusPinned Status = "pinned" // Persistent bead that stays open indefinitely (bd-6v2)`

## Where to Document

Add to the 'Issue Types' or create a new 'Issue Statuses' section in docs/CLI_REFERENCE.md.

## Related

The pinned status is heavily used by gt for hook management (`--status=pinned`).


GH#505: Add bd reset/wipe command

Add command to cleanly reset/wipe beads database. User reports painful manual process to start fresh. See GitHub issue #505.


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


Mol Mall: Formula marketplace using GitHub as backend

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

## Architecture Update (Dec 2025)

**Formulas are the sharing layer.** With ephemeral protos (bd-rciw), the architecture is:

```
Formulas ──cook──→ [ephemeral proto] ──pour/wisp──→ Mol/Wisp
    ↑                                                    │
    └────────────────── distill ─────────────────────────┘
```

- **Formulas**: JSON source files (.formula.json) - the thing you share
- **Protos**: Transient compilation artifacts - auto-deleted after use
- **Mols/Wisps**: Execution instances - not shared directly

**Key operations:**
- `bd distill <mol-id>` → Extract formula from completed work
- `bd mol publish <formula>` → Share to GitHub
- `bd mol install <url>` → Fetch from GitHub
- `bd pour <formula>` → Cook and spawn (proto is ephemeral)

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

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

## ID Namespace

| Entity | ID Format | Example |
|--------|-----------|---------|
| Formula (GitHub) | `github.com/org/repo` | `github.com/anthropics/mol-code-review` |
| Installed formula | `mol-name` | `mol-code-review` |
| Poured instance | `<db>-mol-xxx` | `bd-mol-b8c` |

Deferred - focusing on Christmas launch first


Audit and fix JSONL filename mismatches across all repo clones

## Problem

Multiple clones of repos are configured with different JSONL filenames (issues.jsonl vs beads.jsonl), causing:
1. JSONL files to be resurrected after deletion (one clone pushes issues.jsonl, another pushes beads.jsonl)
2. Agents unable to see issues filed by other agents after sync
3. Merge conflicts and data inconsistencies

## Root Cause

When repos were "bd doctored" or initialized at different times, some got issues.jsonl (old default) and others got beads.jsonl (Beads repo specific). These clones push their respective files, creating duplicates.

## Task

Scan all repo clones under ~/src/ (1-2 levels deep) and standardize their JSONL configuration.

### Step 1: Find all beads-enabled repos

```bash
# Find all directories named 'beads' at levels 1-2 under ~/src/
find ~/src -maxdepth 2 -type d -name beads
```

### Step 2: For each repo found, check configuration

For each directory from Step 1, check:
- Does `.beads/metadata.json` exist?
- What is the `jsonl_export` value?
- What JSONL files actually exist in `.beads/`?
- Are there multiple JSONL files (problem!)?

### Step 3: Create audit report

Generate a report showing:
```
Repo Path                           | Config        | Actual Files           | Status
----------------------------------- | ------------- | ---------------------- | --------
~/src/beads                         | beads.jsonl   | beads.jsonl            | OK
~/src/dave/beads                    | issues.jsonl  | issues.jsonl           | MISMATCH
~/src/emma/beads                    | issues.jsonl  | issues.jsonl, beads.jsonl | DUPLICATE!
```

### Step 4: Determine canonical name for each repo

For repos that are the SAME git repository (check `git remote -v`):
- Group them together
- Determine which JSONL filename should be canonical (majority wins, or beads.jsonl for the beads repo itself)
- List which clones need to be updated

### Step 5: Generate fix script

Create a script that for each mismatched clone:
1. Updates `.beads/metadata.json` to use the canonical name
2. If JSONL file needs renaming: `git mv .beads/old.jsonl .beads/new.jsonl`
3. Removes any duplicate JSONL files: `git rm .beads/duplicate.jsonl`
4. Commits the change
5. Syncs: `bd sync`

### Expected Output

1. Audit report showing all repos and their config status
2. List of repos grouped by git remote (same repository)
3. Fix script or manual instructions for standardizing each repo
4. Verification that after fixes, all clones of the same repo use the same JSONL filename

### Edge Cases

- Handle repos without metadata.json (use default discovery)
- Handle repos with no git remote (standalone/local)
- Handle repos that are not git repositories
- Don't modify repos with uncommitted changes (warn instead)

### Success Criteria

- All clones of the same git repository use the same JSONL filename
- No duplicate JSONL files in any repo
- All configurations documented in metadata.json
- bd doctor passes on all repos


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


Fix FindBeadsDir to prioritize main repo .beads for worktrees

The FindBeadsDir function should prioritize finding .beads in the main repository root when accessed from a worktree, rather than finding worktree-local .beads directories. This ensures proper sharing of the database across all worktrees.


Test message

This is a test message body


Priority: refactor mol.go then bd squash

Two tasks:

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

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

- Mayor


Delete server_cache_storage.go

Remove the entire cache implementation file (~286 lines)


Swarm: Test epic for duplicate swarm

Swarm molecule orchestrating epic bd-4d9d.

Epic: bd-4d9d
Coordinator:


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

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


Commit release

Stage and commit all version changes.

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

Review the commit to ensure all expected files are included.


Refactor duplicate JSONL-from-git parsing code

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

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

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

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


bd admin compact --older-than=0 ignores the flag, uses default 30 days

When running bd admin compact --prune --older-than=0, the output says 'No expired tombstones to prune (TTL: 30 days)' - the flag value is ignored.

Root cause: In runCompactPrune() in compact_tombstone.go:180, the check 'if compactOlderThan > 0' means that --older-than=0 is treated the same as not passing the flag, defaulting to 30 days.

Fix: Change the default flag value in compact.go:785 to -1 (sentinel for 'use default'), and treat 0 as 'expire all tombstones' by passing a negative TTL to pruneExpiredTombstones().

Moved from gt-65gwa (gastown) - was filed in wrong rig.


HOP-compatible schema additions

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

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

## P1 Changes (Must Have Before Launch)

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

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

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

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

## P2 Changes (Should Have)

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

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

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

## P3 Changes (Nice to Have)

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

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

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


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

## Problem

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

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

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

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


Release v{{version}}

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


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)


Add {{version}} to versionChanges in info.go

Add new entry at TOP of versionChanges in cmd/bd/info.go with release notes from CHANGELOG.md. Must do before bump-version.sh --commit.


Improve clean vs cleanup command naming/documentation

Clarify the difference between bd clean and bd cleanup to reduce user confusion.

**Current state:**
- bd clean: Remove temporary artifacts (.beads/bd.sock, logs, etc.)
- bd cleanup: Delete old closed issues from database

**Options:**
1. Rename for clarity:
   - bd clean → bd clean-temp
   - bd cleanup → bd cleanup-issues

2. Keep names but improve help text and documentation

3. Add prominent warnings in help output

**Preferred approach:** Option 2 (improve documentation)
- Update short/long descriptions in commands
- Add examples to help text
- Update README.md
- Add cross-references in help output

**Files to modify:**
- cmd/bd/clean.go
- cmd/bd/cleanup.go
- README.md or ADVANCED.md


Simplify bd onboard to minimal AGENTS.md snippet pointing to bd prime

## Context
GH#604 raised concerns about bd onboard bloating AGENTS.md with ~100+ lines of static instructions that:
- Load every session whether beads is being used or not
- Get stale when bd upgrades
- Waste tokens

## Solution
Simplify `bd onboard` to output a minimal snippet (~2 lines) that points to `bd prime`:

```markdown
## Issue Tracking
This project uses beads (`bd`) for issue tracking.
Run `bd prime` for workflow context, or hooks auto-inject it.
```

## Rationale
- `bd prime` is dynamic, concise (~80 lines), and always matches installed bd version
- Hooks already auto-inject `bd prime` at session start when .beads/ detected
- AGENTS.md only needs to mention beads exists, not contain full instructions

## Implementation
1. Update `cmd/bd/onboard.go` to output minimal snippet
2. Keep `--output` flag for BD_GUIDE.md generation (may still be useful)
3. Update help text to explain the new approach


bd sync: redirect triggers 'External BEADS_DIR' mode, bypasses sync.branch

## Summary

When `.beads/redirect` points to a location in the same git repo, `bd sync` incorrectly triggers "External BEADS_DIR detected, using direct commit" mode, bypassing the configured `sync.branch` workflow.

## Environment

- beads version: 0.41.0
- Setup: bare repo with worktrees (Gas Town pattern)
- Redirect: `.beads/redirect` contains `../../mayor/rig/.beads`
- Config: `git config beads.sync.branch beads-sync`

## Expected Behavior

With `sync.branch = beads-sync` configured:
1. Export changes to JSONL
2. Commit to beads-sync branch (via worktree at `.git/beads-worktrees/beads-sync`)
3. Push/pull beads-sync
4. Import JSONL
5. **No commits on main or current working branch**

## Actual Behavior

```
$ bd sync
→ Exporting pending changes to JSONL...
→ External BEADS_DIR detected, using direct commit...  ← Wrong!
→ Commits to current branch (mayor-rig)
→ Tries to push to origin/main
```

The "External BEADS_DIR" detection takes precedence over sync.branch config.

## Root Cause (suspected)

In `cmd/bd/sync.go`, the redirect resolution likely marks the BEADS_DIR as "external" because the path crosses directory boundaries, even though:
1. It resolves to the same git repository
2. `sync.branch` is explicitly configured

## Suggested Fix

When determining sync mode, check:
```go
if syncBranch != "" && isInSameGitRepo(resolvedBeadsDir, cwd) {
    // Use sync.branch mode, not direct commit
    useSyncBranch = true
} else if isExternalBeadsDir {
    // Only use direct commit for truly external repos
    useDirectCommit = true
}
```

## Workaround

Created a `mayor-rig` branch that tracks origin/main so git operations succeed, but commits still land on wrong branch.

## Related

- Affects Gas Town multi-clone setups using redirects
- GH#810 was a related sync.branch issue (worktree → local copy direction)


Run bump-version.sh {{version}}

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


Update Homebrew formula

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


bd mol update: Check and update installed formulas

Check for newer versions of installed formulas and update them.

## Usage
```bash
bd mol update                    # Update all
bd mol update mol-code-review    # Update specific formula
bd mol update --check            # Just check, don't update
```

## Implementation
1. Read .beads/installed.json
2. For each installed formula:
   - Fetch latest tag from GitHub API
   - Compare with installed version
   - If newer: download, cook, update installed.json
3. Report what was updated

## Flags
- `--check` - Only check for updates, don't install
- `--force` - Reinstall even if up to date
- `--json` - Machine-readable output


Migration invariants should exclude external dependencies from orphan check

## Summary

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

## Location

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

## Current Code (buggy)

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

## Fix

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

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

## Reproduction

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

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

## Files to Modify

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

## Testing

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

# Sync should succeed
bd sync

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

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


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

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

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

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

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


Wisp operations should auto-bypass daemon

During the v0.39.1 release, every wisp operation required --no-daemon flag:

bd --no-daemon mol wisp create beads-release --var version=0.39.1
bd --no-daemon close bd-eph-xxx
bd --no-daemon mol burn bd-eph-bv2

Since wisps are ephemeral (Ephemeral=true) and never exported to JSONL, they are inherently local-only. The daemon cannot help with them anyway.

Proposal: When operating on bd-eph-* issues or wisp subcommands, auto-detect and bypass the daemon. This would:
- Reduce friction in wisp workflows
- Prevent accidental daemon use that would fail anyway
- Make the ephemeral nature more obvious


Merge: bd-dxtc

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


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

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


Add orphan detection migration

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


Test agent with fields


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


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


Revert/remove YAML workflow system

Revert the recent commit and remove all YAML workflow code:

1. `git revert aae8407a` (the commit we just pushed with workflow fixes)
2. Remove `cmd/bd/templates/workflows/` directory
3. Remove workflow.go or gut it to minimal stub
4. Remove WorkflowTemplate types from internal/types/workflow.go
5. Remove any workflow-related RPC handlers

Keep only minimal scaffolding if needed for the new template system.


gt spawn/crew setup should create .beads/redirect for worktrees

Crew clones and polecats need a .beads/redirect file pointing to the shared beads database (../../mayor/rig/.beads). Currently:

- redirect files can get deleted by git clean
- not auto-created during gt spawn or worktree setup
- missing redirects cause 'no beads database found' errors

Found missing in: gastown/joe, beads/zoey (after git clean)

Fix options:
1. gt spawn creates redirect during worktree setup
2. gt prime regenerates missing redirects
3. bd commands auto-detect worktree and find shared beads

This should be standard Gas Town rig configuration.


Add MCP server functions for repair commands

**Summary:** Added MCP server repair functions for agent dependency management, system validation, and pollution detection. Implemented across BdClientBase, BdCliClient, and daemon clients to enhance system diagnostics and self-healing capabilities.

**Key Decisions:**
- Expose repair_deps(), detect_pollution(), validate() via MCP server
- Create abstract method stubs with fallback to CLI execution
- Use @mcp.tool decorators for function registration

**Resolution:** Successfully implemented comprehensive repair command infrastructure, enabling more robust system health monitoring and automated remediation with full CLI and daemon support.

Implemented all three MCP server functions:

1. **repair_deps(fix=False)** - Find/fix orphaned dependencies
2. **detect_pollution(clean=False)** - Detect/clean test issues
3. **validate(checks=None, fix_all=False)** - Run comprehensive health checks

Changes:
- Added abstract methods to BdClientBase
- Implemented in BdCliClient (CLI execution)
- Added NotImplementedError stubs in BdDaemonClient (falls back to CLI)
- Created wrapper functions in tools.py
- Registered @mcp.tool decorators in server.py

All commands tested and working with --no-daemon flag.


Update molecular chemistry docs with discoveries

Document recent discoveries about molecule/epic relationship and lifecycle:

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

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

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

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

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


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


Create git tag v0.33.2

Create the release tag:

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

Verify: `git tag | grep 0.33.2`


Document labels-as-state pattern for operational state

Labels on role beads cache current operational state for fast queries.

Convention: <dimension>:<value>
- patrol:muted / patrol:active
- mode:degraded / mode:normal
- status:idle / status:working

Pattern:
1. Create event bead (full context, immutable history)
2. Update role bead labels (current state cache)

No schema change needed - labels already exist. This task:
- Document the convention in beads docs
- Consider helper: bd state <role> <dimension> (queries label)
- Consider helper: bd set-state <role> <dimension>=<value> (creates event + updates label)

Events are source of truth. Labels are cache.


Move pour/ephemeral under bd mol subcommand

For consistency, bd pour and bd ephemeral should become bd mol pour and bd mol ephemeral:

Current:
  bd mol list                  # Available protos
  bd mol show <id>             # Proto details
  bd pour <proto>              # Create mol ← sticks out
  bd ephemeral <proto>         # Create ephemeral ← sticks out
  bd mol bond <proto> <parent> # Attach to existing mol
  bd mol squash <id>           # Condense to digest
  bd mol burn <id>             # Discard

Proposed:
  bd mol list
  bd mol show <id>
  bd mol pour <proto>          # Moved under mol
  bd mol ephemeral <proto>     # Moved under mol
  bd mol bond <proto> <parent>
  bd mol squash <id>
  bd mol burn <id>

All molecule operations should be under bd mol for discoverability and consistency.


Merge: opal-1767138520240

branch: polecat/opal-1767138520240
target: main
source_issue: opal-1767138520240
rig: beads


Add sync resilience guardrails for forced pushes and prefix mismatches

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


Read operations should not modify database file

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