Refactor everything

Change-Id: Ic3a37c38cfecba943c91f6ae545ce1c5b551c0d5
diff --git a/server/tm/git_tm/git_task_manager.go b/server/tm/git_tm/git_task_manager.go
index 515fa51..cca9da8 100644
--- a/server/tm/git_tm/git_task_manager.go
+++ b/server/tm/git_tm/git_task_manager.go
@@ -5,12 +5,14 @@
 	"fmt"
 	"log/slog"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"sort"
 	"strings"
 	"time"
 
 	"github.com/google/uuid"
+	"github.com/iomodo/staff/config"
 	"github.com/iomodo/staff/git"
 	"github.com/iomodo/staff/tm"
 	"gopkg.in/yaml.v3"
@@ -29,68 +31,23 @@
 	TaskIDPrefix = "task-"
 )
 
-// UserService defines interface for user-related operations
-type UserService interface {
-	GetUserName(userID string) (string, error)
-}
-
-// DefaultUserService provides a simple implementation that uses userID as name
-type DefaultUserService struct{}
-
-func (dus *DefaultUserService) GetUserName(userID string) (string, error) {
-	// For now, just return the userID as the name
-	// This can be enhanced to lookup from a proper user service
-	return userID, nil
-}
-
 // GitTaskManager implements TaskManager interface using git as the source of truth
 type GitTaskManager struct {
-	git         git.GitInterface
-	repoPath    string
-	tasksDir    string
-	logger      *slog.Logger
-	userService UserService
+	git      git.GitInterface
+	repoPath string
+	tasksDir string
+	config   *config.Config
+	logger   *slog.Logger
 }
 
 // NewGitTaskManager creates a new GitTaskManager instance
-func NewGitTaskManager(git git.GitInterface, repoPath string, logger *slog.Logger) *GitTaskManager {
+func NewGitTaskManager(gitInter git.GitInterface, cfg *config.Config, logger *slog.Logger) *GitTaskManager {
 	return &GitTaskManager{
-		git:         git,
-		repoPath:    repoPath,
-		tasksDir:    filepath.Join(repoPath, "tasks"),
-		logger:      logger,
-		userService: &DefaultUserService{},
-	}
-}
-
-// NewGitTaskManagerWithLogger creates a new GitTaskManager instance with a custom logger
-func NewGitTaskManagerWithLogger(git git.GitInterface, repoPath string, logger *slog.Logger) *GitTaskManager {
-	if logger == nil {
-		logger = slog.Default()
-	}
-	return &GitTaskManager{
-		git:         git,
-		repoPath:    repoPath,
-		tasksDir:    filepath.Join(repoPath, "tasks"),
-		logger:      logger,
-		userService: &DefaultUserService{},
-	}
-}
-
-// NewGitTaskManagerWithUserService creates a new GitTaskManager with custom user service
-func NewGitTaskManagerWithUserService(git git.GitInterface, repoPath string, logger *slog.Logger, userService UserService) *GitTaskManager {
-	if logger == nil {
-		logger = slog.Default()
-	}
-	if userService == nil {
-		userService = &DefaultUserService{}
-	}
-	return &GitTaskManager{
-		git:         git,
-		repoPath:    repoPath,
-		tasksDir:    filepath.Join(repoPath, "tasks"),
-		logger:      logger,
-		userService: userService,
+		git:      gitInter,
+		repoPath: cfg.Tasks.StoragePath,
+		tasksDir: filepath.Join(cfg.Tasks.StoragePath, "tasks"),
+		config:   cfg,
+		logger:   logger,
 	}
 }
 
@@ -320,12 +277,7 @@
 	taskID := gtm.generateTaskID()
 	now := time.Now()
 
-	// Get owner name from user service
-	ownerName, err := gtm.userService.GetUserName(req.OwnerID)
-	if err != nil {
-		gtm.logger.Warn("Failed to get owner name, using ID", slog.String("ownerID", req.OwnerID), slog.String("error", err.Error()))
-		ownerName = req.OwnerID
-	}
+	ownerName := (req.OwnerID) //TODO: Get owner name from user service
 
 	// Create task
 	task := &tm.Task{
@@ -581,5 +533,600 @@
 	return gtm.ListTasks(ctx, filter, page, pageSize)
 }
 
+// GenerateSubtaskPR creates a PR with the proposed subtasks
+func (gtm *GitTaskManager) ProposeSubTasks(ctx context.Context, task *tm.Task, analysis *tm.SubtaskAnalysis) (string, error) {
+	branchName := generateBranchName("subtasks", task)
+	gtm.logger.Info("Creating subtask PR", slog.String("branch", branchName))
+
+	// Create Git branch and commit subtask proposal
+	if err := gtm.createSubtaskBranch(ctx, analysis, branchName); err != nil {
+		return "", fmt.Errorf("failed to create subtask branch: %w", err)
+	}
+
+	// Generate PR content
+	prContent := gtm.generateSubtaskPRContent(analysis)
+	title := fmt.Sprintf("Subtask Proposal: %s", analysis.ParentTaskID)
+
+	// Validate PR content
+	if title == "" {
+		return "", fmt.Errorf("PR title cannot be empty")
+	}
+	if prContent == "" {
+		return "", fmt.Errorf("PR description cannot be empty")
+	}
+
+	// Determine base branch (try main first, fallback to master)
+	baseBranch := gtm.determineBaseBranch(ctx)
+	gtm.logger.Info("Using base branch", slog.String("base_branch", baseBranch))
+
+	// Create the pull request
+	options := git.PullRequestOptions{
+		Title:       title,
+		Description: prContent,
+		HeadBranch:  branchName,
+		BaseBranch:  baseBranch,
+		Labels:      []string{"subtasks", "proposal", "ai-generated"},
+		Draft:       false,
+	}
+
+	gtm.logger.Info("Creating PR with options",
+		slog.String("title", options.Title),
+		slog.String("head_branch", options.HeadBranch),
+		slog.String("base_branch", options.BaseBranch))
+
+	pr, err := gtm.git.CreatePullRequest(ctx, options)
+	if err != nil {
+		return "", fmt.Errorf("failed to create PR: %w", err)
+	}
+
+	gtm.logger.Info("Generated subtask proposal PR", slog.String("pr_url", pr.URL))
+
+	return pr.URL, nil
+}
+
+func (gtm *GitTaskManager) ProposeSolution(ctx context.Context, task *tm.Task, solution, agentName string) (string, error) {
+	branchName := generateBranchName("solution", task)
+	gtm.logger.Info("Creating solution PR", slog.String("branch", branchName))
+
+	if err := gtm.createSolutionBranch(ctx, task, solution, branchName, agentName); err != nil {
+		return "", fmt.Errorf("failed to create solution branch: %w", err)
+	}
+	// Build PR description from template
+	description := buildSolutionPRDescription(task, solution, gtm.config.Git.PRTemplate, agentName)
+
+	options := git.PullRequestOptions{
+		Title:       fmt.Sprintf("Task %s: %s", task.ID, task.Title),
+		Description: description,
+		HeadBranch:  branchName,
+		BaseBranch:  "main",
+		Labels:      []string{"ai-generated"},
+		Draft:       false,
+	}
+
+	pr, err := gtm.git.CreatePullRequest(ctx, options)
+	if err != nil {
+		return "", fmt.Errorf("failed to create PR: %w", err)
+	}
+	gtm.logger.Info("Generated subtask proposal PR", slog.String("pr_url", pr.URL))
+	return pr.URL, nil
+}
+
+// createSubtaskBranch creates a Git branch with subtask proposal content
+func (gtm *GitTaskManager) createSubtaskBranch(ctx context.Context, analysis *tm.SubtaskAnalysis, branchName string) error {
+	clonePath, err := gtm.git.GetAgentClonePath("subtask-service")
+	if err != nil {
+		return fmt.Errorf("failed to get clone path: %w", err)
+	}
+
+	// All Git operations use the clone directory
+	gitCmd := func(args ...string) *exec.Cmd {
+		return exec.CommandContext(ctx, "git", append([]string{"-C", clonePath}, args...)...)
+	}
+
+	// Ensure we're on main branch before creating new branch
+	cmd := gitCmd("checkout", "main")
+	if err := cmd.Run(); err != nil {
+		// Try master branch if main doesn't exist
+		cmd = gitCmd("checkout", "master")
+		if err := cmd.Run(); err != nil {
+			return fmt.Errorf("failed to checkout main/master branch: %w", err)
+		}
+	}
+
+	// Pull latest changes
+	cmd = gitCmd("pull", "origin")
+	if err := cmd.Run(); err != nil {
+		gtm.logger.Warn("Failed to pull latest changes", slog.String("error", err.Error()))
+	}
+
+	// Delete branch if it exists (cleanup from previous attempts)
+	cmd = gitCmd("branch", "-D", branchName)
+	_ = cmd.Run() // Ignore error if branch doesn't exist
+
+	// Also delete remote tracking branch if it exists
+	cmd = gitCmd("push", "origin", "--delete", branchName)
+	_ = cmd.Run() // Ignore error if branch doesn't exist
+
+	// Create and checkout new branch
+	cmd = gitCmd("checkout", "-b", branchName)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to create branch: %w", err)
+	}
+
+	// Create individual task files for each subtask
+	tasksDir := filepath.Join(clonePath, "operations", "tasks")
+	if err := os.MkdirAll(tasksDir, 0755); err != nil {
+		return fmt.Errorf("failed to create tasks directory: %w", err)
+	}
+
+	var stagedFiles []string
+
+	// Update parent task to mark as completed
+	parentTaskFile := filepath.Join(tasksDir, fmt.Sprintf("%s.md", analysis.ParentTaskID))
+	if err := gtm.updateParentTaskAsCompleted(parentTaskFile, analysis); err != nil {
+		return fmt.Errorf("failed to update parent task: %w", err)
+	}
+
+	// Track parent task file for staging
+	parentRelativeFile := filepath.Join("operations", "tasks", fmt.Sprintf("%s.md", analysis.ParentTaskID))
+	stagedFiles = append(stagedFiles, parentRelativeFile)
+	gtm.logger.Info("Updated parent task file", slog.String("file", parentRelativeFile))
+
+	// Create a file for each subtask
+	for i, subtask := range analysis.Subtasks {
+		taskID := fmt.Sprintf("%s-subtask-%d", analysis.ParentTaskID, i+1)
+		taskFile := filepath.Join(tasksDir, fmt.Sprintf("%s.md", taskID))
+		taskContent := gtm.generateSubtaskFile(subtask, taskID, analysis.ParentTaskID)
+
+		if err := os.WriteFile(taskFile, []byte(taskContent), 0644); err != nil {
+			return fmt.Errorf("failed to write subtask file %s: %w", taskID, err)
+		}
+
+		// Track file for staging
+		relativeFile := filepath.Join("operations", "tasks", fmt.Sprintf("%s.md", taskID))
+		stagedFiles = append(stagedFiles, relativeFile)
+		gtm.logger.Info("Created subtask file", slog.String("file", relativeFile))
+	}
+
+	// Stage all subtask files
+	for _, file := range stagedFiles {
+		cmd = gitCmd("add", file)
+		if err := cmd.Run(); err != nil {
+			return fmt.Errorf("failed to stage file %s: %w", file, err)
+		}
+	}
+
+	// Commit changes
+	commitMsg := fmt.Sprintf("Create %d subtasks for task %s and mark parent as completed\n\nGenerated by Staff AI Agent System\n\nFiles modified:\n- %s.md (marked as completed)\n\nCreated individual task files:\n",
+		len(analysis.Subtasks), analysis.ParentTaskID, analysis.ParentTaskID)
+
+	// Add list of created files to commit message
+	for i := range analysis.Subtasks {
+		taskID := fmt.Sprintf("%s-subtask-%d", analysis.ParentTaskID, i+1)
+		commitMsg += fmt.Sprintf("- %s.md\n", taskID)
+	}
+
+	if len(analysis.AgentCreations) > 0 {
+		commitMsg += fmt.Sprintf("\nProposed %d new agents for specialized skills", len(analysis.AgentCreations))
+	}
+	cmd = gitCmd("commit", "-m", commitMsg)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to commit: %w", err)
+	}
+
+	// Push branch
+	cmd = gitCmd("push", "-u", "origin", branchName)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to push branch: %w", err)
+	}
+
+	gtm.logger.Info("Created subtask proposal branch", slog.String("branch", branchName))
+	return nil
+}
+
+// updateParentTaskAsCompleted updates the parent task file to mark it as completed
+func (gtm *GitTaskManager) updateParentTaskAsCompleted(taskFilePath string, analysis *tm.SubtaskAnalysis) error {
+	// Read the existing parent task file
+	content, err := os.ReadFile(taskFilePath)
+	if err != nil {
+		return fmt.Errorf("failed to read parent task file: %w", err)
+	}
+
+	taskContent := string(content)
+
+	// Find the YAML frontmatter boundaries
+	lines := strings.Split(taskContent, "\n")
+	var frontmatterStart, frontmatterEnd int = -1, -1
+
+	for i, line := range lines {
+		if line == "---" {
+			if frontmatterStart == -1 {
+				frontmatterStart = i
+			} else {
+				frontmatterEnd = i
+				break
+			}
+		}
+	}
+
+	if frontmatterStart == -1 || frontmatterEnd == -1 {
+		return fmt.Errorf("invalid task file format: missing YAML frontmatter")
+	}
+
+	// Update the frontmatter
+	now := time.Now().Format(time.RFC3339)
+	var updatedLines []string
+
+	// Add lines before frontmatter
+	updatedLines = append(updatedLines, lines[:frontmatterStart+1]...)
+
+	// Process frontmatter lines
+	for i := frontmatterStart + 1; i < frontmatterEnd; i++ {
+		line := lines[i]
+		if strings.HasPrefix(line, "status:") {
+			updatedLines = append(updatedLines, "status: completed")
+		} else if strings.HasPrefix(line, "updated_at:") {
+			updatedLines = append(updatedLines, fmt.Sprintf("updated_at: %s", now))
+		} else if strings.HasPrefix(line, "completed_at:") {
+			updatedLines = append(updatedLines, fmt.Sprintf("completed_at: %s", now))
+		} else {
+			updatedLines = append(updatedLines, line)
+		}
+	}
+
+	// Add closing frontmatter and rest of content
+	updatedLines = append(updatedLines, lines[frontmatterEnd:]...)
+
+	// Add subtask information to the task description
+	if frontmatterEnd+1 < len(lines) {
+		// Add subtask information
+		subtaskInfo := fmt.Sprintf("\n\n## Subtasks Created\n\nThis task has been broken down into %d subtasks:\n\n", len(analysis.Subtasks))
+		for i, subtask := range analysis.Subtasks {
+			subtaskID := fmt.Sprintf("%s-subtask-%d", analysis.ParentTaskID, i+1)
+			subtaskInfo += fmt.Sprintf("- **%s**: %s (assigned to %s)\n", subtaskID, subtask.Title, subtask.AssignedTo)
+		}
+		subtaskInfo += fmt.Sprintf("\n**Total Estimated Hours:** %d\n", analysis.EstimatedTotalHours)
+		subtaskInfo += fmt.Sprintf("**Completed:** %s - Task broken down into actionable subtasks\n", now)
+
+		// Insert subtask info before any existing body content
+		updatedContent := strings.Join(updatedLines[:], "\n") + subtaskInfo
+
+		// Write the updated content back to the file
+		if err := os.WriteFile(taskFilePath, []byte(updatedContent), 0644); err != nil {
+			return fmt.Errorf("failed to write updated parent task file: %w", err)
+		}
+	}
+
+	gtm.logger.Info("Updated parent task to completed status", slog.String("task_id", analysis.ParentTaskID))
+	return nil
+}
+
+// generateSubtaskFile creates the content for an individual subtask file
+func (gtm *GitTaskManager) generateSubtaskFile(subtask tm.SubtaskProposal, taskID, parentTaskID string) string {
+	var content strings.Builder
+
+	// Generate YAML frontmatter
+	content.WriteString("---\n")
+	content.WriteString(fmt.Sprintf("id: %s\n", taskID))
+	content.WriteString(fmt.Sprintf("title: %s\n", subtask.Title))
+	content.WriteString(fmt.Sprintf("description: %s\n", subtask.Description))
+	content.WriteString(fmt.Sprintf("assignee: %s\n", subtask.AssignedTo))
+	content.WriteString(fmt.Sprintf("owner_id: %s\n", subtask.AssignedTo))
+	content.WriteString(fmt.Sprintf("owner_name: %s\n", subtask.AssignedTo))
+	content.WriteString("status: todo\n")
+	content.WriteString(fmt.Sprintf("priority: %s\n", strings.ToLower(string(subtask.Priority))))
+	content.WriteString(fmt.Sprintf("parent_task_id: %s\n", parentTaskID))
+	content.WriteString(fmt.Sprintf("estimated_hours: %d\n", subtask.EstimatedHours))
+	content.WriteString(fmt.Sprintf("created_at: %s\n", time.Now().Format(time.RFC3339)))
+	content.WriteString(fmt.Sprintf("updated_at: %s\n", time.Now().Format(time.RFC3339)))
+	content.WriteString("completed_at: null\n")
+	content.WriteString("archived_at: null\n")
+
+	// Add dependencies if any
+	if len(subtask.Dependencies) > 0 {
+		content.WriteString("dependencies:\n")
+		for _, dep := range subtask.Dependencies {
+			// Convert dependency index to actual subtask ID
+			if depIndex := parseDependencyIndex(dep); depIndex >= 0 {
+				depTaskID := fmt.Sprintf("%s-subtask-%d", parentTaskID, depIndex+1)
+				content.WriteString(fmt.Sprintf("  - %s\n", depTaskID))
+			}
+		}
+	}
+
+	// Add required skills if any
+	if len(subtask.RequiredSkills) > 0 {
+		content.WriteString("required_skills:\n")
+		for _, skill := range subtask.RequiredSkills {
+			content.WriteString(fmt.Sprintf("  - %s\n", skill))
+		}
+	}
+
+	content.WriteString("---\n\n")
+
+	// Add markdown content
+	content.WriteString("# Task Description\n\n")
+	content.WriteString(fmt.Sprintf("%s\n\n", subtask.Description))
+
+	if subtask.EstimatedHours > 0 {
+		content.WriteString("## Estimated Effort\n\n")
+		content.WriteString(fmt.Sprintf("**Estimated Hours:** %d\n\n", subtask.EstimatedHours))
+	}
+
+	if len(subtask.RequiredSkills) > 0 {
+		content.WriteString("## Required Skills\n\n")
+		for _, skill := range subtask.RequiredSkills {
+			content.WriteString(fmt.Sprintf("- %s\n", skill))
+		}
+		content.WriteString("\n")
+	}
+
+	if len(subtask.Dependencies) > 0 {
+		content.WriteString("## Dependencies\n\n")
+		content.WriteString("This task depends on the completion of:\n\n")
+		for _, dep := range subtask.Dependencies {
+			if depIndex := parseDependencyIndex(dep); depIndex >= 0 {
+				depTaskID := fmt.Sprintf("%s-subtask-%d", parentTaskID, depIndex+1)
+				content.WriteString(fmt.Sprintf("- %s\n", depTaskID))
+			}
+		}
+		content.WriteString("\n")
+	}
+
+	content.WriteString("## Notes\n\n")
+	content.WriteString(fmt.Sprintf("This subtask was generated from parent task: %s\n", parentTaskID))
+	content.WriteString("Generated by Staff AI Agent System\n\n")
+
+	return content.String()
+}
+
+func (gtm *GitTaskManager) generateSubtaskPRContent(analysis *tm.SubtaskAnalysis) string {
+	var content strings.Builder
+
+	content.WriteString(fmt.Sprintf("# Subtasks Created for Task %s\n\n", analysis.ParentTaskID))
+	content.WriteString(fmt.Sprintf("This PR creates **%d individual task files** in `/operations/tasks/` ready for agent assignment.\n\n", len(analysis.Subtasks)))
+	content.WriteString(fmt.Sprintf("✅ **Parent task `%s` has been marked as completed** - the complex task has been successfully broken down into actionable subtasks.\n\n", analysis.ParentTaskID))
+	content.WriteString(fmt.Sprintf("## Analysis Summary\n%s\n\n", analysis.AnalysisSummary))
+	content.WriteString(fmt.Sprintf("## Recommended Approach\n%s\n\n", analysis.RecommendedApproach))
+	content.WriteString(fmt.Sprintf("**Estimated Total Hours:** %d\n\n", analysis.EstimatedTotalHours))
+
+	// List the created task files
+	content.WriteString("## Created Task Files\n\n")
+	for i, subtask := range analysis.Subtasks {
+		taskID := fmt.Sprintf("%s-subtask-%d", analysis.ParentTaskID, i+1)
+		content.WriteString(fmt.Sprintf("### %d. `%s.md`\n", i+1, taskID))
+		content.WriteString(fmt.Sprintf("- **Title:** %s\n", subtask.Title))
+		content.WriteString(fmt.Sprintf("- **Assigned to:** %s\n", subtask.AssignedTo))
+		content.WriteString(fmt.Sprintf("- **Priority:** %s\n", subtask.Priority))
+		content.WriteString(fmt.Sprintf("- **Estimated Hours:** %d\n", subtask.EstimatedHours))
+		content.WriteString(fmt.Sprintf("- **Description:** %s\n\n", subtask.Description))
+	}
+
+	if analysis.RiskAssessment != "" {
+		content.WriteString(fmt.Sprintf("## Risk Assessment\n%s\n\n", analysis.RiskAssessment))
+	}
+
+	content.WriteString("## Proposed Subtasks\n\n")
+
+	for i, subtask := range analysis.Subtasks {
+		content.WriteString(fmt.Sprintf("### %d. %s\n", i+1, subtask.Title))
+		content.WriteString(fmt.Sprintf("- **Assigned to:** %s\n", subtask.AssignedTo))
+		content.WriteString(fmt.Sprintf("- **Priority:** %s\n", subtask.Priority))
+		content.WriteString(fmt.Sprintf("- **Estimated Hours:** %d\n", subtask.EstimatedHours))
+
+		if len(subtask.Dependencies) > 0 {
+			deps := strings.Join(subtask.Dependencies, ", ")
+			content.WriteString(fmt.Sprintf("- **Dependencies:** %s\n", deps))
+		}
+
+		content.WriteString(fmt.Sprintf("- **Description:** %s\n\n", subtask.Description))
+	}
+
+	content.WriteString("---\n")
+	content.WriteString("*Generated by Staff AI Agent System*\n\n")
+	content.WriteString("**Instructions:**\n")
+	content.WriteString("- Review the proposed subtasks\n")
+	content.WriteString("- Approve or request changes\n")
+	content.WriteString("- When merged, the subtasks will be automatically created and assigned\n")
+
+	return content.String()
+}
+
+func (gtm *GitTaskManager) determineBaseBranch(ctx context.Context) string {
+	// Get clone path to check branches
+	clonePath, err := gtm.git.GetAgentClonePath("subtask-service")
+	if err != nil {
+		gtm.logger.Warn("Failed to get clone path for base branch detection", slog.String("error", err.Error()))
+		return "main"
+	}
+
+	// Check if main branch exists
+	gitCmd := func(args ...string) *exec.Cmd {
+		return exec.CommandContext(ctx, "git", append([]string{"-C", clonePath}, args...)...)
+	}
+
+	// Try to checkout main branch
+	cmd := gitCmd("show-ref", "refs/remotes/origin/main")
+	if err := cmd.Run(); err == nil {
+		return "main"
+	}
+
+	// Try to checkout master branch
+	cmd = gitCmd("show-ref", "refs/remotes/origin/master")
+	if err := cmd.Run(); err == nil {
+		return "master"
+	}
+
+	// Default to main if neither can be detected
+	gtm.logger.Warn("Could not determine base branch, defaulting to 'main'")
+	return "main"
+}
+
+// createAndCommitSolution creates a Git branch and commits the solution using per-agent clones
+func (gtm *GitTaskManager) createSolutionBranch(ctx context.Context, task *tm.Task, solution, branchName, agentName string) error {
+	// Get agent's dedicated Git clone
+	clonePath, err := gtm.git.GetAgentClonePath(agentName)
+	if err != nil {
+		return fmt.Errorf("failed to get agent clone: %w", err)
+	}
+
+	gtm.logger.Info("Agent working in clone",
+		slog.String("agent", agentName),
+		slog.String("clone_path", clonePath))
+
+	// Refresh the clone with latest changes
+	if err := gtm.git.RefreshAgentClone(agentName); err != nil {
+		gtm.logger.Warn("Failed to refresh clone for agent",
+			slog.String("agent", agentName),
+			slog.String("error", err.Error()))
+	}
+
+	// All Git operations use the agent's clone directory
+	gitCmd := func(args ...string) *exec.Cmd {
+		return exec.CommandContext(ctx, "git", append([]string{"-C", clonePath}, args...)...)
+	}
+
+	// Ensure we're on main branch before creating new branch
+	cmd := gitCmd("checkout", "main")
+	if err := cmd.Run(); err != nil {
+		// Try master branch if main doesn't exist
+		cmd = gitCmd("checkout", "master")
+		if err := cmd.Run(); err != nil {
+			return fmt.Errorf("failed to checkout main/master branch: %w", err)
+		}
+	}
+
+	// Create branch
+	cmd = gitCmd("checkout", "-b", branchName)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to create branch: %w", err)
+	}
+
+	// Create solution file in agent's clone
+	solutionDir := filepath.Join(clonePath, "tasks", "solutions")
+	if err := os.MkdirAll(solutionDir, 0755); err != nil {
+		return fmt.Errorf("failed to create solution directory: %w", err)
+	}
+
+	solutionFile := filepath.Join(solutionDir, fmt.Sprintf("%s-solution.md", task.ID))
+	solutionContent := fmt.Sprintf(`# Solution for Task: %s
+
+**Agent:** %s
+**Completed:** %s
+
+## Task Description
+%s
+
+## Solution
+%s
+
+---
+*Generated by Staff AI Agent System*
+`, task.Title, agentName, time.Now().Format(time.RFC3339), task.Description, solution)
+
+	if err := os.WriteFile(solutionFile, []byte(solutionContent), 0644); err != nil {
+		return fmt.Errorf("failed to write solution file: %w", err)
+	}
+
+	// Stage files
+	relativeSolutionFile := filepath.Join("tasks", "solutions", fmt.Sprintf("%s-solution.md", task.ID))
+	cmd = gitCmd("add", relativeSolutionFile)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to stage files: %w", err)
+	}
+
+	// Commit changes
+	commitMsg := buildCommitMessage(task, gtm.config.Git.CommitMessageTemplate, agentName)
+	cmd = gitCmd("commit", "-m", commitMsg)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to commit: %w", err)
+	}
+
+	// Push branch
+	cmd = gitCmd("push", "-u", "origin", branchName)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("failed to push branch: %w", err)
+	}
+
+	gtm.logger.Info("Agent successfully pushed branch",
+		slog.String("agent", agentName),
+		slog.String("branch", branchName))
+	return nil
+}
+
+func buildCommitMessage(task *tm.Task, template, agentName string) string {
+	replacements := map[string]string{
+		"{task_id}":    task.ID,
+		"{task_title}": task.Title,
+		"{agent_name}": agentName,
+		"{solution}":   "See solution file for details",
+	}
+
+	result := template
+	for placeholder, value := range replacements {
+		result = strings.ReplaceAll(result, placeholder, value)
+	}
+
+	return result
+}
+
+// parseDependencyIndex parses a dependency string to an integer index
+func parseDependencyIndex(dep string) int {
+	var idx int
+	if _, err := fmt.Sscanf(dep, "%d", &idx); err == nil {
+		return idx
+	}
+	return -1 // Invalid dependency format
+}
+
+// generateBranchName creates a Git branch name for the task
+func generateBranchName(prefix string, task *tm.Task) string {
+	// Clean title for use in branch name
+	cleanTitle := strings.ToLower(task.Title)
+	cleanTitle = strings.ReplaceAll(cleanTitle, " ", "-")
+	cleanTitle = strings.ReplaceAll(cleanTitle, "/", "-")
+	// Remove special characters
+	var result strings.Builder
+	for _, r := range cleanTitle {
+		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
+			result.WriteRune(r)
+		}
+	}
+	cleanTitle = result.String()
+
+	// Limit length
+	if len(cleanTitle) > 40 {
+		cleanTitle = cleanTitle[:40]
+	}
+
+	return fmt.Sprintf("%s%s-%s", prefix, task.ID, cleanTitle)
+}
+
+// buildSolutionPRDescription creates PR description from template
+func buildSolutionPRDescription(task *tm.Task, solution, template, agentName string) string {
+	// Truncate solution for PR if too long
+	truncatedSolution := solution
+	if len(solution) > 1000 {
+		truncatedSolution = solution[:1000] + "...\n\n*See solution file for complete details*"
+	}
+
+	replacements := map[string]string{
+		"{task_id}":          task.ID,
+		"{task_title}":       task.Title,
+		"{task_description}": task.Description,
+		"{agent_name}":       fmt.Sprintf("%s", agentName),
+		"{priority}":         string(task.Priority),
+		"{solution}":         truncatedSolution,
+		"{files_changed}":    fmt.Sprintf("- `tasks/solutions/%s-solution.md`", task.ID),
+	}
+
+	result := template
+	for placeholder, value := range replacements {
+		result = strings.ReplaceAll(result, placeholder, value)
+	}
+
+	return result
+}
+
 // Ensure GitTaskManager implements TaskManager interface
 var _ tm.TaskManager = (*GitTaskManager)(nil)