diff --git a/cmd/antidriftd/main.go b/cmd/antidriftd/main.go index 340b462..7a81b04 100644 --- a/cmd/antidriftd/main.go +++ b/cmd/antidriftd/main.go @@ -15,6 +15,7 @@ import ( "antidrift/internal/session" "antidrift/internal/statusfile" "antidrift/internal/store" + "antidrift/internal/tasks" "antidrift/internal/web" ) @@ -46,6 +47,13 @@ func main() { log.Printf("ai: %s backend (coach + drift judge + nudge)", backend.Name()) } + // Wire the Tasks port: Amazing Marvin via ampy's `am` CLI. The command is + // overridable with ANTIDRIFT_MARVIN_CMD (e.g. "uv run am" or an absolute + // path). A missing or failing CLI degrades to no tasks panel at fetch time — + // planning still works. + ctrl.SetTasks(tasks.NewMarvin(os.Getenv("ANTIDRIFT_MARVIN_CMD"))) + log.Printf("tasks: marvin adapter (am)") + // Mirror runtime status to ~/.antidrift_status for a window-manager bar. // A misconfigured home dir degrades to no status file, not a failed start. if statusPath, err := statusfile.DefaultPath(); err != nil { diff --git a/internal/web/web_test.go b/internal/web/web_test.go index 488babb..da9049a 100644 --- a/internal/web/web_test.go +++ b/internal/web/web_test.go @@ -13,6 +13,7 @@ import ( "antidrift/internal/domain" "antidrift/internal/evidence" "antidrift/internal/session" + "antidrift/internal/tasks" "github.com/gin-gonic/gin" ) @@ -230,6 +231,37 @@ func TestServesStaticAssets(t *testing.T) { } } +type stubProvider struct { + list []tasks.Task +} + +func (s stubProvider) Today(ctx context.Context) ([]tasks.Task, error) { + return s.list, nil +} + +func TestPlanningStatePayloadCarriesTasks(t *testing.T) { + s := newTestServer(t) + s.ctrl.SetTasks(stubProvider{list: []tasks.Task{{ID: "a", Title: "Write the spec", Day: "2026-05-31"}}}) + r := s.Router() + if w := post(t, r, "/planning", ""); w.Code != http.StatusOK { + t.Fatalf("/planning code %d", w.Code) + } + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if tv := s.ctrl.State().Tasks; tv != nil && tv.Status == "ready" { + break + } + time.Sleep(5 * time.Millisecond) + } + js := s.stateJSON() + if !strings.Contains(js, `"tasks"`) { + t.Fatalf("planning payload missing tasks object: %s", js) + } + if !strings.Contains(js, `"title":"Write the spec"`) { + t.Fatalf("planning payload missing task title: %s", js) + } +} + func TestCommitmentCarriesAllowedClassesAndRoutesWork(t *testing.T) { s := newTestServer(t) r := s.Router()