Add repo sync

Change-Id: I6b61873c97e9ff46a699151fc52aa937248bcf81
diff --git a/server/app/proposal.go b/server/app/proposal.go
index 165f336..8feaded 100644
--- a/server/app/proposal.go
+++ b/server/app/proposal.go
@@ -1,26 +1,105 @@
 package app
 
 import (
+	"context"
 	"fmt"
+	"log/slog"
+	"time"
 
 	"github.com/iomodo/staff/git"
 )
 
 func (a *App) ProposalApproval(body []byte, signature string) error {
-	// Validate the webhook signature
-	if err := git.ValidateSignature(body, signature, a.config.GitHub.WebhookSecret); err != nil {
-		return fmt.Errorf("invalid webhook signature: %w", err)
+	// Set reasonable timeout for webhook processing
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+
+	// Validate required dependencies
+	if a.git == nil {
+		return fmt.Errorf("git interface not initialized")
+	}
+	if a.taskManager == nil {
+		return fmt.Errorf("task manager not initialized")
 	}
 
-	// Process the webhook payload
+	// Process the webhook payload (includes signature validation)
 	taskID, err := git.ProcessMergeWebhook(body, signature, a.config.GitHub.WebhookSecret)
 	if err != nil {
 		return fmt.Errorf("failed to process webhook: %w", err)
 	}
 
-	// Log the successful approval
 	a.logger.Info("Proposal approved via webhook",
-		"task_id", taskID,
+		slog.String("task_id", taskID),
+		slog.String("repository", fmt.Sprintf("%s/%s", a.config.GitHub.Owner, a.config.GitHub.Repo)),
 	)
+
+	// Synchronize repository to get any new tasks that were added
+	a.syncRepository(ctx)
+	a.logger.Info("Webhook processing completed successfully",
+		slog.String("task_id", taskID),
+	)
+
 	return nil
 }
+
+// syncRepository pulls the remote repository to fetch new tasks
+// This is non-critical for webhook success, so failures are logged but don't fail the webhook
+func (a *App) syncRepository(ctx context.Context) {
+	a.logger.Info("Synchronizing repository to fetch new tasks")
+
+	// Get current branch to determine the upstream branch to pull from
+	currentBranch, err := a.git.GetCurrentBranch(ctx)
+	if err != nil {
+		a.logger.Warn("Failed to get current branch, using default pull",
+			slog.String("error", err.Error()),
+		)
+		// Fallback to simple pull without specifying branch
+		if err := a.git.Pull(ctx, "origin", ""); err != nil {
+			a.logger.Warn("Failed to synchronize repository",
+				slog.String("remote", "origin"),
+				slog.String("error", err.Error()),
+			)
+		} else {
+			a.logger.Info("Successfully synchronized repository",
+				slog.String("remote", "origin"),
+			)
+		}
+		return
+	}
+
+	// Get the upstream branch for the current branch
+	upstreamBranch, err := a.git.GetUpstreamBranch(ctx, currentBranch)
+	if err != nil {
+		a.logger.Warn("Failed to get upstream branch, trying simple pull",
+			slog.String("current_branch", currentBranch),
+			slog.String("error", err.Error()),
+		)
+		// Fallback to simple pull without specifying branch
+		if err := a.git.Pull(ctx, "origin", ""); err != nil {
+			a.logger.Warn("Failed to synchronize repository",
+				slog.String("remote", "origin"),
+				slog.String("error", err.Error()),
+			)
+		} else {
+			a.logger.Info("Successfully synchronized repository",
+				slog.String("remote", "origin"),
+			)
+		}
+		return
+	}
+
+	// Use the upstream branch for pulling
+	if err := a.git.Pull(ctx, "origin", upstreamBranch); err != nil {
+		// Don't fail the entire webhook if git pull fails - agents can still work with existing tasks
+		a.logger.Warn("Failed to synchronize repository",
+			slog.String("remote", "origin"),
+			slog.String("branch", upstreamBranch),
+			slog.String("error", err.Error()),
+		)
+	} else {
+		a.logger.Info("Successfully synchronized repository",
+			slog.String("remote", "origin"),
+			slog.String("branch", upstreamBranch),
+		)
+	}
+}