Graph Link: relates_to for knowledge graph

Implement relates_to link type for loose associations.

New command:
- bd relate <id1> <id2> - creates bidirectional relates_to link

Query support:
- bd show <id> --related shows related issues
- bd list --related-to <id>

Storage:
- relates_to stored as JSON array of issue IDs
- Consider: separate junction table for efficiency at scale?

This enables 'see also' connections without blocking or hierarchy.


Test agent with fields


Code smell: Long function flushToJSONLWithState (~280 lines)

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

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

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

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


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

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


Add 'convoy' issue type with reactive completion

Add convoy as a new issue type with reactive completion semantics.

Behavior:
- Convoy has list of tracked issues (via 'tracks' relation)
- When all tracked issues close (including wontfix), convoy auto-closes
- Supports cross-prefix tracking (convoy in hq-* tracks gt-*, bd-*)

Implementation:
- New type: convoy
- Reactive completion trigger on tracked issue closure
- Query support: 'bd list --type=convoy'

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


bd mol current: soft cursor showing current/next step

Add bd mol current command for molecule navigation orientation.

## Usage

bd mol current [mol-id]

If mol-id given, show status for that molecule.
If not given, infer from in_progress issues assigned to current agent.

## Output

You're working on molecule gt-abc (Feature X)

  [done] gt-abc.1: Design
  [done] gt-abc.2: Scaffold
  [done] gt-abc.3: Implement
  [current] gt-abc.4: Write tests [in_progress] <- YOU ARE HERE
  [pending] gt-abc.5: Documentation
  [pending] gt-abc.6: Exit decision

Progress: 3/6 steps complete

## Key behaviors
- Shows full molecule structure with status indicators
- Highlights current in_progress step
- If no in_progress, highlights first ready step
- Works without explicit cursor tracking (inferred from state)

## Implementation notes
- Query children of mol-id
- Sort by dependency order
- Find first in_progress or first ready
- Format with status indicators

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


Review jordanhubbard chaos testing PR #752

FINDINGS:
- Implementation quality: HIGH
- Recommendation: MERGE WITH MODIFICATIONS
- Mods: No hard coverage threshold, chaos tests on releases only

DECISION: User agrees. Next steps:
1. Add chaos tests to release-bump formula
2. Merge PR #752

Review doc: docs/pr-752-chaos-testing-review.md


Agent beads: structured labels for filtering

## Agent Beads Need Queryable Labels

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

## Current State

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

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

## Required

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

## Where to Fix

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

## Queries Enabled

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

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


Edge Schema Consolidation: Unify all edges in dependencies table

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

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

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


Test hook


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.


Refactor sync_test to use direct import logic

In cmd/bd/sync_test.go:444, there's a TODO to refactor the test to use direct import logic instead of subprocess calls. This would make the test more reliable and faster.

File: cmd/bd/sync_test.go:444


Add 0.33.2 to versionChanges in info.go

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

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

Copy changes from CHANGELOG.md [Unreleased] section.


Fix or remove TestFindJSONLPathDefault (issue #356)

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

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

Test file: internal/beads/beads_test.go

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


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: onyx-1767106262992

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


Review SQLite indexes and scaling bottlenecks

Audit the beads SQLite schema for:

## Index Review
- Are all frequently-queried columns indexed?
- Are compound indexes needed for common query patterns?
- Any missing indexes on foreign keys or filter columns?

## Scaling Bottlenecks
- How does performance degrade with 10k, 100k, 1M issues?
- Full table scans in hot paths?
- JSONL export/import performance at scale
- Transaction contention in multi-agent scenarios

## Common Query Patterns to Optimize
- bd ready (status + blocked_by resolution)
- bd list with filters (status, type, priority, labels)
- bd show with dependency graph traversal
- bd sync import/export

## Deliverables
- Document current indexes
- Identify missing indexes
- Benchmark key operations at scale
- Recommend schema improvements


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

## Problem

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

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

## Expected Behavior

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

## Workaround

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

## Fix Options

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

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


bd swarm status command

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

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

NOT stored separately. If beads changes, status changes.

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

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

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

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

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

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


Implement bd pin/unpin commands

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


.beads-ephemeral/ storage backend

Implement ephemeral storage layer for wisps.

## Requirements
- New storage location: .beads-ephemeral/issues.jsonl (sibling to .beads/)
- Gitignored by default (add to .beads/.gitignore)
- Same JSONL format as regular beads
- Config option: ephemeral.directory (relative path)
- ephemeral.enabled config flag

## Storage Behavior
- Ephemeral issues have `ephemeral: true` field
- No sync to remote (local only)
- No daemon tracking needed (transient)

## Implementation
- Add EphemeralStore in storage package
- Initialize on demand when --ephemeral flag used
- Share Issue struct, just different storage path


mergeDependencies uses union instead of 3-way merge

## Critical Bug

The `mergeDependencies` function in internal/merge/merge.go performs a UNION of left and right dependencies instead of a proper 3-way merge. This causes removed dependencies to be resurrected.

### Root Cause

```go
// Current code (lines 795-816):
func mergeDependencies(left, right []Dependency) []Dependency {
    // Just unions left + right
    // NEVER REMOVES anything
    // Doesn't even look at base!
}
```

And `mergeIssue` (line 579) doesn't pass `base`:
```go
result.Dependencies = mergeDependencies(left.Dependencies, right.Dependencies)
```

### Impact

If:
- Base has dependency D
- Left removes D (intentional)
- Right still has D (stale)

Current: D is in result (resurrection!)
Correct: Left removed it, D should NOT be in result

This breaks Gas Town's workflow and data integrity. Closed means closed.

### Fix

Change `mergeDependencies` to take `base` and do proper 3-way merge:
- If dep was in base and removed by left → exclude (left wins)
- If dep was in base and removed by right → exclude (right wins)
- If dep wasn't in base and added by either → include
- If dep was in base and both still have it → include

Key principle: **REMOVALS ARE AUTHORITATIVE**

### Files to Change

1. internal/merge/merge.go:
   - `mergeDependencies(left, right)` → `mergeDependencies(base, left, right)`
   - `mergeIssue` line 579: pass `base.Dependencies`

### Related

This also explains why `ProtectLocalExportIDs` in importer is defined but never used - the protection was never actually implemented.


Implement conditional bond type for mol bond

The mol bond command accepts 'conditional' as a bond type but doesn't implement any conditional-specific behavior. It currently behaves identically to 'parallel'.

**Expected behavior:**
Conditional bonds should mean 'B runs only if A fails' per the help text (mol.go:318).

**Implementation needed:**
- Add failure-condition dependency handling
- Possibly new dependency type or status-based blocking
- Update bondProtoProto, bondProtoMol, bondMolMol to handle conditional

**Alternative:**
Remove 'conditional' from valid bond types until implemented.

This is new functionality, not a regression.


Code smell: init.go is 1928 lines - needs splitting

cmd/bd/init.go is 1928 lines covering multiple init modes, contributor/team setup, hooks setup, and stealth mode.

**Problem:**
- File is too large to understand and maintain
- Multiple responsibilities within single file
- Harder to test individual components

**Acceptance Criteria:**
- [ ] Split into logical files (init_setup.go, init_hooks.go, init_stealth.go, etc.)
- [ ] Each file under 500 lines
- [ ] No functionality changes
- [ ] Tests pass


activity --follow: Silent error handling

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

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

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

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

Discovered during code review of bd-xo1o implementation.


Update info.go versionChanges

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


Wisp operations should auto-bypass daemon

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

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

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

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


GH#692: Code quality and refactoring improvements

Epic for implementing refactoring suggestions from GitHub issue #692 (rsnodgrass). These are code quality improvements that don't change functionality but improve maintainability, type safety, and performance.

Original issue: https://github.com/steveyegge/beads/issues/692

High priority items:
1. Replace map[string]interface{} with typed structs for JSON output
2. Adopt slices.SortFunc instead of sort.Slice (Go 1.21+)
3. Split large files (sync.go, init.go, show.go)
4. Introduce slog for structured logging in daemon

Lower priority:
5. Further CLI helper extraction
6. Preallocate slices in hot paths
7. Polish items (error wrapping, table-driven parsing)


Merge: beads-ocs

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


Consider backwards compatibility for ready() and list() return type change

PR #481 changed the return types of `ready()` and `list()` from `list[Issue]` to `list[IssueMinimal] | CompactedResult`. This is a breaking change for MCP clients.

## Impact Assessment
Breaking change affects:
- Any MCP client expecting `list[Issue]` from ready()
- Any MCP client expecting `list[Issue]` from list()
- Client code that accesses full Issue fields (description, design, acceptance_criteria, timestamps, dependencies, dependents)

## Current Behavior
- ready() returns `list[IssueMinimal] | CompactedResult`
- list() returns `list[IssueMinimal] | CompactedResult`
- show() still returns full `Issue` (good)

## Considerations
**Pros of current approach:**
- Forces clients to use show() for full details (good for context efficiency)
- Simple mental model (always use show for full data)
- Documentation warns about this

**Cons:**
- Clients expecting list[Issue] will break
- No graceful degradation option
- No migration period

## Potential Solutions
1. Add optional parameter `full_details=false` to ready/list (would increase payload)
2. Create separate tools: ready_minimal/list_minimal + ready_full/list_full
3. Accept breaking change and document upgrade path (current approach)
4. Version the MCP server and document migration guide

## Recommendation
Current approach (solution 3) is reasonable if:
- Changelog clearly documents the breaking change
- Migration guide provided to clients
- Error handling is graceful for clients expecting specific fields


Add bd agent state command for ZFC-compliant state reporting

Add command for agents to write their own state to agent beads.

```bash
bd agent state <agent-id> <state>
# Example: bd agent state gt-mayor running
# States: idle | running | stuck | stopped | dead
```

Implementation:
1. Lookup agent bead by ID (type=agent)
2. Update agent_state field in description
3. Update last_activity timestamp
4. Trigger bd sync if configured

Also add:
- bd agent heartbeat <agent-id> - just updates last_activity
- bd agent show <agent-id> - show agent bead details

This is the ZFC-compliant way for agents to self-report state.

Cross-ref: gt-p2vyo in gastown


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


Rename 'bond' to 'spawn' for instantiation

Rename the bd mol bond command to bd mol spawn for instantiating protos.

- Rename molBondCmd to molSpawnCmd
- Update command Use/Short/Long descriptions
- Keep 'bond' available for the new bonding feature
- Update all documentation references
- Add 'protomolecule' as easter egg alias for 'proto'


Build and verify

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


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


Update git hooks

Install the updated hooks:
```bash
bd hooks install
```

Verify hook version:
```bash
grep 'bd-hooks-version' .git/hooks/pre-commit
```

```verify
grep -q 'bd-hooks-version: 0.30.4' .git/hooks/pre-commit 2>/dev/null || echo 'Hooks may not be installed - verify manually'
```


bd create -f crashes with nil pointer dereference

GitHub issue #674. The markdown import feature crashes at markdown.go:338 because global variables (store, ctx, actor) aren't initialized when createIssuesFromMarkdown is called. The function uses globals set by cobra command framework but is being called before they're ready. Need to either initialize globals at start of function or pass them as parameters.


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


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.


witness Handoff


Support wisps in nodb mode for lightweight Gas Town rigs

Enable proper wisp support in --no-db mode to allow lightweight Gas Town rigs (including HQ/Mayor) to run without SQLite overhead.

## Motivation
Gas Town HQ and simple rigs could benefit from nodb mode's simplicity. Currently wisps don't work properly in nodb mode.

## Proposed Design

### Phase 1: Session-scoped wisps
- Memory storage already has Wisp field on Issue
- Wisps exist only for the session lifetime
- Acceptable for patrol cycles that complete within a session
- Depends on: bd-9avq (fix the leak)

### Phase 2: Optional wisp persistence
- Add `.beads/wisps.jsonl` for wisp storage (gitignored)
- Load wisps from this file on nodb startup
- Save wisps to this file on nodb exit
- Wisps persist across restarts but don't sync via git

### Phase 3: Verify mol commands
- Test mol squash, burn, bond work with memory storage
- These currently check for SQLite storage type
- May need interface-based approach instead of type assertions

## Files affected
- cmd/bd/nodb.go - wisp filtering and optional persistence
- internal/storage/memory/ - verify wisp field handling
- cmd/bd/mol_*.go - verify memory storage compatibility
- .beads/.gitignore - add wisps.jsonl


Code smell: runCook function is ~275 lines

attached_args: Refactor runCook function (~275 lines)

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

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

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

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

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


Work on gt-8tmz.10: Rename Engineer in Box to Shiny. Rena...

Work on gt-8tmz.10: Rename Engineer in Box to Shiny. Rename mol-engineer-in-box references to mol-shiny or just 'shiny'. Update docs and code in internal/formula/ and .beads/formulas/. When done: 1) bd close gt-8tmz.10, 2) bd sync, 3) git push, 4) gt mq submit


Identity Configuration

Implement identity system for sender field.

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

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

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

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


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

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

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

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

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

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

Requires: Identity configuration (bd-kwro.7)


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.


bd ready resolves external dependencies

Extend bd ready to check external blocked_by references:

1. Parse external:<project>:<capability> from blocked_by
2. Look up project path from external_projects config
3. Check if target project has provides:<capability> label on a closed issue
4. If not satisfied, issue is blocked

Example output:
```bash
bd ready
# gt-xyz: blocked by external:beads:mol-run-assignee (not provided)
# gt-abc: ready
```

Depends on: bd-om4a (external: prefix), bd-66w1 (config)

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


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


Update LINTING.md with current baseline

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


Rebuild local binary

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


Add thread-safety warning to git.ResetCaches() doc comment

Code review finding from bd-7di fix.

ResetCaches() reassigns sync.Once values which is not thread-safe. While this is only used in tests (which are single-threaded), the function is exported and could be misused.

Add a warning comment:
```go
// ResetCaches resets all cached git information.
// WARNING: Not thread-safe. Only call from single-threaded test contexts.
```

File: internal/git/gitdir.go


HANDOFF: Molecule bonding - spawn done, bond next

## Context

Continuing work on bd-o5xe (Molecule bonding epic).

## Completed This Session

- bd-mh4w: Renamed bond to spawn in mol.go
- bd-rnnr: Added BondRef data model to types.go

## Now Unblocked

1. bd-o91r: Polymorphic bond command [P1]
2. bd-iw4z: Compound visualization [P2]
3. bd-iq19: Distill command [P2]

## Key Files

- cmd/bd/mol.go
- internal/types/types.go

## Next Step

Start with bd-o91r. Run bd show bd-o5xe for context.


Create release tag

Create annotated git tag.

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

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


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

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


Code cleanup: Search for leftover debugging code

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

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

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

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


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.


Run bump-version.sh {{version}}

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


Migration invariants should exclude external dependencies from orphan check

## Summary

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

## Location

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

## Current Code (buggy)

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

## Fix

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

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

## Reproduction

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

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

## Files to Modify

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

## Testing

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

# Sync should succeed
bd sync

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

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


Wait for GoReleaser

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


Create git tag v{{version}}

Create the release tag:

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

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


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


Verify npm package

Confirm npm package published.

```bash
npm show @beads/bd version
```

Should show 0.99.0-test.

Also check: https://www.npmjs.com/package/@beads/bd


Run bump-version.sh

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


Performance Validation

Confirm no performance regression from cache removal


bd mol install: Install formula from GitHub

Download and cook a formula from a GitHub repository.

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

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

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

## Implementation
1. Parse URL: extract org, repo, optional version tag
2. Construct raw URL: `https://raw.githubusercontent.com/org/repo/[tag]/formula.json`
3. Fetch formula.json via HTTP
4. Validate formula structure
5. Save to .beads/formulas/
6. Run cook to create local proto
7. Record in .beads/installed.json for update tracking

## installed.json Format
```json
{
  "mol-code-review": {
    "source": "github.com/anthropics/mol-code-review",
    "version": "v1.2.0",
    "installed_at": "2025-12-25T12:00:00Z"
  }
}
```

## Dependencies
- Requires network access
- Uses `gh` CLI or direct HTTP for auth (private repos)


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


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

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

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

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


dave Handoff


GH#532: bd sync circular error (suggests running bd sync)

bd sync error message recommends running bd sync to fix the bd sync error. Fix error handling to provide useful guidance. See GitHub issue #532.


bd sync deleted non-.beads files (skills/beads/*)

On 2025-12-23, commit 7b671662 (bd sync) inadvertently deleted 5 files from skills/beads/ that were added by PR #722 (commit e432fcc8). These files are outside .beads/ and should never be touched by bd sync.

Files deleted:
- skills/beads/README.md
- skills/beads/references/INTEGRATION_PATTERNS.md
- skills/beads/references/MOLECULES.md
- skills/beads/references/PATTERNS.md
- skills/beads/references/TROUBLESHOOTING.md

Investigation needed:
1. How did bd sync stage/commit files outside .beads/?
2. Was this a one-time accident or is there a systematic bug?
3. Add safeguards to prevent bd sync from touching non-.beads paths

Reported via GH#738.


Work on beads-ocs: Thin shim hooks to eliminate version d...

Work on beads-ocs: Thin shim hooks to eliminate version drift (GH#615). Replace full hook scripts with thin shims that call bd hooks run. When done, submit MR (not PR) to integration branch for Refinery.


GH#518: Document bd setup command

bd setup is undiscoverable. Add to README/docs. Currently only findable by grepping source. See GitHub issue #518.


Fix wisp leak in nodb mode writeIssuesToJSONL

writeIssuesToJSONL in nodb.go calls writeJSONLAtomic without filtering wisps. This means any wisps created during a nodb session would be written to issues.jsonl, leaking ephemeral data into the git-tracked file.

Fix: Add the same wisp filter pattern used in sync_export.go, autoflush.go, and export.go:

```go
// Filter out wisps before writing
filtered := make([]*types.Issue, 0, len(issues))
for _, issue := range issues {
    if !issue.Wisp {
        filtered = append(filtered, issue)
    }
}
issues = filtered
```

Location: cmd/bd/nodb.go:writeIssuesToJSONL()


Rename 'wisp' back to 'ephemeral' in beads API

The beads API uses 'wisp' terminology (Wisp field, bd wisp command) but the underlying SQLite column is 'ephemeral'.

This creates cognitive overhead since wisp is a Gas Town concept.

Rename to use 'ephemeral' consistently:
- types.Issue.Wisp → types.Issue.Ephemeral
- JSON field: wisp → ephemeral
- CLI: bd wisp → bd ephemeral (or just use flags on existing commands)

The SQLite column already uses 'ephemeral' so no schema migration needed.


Test Second Parent


Document sync.branch 'always dirty' working tree behavior

## Context

When sync.branch is configured, the .beads/issues.jsonl file in main's working tree is ALWAYS dirty. This is by design:

1. bd sync commits to beads-sync branch (via worktree)
2. bd sync copies JSONL to main's working tree (so CLI commands work)
3. This copy is NOT committed to main (to reduce commit noise)

Contributors who watch main branch history pushed for sync.branch to avoid constant beads commit noise. But users need to understand the trade-off.

## Documentation Needed

Update README.md sync.branch section with:

1. **Clear explanation** of why .beads/ is always dirty on main
2. **"Be Zen about it"** - this is expected, not a bug
3. **Workflow options:**
   - Accept dirty state, use `bd sync --merge` periodically to snapshot to main
   - Or disable sync.branch if clean working tree is more important
4. **Shell alias tip** to hide beads from git status:
   ```bash
   alias gs='git status -- ":!.beads/"'
   ```
5. **When to merge**: releases, milestones, or periodic snapshots

## Related

- bd-7b7h: Fix that allows bd sync --merge to work with dirty .beads/
- bd-elqd: Investigation that identified this as expected behavior


Test actor direct


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


Design auto-squash behavior for wisps

Explore the design space for automatic wisp squashing.

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

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

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

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

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

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

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


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


Add --body-file flag to bd create for reading descriptions from files

## Problem

Creating issues with long/complex descriptions via CLI requires shell escaping gymnastics:

```bash
# Current workaround - awkward heredoc quoting
bd create --title="..." --description="$(cat <<'EOF'
...markdown...
EOF
)"

# Often fails with quote escaping errors in eval context
# Agents resort to writing temp files then reading them
```

## Proposed Solution

Add `--body-file` and `--description-file` flags to read description from a file, matching `gh` CLI pattern.

```bash
# Natural pattern that aligns with training data
cat > /tmp/desc.md << 'EOF'
...markdown content...
EOF

bd create --title="..." --body-file=/tmp/desc.md
```

## Implementation

### 1. Add new flags to `bd create`

```go
createCmd.Flags().String("body-file", "", "Read description from file (use - for stdin)")
createCmd.Flags().String("description-file", "", "Alias for --body-file")
```

### 2. Flag precedence

- If `--body-file` or `--description-file` is provided, read from file
- If value is `-`, read from stdin
- Otherwise fall back to `--body` or `--description` flag
- If neither provided, description is empty (current behavior)

### 3. Error handling

- File doesn't exist → clear error message
- File not readable → clear error message
- stdin specified but not available → clear error message

## Benefits

✅ **Matches training data**: `gh issue create --body-file file.txt` is a common pattern
✅ **No shell escaping issues**: File content is read directly
✅ **Works with any content**: Markdown, special characters, quotes, etc.
✅ **Agent-friendly**: Agents already write complex content to temp files
✅ **User-friendly**: Easier for humans too when pasting long descriptions

## Related Commands

Consider adding similar support to:
- `bd update --body-file` (for updating descriptions)
- `bd comment --body-file` (if/when we add comments)

## Examples

```bash
# From file
bd create --title="Add new feature" --body-file=feature.md

# From stdin
echo "Quick description" | bd create --title="Bug fix" --body-file=-

# With other flags
bd create \
  --title="Security issue" \
  --type=bug \
  --priority=0 \
  --body-file=security-report.md \
  --label=security
```

## Testing

- Test with normal files
- Test with stdin (`-`)
- Test with non-existent files (error handling)
- Test with binary files (should handle gracefully)
- Test with empty files (valid - empty description)
- Test that `--description-file` and `--body-file` are equivalent aliases


bd: Add town-level activity signal in PersistentPreRun

Add activity signaling to beads so Gas Town daemon can detect bd usage.

In cmd/bd/main.go PersistentPreRun, add a call to write activity to
the Gas Town daemon directory if running inside a Gas Town workspace.

The signal file is ~/gt/daemon/activity.json (or detected town root).

Format:
{
  "last_command": "bd create ...",
  "actor": "gastown/crew/max",
  "timestamp": "2025-12-26T19:30:00Z"
}

Should be best-effort (silent failure) to avoid breaking bd outside Gas Town.

Cross-rig ref: gastown gt-ws8ol (Deacon exponential backoff epic)


Code review findings: mol squash deletion bypasses tombstones


Review changes since last release

Understand what's being released.

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

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


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.


bd mol advance: Step through molecule execution

Implement bd mol advance for stepping through molecules.

## Command

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

## Behavior

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

## Step State Machine

```
pending → in_progress → completed
```

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

## Output

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

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

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

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

## Files

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


Merge: bd-dtl8

branch: polecat/dag
target: main
source_issue: bd-dtl8
rig: beads


Add versionChanges to info.go

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


Work on beads-399: Add omitempty to JSONL fields for smal...

Work on beads-399: Add omitempty to JSONL fields for smaller notifications. When done, submit MR (not PR) to integration branch for Refinery.


Add tests for validation functions

Validation functions like ParseIssueType have 0% coverage. These are critical for ensuring data quality and preventing invalid data from entering the system.


Add tests for import/export functionality

Import/export functions like ImportIssues, exportToJSONLWithStore, and AutoImportIfNewer have low coverage. These are critical for data integrity and multi-repo synchronization.


Update cmd/bd/info.go with release notes for 0.37.0.

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

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

instantiated_from: bd-mol-xga
step: update-release-notes


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 header

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


Molecule bonding: composable workflow templates

Vision: Molecules should be composable like LEGO bricks or Mad Max war rig sections. Bonding lets you attach molecules together to create compound workflows.

THREE BONDING CONTEXTS:
1. Template-time: bd mol bond A B → Create reusable compound proto
2. Spawn-time: bd mol spawn A --attach B → Attach modules when instantiating
3. Runtime: bd mol attach epic B → Add to running workflow

BOND TYPES:
- Sequential: B after A completes (feature → deploy)
- Parallel: B runs alongside A (feature + docs)
- Conditional: B only if A fails (feature → hotfix)

BOND POINTS (Attachment Sites):
- Default: B depends on A root epic completion
- Explicit: --after issue-id for specific attachment
- Future: Named bond points in proto definitions

VARIABLE FLOW:
- Shared namespace between bonded molecules
- Warn on variable name conflicts
- Future: explicit mapping with --map

DATA MODEL: Issues track bonded_from to preserve compound lineage.

SUCCESS CRITERIA:
- Can bond two protos into a compound proto
- Can spawn with --attach for on-the-fly composition
- Can attach molecules to running workflows
- Compound structure visible in bd mol show
- Variables flow correctly between bonded molecules


GH#483: Pre-commit hook fails unnecessarily when .beads removed

Pre-commit hook fails on bd sync when .beads directory exists but user is on branch without beads. Should exit gracefully. See GitHub issue #483.


Gate eval: Consider rate limiting for GitHub API calls

Each gate evaluation makes a network call to GitHub API via gh CLI. With many gates, this could hit API rate limits.

Future improvements:
1. Batch queries where possible
2. Add caching with TTL for recently-checked gates
3. Exponential backoff on rate limit errors

Low priority - unlikely to be an issue until we have many concurrent gates.


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


Release v{{version}}

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


Support --reason flag in daemon mode for reopen command

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


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


bd update --parent: allow reparenting issues

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

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

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

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

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


Implement global N-way collision resolution algorithm

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

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

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


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.


bd list --parent: filter by parent issue

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

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

Would show all open children of gt-h5n.

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


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

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

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

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

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


Wisp storage: ephemeral molecule tracking

Implement ephemeral molecule storage for patrol cycles.

## Architecture

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

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

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

## Key Requirements

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

## Storage Locations

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

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


Fix JSON errors in comments.go

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

Commands affected:
- commentsCmd (list)
- commentsAddCmd


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.


GetBlockedIssues shows external deps as blocking even when satisfied

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

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

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

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


Update go install bd to 0.33.2

Rebuild and install bd to ~/go/bin:

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

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


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


Test Thread

Initial message for threading test


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

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

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

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


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.


Consider using types.Status in merge package for type safety

The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521


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

Add consistent verbosity controls across all commands.

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

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

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

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

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


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.


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

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


Merge: amber-1767138546434

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


Cross-project dependency support

Enable tracking dependencies across project boundaries.

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

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

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


Full event test


Add integration tests for RPC server and event loops

After adding basic unit tests for daemon utilities, the complex daemon functions still need integration tests:

Core daemon lifecycle:
- startRPCServer: Initializes and starts RPC server with proper error handling
- runEventLoop: Polling-based sync loop with parent monitoring and signal handling
- runDaemonLoop: Main daemon initialization and setup

Health checking:
- isDaemonHealthy: Checks daemon responsiveness and health metrics
- checkDaemonHealth: Periodic health verification

These require more complex test infrastructure (mock RPC, test contexts, signal handling) and should be tackled after the unit test foundation is in place.


Show pin indicator in bd list output

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


Run tests and verify lint passes.

Run tests and verify lint passes.

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

CI will run full lint, but fix any obvious issues first.

instantiated_from: bd-mol-xga
step: run-tests


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`


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


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

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


gt nudge doesn't work with crew addresses

## Bug

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

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

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

## Root Cause

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

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

## Fix

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


gt polecat done should auto-stop running session

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


Merge: bd-05a8

branch: polecat/valkyrie
target: main
source_issue: bd-05a8
rig: beads


bd mol reset: Reset molecule to initial state for looping

Implement bd mol reset for patrol loop behavior.

## Command

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

## Behavior

1. Load molecule state from .beads/molecules/<mol-id>.state.yaml
2. Reset all step states to pending
3. Clear current_step (or set to first step)
4. Preserve: bonded_at, bonded_by, variables
5. Update: reset_count++, last_reset_at

## Use Case: Deacon Patrol Loop

```
inbox-check → spawn-work → self-inspect → mol-reset → (loop)
```

The Deacon runs its patrol, then resets the molecule to start fresh.
No gt handoff needed unless context is high.

## State After Reset

```yaml
id: mol-deacon-patrol
status: running
current_step: inbox-check  # Back to first
reset_count: 5
last_reset_at: 2025-12-24T...
steps:
  inbox-check: pending      # All reset
  spawn-work: pending
  self-inspect: pending
```

## Files

- cmd/bd/mol_reset.go
- internal/mol/state.go (add Reset method)


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


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


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


Add missing hook calls in mail reply and ack

The mail commands are missing hook calls:

1. runMailReply (mail.go:525-672) creates a message but doesn't call hookRunner.Run(hooks.EventMessage, ...) after creating the reply in direct mode (around line 640)

2. runMailAck (mail.go:432-523) closes messages but doesn't call hookRunner.Run(hooks.EventClose, ...) after closing each message (around line 487 for daemon mode, 493 for direct mode)

This means GGT hooks won't fire for replies or message acknowledgments.


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.


Execute molecule bd-mol-xga: Release beads 0.37.0


Make DetectCollisions read-only (separate detection from modification)

**Summary:** The project restructured the collision detection process in the database to separate read-only detection from state modification, eliminating race conditions and improving system reliability. This was achieved by introducing a two-phase approach: first detecting potential collisions, then applying resolution separately.

**Key Decisions:**
- Create read-only DetectCollisions method
- Add RenameDetail to track potential issue renames
- Implement atomic ApplyCollisionResolution function
- Separate detection logic from database modification

**Resolution:** The refactoring creates a more robust, composable collision handling mechanism that prevents partial failures and maintains database consistency during complex issue import scenarios.


Merge: bd-ykd9

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


Optimization: Tombstones still synced, adding overhead

Tombstoned (deleted) issues are still processed during sync, adding overhead.

## Evidence
```
Import complete: 0 created, 0 updated, 407 unchanged, 100 skipped
```
Those 100 skipped are tombstones - they're read from JSONL, parsed, then filtered out.

## Current State (Beads repo)
- 408 total issues
- 99 tombstones (24% of database)
- Every sync reads and skips these 99 entries

## Impact
- Sync time increases with tombstone count
- JSONL file size grows indefinitely
- Git history accumulates tombstone churn

## Proposed Solutions

### 1. JSONL Compaction (`bd compact`)
Periodically rewrite JSONL without tombstones:
```bash
bd compact  # Removes tombstones, rewrites issues.jsonl
```
Trade-off: Loses delete history, but that's in git anyway.

### 2. Tombstone TTL
Auto-remove tombstones older than N days during sync:
```go
if issue.Deleted && time.Since(issue.UpdatedAt) > 7*24*time.Hour {
    // Skip writing to new JSONL
}
```

### 3. Archive File
Move old closed issues to `issues-archive.jsonl`:
- Not synced regularly
- Available for historical queries
- Main JSONL stays small

### 4. Lazy Tombstone Handling
Don't write tombstones to JSONL at all - just remove the line:
- Simpler, but loses cross-clone delete propagation
- Would need different delete propagation mechanism

## Recommendation
Start with `bd compact` command - simple, explicit, user-controlled.

## Related
- gt-tnss: Analysis - Beads database size and hygiene strategy
- gt-ox67: Maintenance - Regular cleanup of closed MR/gate beads


Release v{{version}}

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


Run bd doctor at end of bd init to verify setup

Run bd doctor diagnostics at end of bd init (after line 398 in init.go). If issues found, warn user immediately: '⚠ Setup incomplete. Run bd doctor --fix to complete setup.' Catches configuration problems before user encounters them in normal workflow.


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


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

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

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

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

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

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


Update local installation

go build -o ~/.local/bin/bd ./cmd/bd && codesign -s - ~/.local/bin/bd


Create migration script for edge field to dependency conversion

Migration must:
1. Read existing JSONL with old fields
2. Convert field values to dependency records
3. Write updated JSONL without old fields
4. Handle edge cases (missing refs, duplicates)

Run via: bd migrate or automatic on bd prime


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

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


Phase 2.5: TOON-based daemon sync

Implement TOON-native daemon sync (replaces JSONL sync machinery).

## Overview
Daemon sync is the final integration point. Replace export/import/merge machinery with TOON-native sync, building on deletion tracking (2.3) and merge optimization (2.4).

## Required Work

### 2.5.1 TOON-based Daemon Sync
- [ ] Understand current JSONL sync machinery (export.go, import.go, merge.go)
- [ ] Replace export step with TOON encoding (EncodeTOON)
- [ ] Replace import step with TOON decoding (DecodeTOON)
- [ ] Replace merge step with TOON-aware 3-way merge
- [ ] Update daemon auto-sync to read/write TOON
- [ ] Verify 5-second debounce still works

### 2.5.2 Deletion Sync Integration
- [ ] Load deletions.toon during import phase
- [ ] Apply deletions after merging issues
- [ ] Ensure deletion TTL respects daemon schedule

### 2.5.3 Testing
- [ ] Unit tests for daemon sync with TOON
- [ ] Integration tests with actual daemon operations
- [ ] Multi-clone sync scenarios with concurrent edits
- [ ] Performance comparison with JSONL sync
- [ ] Long-running daemon stability tests

## Success Criteria
- Daemon reads/writes TOON format (not JSONL)
- Sync latency comparable to JSONL (<100ms)
- All 70+ tests passing
- bdt commands work seamlessly with daemon
- Multi-clone sync scenarios work correctly


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


Move relate/unrelate under dep command

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


Verify GitHub release

Check the GitHub releases page.

https://github.com/steveyegge/beads/releases/tag/v0.39.0

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


Code smell: Incomplete TODOs in formula/types.go

internal/formula/types.go has multiple TODO items for features that are defined in the schema but not implemented:

- Line 170: Expand field not implemented in bd cook
- Line 174: ExpandVars field not implemented
- Line 179: Gate integration pending (bd-udsi gates)
- Line 186: Condition evaluation not implemented
- Line 210: Additional expansion features

**Problem:**
- Schema defines features that aren't implemented
- Can confuse users who try to use these fields
- Creates maintenance burden of unused code paths

**Acceptance Criteria:**
- [ ] Either implement the features OR
- [ ] Remove unimplemented fields from schema OR
- [ ] Document clearly which fields are future-only
- [ ] Update related beads issues if they exist


Test epic for duplicate swarm


Restart running daemons

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


Swarm: Test coordinator display

Swarm molecule orchestrating epic bd-2rkr.

Epic: bd-2rkr
Coordinator:


Work on beads-7jl: Fix Windows installer file locking iss...

Work on beads-7jl: Fix Windows installer file locking issue (GH#652). Close file handle before extraction in postinstall.js. When done, submit MR (not PR) to integration branch for Refinery.


Merge: bd-9usz

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


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.


Test suite hangs/never finishes

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


Remove Storage Cache from Server Struct

Eliminate cache fields and use s.storage directly


Implement CLI command (cmd/bd/reset.go)

Wire up the reset command with Cobra CLI.

## Responsibilities
- Define command and all flags
- User confirmation prompt (unless --force)
- Display impact summary before confirmation
- Colored output and progress indicators
- Call core reset package
- Handle errors with user-friendly messages
- Register command with rootCmd in init()

## Flags
```go
--hard       bool   "Also remove from git and commit"
--force      bool   "Skip confirmation prompt"
--backup     bool   "Create backup before reset"
--dry-run    bool   "Preview what would happen"
--skip-init  bool   "Do not re-initialize after reset"
--verbose    bool   "Show detailed progress output"
```

## Output Format
```
⚠  This will reset beads to a clean state.

Will be deleted:
  • 47 issues (23 open, 24 closed)
  • 12 tombstones

Continue? [y/N] y

→ Stopping daemons... ✓
→ Removing .beads/... ✓
→ Initializing fresh... ✓

✓ Reset complete. Run 'bd onboard' to set up hooks.
```

## Implementation Notes
- Confirmation logic lives HERE, not in core package
- Use color package (github.com/fatih/color) for output
- Follow patterns from other commands (init.go, doctor.go)
- Add to rootCmd in init() function

## File Location
`cmd/bd/reset.go`


Update info.go versionChanges

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


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.


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)


Release v0.30.4

## Version Bump Workflow

Coordinating release from 0.30.3 to 0.30.4.

### Components Updated
- Go CLI (cmd/bd/version.go)
- Claude Plugin (.claude-plugin/*.json)
- MCP Server (integrations/beads-mcp/)
- npm Package (npm-package/package.json)
- Git hooks (cmd/bd/templates/hooks/)

### Release Channels
- GitHub Releases (GoReleaser)
- PyPI (beads-mcp)
- npm (@beads/cli)
- Homebrew (homebrew-beads tap)


Fix update-homebrew.sh awk script corrupts formula

The awk script in scripts/update-homebrew.sh incorrectly removes platform conditionals (on_macos do, on_linux do, if Hardware::CPU.arm?, etc.) when updating SHA256 hashes. This corrupts the Homebrew formula.

The issue is the awk script uses 'next' to skip lines containing platform conditionals but never reconstructs them, resulting in a syntax-invalid formula.

Found during v0.34.0 release - had to manually fix the formula.

Fix options:
1. Rewrite awk script to properly preserve structure while updating sha256 lines only
2. Use sed instead with targeted sha256 replacements
3. Template approach - store formula template and fill in version/hashes


Implement bd gate create/show/list/close/wait commands

Implement the gate CLI commands.

## Commands
```bash
# Create gate (returns gate ID)
bd gate create --await <type>:<id> --timeout <duration> --notify <addr>

# Check gate status
bd gate show <id>

# List open gates
bd gate list

# Close gate (usually done by Deacon)
bd gate close <id> --reason "completed"

# Add waiter to existing gate
bd gate wait <id> --notify <addr>
```

## Implementation
- Add cmd/bd/gate.go with subcommands
- Gate create creates wisp issue of type gate
- Gate list filters for open gates
- Gate wait adds to waiters[] array
- All support --json output


bd mol squash handles wisp→digest

Update bd mol squash to handle ephemeral molecules.

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

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

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

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


Monitor npm publish

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

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

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

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


bd prime: AI context loading and Claude Code integration

Implement `bd prime` command and Claude Code hooks for context recovery. Hooks work with BOTH MCP server and CLI approaches - they solve the context memory problem (keeping bd workflow fresh after compaction) not the tool access problem (MCP vs CLI).


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

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

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

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

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

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

This would make cross-rig dependencies much more ergonomic.


Add mol_type schema field to beads

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

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

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

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

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


Fix JSON errors in epic.go

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

Commands affected:
- epicStatusCmd
- epicCloseEligibleCmd


Verify PyPI package

Confirm PyPI package published.

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

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

Should show 0.99.0-test.


Update Homebrew formula

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


Add .beads-backup-* pattern to gitignore template

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

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

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

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


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

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

These are lower priority but should have basic coverage.


Verify the release is complete.

Verify the release is complete.

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

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

# Check npm
npm view @beads/bd version

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

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


bd mol update: Check and update installed formulas

Check for newer versions of installed formulas and update them.

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

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

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


Add type: event for state transitions

Events capture operational state changes as immutable records.

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

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

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


Restart daemons

bd daemons killall


Code smell: ComputeContentHash() is 100+ lines of repetitive code

attached_args: Refactor ComputeContentHash repetitive code

The ComputeContentHash() method in internal/types/types.go (lines 87-190) is over 100 lines of repetitive code:

```go
h.Write([]byte(i.Title))
h.Write([]byte{0}) // separator
h.Write([]byte(i.Description))
h.Write([]byte{0})
h.Write([]byte(i.Design))
// ... repeated 40+ times
```

Consider:
1. Using reflection to iterate over struct fields tagged for hashing
2. Creating a helper function that takes field name and value
3. Using a slice of fields to hash and iterating over it
4. At minimum, extracting the repetitive pattern into a helper

Location: internal/types/types.go:87-190


Run tests and linting

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


Run bump-version.sh

Update all component versions atomically.

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

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)


Support redirect files in .beads/ directory

Gas Town creates polecat worktrees with .beads/redirect files that point to a shared beads database. The bd CLI should:

1. When finding a .beads/ directory, check if it contains a 'redirect' file
2. If redirect exists, read the relative path and use that as the beads directory
3. This allows multiple git worktrees to share a single beads database

Example:
- polecats/alpha/.beads/redirect contains '../../mayor/rig/.beads'
- bd commands from alpha should use mayor/rig/.beads

Currently bd ignores redirect files and either uses the local .beads/ or walks up to find a parent .beads/.

Related: gt-nriy (test message that can't be retrieved due to missing redirect support)


Add git operations for --hard reset

Implement git integration for hard reset mode.

## Operations Needed
1. `git rm -rf .beads/*.jsonl` - remove data files from index
2. `git commit -m 'beads: reset to clean state'` - commit removal
3. After re-init: `git add .beads/` and commit fresh state

## Edge Cases to Handle
- Uncommitted changes in .beads/ - warn or error
- Detached HEAD state - warn, maybe block
- Git not initialized - skip git ops, warn
- Git operations fail mid-way - clear error messaging

## Interface
```go
type GitState struct {
    IsRepo       bool
    IsDirty      bool   // uncommitted changes in .beads/
    IsDetached   bool   // detached HEAD
    Branch       string // current branch name
}

func CheckGitState(beadsDir string) (*GitState, error)
func GitRemoveBeads(beadsDir string) error
func GitCommitReset(message string) error
func GitAddAndCommit(beadsDir, message string) error
```

## Location
`internal/reset/git.go` - keep with reset package for now

Note: Codebase has no central git package. internal/compact/git.go is compact-specific.
Future refactoring could extract shared git utilities, but YAGNI for now.


Merge: bd-l13p

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


Push commit and tag to origin

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

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

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


BondRef data model for compound lineage

Add data model support for tracking compound molecule lineage.

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

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

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

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


Consolidate migrate-* commands into migrate subcommands

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


Add missing updated_at index on issues table

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

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

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

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

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


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.


Enhance bd doctor to suggest reset for broken states

Update bd doctor to detect severely broken states and suggest reset.

## Detection Criteria
Suggest reset when:
- Multiple unfixable errors detected
- Corrupted JSONL that can't be repaired
- Schema version mismatch that can't be migrated
- Daemon state inconsistent and unkillable

## Implementation
Add to doctor's check/fix flow:
```go
if unfixableErrors > threshold {
    suggest('State may be too broken to fix. Consider: bd reset')
}
```

## Output Example
```
✗ Found 5 unfixable errors

  Your beads state may be too corrupted to repair.
  Consider running 'bd reset' to start fresh.
  (Use 'bd reset --backup' to save current state first)
```

## Notes
- Don't auto-run reset, just suggest
- This is lower priority, can be done in parallel with main work


Create git tag v0.33.2

Create the release tag:

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

Verify: `git tag | grep 0.33.2`


Sling reports 'already pinned' but mol status shows empty hook

During swarm operations, gt sling reported beads were 'already pinned' but gt mol status showed nothing on the hook.

**Observed:**
```
$ gt sling bd-9btu beads/Nux
Error: bead bd-9btu is already pinned to gt-beads-nux

$ gt mol status beads/Nux
Nothing on hook - no work slung
```

**Expected:**
If a bead is pinned, mol status should show it. If mol status shows empty, sling should work.

**Workaround:**
Used --force flag to re-sling, which worked.

**Root cause hypothesis:**
Agent bead state may be stored in town beads but polecat's local view doesn't see it, or the pin record exists but session state is stale.


Default to YES for git hooks and merge driver installation

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