fix(ci): resolve lint errors and test failures
- Fix errcheck: handle watcher.Close() and Set() return values - Fix unparam: remove always-nil error from NewActivityWatcher - Fix unparam: remove unused sinceTime param, delete dead code - Fix version mismatch: update MCP __init__.py to 0.48.0 - Fix routing tests: change CWD so routing can find town root Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
8807a171d3
commit
83e3c75635
@@ -185,13 +185,8 @@ func runActivityFollow(sinceTime time.Time) {
|
|||||||
// Create filesystem watcher for near-instant wake-up
|
// Create filesystem watcher for near-instant wake-up
|
||||||
// Falls back to polling internally if fsnotify fails
|
// Falls back to polling internally if fsnotify fails
|
||||||
beadsDir := filepath.Dir(dbPath)
|
beadsDir := filepath.Dir(dbPath)
|
||||||
watcher, err := NewActivityWatcher(beadsDir, activityInterval)
|
watcher := NewActivityWatcher(beadsDir, activityInterval)
|
||||||
if err != nil {
|
defer func() { _ = watcher.Close() }()
|
||||||
// Watcher creation failed entirely - fall back to legacy polling
|
|
||||||
runActivityFollowPolling(sinceTime, lastPoll)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
|
|
||||||
// Start watching
|
// Start watching
|
||||||
watcher.Start(rootCtx)
|
watcher.Start(rootCtx)
|
||||||
@@ -261,69 +256,6 @@ func runActivityFollow(sinceTime time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runActivityFollowPolling is the legacy polling-based follow mode.
|
|
||||||
// Used as fallback when ActivityWatcher cannot be created.
|
|
||||||
func runActivityFollowPolling(sinceTime time.Time, lastPoll time.Time) {
|
|
||||||
ticker := time.NewTicker(activityInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
consecutiveFailures := 0
|
|
||||||
const failureWarningThreshold = 5
|
|
||||||
lastWarningTime := time.Time{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-rootCtx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
newEvents, err := fetchMutations(lastPoll)
|
|
||||||
if err != nil {
|
|
||||||
consecutiveFailures++
|
|
||||||
if consecutiveFailures >= failureWarningThreshold {
|
|
||||||
if time.Since(lastWarningTime) >= 30*time.Second {
|
|
||||||
if jsonOutput {
|
|
||||||
errorEvent := map[string]interface{}{
|
|
||||||
"type": "error",
|
|
||||||
"message": fmt.Sprintf("daemon unreachable (%d failures)", consecutiveFailures),
|
|
||||||
"timestamp": time.Now().Format(time.RFC3339),
|
|
||||||
}
|
|
||||||
data, _ := json.Marshal(errorEvent)
|
|
||||||
fmt.Fprintln(os.Stderr, string(data))
|
|
||||||
} else {
|
|
||||||
timestamp := time.Now().Format("15:04:05")
|
|
||||||
fmt.Fprintf(os.Stderr, "[%s] %s daemon unreachable (%d consecutive failures)\n",
|
|
||||||
timestamp, ui.RenderWarn("!"), consecutiveFailures)
|
|
||||||
}
|
|
||||||
lastWarningTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if consecutiveFailures > 0 {
|
|
||||||
if consecutiveFailures >= failureWarningThreshold && !jsonOutput {
|
|
||||||
timestamp := time.Now().Format("15:04:05")
|
|
||||||
fmt.Fprintf(os.Stderr, "[%s] %s daemon reconnected\n", timestamp, ui.RenderPass("✓"))
|
|
||||||
}
|
|
||||||
consecutiveFailures = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
newEvents = filterEvents(newEvents)
|
|
||||||
for _, e := range newEvents {
|
|
||||||
if jsonOutput {
|
|
||||||
data, _ := json.Marshal(formatEvent(e))
|
|
||||||
fmt.Println(string(data))
|
|
||||||
} else {
|
|
||||||
printEvent(e)
|
|
||||||
}
|
|
||||||
if e.Timestamp.After(lastPoll) {
|
|
||||||
lastPoll = e.Timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchMutations retrieves mutations from the daemon
|
// fetchMutations retrieves mutations from the daemon
|
||||||
func fetchMutations(since time.Time) ([]rpc.MutationEvent, error) {
|
func fetchMutations(since time.Time) ([]rpc.MutationEvent, error) {
|
||||||
var sinceMillis int64
|
var sinceMillis int64
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type ActivityWatcher struct {
|
|||||||
// Watches the dolt noms directory for commits, falling back to polling if fsnotify fails.
|
// Watches the dolt noms directory for commits, falling back to polling if fsnotify fails.
|
||||||
// The beadsDir should be the .beads directory path.
|
// The beadsDir should be the .beads directory path.
|
||||||
// The pollInterval is used for polling fallback mode.
|
// The pollInterval is used for polling fallback mode.
|
||||||
func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityWatcher, error) {
|
func NewActivityWatcher(beadsDir string, pollInterval time.Duration) *ActivityWatcher {
|
||||||
aw := &ActivityWatcher{
|
aw := &ActivityWatcher{
|
||||||
pollInterval: pollInterval,
|
pollInterval: pollInterval,
|
||||||
events: make(chan struct{}, 1), // Buffered to avoid blocking
|
events: make(chan struct{}, 1), // Buffered to avoid blocking
|
||||||
@@ -74,7 +74,7 @@ func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityW
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Fall back to polling mode
|
// Fall back to polling mode
|
||||||
aw.pollingMode = true
|
aw.pollingMode = true
|
||||||
return aw, nil
|
return aw
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add watches for each path
|
// Add watches for each path
|
||||||
@@ -91,11 +91,11 @@ func NewActivityWatcher(beadsDir string, pollInterval time.Duration) (*ActivityW
|
|||||||
// No paths could be watched, fall back to polling
|
// No paths could be watched, fall back to polling
|
||||||
_ = watcher.Close()
|
_ = watcher.Close()
|
||||||
aw.pollingMode = true
|
aw.pollingMode = true
|
||||||
return aw, nil
|
return aw
|
||||||
}
|
}
|
||||||
|
|
||||||
aw.watcher = watcher
|
aw.watcher = watcher
|
||||||
return aw, nil
|
return aw
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events returns the channel that receives wake-up signals when changes are detected.
|
// Events returns the channel that receives wake-up signals when changes are detected.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
// TestAgentStateWithRouting tests that bd agent state respects routes.jsonl
|
// TestAgentStateWithRouting tests that bd agent state respects routes.jsonl
|
||||||
// for cross-repo agent resolution. This is a regression test for the bug where
|
// for cross-repo agent resolution. This is a regression test for the bug where
|
||||||
// bd agent state failed to find agents in routed databases while bd show worked.
|
// bd agent state failed to find agents in routed databases while bd show worked.
|
||||||
|
//
|
||||||
|
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
|
||||||
func TestAgentStateWithRouting(t *testing.T) {
|
func TestAgentStateWithRouting(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -73,6 +75,16 @@ func TestAgentStateWithRouting(t *testing.T) {
|
|||||||
dbPath = townDBPath
|
dbPath = townDBPath
|
||||||
t.Cleanup(func() { dbPath = oldDbPath })
|
t.Cleanup(func() { dbPath = oldDbPath })
|
||||||
|
|
||||||
|
// Change to tmpDir so routing can find town root via CWD
|
||||||
|
oldWd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||||
|
|
||||||
// Test the routed resolution
|
// Test the routed resolution
|
||||||
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-testrig-polecat-test")
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-testrig-polecat-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,6 +127,8 @@ func TestNeedsRoutingFunction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestAgentHeartbeatWithRouting tests that bd agent heartbeat respects routes.jsonl
|
// TestAgentHeartbeatWithRouting tests that bd agent heartbeat respects routes.jsonl
|
||||||
|
//
|
||||||
|
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
|
||||||
func TestAgentHeartbeatWithRouting(t *testing.T) {
|
func TestAgentHeartbeatWithRouting(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -167,6 +181,16 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
|
|||||||
dbPath = townDBPath
|
dbPath = townDBPath
|
||||||
t.Cleanup(func() { dbPath = oldDbPath })
|
t.Cleanup(func() { dbPath = oldDbPath })
|
||||||
|
|
||||||
|
// Change to tmpDir so routing can find town root via CWD
|
||||||
|
oldWd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||||
|
|
||||||
// Test that we can resolve the agent from the town directory
|
// Test that we can resolve the agent from the town directory
|
||||||
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-test-witness")
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-test-witness")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -189,6 +213,8 @@ func TestAgentHeartbeatWithRouting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestAgentShowWithRouting tests that bd agent show respects routes.jsonl
|
// TestAgentShowWithRouting tests that bd agent show respects routes.jsonl
|
||||||
|
//
|
||||||
|
// NOTE: This test uses os.Chdir and cannot run in parallel with other tests.
|
||||||
func TestAgentShowWithRouting(t *testing.T) {
|
func TestAgentShowWithRouting(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -241,6 +267,16 @@ func TestAgentShowWithRouting(t *testing.T) {
|
|||||||
dbPath = townDBPath
|
dbPath = townDBPath
|
||||||
t.Cleanup(func() { dbPath = oldDbPath })
|
t.Cleanup(func() { dbPath = oldDbPath })
|
||||||
|
|
||||||
|
// Change to tmpDir so routing can find town root via CWD
|
||||||
|
oldWd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||||
|
|
||||||
// Test that we can resolve the agent from the town directory
|
// Test that we can resolve the agent from the town directory
|
||||||
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-myrig-crew-alice")
|
result, err := resolveAndGetIssueWithRouting(ctx, townStore, "gt-myrig-crew-alice")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ Examples:
|
|||||||
parentID := args[0]
|
parentID := args[0]
|
||||||
|
|
||||||
// Set the parent flag on listCmd, run it, then reset
|
// Set the parent flag on listCmd, run it, then reset
|
||||||
listCmd.Flags().Set("parent", parentID)
|
_ = listCmd.Flags().Set("parent", parentID)
|
||||||
defer listCmd.Flags().Set("parent", "")
|
defer func() { _ = listCmd.Flags().Set("parent", "") }()
|
||||||
listCmd.Run(listCmd, []string{})
|
listCmd.Run(listCmd, []string{})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.47.2"
|
__version__ = "0.48.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user