mol-beads-release

Release checklist for beads version {{version}}.

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

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

Add a new VersionChange entry at the top of versionChanges slice:
```go
{
    Version: "{{version}}",
    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
## [{{version}}] - 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 {{version}}
```

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 v{{version}}"
```

Needs: run-tests

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

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

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 v{{version}}

# 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 with proper codesigning (macOS).

```bash
# Build from source in mayor/rig (canonical build location)
cd ~/gt/beads/mayor/rig
git pull
go build -o bd ./cmd/bd

# Sign and install (macOS requires codesigning to avoid "Killed: 9")
# Uses fix-gt script which handles both gt and bd binaries
fix-gt

# Or manually sign if fix-gt not available:
# xattr -cr bd && codesign -f -s - bd
# cp bd ~/go/bin/bd && codesign -f -s - ~/go/bin/bd
# cp bd ~/.local/bin/bd && codesign -f -s - ~/.local/bin/bd

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

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

Verify: `bd --version` shows {{version}}
Needs: verify-release

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

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

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

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

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


Merge: bd-ykd9

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


Code smell: Repeated validation patterns could be centralized

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

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

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


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.


Update version to 0.33.2 in version.go

Edit cmd/bd/version.go line 17:

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

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


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

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

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

## Functions Needing Tests

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

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

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

## Implementation Guide

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

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

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

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

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

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


Correction: Pinned field already in v0.31.0

Quick correction - the Pinned field is already in the current bd v0.31.0:

```go
// In beads internal/types/types.go
Pinned bool `json:"pinned,omitempty"`
```

So you just need to:
1. Add `Pinned bool `json:"pinned,omitempty"`` to BeadsMessage in types.go
2. Sort pinned messages first in listBeads() after fetching

No migration needed - the field is already there.

-- Mayor


Deferred HOP schema additions (P2/P3)

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

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

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


bd mol burn should work on mols, not just wisps

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

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

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

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

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


Gate eval: Add error visibility for gh CLI failures

Currently evalGHRunGate and evalGHPRGate silently swallow errors when the gh CLI fails (lines 738-741, 780-783 in gate.go).

This makes debugging difficult - if gh isn't installed, auth fails, or network issues occur, gates silently stall forever with no indication of why.

Options:
1. Add debug logging when gh fails
2. Return error info in the skipped list for aggregate reporting
3. Add a --verbose flag to gate eval that shows failures

Low priority since the fail-safe behavior (don't close gate on error) is correct.


Tier 5 (Future): JSONL archive rotation

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

## Design

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

## Commands

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

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

## Config

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

## Considerations

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

## Priority

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


Create git tag v{{version}}

Create the release tag:

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

Verify: `git tag | grep {{version}}`


Code Review: Beads Refactoring Sprint

Epic for code smell fixes identified during Beads code review.

## Scope
11 issues covering:
- P2: Duplicated code in cook.go (2 issues)
- P3: Long functions and God Objects (5 issues)
- P4: Technical debt and cleanup (4 issues)

## Approach
Swarm with polecats, each taking 1-2 issues. All changes should:
1. Pass existing tests
2. Not change external behavior
3. Improve code maintainability

## Success Criteria
All 11 child issues closed with passing CI.


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


bd mol bond --pour should set Wisp=false

In mol_bond.go bondProtoMol(), opts.Wisp is hardcoded to true (line 392). This ignores the --pour flag. When user specifies --pour to make an issue persistent, the Wisp field should be false so the issue is not marked for bulk deletion.

Current behavior:
- --pour flag correctly selects regular storage (not wisp storage)
- But opts.Wisp=true means spawned issues are still marked for cleanup when closed

Expected behavior:
- --pour should set Wisp=false so persistent issues are not auto-cleaned

Comparison with mol_spawn.go (line 204):
  wisp := !pour  // Correctly respects --pour flag
  result, err := spawnMolecule(ctx, store, subgraph, vars, assignee, actor, wisp)

Fix: Pass pour flag to bondProtoMol and set opts.Wisp = !pour


Commit and push release

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


Release v0.33.2

Version bump workflow for beads release 0.33.2.

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

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

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

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


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


Feature: bd swarm list - discover existing swarm molecules

No command to list existing swarm molecules.

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

```
bd swarm list

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

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


gt mail send: support reading message body from stdin

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

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

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

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


Test actor flag


Commit and push release


Remove molecule.go (simple instantiation)

molecule.go uses is_template field for simple single-issue cloning. This is too simple for what molecules should be - full DAG orchestration. The use case is covered by bd mol bond with a single-issue molecule. Delete molecule.go and its commands.


Add unit tests for reset package

Comprehensive unit tests for internal/reset package.

## Test Cases

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

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

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

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

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

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

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


Wait for CI to pass

Monitor GitHub Actions - all checks must pass before release artifacts are built


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.


Test parent task


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.


Remove all deletions.jsonl code except migration

There's deletions manifest code spread across the entire codebase that should have been removed after tombstone migration:

Files with deletions code (non-migration):
- internal/deletions/ - entire package
- cmd/bd/sync.go - 25+ references, auto-compact, sanitize
- cmd/bd/delete.go - dual-writes to deletions.jsonl
- internal/importer/importer.go - checks deletions manifest
- internal/syncbranch/worktree.go - merges deletions.jsonl
- cmd/bd/doctor/fix/sync.go - cleanupDeletionsManifest
- cmd/bd/doctor/fix/deletions.go - HydrateDeletionsManifest
- cmd/bd/integrity.go - checks deletions for data loss
- cmd/bd/deleted.go - entire command
- cmd/bd/compact.go - pruneDeletionsManifest
- cmd/bd/doctor.go - checkDeletionsManifest
- Plus many more

Action: Aggressively remove all non-migration deletions code. Tombstones are the only deletion mechanism now.


Run tests and verify build

Run the test suite to verify nothing is broken:

```bash
./scripts/test.sh
```

Or manually:
```bash
go build ./cmd/bd/...
go test ./...
```

Fix any failures before proceeding.


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


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


Auto-repair stale merge driver configs with invalid placeholders

Old bd versions (<0.24.0) installed merge driver with invalid placeholders %L %R instead of %A %B. Add detection to bd doctor --fix: check if git config merge.beads.driver contains %L or %R, auto-repair to 'bd merge %A %O %A %B'. One-time migration for users who initialized with old versions.


enhance 'bd status' to show recent activity

It would be nice to be able to quickly view the last N changes in the database, to see wha's recently been worked on. I'm imagining something like 'bd status activity'.


Update CHANGELOG.md

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

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

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

The bump script will stamp the date automatically.


Add metadata and thread_id columns to dependencies table

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


Merge: bd-u2sc.3

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


Fix JSON errors in dep.go (dependency commands)

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

Commands affected:
- depAddCmd
- depRemoveCmd
- depTreeCmd
- depCyclesCmd

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


bd repair: Add --json output flag

bd repair lacks --json output for machine-readable results, unlike most other bd commands.

Add --json flag that outputs structured JSON with:
- orphan counts by type
- list of orphaned references
- repair status (success/failure)
- backup path


Centralize error handling patterns in storage layer

80+ instances of inconsistent error handling across sqlite.go with mix of %w, %v, and no wrapping.

Location: internal/storage/sqlite/sqlite.go (throughout)

Problem:
- Some use fmt.Errorf("op failed: %w", err) - correct wrapping
- Some use fmt.Errorf("op failed: %v", err) - loses error chain
- Some return err directly - no context
- Hard to debug production issues
- Can't distinguish error types

Solution: Create internal/storage/sqlite/errors.go:
- Define sentinel errors (ErrNotFound, ErrInvalidID, etc.)
- Create wrapDBError(op string, err error) helper
- Convert sql.ErrNoRows to ErrNotFound
- Always wrap with operation context

Impact: Lost error context; inconsistent messages; hard to debug

Effort: 5-7 hours


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

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

Current coverage: 23.9%
Target coverage: 50%


Merge: bd-m8ro

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


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

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

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

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

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

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


Formula parser: YAML schema for .formula.yaml files

Implement formula parsing from YAML:
- Define YAML schema for .formula.yaml files
- Parse steps, compose rules, vars
- Support extends for formula inheritance
- Validate formula structure

Schema should support:
- formula: name
- description: text
- version: number
- type: workflow|expansion|aspect
- vars: variable definitions
- steps: step definitions
- compose: composition rules

Gas Town tracking: gt-8tmz.12


Add Validation type and validations field to Issue

Add Validation struct (Validator *EntityRef, Outcome string, Timestamp time.Time, Score *float32) and Validations []Validation field to Issue. Tracks who validated/approved work completion. Core to HOP proof-of-stake concept - validators stake reputation on approvals.


Add daemon-side delete operation tests

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

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

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


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

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

Current coverage: 37.1%
Target coverage: 60%


bd close hooks: context check and notifications

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

## Scope (MVP)

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

## Implementation

### 1. Config Schema

Add to internal/configfile/config.go:

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

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

Add `Hooks HooksConfig` field to Config struct.

### 2. Hook Execution

Create internal/hooks/close_hooks.go:

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

### 3. Integration Point

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

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

### 4. Example Config

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

## Environment Variables

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

## Testing

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

## Files to Create/Modify

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

## Out of Scope (Future)

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


Verify GitHub release

Check the GitHub releases page.

https://github.com/steveyegge/beads/releases/tag/v0.99.0-test

Verify:
- Release created
- Binaries attached (linux, darwin, windows)
- Checksums present


Phase 1.2 follow-up: Clarify format storage

Phase 1.2 created the bdt executable structure but issues.toon is currently stored in JSONL format, not TOON format.

This is intentional for now:
- Phase 1.2 (bd-jv4w): Just infrastructure - separate binary, separate directory
- Phase 1.3 (bd-j0tr): Implement actual TOON encoding/writing

For now, keep as-is: filename '.toon' signals intent, content is JSONL (interim format). Phase 1.3 will switch to actual TOON.


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

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


Investigate TestRoutingIntegration failure

TestRoutingIntegration/maintainer_with_SSH_remote failed during pre-commit check with "expected role maintainer, got contributor".
This occurred while running `go test -short ./...` on darwin/arm64.
The failure appears unrelated to storage/sqlite changes.
Need to investigate if this is a flaky test or environmental issue.


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.


Work on gt-8tmz.31: Formula validation specification. Wri...

Work on gt-8tmz.31: Formula validation specification. Write docs/formula-validation.md specifying all validation rules for formulas. When done: 1) bd close gt-8tmz.31, 2) bd sync, 3) git push, 4) gt mq submit


Verify PyPI package

Confirm PyPI package published.

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

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

Should show 0.39.0.


Update CHANGELOG header

Change [Unreleased] to [{{version}}] - {{date}} and add new empty [Unreleased] section above it.


Technical debt: Multiple deprecated command aliases still exist

attached_args: Remove deprecated command aliases

Several deprecated command aliases are still present in the codebase:

1. cmd/bd/relate.go:51 - 'bd relate' deprecated for 'bd dep relate'
2. cmd/bd/relate.go:56 - 'bd unrelate' deprecated for 'bd dep unrelate'
3. cmd/bd/daemons.go:618 - 'bd daemons' deprecated for 'bd daemon <subcommand>'
4. cmd/bd/migrate_hash_ids.go:433 - alias for 'bd migrate hash-ids'
5. cmd/bd/migrate_tombstones.go:352 - alias for 'bd migrate tombstones'
6. cmd/bd/migrate_sync.go:68 - alias for 'bd migrate sync'
7. cmd/bd/migrate_issues.go:717 - alias for 'bd migrate issues'
8. cmd/bd/comments.go:214 - deprecated for 'bd comments add'
9. cmd/bd/template.go:64,82,147,226 - multiple template commands deprecated for 'bd mol'
10. cmd/bd/admin_aliases.go - multiple admin aliases deprecated
11. cmd/bd/detect_pollution.go:24 - deprecated for 'bd doctor --check=pollution'

Consider:
1. Setting a deprecation timeline (e.g., remove in v2.0)
2. Adding warnings when deprecated commands are used
3. Documenting migration path in CHANGELOG

Location: Various files in cmd/bd/


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.


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

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

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

Expected: The sender field should use BEADS_AGENT_NAME when set.

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

Related: gt mail routing integration with Gas Town


Remove dead test code in info_test.go

Code health review found cmd/bd/info_test.go has two tests permanently skipped:

- TestInfoCommand
- TestInfoCommandNoDaemon

Both skip with: 'Manual test - bd info command is working, see manual testing'

These are essentially dead code. Either automate them or remove them entirely.


Time-dependent worktree detection tests may be flaky


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 all code to use dependencies API for edges

Find and update all code that reads/writes:
- replies_to field -> use dependency API
- relates_to field -> use dependency API
- duplicate_of field -> use dependency API
- superseded_by field -> use dependency API

Commands affected: bd mail, bd relate, bd duplicate, bd supersede, bd show, etc.


Remove bd mol spawn - use pour/wisp only

Remove the spawn command from bd mol. Replace with:

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

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

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

Gas Town tracking: gt-ingm.6


Add refs field for cross-references with relationship type

The refs field enables rich relationships beyond parent/child and depends-on.

Structure:
  refs: [{id: string, rel: string}]

Relationship types:
- until: active until target closes
- caused_by: triggered by target
- relates_to: general association
- supersedes: replaces target
- validates: approval relationship

Example:
  refs:
    - id: gt-abc1
      rel: until
    - id: hq-xyz9
      rel: caused_by

Enables:
- bd show gt-abc1 --refs (what references this?)
- Conditional state (muted UNTIL issue closes)
- Audit trails (this event CAUSED_BY that issue)


Push release v{{version}} to remote

Push the commit and tag:

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

Verify on GitHub that the tag appears in releases.


Daemon should be singleton and aggressively kill stale instances

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

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

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

**Investigation 2025-12-21:**

The singleton mechanism is already implemented and working correctly:

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

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

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

Consider closing as RESOLVED or clarifying the original scenario.


Move relate/unrelate under dep command

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


Systematic bd sync stability investigation

## Context

bd sync has chronic instability issues that have persisted since inception:
- issues.jsonl is always dirty after push
- bd sync often creates messes requiring manual cleanup
- Problems escalating despite accumulated bug fixes
- Workarounds are getting increasingly draconian

## Goal

Systematically observe and diagnose bd sync failures rather than applying band-aid fixes.

## Approach

1. Start fresh session with latest binary (all fixes applied)
2. Run bd sync and carefully observe what happens
3. Document exact sequence of events when things go wrong
4. File specific issues for each discrete problem identified
5. Track the root causes, not just symptoms

## Test Environment

- Fresh clone or clean state
- Latest bd binary with all bug fixes
- Monitor both local and remote JSONL state
- Check for timing issues, race conditions, merge conflicts

## Success Criteria

- Identify root causes of sync instability
- Create actionable issues for each problem
- Eventually achieve stable bd sync (no manual intervention needed)


Tier 1: Ephemeral repo routing

Add routing.ephemeral config option to route ephemeral=true issues to separate location.

## Changes Required

1. Add `routing.ephemeral` config option (default: empty = disabled)
2. Update routing logic in `determineRepo()` to check ephemeral flag
3. Update `bd create` to respect ephemeral routing
4. Update import/export for multi-location support
5. Ephemeral repo can be:
   - Separate git repo (~/.beads-ephemeral)
   - Non-git directory (just filesystem)
   - Same repo, different branch (future)

## Config

```bash
bd config set routing.ephemeral "~/.beads-ephemeral"
```

## Acceptance Criteria

- `bd create "test" --ephemeral` creates in ephemeral repo when configured
- `bd list` shows issues from both repos
- Ephemeral repo never synced to remote


Import reports misleading '0 created, 0 updated' when actually importing all issues

When running 'bd import' on a fresh database (no existing issues), the command reports 'Import complete: 0 created, 0 updated' even though it successfully imported all issues from the JSONL file.

**Steps to reproduce:**
1. Delete .beads/beads.db
2. Run: bd import .beads/issues.jsonl
3. Observe output: 'Import complete: 0 created, 0 updated'
4. Run: bd list
5. Confirm: All issues are actually present in the database

**Expected behavior:**
Report the actual number of issues imported, e.g., 'Import complete: 523 created, 0 updated'

**Actual behavior:**
'Import complete: 0 created, 0 updated' (misleading - makes user think import failed)

**Impact:**
- Users think import failed when it succeeded
- Confusing during database sync operations (e.g., after git pull)
- Makes debugging harder (can't tell if import actually worked)

**Context:**
Discovered during VC session when syncing database after git pull. The misleading message caused confusion about whether the database was properly synced with the canonical JSONL file.


Add gate issue type to beads

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

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

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


Test coordinator display


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.


Preflight: Check git status

Ensure working tree is clean before starting release.

```bash
git status
```

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


Update go install bd to {{version}}

Rebuild and install bd to ~/go/bin:

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

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


Update hook templates to 0.30.4

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

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

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


Remove top-level comment alias

Remove 'comment' as top-level command. Users should use 'comments add' instead. Reduces command surface area.


Re: Thread Test 2

Great! Thread is working well.


Code smell: Inconsistent error handling patterns

Commands use a mix of error handling patterns:
- FatalError()
- FatalErrorRespectJSON()
- fmt.Fprintf(os.Stderr, ...) + os.Exit(1)
- Silent continue
- Inconsistent resource cleanup (result.Close())

**Example in show.go lines 89-146:**
Different error paths use different patterns, some close resources, some don't.

**Acceptance Criteria:**
- [ ] Document error handling contract
- [ ] Standardize on consistent pattern
- [ ] Use defer for resource cleanup
- [ ] Create helper for common fetch-and-handle pattern
- [ ] Tests pass


Add bd update --parent flag to change issue parent

Allow changing an issue's parent with bd update --parent <new-parent-id>. Useful for reorganizing tasks under different epics or moving issues between hierarchies. Should update the parent-child dependency relationship.


Restart running daemons

Kill and restart any running bd daemons to pick up new version: pkill -f 'bd daemon' && bd daemon --start


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.


Merge: topaz-1767138533986

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


Run bump-version.sh {{version}}

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


Test Molecule

A test molecule


Test createTombstone and deleteIssue wrappers

Add tests for the createTombstone and deleteIssue wrapper functions in cmd/bd/delete.go.

## Functions under test
- createTombstone (cmd/bd/delete.go:335) - Wrapper around SQLite CreateTombstone
- deleteIssue (cmd/bd/delete.go:349) - Wrapper around SQLite DeleteIssue

## Test scenarios for createTombstone
1. Successful tombstone creation
2. Tombstone with reason and actor tracking
3. Error when issue doesn't exist
4. Verify tombstone status set correctly
5. Verify audit trail recorded
6. Rollback/error handling

## Test scenarios for deleteIssue
1. Successful issue deletion
2. Error on non-existent issue
3. Verify issue removed from database
4. Error handling when storage backend doesn't support delete

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

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


Update info.go versionChanges

Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for test-squash


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)


Implement Jira issue timestamp comparison for sync

In cmd/bd/jira.go:633, there's a TODO for a full implementation that would fetch the Jira issue and compare timestamps to detect conflicts during sync.

File: cmd/bd/jira.go:633


bd dep tree doesn't display external dependencies

GetDependencyTree (dependencies.go:464-624) uses a recursive CTE that JOINs with the issues table, which means external refs (external:project:capability) are invisible in the tree output.

When an issue has an external blocking dependency, running 'bd dep tree <id>' won't show it.

Options:
1. Query dependencies table separately for external refs and display them as leaf nodes
2. Add a synthetic 'external' node type that shows the ref and resolution status
3. Document that external deps aren't shown in tree view (use bd show for full deps)

Lower priority since bd show <id> displays all dependencies including external refs.


Update info.go versionChanges

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


Add daemon RPC support for gate commands.

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

## Implementation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### 3. Register methods in daemon

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

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

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

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

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

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

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

## Files to Modify

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

## Testing

```bash
# Start daemon
bd daemon start

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

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

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


Merge: bd-l13p

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


bd repair command: fix orphaned foreign key refs


Issue lifecycle hooks (on-close, on-complete)

Add hooks that fire on issue state transitions, enabling automation like closing linked GitHub issues.

## Problem

We have `external_ref` to link beads issues to external systems (GitHub, Linear, Jira), but no mechanism to trigger actions when issues close. Currently:

```
bd-u2sc (external_ref: gh-692) closes → nothing happens
```

## Proposed Solution

### Phase 1: Shell Hooks

Add `.beads-hooks/on-close.sh` (and similar lifecycle hooks):

```bash
# .beads-hooks/on-close.sh
# Called by bd close with issue JSON on stdin
#\!/bin/bash
issue=$(cat)
external_ref=$(echo "$issue" | jq -r '.external_ref // empty')
if [[ "$external_ref" == gh-* ]]; then
    number="${external_ref#gh-}"
    gh issue close "$number" --repo steveyegge/beads \
      --comment "Completed via beads epic $(echo $issue | jq -r .id)"
fi
```

### Lifecycle Events

| Event | Trigger | Use Cases |
|-------|---------|-----------|
| `on-close` | Issue closed | Close external refs, notify, archive |
| `on-complete` | Epic children all done | Roll-up completion, close parent refs |
| `on-status-change` | Any status transition | Sync to external systems |

### Phase 2: Molecule Completion Handlers

Molecules could define completion actions:

```yaml
name: github-issue-tracker
on_complete:
  - action: shell
    command: gh issue close {{external_ref}} --repo {{repo}}
  - action: mail
    to: mayor/
    subject: "Epic {{id}} completed"
```

### Phase 3: Gas Town Integration

For full Gas Town deployments:
- Witness observes closures via beads events
- Routes to integration agents via mail
- Agents handle external system interactions

## Implementation Notes

- Hooks should be async (don't block bd close)
- Pass full issue JSON to hook via stdin
- Support hook timeout and failure handling
- Consider `--no-hooks` flag for bulk operations

## Related

- `external_ref` field already exists (GH#142)
- Cross-project deps: bd-h807, bd-d9mu
- Git hooks: .beads-hooks/ pattern established

## Use Cases

1. **GitHub integration**: Close GH issues when beads epic completes
2. **Linear sync**: Update Linear status when beads status changes
3. **Notifications**: Send mail/Slack when high-priority issues close
4. **Audit**: Log all closures to external system


Update LINTING.md with current baseline

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


CLI cleanup: Consolidate bd top-level commands

## Problem
bd has 76 top-level commands, making it hard to discover and remember.

## Proposed Changes

### Remove from top-level (nest instead):
- `bd cook` → `bd formula cook` (cooking is a formula operation)
- `bd pin` → already covered by `bd mol` commands
- `bd thanks` → `bd info --thanks` or just README
- `bd quickstart` → `bd help quickstart` or docs
- `bd detect-pollution` → `bd doctor --check=pollution`

### Consolidate migrations under `bd migrate`:
- `bd migrate-hash-ids` → `bd migrate hash-ids`
- `bd migrate-issues` → `bd migrate issues`
- `bd migrate-sync` → `bd migrate sync`
- `bd migrate-tombstones` → `bd migrate tombstones`

### Consolidate admin under `bd admin`:
- `bd cleanup` → `bd admin cleanup`
- `bd compact` → `bd admin compact`
- `bd reset` → `bd admin reset`

## Backwards Compatibility
Keep old commands as hidden aliases for one release cycle.


Update version to {{version}} in version.go

Edit cmd/bd/version.go line 17:

```go
Version = "{{version}}"
```

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


Merge: bd-4opy

branch: polecat/angharad
target: main
source_issue: bd-4opy
rig: beads


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


Command Set Standardization & Flag Consistency

Comprehensive improvements to bd command set based on 2025 audit findings.

## Background
See docs/command-audit-2025.md for detailed analysis.

## Goals
1. Standardize flag naming and behavior across all commands
2. Add missing flags for feature parity
3. Fix naming confusion
4. Improve consistency in JSON output

## Success Criteria
- All mutating commands support --dry-run (no --preview variants)
- bd update supports label operations
- bd search has filter parity with bd list
- Priority flags accept both int and P0-P4 format everywhere
- JSON output is consistent across all commands


Upgrade Homebrew bd

brew update && brew upgrade bd


bd doctor: detect issues referenced in commits but still open

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

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

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

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

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


Merge: bd-tvu3

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


Merge: bd-io8c

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


Consolidate daemon/daemons commands

Merge daemons functionality into daemon command with subcommands. Currently have both 'daemon' and 'daemons' at top level.


Refactor long SQLite test files

The SQLite test files have grown unwieldy. Review and refactor.

## Goals
- Break up large test files into focused modules
- Improve test organization by feature area
- Reduce test duplication
- Make tests easier to maintain and extend

## Areas to Review
- main_test.go (likely the largest)
- Any test files over 500 lines
- Shared test fixtures and helpers
- Test coverage gaps

## Approach
- Group tests by feature (CRUD, sync, queries, transactions)
- Extract common fixtures to test helpers
- Consider table-driven tests where appropriate
- Ensure each test file has clear focus

## Reference
See docs/dev-notes/ for any existing test audit notes


Add bd doctor --fix flag to automatically repair issues

Add bd doctor --fix flag to automatically repair detected issues.

## Current State
- Doctor checks return issues with "Fix" field containing fix instructions
- No automatic fix execution
- User must manually follow fix instructions

## Implementation

### 1. Add --fix flag to doctor.go
```go
// In cmd/bd/doctor.go init()
doctorCmd.Flags().Bool("fix", false, "Automatically fix detected issues")
doctorCmd.Flags().Bool("yes", false, "Skip confirmation prompts (use with --fix)")
```

### 2. Create fix registry (cmd/bd/doctor/fix/registry.go)
```go
package fix

// Fixer can automatically repair an issue
type Fixer interface {
    // CanFix returns true if this fixer handles the given check name
    CanFix(checkName string) bool
    // Fix attempts to repair the issue, returns error if failed
    Fix(ctx context.Context, issue *CheckResult) error
    // Description returns human-readable description of what will be fixed
    Description() string
}

var registry []Fixer

func Register(f Fixer) {
    registry = append(registry, f)
}

func GetFixer(checkName string) Fixer {
    for _, f := range registry {
        if f.CanFix(checkName) {
            return f
        }
    }
    return nil
}
```

### 3. Implement fixers (one per file)

**cmd/bd/doctor/fix/hooks.go**
```go
type HooksFixer struct{}

func (f *HooksFixer) CanFix(name string) bool {
    return name == "git-hooks" || name == "hooks-outdated"
}

func (f *HooksFixer) Fix(ctx context.Context, issue *CheckResult) error {
    return hooks.Install(".", true) // force reinstall
}

func (f *HooksFixer) Description() string {
    return "Reinstall git hooks"
}

func init() {
    Register(&HooksFixer{})
}
```

**cmd/bd/doctor/fix/sync_branch.go**
```go
type SyncBranchFixer struct{}

func (f *SyncBranchFixer) CanFix(name string) bool {
    return name == "sync-branch-missing" || name == "sync-branch-diverged"
}

func (f *SyncBranchFixer) Fix(ctx context.Context, issue *CheckResult) error {
    // Reset to remote or create branch
    return syncbranch.ResetToRemote(ctx, repoRoot, branch, jsonlPath)
}
```

**cmd/bd/doctor/fix/merge_driver.go**
```go
type MergeDriverFixer struct{}

func (f *MergeDriverFixer) CanFix(name string) bool {
    return name == "merge-driver-missing" || name == "merge-driver-outdated"
}

func (f *MergeDriverFixer) Fix(ctx context.Context, issue *CheckResult) error {
    return setupMergeDriver()
}
```

### 4. Update doctor run logic

```go
func runDoctor(cmd *cobra.Command, args []string) {
    issues := runAllChecks()

    if \!fixFlag {
        // Existing behavior - just display issues
        displayIssues(issues)
        return
    }

    // Collect fixable issues
    var fixable []FixableIssue
    for _, issue := range issues {
        if fixer := fix.GetFixer(issue.CheckName); fixer \!= nil {
            fixable = append(fixable, FixableIssue{issue, fixer})
        }
    }

    if len(fixable) == 0 {
        fmt.Println("No automatically fixable issues found")
        return
    }

    // Show what will be fixed
    fmt.Printf("Found %d fixable issues:\n", len(fixable))
    for i, f := range fixable {
        fmt.Printf("  %d. [%s] %s\n", i+1, f.Issue.CheckName, f.Fixer.Description())
    }

    // Confirm unless --yes
    if \!yesFlag {
        fmt.Print("\nProceed with fixes? [Y/n] ")
        // ... read confirmation
    }

    // Apply fixes
    for _, f := range fixable {
        fmt.Printf("Fixing %s... ", f.Issue.CheckName)
        if err := f.Fixer.Fix(ctx, f.Issue); err \!= nil {
            fmt.Printf("FAILED: %v\n", err)
        } else {
            fmt.Println("OK")
        }
    }
}
```

## Fixable Checks (Initial Set)

| Check | Fixer | Action |
|-------|-------|--------|
| git-hooks | HooksFixer | Reinstall hooks |
| hooks-outdated | HooksFixer | Update hooks |
| merge-driver-missing | MergeDriverFixer | Configure merge driver |
| sync-branch-diverged | SyncBranchFixer | Reset to remote |
| daemon-version-mismatch | DaemonFixer | Restart daemon |

## Testing
```bash
# Test with broken hooks
rm .git/hooks/pre-commit
bd doctor --fix --yes

# Verify fix applied
bd doctor  # Should pass now
```

## Success Criteria
- --fix flag triggers automatic repair
- User prompted for confirmation (unless --yes)
- Clear output showing what was fixed
- Graceful handling of fix failures
- At least 5 common issues auto-fixable


bd init should stop running daemon to avoid stale cache

When running bd init, any running daemon continues with stale cached data, causing bd stats and other commands to show old counts.

Repro:
1. Have daemon running with 788 issues cached
2. Clean JSONL to 128 issues, delete db, run bd init
3. bd stats still shows 788 (daemon cache)
4. Must manually run bd daemon --stop

Fix: bd init should automatically stop any running daemon before reinitializing.


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

## Problem

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

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

## Impact

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

## Root Cause

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

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

## Proposed Fix

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

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

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

## Files to Modify

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

## Fix Implemented

Modified cmd/bd/sync.go mergeSyncBranch function:

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

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

## Testing

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


Add 0.30.4 to info.go release notes

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


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


Add prefix-based routing with routes.jsonl

## Summary

When running `bd show gt-xyz` from a location where that bead doesn't exist locally,
bd should check a `routes.jsonl` file to find where beads with that prefix live.

## Problem

From ~/gt (town root):
- `bd show hq-xxx` works (hq-* beads are local)
- `bd show gt-xxx` fails (gt-* beads live in gastown/mayor/rig)

This breaks `gt hook`, `gt handoff`, and `gt prime` which need to verify beads exist.

## Solution

Add `.beads/routes.jsonl` for prefix-based routing:

\`\`\`json
{"prefix": "gt-", "path": "gastown/mayor/rig"}
{"prefix": "hq-", "path": "."}
{"prefix": "bd-", "path": "beads/crew/dave"}
\`\`\`

### Lookup Algorithm

1. Try local .beads/ first (current behavior)
2. If not found, check for routes.jsonl in current .beads/
3. Match ID prefix against routes
4. If match found, resolve path relative to routes file location and retry
5. If still not found, error as usual

### Affected Commands

Read operations that take a bead ID:
- bd show <id>
- bd update <id>
- bd close <id>
- bd dep add <id> <dep> (both IDs)

Write operations (bd create) still go to local .beads/ - no change needed.

## Implementation Notes

1. New file: routes.jsonl in .beads/ directory
2. New function: resolveBeadsPath(id string) (string, error)
3. Update: beads.Show(), beads.Update(), beads.Close(), etc. to use resolver
4. Caching: Can cache routes in memory since file rarely changes

## Context

This unblocks the gt handoff flow for Mayor. Currently gt handoff gt-xyz fails
because it can't verify the bead exists when running from town root.


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


Fix bd sync race condition with daemon causing dirty working directory

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

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

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

Related: bd-c83r (multiple daemon prevention)


Update cmd/bd/version.go to 0.30.4

Update the Version constant in cmd/bd/version.go:
```go
Version = "0.30.4"
```

```verify
grep -q 'Version = "0.30.4"' cmd/bd/version.go
```


Run tests and linting

go test -short ./... && golangci-lint run ./...


Add stale molecule check to bd doctor

Extend bd doctor to detect stale molecules.

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

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

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

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

Depends on: bd mol stale command


Install 0.30.4 MCP server locally

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

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

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


Fix JSON errors in show.go (update/close/edit commands)

Replace ~51 direct stderr writes with FatalErrorRespectJSON() in show.go.

Commands affected:
- updateCmd (~10 error handlers)
- closeCmd (~15 error handlers)
- editCmd (~10 error handlers)
- showCmd (already uses FatalErrorRespectJSON in some places)

Key error paths:
- ID resolution errors (lines 565, 570, 579)
- Validation errors (lines 474, 515, 524, 545)
- RPC/daemon errors (line 639+)
- Store operation errors


Consider FTS5 for text search at scale

SearchIssues uses LIKE patterns for text search which can't use indexes.

**Current query (queries.go:1475-1477):**
```sql
(title LIKE ? OR description LIKE ? OR id LIKE ?)
```

**Problem:** Full table scan on every text search. At 100K+ issues, this becomes slow.

**SQLite FTS5 solution:**
```sql
CREATE VIRTUAL TABLE issues_fts USING fts5(
  id, title, description, design, notes,
  content='issues',
  content_rowid='rowid'
);

-- Triggers to keep FTS in sync
CREATE TRIGGER issues_ai AFTER INSERT ON issues BEGIN
  INSERT INTO issues_fts(rowid, id, title, description, design, notes)
  VALUES (new.rowid, new.id, new.title, new.description, new.design, new.notes);
END;
-- (similar for UPDATE, DELETE)
```

**Trade-offs:**
- Database size increase (~30-50% for text content)
- Additional write overhead (trigger execution)
- Better search capabilities (ranking, phrase search)

**Decision needed:** Is full-text search a priority feature? Current LIKE search may be acceptable for most use cases.

**Benchmark first:** Measure SearchIssues at 100K scale before implementing.


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.


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.


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

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

Current coverage: 47.5%
Target coverage: 60%


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


Test work item


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.


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

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

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

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

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


Replace map[string]interface{} with typed JSON response structs

Many CLI commands use map[string]interface{} for JSON output which loses type safety and compile-time error detection.

Files with map[string]interface{}:
- cmd/bd/compact.go (10+ instances)
- cmd/bd/cleanup.go
- cmd/bd/daemons.go
- cmd/bd/daemon_lifecycle.go

Example fix:
```go
// Before
result := map[string]interface{}{
    "status": "ok",
    "count": 42,
}

// After
type CompactResponse struct {
    Status string `json:"status"`
    Count  int    `json:"count"`
}
result := CompactResponse{Status: "ok", Count: 42}
```

Benefits:
- Compile-time type checking
- IDE autocompletion
- Easier refactoring
- Self-documenting API


Re: Thread Test 2

Got your message. Testing reply feature.


Merge: bd-likt

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


Run bump-version.sh

./scripts/bump-version.sh {{version}}


bd mol bond --ephemeral flag

Add --ephemeral flag to bd mol bond command.

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

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

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


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.


swarm status: Handle external dependencies and error cases

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


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

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

## Usage

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

## Behavior

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

## Output

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

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

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

## If no next step

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

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

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

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


Merge: bd-au0.7

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


Add GetWorkerStatus RPC endpoint

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


Merge: jade-1767138549967

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


Installation tracking: .beads/installed.json

Track installed formulas for update checking and provenance.

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

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

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

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


Update info.go versionChanges

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

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

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

Focus on workflow-impacting changes agents need to know.


Test hooked styling


Update docs for import bug fix

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


Merge: ux-1766978911613

branch: polecat/Nux-1766978911613
target: main
source_issue: ux-1766978911613
rig: beads


Update CHANGELOG.md with release notes

Add meaningful release notes to CHANGELOG.md describing what changed in 0.30.7


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


mol-christmas-launch: 3-day execution plan

Christmas Launch Molecule - execute phases in order, survive restarts.

PIN THIS BEAD. Check progress each session start.

## Step: phase0-beads-foundation
Fix blocking issues before swarming:
1. Verify gastown beads schema works: bd list --status=open
2. Ensure bd mol bond exists (check bd-usro)
3. Verify bd-2vh3 (squash) is filed

## Step: phase1-polecat-loop
Serial work on polecat execution:
1. gt-9nf: Fresh polecats only
2. gt-975: Molecule execution support
3. gt-8v8: Refuse uncommitted work
Then swarm: gt-e1y, gt-f8v, gt-eu9
Needs: phase0-beads-foundation

## Step: phase2-refinery
Serial work on refinery autonomy:
1. gt-5gkd: Refinery CLAUDE.md
2. gt-bj6f: Refinery context in gt prime
3. gt-0qki: Refinery-Witness protocol
Needs: phase1-polecat-loop

## Step: phase3-deacon
Health monitoring infrastructure:
1. gt-5af.4: Simplify daemon
2. gt-5af.7: Crew session patterns
3. gt-976: Crew lifecycle
Needs: phase2-refinery

## Step: phase4-code-review
Self-improvement flywheel:
1. Define mol-code-review (gt-fjvo)
2. Test on open MRs
3. Integrate with Refinery
Needs: phase3-deacon

## Step: phase5-polish
Demo readiness:
1. gt-b2hj: Find orphaned work
2. Doctor checks
3. Clean up open MRs
Needs: phase4-code-review

## Step: verify-flywheel
Success criteria:
- gt spawn works with molecules
- Refinery processes MRs autonomously
- mol-code-review runs on a PR
- bd cleanup --ephemeral works
Needs: phase5-polish


Consolidate duplicate path-finding utilities (findJSONLPath, findBeadsDir, findGitRoot)

Code health review found these functions defined in multiple places:

- findJSONLPath() in autoflush.go:45-73 and doctor/fix/migrate.go
- findBeadsDir() in autoimport.go:197-239 (with git worktree handling)
- findGitRoot() in autoimport.go:242-269 (Windows path conversion)

The beads package has public FindBeadsDir() and FindJSONLPath() APIs that should be used consistently.

Impact: Bug fixes need to be applied in multiple places. Git worktree handling may not be replicated everywhere.

Fix: Consolidate all implementations to use the beads package APIs. Remove duplicates.


Review PR #513: fix hooks install docs

Review and merge PR #513 from aspiers. This PR fixes incorrect docs for how to install git hooks - updates README to use bd hooks install instead of removed install.sh. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/513


bd prime should mention when beads is redirected

## Problem

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

## Proposed Solution

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

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

## Why

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

## Related

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


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.


Restart daemon with 0.33.2

Restart the bd daemon to pick up new version:

```bash
bd daemon --stop
bd daemon --start
bd daemon --health  # Verify Version: 0.33.2
```


HANDOFF: Version 0.30.7 release in progress

## Context
Doing a 0.30.7 patch release with bug fixes.

## What's done
- Fixed #657: bd graph nil pointer crash (graph.go:102)
- Fixed #652: Windows npm installer file lock (postinstall.js)
- Updated CHANGELOG.md and info.go
- Pushed to main, CI running (run 20390861825)
- Created version-bump molecule template (bd-6s61) and instantiated for 0.30.7 (bd-8pyn)

## In progress
Molecule bd-8pyn has 3 remaining tasks:
  - bd-dxo7: Wait for CI to pass
  - bd-7l70: Verify release artifacts
  - bd-5c91: Update local installation

## Check CI
  gh run list --repo steveyegge/beads --limit 1
  gh run view 20390861825 --repo steveyegge/beads

## New feature filed
bd-n777: Timer beads for scheduled agent callbacks
Design for Deacon-managed timers that can interrupt agents via tmux

## Resume commands
  bd --no-daemon show bd-8pyn
  gh run list --repo steveyegge/beads --limit 1


Support external: prefix in blocked_by field

Allow blocked_by to include external project references:

```bash
bd update gt-xyz --blocked-by="external:beads:mol-run-assignee"
```

Syntax: `external:<project>:<capability>`
- project: name from external_projects config
- capability: matches provides:<capability> label in target project

Storage: Store as-is in blocked_by array. Resolution happens at query time.

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


Add bd state and bd set-state helper commands

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

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

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

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

Into a single atomic operation.

Discovered from: bd-31ae


Add BD_AGENT_MODE for ultra-compact output

Optional agent-optimized output mode triggered by BD_AGENT_MODE=1 env var:

- Compact format: just ID and title per line
- Default to open/in_progress only
- No colors, no emojis
- Lower default limit (~20)
- One-line summaries

This is independent but benefits from TTY detection infrastructure.

Consider auto-detecting agent context via CLAUDE_CODE or similar env vars.

## Implementation Plan

### Trigger
```bash
BD_AGENT_MODE=1 bd list
```

Or auto-detect common agent environments:
```go
func IsAgentMode() bool {
    if os.Getenv("BD_AGENT_MODE") != "" {
        return true
    }
    // Auto-detect known agent contexts
    if os.Getenv("CLAUDE_CODE") != "" {
        return true
    }
    // Add other agent env vars as discovered
    return false
}
```

### Code Changes

1. **Add agent output format** (cmd/bd/list.go):
```go
if ui.IsAgentMode() {
    // Ultra-compact: just ID and title
    for _, issue := range issues {
        fmt.Printf("%s: %s\n", issue.ID, issue.Title)
    }
    return
}
```

2. **Agent mode defaults**:
```go
if ui.IsAgentMode() {
    if limit == 0 {
        limit = 20 // Lower default for agents
    }
    if status == "" {
        // Only actionable work
        filter.Status = []types.Status{"open", "in_progress"}
    }
}
```

### Output Format (Agent Mode)
```
bd-mypl: Default bd list to non-closed issues
bd-v5fn: Add default limit to bd list (50 issues)
bd-jdz3: Add pager support to bd list
```

No colors, no emojis, no brackets, no priority display.
Just ID + title for maximum context efficiency.

### Testing
- `BD_AGENT_MODE=1 bd list` outputs compact format
- Verify no ANSI codes in output
- Verify default limit is 20
- Verify only open/in_progress shown by default


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


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.99.0-test.


Create release tag

Create annotated git tag.

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

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


Merge: bd-401h

branch: polecat/rictus
target: main
source_issue: bd-401h
rig: beads


Auto-detect identity from directory context for bd mail

Currently bd mail inbox defaults to git user name, requiring --identity flag with exact format.

## Problem
- Mail sent to `gastown/crew/max`
- Max runs `bd mail inbox` → defaults to 'Steve Yegge' (git user)
- Max must know to use `--identity 'gastown/crew/max'` with exact slashes

## Proposed Fix
Auto-detect identity from directory context when in a Gas Town workspace:
- In `/Users/stevey/gt/gastown/crew/max`, infer identity = `gastown/crew/max`
- Pattern: `<town>/<rig>/<role>/<name>` → `<rig>/<role>/<name>`

## Additional Improvements
1. Support GT_IDENTITY env var (set by gt crew at / session spawning)
2. Support identity in .beads/config.yaml
3. Normalize format: accept both slashes and dashes as equivalent

## Context
Discovered during crew-to-crew work assignment. Max couldn't see mail despite correct nudge because identity defaulted wrong.


Investigate Claude Code crash logging improvements

## Problem

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

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

## What we found

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

## Desired improvements

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

## Investigation paths

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

## Related

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


Remove unreachable RPC methods

Several RPC server and client methods are unreachable and should be removed:

Server methods (internal/rpc/server.go):
- `Server.GetLastImportTime` (line 2116)
- `Server.SetLastImportTime` (line 2123)
- `Server.findJSONLPath` (line 2255)

Client methods (internal/rpc/client.go):
- `Client.Import` (line 311) - RPC import not used (daemon uses autoimport)

Evidence:
```bash
go run golang.org/x/tools/cmd/deadcode@latest -test ./...
```

Impact: Removes ~80 LOC of unused RPC code


Upgrade beads-mcp

pip3 install --upgrade beads-mcp


Create and push tag

git tag v{{version}} && git push origin main && git push origin v{{version}}


Execute molecule bd-mol-xga: Release beads 0.37.0


Add tests for daemon functionality

Critical daemon functions have 0% test coverage including daemon lifecycle, health checks, and RPC server functionality. These are essential for system reliability and need comprehensive test coverage.


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

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


Respond to GitHub issue #479 with solution

Once bd reset is implemented and released, respond to GitHub issue #479.

## Response should include
- Announce the new bd reset command
- Show basic usage examples
- Link to any documentation
- Thank the user for the feedback

## Example response
```
Thanks for raising this! We've added a `bd reset` command to handle this case.

Usage:
- `bd reset` - Reset to clean state (prompts for confirmation)
- `bd reset --backup` - Create backup first
- `bd reset --hard` - Also clean up git history

This is available in version X.Y.Z.
```

## Notes
- Wait until feature is merged and released
- Consider if issue should be closed or left for user confirmation


Update documentation for template system

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

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


Implement transaction retry logic for SQLITE_BUSY

BEGIN IMMEDIATE fails immediately on SQLITE_BUSY instead of retrying with exponential backoff.

Location: internal/storage/sqlite/sqlite.go:223-225

Problem:
- Under concurrent write load, BEGIN IMMEDIATE can fail with SQLITE_BUSY
- Current implementation fails immediately instead of retrying
- Results in spurious failures under normal concurrent usage

Solution: Implement exponential backoff retry:
- Retry up to N times (e.g., 5)
- Backoff: 10ms, 20ms, 40ms, 80ms, 160ms
- Check for context cancellation between retries
- Only retry on SQLITE_BUSY/database locked errors

Impact: Spurious failures under concurrent write load

Effort: 3 hours


Test agent bead

Testing new agent type


bd merge omits priority:0 due to omitempty JSON tag

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


Wait for GoReleaser

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


Fix JSON errors in epic.go

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

Commands affected:
- epicStatusCmd
- epicCloseEligibleCmd


Work on beads-2vn: Add simple built-in beads viewer (GH#6...

Work on beads-2vn: Add simple built-in beads viewer (GH#654). Add bd list --pretty with --watch flag, tree view with priority/status symbols. When done, submit MR (not PR) to integration branch for Refinery.