Change routing.mode default from "auto" to "" (empty/disabled).
This fixes GH#1165 where fresh `bd init --prefix X` followed by
`bd create` would unexpectedly route to ~/.beads-planning and fail
with "database not initialized: issue_prefix config is missing".
Auto-routing now requires explicit opt-in via:
- `bd init --contributor` flag, OR
- `bd config set routing.mode auto`
Includes test verifying the default and doc updates clarifying
the opt-in requirement.
Code review improvements to internal/config/sync.go:
1. Warning suppression toggle
- Add ConfigWarnings bool to enable/disable warnings
- Add ConfigWarningWriter io.Writer for testable output
2. Consolidate sync mode constants
- cmd/bd/sync_mode.go now imports from internal/config
- Single source of truth for mode values
- Uses shared IsValidSyncMode() for validation
3. Fix empty sovereignty semantics
- Empty now returns SovereigntyNone (no restriction)
- Only non-empty invalid values fall back to T1 with warning
4. Export validation helpers
- IsValidSyncMode(), IsValidConflictStrategy(), IsValidSovereignty()
- ValidSyncModes(), ValidConflictStrategies(), ValidSovereigntyTiers()
- String() methods on all typed values
5. Logger interface
- ConfigWarningWriter allows custom logging destinations
- Tests can capture warnings without os.Stderr manipulation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Commit 356ab92b deleted internal/config/sync.go while adding
cmd/bd/sync_mode.go, but config.go still references the types and
functions from sync.go, causing build failures.
These serve different purposes:
- internal/config/sync.go: viper-based (config.yaml), has warnings
- cmd/bd/sync_mode.go: storage-based (database), no warnings
Both should coexist. This restores sync.go and sync_test.go from
e82e15a8, and fixes type conversion in sync.go:805.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes#1076.
When git worktrees have sparse-checkout enabled, git add operations can
fail with 'outside of sparse-checkout cone' errors. Adding the --sparse
flag allows git to stage files outside the sparse-checkout cone.
Changes:
- Add --sparse flag to git add in daemon_sync_branch.go (line 165)
- Add --sparse flag to git add in migrate_sync.go (line 343)
- Add --sparse flag to git add in sync_branch.go (line 308)
- Add --sparse flag to git add in worktree.go (line 732)
All changes include comments referencing #1076 for traceability.
The scanIssues and scanIssuesWithDependencyType helper functions were
not checking rows.Err() after iterating through query results. This
could cause errors during iteration (connection drops, context
cancellation, etc.) to be silently ignored.
Per Go database/sql best practices, rows.Err() should always be checked
after a rows.Next() loop completes to catch any errors that occurred
during iteration.
Co-authored-by: Steven Syrek <steven.syrek@deepl.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(config): remove duplicate declarations between config.go and sync.go
Commit e82e15a8 created sync.go with typed constants (SyncMode,
ConflictStrategy, Sovereignty) but didn't remove the original untyped
constants from config.go that were added in 16f8c3d3. This caused
redeclaration errors preventing the project from building.
Changes:
- Remove duplicate SyncMode, ConflictStrategy, Sovereignty constants
from config.go (keep typed versions in sync.go)
- Remove duplicate GetSyncMode, GetConflictStrategy, GetSovereignty
functions from config.go (keep sync.go versions with warnings)
- Update SyncConfig, ConflictConfig, FederationConfig structs to use
typed fields instead of string
- Add IsSyncModeValid, IsConflictStrategyValid, IsSovereigntyValid
wrapper functions that use sync.go's validation maps
- Update cmd/bd/sync.go to use typed ConflictStrategy parameter
- Update tests to work with typed constants
* fix(dolt): handle Merge return values in concurrent test
* fix(test): add --repo flag to show_test.go to bypass auto-routing
The tests were failing because the create command was routing issues
to ~/.beads-planning instead of the test's temp directory. Adding
--repo . overrides auto-routing and creates issues in the test dir.
* fix(config): validate sync-branch at config time
Add sync-branch validation to validateYamlConfigValue() to reject
main/master at config time, preventing the validation bypass in GH#1166.
- Add case for sync-branch and sync.branch keys
- Inline validation logic to avoid import cycle (config <-> syncbranch)
- Add unit tests for rejection (main/master) and acceptance (valid names)
Part of: #1166
* fix(sync): add runtime guard for sync-branch == current-branch
Add dynamic runtime check before worktree operations to catch cases
where sync-branch matches the current branch. This provides defense
in depth for manual YAML edits, pre-fix configs, or non-main/master
branch names (trunk, develop, production, etc.).
- Check IsSyncBranchSameAsCurrent() after hasSyncBranchConfig is set
- Position check BEFORE worktree entry (CWD changes inside worktree)
- Add integration test TestSync_FailsWhenOnSyncBranch
Part of: #1166
* docs: note main/master restriction in sync-branch FAQ
Clarifies that git worktrees cannot checkout the same branch in
multiple locations, so main/master cannot be used as sync branch.
When no git remote is configured, DetectUserRole() now defaults to
Maintainer instead of Contributor. This fixes issue routing for:
1. New personal projects (no remote configured yet)
2. Intentionally local-only repositories
Previously, issues would silently route to ~/.beads-planning instead
of the local .beads/ directory.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When creating issues with explicit IDs like `bd create --id hq-cv-test`,
the prefix validation was failing even when `hq-cv` was in `allowed_prefixes`.
Root cause: `ExtractIssuePrefix("hq-cv-test")` returns `"hq"` (not `"hq-cv"`)
because "test" looks like an English word, causing the algorithm to fall back
to the first hyphen. The validation then checked if `"hq"` was in the allowed
list containing `"hq-cv"` - which failed.
The fix adds `ValidateIDPrefixAllowed()` which validates the full ID using
"starts with" matching (the same approach the importer uses successfully).
This correctly handles multi-hyphen prefixes like `hq-cv-` regardless of
what the suffix looks like.
Fixes#1135
Co-authored-by: Steven Syrek <steven.syrek@deepl.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When ExtractIssuePrefix returns "hq" from an ID like "hq-cv-test"
(because "test" is word-like), but "hq-cv" is in allowedPrefixes,
we should accept "hq" since it's clearly intended to be part of "hq-cv".
This handles cases where the prefix extraction algorithm yields a
shorter prefix than the user intended, but the full intended prefix
is in the allowed list.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements hq-ew1mbr.27: The sync.mode config now actually changes how
bd sync operates:
- git-portable (default): JSONL exported on push, imported on pull
- realtime: JSONL exported on every change (placeholder for daemon hook)
- dolt-native: Uses Dolt Push/Pull, skips JSONL workflow entirely
- belt-and-suspenders: Both Dolt remotes AND JSONL for redundancy
Changes:
- Add sync_mode.go with mode constants, Get/Set functions, and helpers
- Update bd sync --status to show actual mode from config
- Add --set-mode flag to bd sync for configuring the mode
- Modify doExportSync to respect mode (Dolt push for dolt-native)
- Modify doPullFirstSync to use Dolt pull for dolt-native mode
- Add RemoteStorage interface for Push/Pull operations
- Add comprehensive tests for sync mode functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add config accessor functions for sync mode, conflict strategy, and
federation sovereignty tier settings. These functions:
- Read from config.yaml via viper
- Validate against known valid values
- Return sensible defaults when not set
- Log warnings to stderr when invalid values are configured (instead
of silently falling back to defaults)
Closes: hq-ew1mbr.26
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Follow-up to 828fc11b addressing code review feedback:
1. Added LoadTownRoutes() - exported function that walks up to find
town-level routes.jsonl (e.g., ~/gt/.beads/routes.jsonl)
2. Updated buildAllowedPrefixSet to use LoadTownRoutes instead of
LoadRoutes, so it finds routes even when importing from a rig's
local beads directory
3. Added unit tests for buildAllowedPrefixSet covering:
- Primary prefix inclusion
- allowed_prefixes config parsing
- Routes from routes.jsonl
- Missing routes.jsonl handling
- Empty beadsDir handling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Core beads built-in types now only include work types:
- bug, feature, task, epic, chore
Gas Town types (molecule, gate, convoy, merge-request, slot, agent,
role, rig, event, message) are now "well-known custom types":
- Constants still exist for code convenience
- Require types.custom configuration for validation
- bd types command shows core types and configured custom types
Changes:
- types.go: Separate core work types from well-known custom types
- IsValid(): Only accepts core work types
- bd types: Updated to show core types and custom types from config
- memory.go: Use ValidateWithCustom for custom type support
- multirepo.go: Only check core types as built-in
- Updated all tests to configure custom types
This allows Gas Town (and other projects) to define their own types
via config while keeping beads core focused on work tracking.
Closes: bd-find4
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When importing issues in a multi-rig setup (Gas Town), the prefix
validation was failing for issues with prefixes from other rigs
(e.g., hq-* prefixes from town-level beads).
This fix extends buildAllowedPrefixSet to also load prefixes from
routes.jsonl, allowing issues from any routed rig to pass validation.
Fixes: gt-2maz79
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extends Storage interface with Dolt-specific version control capabilities:
- New VersionedStorage interface in storage/versioned.go with:
- History queries: History(), AsOf(), Diff()
- Branch operations: Branch(), Merge(), CurrentBranch(), ListBranches()
- Commit operations: Commit(), GetCurrentCommit()
- Conflict resolution: GetConflicts(), ResolveConflicts()
- Helper types: HistoryEntry, DiffEntry, Conflict
- DoltStore implements VersionedStorage interface
- New CLI commands:
- bd history <id> - Show issue version history
- bd diff <from> <to> - Show changes between commits/branches
- bd branch [name] - List or create branches
- bd vc merge <branch> - Merge branch to current
- bd vc commit -m <msg> - Create a commit
- bd vc status - Show current branch/commit
- Added --as-of flag to bd show for time-travel queries
- IsVersioned() helper for graceful SQLite backend detection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The generic ValidateIDFormat() used isLikelyHash() which treated
3-character suffixes like "nux" as valid hashes, causing agent IDs
like "nx-nexus-polecat-nux" to extract prefix as "nx-nexus-polecat"
instead of the correct "nx".
Fix: For --type=agent, validate agent ID format first and use
ExtractAgentPrefix() which correctly extracts prefix from the
first hyphen for agent IDs.
Fixes#591
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add nil check at start of ResolvePartialID to return a proper error
instead of panicking when storage interface is nil.
Root cause: When bd refile (or other commands) is called with a nil
storage, calling store.SearchIssues() panics with SIGSEGV. This can
happen when routing fails to initialize storage properly.
Now returns: "cannot resolve issue ID <id>: storage is nil"
Fixes: bd-7ypor
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add "enhancement" to util.issueTypeAliases for consistency
- Make types.IssueType.Normalize() case-insensitive and include all aliases
- Fix update.go to normalize type before validation
- Remove duplicate type validation block in update.go
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add DeleteBranch method to DoltStore for removing branches
- Update hookPostMergeDolt to clean up import branches after merge
- Completes hq-ew1mbr.9 git hook infrastructure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Support --type enhancement as an alias for --type feature when creating
issues. The normalization happens before validation to ensure consistency
across all code paths.
Closes gt-hzanoe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add batch query optimization to avoid N+1 queries in scanIssueIDs
- Create GetIssuesByIDs helper to fetch multiple issues in single query
- Add scanIssueRow helper to scan issue data from rows iterator
- Add proper timeout contexts to all Dolt tests using testContext helper
The embedded Dolt driver is slow for repeated queries. Replacing N+1
GetIssue calls with a single IN clause query fixes the 30s+ timeouts
in TestDoltStoreDependencies, TestDoltStoreSearch, and
TestDoltStoreGetReadyWork.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use exec.LookPath instead of hardcoded path for Dolt detection
- Add test context with timeout to prevent tests from hanging
- Document known issues with embedded Dolt driver async operations
The embedded Dolt driver can hang on complex JOIN queries. This change
ensures tests fail gracefully with timeout rather than hanging.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When an issue is deleted, issues that depend on it were not being marked
dirty. This caused stale dependency references to persist in JSONL after
the target issue was deleted, because the dependent issues were never
re-exported.
This manifests as FK validation failures during multi-repo hydration:
"foreign key violation: issue X depends on non-existent issue Y"
The fix queries for dependent issues before deleting and marks them dirty
so they get re-exported without the stale dependency reference.
Adds test: TestDeleteIssueMarksDependentsDirty
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Git hooks are shared across all worktrees and live in the common git
directory (e.g., /repo/.git/hooks), not the worktree-specific directory
(e.g., /repo/.git/worktrees/feature/hooks).
The core issue was in GetGitHooksDir() which used GetGitDir() instead
of GetGitCommonDir(). This caused hooks to be installed to/read from
the wrong location when running in a worktree.
Additionally, several places in the codebase manually constructed
hooks paths using gitDir + "hooks" instead of calling GetGitHooksDir().
These have been updated to use the proper worktree-aware path.
Affected areas:
- GetGitHooksDir() now uses GetGitCommonDir()
- CheckGitHooks() uses GetGitHooksDir()
- installHooks/uninstallHooks use GetGitHooksDir()
- runChainedHook() uses GetGitHooksDir()
- Doctor checks use git-common-dir for hooks paths
- Reset command uses GetGitCommonDir() for hooks and beads-worktrees
Symptoms that this fixes:
- Chained hooks (pre-commit.old) not running in worktrees
- bd hooks install not finding/installing hooks correctly in worktrees
- bd hooks list showing incorrect status in worktrees
- bd doctor reporting incorrect hooks status in worktrees
Co-authored-by: Zain Rizvi <4468967+ZainRizvi@users.noreply.github.com>
The dolt storage backend requires CGO due to its gozstd dependency.
This change makes the dolt backend optional using build tags, allowing
`go install` to work on Windows where CGO is disabled by default.
Changes:
- Add BackendFactory registration pattern to factory package
- Create factory_dolt.go with `//go:build cgo` constraint that
registers the dolt backend only when CGO is available
- Update init.go to use factory instead of direct dolt import
- When dolt backend is requested without CGO, provide helpful error
message directing users to pre-built binaries
The sqlite backend (default) works without CGO and covers the majority
of use cases. Users who need dolt can either:
1. Use pre-built binaries from GitHub releases
2. Enable CGO by installing a C compiler
Fixes#1116
All time.Now() calls in the dolt storage layer now use time.Now().UTC()
to ensure consistent timezone handling. Previously, timestamps could be
stored with mixed timezone formats (UTC 'Z' vs local '+01:00'), causing
bv validation to fail when updated_at appeared earlier than created_at
in absolute time.
Files modified:
- transaction.go: CreateIssue, UpdateIssue, CloseIssue
- issues.go: CreateIssue, CreateIssues, UpdateIssue, CloseIssue, markDirty, manageClosedAt
- rename.go: UpdateIssueID (2 locations)
- events.go: AddIssueComment (2 locations)
- dirty.go: SetExportHash
- queries.go: Overdue filter, GetStaleIssues
Fixes: bd-84gw9
Co-authored-by: LoomDeBWiles <loomenwiles@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Role and rig beads are reference metadata that should never be closed
or appear as actionable work. They are similar to agent beads which
are already excluded.
- Add 'role' and 'rig' to the issue type exclusion list in GetReadyWork
- Update comments to document the excluded types
Fixes confusion where role/rig beads appeared in bd ready output,
leading agents to try to close them as regular work items.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When .beads is a symlink (e.g., ~/gt/.beads -> ~/gt/olympus/.beads),
findTownRoutes() incorrectly used filepath.Dir() on the resolved
symlink path to determine the town root.
This caused route resolution to fail because the town root would be
~/gt/olympus instead of ~/gt, making routes point to non-existent
directories.
The fix adds findTownRootFromCWD() which walks up from the current
working directory instead of the beads directory path. This finds
the correct town root regardless of symlink resolution.
Changes:
- Add findTownRootFromCWD() function
- Update findTownRoutes() to use CWD-based town root detection
- Add fallback to filepath.Dir() for non-Gas Town repos
- Add debug logging (BD_DEBUG_ROUTING=1)
- Add comprehensive test case
Test: go test ./internal/routing/...
The SQL formatting warnings (G201) are safe because:
- Placeholders only contain "?" markers for parameterized queries
- WHERE/SET clauses use validated column names with ? placeholders
- Refs are validated by validateRef() before use in AS OF queries
- LIMIT values are safe integers from filter.Limit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Save issue-prefix in config.yaml when using --no-db mode
(previously only saved in database which doesn't exist in no-db mode)
- Add config.ResetForTesting() to allow reloading config in tests
- Simplify test to verify config values rather than execute subsequent
commands (cobra's flag caching makes multi-Execute() testing complex)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Centralizes repository context resolution via RepoContext API, fixing bugs where git commands run in the wrong repo when BEADS_DIR points elsewhere or in worktree scenarios.
Update all documentation to use the new subcommand syntax:
- `bd daemon --start` → `bd daemon start`
- `bd daemon --stop` → `bd daemon stop`
- `bd daemon --status` → `bd daemon status`
- `bd daemon --health` → `bd daemon status --all`
- `--global=false` → `--local`
The old flag syntax is deprecated but still works with warnings.
Closes: bd-734vd
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 2 of Dolt integration - enables runtime backend selection:
- Add --backend flag to bd init (sqlite|dolt)
- Create storage factory for backend instantiation
- Update daemon and main.go to use factory with config detection
- Update database discovery to find Dolt backends via metadata.json
- Fix Dolt schema init to split statements for MySQL compatibility
- Add ReadOnly mode to skip schema init for read-only commands
Usage: bd init --backend dolt --prefix myproject
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements a complete Dolt storage backend that mirrors the SQLite implementation
with MySQL-compatible syntax and adds version control capabilities.
Key features:
- Full Storage interface implementation (~50 methods)
- Version control operations: commit, push, pull, branch, merge, checkout
- History queries via AS OF and dolt_history_* tables
- Cell-level merge instead of line-level JSONL merge
- SQL injection protection with input validation
Bug fixes applied during implementation:
- Added missing quality_score, work_type, source_system to scanIssue
- Fixed Status() to properly parse boolean staged column
- Added validation to CreateIssues (was missing in batch create)
- Made RenameDependencyPrefix transactional
- Expanded GetIssueHistory to return more complete data
Test coverage: 17 tests covering CRUD, dependencies, labels, search,
comments, events, statistics, and SQL injection protection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When using `bd create --id=<id>` where the ID matches an existing
tombstone (from `bd delete --hard --force`), the creation now succeeds
by first deleting the tombstone and all related records.
This enables use cases like polecat respawn where a worker needs to
recreate an issue with the same ID.
Changes:
- queries.go: Check for tombstone before insert, delete it if found
(cleans up events, labels, dependencies, comments, dirty_issues)
- tombstone_test.go: Add regression test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Executed-By: beads/crew/dave
Rig: beads
Role: crew
When a crew worker's .beads/ is redirected to another repo, bd sync
now detects this and skips all git operations (sync-branch worktree
manipulation). Instead, it just exports to JSONL and lets the target
repo's owner handle the git sync.
Changes:
- sync.go: Detect redirect early, skip git operations when active
- beads.go: Update GetRedirectInfo() to check git repo even when
BEADS_DIR is pre-set (findLocalBdsDirInRepo helper)
- validation.go: Add doctor check for redirect + sync-branch conflict
- doctor.go: Register new check, remove undefined CheckMisclassifiedWisps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Executed-By: beads/crew/dave
Rig: beads
Role: crew
- Add witness, deacon, refinery role templates
- Ignore bd_test binary (bd-test was already ignored)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add support for JetBrains Junie AI agent:
- Create .junie/guidelines.md with workflow instructions
- Create .junie/mcp/mcp.json for MCP server configuration
- Add 'junie' to BuiltinRecipes in recipes.go
- Add runJunieRecipe() handler in setup.go
- Add website documentation
- Add integrations/junie/README.md
Usage: bd setup junie
Add daemonClient.ResolveID() calls before AddComment and ListComments
operations in daemon mode, following the pattern from update.go.
Previously, short IDs (e.g., "5wbm") worked with most bd commands but
failed with `comments add` and `comments list` when using the daemon.
The short ID was passed directly to the RPC server which expected full
IDs (e.g., "prefix-5wbm").
Changes:
- cmd/bd/comments.go: Add ID resolution before daemon RPC calls
- internal/rpc/comments_test.go: Update tests to reflect client-side
resolution pattern (RPC server expects full IDs, CLI resolves first)
Fixes: https://github.com/steveyegge/beads/issues/1070
Add tests demonstrating that `bd comments add` and `bd comments list`
don't accept short IDs in daemon mode, while other commands do.
Tests added:
- TestCLI_CommentsAddShortID (cli_fast_test.go)
- Tests short ID, partial ID, and comment alias in direct mode (passes)
- TestCommentAddWithShortID (internal/rpc/comments_test.go)
- Tests RPC layer with short ID (FAILS - demonstrates bug)
- TestCommentListWithShortID (internal/rpc/comments_test.go)
- Tests listing comments with short ID (FAILS - demonstrates bug)
The fix should add daemonClient.ResolveID() before AddComment/ListComments,
following the pattern in update.go and label.go.
Refs: https://github.com/steveyegge/beads/issues/1070