PR complete a task

Change-Id: Icb3f24db0ccd2914a528370d96b8c21168b24e98
diff --git a/server/agent/manager.go b/server/agent/manager.go
index ab2ce72..016f5ac 100644
--- a/server/agent/manager.go
+++ b/server/agent/manager.go
@@ -149,6 +149,32 @@
 	return m.isRunning[agentName]
 }
 
+// CompleteTaskForAgent finds the agent performing a task and resets its state
+func (m *Manager) CompleteTaskForAgent(taskID string) error {
+	// Get the task to find the assignee
+	task, err := m.taskManager.GetTask(taskID)
+	if err != nil {
+		return fmt.Errorf("failed to get task %s: %w", taskID, err)
+	}
+
+	// Find the agent assigned to this task
+	agent, exists := m.agents[task.Assignee]
+	if !exists {
+		return fmt.Errorf("agent %s not found for task %s", task.Assignee, taskID)
+	}
+
+	// Reset the agent's current task and running state
+	agent.CurrentTask = nil
+	agent.IsRunning = false
+	m.isRunning[agent.Name] = false
+
+	m.logger.Info("Completed task for agent",
+		slog.String("task_id", taskID),
+		slog.String("agent", agent.Name))
+
+	return nil
+}
+
 // Close shuts down the agent manager
 func (m *Manager) Close() error {
 	// Stop all running agents
diff --git a/server/app/proposal.go b/server/app/proposal.go
index 8feaded..7858d2b 100644
--- a/server/app/proposal.go
+++ b/server/app/proposal.go
@@ -28,6 +28,14 @@
 		return fmt.Errorf("failed to process webhook: %w", err)
 	}
 
+	// Complete the task for the agent that was performing it
+	if err := a.manager.CompleteTaskForAgent(taskID); err != nil {
+		a.logger.Warn("Failed to complete task for agent",
+			slog.String("task_id", taskID),
+			slog.String("error", err.Error()))
+		// Don't fail the webhook if agent completion fails
+	}
+
 	a.logger.Info("Proposal approved via webhook",
 		slog.String("task_id", taskID),
 		slog.String("repository", fmt.Sprintf("%s/%s", a.config.GitHub.Owner, a.config.GitHub.Repo)),
diff --git a/server/git/webhook.go b/server/git/webhook.go
index df12492..285ea28 100644
--- a/server/git/webhook.go
+++ b/server/git/webhook.go
@@ -281,14 +281,14 @@
 		webhook.PullRequest.Head != nil
 }
 
-// ExtractTaskID extracts task ID from branch name like "solution/{task-id}" or "subtasks/{task-id}"
+// ExtractTaskID extracts task ID from branch name like "solution/[task-id]-title" or "subtasks/[task-id]-title"
 func ExtractTaskID(branchName string) (string, error) {
-	// Match patterns like "solution/task-123", "subtasks/task-456", etc.
-	re := regexp.MustCompile(`^(?:solution|subtasks)/(.+)$`)
+	// Match patterns like "solution/[task-123]-title", "subtasks/[task-456]-description", etc.
+	re := regexp.MustCompile(`^(?:solution|subtasks)/\[([^\]]+)\]-.+$`)
 	matches := re.FindStringSubmatch(branchName)
 
 	if len(matches) != 2 {
-		return "", fmt.Errorf("branch name '%s' does not match expected pattern (solution/{task-id} or subtasks/{task-id})", branchName)
+		return "", fmt.Errorf("branch name '%s' does not match expected pattern (solution/[task-id]-title or subtasks/[task-id]-title)", branchName)
 	}
 
 	return matches[1], nil
diff --git a/server/tm/git_tm/git_task_manager.go b/server/tm/git_tm/git_task_manager.go
index a49a866..f7d7a07 100644
--- a/server/tm/git_tm/git_task_manager.go
+++ b/server/tm/git_tm/git_task_manager.go
@@ -1103,7 +1103,7 @@
 		cleanTitle = cleanTitle[:40]
 	}
 
-	return fmt.Sprintf("%s/%s-%s", prefix, task.ID, cleanTitle)
+	return fmt.Sprintf("%s/[%s]-%s", prefix, task.ID, cleanTitle)
 }
 
 // buildSolutionPRDescription creates PR description from template