Add subtasks

Change-Id: Ica6afd9eef38bcf29135bf2c8a2e4bf0407ccfa1
diff --git a/server/agent/manager.go b/server/agent/manager.go
index 74e6771..3d8277f 100644
--- a/server/agent/manager.go
+++ b/server/agent/manager.go
@@ -15,19 +15,21 @@
 	"github.com/iomodo/staff/git"
 	"github.com/iomodo/staff/llm"
 	_ "github.com/iomodo/staff/llm/providers" // Auto-register all providers
+	"github.com/iomodo/staff/subtasks"
 	"github.com/iomodo/staff/tm"
 )
 
 // Manager manages multiple AI agents with Git operations and task processing
 type Manager struct {
-	config       *config.Config
-	agents       map[string]*Agent
-	taskManager  tm.TaskManager
-	autoAssigner *assignment.AutoAssigner
-	prProvider   git.PullRequestProvider
-	cloneManager *git.CloneManager
-	isRunning    map[string]bool
-	stopChannels map[string]chan struct{}
+	config          *config.Config
+	agents          map[string]*Agent
+	taskManager     tm.TaskManager
+	autoAssigner    *assignment.AutoAssigner
+	prProvider      git.PullRequestProvider
+	cloneManager    *git.CloneManager
+	subtaskService  *subtasks.SubtaskService
+	isRunning       map[string]bool
+	stopChannels    map[string]chan struct{}
 }
 
 // NewManager creates a new agent manager
@@ -62,6 +64,11 @@
 		return nil, fmt.Errorf("failed to initialize agents: %w", err)
 	}
 
+	// Initialize subtask service after agents are created
+	if err := manager.initializeSubtaskService(); err != nil {
+		return nil, fmt.Errorf("failed to initialize subtask service: %w", err)
+	}
+
 	return manager, nil
 }
 
@@ -77,6 +84,34 @@
 	return nil
 }
 
+// initializeSubtaskService creates the subtask service with available agent roles
+func (m *Manager) initializeSubtaskService() error {
+	// Get agent roles from configuration
+	agentRoles := make([]string, 0, len(m.config.Agents))
+	for _, agentConfig := range m.config.Agents {
+		agentRoles = append(agentRoles, agentConfig.Name)
+	}
+
+	// Use the first agent's LLM provider for subtask analysis
+	if len(m.agents) == 0 {
+		return fmt.Errorf("no agents available for subtask service")
+	}
+
+	var firstAgent *Agent
+	for _, agent := range m.agents {
+		firstAgent = agent
+		break
+	}
+
+	m.subtaskService = subtasks.NewSubtaskService(
+		firstAgent.Provider,
+		m.taskManager,
+		agentRoles,
+	)
+
+	return nil
+}
+
 // createAgent creates a single agent instance
 func (m *Manager) createAgent(agentConfig config.AgentConfig) (*Agent, error) {
 	// Load system prompt
@@ -226,6 +261,29 @@
 		return fmt.Errorf("failed to update task status: %w", err)
 	}
 
+	// Check if this task should generate subtasks
+	if m.shouldGenerateSubtasks(task) {
+		log.Printf("Analyzing task %s for subtask generation", task.ID)
+		if err := m.generateSubtasksForTask(ctx, task); err != nil {
+			log.Printf("Warning: Failed to generate subtasks for task %s: %v", task.ID, err)
+			// Continue with normal processing if subtask generation fails
+		} else {
+			// Task has been converted to subtask management, mark as completed
+			task.Status = tm.StatusCompleted
+			task.Solution = "Task broken down into subtasks. See subtasks PR for details."
+			completedAt := time.Now()
+			task.CompletedAt = &completedAt
+			agent.CurrentTask = nil
+
+			if err := m.taskManager.UpdateTask(task); err != nil {
+				return fmt.Errorf("failed to update task with subtasks: %w", err)
+			}
+
+			log.Printf("Task %s converted to subtasks by agent %s", task.ID, agent.Name)
+			return nil
+		}
+	}
+
 	// Generate solution using LLM
 	solution, err := m.generateSolution(ctx, agent, task)
 	if err != nil {
@@ -553,6 +611,54 @@
 	return status
 }
 
+// shouldGenerateSubtasks determines if a task should be broken down into subtasks
+func (m *Manager) shouldGenerateSubtasks(task *tm.Task) bool {
+	// Don't generate subtasks for subtasks
+	if task.ParentTaskID != "" {
+		return false
+	}
+
+	// Don't generate if already generated
+	if task.SubtasksGenerated {
+		return false
+	}
+
+	// Only generate for high priority tasks or complex descriptions
+	if task.Priority == tm.PriorityHigh || len(task.Description) > 200 {
+		return true
+	}
+
+	return false
+}
+
+// generateSubtasksForTask analyzes a task and creates a PR with proposed subtasks
+func (m *Manager) generateSubtasksForTask(ctx context.Context, task *tm.Task) error {
+	if m.subtaskService == nil {
+		return fmt.Errorf("subtask service not initialized")
+	}
+
+	// Analyze the task for subtasks
+	analysis, err := m.subtaskService.AnalyzeTaskForSubtasks(ctx, task)
+	if err != nil {
+		return fmt.Errorf("failed to analyze task for subtasks: %w", err)
+	}
+
+	// Generate a PR with the subtask proposals
+	prURL, err := m.subtaskService.GenerateSubtaskPR(ctx, analysis)
+	if err != nil {
+		return fmt.Errorf("failed to generate subtask PR: %w", err)
+	}
+
+	// Update the task with subtask information
+	task.SubtasksPRURL = prURL
+	task.SubtasksGenerated = true
+
+	log.Printf("Generated subtask PR for task %s: %s", task.ID, prURL)
+	log.Printf("Proposed %d subtasks for task %s", len(analysis.Subtasks), task.ID)
+
+	return nil
+}
+
 // IsAgentRunning checks if an agent is currently running
 func (m *Manager) IsAgentRunning(agentName string) bool {
 	return m.isRunning[agentName]
@@ -579,5 +685,12 @@
 		log.Printf("Error cleaning up agent clones: %v", err)
 	}
 
+	// Cleanup subtask service
+	if m.subtaskService != nil {
+		if err := m.subtaskService.Close(); err != nil {
+			log.Printf("Error closing subtask service: %v", err)
+		}
+	}
+
 	return nil
 }