Implement BEADS_DIR environment variable (bd-e16b)

Add BEADS_DIR as a replacement for BEADS_DB to point to the .beads
directory instead of the database file directly.

Rationale:
- With --no-db mode, there's no .db file to point to
- The .beads directory is the logical unit (contains config.yaml, db
  files, jsonl files)
- More intuitive: point to the beads directory not the database file

Implementation:
- Add BEADS_DIR environment variable support to FindDatabasePath()
- Priority order: BEADS_DIR > BEADS_DB > auto-discovery
- Maintain backward compatibility with BEADS_DB (now deprecated)
- Update --no-db mode to respect BEADS_DIR
- Update MCP integration (config.py, bd_client.py)
- Update documentation to show BEADS_DIR as preferred method

Testing:
- Backward compatibility: BEADS_DB still works
- BEADS_DIR works with regular database mode
- BEADS_DIR works with --no-db mode
- Priority: BEADS_DIR takes precedence over BEADS_DB

Follow-up issues for refactoring:
- bd-efe8: Refactor path canonicalization into helper function
- bd-c362: Extract database search logic into helper function

Closes bd-e16b

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-02 18:34:34 -08:00
parent c5b2fbbc9d
commit 6ecfd04ec8
8 changed files with 187 additions and 54 deletions

View File

@@ -144,6 +144,7 @@ class BdCliClient(BdClientBase):
"""Client for calling bd CLI commands and parsing JSON output."""
bd_path: str
beads_dir: str | None
beads_db: str | None
actor: str | None
no_auto_flush: bool
@@ -153,6 +154,7 @@ class BdCliClient(BdClientBase):
def __init__(
self,
bd_path: str | None = None,
beads_dir: str | None = None,
beads_db: str | None = None,
actor: str | None = None,
no_auto_flush: bool | None = None,
@@ -163,7 +165,8 @@ class BdCliClient(BdClientBase):
Args:
bd_path: Path to bd executable (optional, loads from config if not provided)
beads_db: Path to beads database file (optional, loads from config if not provided)
beads_dir: Path to .beads directory (optional, loads from config if not provided)
beads_db: Path to beads database file (deprecated, optional, loads from config if not provided)
actor: Actor name for audit trail (optional, loads from config if not provided)
no_auto_flush: Disable automatic JSONL sync (optional, loads from config if not provided)
no_auto_import: Disable automatic JSONL import (optional, loads from config if not provided)
@@ -171,6 +174,7 @@ class BdCliClient(BdClientBase):
"""
config = load_config()
self.bd_path = bd_path if bd_path is not None else config.beads_path
self.beads_dir = beads_dir if beads_dir is not None else config.beads_dir
self.beads_db = beads_db if beads_db is not None else config.beads_db
self.actor = actor if actor is not None else config.beads_actor
self.no_auto_flush = no_auto_flush if no_auto_flush is not None else config.beads_no_auto_flush
@@ -225,7 +229,12 @@ class BdCliClient(BdClientBase):
# Log database routing for debugging
import sys
working_dir = self._get_working_dir()
db_info = self.beads_db if self.beads_db else "auto-discover"
if self.beads_dir:
db_info = f"BEADS_DIR={self.beads_dir}"
elif self.beads_db:
db_info = f"BEADS_DB={self.beads_db} (deprecated)"
else:
db_info = "auto-discover"
print(f"[beads-mcp] Running bd command: {' '.join(args)}", file=sys.stderr)
print(f"[beads-mcp] Database: {db_info}", file=sys.stderr)
print(f"[beads-mcp] Working dir: {working_dir}", file=sys.stderr)
@@ -656,6 +665,7 @@ BdClient = BdCliClient
def create_bd_client(
prefer_daemon: bool = False,
bd_path: Optional[str] = None,
beads_dir: Optional[str] = None,
beads_db: Optional[str] = None,
actor: Optional[str] = None,
no_auto_flush: Optional[bool] = None,
@@ -667,7 +677,8 @@ def create_bd_client(
Args:
prefer_daemon: If True, attempt to use daemon client first, fall back to CLI
bd_path: Path to bd executable (for CLI client)
beads_db: Path to beads database (for CLI client)
beads_dir: Path to .beads directory (for CLI client)
beads_db: Path to beads database (deprecated, for CLI client)
actor: Actor name for audit trail
no_auto_flush: Disable auto-flush (CLI only)
no_auto_import: Disable auto-import (CLI only)
@@ -732,6 +743,7 @@ def create_bd_client(
# Use CLI client
return BdCliClient(
bd_path=bd_path,
beads_dir=beads_dir,
beads_db=beads_db,
actor=actor,
no_auto_flush=no_auto_flush,

View File

@@ -32,6 +32,7 @@ class Config(BaseSettings):
model_config = SettingsConfigDict(env_prefix="")
beads_path: str = Field(default_factory=_default_beads_path)
beads_dir: str | None = None
beads_db: str | None = None
beads_actor: str | None = None
beads_no_auto_flush: bool = False
@@ -75,6 +76,35 @@ class Config(BaseSettings):
return v
@field_validator("beads_dir")
@classmethod
def validate_beads_dir(cls, v: str | None) -> str | None:
"""Validate BEADS_DIR points to an existing .beads directory.
Args:
v: Path to .beads directory or None
Returns:
Validated path or None
Raises:
ValueError: If path is set but directory doesn't exist
"""
if v is None:
return v
path = Path(v)
if not path.exists():
raise ValueError(
f"BEADS_DIR points to non-existent directory: {v}\n"
+ "Please verify the .beads directory path is correct."
)
if not path.is_dir():
raise ValueError(f"BEADS_DIR must point to a directory, not a file: {v}")
return v
@field_validator("beads_db")
@classmethod
def validate_beads_db(cls, v: str | None) -> str | None:
@@ -129,7 +159,8 @@ def load_config() -> Config:
+ "After installation, restart Claude Code.\n\n"
+ "Advanced configuration (optional):\n"
+ f" BEADS_PATH - Path to bd executable (default: {default_path})\n"
+ " BEADS_DB - Path to beads database file (default: auto-discover)\n"
+ " BEADS_DIR - Path to .beads directory (default: auto-discover)\n"
+ " BEADS_DB - Path to database file (deprecated, use BEADS_DIR)\n"
+ " BEADS_WORKING_DIR - Working directory for bd commands (default: $PWD or cwd)\n"
+ " BEADS_ACTOR - Actor name for audit trail (default: $USER)\n"
+ " BEADS_NO_AUTO_FLUSH - Disable automatic JSONL sync (default: false)\n"