Add creator field to Issue struct

Add Creator *EntityRef field to Issue. Tracks who created the issue. Optional, omitted if nil in JSONL. This enables CV chain tracking - every piece of work is attributed to its creator.


Fix FindBeadsDir to prioritize main repo .beads for worktrees

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


Consolidate git context into single cached struct

Code review finding from bd-7di fix.

Currently there are 3 separate caches:
- isWorktreeOnce/isWorktreeResult
- mainRepoRootOnce/mainRepoRootResult/mainRepoRootErr
- repoRootOnce/repoRootResult

These have redundant git calls - e.g. both isWorktreeUncached() and getMainRepoRootUncached() call getGitDirNoError("--git-common-dir").

Could consolidate into a single cached struct:
```go
type gitContext struct {
    gitDir     string
    commonDir  string
    repoRoot   string
    isWorktree bool
}
```

This would reduce git calls further and simplify ResetCaches().

File: internal/git/gitdir.go


Test Second Parent


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

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

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

## Functions Needing Tests

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

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

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

## Implementation Guide

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

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

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

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

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

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


Test coordinator display


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


Wait for CI

Monitor GitHub Actions for release completion.

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

Expected time: 5-10 minutes

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


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.


Audit Current Cache Usage

**Summary:** Comprehensive audit of storage cache usage revealed minimal dependency across server components, with most calls following a consistent pattern. Investigation confirmed cache was largely unnecessary in single-repository daemon architecture.

**Key Decisions:**
- Remove all cache-related environment variables
- Delete server struct cache management fields
- Eliminate cache-specific test files
- Deprecate req.Cwd routing logic

**Resolution:** Cache system will be completely removed, simplifying server storage access and reducing unnecessary complexity with negligible performance impact.

AUDIT COMPLETE

getStorageForRequest() callers: 17 production + 11 test
- server_issues_epics.go: 8 calls
- server_labels_deps_comments.go: 4 calls
- server_export_import_auto.go: 2 calls
- server_compact.go: 2 calls
- server_routing_validation_diagnostics.go: 1 call
- server_eviction_test.go: 11 calls (DELETE entire file)

Pattern everywhere: store, err := s.getStorageForRequest(req) → store := s.storage

req.Cwd usage: Only for multi-repo routing. Local daemon always serves 1 repo, so routing is unused.

MCP server: Uses separate daemons per repo (no req.Cwd usage found). NOT affected by cache removal.

Cache env vars to deprecate:
- BEADS_DAEMON_MAX_CACHE_SIZE (used in server_core.go:63)
- BEADS_DAEMON_CACHE_TTL (used in server_core.go:72)
- BEADS_DAEMON_MEMORY_THRESHOLD_MB (used in server_cache_storage.go:47)

Server struct fields to remove:
- storageCache, cacheMu, maxCacheSize, cacheTTL, cleanupTicker, cacheHits, cacheMisses

Tests to delete:
- server_eviction_test.go (entire file - 9 tests)
- limits_test.go cache assertions

Special consideration: ValidateDatabase endpoint uses findDatabaseForCwd() outside cache. Verify if used, then remove or inline.

Safe to proceed with removal - cache always had 1 entry in local daemon model.


Fix TestImportBugIntegration deadlock in importer_test.go

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

TODO: Test hangs due to database deadlock - needs investigation

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

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


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.


Restart running daemons

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


Monitor GoReleaser CI job

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

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

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

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

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


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

bd sync tries to create worktree for sync.branch even when already on that branch. Should commit directly instead. See GitHub issue #519.


Improve test coverage for internal/daemon (27.3% → 60%)

The daemon package has only 27.3% test coverage. The daemon is critical for background operations and reliability.

Key areas needing tests:
- Daemon autostart logic
- Socket handling
- PID file management
- Health checks

Current coverage: 27.3%
Target coverage: 60%


bd repair command: fix orphaned foreign key refs


Test hook


Test Parent Epic


Test Thread

Initial message for threading test


Add close_reason to JSONL export format documentation

PR #551 now persists close_reason to the database, but there's a question about whether this field should be exported to JSONL format.

## Current State
- close_reason is stored in issues.close_reason column
- close_reason is also stored in events table (audit trail)
- The JSONL export format may or may not include close_reason

## Questions
1. Should close_reason be exported to JSONL format?
2. If yes, where should it go (root level or nested in events)?
3. Should there be any special handling to avoid duplication?
4. How should close_reason be handled during JSONL import?

## Why This Matters
- JSONL is the git-friendly sync format
- Other beads instances import from JSONL
- close_reason is meaningful data that should be preserved across clones

## Suggested Action
- Check if close_reason is currently exported in JSONL
- If not, add it to the export schema
- Document the field in JSONL format spec
- Add tests for round-trip (export -> import -> verify close_reason)


Implement or remove ExpectExit/ExpectStdout verification fields

The Verification struct in internal/types/workflow.go has ExpectExit and ExpectStdout fields that are never used by workflowVerifyCmd. Either implement the functionality or remove the dead fields.


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

## Problem

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

## Reproduction Steps

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

2. Check database - fields are present

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

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

## Root Cause

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

## Workaround

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

## Impact

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

## Suggested Fix

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


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

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


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

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

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

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

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

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


Add covering index (label, issue_id) for label queries

GetIssuesByLabel joins labels table but requires table lookup after using idx_labels_label.

**Query (labels.go:165):**
```sql
SELECT ... FROM issues i
JOIN labels l ON i.id = l.issue_id
WHERE l.label = ?
```

**Problem:** Current idx_labels_label index doesn't cover issue_id, requiring row lookup.

**Solution:** Add migration:
```sql
CREATE INDEX IF NOT EXISTS idx_labels_label_issue ON labels(label, issue_id);
```

This is a covering index - query can be satisfied entirely from the index without touching the labels table rows.


Commit release

Stage and commit all version changes.

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

Review the commit to ensure all expected files are included.


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)


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.


Create git tag v{{version}}

Create the release tag:

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

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


Merge: bd-9usz

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


GH#505: Add bd reset/wipe command

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


Tier 3: AI-powered squash summarization

## Design: Agent-Provided Summarization (Inversion of Control)

bd is a tool FOR agents, not an agent itself. The calling agent provides
the summary; bd just stores it.

### API

```bash
# Agent generates summary, passes to bd
bd mol squash bd-xxx --summary "Agent-generated summary here"

# Without --summary, falls back to basic concatenation
bd mol squash bd-xxx
```

### Gas Town Integration Pattern

```go
// In polecat completion handler or witness
raw := exec.Command("bd", "mol", "show", molID, "--json").Output()
summary := callHaiku(buildSummaryPrompt(raw))  // agent's job
exec.Command("bd", "mol", "squash", molID, "--summary", summary).Run()
```

### Why This Design

| Concern | bd's job | Agent's job |
|---------|----------|-------------|
| Store data | ✅ | |
| Query data | ✅ | |
| Generate summaries | | ✅ |
| Call LLMs | | ✅ |
| Manage API keys | | ✅ |

### Implementation Status

- [x] --summary flag added to bd mol squash
- [x] Tests for agent-provided summary
- [ ] Gas Town integration (separate task)

### Acceptance Criteria

- ✅ bd mol squash --summary uses provided text
- ✅ Without --summary, falls back to concatenation
- ✅ No LLM calls in bd itself


Release complete

Release v0.39.0 is complete!

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

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


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 graph crashes with --no-daemon on closed issues

The `bd graph` command panics with nil pointer dereference when using `--no-daemon` flag on an issue with closed children.

**Reproduction:**
```bash
bd graph bd-qqc --no-daemon
# panic: runtime error: invalid memory address or nil pointer dereference
# in main.computeDependencyCounts
```

**Stack trace:**
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x20 pc=0x1010bdfb0]

goroutine 1 [running]:
main.computeDependencyCounts(...)
    /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:428
main.renderGraph(0x1400033bb80, 0x0)
    /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:307 +0x300
```

**Location:** cmd/bd/graph.go:428 - computeDependencyCounts() not handling nil case


Final release verification

Verify all release artifacts are accessible:

- [ ] `bd --version` shows 0.30.4
- [ ] `bd version --daemon` shows 0.30.4
- [ ] GitHub release exists: https://github.com/steveyegge/beads/releases/tag/v0.30.4
- [ ] `brew upgrade beads && bd --version` shows 0.30.4 (if using Homebrew)
- [ ] `pip show beads-mcp` shows 0.30.4
- [ ] npm package available at 0.30.4
- [ ] `bd info --whats-new` shows 0.30.4 notes

Run final checks:
```bash
bd --version
bd version --daemon
pip show beads-mcp | grep Version
bd info --whats-new
```


Protect pinned issues from bd cleanup/compact

Update bd cleanup and bd compact to never delete pinned issues, even if they are closed. Pinned issues should persist indefinitely as reference material.


Fix unparam unused parameter in cmd/bd/doctor.go:1879

Linting issue: checkGitHooks - path is unused (unparam) at cmd/bd/doctor.go:1879:20. Error: func checkGitHooks(path string) doctorCheck {


Implement bd close --continue flag

Auto-advance to next step in molecule when closing an issue. Referenced by gt-um6q, gt-lz13. Needed for molecule navigation workflow.


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


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

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

## Problem

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

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

## Proposed Solution

### Phase 1: Shell Hooks

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

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

### Lifecycle Events

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

### Phase 2: Molecule Completion Handlers

Molecules could define completion actions:

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

### Phase 3: Gas Town Integration

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

## Implementation Notes

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

## Related

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

## Use Cases

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


GH#444: Fix inconsistent status naming in_progress vs in-progress

Documentation uses in-progress (hyphen) but code expects in_progress (underscore). Update all docs to use canonical in_progress. See GitHub issue #444.


Warn on invalid depends_on references in workflow templates

workflow.go:780-781 silently skips invalid dependency names. Should log a warning when a depends_on reference doesn't match any task ID in the template.


Fix unparam unused parameter in cmd/bd/doctor.go:541

Linting issue: checkHooksQuick - path is unused (unparam) at cmd/bd/doctor.go:541:22. Error: func checkHooksQuick(path string) string {


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

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


Release v{{version}}

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


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


doctor --fix should export when DB has more issues than JSONL

When 'bd doctor' detects a count mismatch (DB has more issues than JSONL), it currently recommends 'bd sync --import-only', which imports JSONL into DB. But JSONL is the source of truth, not the DB.

**Current behavior:**
- Doctor detects: DB has 355 issues, JSONL has 292
- Recommends: 'bd sync --import-only'
- User runs it: Returns '0 created, 0 updated' (no-op, because JSONL hasn't changed)
- User is stuck

**Root cause:**
The doctor fix is one-directional (JSONL→DB) when it should be bidirectional. If DB has MORE issues, they haven't been exported yet - the fix should be 'bd export' (DB→JSONL), not import.

**Desired fix:**
In fix.DBJSONLSync(), detect which has more data:
- If DB > JSONL: Run 'bd export' to sync JSONL (since DB is the working copy)
- If JSONL > DB: Run 'bd sync --import-only' to import (JSONL is source of truth)
- If equal but timestamps differ: Detect based on file mtime

This makes 'bd doctor --fix' actually fix the problem instead of being a no-op.


Investigate Claude Code crash logging improvements

## Problem

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

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

## What we found

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

## Desired improvements

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

## Investigation paths

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

## Related

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


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

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

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

## Functions Needing Tests

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

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

## Implementation Guide

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

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

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

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

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

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


Wait for GoReleaser

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


bd mol bond: Dynamic bond with variable substitution

Implement dynamic molecule bonding with runtime variable substitution.

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

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

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

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


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


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

## Problem

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

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

## Impact

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

## Root Cause

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

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

## Proposed Fix

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

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

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

## Files to Modify

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

## Fix Implemented

Modified cmd/bd/sync.go mergeSyncBranch function:

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

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

## Testing

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


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.


Upgrade beads-mcp

pip3 install --upgrade beads-mcp


beads-release

Beads release workflow - from version bump to verified release.

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

## Usage

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

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


Sync race: rebase-based divergence recovery resurrects tombstones

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

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

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

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

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

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


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

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


Add 'bd status' command for issue database overview

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

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

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

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

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


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

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

```go
{
    Version: "{{version}}",
    Date:    "{{date}}",
    Changes: []string{
        // Add notable changes here
    },
},
```

Copy changes from CHANGELOG.md [Unreleased] section.


Restart daemon with {{version}}

Restart the bd daemon to pick up new version:

```bash
bd daemon --stop
bd daemon --start
bd daemon --health  # Verify Version: {{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


Add external_projects to config schema

Add external_projects mapping to .beads/config.yaml:

```yaml
external_projects:
  beads: ../beads
  gastown: ../gastown
  other: /absolute/path/to/project
```

Used by bd ready and other commands to resolve external: references.

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


Restart daemon with 0.33.2

Restart the bd daemon to pick up new version:

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


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

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

## Usage

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

## Behavior

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

## Output

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

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

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

## If no next step

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

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

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

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


Create and push git tag v{{version}}

Create the release tag and push it:

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

This triggers the GoReleaser GitHub Action to build release binaries.


Graph Link: replies_to for conversation threading

Implement replies_to link type for message threading.

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

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

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

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


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 CHANGELOG.md for 0.33.2

In CHANGELOG.md:

1. Change `## [Unreleased]` section header to `## [0.33.2] - 2025-12-21`
2. Add new empty `## [Unreleased]` section above it
3. Review and clean up the changes list

Format:
```markdown
## [Unreleased]

## [0.33.2] - 2025-12-21

### Added
- ...

### Changed
- ...

### Fixed
- ...
```


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.


Unify template.go and mol.go under bd mol

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


Command Set Standardization & Flag Consistency

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

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

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

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


CLI cleanup: Rename bd mol catalog to bd formula list

## Problem
`bd mol catalog` shows formulas, not molecules. This is confusing.

Current state:
- `bd formula list` - lists formulas
- `bd mol catalog` - ALSO lists formulas (duplicate!)

The "mol" namespace should be for molecules (instantiated work), not formulas (templates).

## Proposed Change
- Remove `bd mol catalog`
- Keep `bd formula list` as the canonical command
- Update docs to use `bd formula list`

## Alternative
If we want a "catalog" concept separate from "list", make it `bd formula catalog`.


🤝 HANDOFF: Review remaining beads PRs

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

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

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

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

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


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

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

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

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


Update CHANGELOG.md for {{version}}

In CHANGELOG.md:

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

Format:
```markdown
## [Unreleased]

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

### Added
- ...

### Changed
- ...

### Fixed
- ...
```


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.


Beads commands operate on stale database without warning

All beads read operations should validate database is in sync with JSONL before proceeding.

**Current Behavior:**
- Commands can query/read from stale database
- Only mutation operations (like 'bd sync') check if JSONL is newer
- User gets incorrect results without realizing database is out of sync

**Expected Behavior:**
- All beads commands should have pre-flight check for database freshness
- If JSONL is newer than database, refuse to operate with error: "Database out of sync. Run 'bd import' first."
- Same safety check that exists for 'bd sync' should apply to ALL operations

**Impact:**
- Users make decisions based on incomplete/outdated data
- Silent failures lead to confusion (e.g., thinking issues don't exist when they do)
- Similar to running git commands on stale repo without being warned to pull

**Example:**
- Searched for bd-g9eu issue file: not found
- Issue exists in .beads/issues.jsonl (in git)
- Database was stale, but no warning was given
- Led to incorrect conclusion that issue was already closed/deleted

## Implementation Complete

**Phase 1: Created staleness check (cmd/bd/staleness.go)**
- ensureDatabaseFresh() function checks JSONL mtime vs last_import_time
- Returns error with helpful message when database is stale
- Auto-skips in daemon mode (daemon has auto-import)

**Phase 2: Added to all read commands**
- list, show, ready, status, stale, info, duplicates, validate
- Check runs before database queries in direct mode
- Daemon mode already protected via checkAndAutoImportIfStale()

**Phase 3: Code Review Findings**
See follow-up issues:
- bd-XXXX: Add warning when staleness check errors
- bd-YYYY: Improve CheckStaleness error handling
- bd-ZZZZ: Refactor redundant daemon checks (low priority)

**Testing:**
- Build successful: go build ./cmd/bd
- Binary works: ./bd --version
- Ready for manual testing

**Next Steps:**
1. Test with stale database scenario
2. Implement review improvements
3. Close issue when tests pass


bd repair: Limit verbose output for large orphan counts

When there are thousands of orphaned references, bd repair prints them all, flooding the terminal.

Fix: Add --verbose flag; default to summary only (e.g., 'Found 1,234 orphaned dependencies'). Only print individual items with --verbose or when count is small (<20).


bd repair: Check comments and events tables for orphans

bd repair currently checks dependencies and labels tables, but the schema also has comments and events tables with issue_id foreign keys that could be orphaned.

Add orphan detection and cleanup for:
- comments table (issue_id not in issues)
- events table (issue_id not in issues)


Deleted issues resurrect during bd sync (tombstones not propagating)

## Problem

When issues are deleted with bd delete --force, they get deleted from the local DB but resurrect during the next bd sync.

## Reproduction

1. Observe orphan warnings (bd-cb64c226.*, bd-cbed9619.*)
2. Delete them: bd delete bd-cb64c226.1 ... --force
3. Run bd sync
4. Orphan warnings reappear - issues were resurrected!

## Root Cause Hypothesis

The sync branch workflow (beads-sync) has the old state before deletions. When bd sync pulls from beads-sync and copies JSONL to main, the deleted issues are re-imported.

Tombstones may not be properly:
1. Written to beads-sync during export
2. Propagated during pull/merge
3. Honored during import

## Related

- bd-7b7h: chicken-and-egg sync.branch bug (same workflow)
- bd-ncwo: ID-based fallback matching to prevent ghost resurrection

## Files to Investigate

- cmd/bd/sync.go (export/import flow)
- internal/syncbranch/worktree.go (PullFromSyncBranch, copyJSONLToMainRepo)
- internal/importer/ (tombstone handling)


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)


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


Verify release artifacts

Check GitHub releases page - binaries for darwin/linux/windows should be available


Auto-migrate database on CLI version bump

When CLI is upgraded (e.g., 0.24.0 → 0.24.1), database version becomes stale. Add auto-migration in PersistentPreRun or daemon startup. Check dbVersion != CLIVersion and run bd migrate automatically. Fixes recurring UX issue where bd doctor shows version mismatch after every CLI upgrade.


Update local binary

cp ./bd ~/.local/bin/bd


Update CHANGELOG header

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


Add bd daemon --stop-all command to kill all daemon processes

Currently there's no easy way to stop all running bd daemon processes. Users must resort to pkill -f 'bd daemon' or similar shell commands.

Add a --stop-all flag to bd daemon that:
1. Finds all running bd daemon processes (not just the current repo's daemon)
2. Gracefully stops them all
3. Reports how many were stopped

This is useful when:
- Multiple daemons are running and causing race conditions
- User wants a clean slate before running bd sync
- Debugging daemon-related issues


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

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

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

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

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


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.


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)


Design contributor namespace isolation for beads pollution prevention

## Problem

When contributors work on beads-the-project using beads-the-tool, their personal work-tracking issues leak into PRs. The .beads/issues.jsonl is intentionally tracked (it's the project's issue database), but contributors' local issues pollute the diff.

This is a recursion problem unique to self-hosting projects.

## Possible Solutions to Explore

1. **Contributor namespaces** - Each contributor gets a private prefix (e.g., `bd-steve-xxxx`) that's gitignored or filtered
2. **Separate database** - Contributors use BEADS_DIR pointing elsewhere for personal tracking
3. **Issue ownership/visibility flags** - Mark issues as "local-only" vs "project"
4. **Prefix-based filtering** - Configure which prefixes are committed vs ignored

## Design Considerations

- Should be zero-friction for contributors (no manual setup)
- Must not break existing workflows
- Needs to work with sync/collaboration features
- Consider: what if a "personal" issue graduates to "project" issue?

## Expansion Needed

This is a placeholder. Needs detailed design exploration before implementation.


HOP-compatible schema additions

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

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

## P1 Changes (Must Have Before Launch)

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

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

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

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

## P2 Changes (Should Have)

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

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

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

## P3 Changes (Nice to Have)

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

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

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


Consider caching external dep resolution results

Each call to GetReadyWork re-checks all external dependencies by:
1. Querying for external deps in the local database
2. Opening each external project's database
3. Querying for closed issues with provides: labels

For workloads with many external deps or slow external databases, this adds latency on every bd ready call.

Potential optimizations:
- In-memory TTL cache for external dep status (e.g., 60 second TTL)
- Store resolved status in a local cache table with timestamp
- Batch resolution of common project/capability pairs

This is not urgent - current implementation is correct and performant for typical workloads. Only becomes an issue with many external deps across many projects.


Update LINTING.md with current baseline

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


bd find-duplicates - AI-powered duplicate detection

Find semantically duplicate issues.

Approaches:
1. Mechanical: Exact title/description matching
2. Embeddings: Cosine similarity (cheap, scalable)
3. AI: LLM-based semantic comparison (expensive, accurate)

Uses embeddings by default for >100 issues.

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


Merge: bd-05a8

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


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.


Introduce slog for structured daemon logging.

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

This produces unstructured, hard-to-parse logs.

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

## Implementation

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

```go
package daemon

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

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

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

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

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

    slog.SetDefault(slog.New(handler))

    return cleanup, nil
}
```

### 2. Add log level flag

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

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

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

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

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

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

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

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

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

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

### 4. Files to Update

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

### 5. Log output examples

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

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

## Migration Strategy

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

## Testing

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

# Check logs
bd daemons logs . | head -20

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

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


Relocate quickstart to bd help quickstart or docs

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

## Options

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

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

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

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

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


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.


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.


Create and push tag

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


Audit and fix JSONL filename mismatches across all repo clones

## Problem

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

## Root Cause

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

## Task

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

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

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

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

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

### Step 3: Create audit report

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

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

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

### Step 5: Generate fix script

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

### Expected Output

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

### Edge Cases

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

### Success Criteria

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


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

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

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

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

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

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


Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1181

Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1181:4. Error: rows.Close()


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


Run bump-version.sh

Update all component versions atomically.

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

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


Blocking logic: Include 'hooked' in blocking statuses

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

## Current Logic (line ~163)

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

## Updated Logic

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

## Files to Update

- internal/storage/sqlite/blocked_cache.go

## Test Cases

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


Update molecular chemistry docs with discoveries

Document recent discoveries about molecule/epic relationship and lifecycle:

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

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

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

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

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


Test Agent


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

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

Extract to a shared helper function like:

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

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


Fix: Empty coordinator shows blank in bd swarm create output

Line 992 prints 'Coordinator: ' even when coordinator is empty string.

Should either:
- Print 'Coordinator: (none)'
- Skip the line entirely when empty

Minor polish issue.


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


Improve CheckStaleness error handling

## Problem

CheckStaleness returns 'false' (not stale) for multiple error conditions instead of returning errors. This masks problems.

**Location:** internal/autoimport/autoimport.go:253-285

## Edge Cases That Return False

1. **Invalid last_import_time format** (line 259-262)
2. **No JSONL file found** (line 267-277)
3. **JSONL stat fails** (line 279-282)

## Fix

Return errors for abnormal conditions:

```go
lastImportTime, err := time.Parse(time.RFC3339, lastImportStr)
if err != nil {
    return false, fmt.Errorf("corrupted last_import_time: %w", err)
}

if jsonlPath == "" {
    return false, fmt.Errorf("no JSONL file found")
}

stat, err := os.Stat(jsonlPath)
if err != nil {
    return false, fmt.Errorf("cannot stat JSONL: %w", err)
}
```

## Impact
Medium - edge cases are rare but should be handled

## Effort
30 minutes - requires updating callers in RPC server


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

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

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

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

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

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


Fix multi-round convergence for N-way collisions

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

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

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


bd merge omits priority:0 due to omitempty JSON tag

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


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


Revert/remove YAML workflow system

Revert the recent commit and remove all YAML workflow code:

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

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


Remove legacy autoflush code paths

## Problem

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

## Locations

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

## Current Behavior

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

## Proposed Fix

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

## Risk

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


Implement subgraph cloning with variable substitution

Core implementation of template instantiation:

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

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


Run tests

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


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


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


bd delete --from-file should add deleted issues to deletions manifest

When using bd delete --from-file to bulk delete issues, the deleted issue IDs are not being added to the deletions.jsonl manifest.

This causes those issues to be resurrected during bd sync when git history scanning finds them in old commits.

Expected: All deleted issues should be added to deletions.jsonl so they wont be reimported from git history.

Workaround: Manually add deletion records to deletions.jsonl.


UI: Style 'hooked' status in bd list output

Add styling for 'hooked' status in bd list and bd show output.

## Semantic

'hooked' means actively being worked by an agent (GUPP). Should be visually prominent.

## Suggested Styling

- Color: Cyan or bright blue (active, in-flight)
- Distinct from 'in_progress' (which is more passive/cooperative)

## Files to Update

- internal/ui/styles.go - Add case for 'hooked' status


Update local installation

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


Add bd reset command for clean slate restart

Implement a `bd reset` command to reset beads to a clean starting state.

## Context
GitHub issue #479 - users sometimes get beads into an invalid state after updates, and there's no clean way to start fresh. The git backup/restore mechanism that protects against accidental deletion also makes it hard to intentionally reset.

## Design

### Command Interface
```
bd reset [--hard] [--force] [--backup] [--dry-run] [--no-init]
```

| Flag | Effect |
|------|--------|
| `--hard` | Also remove from git index and commit |
| `--force` | Skip confirmation prompt |
| `--backup` | Create `.beads-backup-{timestamp}/` first |
| `--dry-run` | Preview what would happen |
| `--no-init` | Don't re-initialize after clearing |

### Reset Levels
1. **Soft Reset (default)** - Kill daemons, clear .beads/, re-init. Git history unchanged.
2. **Hard Reset (`--hard`)** - Also git rm and commit the removal, then commit fresh state.

### Implementation Flow
1. Validate .beads/ exists
2. If not --force: show impact summary, prompt confirmation
3. If --backup: copy .beads/ to .beads-backup-{timestamp}/
4. Kill daemons
5. If --hard: git rm + commit
6. rm -rf .beads/*
7. If not --no-init: bd init (and git add+commit if --hard)
8. Print summary

### Safety Mechanisms
- Confirmation prompt (skip with --force)
- Impact summary (issue/tombstone counts)
- Backup option
- Dry-run preview
- Git dirty check warning

### Code Structure
- `cmd/bd/reset.go` - CLI command
- `internal/reset/` - Core logic package


Dynamic Molecule Bonding: Fanout patterns for patrol molecules

## Vision

Enable molecules to dynamically spawn child molecules at runtime based on discovered
work. This is the foundation for the "Christmas Ornament" pattern where a patrol
molecule grows arms per-polecat.

## The Activity Feed Vision

Instead of parsing agent logs, users see structured work state:

```
[14:32:08] + patrol-x7k.arm-ace bonded (5 steps)
[14:32:08] + patrol-x7k.arm-nux bonded (5 steps)
[14:32:09] → patrol-x7k.arm-ace.capture in_progress
[14:32:10] ✓ patrol-x7k.arm-ace.capture completed
[14:32:14] ✓ patrol-x7k.arm-ace.decide completed (action: nudge-1)
```

This requires beads to track molecule step state transitions in real-time.

## Key Primitives Needed

### 1. Dynamic Bond with Variables
```bash
bd mol bond mol-polecat-arm <parent-wisp-id> \
  --var polecat_name=ace \
  --var rig=gastown
```

Creates wisp children under the parent:
- parent-id.arm-ace
- parent-id.arm-ace.capture
- parent-id.arm-ace.assess
- etc.

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

The `WaitsFor: all-children` directive makes this a fanout gate - it can't
proceed until ALL dynamically-bonded children complete.

### 3. Activity Feed Query
```bash
bd activity --follow              # Real-time state stream
bd activity --mol <id>            # Activity for specific molecule
bd activity --since 5m            # Last 5 minutes
```

### 4. Parallel Step Detection
Steps with no inter-dependencies should be flagged as parallelizable.
When arms are bonded, their steps can run in parallel across arms.

## Use Case: mol-witness-patrol

The Witness monitors N polecats where N varies at runtime:

```
survey-workers discovers: [ace, nux, toast]
For each polecat:
  bd mol bond mol-polecat-arm <patrol-id> --var polecat_name=<name>
aggregate step waits for all arms to complete
```

This creates the Christmas Ornament shape:
- Trunk: preflight steps
- Arms: per-polecat inspection molecules
- Base: cleanup after all arms complete

## Design Docs

See Gas Town docs:
- docs/molecular-chemistry.md (updated with Christmas Ornament pattern)
- docs/architecture.md (activity feed section)

## Dependencies

This epic may depend on:
- Wisp storage (.beads-wisp/) - already implemented
- Variable substitution in molecules - may need enhancement


Add empty tasks validation in workflow create

workflow.go:321 will panic if wf.Tasks is empty. Add validation that len(wf.Tasks) > 0 before accessing wf.Tasks[0].


Simplify wisp architecture: single DB with ephemeral flag

## Problem

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

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

## Current State

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

## Proposed Solution

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

## Migration

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

## Acceptance Criteria

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


Add date and priority filters to bd search for parity with bd list.

## Current State
bd search supports: --status, --type, --assignee, --label, --limit
bd list supports: all of the above PLUS date ranges and priority filters

## Filters to Add

### Priority Filters
```bash
bd search "query" --priority 1           # Exact priority
bd search "query" --priority-min 0       # P0 and above (higher priority)
bd search "query" --priority-max 2       # P2 and below (lower priority)
```

### Date Filters
```bash
bd search "query" --created-after 2025-01-01
bd search "query" --created-before 2025-12-31
bd search "query" --updated-after 2025-01-01
bd search "query" --closed-after 2025-01-01
```

### Content Filters
```bash
bd search "query" --desc-contains "bug"
bd search "query" --notes-contains "todo"
bd search "query" --empty-description    # Issues with no description
bd search "query" --no-assignee          # Unassigned issues
bd search "query" --no-labels            # Issues without labels
```

## Files to Modify

### 1. cmd/bd/search.go
Add flag definitions in init():
```go
searchCmd.Flags().IntP("priority", "p", -1, "Filter by exact priority (0-4)")
searchCmd.Flags().Int("priority-min", -1, "Filter by minimum priority")
searchCmd.Flags().Int("priority-max", -1, "Filter by maximum priority")
searchCmd.Flags().String("created-after", "", "Filter by creation date (YYYY-MM-DD)")
searchCmd.Flags().String("created-before", "", "Filter by creation date")
searchCmd.Flags().String("updated-after", "", "Filter by update date")
searchCmd.Flags().String("updated-before", "", "Filter by update date")
searchCmd.Flags().String("closed-after", "", "Filter by close date")
searchCmd.Flags().String("closed-before", "", "Filter by close date")
searchCmd.Flags().String("desc-contains", "", "Filter by description content")
searchCmd.Flags().String("notes-contains", "", "Filter by notes content")
searchCmd.Flags().Bool("empty-description", false, "Filter issues with empty description")
searchCmd.Flags().Bool("no-assignee", false, "Filter unassigned issues")
searchCmd.Flags().Bool("no-labels", false, "Filter issues without labels")
```

### 2. internal/rpc/protocol.go
Update SearchArgs struct:
```go
type SearchArgs struct {
    Query             string
    Filter            types.IssueFilter
    // Already has most fields via IssueFilter
}
```

Note: types.IssueFilter already has these fields - just need to wire them up!

### 3. cmd/bd/search.go Run function
Parse flags and populate filter:
```go
if priority, _ := cmd.Flags().GetInt("priority"); priority >= 0 {
    filter.Priority = &priority
}
if createdAfter, _ := cmd.Flags().GetString("created-after"); createdAfter != "" {
    t, err := time.Parse("2006-01-02", createdAfter)
    if err != nil {
        FatalError("invalid date format for --created-after: %v", err)
    }
    filter.CreatedAfter = &t
}
// ... similar for other flags
```

## Implementation Steps

1. **Check types.IssueFilter** - verify all needed fields exist
2. **Add flags to search.go** init()
3. **Parse flags** in Run function
4. **Pass to SearchIssues** via filter
5. **Test all combinations**

## Testing
```bash
# Create test issues
bd create "Test P1" -p 1
bd create "Test P2" -p 2 --description "Has description"

# Test filters
bd search "" --priority 1
bd search "" --priority-min 0 --priority-max 1
bd search "" --empty-description
bd search "" --desc-contains "description"
```

## Success Criteria
- All filters work in both direct and daemon mode
- Date parsing handles YYYY-MM-DD format
- --json output includes filtered results
- Help text documents all new flags


Install 0.30.4 MCP server locally

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

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

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


Add gate checking to Deacon patrol loop

Integrate gate checking into Deacon's patrol cycle.

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

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

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

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


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

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

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

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

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


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


Rename ephemeral → wisp throughout codebase

## The Change

Rename 'ephemeral' to 'wisp' throughout the beads codebase.

## Why

**Ephemeral** is:
- 4 syllables (too long)
- Greek/academic (doesn't match bond/burn/squash)
- Overused in tech (K8s, networking, storage)
- Passive/descriptive

**Wisp** is:
- 1 syllable (matches bond/burn/squash)
- Evocative - you can SEE a wisp
- Steam engine metaphor - Gas Town is engines, steam wisps rise and dissipate
- Will-o'-the-wisp - transient spirits that guide then vanish
- Unique - nobody else uses it

## The Steam Engine Metaphor

```
Engine does work → generates steam
Steam wisps rise → execution trace
Steam condenses → digest (distillate)
Steam dissipates → cleaned up (burned)
```

## Full Vocabulary

| Term | Meaning |
|------|---------|
| bond | Attach proto to work (creates wisps) |
| wisp | Temporary execution step |
| squash | Condense wisps into digest |
| burn | Destroy wisps without record |
| digest | Permanent condensed record |

## Changes Required

### Code
- `Ephemeral bool` → `Wisp bool` in types/issue.go
- `--ephemeral` flag → remove (wisp is default)
- `--persistent` flag → keep as opt-out
- `bd cleanup --ephemeral` → `bd cleanup --wisps`
- Update all references in mol_*.go files

### Docs
- Update all documentation
- Update CLAUDE.md examples
- Update CLI help text

### Database Migration
- Add migration to rename field (or keep internal name, just change API)

## Example Usage After

```bash
bd mol bond mol-polecat-work   # Creates wisps (default)
bd mol bond mol-xxx --persistent  # Creates permanent issues
bd mol squash bd-xxx           # Condenses wisps → digest
bd cleanup --wisps             # Clean old wisps
bd list --wisps                # Show wisp issues
```


bd mol burn deletes ephemeral without digest

Update bd mol burn to handle ephemeral molecules.

## Behavior for Ephemeral Molecules
- Delete wisp from .beads-ephemeral/
- NO digest created (unlike squash)
- Used for abandoned/crashed cycles

## Difference from Squash
| Command | Ephemeral Behavior |
|---------|-------------------|
| squash | Delete wisp, create digest |
| burn | Delete wisp, no trace |

## Implementation
- Detect if molecule is ephemeral
- Delete from ephemeral store
- Skip digest creation


Improve test coverage for cmd/bd/setup (28.4% → 50%)

The setup package has only 28.4% test coverage. Setup commands are critical for first-time user experience.

Current coverage: 28.4%
Target coverage: 50%


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.


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.


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.


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.


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


Parallel step detection in molecules

Detect and flag parallelizable steps in molecules.

## Detection Rules
Steps can run in parallel when:
1. No Needs dependencies between them
2. Not in same sequential chain
3. Across dynamic arms (arm-ace and arm-nux can parallelize)

## Output in bd mol show
```
patrol-x7k (mol-witness-patrol)
├── inbox-check [completed]
├── survey-workers [completed]
│   ├── arm-ace [parallel group A]
│   │   ├── capture [can parallelize]
│   │   ├── assess [needs: capture]
│   │   └── execute [needs: assess]
│   └── arm-nux [parallel group A]
│       ├── capture [can parallelize]
│       └── ...
├── aggregate [gate: waits for all-children]
```

## Flags
- `bd mol show <id> --parallel` - Highlight parallel opportunities
- `bd ready --mol <id>` - List all steps that can run now

## Future: Parallel Execution Hints
For agents using Task tool subagents:
- Identify independent arms that can run simultaneously
- Suggest parallelization strategy


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.


GH#518: Document bd setup command

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


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.


Refactor: Extract shared getEpicChildren helper for swarm commands

## Context
`getSwarmStatus` and `analyzeEpicForSwarm` in cmd/bd/swarm.go have nearly identical code for:
- Getting epic dependents via GetDependents
- Filtering to parent-child relationships
- Building childIDSet map
- Building dependency maps

## Implementation
1. Extract `getEpicChildren(ctx, store, epicID)` that returns:
   - `[]*types.Issue` - child issues
   - `map[string]bool` - child ID set
   - `map[string][]string` - dependency map (within children)
   - `error`

2. Refactor both `analyzeEpicForSwarm` and `getSwarmStatus` to use it

3. Consider defining a named interface for the store methods:
   ```go
   type SwarmStore interface {
       GetIssue(context.Context, string) (*types.Issue, error)
       GetDependents(context.Context, string) ([]*types.Issue, error)
       GetDependencyRecords(context.Context, string) ([]*types.Dependency, error)
   }
   ```

## Files
- cmd/bd/swarm.go (lines 150-248, 616-682)

## Acceptance
- No duplicated code for fetching epic children
- Both commands produce identical results as before


Improve test coverage for internal/beads (48.1% → 70%)

Improve test coverage for internal/beads package from 48% to 70%.

## Current State
- Coverage: 48.4%
- Files: beads.go, fingerprint.go
- Tests: beads_test.go (moderate coverage)

## Functions Needing Tests

### beads.go (database discovery)
- [ ] followRedirect - needs redirect file tests
- [ ] findDatabaseInBeadsDir - needs various dir structures
- [x] NewSQLiteStorage - likely covered
- [ ] FindDatabasePath - needs BEADS_DB env var tests
- [ ] hasBeadsProjectFiles - needs file existence tests
- [ ] FindBeadsDir - needs directory traversal tests
- [ ] FindJSONLPath - needs path derivation tests
- [ ] findGitRoot - needs git repo tests
- [ ] findDatabaseInTree - needs nested directory tests
- [ ] FindAllDatabases - needs multi-database tests
- [ ] FindWispDir - needs wisp directory tests
- [ ] FindWispDatabasePath - needs wisp path tests
- [ ] NewWispStorage - needs wisp storage tests
- [ ] EnsureWispGitignore - needs gitignore creation tests
- [ ] IsWispDatabase - needs path classification tests

### fingerprint.go (repo identification)
- [ ] ComputeRepoID - needs various remote URL tests
- [ ] canonicalizeGitURL - needs URL normalization tests
- [ ] GetCloneID - needs clone identification tests

## Implementation Guide

1. **Use temp directories:**
   ```go
   func TestFindBeadsDir(t *testing.T) {
       tmpDir := t.TempDir()
       beadsDir := filepath.Join(tmpDir, ".beads")
       os.MkdirAll(beadsDir, 0755)

       // Create test files
       os.WriteFile(filepath.Join(beadsDir, "beads.db"), []byte{}, 0644)

       // Change to tmpDir and test
       oldWd, _ := os.Getwd()
       os.Chdir(tmpDir)
       defer os.Chdir(oldWd)

       result := FindBeadsDir()
       assert.Equal(t, beadsDir, result)
   }
   ```

2. **Test scenarios:**
   - BEADS_DB environment variable set
   - .beads/ in current directory
   - .beads/ in parent directory
   - Redirect file pointing elsewhere
   - No beads directory found
   - Wisp directory alongside main beads

3. **Git remote URL tests:**
   ```go
   tests := []struct{
       input    string
       expected string
   }{
       {"git@github.com:user/repo.git", "github.com/user/repo"},
       {"https://github.com/user/repo", "github.com/user/repo"},
       {"ssh://git@github.com/user/repo.git", "github.com/user/repo"},
   }
   ```

## Success Criteria
- Coverage ≥ 70%
- All FindXxx functions have tests
- Environment variable handling tested
- Edge cases (missing dirs, redirects) covered

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


Push release tag

Push the version tag to trigger CI release.

```bash
git push origin v0.39.0
```

This triggers GitHub Actions to build artifacts and publish.


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


Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1186

Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1186:2. Error: rows.Close()