feat(daemon): event-driven convoy completion check (hq-5kmkl)

Add ConvoyWatcher that monitors bd activity for issue closes and
triggers convoy completion checks immediately rather than waiting
for patrol.

- Watch bd activity --follow --town --json for status=closed events
- Query SQLite for convoys tracking the closed issue
- Trigger gt convoy check when tracked issue closes
- Convoys close within seconds of last issue closing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/george
2026-01-12 18:38:57 -08:00
committed by Steve Yegge
parent e442212c05
commit f79614d764
3 changed files with 349 additions and 6 deletions

View File

@@ -37,12 +37,13 @@ import (
// This is recovery-focused: normal wake is handled by feed subscription (bd activity --follow).
// The daemon is the safety net for dead sessions, GUPP violations, and orphaned work.
type Daemon struct {
config *Config
tmux *tmux.Tmux
logger *log.Logger
ctx context.Context
cancel context.CancelFunc
curator *feed.Curator
config *Config
tmux *tmux.Tmux
logger *log.Logger
ctx context.Context
cancel context.CancelFunc
curator *feed.Curator
convoyWatcher *ConvoyWatcher
// Mass death detection: track recent session deaths
deathsMu sync.Mutex
@@ -143,6 +144,14 @@ func (d *Daemon) Run() error {
d.logger.Println("Feed curator started")
}
// Start convoy watcher for event-driven convoy completion
d.convoyWatcher = NewConvoyWatcher(d.config.TownRoot, d.logger.Printf)
if err := d.convoyWatcher.Start(); err != nil {
d.logger.Printf("Warning: failed to start convoy watcher: %v", err)
} else {
d.logger.Println("Convoy watcher started")
}
// Initial heartbeat
d.heartbeat(state)
@@ -579,6 +588,12 @@ func (d *Daemon) shutdown(state *State) error { //nolint:unparam // error return
d.logger.Println("Feed curator stopped")
}
// Stop convoy watcher
if d.convoyWatcher != nil {
d.convoyWatcher.Stop()
d.logger.Println("Convoy watcher stopped")
}
state.Running = false
if err := SaveState(d.config.TownRoot, state); err != nil {
d.logger.Printf("Warning: failed to save final state: %v", err)