Implements the Christmas Ornament pattern for patrol molecules:
- Add CloneOptions struct with ParentID and ChildRef for dynamic bonding
- Add generateBondedID() to create custom IDs like "patrol-x7k.arm-ace"
- Add --ref flag to `bd mol bond` for custom child references
- Variable substitution in childRef (e.g., "arm-{{polecat_name}}")
This enables:
bd mol bond mol-polecat-arm bd-patrol --ref arm-{{name}} --var name=ace
# Creates: bd-patrol.arm-ace, bd-patrol.arm-ace.capture, etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Filters issues by parent issue ID using parent-child dependencies.
Example: bd list --parent=bd-xyz --status=open
Changes:
- Add ParentID field to IssueFilter type
- Add --parent flag to list command
- Forward parent filter through RPC
- Implement filtering in SQLite and memory storage
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove dual code paths in the autoflush system. FlushManager is now the
only code path for auto-flush operations.
Changes:
- Remove legacy globals: isDirty, needsFullExport, flushTimer
- Remove flushToJSONL() wrapper function (was backward-compat shim)
- Simplify markDirtyAndScheduleFlush/FullExport to just call FlushManager
- Update tests to use FlushManager or flushToJSONLWithState directly
FlushManager handles all flush state internally in its run() goroutine,
eliminating the need for global state. Sandbox mode and tests that do
not need flushing get a no-op when FlushManager is nil.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Conditional bonds now work as documented: "B runs only if A fails".
Implementation:
- Add DepConditionalBlocks dependency type to types.go
- Add IsFailureClose() helper to detect failure keywords in close_reason
- Update blocked cache to handle conditional-blocks:
- B is blocked while A is open
- B stays blocked if A closes with success
- B becomes unblocked if A closes with failure
Failure keywords: failed, rejected, wontfix, cancelled, abandoned,
blocked, error, timeout, aborted (case-insensitive)
Updated bondProtoProto, bondProtoMol, bondMolMol to use
DepConditionalBlocks for conditional bond type.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new Maintenance category to bd doctor with checks for:
- Stale closed issues (older than 30 days)
- Expired tombstones (older than TTL)
- Compaction candidates (info only)
Add fix handlers for cleanup and tombstone pruning via bd doctor --fix.
Add deprecation hints to cleanup, compact, and detect-pollution commands
suggesting users try bd doctor instead.
This consolidation reduces cognitive load - users just need to remember
'bd doctor' for health checks and 'bd doctor --fix' for maintenance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The test was creating an empty .beads/ directory after simulating rm -rf,
but FindBeadsDir() requires some project files (config.yaml, metadata.json,
or *.db/*.jsonl) to recognize a directory as a valid beads directory.
Updated the test to create a minimal config.yaml like bd init actually does
before auto-import runs. This makes the test more realistic and fixes the
flakiness.
When using bd create -f with a running daemon, the command would fail
with 'database not initialized' because it bypassed the daemon RPC path.
The fix adds createIssuesFromMarkdownViaDaemon which:
- Parses the markdown file (no store access needed)
- Converts issue templates to CreateArgs
- Uses daemon Batch RPC to create all issues efficiently
- Preserves all features: labels, dependencies, hooks, JSON output
Tested with daemon and non-daemon modes, verifying priority 0 is
preserved correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
External dependencies (external:project:capability) are now visible in
the dependency tree output. Previously they were invisible because the
recursive CTE only JOINed against the issues table.
Changes:
- GetDependencyTree now fetches external deps and adds them as synthetic
leaf nodes with resolution status (satisfied/blocked)
- formatTreeNode displays external deps with special formatting
- Added helper parseExternalRefParts for parsing external refs
Test coverage added for:
- External deps appearing in dependency tree
- Cycle detection ignoring external refs
- CheckExternalDep when target has no .beads directory
- Various invalid external ref format variations
Closes: bd-vks2, bd-mv6h, bd-d9mu
- Remove TestFindJSONLPathDefault from .test-skip (now passes)
- Add explanatory comments to 24 ignored error locations in cmd/bd:
- Cobra flag methods (MarkHidden, MarkRequired, MarkDeprecated)
- Best-effort cleanup/close operations
- Process signaling operations
Part of code health review epic bd-tggf.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add git.GetRepoRoot() with Windows path normalization
- Update beads.findGitRoot() to delegate to git.GetRepoRoot()
- Replace findBeadsDir() with beads.FindBeadsDir() across 8 files
- Remove duplicate findBeadsDir() and findGitRoot() function definitions
- Remove dead test code (TestInfoCommand, TestInfoWithNoDaemon)
- Update tests to work with consolidated APIs
Part of Code Health Review Dec 2025 epic (bd-tggf).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add bd mol current: Shows current position in molecule workflow
- Displays all steps with status indicators (done/current/ready/blocked/pending)
- Infers molecule from in_progress issues when no ID given
- Supports --for flag to check another agent's molecules
Add bd close --continue: Auto-advances to next molecule step
- After closing, finds parent molecule and next ready step
- Auto-claims next step by default (--no-auto to skip)
- Shows molecule completion message when all steps closed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The daemon.log was showing duplicate log messages:
- 'File change detected' appeared 4x for a single file change
- 'Git ref change detected' appeared 2x for a single ref update
Root cause:
- fsnotify generates multiple events (Write, Create, Chmod) for single file ops
- Both parent directory and file watchers can trigger for the same change
- Multiple git ref files may be updated simultaneously
Fix:
- Add log deduplication with 500ms window (matching debouncer window)
- Track last log time for file changes and git ref changes separately
- Only log if enough time has passed since last log of same type
- Still trigger debouncer for every event (functionality unchanged)
This reduces log noise while maintaining full functionality.
The error message 'path exists but is not a valid git worktree' was appearing
in daemon.log when the daemon attempted to use an existing worktree that was
in the git worktree list but had other issues (broken sparse checkout, etc.).
Root cause:
- CreateBeadsWorktree only checked isValidWorktree (is it in git worktree list)
- CheckWorktreeHealth was called separately and checked additional things
- If the worktree passed isValidWorktree but failed health check, an error
was logged and repair was attempted
Fix:
- CreateBeadsWorktree now performs a full health check when it finds an
existing worktree that's in the git worktree list
- If the health check fails, it automatically removes and recreates the
worktree
- Removed redundant CheckWorktreeHealth calls in daemon_sync_branch.go and
syncbranch/worktree.go since CreateBeadsWorktree now handles this internally
This eliminates the confusing error message and ensures worktrees are always
in a healthy state after CreateBeadsWorktree returns successfully.
Add --auto-pull flag to control whether the daemon periodically pulls from
remote to check for updates from other clones.
Configuration precedence:
1. --auto-pull CLI flag (highest)
2. BEADS_AUTO_PULL environment variable
3. daemon.auto_pull in database config
4. Default: true when sync.branch is configured
When auto_pull is enabled, the daemon creates a remoteSyncTicker that
periodically calls doAutoImport() to pull remote changes. When disabled,
users must manually run 'git pull' to sync remote changes.
Changes:
- cmd/bd/daemon.go: Add --auto-pull flag and config reading logic
- cmd/bd/daemon_event_loop.go: Gate remoteSyncTicker on autoPull parameter
- cmd/bd/daemon_lifecycle.go: Add auto-pull to status output and spawn args
- internal/rpc/protocol.go: Add AutoPull field to StatusResponse
- internal/rpc/server_core.go: Add autoPull to Server struct and SetConfig
- internal/rpc/server_routing_validation_diagnostics.go: Include in status
- Tests updated to pass autoPull parameter
Closes #TBD
Modernize sorting code to use Go 1.21+ slices package:
- Replace sort.Slice with slices.SortFunc across 16 files
- Use cmp.Compare for orderable types (strings, ints)
- Use time.Time.Compare for time comparisons
- Use cmp.Or for multi-field sorting
- Use slices.SortStableFunc where stability matters
Benefits: cleaner 3-way comparison, slightly better performance,
modern idiomatic Go.
Part of GH#692 refactoring epic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(daemon): include tombstones in exportToJSONLWithStore for sync propagation
The daemon's exportToJSONLWithStore() function was using an empty
IssueFilter which defaults to IncludeTombstones: false. This caused
deleted issues (tombstones) to be excluded from JSONL exports during
daemon sync cycles.
Bug scenario:
1. User runs `bd delete <issue>` with daemon active
2. Database correctly marks issue as tombstone
3. Main .beads/issues.jsonl correctly shows status:"tombstone"
4. But sync branch worktree JSONL still showed status:"open"
5. Other clones would not see the deletion
The fix adds IncludeTombstones: true to match the behavior of
exportToJSONL() in sync.go, ensuring tombstones propagate to other
clones and prevent resurrection of deleted issues.
Adds regression test TestExportToJSONLWithStore_IncludesTombstones
that verifies tombstones are included in daemon JSONL exports.
* fix: resolve all golangci-lint errors (cherry-pick from fix/linting-errors)
Cherry-picked linting fixes to ensure CI passes.
---------
Co-authored-by: Charles P. Cross <cpdata@users.noreply.github.com>
* fix(daemon): handle diverged sync branch with fetch-rebase-retry on push
When pushing to the sync branch, if the remote has newer commits that
the local worktree doesn't have, the push would fail with "fetch first"
error and the daemon would log the failure without recovery.
Bug scenario:
1. Clone A pushes commit X to sync branch
2. Clone B has local commit Y (not based on X)
3. Clone B's push fails with "fetch first" error
4. Without this fix: daemon logs failure and stops
5. With this fix: daemon fetches, rebases Y on X, and retries push
This fix adds fetch-rebase-retry logic to gitPushFromWorktree():
1. Detect push rejection due to remote having newer commits
2. Fetch the latest remote sync branch
3. Rebase local commits on top of remote
4. Retry the push
If rebase fails (e.g., due to conflicts), the rebase is aborted and
an error is returned with helpful context.
This allows multiple clones to push to the same sync branch without
manual intervention, as long as the changes don't conflict.
Adds integration test TestGitPushFromWorktree_FetchRebaseRetry that
verifies the fetch-rebase-retry behavior with diverged branches.
* fix: resolve all golangci-lint errors (cherry-pick from fix/linting-errors)
Cherry-picked linting fixes to ensure CI passes.
---------
Co-authored-by: Charles P. Cross <cpdata@users.noreply.github.com>
* fix(daemon): add periodic remote sync to event-driven mode
The event-driven daemon mode only triggered imports when the local JSONL
file changed (via file watcher) or when the fallback ticker fired (only
if watcher failed). This meant the daemon wouldn't see updates pushed
by other clones until something triggered a local file change.
Bug scenario:
1. Clone A creates an issue and daemon pushes to sync branch
2. Clone B's daemon only watched local file changes
3. Clone B would not see the new issue until something triggered local change
4. With this fix: Clone B's daemon periodically calls doAutoImport
This fix adds a 30-second periodic remote sync ticker that calls
doAutoImport(), which includes syncBranchPull() to fetch and import
updates from the remote sync branch.
This is essential for multi-clone workflows where:
- Clone A creates an issue and daemon pushes to sync branch
- Clone B's daemon needs to periodically pull to see the new issue
- Without periodic sync, Clone B would only see updates if its local
JSONL file happened to change
The 30-second interval balances responsiveness with network overhead.
Adds integration test TestEventDrivenLoop_PeriodicRemoteSync that
verifies the event-driven loop starts with periodic sync support.
* feat(daemon): add configurable interval for periodic remote sync
- Add BEADS_REMOTE_SYNC_INTERVAL environment variable to configure
the interval for periodic remote sync (default: 30s)
- Add getRemoteSyncInterval() function to parse the env var
- Minimum interval is 5s to prevent excessive load
- Setting to 0 disables periodic sync (not recommended)
- Add comprehensive integration tests for the configuration
Valid duration formats:
- "30s" (30 seconds)
- "1m" (1 minute)
- "5m" (5 minutes)
Tests added:
- TestEventDrivenLoop_HasRemoteSyncTicker
- TestGetRemoteSyncInterval_Default
- TestGetRemoteSyncInterval_CustomValue
- TestGetRemoteSyncInterval_MinimumEnforced
- TestGetRemoteSyncInterval_InvalidValue
- TestGetRemoteSyncInterval_Zero
- TestSyncBranchPull_FetchesRemoteUpdates
* fix: resolve all golangci-lint errors (cherry-pick from fix/linting-errors)
Cherry-picked linting fixes to ensure CI passes.
* feat(daemon): add config.yaml support for remote-sync-interval
- Add remote-sync-interval to .beads/config.yaml as alternative to
BEADS_REMOTE_SYNC_INTERVAL environment variable
- Environment variable takes precedence over config.yaml (follows
existing pattern for flush-debounce)
- Add config binding in internal/config/config.go
- Update getRemoteSyncInterval() to use config.GetDuration()
- Add doctor validation for remote-sync-interval in config.yaml
Configuration sources (in order of precedence):
1. BEADS_REMOTE_SYNC_INTERVAL environment variable
2. remote-sync-interval in .beads/config.yaml
3. DefaultRemoteSyncInterval (30s)
Example config.yaml:
remote-sync-interval: "1m"
---------
Co-authored-by: Charles P. Cross <cpdata@users.noreply.github.com>
The interactions.jsonl file is an append-only audit log that should be
tracked in git (synced with bd sync), but it was missing from the
negation patterns in .beads/.gitignore.
This adds !interactions.jsonl alongside !issues.jsonl and !metadata.json
for consistency and clarity that this file should be committed.
Co-authored-by: Christian Catalan <crcatala@gmail.com>
- bd repo add/remove now writes to .beads/config.yaml instead of database
- bd repo remove deletes hydrated issues from the removed repo
- Added internal/config/repos.go for YAML config manipulation
- Added DeleteIssuesBySourceRepo for cleanup on remove
Fixes config disconnect where bd repo add wrote to DB but hydration read from YAML.
Breaking change: bd repo add no longer accepts optional alias argument.
Co-authored-by: Dylan Conlin <dylan.conlin@gmail.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(doctor): UX improvements for diagnostics and daemon
- Add Repo Fingerprint check to detect when database belongs to a
different repository (copied .beads dir or git remote URL change)
- Add interactive fix for repo fingerprint with options: update repo ID,
reinitialize database, or skip
- Add visible warning when daemon takes >5s to start, recommending
'bd doctor' for diagnosis
- Detect install method (Homebrew vs script) and show only relevant
upgrade command
- Improve WARNINGS section:
- Add icons (⚠ or ✖) next to each item
- Color numbers by severity (yellow for warnings, red for errors)
- Render entire error lines in red
- Sort by severity (errors first)
- Fix alignment with checkmarks above
- Use heavier fail icon (✖) for better visibility
- Add integration and validation tests for doctor fixes
* fix(lint): address errcheck and gosec warnings
- mol_bond.go: explicitly ignore ephStore.Close() error
- beads.go: add nosec for .gitignore file permissions (0644 is standard)
Implements wisp management commands for ephemeral molecules:
- bd wisp list: List wisps with stale detection
- bd wisp gc: Garbage collect orphaned wisps
- bd mol burn: Delete wisp without creating digest
Wisps are ephemeral molecules stored in .beads-wisp/ for patrol cycles
and operational loops that shouldn't accumulate in permanent storage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add wisp detection in mol squash: checks wisp storage if not in main
- squashWispToPermanent: creates digest in permanent, deletes from wisp
- Fix directory naming: .beads-wisps → .beads-wisp (singular, matches doc)
- Add comprehensive tests for wisp squash scenarios
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Standardize terminology for ephemeral molecule storage:
- .beads-ephemeral/ → .beads-wisps/
- --ephemeral flag → --wisp flag
- All Ephemeral* functions → Wisp*
Wisps are the "steam" in Gas Town's engine metaphor - ephemeral
molecules that evaporate after squash.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When --ephemeral is set:
- Spawned molecules go to .beads-ephemeral/ instead of .beads/
- Ephemeral directory is automatically gitignored
- No auto-flush (ephemeral does not sync)
- Proto+proto bonding ignores flag (templates stay in permanent storage)
Updated help text to document wisp workflow (bond then squash/burn).
Generated with Claude Code
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- bd ship <capability> finds issue with export:<capability> label
- Validates issue is closed (or --force to override)
- Adds provides:<capability> label to mark capability as shipped
- Protects provides:* namespace in bd label add
- Supports --dry-run for preview
External projects depend on capabilities via:
bd dep add <issue> external:<project>:<capability>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Config (bd-66w1):
- Add external_projects config for mapping project names to paths
- Add GetExternalProjects() and ResolveExternalProjectPath() functions
- Add config documentation and tests
External deps (bd-om4a):
- bd dep add accepts external:project:capability syntax
- External refs stored as-is in dependencies table
- GetBlockedIssues includes external deps in blocked_by list
- blocked_issues_cache includes external dependencies
- Add validation and parsing helpers for external refs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The code assumed rootID was at least 8 chars, but issue IDs
like 'gt-i4lo' are only 7 chars (prefix-hash format).
Simply use the full ID instead of truncating - IDs are already
short enough for display.
- Add CheckOrphanedIssues to detect issues referenced in commits but still open
- Pattern matches (prefix-xxx) in git log against open issues in database
- Reports warning with issue IDs and commit hashes
- Add 8 comprehensive tests for the new check
Also:
- Add tests for mol spawn --attach functionality (bd-f7p1)
- Document commit message convention in AGENT_INSTRUCTIONS.md
- Fix CheckpointWAL to use wrapDBError for consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The --as flag description correctly says "Custom title" but the variable
was named customID and dry-run output said "Custom ID". Fixed for consistency:
- Renamed variable customID -> customTitle
- Changed dry-run output from "Custom ID" to "Custom title"
Closes bd-e7ou
GetConfig() returns ("", nil) for missing/empty config keys.
getRepoConfig() then tried json.Unmarshal([]byte(""), ...) which
fails with "unexpected end of JSON input".
Add empty value check before JSON parsing - return empty map when
config value is empty string.
Closes GH#680
Co-Authored-By: dylan-conlin <dylan-conlin@users.noreply.github.com>
Fixes:
- P0 priority preserved - omitempty removed from Priority field (GH#671)
- nil pointer check in markdown parsing (GH#674)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove cleanupSnapshots() and validateSnapshotConsistency() from
deletion_tracking.go - both were marked deprecated and never called.
The other two deprecated functions (getSnapshotStats, initializeSnapshotsIfNeeded)
are still used by tests and production code respectively.
Filed bd-xsl9 to track removal of legacy autoflush dual-path code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove omitempty from Priority field so P0 issues preserve priority:0
- Add nil check for store in createIssuesFromMarkdown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>