feat(daemon): add --local flag for git-free daemon operation (#433)

* feat(daemon): add --local flag for git-free operation

Add --local mode to the daemon that allows it to run without a git
repository. This decouples the daemon's core functionality (auto-flush
to JSONL, auto-import from JSONL) from git synchronization.

Changes:
- Add --local flag to daemon command
- Skip git repo check when --local is set
- Add validation that --auto-commit and --auto-push cannot be used with --local
- Create local-only sync functions that skip git operations:
  - createLocalSyncFunc: export-only for polling mode
  - createLocalExportFunc: export without git commit/push
  - createLocalAutoImportFunc: import without git pull
- Update startup message to indicate LOCAL mode
- Update event loop to use local functions when in local mode

This enables use cases like:
- Single-machine issue tracking without git
- Auto-flush to JSONL for backup purposes
- Running daemon in environments without git access

Multi-machine sync still requires git (as expected).

* fix(daemon): skip fingerprint validation in local mode

validateDatabaseFingerprint() calls beads.ComputeRepoID() which
executes git commands. This fails in non-git directories even
with --local flag.

Skip fingerprint validation entirely when running in local mode
since there's no git repository to validate against.

* test(daemon): add comprehensive test coverage for --local mode

Add tests for:
- Flag validation (--local incompatible with --auto-commit/--auto-push)
- Git check skip logic in local mode
- createLocalSyncFunc, createLocalExportFunc, createLocalAutoImportFunc
- Fingerprint validation skip in local mode
- Full integration test in non-git directory
- Export/import round-trip test

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yaakov Nemoy
2025-12-01 17:37:56 -08:00
committed by GitHub
parent 34799a001d
commit f761ba1f3a
4 changed files with 762 additions and 15 deletions

View File

@@ -276,7 +276,7 @@ func stopDaemon(pidFile string) {
}
// startDaemon starts the daemon in background
func startDaemon(interval time.Duration, autoCommit, autoPush bool, logFile, pidFile string) {
func startDaemon(interval time.Duration, autoCommit, autoPush, localMode bool, logFile, pidFile string) {
logPath, err := getLogFilePath(logFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -284,7 +284,7 @@ func startDaemon(interval time.Duration, autoCommit, autoPush bool, logFile, pid
}
if os.Getenv("BD_DAEMON_FOREGROUND") == "1" {
runDaemonLoop(interval, autoCommit, autoPush, logPath, pidFile)
runDaemonLoop(interval, autoCommit, autoPush, localMode, logPath, pidFile)
return
}
@@ -303,6 +303,9 @@ func startDaemon(interval time.Duration, autoCommit, autoPush bool, logFile, pid
if autoPush {
args = append(args, "--auto-push")
}
if localMode {
args = append(args, "--local")
}
if logFile != "" {
args = append(args, "--log", logFile)
}