Update JSONL format to use hash IDs

Update JSONL import/export to use hash IDs, store aliases separately.

## Current JSONL Format
```jsonl
{"id":"bd-1c63eb84","title":"Fix bug","status":"open",...}
{"id":"bd-9063acda","title":"Add test","status":"open",...}
```

## New JSONL Format (Option A: Include Alias)
```jsonl
{"id":"bd-af78e9a2","alias":1,"title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","alias":2,"title":"Add test","status":"open",...}
```

## New JSONL Format (Option B: Hash ID Only)
```jsonl
{"id":"bd-af78e9a2","title":"Fix bug","status":"open",...}
{"id":"bd-e5f6a7b8","title":"Add test","status":"open",...}
```

Store aliases in separate .beads/aliases.jsonl (local only, git-ignored):
```jsonl
{"hash":"bd-af78e9a2","alias":1}
{"hash":"bd-e5f6a7b8","alias":2}
```

**Recommendation**: Option B (hash only in main JSONL)
- Cleaner git diffs (no alias conflicts)
- Aliases are workspace-local preference
- Main JSONL is canonical, portable

## Export Changes
File: cmd/bd/export.go
```go
// Export issues with hash IDs
for _, issue := range issues {
    json := marshalIssue(issue)  // Uses issue.ID (hash)
    // Don't include alias in JSONL
}

// Separately export aliases to .beads/aliases.jsonl
exportAliases(issues)
```

## Import Changes
File: cmd/bd/import.go, internal/importer/importer.go
```go
// Import issues by hash ID
issue := unmarshalIssue(line)
// Assign new alias on import (don't use incoming alias)
issue.Alias = getNextAlias()

// No collision detection needed! Hash IDs are globally unique
```

## Dependency Reference Format
No change needed - already uses issue IDs:
```json
{"depends_on_id":"bd-af78e9a2","type":"blocks"}
```

## Files to Modify
- cmd/bd/export.go (use hash IDs)
- cmd/bd/import.go (import hash IDs, assign aliases)
- internal/importer/importer.go (remove collision detection!)
- .gitignore (add .beads/aliases.jsonl)

## Testing
- Test export produces hash IDs
- Test import assigns new aliases
- Test dependencies preserved with hash IDs
- Test no collision detection triggered


Implement hash ID generation in CreateIssue

Replace sequential ID generation with hash-based IDs in CreateIssue function.

## Current Behavior (internal/storage/sqlite/sqlite.go)
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // ID comes from auto-increment counter
    // Collisions possible across clones
}
```

## New Behavior
```go
func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {
    // Generate hash ID if not provided
    if issue.ID == "" {
        issue.ID = idgen.GenerateHashID(
            issue.Title,
            issue.Description,
            time.Now(),
            s.workspaceID,
        )
    }

    // Assign next alias
    issue.Alias = s.getNextAlias()

    // Insert with hash ID + alias
    // ...
}
```

## Workspace ID Generation
Add to database initialization:
```go
// Generate stable workspace ID (persisted in .beads/workspace_id)
workspaceID := getOrCreateWorkspaceID()
```

Options for workspace ID:
1. Hostname + random suffix
2. UUID (random)
3. Git remote URL hash (deterministic per repo)

Recommended: Option 3 (git remote hash) for reproducibility

## Hash Collision Detection
```go
// On insert, check for collision (unlikely but possible)
existing, err := s.GetIssue(ctx, issue.ID)
if err == nil {
    // Hash collision! Add random suffix and retry
    issue.ID = issue.ID + "-" + randomSuffix(4)
}
```

## Files to Create/Modify
- internal/types/id_generator.go (new)
- internal/storage/sqlite/sqlite.go (CreateIssue)
- internal/storage/sqlite/workspace.go (new - workspace ID management)
- .beads/workspace_id (new file, git-ignored)

## Testing
- Test hash ID generation is deterministic
- Test collision detection and retry
- Test workspace ID persistence
- Benchmark: hash generation performance (<1μs)


Add index usage verification test for external_ref lookups

Currently we test that idx_issues_external_ref index exists, but we don't verify that it's actually being used by the query planner.

Proposed solution:
- Add test using EXPLAIN QUERY PLAN
- Verify that 'SEARCH TABLE issues USING INDEX idx_issues_external_ref' appears in plan
- Ensures O(1) lookup performance is maintained

Related: bd-1022
Files: internal/storage/sqlite/external_ref_test.go:260


bd swarm create command

Add bd swarm create to create a swarm molecule from an epic.

## Context
Swarms ARE persistent molecules - not separate JSON state. This command creates
a swarm molecule that:
- Links to the epic it orchestrates
- Has mol_type=swarm
- Specifies a coordinator
- Can be discovered by any agent

## Core Principle: Discovery Over Tracking
The swarm molecule doesn't track step-by-step state. Instead:
- Query bd ready --parent=<epic> to find the ready front
- Query bd list --parent=<epic> --status=closed to find completed
- Derive progress from children's statuses

## Implementation
1. New command: bd swarm create <epic-id> [--coordinator=<addr>]
2. Validate epic structure first (call bd swarm validate)
3. Create molecule bead:
   - mol_type: swarm
   - epic: <epic-id>
   - coordinator: <addr>
   - status: open
4. Return the swarm molecule ID

## Single-Issue Wrapping
If given a single issue (not epic), auto-wrap:
- Create an epic with that issue as child
- Then create swarm molecule

## Reference
~/gt/docs/swarm-architecture.md - 'Swarm Structure' section

## Acceptance
- bd swarm create <epic> creates a swarm molecule
- Molecule has mol_type=swarm
- Links to epic via epic field
- Validates structure before creation


🤝 HANDOFF: v0.31.0 released, molecules discussion

Session completed 0.31.0 release and had important molecules discussion.

## Completed
- v0.31.0 released (deferred status, audit trail, directory labels, etc.)
- Fixed lint issues, hook version markers, codesigning
- All CI green, artifacts verified

## Filed Issues
- bd-usro: Rename template instantiate → bd mol bond
- bd-y8bj: Auto-detect identity for bd mail (P1 bug)
- gt-975: Molecule execution support for polecats/crew
- gt-976: Crew lifecycle support in Deacon

## Key Insight
Molecules are the future - TodoWrite is ephemeral, molecules are persistent institutional memory on the world chain. I tried to use TodoWrite for version bump and missed steps (codesigning, MCP verification). Molecules would have caught this.

## Next Steps
- bd mol bond implementation is priority
- Max has gt-976 for crew lifecycle (enables automated refresh mid-molecule)

Check bd ready and gt-975/976 status.


Prefix detection treats embedded hyphens as prefix delimiters

The prefix detection logic in bd import incorrectly identifies issues like 'vc-baseline-test' and 'vc-92cl-gate-test' as having different prefixes ('vc-baseline-' and 'vc-92cl-gate-') instead of recognizing them as having the standard 'vc-' prefix with hyphenated suffixes.

This breaks import with error: 'prefix mismatch detected: database uses vc- but found issues with prefixes: [vc-92cl-gate- (1 issues) vc-baseline- (1 issues)]'

The prefix should be determined by the pattern: prefix is everything up to and including the first hyphen. The suffix can contain hyphens without being treated as part of the prefix.

Example problematic IDs:
- vc-baseline-test (detected as prefix: vc-baseline-)
- vc-92cl-gate-test (detected as prefix: vc-92cl-gate-)
- vc-test (correctly detected as prefix: vc-)

Impact: Users cannot use descriptive multi-part IDs without triggering false prefix mismatch errors.


Refactor sqlite.go into focused modules

Split sqlite.go (2,298 lines) into focused modules: migrations.go, ids.go, issues.go, events.go, dirty.go, db.go. This will improve maintainability and reduce cognitive load.


Code Health & Technical Debt Cleanup

Comprehensive codebase cleanup to remove dead code, refactor monolithic files, deduplicate utilities, and improve maintainability. Based on ultrathink code health analysis conducted 2025-10-27.

Goals:
- Remove ~1,500 LOC of dead/unreachable code
- Split 2 monolithic files (server.go 2,273 LOC, sqlite.go 2,136 LOC) into focused modules
- Deduplicate scattered utility functions (normalizeLabels, BD_DEBUG checks)
- Consolidate test coverage (2,019 LOC of collision tests)
- Improve code navigation and reduce merge conflicts

Impact: Reduces codebase by ~6-8%, improves maintainability, faster CI/CD

Estimated Effort: 11 days across 4 phases


Run final validation and cleanup checks

Final validation pass to ensure all cleanup objectives met and no regressions introduced.

Validation checklist:
1. Dead code verification: `go run golang.org/x/tools/cmd/deadcode@latest -test ./...`
2. Test coverage: `go test -cover ./...`
3. Build verification: `go build ./cmd/bd/`
4. Linting: `golangci-lint run`
5. Integration tests
6. Metrics verification
7. Git clean check

Final metrics to report:
- LOC removed: ~____
- Files deleted: ____
- Files created: ____
- Test coverage: ____%
- Build time: ____ (before/after)
- Test run time: ____ (before/after)

Impact: Confirms all cleanup objectives achieved successfully

Validation completed:
- LOC: 52,372 lines total
- Dead code: 4 functions in import_shared.go (tracked in bd-6fe4622f)
- Build: ✓ Successful
- Test coverage: ~20-82% across packages
- Test failure: TestTwoCloneCollision (timeout issue)
- Linting: errcheck warnings present (defer close, fmt errors)
- Test time: ~20s

Issues found:
1. bd-6fe4622f: Remove unreachable import functions (renameImportedIssuePrefixes, etc)
2. TestTwoCloneCollision: Daemon killall timeout causing test failure
3. Linting: errcheck violations need fixing


Consider central serialization package for JSON handling

Multiple parts of the codebase handle JSON serialization of issues with slightly different approaches. Consider creating a centralized serialization package to ensure consistency.

Current serialization locations:
- `cmd/bd/export.go` - JSONL export (issues to file)
- `cmd/bd/import.go` - JSONL import (file to issues)
- `internal/rpc/protocol.go` - RPC JSON marshaling
- `internal/storage/memory/memory.go` - In-memory marshaling

Potential benefits:
- Single source of truth for JSON format
- Consistent field naming
- Easier to add new fields
- Centralized validation

Note: This is marked **optional** because:
- Current serialization mostly works
- May not provide enough benefit to justify refactor
- Risk of breaking compatibility

Decision point: Evaluate if benefits outweigh refactoring cost

Impact: TBD based on investigation - may defer to future work


Delete skipped tests for "old buggy behavior"

Three test functions are permanently skipped with comments indicating they test behavior that was fixed in GH#120. These tests will never run again and should be deleted.

Test functions to remove:

1. `cmd/bd/import_collision_test.go:228`
   ```go
   t.Skip("Test expects old buggy behavior - needs rewrite for GH#120 fix")
   ```

2. `cmd/bd/import_collision_test.go:505`
   ```go
   t.Skip("Test expects old buggy behavior - needs rewrite for GH#120 fix")
   ```

3. `internal/storage/sqlite/collision_test.go:919`
   ```go
   t.Skip("Test expects old buggy behavior - needs rewrite for GH#120 fix")
   ```

Impact: Removes ~150 LOC of permanently skipped tests


Update documentation after code health cleanup

Update all documentation to reflect code structure changes after cleanup phases complete.

Documentation to update:
1. **AGENTS.md** - Update file structure references
2. **CONTRIBUTING.md** (if exists) - Update build/test instructions
3. **Code comments** - Update any outdated references
4. **Package documentation** - Update godoc for reorganized packages

New documentation to add:
1. **internal/util/README.md** - Document shared utilities
2. **internal/debug/README.md** - Document debug logging
3. **internal/rpc/README.md** - Document new file organization
4. **internal/storage/sqlite/migrations/README.md** - Migration system docs

Impact: Keeps documentation in sync with code


Audit and consolidate collision test coverage

The codebase has 2,019 LOC of collision detection tests across 3 files. Run coverage analysis to identify redundant test cases and consolidate.

Test files:
- `cmd/bd/import_collision_test.go` - 974 LOC
- `cmd/bd/autoimport_collision_test.go` - 750 LOC
- `cmd/bd/import_collision_regression_test.go` - 295 LOC

Total: 2,019 LOC of collision tests

Analysis steps:
1. Run coverage analysis
2. Identify redundant tests
3. Document findings

Consolidation strategy:
- Keep regression tests for critical bugs
- Merge overlapping table-driven tests
- Remove redundant edge case tests covered elsewhere
- Ensure all collision scenarios still tested

Expected outcome: Reduce to ~1,200 LOC (save ~800 lines) while maintaining coverage

Impact: Faster test runs, easier maintenance, clearer test intent


Centralize BD_DEBUG logging into debug package

The codebase has 43 scattered instances of `if os.Getenv("BD_DEBUG") != ""` debug checks across 6 files. Centralize into a debug logging package.

Current locations:
- `cmd/bd/main.go` - 15 checks
- `cmd/bd/autoflush.go` - 6 checks
- `cmd/bd/nodb.go` - 4 checks
- `internal/rpc/server.go` - 2 checks
- `internal/rpc/client.go` - 5 checks
- `cmd/bd/daemon_autostart.go` - 11 checks

Target structure:
```
internal/debug/
└── debug.go
```

Benefits:
- Centralized debug logging
- Easier to add structured logging later
- Testable (can mock debug output)
- Consistent debug message format

Impact: Removes 43 scattered checks, improves code clarity


Extract normalizeLabels to shared utility package

The `normalizeLabels` function appears in multiple locations with identical implementation. Extract to a shared utility package.

Current locations:
- `internal/rpc/server.go:37` (53 lines) - full implementation
- `cmd/bd/list.go:50-52` - uses the server version (needs to use new shared version)

Function purpose:
- Trims whitespace from labels
- Removes empty strings
- Deduplicates labels
- Preserves order

Target structure:
```
internal/util/
├── strings.go         # String utilities
    └── NormalizeLabels([]string) []string
```

Impact: DRY principle, single source of truth, easier to test


Extract SQLite migrations into separate files

The file `internal/storage/sqlite/sqlite.go` is 2,136 lines and contains 11 sequential migrations alongside core storage logic. Extract migrations into a versioned system.

Current issues:
- 11 migration functions mixed with core logic
- Hard to see migration history
- Sequential migrations slow database open
- No clear migration versioning

Migration functions to extract:
- `migrateDirtyIssuesTable()`
- `migrateIssueCountersTable()`
- `migrateExternalRefColumn()`
- `migrateCompositeIndexes()`
- `migrateClosedAtConstraint()`
- `migrateCompactionColumns()`
- `migrateSnapshotsTable()`
- `migrateCompactionConfig()`
- `migrateCompactedAtCommitColumn()`
- `migrateExportHashesTable()`
- Plus 1 more (11 total)

Target structure:
```
internal/storage/sqlite/
├── sqlite.go          # Core storage (~800 lines)
├── schema.go          # Table definitions (~200 lines)
├── migrations.go      # Migration orchestration (~200 lines)
└── migrations/        # Individual migrations
    ├── 001_initial_schema.go
    ├── 002_dirty_issues.go
    ├── 003_issue_counters.go
    [... through 011_export_hashes.go]
```

Benefits:
- Clear migration history
- Each migration self-contained
- Easier to review migration changes in PRs
- Future migrations easier to add


Remove unreachable utility functions

Several small utility functions are unreachable:

Files to clean:
1. `internal/storage/sqlite/hash.go` - `computeIssueContentHash` (line 17)
   - Check if entire file can be deleted if only contains this function

2. `internal/config/config.go` - `FileUsed` (line 151)
   - Delete unused config helper

3. `cmd/bd/git_sync_test.go` - `verifyIssueOpen` (line 300)
   - Delete dead test helper

4. `internal/compact/haiku.go` - `HaikuClient.SummarizeTier2` (line 81)
   - Tier 2 summarization not implemented
   - Options: implement feature OR delete method

Impact: Removes 50-100 LOC depending on decisions


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


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

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


Gate check should auto-discover non-numeric await_id

When `bd gate check` encounters a gh:run gate with a non-numeric await_id (workflow name hint like 'release.yml'), it currently returns:

```
awaiting discovery (workflow hint: release.yml) - run 'bd gate discover'
```

This requires manual intervention. The fix committed in 8528fcaa added the message, but didn't add auto-discovery.

**Proposal:** `checkGHRun` should automatically call the discovery logic when it encounters a workflow name hint, rather than requiring a separate command.

**Why this matters:**
- The beads-release formula creates gates with `id = 'release.yml'`
- Agents running the formula shouldn't need to know about the discover command
- The chicken-and-egg problem: you can't release the version that fixes the release process

**Implementation:**
1. In `checkGHRun`, when `!isNumericID(gate.AwaitID)`:
   - Query recent GitHub runs
   - Match using `workflowNameMatches`
   - Auto-update the gate's await_id
   - Then proceed with the normal check

Implemented auto-discovery in gate check - uses gh run list --workflow=X and takes most recent run (ZFC-compliant)


Test parent task


Refactor sqlite.go (2298 lines)

Break down internal/storage/sqlite/sqlite.go into smaller, more focused modules. The file is currently 2298 lines and should be split into logical components.


Merge: bd-au0.5

branch: polecat/Searcher
target: main
source_issue: bd-au0.5
rig: beads


Wrap git operations in GitClient interface

Create internal/daemonrunner/git.go with GitClient interface (HasUpstream, HasChanges, Commit, Push, Pull). Default implementation using os/exec. Use in Syncer and Run loop for testability.


Document bd edit command and verify MCP exclusion

Follow-up from PR #152:
1. Add "bd edit" to AGENTS.md with "Humans only" note
2. Verify MCP server doesn't expose bd edit command
3. Consider adding test for command registration


npm package for bd with native binaries

Create an npm package that wraps native bd binaries for easy installation in Claude Code for Web and other Node.js environments.

## Problem
Claude Code for Web sandboxes are full Linux VMs with npm support, but cannot easily download binaries from GitHub releases due to network restrictions or tooling limitations.

## Solution
Publish bd as an npm package that:
- Downloads platform-specific native binaries during postinstall
- Provides a CLI wrapper that invokes the native binary
- Works seamlessly in Claude Code for Web SessionStart hooks
- Maintains full feature parity (uses native SQLite)

## Benefits vs WASM
- ✅ Full SQLite support (no custom VFS needed)
- ✅ All features work identically to native bd
- ✅ Better performance (native vs WASM overhead)
- ✅ ~4 hours effort vs ~2 days for WASM
- ✅ Minimal maintenance burden

## Success Criteria
- npm install @beads/bd works in Claude Code for Web
- All bd commands function identically to native binary
- SessionStart hook documented for auto-installation
- Package published to npm registry


Polecats should spawn with auto-accept mode enabled

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

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

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

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

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


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

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

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

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

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

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


Remove Gas Town types from IssueType enum

Remove agent, role, rig, convoy, slot from built-in IssueType constants.

## Work
- Remove TypeAgent, TypeRole, TypeRig, TypeConvoy, TypeSlot constants
- Update IsValid() switch statement to remove these types
- Update RequiredSections() to remove agent/role/convoy references in default case comment

## Files
- internal/types/types.go

Removed Gas Town types (agent, role, rig, convoy, slot) from IssueType enum and updated all tests


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.


Review PR #908: feat(doctor) external hook managers

dispatched_by: beads/crew/emma

Review and merge PR #908 from roelofb.

PR: https://github.com/anthropics/beads/pull/908
Changes: +1364/-5, 4 files (doctor/fix/hooks.go, hooks_test.go, git.go, CHANGELOG.md)

Feature: detect external hook managers and check bd integration

Review checklist:
- [ ] Code quality and style
- [ ] Tests pass
- [ ] Feature works as intended
- [ ] Approve or request changes


deletions.jsonl recreated locally after tombstone migration

After running bd migrate-tombstones and removing deletions.jsonl from git, the file gets recreated locally on subsequent bd commands.

**Steps to reproduce:**
1. Run bd migrate-tombstones (converts to inline tombstones)
2. Remove deletions.jsonl from git and filesystem
3. Run bd sync or bd stats
4. deletions.jsonl reappears

**Expected behavior:**
After migration, deletions.jsonl should not be recreated. All tombstone data should come from inline tombstones in issues.jsonl.

**Workaround:** Add deletions.jsonl to .gitignore to prevent re-tracking. File still gets created but won't pollute the repo.


bd mol ready --gated: hardcoded query limits

Hardcoded Limit:100 for closed gates and Limit:500 for ready work (mol_ready_gated.go:122,134). Could miss molecules in large deployments. Consider making configurable or at least documenting the limitation.


Update local installation

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


Session ended: gt-beads-crew-emma


bd sync fails with orphaned issues and duplicate ID conflict

After fixing the deleted_at TEXT column scanning bug (commit 18b1eb2), bd sync still fails with two issues:

1. Orphan Detection Warning: 12 orphaned child issues whose parents no longer exist (bd-cb64c226.* and bd-cbed9619.*)

2. Import Failure: UNIQUE constraint failed for bd-360 - this tombstone exists in both DB and JSONL

Error: "Import failed: error creating depth-0 issues: bulk insert issues: failed to insert issue bd-360: sqlite3: constraint failed: UNIQUE constraint failed: issues.id"

Fix options:
- Delete orphaned child issues with bd delete
- Resolve bd-360 duplicate (in deletions.jsonl vs tombstone in DB)
- Reset sync branch: git branch -f beads-sync main && git push --force-with-lease origin beads-sync

Fixed tombstone constraint violation bug. When deleting closed issues, the CHECK constraint (status = 'closed') = (closed_at IS NOT NULL) was violated because CreateTombstone didn't clear closed_at. Fix: set closed_at = NULL in tombstone creation SQL.

The sync data corruption (orphaned issues in beads-sync branch) requires manual cleanup: reset sync branch with 'git branch -f beads-sync main && git push --force-with-lease origin beads-sync'


Refactor: Use types.MaxHierarchyDepth constant for fallback default

The GH#995 fix added fallback defaults in two places:

- internal/storage/sqlite/hash_ids.go:58: `maxDepth = 3 // fallback default`
- internal/storage/memory/memory.go:1606: `maxDepth = 3 // fallback default`

These duplicate the value from types.MaxHierarchyDepth (internal/types/id_generator.go:92).

Should reference the constant instead of hardcoding 3 to maintain single source of truth.

Low priority - cosmetic improvement.


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

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


Update bash-agent example with Agent Mail integration

Add Agent Mail integration to examples/bash-agent/agent.sh using curl for HTTP calls.

Acceptance Criteria:
- Health check function using curl
- Reserve issue before claiming
- Send notifications on status change
- Release on completion
- Graceful degradation if curl fails
- No bash errors when Agent Mail unavailable

File: examples/bash-agent/agent.sh


Review PR #960: Fix filepath.Rel sync error

dispatched_by: beads/crew/emma

Review and verify PR #960 (steveyegge/beads)

PR: https://github.com/steveyegge/beads/pull/960
Author: peterkc
Fixes: #959

Fixes bd sync failing with filepath.Rel error when dbPath is relative.
- gh pr view 960 --repo steveyegge/beads
- gh pr diff 960 --repo steveyegge/beads
- If good: gh pr merge 960 --repo steveyegge/beads --squash

PR #960 reviewed and merged. Fix ensures dbPath is always absolute via CanonicalizePath, preventing filepath.Rel errors in sync-branch mode.


Merge: bd-kzda

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


Remove all deletions.jsonl code except migration

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

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

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


Session ended: gt-gastown-crew-george


Test issue


Fix daemon/direct mode inconsistency in relate and duplicate commands

The relate.go and duplicate.go commands have inconsistent daemon/direct mode handling:

When daemonClient is connected, they resolve IDs via RPC but then perform updates directly via store.UpdateIssue(), bypassing the daemon.

Affected locations:
- relate.go:125-139 (runRelate update)
- relate.go:235-246 (runUnrelate update)
- duplicate.go:120 (runDuplicate update)
- duplicate.go:207 (runSupersede update)

Should either use RPC for updates when daemon is running, or document why direct access is intentional.


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

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

Current coverage: 23.9%
Target coverage: 50%


Phase 1.2 follow-up: Clarify format storage

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

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

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


Update python-agent example with Agent Mail integration

Modify examples/python-agent/agent.py to use Agent Mail adapter at 4 integration points.

Acceptance Criteria:
- Import and initialize adapter
- Check inbox before find_ready_work()
- Reserve issue before claim_task()
- Notify on status changes
- Release reservation on complete_task()
- Works identically when Agent Mail disabled
- No changes required to core Beads CLI

File: examples/python-agent/agent.py


Merge: opal-1767106266450

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

Branch no longer exists on remote - stale MR


bd sync --import-only doesn't update DB mtime causing bd doctor false warning


bd close hooks: context check and notifications

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

## Scope (MVP)

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

## Implementation

### 1. Config Schema

Add to internal/configfile/config.go:

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

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

Add `Hooks HooksConfig` field to Config struct.

### 2. Hook Execution

Create internal/hooks/close_hooks.go:

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

### 3. Integration Point

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

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

### 4. Example Config

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

## Environment Variables

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

## Testing

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

## Files to Create/Modify

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

## Out of Scope (Future)

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


Extract duplicated validation logic from CLI commands

~150 lines of identical validation logic duplicated between cmd_create.go and cmd_update.go

Duplication found:
- validateBeadFields(): 2 identical copies (50+ lines each)
- parseTimeWithDefault(): 2 identical copies (30 lines each)
- Flag definitions: 15+ duplicate registrations

Solution: Extract to shared packages:
- internal/validation/bead.go - Centralized validation
- internal/utils/time.go - Consolidate time parsing (already exists)
- cmd/bd/flags.go - Shared flag registration

Impact: Changes require touching 2+ files; high risk of inconsistency; steep learning curve

Effort: 4-6 hours


Session ended: gt-beads-crew-emma


Code smell: Mixed daemon/direct mode logic throughout commands

Most commands duplicate logic for both daemon and direct modes:

```go
if daemonClient != nil {
    // Daemon implementation
} else {
    // Direct implementation
}
```

This pattern appears multiple times within the same function in show.go, list.go, and other commands.

**Problem:**
- Doubles the code paths to test
- Changes to data retrieval require updates in two places
- Difficult to reason about behavior

**Acceptance Criteria:**
- [ ] Create StorageAccessor interface for both modes
- [ ] Use strategy pattern for daemon vs direct operations
- [ ] Centralize dispatching logic
- [ ] Reduce code duplication
- [ ] Tests pass


Merge: obsidian-mjwn3dvc

branch: polecat/obsidian-mjwn3dvc
target: main
source_issue: obsidian-mjwn3dvc
rig: beads


Agent beads: structured labels for filtering

## Agent Beads Need Queryable Labels

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

## Current State

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

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

## Required

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

## Where to Fix

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

## Queries Enabled

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

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

Implemented: role_type/rig labels for agent beads, auto-labeling on create/update, backfill command


Investigate TestRoutingIntegration failure

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


Debug test 2


Add functional tests for FlushManager correctness verification


Import fails on same-content-different-ID instead of treating as update

## Problem

The importer still has rename detection (importer.go:482-500) that triggers when same content hash has different IDs. With hash IDs, this shouldn't happen, but when it does (test data, bugs, legacy data), the import fails:

```
failed to handle rename bd-ce75 -> bd-5a90: rename collision handling removed - should not occur with hash IDs
```

## Current Behavior

1. Importer finds same content hash with different IDs
2. Calls handleRename() (line 490)
3. handleRename() errors out (line 294): "rename collision handling removed"
4. Import fails

## Expected Behavior

Same content hash + different IDs should be treated as an **update**, not a rename:
- Keep existing ID (already in database)
- Update fields if incoming has newer timestamp
- Discard incoming ID (it's wrong - hash should have generated same ID)

## Impact

- Import fails on legitimate edge cases (test data, data corruption)
- Cryptic error message
- Blocks sync operations

## Fix

In handleRename() or import loop, instead of erroring:
```go
// Same content, different ID - treat as update
if incoming.UpdatedAt.After(existing.UpdatedAt) {
    existing.Status = incoming.Status
    // ... copy other fields
    s.UpdateIssue(ctx, existing)
}
result.Updated++
```

## Files
- internal/importer/importer.go:271-294 (handleRename)
- internal/importer/importer.go:482-500 (rename detection)

## Repro
Import JSONL with bd-ce75 and bd-5a90 (both "Test parent issue" but different content hashes).


Improve Beads test coverage from 46% to 80%


Merge: bd-ykd9

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


Update documentation for merge driver auto-config

Update documentation to reflect the new merge driver auto-configuration during `bd init`.

**Files to update:**
- README.md - Mention merge driver setup in initialization section
- AGENTS.md - Update onboarding section about merge driver
- Possibly QUICKSTART.md

**Content:**
- Explain what the merge driver does
- Show --skip-merge-driver flag usage
- Manual installation steps for post-init setup


Add --yes flag to bd doctor --fix for non-interactive mode

## Feature Request

Add a `--yes` or `-y` flag to `bd doctor --fix` that automatically confirms all prompts, enabling non-interactive usage in scripts and CI/CD pipelines.

## Current Behavior
`bd doctor --fix` prompts for confirmation before applying fixes, which blocks automated workflows.

## Desired Behavior
`bd doctor --fix --yes` should apply all fixes without prompting.

## Use Cases
- CI/CD pipelines that need to auto-fix issues
- Scripts that automate repository setup
- Pre-commit hooks that want to silently fix issues


Session ended: gt-beads-crew-dave


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

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

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

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

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

Fixed: flag default changed to -1 sentinel, 0 now means expire all


Test Thread

Initial message for threading test


Fix agent ID parser for hyphenated rig names (GH#868)

Agent ID format <prefix>-<rig>-<role>-<name> fails when rig contains hyphens. Fix: parse from ends - prefix first, name last, role second-to-last (must be crew/polecat/witness/etc), rig is everything in between.


Use in-memory filesystem for test git operations

Use tmpfs/ramdisk for git operations in tests to reduce I/O overhead.

Options:
1. Mount /tmp as tmpfs in CI (GitHub Actions supports this)
2. Use Go's testing.TB.TempDir() which may already use tmpfs on some systems
3. Explicitly create ramdisk for tests on macOS

Expected savings: 20-30% reduction in git operation time


Implement bd lint command

New command to check issues for missing sections. Supports: bd lint (all open), bd lint <id> (single), bd lint --fix (interactive).


Session ended: gt-beads-crew-giles


Run bump-version.sh 0.32.1

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


Tests take too long - unacceptable for project size

## Problem

Running `go test ./internal/importer/... -v` takes an unacceptably long time (minutes). For a project this size, tests should complete in seconds.

## Impact

- Slows down development iteration
- AI agents waste time waiting for tests
- Blocks rapid bug fixes and validation
- Poor developer experience

## Investigation Needed

- Profile which tests are slow
- Check for unnecessary sleeps, timeouts, or integration tests
- Look for tests that could be parallelized
- Consider splitting unit vs integration tests

## Goal

Test suite for a single package should complete in <5 seconds, ideally <2 seconds.

## Optimizations Applied

1. **Added t.Parallel() to CLI tests** (13 tests) - allows concurrent execution
2. **Removed unnecessary 200ms sleep** in daemon_autoimport_test.go - Execute() forces auto-import synchronously
3. **Reduced filesystem settle wait** from 100ms → 50ms on non-Windows platforms
4. **Optimized debouncer test sleeps** (9 reductions):
   - Before debounce waits: 30ms → 20ms, 20ms → 10ms
   - After debounce waits: 40ms → 35ms, 30ms → 35ms, etc.
   - Thread safety test: 100ms → 70ms
   - Sequential cycles: 50ms → 40ms (3x)
   - Cancel tests: 70-80ms → 60ms

## Results

### cmd/bd package (main improvement target):
- **Before**: 5+ minutes (timeout)
- **After**: ~18-20 seconds
- **Speedup**: ~15-18x faster

### internal/importer package:
- **After**: <1 second (0.9s)

### Full test suite (with `-short` flag):
- Most packages complete in <2s
- Total runtime constrained by sequential integration tests

## Known Issues

- TestConcurrentExternalRefImports hangs due to :memory: connection pool issue (bd-b121)
- Some sync_branch tests may need sequential execution (git worktree conflicts)


Session ended: gt-beads-crew-emma


Implement health checks in daemon event loop

Add health checks to checkDaemonHealth() function in daemon_event_loop.go:170:
- Database integrity check
- Disk space check
- Memory usage check

Currently it's just a no-op placeholder.


Enrich MutationEvent with title and assignee

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


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


Add error handling test for cmd.Help() in search command

Test coverage gap identified by automated analysis (vc-217).

**Original Issue:** [deleted:bd-da96-baseline-lint]

In cmd/bd/search.go:39, the return value of cmd.Help() is not checked, flagged by errcheck linter.

Add test to verify:
- Error handling when cmd.Help() fails (e.g., output redirection fails)
- Proper error propagation to caller
- Command still exits gracefully on help error

This ensures the search command handles help errors properly and doesn't silently ignore failures.

_This issue was automatically created by AI test coverage analysis._


bd sync should not stage gitignored snapshot files

## Problem

`gitCommitBeadsDir` in `cmd/bd/sync.go` runs `git add .beads/` which stages all files in the directory, including snapshot files that are listed in `.beads/.gitignore`.

If a snapshot file (e.g., `beads.left.meta.json`) was ever committed before being added to `.gitignore`, git continues to track it. This causes merge conflicts when multiple polecats run `bd sync` concurrently, since each one modifies and commits these temporary files.

## Root Cause

Line ~568 in sync.go:
```go
addCmd := exec.CommandContext(ctx, "git", "add", beadsDir)
```

This stages everything in `.beads/`, but `.gitignore` only prevents *untracked* files from being added - it doesn't affect already-tracked files.

## Suggested Fix

Option A: After `git add .beads/`, run `git reset` on snapshot files:
```go
exec.Command("git", "reset", "HEAD", ".beads/beads.*.jsonl", ".beads/*.meta.json")
```

Option B: Stage only specific files instead of the whole directory:
```go
exec.Command("git", "add", ".beads/issues.jsonl", ".beads/deletions.jsonl", ".beads/metadata.json")
```

Option C: Detect and untrack snapshot files if they're tracked:
```go
// Check if file is tracked: git ls-files --error-unmatch <file>
// If tracked, run: git rm --cached <file>
```

Option B is probably cleanest - explicitly add only the files that should be committed.


BUG: gt rig shutdown doesn't kill tmux sessions

## Problem

`gt rig shutdown wyvern` updates runtime JSON to `state: stopped` but does NOT kill the tmux sessions (`gt-wyvern-witness`, `gt-wyvern-refinery`).

Since `gt rig status` checks for tmux sessions (correctly), it shows them as running.

## Evidence

After `gt rig shutdown wyvern`:
- Runtime JSON: `state: stopped` ✓
- tmux sessions: still exist ✗

## Impact

- Rig appears running after shutdown
- tmux status shows green icon for 'stopped' rigs
- User confusion

## Root Cause

`gt rig shutdown` in internal/cmd/rig.go updates runtime state but doesn't call `tmux kill-session` for witness/refinery.

## Fix

In shutdown logic, after setting state to stopped, kill:
- `gt-{rig}-witness` session
- `gt-{rig}-refinery` session

Fixed: Added tmux kill-session to witness.Manager.Stop() matching refinery's implementation


gt done --phase-complete: gastown changes for phase handoff

The gt done --phase-complete flag and Witness handling changes need to be
applied to the gastown project. These changes are already written and tested
in the mayor/rig but need to be properly committed through a gastown worktree.

Files changed:
- internal/cmd/done.go: Add --phase-complete and --gate flags
- internal/beads/beads.go: Add AddGateWaiter method
- internal/witness/protocol.go: Add Gate field to PolecatDonePayload
- internal/witness/handlers.go: Handle PHASE_COMPLETE exit type

This is a dependency for bd-quw1 (polecat phase handoff).

The beads changes (bd gate add-waiter, bd gate show) are committed in bd-quw1.


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

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

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

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

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

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


Fix daemon exiting after 5s on macOS due to PID 1 parent monitoring

GitHub issue #278 reports that the daemon exits after <=5 seconds on macOS because it incorrectly treats PID 1 (launchd) as a dead parent.

When the daemon detaches on macOS, it gets reparented to PID 1 (launchd), which is the init process. The checkParentProcessAlive function was incorrectly treating PID 1 as a sign that the parent died.

Fixed by changing the logic to treat PID 1 as a valid parent for detached daemons.


Refactor sync_test to use direct import logic

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

File: cmd/bd/sync_test.go:444


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


Merge: bd-g4b4

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


Test child issue


Refinery Patrol

Merge queue processor patrol loop with verification gates.


Swarm: Test epic for duplicate swarm

Swarm molecule orchestrating epic bd-4d9d.

Epic: bd-4d9d
Coordinator:

Stale/spurious - test artifacts, merged PRs, or auto-close candidates


bd cleanup should also prune expired tombstones

## Problem

`bd cleanup` deletes closed issues (converting them to tombstones) but does NOT prune expired tombstones. Users expect 'cleanup' to do comprehensive cleanup.

## Current Behavior

1. `bd cleanup --force` → converts closed issues to tombstones
2. Expired tombstones (>30 days) remain in issues.jsonl
3. User must separately run `bd compact` to prune tombstones
4. `bd doctor` warns about expired tombstones: 'Run bd compact to prune'

## Expected Behavior

`bd cleanup` should also prune expired tombstones from issues.jsonl.

## Impact

With v0.30.0 making tombstones the default migration path, this UX gap becomes more visible. Users cleaning up their database shouldn't need to know about a separate `bd compact` command.

## Proposed Solution

Call `pruneExpiredTombstones()` at the end of the cleanup command (same function used by compact). Report results:

```
✓ Deleted 15 closed issue(s)
✓ Pruned 3 expired tombstone(s) (older than 30 days)
```

## Files to Modify

- `cmd/bd/cleanup.go` - Add call to pruneExpiredTombstones after deleteBatch
- May need to move pruneExpiredTombstones to shared location if not already accessible

## Acceptance Criteria

- [ ] `bd cleanup --force` prunes expired tombstones after deleting closed issues
- [ ] `bd cleanup --dry-run` shows what tombstones would be pruned
- [ ] JSON output includes tombstone prune results


Protos should have distinct prefix (e.g., proto-)

Currently protos use the same bd- prefix as regular issues, making them visually indistinguishable. When you see bd-7bs4 it looks like work to squash, not a reusable template.

**Problem:**
- Protos look like regular issues
- No visual signal that something is a template vs active work
- Easy to accidentally close/modify protos thinking they're issues

**Proposed Solution:**
- Protos should have their own prefix (e.g., `proto-`, `mol-`, or `tpl-`)
- Could be a separate database/storage for the proto library
- Or a convention where proto IDs are generated differently

**Alternatives:**
1. Separate proto database with `proto-` prefix
2. Special ID format for protos (e.g., `bd:proto:release`)
3. Visual indicator in bd list/show output (already have labels, but not enough)

**Current workaround:** Label with `template` and `molecule`, but IDs still look like regular issues.


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


Add tests for validation functions

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


Wait for CI to pass

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


Restart running daemons

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


Fix beads-9yc: pinned column missing from schema. gt mail...

Fix beads-9yc: pinned column missing from schema. gt mail send fails because some beads DBs lack the pinned column. Add migration to ensure it exists.


Implement auto-merge functionality in duplicates command

The duplicates.go file has a TODO at line 95 to implement the performMerge function for automatic duplicate merging. Currently it just prints a warning message. This would automate the merge process instead of just suggesting commands.


Session ended: gt-beads-crew-emma


Test and document molecular chemistry commands

## Context

Implemented the molecular chemistry UX commands per the design docs:
- gastown/mayor/rig/docs/molecular-chemistry.md
- gastown/mayor/rig/docs/chemistry-design-changes.md

Commit: cadf798b

## New Commands to Test

| Command | Purpose |
|---------|---------|
| `bd pour <proto>` | Instantiate proto as persistent mol |
| `bd wisp create <proto>` | Instantiate proto as ephemeral wisp |
| `bd hook [--agent]` | Inspect what's on an agent's hook |

## Enhanced Commands to Test

| Command | Changes |
|---------|---------|
| `bd mol spawn --pour` | New flag, `--persistent` deprecated |
| `bd mol bond --pour` | Force liquid phase on wisp target |
| `bd pin --for <agent> --start` | Chemistry workflow support |

## Test Scenarios

1. **bd pour**: Create persistent mol from a proto
   - Verify creates in .beads/ (not .beads-wisp/)
   - Verify variable substitution works
   - Verify --dry-run works

2. **bd wisp create**: Create ephemeral wisp from proto
   - Verify creates in .beads-wisp/
   - Verify bd wisp list shows it
   - Verify bd mol squash works
   - Verify bd mol burn works

3. **bd hook**: Inspect pinned work
   - Pin something, verify bd hook shows it
   - Test --agent flag
   - Test --json output

4. **bd pin --for**: Assign work to agent
   - Verify sets pinned=true
   - Verify sets assignee
   - Verify --start sets status=in_progress

5. **bd mol bond --pour**: Force liquid on wisp target
   - Bond a proto to a wisp with --pour
   - Verify spawned issues are in .beads/

## Documentation

- Update CLAUDE.md with new commands
- Add examples to --help output (already done)
- Consider adding to docs/CLI_REFERENCE.md

## Code Review

- Check for edge cases
- Verify error messages are helpful
- Ensure --json output is consistent


Add 'convoy' issue type with reactive completion

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

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

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

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


GH#518: Document bd setup command

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


Add sync resilience guardrails for forced pushes and prefix mismatches

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


Pre-sync integrity check (bd sync --check)

Add bd sync --check flag that detects problematic states before attempting sync: forced pushes on sync branch (via git reflog), prefix mismatches in JSONL, orphaned children with missing parents. Output combined diagnostic without modifying state.


Improve sync error messages

When bd sync fails, immediately surface the actual root cause (prefix mismatch, orphaned children, forced push detected, etc.) instead of requiring multiple retries to discover it. Make errors actionable with specific recovery steps.


Auto-recovery mode (bd sync --auto-recover)

Add bd sync --auto-recover flag that: detects problematic sync state, backs up .beads/issues.db with timestamp, rebuilds DB from JSONL atomically, verifies consistency, reports what was fixed. Provides safety valve when sync integrity fails.


Sync branch integrity guards

Track sync branch parent commit. If sync branch was force-pushed, warn user and require confirmation before proceeding. Add option to reset to remote if user accepts rebase. Prevents silent corruption from forced pushes.


Merge: bd-m8ro

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


bd slot set doesn't support cross-beads prefix routing

## Problem

`bd slot set` fails when setting a slot to a bead that lives in a different beads database (cross-db routing).

## Evidence

When spawning a polecat:
```
Warning: could not set role slot: bd slot set gt-gastown-polecat-chumbucket role hq-polecat-role:
Error: failed to resolve bead hq-polecat-role: no issue found matching "hq-polecat-role"
```

The agent bead `gt-gastown-polecat-chumbucket` is in rig beads (`gastown/mayor/rig/.beads/`).
The role bead `hq-polecat-role` is in town beads (`~/gt/.beads/`).

`bd show hq-polecat-role` works fine (uses prefix routing via routes.jsonl).
`bd slot set ... hq-polecat-role` fails (does not use routing).

## Root Cause

`bd slot set` resolves the target bead in the current database only, ignoring the `hq-` prefix that should route to town beads.

## Expected Behavior

`bd slot set <agent> <slot> <bead>` should:
1. Check if `<bead>` has a routable prefix (e.g., `hq-`, `gt-`, `bd-`)
2. If so, resolve it via routes.jsonl like `bd show` does
3. Store the bead reference (cross-db references are valid for slots)

## Workaround

Currently the warning is non-fatal - the role slot just is not set. But this breaks role-based queries.

## Files

- `cmd/slot.go` - slot set command implementation
- Need to add routing logic similar to `cmd/show.go`

Added prefix routing to bd slot set command. Now uses needsRouting() and resolveAndGetIssueWithRouting() to handle cross-beads references like hq-* from rig beads.


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

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


gt prime: add tests for --dry-run, --state, --explain flags

The new observability flags have no test coverage:

1. --dry-run: Test that side effects are skipped (marker not removed, bd prime not called)
2. --state: Test state detection for each state (normal, post-handoff, crash-recovery, autonomous)
3. --explain: Test that [EXPLAIN] tags are added when flag is set

Key functions to test:
- detectSessionState()
- outputState()
- checkHandoffMarkerDryRun()
- explain()

Location: gastown/mayor/rig/internal/cmd/prime.go, needs prime_test.go additions


Add 'town report' command for aggregated swarm status

## Problem
Getting a full swarm status requires running 6+ commands:
- `town list <rig>` for each rig
- `town mail inbox` as Boss
- `bd list --status=open/in_progress` per rig

This is slow and error-prone for both humans and agents.

## Proposed Solution
Add `town report [RIG]` command that aggregates:
- All rigs with polecat states (running/stopped, awake/asleep)
- Boss inbox summary (unread count, recent senders)
- Aggregate issue counts per rig (open/in_progress/blocked)

Example output:
```
=== beads ===
Polecats: 5 (5 running, 0 stopped)
Issues: 20 open, 0 in_progress, 0 blocked

=== gastown ===
Polecats: 6 (4 running, 2 stopped)
Issues: 0 open, 0 in_progress, 0 blocked

=== Boss Mail ===
Unread: 10 | Total: 22
Recent: rictus (21:19), scrotus (21:14), immortanjoe (21:14)
```

## Acceptance Criteria
- [ ] `town report` shows all rigs
- [ ] `town report <rig>` shows single rig detail
- [ ] Output is concise and scannable
- [ ] Completes in <2 seconds


Distinct prefixes for protos, molecules, wisps

Template/workflow entities should have visually distinct prefixes from regular issues.

**Problem:**
- Protos (bd-7bs4) look like regular issues - invites squashing
- Molecules (poured instances) also use bd- prefix
- Wisps are in separate DB but still use bd- when referenced

**Proposed Prefixes:**
- `proto-` for templates (e.g., proto-release, proto-review)
- `mol-` for active molecules (poured from protos)
- `wisp-` for ephemeral wisps (vapor phase)

**Benefits:**
- Instant visual recognition of entity type
- Prevents accidental modification of templates
- Clear lifecycle: proto → mol → wisp → digest

**Implementation options:**
1. Separate databases with different prefixes
2. Issue type determines prefix generation
3. Naming convention enforced by bd pour/wisp commands


gt sling should look up formulas from .beads/formulas/ registry

## Problem

gt sling <formula-name> doesn't find formulas from the registry that bd formula list shows.

## What I tried

1. gt sling beads/crew/emma --formula beads-release --var version=0.42.0
   - Problem: --formula flag doesn't exist

2. gt sling beads-release beads/crew/emma --var version=0.42.0
   - Error: open .../beads-release: no such file or directory
   - Problem: Looks for file in cwd, not formula registry

## What worked

bd mol wisp beads-release --var version=0.42.0 - correctly finds formula from .beads/formulas/

## Expected

gt sling should use the same formula lookup as bd mol wisp - checking .beads/formulas/*.formula.json

## Documentation gap

The help shows examples like 'gt sling mol-release mayor/' suggesting formula slinging works, but it doesn't find registry formulas.


show commit hash in 'bd version' when built from source'


bd cook: needs field not converted to step dependencies

When cooking a formula with `needs` fields on steps, the dependencies are not created between sibling steps.

## Expected

Formula:
```yaml
steps:
  - id: inbox-check
    title: Check inbox
  - id: trigger-spawns
    title: Nudge polecats
    needs: [inbox-check]
```

Should create:
- mol-foo.inbox-check (no deps)
- mol-foo.trigger-spawns → depends on → mol-foo.inbox-check

## Actual

Both steps only depend on the parent proto:
- mol-foo.inbox-check → depends on → mol-foo
- mol-foo.trigger-spawns → depends on → mol-foo

The `needs` field is ignored.

## Impact

This breaks the step execution order. Steps that should wait for predecessors will run in parallel or out of order.

## Reproduction

```bash
bd cook mol-deacon-patrol.formula.yaml
bd show mol-deacon-patrol.trigger-pending-spawns
# Shows: Depends on mol-deacon-patrol (parent only)
# Should show: Depends on mol-deacon-patrol.inbox-check
```


Updated title


Measure notification latency vs git sync

Benchmark end-to-end latency for status updates to propagate between agents using both methods.

Acceptance Criteria:
- Measure git sync latency (commit → push → pull → import)
- Measure Agent Mail latency (send_message → fetch_inbox)
- Document latency distribution (p50, p95, p99)
- Verify <100ms claim for Agent Mail
- Compare against 1-5s baseline for git

Success Metric: Agent Mail latency < 100ms, git sync latency > 1000ms

Latency benchmark completed. Results documented in latency_results.md:
- Git sync: 2000-5000ms (full cycle with network)
- Agent Mail: <100ms (HTTP API round-trip)
- Confirms 20-50x latency reduction claim


Stale daemon.lock files cause 5+ second delays

## Problem
When a bd daemon crashes or is killed without cleanup, it leaves a stale daemon.lock file. Subsequent bd commands try to connect to the dead daemon, causing 5+ second timeouts before falling back to direct mode.

Found during gt status optimization (gt-zdtpw) - one stale wyvern daemon was causing 10+ second delays.

## Evidence
```
# Lock files with stale PIDs:
$ find ~/gt -name "daemon.lock" -exec cat {} \;
{"pid": 11045, ...}  # Not running (from Dec 27)
{"pid": 15986, ...}  # Not running (from Dec 27)
{"pid": 98055, ...}  # Just restarted

# But running daemons have different PIDs:
$ ps aux | grep "bd.*daemon"
11847, 11683, 11624  # None match the lock files!
```

## Proposed fixes

### 1. Validate lock on startup (quick fix)
Before trusting daemon.lock, check if the PID is still running:
```python
def is_daemon_alive(lock_path):
    lock = json.load(open(lock_path))
    try:
        os.kill(lock["pid"], 0)  # Signal 0 = check if alive
        return True
    except OSError:
        return False
```

### 2. Heartbeat mechanism (robust fix)
Daemon writes periodic heartbeat to lock file or separate file.
Clients check heartbeat age before attempting IPC.

### 3. Lock cleanup in daemon startup
When starting a new daemon, always check for and clean stale locks.

### 4. Graceful shutdown handling
Register signal handlers (SIGTERM, SIGINT) to clean up lock on exit.

## Workaround
For now, gastown uses `--no-daemon` for all read operations to avoid this issue.


Session ended: gt-beads-crew-emma


Molecule execution state management

Implement molecule execution state tracking.

## State Location

```
.beads/molecules/<mol-id>.state.yaml
```

## State Schema

```yaml
id: mol-deacon-patrol
formula: mol-deacon-patrol  # Source formula
bonded_at: ISO timestamp
bonded_by: entity who created it

# Execution state
status: running | paused | completed | failed
current_step: step-id
started_at: ISO timestamp
completed_at: ISO timestamp (if done)

# Loop tracking
reset_count: 0
last_reset_at: null

# Per-step state
steps:
  inbox-check:
    status: completed | in_progress | pending
    started_at: ...
    completed_at: ...
  spawn-work:
    status: pending
  self-inspect:
    status: pending

# Variables (from formula instantiation)
variables:
  rig: gastown
  issue_id: gt-xxx
```

## Operations

- CreateState(molID, formula, variables) - Initialize state
- LoadState(molID) - Read current state
- SaveState(molID, state) - Write state
- AdvanceStep(molID) - Mark current complete, find next
- ResetState(molID) - Reset all steps to pending

## Files

- internal/mol/state.go
- internal/mol/types.go


Deletions not propagated across multi-workspace sync

## Problem

When working with multiple beads workspaces (clones) sharing the same git remote, deleted issues keep coming back.

## Reproduction

1. Clone A deletes issue `bd-xyz` via `bd delete bd-xyz --force`
2. Clone A daemon syncs and pushes to GitHub
3. Clone B still has `bd-xyz` in its database
4. Clone B daemon exports and pushes its JSONL
5. Clone A pulls and imports → `bd-xyz` comes back!

## Root Cause

**No deletion tracking mechanism.** The system has no way to distinguish between:
- "Issue doesn't exist in JSONL because it was deleted"
- "Issue doesn't exist in JSONL because the export is stale"

Import treats missing issues as "not in this export" rather than "explicitly deleted."

## Solution Options

1. **Tombstone records** - Keep deleted issues in JSONL with `"status":"deleted"` or `"deleted_at"` field
2. **Deletion log** - Separate `.beads/deletions.jsonl` file tracking all deleted IDs
3. **Three-way merge** - Import compares: DB state, old JSONL, new JSONL
4. **Manual conflict resolution** - Detect resurrection and prompt user

## Related

- Similar to resurrection logic for orphaned children (bd-cc4f)
- beads-merge tool handles this better with 3-way merge


Session ended: gt-gastown-crew-george


Merge: bd-w193

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


gt prime: detectSessionState duplicates checkSlungWork logic

detectSessionState() (lines 1775-1799) duplicates the hooked bead detection logic from checkSlungWork() (lines 1035-1066). This could lead to drift if one is updated without the other.

Options:
1. Extract shared helper function for hooked bead detection
2. Have detectSessionState call into checkSlungWork (would need refactoring)
3. Accept duplication with comment noting the relationship

Location: gastown/mayor/rig/internal/cmd/prime.go


Merge: quartz-mjxey2w1

branch: polecat/quartz-mjxey2w1
target: main
source_issue: quartz-mjxey2w1
rig: beads
agent_bead: bd-beads-polecat-quartz


Fix GH #227: bd edit broken pipe errors

bd edit command gets "broken pipe" errors when using daemon mode because editing can take minutes and the daemon connection times out.

Solution: Force bd edit to always use direct mode (--no-daemon) since it's human-only and interactive.

Fixed by checking cmd.Name() == "edit" in main.go PersistentPreRun and setting noDaemon = true.


Update info.go versionChanges

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


Add tests for sync redirect handling in git status checks

Commit a61cbde6 added redirect detection for .beads/redirect but lacks tests.

When beadsDir is redirected to another repo (crew worktree scenario), git
commands now detect this and use git -C <targetRepoDir>.

Test cases needed:
- gitHasBeadsChanges() with redirected beads dir runs git from target repo
- gitHasUncommittedBeadsChanges() handles redirect correctly
- Redirect detection works with symlinks vs redirect file
- Non-redirected case still works normally

Location: cmd/bd/sync_git.go:93-108 and 472-485
Suggested test file: cmd/bd/sync_git_test.go


Daemon performAutoImport should update jsonl_content_hash after import

The daemon's performAutoImport function was not updating jsonl_content_hash after successful import, unlike the CLI import path. This could cause repeated imports if the file hash and DB hash diverge.

Fix: Added hash update after validatePostImport succeeds, using repoKey for multi-repo support.


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

## Problem

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

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

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

## Proposed Solution

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

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

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

## Implementation

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

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

### 2. Flag precedence

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

### 3. Error handling

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

## Benefits

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

## Related Commands

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

## Examples

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

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

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

## Testing

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


Update info.go versionChanges

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


Convert magic numbers to named constants in FlushManager


Merge: bd-ao0s

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


Refactor bd setup to recipe-based architecture (GH#898)

## Summary

Replace tool-specific setup commands (cursor, claude, etc.) with a generic recipe-based system. New tools become config entries, not code changes.

## Motivation

GH#898 requests Kilo Code integration. Current approach requires new Go code for each tool:
- `cmd/bd/setup/cursor.go`
- `cmd/bd/setup/kilocode.go` (proposed)
- `cmd/bd/setup/windsurf.go` (future?)
- ...

This does not scale. All these integrations do the same thing:
1. Generate markdown content (beads workflow instructions)
2. Write to tool-specific path

The **content** is identical. Only the **destination path** varies.

## Design

### Core Insight

Beads does not need to know anything about Kilo Code, Cursor, or Claude Code beyond where they read their rules files. That is a path mapping—data, not code.

### Recipe Format

```toml
# .beads/recipes.toml (user additions/overrides)
[cursor]
path = ".cursor/rules/bd.md"

[claude]
path = "CLAUDE.md"

[kilocode]
path = ".kilocode/rules/bd.md"

[my-custom-editor]
path = ".myeditor/config/bd.md"
```

### Built-in Defaults

Ship sensible defaults compiled into binary:
- cursor → `.cursor/rules/bd.md`
- claude → `CLAUDE.md`
- windsurf → `.windsurf/rules/bd.md`
- cody → `.cody/rules/bd.md` (verify path)

User file overrides/extends built-in defaults.

### Commands

| Command | Description |
|---------|-------------|
| `bd setup <recipe>` | Write template to recipe path |
| `bd setup --list` | Show available recipes (built-in + user) |
| `bd setup --print` | Dump template to stdout |
| `bd setup -o <path>` | Write to arbitrary path (escape hatch) |
| `bd setup <recipe> --check` | Verify if installed |
| `bd setup <recipe> --remove` | Delete the file |
| `bd setup --add <name> <path>` | Save custom recipe |

### Template

Single markdown template embedded in binary. Contains:
- What beads is
- Key commands (`bd ready`, `bd create`, `bd close`, etc.)
- Workflow guidance
- JSON flags for programmatic use

### Implementation Plan

1. Add `--print` and `-o` flags to existing setup command
2. Create `internal/recipes/` package
   - Built-in recipes (Go map or embed)
   - Load/merge user `.beads/recipes.toml`
3. Refactor `cmd/bd/setup/setup.go` to use recipes
4. Remove tool-specific files (`cursor.go`, etc.)
5. Add `--add` command to save custom recipes
6. Update documentation

### Migration

Existing `bd setup cursor` continues to work—just backed by recipe lookup instead of dedicated code.

## Out of Scope (for now)

- `--global` flag (user-level config paths) — use `-o` escape hatch
- `mode: append` (add to existing file) — wait until requested
- GitHub recipe registry — overkill for path mappings
- TOML migration for config.yaml — separate initiative

## Response to GH#898

Once implemented, respond to PR:

> Thanks for the contribution! We are refactoring `bd setup` to a recipe-based approach where new tools are config entries, not code changes.
>
> Instead of new Go files, please submit a PR adding Kilo Code to the built-in recipes in `internal/recipes/builtin.go` (or equivalent). Happy to help if you have questions!

## References

- GH#898: Kilo Code integration request
- Current setup code: `cmd/bd/setup/`

Implemented recipe-based architecture for bd setup


Extract Gas Town-specific issue types from beads core

dispatched_by: beads/crew/dave

## Problem

Beads has accumulated issue types that are specific to Gas Town's orchestration model:

```go
TypeAgent   IssueType = "agent"   // Agent identity bead
TypeRole    IssueType = "role"    // Agent role definition
TypeRig     IssueType = "rig"     // Rig identity bead (project container)
TypeConvoy  IssueType = "convoy"  // Cross-project tracking
TypeSlot    IssueType = "slot"    // Exclusive access slot (merge-slot gate)
```

This violates separation of concerns. Beads is supposed to be a **generic AI-friendly issue tracker** that Gas Town happens to use - not "Gas Town's issue tracker."

## Impact

- **Credibility**: Beads can't be presented as a standalone tool when it has hardcoded knowledge of a specific orchestration system
- **Coupling**: Changes to Gas Town's identity model require beads releases
- **Confusion**: Users outside Gas Town see types that don't apply to them

## Solution

### Option A: Labels instead of types
Gas Town uses labels like `gt:agent`, `gt:role`, `gt:rig` instead of dedicated types. Beads stays generic.

### Option B: Type extension mechanism
Beads supports custom type registration via config. Gas Town registers its types at init time.

### Option C: Separate identity store
Gas Town maintains agent/role/rig identity outside beads entirely. Beads only tracks work items.

## Types to evaluate

| Type | Keep in beads? | Rationale |
|------|----------------|-----------|
| `gate` | Yes | Generic workflow concept (async waits) |
| `molecule` | Yes | Generic workflow templates |
| `agent` | **No** | Gas Town identity |
| `role` | **No** | Gas Town identity |
| `rig` | **No** | Gas Town identity |
| `convoy` | **No** | Gas Town cross-rig tracking |
| `slot` | Maybe | Could be generic "mutex" concept |

## Files affected

- `internal/types/types.go` - Type definitions
- `cmd/bd/create.go` - Type validation
- `cmd/bd/update.go` - Type validation
- `internal/storage/sqlite/migrations/030_agent_fields.go` - Schema fields
- Possibly others with type switch statements

Core type extraction complete. All Gas Town types (agent, role, rig, convoy, slot) removed from beads core and replaced with label-based identification. Remaining task bd-jybi is a data migration for existing beads, which is a deployment concern.


Witness Patrol

Per-rig worker monitor patrol loop with progressive nudging.


Test actor flag


Batch wisp deletion in deleteSessionCostWisps

## Problem

`deleteSessionCostWisps()` (internal/cmd/costs.go:1188) burns wisps one at a time in a loop. This creates O(n) subprocess calls for deletion.

## Location

internal/cmd/costs.go:1143-1191

## Solution

Check if `bd mol burn` supports multiple IDs. If so, collect IDs and burn in one call.
If not, consider adding batch support to bd.

Implemented batch wisp deletion in bd mol burn and updated deleteSessionCostWisps to use it


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.


Merge: bd-4qfb

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


Benchmark git traffic reduction

Automated benchmark comparing git operations with/without Agent Mail.

Acceptance Criteria:
- Script that processes 50 issues
- Counts git operations (pull, commit, push)
- Generates comparison report
- Verifies ≥70% reduction
- Fails if regression detected

File: tests/benchmarks/git_traffic.py

Output: Without Agent Mail: 450 git operations, With Agent Mail: 135 git operations, Reduction: 70%

Implemented automated benchmark script with following features:
- Processes configurable number of issues (default 50)
- Compares git operations in two modes: git-only vs Agent Mail
- Generates detailed comparison report with statistics
- Exit code reflects pass/fail based on 70% reduction target
- Results: 98.5% reduction (200 ops → 3 ops) for 50 issues

Files created:
- tests/benchmarks/git_traffic.py (main benchmark script)
- tests/benchmarks/README.md (documentation)
- tests/benchmarks/git_traffic_50_issues.md (sample results)

The benchmark vastly exceeds the 70% target, showing 98.5% reduction.


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

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

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

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

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

This should be standard Gas Town rig configuration.


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

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


bd repair: Add --json output flag

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

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

Implemented in repair.go: added orphan detection for comments/events tables and --json output flag


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

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

## Usage

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

## Behavior

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

## Output

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

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

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

## If no next step

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

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

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

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


Merge: amber-1767146829144

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


test hook pin fix


Fix FOREIGN KEY constraint failures in AddComment and ApplyCompaction

The 'CloseIssue', 'UpdateIssueID', and 'RemoveLabel' methods were fixed in PR #348 to prevent foreign key constraint failures when operating on non-existent issues.

However, the Oracle identified two other methods that follow the same problematic pattern:
1. `AddComment` (in `internal/storage/sqlite/events.go`)
2. `ApplyCompaction` (in `internal/storage/sqlite/compact.go`)

These methods attempt to insert an event record after updating the issue, without verifying that the issue update actually affected any rows. This leads to a foreign key constraint failure if the issue does not exist.

We need to:
1. Create reproduction tests for these failure cases
2. Apply the same fix pattern: check `RowsAffected()` after the update, and return a proper "issue not found" error if it is 0, before attempting to insert the event.
3. Standardize the error message format to "issue %s not found" or "issue not found: %s" for consistency.


Phase 2.2: Switch bdt storage to TOON format

Currently bdt stores issues in JSONL format in issues.toon file. Phase 2.2 must implement actual TOON format storage - this is the fundamental goal of the bdtoon project.

## Current State (Phase 2.1)
- issues.toon stores JSONL (intermediate format)
- --toon flag allows output in TOON format for LLM consumption
- Problem: We're not actually using TOON as the fundamental storage format

## Required Work (Phase 2.2)
1. Switch issue file I/O to write TOON format instead of JSONL
   - Update cmd/bdt/storage.go to use EncodeTOON for writing
   - Update cmd/bdt/storage.go to decode TOON (currently decodes JSON)
   - Ensure round-trip: write TOON → read TOON → write TOON is byte-identical

2. Update command implementations
   - cmd/bdt/create.go: Write newly created issues to TOON format
   - cmd/bdt/list.go: Read issues from TOON format
   - cmd/bdt/show.go: Read from TOON format
   - cmd/bdt/import.go: Convert imported JSONL to TOON
   - cmd/bdt/export.go: Export TOON to JSONL (for bd compatibility)

3. Implement TOON parser that handles gotoon's encoder-only limitation
   - Since gotoon doesn't decode TOON, need custom TOON→JSON decoder
   - OR continue storing TOON but decoding via intermediate JSON conversion

4. Git merge driver optimization
   - TOON is line-oriented, better for 3-way merges than binary formats
   - Configure git merge driver for .toon files

5. Comprehensive testing
   - Round-trip tests: Issue → TOON → storage → read → Issue
   - Merge conflict resolution tests with TOON format
   - Large issue set performance tests

## Success Criteria
- issues.toon stores actual TOON format (not JSONL)
- bdt list reads from TOON file
- bdt create writes to TOON file
- Round-trip: create issue → list → show returns identical data
- All 65+ tests still passing
- Performance comparable to JSONL storage


Add --validate flag to bd create

Opt-in strict mode that fails creation if required sections are missing. Uses ValidateTemplate() under the hood.


Closed gate cleanup/pruning mechanism

Closed gates accumulate over time and never get cleaned up. The SearchIssues query for closed gates will grow unbounded. Options: (1) Add --since filter, (2) Automatic pruning of old closed gates, (3) Tombstone old gates after N days.


Deletion propagation via deletions manifest

## Problem

When `bd cleanup -f` or `bd delete` removes issues in one clone, those deletions don't propagate to other clones. The import logic only creates/updates, never deletes. This causes "resurrection" where deleted issues reappear.

## Root Cause

Import sees DB issues not in JSONL and assumes they're "local unpushed work" rather than "intentionally deleted upstream."

## Solution: Deletions Manifest

Add `.beads/deletions.jsonl` - an append-only log of deleted issue IDs with metadata.

### Format
```jsonl
{"id":"bd-xxx","ts":"2025-11-25T10:00:00Z","by":"stevey"}
{"id":"bd-yyy","ts":"2025-11-25T10:05:00Z","by":"claude","reason":"duplicate of bd-zzz"}
```

### Fields
- `id`: Issue ID (required)
- `ts`: ISO 8601 UTC timestamp (required)
- `by`: Actor who deleted (required)
- `reason`: Optional context ("cleanup", "duplicate of X", etc.)

### Import Logic
```
For each DB issue not in JSONL:
  1. Check deletions manifest → if found, delete from DB
  2. Fallback: check git history → if found, delete + backfill manifest
  3. Neither → keep (local unpushed work)
```

### Conflict Resolution
Simultaneous deletions from multiple clones are handled naturally:
- Append-only design means both clones append their deletion records
- On merge, file contains duplicate entries (same ID, different timestamps)
- `LoadDeletions` deduplicates by ID (keeps any/first entry)
- Result: deletion propagates correctly, duplicates are harmless

### Pruning Policy
- Default retention: 7 days (configurable via `deletions.retention_days`)
- Auto-compact during `bd sync` is **opt-in** (disabled by default)
- Hard cap: `deletions.max_entries` (default 50000)
- Git fallback handles pruned entries (self-healing)

### Self-Healing
When git fallback catches a resurrection (pruned entry), it backfills the manifest. One-time git scan cost, then fast again.

### Size Estimates
- ~80 bytes/entry
- 7-day retention with 100 deletions/day = ~56KB
- Git compressed: ~10KB

## Benefits
- ✅ Deletions propagate across clones
- ✅ O(1) lookup (no git scan in normal case)
- ✅ Works in shallow clones
- ✅ Survives history rewrite
- ✅ Audit trail (who deleted what when)
- ✅ Self-healing via git fallback
- ✅ Bounded size via time-based pruning

## References
- Investigation session: 2025-11-25
- Related: bd-2q6d (stale database warnings)


Add bd setup gemini for Gemini CLI integration

## Summary

Add `bd setup gemini` command to install beads hooks for Gemini CLI, similar to existing `bd setup claude`.

Ref: GitHub #845

## Research Findings

### Gemini CLI Has Compatible Hooks

Gemini CLI (https://geminicli.com/docs/hooks/) supports nearly identical hook events:

| Gemini CLI Event | Claude Code Equivalent | Purpose |
|------------------|----------------------|---------|
| **SessionStart** | SessionStart | Initialize resources, load context |
| **SessionEnd** | - | Clean up, save state |
| **PreCompress** | PreCompact | Before context compression |
| **BeforeAgent** | - | Add context before planning |

Configuration format is almost identical to Claude:
```json
{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "bd prime"
      }]
    }]
  }
}
```

### GEMINI.md Support

- Hierarchical loading: ~/.gemini/GEMINI.md → project root → subdirectories
- Concatenated into system prompt automatically
- Gemini CLI also reads AGENTS.md (already supported via `bd setup factory`)

### Antigravity

Google's IDE-based agentic platform (released Nov 2025):
- Uses Gemini 3 with 1M token context
- Has "knowledge base" for saved context
- Less documented - may share Gemini CLI infrastructure
- Needs separate research for dedicated support

## Implementation Plan

### Phase 1: Core Gemini CLI Support

1. **Create `cmd/bd/setup/gemini.go`**
   - Mirror structure from `claude.go`
   - Target: `~/.gemini/settings.json`
   - Install SessionStart and PreCompress hooks
   - Support --project flag for local settings
   - Support --stealth flag

2. **Add doctor check in `cmd/bd/doctor/gemini.go`**
   - Check if hooks installed
   - Verify settings.json structure

3. **Update `cmd/bd/setup.go`**
   - Add gemini subcommand
   - Wire up to setup/gemini.go

### Phase 2: Optional Enhancements

4. **GEMINI.md generation** (optional)
   - Could generate project-level GEMINI.md
   - Or document that AGENTS.md already works

5. **Antigravity support** (separate issue)
   - Needs research on Antigravity's configuration system
   - May be able to share Gemini CLI hooks
   - May need IDE-specific integration

## Open Questions

1. Where does Gemini CLI store settings? Need to verify `~/.gemini/settings.json` path
2. Does Gemini CLI use same JSON structure as Claude for hooks?
3. Can one implementation cover both Gemini CLI and Antigravity?
4. Should we generate GEMINI.md or rely on AGENTS.md?

## Acceptance Criteria

- [ ] `bd setup gemini` installs hooks to Gemini CLI settings
- [ ] `bd setup gemini --check` verifies installation
- [ ] `bd doctor` reports Gemini CLI integration status
- [ ] Documentation updated in docs/SETUP.md

Implemented bd setup gemini with SessionStart/PreCompress hooks


Test message

Hello world


Fix: bd migrate-tombstones corrupts deletions manifest

When running 'bd migrate-tombstones' followed by 'bd sync', the operation adds thousands of issues to deletions.jsonl with author 'bd-doctor-hydrate' that should never have been deleted. This causes:
1. Issues are marked as deleted that were never intended for deletion
2. Subsequent 'bd sync' fails with 3-way merge errors
3. The migration operation corrupts database state

## Root Cause
The tombstone migration logic incorrectly identifies issues that should be migrated as 'deleted issues' and adds them to the deletions manifest instead of converting them to inline tombstones.

## Reproduction
1. bd sync --import-only
2. bd migrate-tombstones
3. bd sync
Result: See thousands of 'Skipping bd-xxxx (in deletions manifest: deleted 2025-12-14 by bd-doctor-hydrate)' lines and 3-way merge failures

## Impact
- Database becomes inconsistent
- Issues permanently lost from local database
- Requires full reset to recover

## Files to Investigate
- cmd/bd/migrate_tombstones.go - migration logic
- deletions manifest handling during migration


bd template commands fail with daemon mode

The `bd template show` and `bd template instantiate` commands fail with 'Error loading template: no database connection' when daemon is running.

**Reproduction:**
```bash
bd daemon --start
bd template show bd-qqc  # Error: no database connection
bd template show bd-qqc --no-daemon  # Works
```

**Expected:** Template commands should work with daemon like other commands.

**Workaround:** Use `--no-daemon` flag.

**Location:** Likely in cmd/bd/template.go - daemon RPC path not implemented for template operations.


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


Detect and warn about outdated git hooks

Users may have outdated git hooks installed that reference removed flags (e.g., --resolve-collisions). bd should detect this and warn users to reinstall.


Document -short flag in testing guide

Add documentation about the -short flag and how it's used to skip slow tests. Should explain that developers can run 'go test -short ./...' for fast iteration and 'go test ./...' for full coverage.


enhance 'bd status' to show recent activity

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


Update go install bd to 0.33.2

Rebuild and install bd to ~/go/bin:

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

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


Distill: promote ad-hoc epic to proto

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

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

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

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

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

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

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