diff --git a/internal/router/router_test.go b/internal/router/router_test.go index d055f1c..d5b8cba 100644 --- a/internal/router/router_test.go +++ b/internal/router/router_test.go @@ -35,6 +35,47 @@ func TestClassifyTask(t *testing.T) { } } +func TestClassifyTask_OrchestrationNotFalsePositive(t *testing.T) { + // Words like "coordinator", "pipeline", "dispatch" appear in non-orchestration contexts. + // More specific classifications (debug, review, refactor, explain) must win. + tests := []struct { + prompt string + want TaskType + }{ + {"fix the coordinator bug", TaskDebug}, // "coordinator" contains "coordinate" + {"review the orchestration layer", TaskReview}, // "orchestrat" present but review wins + {"refactor the pipeline dispatch", TaskRefactor}, // "dispatch" present but refactor wins + {"explain how coordination works", TaskExplain}, // "coordinat" present but explain wins + {"debug the dispatch table", TaskDebug}, // "dispatch" present but debug wins + } + for _, tt := range tests { + task := ClassifyTask(tt.prompt) + if task.Type != tt.want { + t.Errorf("ClassifyTask(%q).Type = %s, want %s", tt.prompt, task.Type, tt.want) + } + } +} + +func TestClassifyTask_OrchestrationKeywords(t *testing.T) { + // Explicit orchestration-intent phrases should still classify correctly. + tests := []struct { + prompt string + want TaskType + }{ + {"orchestrate the migration across services", TaskOrchestration}, + {"fan out the work to 5 elfs", TaskOrchestration}, + {"split this into subtasks and run them in parallel", TaskOrchestration}, + {"delegate to worker elfs for parallel processing", TaskOrchestration}, + {"spawn elfs to handle this", TaskOrchestration}, + } + for _, tt := range tests { + task := ClassifyTask(tt.prompt) + if task.Type != tt.want { + t.Errorf("ClassifyTask(%q).Type = %s, want %s", tt.prompt, task.Type, tt.want) + } + } +} + func TestClassifyTask_RequiresTools(t *testing.T) { // Explain tasks don't require tools task := ClassifyTask("explain how generics work") diff --git a/internal/router/task.go b/internal/router/task.go index 2997004..f123d47 100644 --- a/internal/router/task.go +++ b/internal/router/task.go @@ -123,16 +123,14 @@ func ClassifyTask(prompt string) Task { RequiresTools: true, // assume tools needed by default } - // Check for task type keywords (order matters — more specific first) + // Check for task type keywords (order matters — more specific/common first). + // Orchestration is placed late: its keywords ("dispatch", "pipeline", "orchestrat") + // appear as nouns in non-orchestration prompts (e.g. "refactor the pipeline dispatch", + // "review the orchestration layer"). Operational task types must gate first. switch { case containsAny(lower, "security", "vulnerability", "cve", "owasp", "xss", "injection", "audit security"): task.Type = TaskSecurityReview task.Priority = PriorityHigh - case containsAny(lower, "plan", "architect", "design", "strategy", "roadmap"): - task.Type = TaskPlanning - case containsAny(lower, "orchestrat", "coordinate", "dispatch", "pipeline"): - task.Type = TaskOrchestration - task.Priority = PriorityHigh case containsAny(lower, "debug", "fix", "troubleshoot", "not working", "error", "crash", "failing", "bug"): task.Type = TaskDebug case containsAny(lower, "review", "check", "analyze", "audit", "inspect"): @@ -144,6 +142,12 @@ func ClassifyTask(prompt string) Task { case containsAny(lower, "explain", "what is", "how does", "describe", "tell me about"): task.Type = TaskExplain task.RequiresTools = false + case containsAny(lower, "plan", "architect", "design", "strategy", "roadmap"): + task.Type = TaskPlanning + case containsAny(lower, "orchestrat", "coordinate", "dispatch", "pipeline", + "fan out", "subtask", "delegate to", "spawn elf"): + task.Type = TaskOrchestration + task.Priority = PriorityHigh case containsAny(lower, "create", "implement", "build", "add", "write", "generate", "make"): task.Type = TaskGeneration case containsAny(lower, "scaffold", "boilerplate", "template", "stub", "skeleton"):