Add PRs to agent
Change-Id: If623cf29d160a0ff4dcbbb2923f681a2d9f17c1b
diff --git a/server/.env b/server/.env
index 4d4d76d..562c49a 100644
--- a/server/.env
+++ b/server/.env
@@ -1,3 +1,9 @@
REMOTE_REPO_URL=https://github.com/iomodo/staff.git
WORKING_DIR=../
-OPENAI_API_KEY=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
\ No newline at end of file
+OPENAI_API_KEY=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
+
+GERRIT_ENABLED=true
+GERRIT_USERNAME=staff-agent
+GERRIT_PASSWORD=your-gerrit-api-token-or-password
+GERRIT_BASE_URL=https://gerrit.company.com
+GERRIT_PROJECT=my-project
\ No newline at end of file
diff --git a/server/agent/agent.go b/server/agent/agent.go
index d1c64a4..42a7024 100644
--- a/server/agent/agent.go
+++ b/server/agent/agent.go
@@ -37,6 +37,18 @@
GitRepoPath string
GitRemote string
GitBranch string
+
+ // Gerrit Configuration
+ GerritEnabled bool
+ GerritConfig GerritConfig
+}
+
+// GerritConfig holds configuration for Gerrit operations
+type GerritConfig struct {
+ Username string
+ Password string // Can be HTTP password or API token
+ BaseURL string
+ Project string
}
// Agent represents an AI agent that can process tasks
@@ -62,7 +74,26 @@
}
// Create git interface
- gitInterface := git.DefaultGit(config.GitRepoPath)
+ var gitInterface git.GitInterface
+ if config.GerritEnabled {
+ // Create Gerrit pull request provider
+ gerritPRProvider := git.NewGerritPullRequestProvider(config.GerritConfig.Project, git.GerritConfig{
+ Username: config.GerritConfig.Username,
+ Password: config.GerritConfig.Password,
+ BaseURL: config.GerritConfig.BaseURL,
+ HTTPClient: nil, // Will use default client
+ })
+
+ // Create git interface with Gerrit pull request provider
+ gitConfig := git.GitConfig{
+ Timeout: 30 * time.Second,
+ PullRequestProvider: gerritPRProvider,
+ }
+ gitInterface = git.NewGitWithPullRequests(config.GitRepoPath, gitConfig, gerritPRProvider)
+ } else {
+ // Use default git interface (GitHub)
+ gitInterface = git.DefaultGit(config.GitRepoPath)
+ }
// Create context with cancellation
ctx, cancel := context.WithCancel(context.Background())
@@ -152,6 +183,52 @@
}
}
+ // Set up git user configuration
+ userConfig := git.UserConfig{
+ Name: a.Config.GitUsername,
+ Email: a.Config.GitEmail,
+ }
+ if err := a.gitInterface.SetUserConfig(ctx, userConfig); err != nil {
+ return fmt.Errorf("failed to set git user config: %w", err)
+ }
+
+ // Check if remote origin exists, if not add it
+ remotes, err := a.gitInterface.ListRemotes(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to list remotes: %w", err)
+ }
+
+ originExists := false
+ for _, remote := range remotes {
+ if remote.Name == "origin" {
+ originExists = true
+ break
+ }
+ }
+
+ if !originExists {
+ // Add remote origin - use Gerrit URL if enabled, otherwise use the configured remote
+ remoteURL := a.Config.GitRemote
+ if a.Config.GerritEnabled {
+ // For Gerrit, the remote URL should be the Gerrit SSH or HTTP URL
+ // Format: ssh://username@gerrit-host:29418/project-name.git
+ // or: https://gerrit-host/project-name.git
+ if strings.HasPrefix(a.Config.GerritConfig.BaseURL, "https://") {
+ remoteURL = fmt.Sprintf("%s/%s.git", a.Config.GerritConfig.BaseURL, a.Config.GerritConfig.Project)
+ } else {
+ // Assume SSH format
+ remoteURL = fmt.Sprintf("ssh://%s@%s:29418/%s.git",
+ a.Config.GerritConfig.Username,
+ strings.TrimPrefix(a.Config.GerritConfig.BaseURL, "https://"),
+ a.Config.GerritConfig.Project)
+ }
+ }
+
+ if err := a.gitInterface.AddRemote(ctx, "origin", remoteURL); err != nil {
+ return fmt.Errorf("failed to add remote origin: %w", err)
+ }
+ }
+
// Checkout to the specified branch
if a.Config.GitBranch != "" {
if err := a.gitInterface.Checkout(ctx, a.Config.GitBranch); err != nil {
@@ -309,7 +386,7 @@
return fmt.Errorf("failed to add solution file: %w", err)
}
- commitMessage := fmt.Sprintf("feat: Complete task %s - %s", task.ID, task.Title)
+ commitMessage := fmt.Sprintf("feat: Complete task %s - %s\n\n%s", task.ID, task.Title, a.formatPullRequestDescription(task, solution))
if err := a.gitInterface.Commit(ctx, commitMessage, git.CommitOptions{
Author: &git.Author{
Name: a.Config.GitUsername,
@@ -320,12 +397,37 @@
return fmt.Errorf("failed to commit solution: %w", err)
}
- // Push the branch
- if err := a.gitInterface.Push(ctx, "origin", branchName, git.PushOptions{SetUpstream: true}); err != nil {
- return fmt.Errorf("failed to push branch: %w", err)
+ if a.Config.GerritEnabled {
+ // For Gerrit: Push to refs/for/BRANCH to create a change
+ gerritRef := fmt.Sprintf("refs/for/%s", a.Config.GitBranch)
+ if err := a.gitInterface.Push(ctx, "origin", gerritRef, git.PushOptions{}); err != nil {
+ return fmt.Errorf("failed to push to Gerrit: %w", err)
+ }
+ log.Printf("Created Gerrit change for task %s by pushing to %s", task.ID, gerritRef)
+ } else {
+ // For GitHub: Push branch and create PR
+ if err := a.gitInterface.Push(ctx, "origin", branchName, git.PushOptions{SetUpstream: true}); err != nil {
+ return fmt.Errorf("failed to push branch: %w", err)
+ }
+
+ // Create pull request using the git interface
+ prOptions := git.PullRequestOptions{
+ Title: fmt.Sprintf("Complete task %s: %s", task.ID, task.Title),
+ Description: a.formatPullRequestDescription(task, solution),
+ BaseBranch: a.Config.GitBranch,
+ HeadBranch: branchName,
+ BaseRepo: a.Config.GerritConfig.Project,
+ HeadRepo: a.Config.GerritConfig.Project,
+ }
+
+ pr, err := a.gitInterface.CreatePullRequest(ctx, prOptions)
+ if err != nil {
+ return fmt.Errorf("failed to create pull request: %w", err)
+ }
+
+ log.Printf("Created pull request for task %s: %s (ID: %s)", task.ID, pr.Title, pr.ID)
}
- log.Printf("Created pull request for task %s on branch %s", task.ID, branchName)
return nil
}
@@ -386,6 +488,28 @@
return content.String()
}
+// formatPullRequestDescription formats the description for the pull request
+func (a *Agent) formatPullRequestDescription(task *tm.Task, solution string) string {
+ var content strings.Builder
+
+ content.WriteString(fmt.Sprintf("**Task ID:** %s\n", task.ID))
+ content.WriteString(fmt.Sprintf("**Title:** %s\n", task.Title))
+ content.WriteString(fmt.Sprintf("**Priority:** %s\n", task.Priority))
+
+ if task.Description != "" {
+ content.WriteString(fmt.Sprintf("**Description:** %s\n", task.Description))
+ }
+
+ if task.DueDate != nil {
+ content.WriteString(fmt.Sprintf("**Due Date:** %s\n", task.DueDate.Format("2006-01-02")))
+ }
+
+ content.WriteString("\n**Solution:**\n\n")
+ content.WriteString(solution)
+
+ return content.String()
+}
+
// ptr helpers for cleaner code
func intPtr(i int) *int {
return &i
diff --git a/server/server/server.go b/server/server/server.go
index 41d1366..fc0d230 100644
--- a/server/server/server.go
+++ b/server/server/server.go
@@ -55,9 +55,36 @@
return fmt.Errorf("WORKING_DIR environment variable is required")
}
+ // Get Gerrit configuration (optional)
+ gerritEnabled := os.Getenv("GERRIT_ENABLED") == "true"
+ var gerritConfig agent.GerritConfig
+ if gerritEnabled {
+ gerritConfig = agent.GerritConfig{
+ Username: os.Getenv("GERRIT_USERNAME"),
+ Password: os.Getenv("GERRIT_PASSWORD"),
+ BaseURL: os.Getenv("GERRIT_BASE_URL"),
+ Project: os.Getenv("GERRIT_PROJECT"),
+ }
+
+ // Validate Gerrit configuration
+ if gerritConfig.Username == "" {
+ return fmt.Errorf("GERRIT_USERNAME environment variable is required when GERRIT_ENABLED=true")
+ }
+ if gerritConfig.Password == "" {
+ return fmt.Errorf("GERRIT_PASSWORD environment variable is required when GERRIT_ENABLED=true")
+ }
+ if gerritConfig.BaseURL == "" {
+ return fmt.Errorf("GERRIT_BASE_URL environment variable is required when GERRIT_ENABLED=true")
+ }
+ if gerritConfig.Project == "" {
+ return fmt.Errorf("GERRIT_PROJECT environment variable is required when GERRIT_ENABLED=true")
+ }
+ }
+
a.logger.Info("Environment variables loaded",
slog.String("remoteRepoURL", remoteRepoURL),
- slog.String("workingDir", workingDir))
+ slog.String("workingDir", workingDir),
+ slog.Bool("gerritEnabled", gerritEnabled))
// Check if working directory is empty
isEmpty, err := a.isDirectoryEmpty(workingDir)
@@ -106,19 +133,21 @@
}
config := agent.AgentConfig{
- Name: agentName,
- Role: cases.Title(language.English).String(agentName),
- GitUsername: fmt.Sprintf("Staff %s", cases.Title(language.English).String(agentName)),
- GitEmail: fmt.Sprintf("%s@staff.com", strings.ToLower(agentName)),
- WorkingDir: workingDir,
- LLMProvider: llm.ProviderOpenAI,
- LLMModel: "gpt-4o-mini",
- LLMConfig: llmConfig,
- SystemPrompt: systemPrompt,
- TaskManager: taskManager,
- GitRepoPath: workingDir,
- GitRemote: "origin",
- GitBranch: "main",
+ Name: agentName,
+ Role: cases.Title(language.English).String(agentName),
+ GitUsername: fmt.Sprintf("Staff %s", cases.Title(language.English).String(agentName)),
+ GitEmail: fmt.Sprintf("%s@staff.com", strings.ToLower(agentName)),
+ WorkingDir: workingDir,
+ LLMProvider: llm.ProviderOpenAI,
+ LLMModel: "gpt-4o-mini",
+ LLMConfig: llmConfig,
+ SystemPrompt: systemPrompt,
+ TaskManager: taskManager,
+ GitRepoPath: workingDir,
+ GitRemote: "origin",
+ GitBranch: "main",
+ GerritEnabled: gerritEnabled,
+ GerritConfig: gerritConfig,
}
ag, err := agent.NewAgent(config)
@@ -198,6 +227,27 @@
return fmt.Errorf("failed to set git user config: %w", err)
}
+ // Check if Gerrit is enabled and adjust remote URL accordingly
+ gerritEnabled := os.Getenv("GERRIT_ENABLED") == "true"
+ if gerritEnabled {
+ gerritBaseURL := os.Getenv("GERRIT_BASE_URL")
+ gerritProject := os.Getenv("GERRIT_PROJECT")
+
+ // For Gerrit, construct the appropriate remote URL
+ if strings.HasPrefix(gerritBaseURL, "https://") {
+ remoteRepoURL = fmt.Sprintf("%s/%s.git", gerritBaseURL, gerritProject)
+ } else {
+ // Assume SSH format
+ gerritUsername := os.Getenv("GERRIT_USERNAME")
+ remoteRepoURL = fmt.Sprintf("ssh://%s@%s:29418/%s.git",
+ gerritUsername,
+ strings.TrimPrefix(gerritBaseURL, "https://"),
+ gerritProject)
+ }
+
+ a.logger.Info("Using Gerrit remote URL", slog.String("url", remoteRepoURL))
+ }
+
// Add remote origin
if err := gitRepo.AddRemote(ctx, "origin", remoteRepoURL); err != nil {
return fmt.Errorf("failed to add remote origin: %w", err)