Merge remote changes

This commit is contained in:
Steve Yegge
2025-10-31 20:28:58 -07:00
10 changed files with 104 additions and 93 deletions

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
"name": "beads", "name": "beads",
"source": "./", "source": "./",
"description": "AI-supervised issue tracker for coding workflows", "description": "AI-supervised issue tracker for coding workflows",
"version": "0.20.1" "version": "0.21.0"
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "beads", "name": "beads",
"description": "AI-supervised issue tracker for coding workflows. Manage tasks, discover work, and maintain context with simple CLI commands.", "description": "AI-supervised issue tracker for coding workflows. Manage tasks, discover work, and maintain context with simple CLI commands.",
"version": "0.20.1", "version": "0.21.0",
"author": { "author": {
"name": "Steve Yegge", "name": "Steve Yegge",
"url": "https://github.com/steveyegge" "url": "https://github.com/steveyegge"

1
.gitignore vendored
View File

@@ -56,3 +56,4 @@ dist/
# Git worktrees # Git worktrees
.worktrees/ .worktrees/
.beads/pollution-backup.jsonl

View File

@@ -1112,14 +1112,36 @@ func createAutoImportFunc(ctx context.Context, store storage.Storage, log daemon
log.log("Removed stale lock (%s), proceeding", holder) log.log("Removed stale lock (%s), proceeding", holder)
} }
// Pull from git // Check JSONL modification time to avoid redundant imports
if err := gitPull(importCtx); err != nil { // (e.g., from self-triggered file watcher events after our own export)
log.log("Pull failed: %v", err) jsonlInfo, err := os.Stat(jsonlPath)
return if err != nil {
} log.log("Failed to stat JSONL: %v", err)
log.log("Pulled from remote") return
}
// Count issues before import // Get database modification time
dbPath := filepath.Join(beadsDir, "beads.db")
dbInfo, err := os.Stat(dbPath)
if err != nil {
log.log("Failed to stat database: %v", err)
return
}
// Skip if JSONL is older than database (nothing new to import)
if !jsonlInfo.ModTime().After(dbInfo.ModTime()) {
log.log("Skipping import: JSONL not newer than database")
return
}
// Pull from git
if err := gitPull(importCtx); err != nil {
log.log("Pull failed: %v", err)
return
}
log.log("Pulled from remote")
// Count issues before import
beforeCount, err := countDBIssues(importCtx, store) beforeCount, err := countDBIssues(importCtx, store)
if err != nil { if err != nil {
log.log("Failed to count issues before import: %v", err) log.log("Failed to count issues before import: %v", err)
@@ -1470,7 +1492,7 @@ func runDaemonLoop(interval time.Duration, autoCommit, autoPush bool, logPath, p
// Choose event loop based on BEADS_DAEMON_MODE // Choose event loop based on BEADS_DAEMON_MODE
daemonMode := os.Getenv("BEADS_DAEMON_MODE") daemonMode := os.Getenv("BEADS_DAEMON_MODE")
if daemonMode == "" { if daemonMode == "" {
daemonMode = "poll" // Default to polling for Phase 1 daemonMode = "events" // Default to event-driven mode (production-ready as of v0.21.0)
} }
switch daemonMode { switch daemonMode {

View File

@@ -47,9 +47,13 @@ func runEventDrivenLoop(
watcher, err := NewFileWatcher(jsonlPath, func() { watcher, err := NewFileWatcher(jsonlPath, func() {
importDebouncer.Trigger() importDebouncer.Trigger()
}) })
var fallbackTicker *time.Ticker
if err != nil { if err != nil {
log.log("WARNING: File watcher unavailable (%v), mutations will trigger export only", err) log.log("WARNING: File watcher unavailable (%v), using 60s polling fallback", err)
watcher = nil watcher = nil
// Fallback ticker to check for remote changes when watcher unavailable
fallbackTicker = time.NewTicker(60 * time.Second)
defer fallbackTicker.Stop()
} else { } else {
watcher.Start(ctx, log) watcher.Start(ctx, log)
defer watcher.Close() defer watcher.Close()
@@ -97,6 +101,16 @@ func runEventDrivenLoop(
// Periodic health validation (not sync) // Periodic health validation (not sync)
checkDaemonHealth(ctx, store, log) checkDaemonHealth(ctx, store, log)
case <-func() <-chan time.Time {
if fallbackTicker != nil {
return fallbackTicker.C
}
// Never fire if watcher is available
return make(chan time.Time)
}():
log.log("Fallback ticker: checking for remote changes")
importDebouncer.Trigger()
case sig := <-sigChan: case sig := <-sigChan:
if isReloadSignal(sig) { if isReloadSignal(sig) {
log.log("Received reload signal, ignoring") log.log("Received reload signal, ignoring")
@@ -120,12 +134,15 @@ func runEventDrivenLoop(
return return
case err := <-serverErrChan: case err := <-serverErrChan:
log.log("RPC server failed: %v", err) log.log("RPC server failed: %v", err)
cancel() cancel()
if watcher != nil { if watcher != nil {
watcher.Close() watcher.Close()
} }
return if stopErr := server.Stop(); stopErr != nil {
log.log("Error stopping server: %v", stopErr)
}
return
} }
} }
} }

View File

@@ -45,12 +45,20 @@ Example:
// Get all issues // Get all issues
allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{}) allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching issues: %v\n", err) fmt.Fprintf(os.Stderr, "Error fetching issues: %v\n", err)
os.Exit(1) os.Exit(1)
} }
// Find duplicates // Filter out closed issues - they're done, no point detecting duplicates
duplicateGroups := findDuplicateGroups(allIssues) openIssues := make([]*types.Issue, 0, len(allIssues))
for _, issue := range allIssues {
if issue.Status != types.StatusClosed {
openIssues = append(openIssues, issue)
}
}
// Find duplicates (only among open issues)
duplicateGroups := findDuplicateGroups(openIssues)
if len(duplicateGroups) == 0 { if len(duplicateGroups) == 0 {
if !jsonOutput { if !jsonOutput {

View File

@@ -11,7 +11,7 @@ import (
var ( var (
// Version is the current version of bd (overridden by ldflags at build time) // Version is the current version of bd (overridden by ldflags at build time)
Version = "0.20.1" Version = "0.21.0"
// Build can be set via ldflags at compile time // Build can be set via ldflags at compile time
Build = "dev" Build = "dev"
) )

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "beads-mcp" name = "beads-mcp"
version = "0.20.2" version = "0.21.0"
description = "MCP server for beads issue tracker." description = "MCP server for beads issue tracker."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@@ -4,4 +4,4 @@ This package provides an MCP (Model Context Protocol) server that exposes
beads (bd) issue tracker functionality to MCP Clients. beads (bd) issue tracker functionality to MCP Clients.
""" """
__version__ = "0.20.2" __version__ = "0.21.0"