Add gerrit implementation

Change-Id: I293cea35572b051cd1d4a0466fe2fd5a06e81ad9
diff --git a/GERRIT.md b/GERRIT.md
new file mode 100644
index 0000000..db3eedb
--- /dev/null
+++ b/GERRIT.md
@@ -0,0 +1,143 @@
+# Gerrit Integration Guide
+
+Staff AI Agent System now supports Gerrit as an alternative to GitHub for pull request management. This guide explains how to configure and use Gerrit with Staff.
+
+## Overview
+
+Gerrit integration provides the same capabilities as GitHub:
+- Create changes (Gerrit's equivalent of pull requests)
+- List and manage changes
+- Update change descriptions
+- Close (abandon) changes
+- Submit (merge) changes
+- Structured logging for all operations
+
+## Configuration
+
+### YAML Configuration
+
+Add Gerrit configuration to your `config.yaml`:
+
+```yaml
+# Either GitHub OR Gerrit is required
+gerrit:
+  username: "your-gerrit-username" 
+  password: "your-gerrit-http-password-or-token"
+  base_url: "https://gerrit.example.com"
+  project: "your-project-name"
+```
+
+### Environment Variables
+
+You can also configure Gerrit using environment variables:
+
+```bash
+export GERRIT_USERNAME="your-username"
+export GERRIT_PASSWORD="your-password-or-token"
+export GERRIT_BASE_URL="https://gerrit.example.com" 
+export GERRIT_PROJECT="your-project"
+```
+
+## Provider Priority
+
+When both GitHub and Gerrit are configured, GitHub takes precedence for backward compatibility. To use Gerrit when both are configured, remove the GitHub configuration.
+
+## Authentication
+
+Gerrit integration supports:
+- **HTTP Password**: Traditional Gerrit HTTP password
+- **HTTP Token**: Generate from Gerrit Settings → HTTP Credentials
+
+## URL Format
+
+Generated change URLs follow Gerrit's standard format:
+```
+https://gerrit.example.com/c/project-name/+/12345
+```
+
+## Agent Behavior
+
+Agents work identically with Gerrit:
+1. Process assigned tasks
+2. Generate solutions using LLM
+3. Create Git branches and commits
+4. Create Gerrit changes for review
+5. Update task status with change URL
+
+## Configuration Validation
+
+Use the config check command to verify your Gerrit setup:
+
+```bash
+./staff config-check
+```
+
+This will show:
+- Which Git provider is configured (GitHub/Gerrit)
+- Validation of all required fields
+- Connection status
+
+## Example Configuration
+
+See `config-gerrit-example.yaml` for a complete example configuration using Gerrit instead of GitHub.
+
+## Structured Logging
+
+All Gerrit operations include structured logging:
+
+```
+INFO Creating Gerrit change url=https://gerrit.example.com/a/changes/ project=my-project subject="Task ABC-123: Implement feature" branch=main topic=task/ABC-123-implement-feature
+```
+
+## Comparison with GitHub
+
+| Feature | GitHub | Gerrit |
+|---------|--------|--------|
+| Changes/PRs | Pull Requests | Changes |
+| Review System | GitHub Reviews | Gerrit Code Review |
+| Merge | Merge PR | Submit Change |
+| Close | Close PR | Abandon Change |
+| URL Format | `/pull/123` | `/c/project/+/123` |
+| Authentication | Token | Username + Password/Token |
+
+## Migration
+
+To migrate from GitHub to Gerrit:
+
+1. Update configuration file or environment variables
+2. Remove GitHub configuration 
+3. Restart the Staff system
+4. All new agent operations will use Gerrit
+
+Existing GitHub pull requests are not affected and remain accessible.
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Authentication Failed**
+   - Verify username and password/token
+   - Check Gerrit HTTP credentials settings
+   - Ensure HTTP authentication is enabled
+
+2. **Project Not Found**
+   - Verify project name matches exactly
+   - Check user has access to the project
+   - Ensure project exists in Gerrit
+
+3. **Network Issues**
+   - Verify base URL is correct and accessible
+   - Check firewall/proxy settings
+   - Test connectivity to Gerrit server
+
+### Debug Logging
+
+Enable debug logging to troubleshoot issues:
+
+```yaml
+# Add to your configuration
+logging:
+  level: debug
+```
+
+This will show detailed HTTP requests and responses for Gerrit operations.
\ No newline at end of file
diff --git a/config-gerrit-example.yaml b/config-gerrit-example.yaml
new file mode 100644
index 0000000..2b14b48
--- /dev/null
+++ b/config-gerrit-example.yaml
@@ -0,0 +1,70 @@
+# Staff AI Agent System - Gerrit Configuration Example
+# This configuration demonstrates how to use Gerrit instead of GitHub for pull requests
+
+openai:
+  api_key: "sk-your-openai-api-key-here"
+  model: "gpt-4"
+  base_url: "https://api.openai.com/v1"
+  timeout: 30s
+  max_retries: 3
+
+# Gerrit configuration instead of GitHub
+gerrit:
+  username: "your-gerrit-username"
+  password: "your-gerrit-http-password-or-token"
+  base_url: "https://gerrit.example.com"
+  project: "your-project-name"
+
+agents:
+  - name: "backend-engineer"
+    role: "Backend Engineer"
+    model: "gpt-4"
+    system_prompt_file: "../operations/agents/backend-engineer/system.md"
+    capabilities: ["go", "api_development", "database_design"]
+    task_types: ["backend", "api", "database"]
+    max_tokens: 4000
+    temperature: 0.1
+
+  - name: "frontend-engineer"
+    role: "Frontend Engineer"  
+    model: "gpt-4"
+    system_prompt_file: "../operations/agents/frontend-engineer/system.md"
+    capabilities: ["react", "typescript", "ui_design"]
+    task_types: ["frontend", "ui", "web"]
+    max_tokens: 4000
+    temperature: 0.3
+
+  - name: "qa-engineer"
+    role: "QA Engineer"
+    model: "gpt-4"
+    system_prompt_file: "../operations/agents/qa-engineer/system.md"
+    capabilities: ["testing", "automation", "quality_assurance"]
+    task_types: ["testing", "qa", "automation"]
+    max_tokens: 3000
+    temperature: 0.1
+
+tasks:
+  storage_path: "../operations/tasks/"
+  completed_path: "../operations/completed/"
+
+git:
+  branch_prefix: "task/"
+  commit_message_template: "Task {task_id}: {task_title}\n\n{solution}\n\nGenerated by Staff AI Agent: {agent_name}"
+  pr_template: |
+    ## Task: {task_title}
+
+    **Task ID:** {task_id}  
+    **Agent:** {agent_name}  
+    **Priority:** {priority}
+
+    ### Description
+    {task_description}
+
+    ### Solution
+    {solution}
+
+    ### Files Changed
+    {files_changed}
+
+    ---
+    *Generated by Staff AI Multi-Agent System*
\ No newline at end of file
diff --git a/server/agent/manager.go b/server/agent/manager.go
index 88bfe21..c206665 100644
--- a/server/agent/manager.go
+++ b/server/agent/manager.go
@@ -41,15 +41,38 @@
 	// Create auto-assigner
 	autoAssigner := assignment.NewAutoAssigner(cfg.Agents)
 
-	// Create GitHub PR provider
-	githubConfig := git.GitHubConfig{
-		Token:  cfg.GitHub.Token,
-		Logger: logger,
+	// Create PR provider based on configuration
+	var prProvider git.PullRequestProvider
+	var repoURL string
+	
+	switch cfg.GetPrimaryGitProvider() {
+	case "github":
+		githubConfig := git.GitHubConfig{
+			Token:  cfg.GitHub.Token,
+			Logger: logger,
+		}
+		prProvider = git.NewGitHubPullRequestProvider(cfg.GitHub.Owner, cfg.GitHub.Repo, githubConfig)
+		repoURL = fmt.Sprintf("https://github.com/%s/%s.git", cfg.GitHub.Owner, cfg.GitHub.Repo)
+		logger.Info("Using GitHub as pull request provider", 
+			slog.String("owner", cfg.GitHub.Owner), 
+			slog.String("repo", cfg.GitHub.Repo))
+	case "gerrit":
+		gerritConfig := git.GerritConfig{
+			Username: cfg.Gerrit.Username,
+			Password: cfg.Gerrit.Password,
+			BaseURL:  cfg.Gerrit.BaseURL,
+			Logger:   logger,
+		}
+		prProvider = git.NewGerritPullRequestProvider(cfg.Gerrit.Project, gerritConfig)
+		repoURL = fmt.Sprintf("%s/%s", cfg.Gerrit.BaseURL, cfg.Gerrit.Project)
+		logger.Info("Using Gerrit as pull request provider", 
+			slog.String("base_url", cfg.Gerrit.BaseURL), 
+			slog.String("project", cfg.Gerrit.Project))
+	default:
+		return nil, fmt.Errorf("no valid Git provider configured")
 	}
-	prProvider := git.NewGitHubPullRequestProvider(cfg.GitHub.Owner, cfg.GitHub.Repo, githubConfig)
 
-	// Create clone manager for per-agent Git repositories
-	repoURL := fmt.Sprintf("https://github.com/%s/%s.git", cfg.GitHub.Owner, cfg.GitHub.Repo)
+	// Create clone manager for per-agent Git repositories  
 	workspacePath := filepath.Join(".", "workspace")
 	cloneManager := git.NewCloneManager(repoURL, workspacePath)
 
@@ -109,13 +132,24 @@
 		break
 	}
 
+	// Get owner and repo for subtask service based on provider
+	var owner, repo string
+	switch m.config.GetPrimaryGitProvider() {
+	case "github":
+		owner = m.config.GitHub.Owner
+		repo = m.config.GitHub.Repo
+	case "gerrit":
+		owner = m.config.Gerrit.Project
+		repo = m.config.Gerrit.Project
+	}
+	
 	m.subtaskService = subtasks.NewSubtaskService(
 		firstAgent.Provider,
 		m.taskManager,
 		agentRoles,
 		m.prProvider,
-		m.config.GitHub.Owner,
-		m.config.GitHub.Repo,
+		owner,
+		repo,
 		m.cloneManager,
 		m.logger,
 	)
@@ -569,7 +603,15 @@
 		return "", fmt.Errorf("failed to create PR: %w", err)
 	}
 
-	return fmt.Sprintf("https://github.com/%s/%s/pull/%d", m.config.GitHub.Owner, m.config.GitHub.Repo, pr.Number), nil
+	// Generate provider-specific PR URL
+	switch m.config.GetPrimaryGitProvider() {
+	case "github":
+		return fmt.Sprintf("https://github.com/%s/%s/pull/%d", m.config.GitHub.Owner, m.config.GitHub.Repo, pr.Number), nil
+	case "gerrit":
+		return fmt.Sprintf("%s/c/%s/+/%d", m.config.Gerrit.BaseURL, m.config.Gerrit.Project, pr.Number), nil
+	default:
+		return "", fmt.Errorf("unknown git provider")
+	}
 }
 
 // buildPRDescription creates PR description from template
diff --git a/server/cmd/commands/config_check.go b/server/cmd/commands/config_check.go
index ec8c894..5256c03 100644
--- a/server/cmd/commands/config_check.go
+++ b/server/cmd/commands/config_check.go
@@ -33,23 +33,27 @@
 		fmt.Printf("ℹ️  OpenAI Base URL: %s\n", cfg.OpenAI.BaseURL)
 	}
 
+	// Check Git provider configuration
+	fmt.Printf("\nGit Provider: %s\n", cfg.GetPrimaryGitProvider())
+	
 	// Check GitHub configuration
-	if cfg.GitHub.Token == "" {
-		fmt.Println("❌ GitHub token is missing")
-	} else {
-		fmt.Printf("✅ GitHub token configured (ends with: ...%s)\n", cfg.GitHub.Token[len(cfg.GitHub.Token)-4:])
+	if cfg.HasGitHubConfig() {
+		fmt.Printf("✅ GitHub configured (ends with: ...%s)\n", cfg.GitHub.Token[len(cfg.GitHub.Token)-4:])
+		fmt.Printf("   Owner: %s, Repo: %s\n", cfg.GitHub.Owner, cfg.GitHub.Repo)
+	} else if cfg.GitHub.Token != "" || cfg.GitHub.Owner != "" || cfg.GitHub.Repo != "" {
+		fmt.Println("⚠️  GitHub partially configured (incomplete)")
 	}
-
-	if cfg.GitHub.Owner == "" {
-		fmt.Println("❌ GitHub owner is missing")
-	} else {
-		fmt.Printf("✅ GitHub owner: %s\n", cfg.GitHub.Owner)
+	
+	// Check Gerrit configuration  
+	if cfg.HasGerritConfig() {
+		fmt.Printf("✅ Gerrit configured (user: %s)\n", cfg.Gerrit.Username)
+		fmt.Printf("   Base URL: %s, Project: %s\n", cfg.Gerrit.BaseURL, cfg.Gerrit.Project)
+	} else if cfg.Gerrit.Username != "" || cfg.Gerrit.BaseURL != "" || cfg.Gerrit.Project != "" {
+		fmt.Println("⚠️  Gerrit partially configured (incomplete)")
 	}
-
-	if cfg.GitHub.Repo == "" {
-		fmt.Println("❌ GitHub repo is missing")
-	} else {
-		fmt.Printf("✅ GitHub repo: %s\n", cfg.GitHub.Repo)
+	
+	if !cfg.HasGitHubConfig() && !cfg.HasGerritConfig() {
+		fmt.Println("❌ No Git provider configured (need GitHub or Gerrit)")
 	}
 
 	// Check agents configuration
diff --git a/server/config/config.go b/server/config/config.go
index 421e861..86dd7a5 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -12,6 +12,7 @@
 type Config struct {
 	OpenAI OpenAIConfig  `yaml:"openai"`
 	GitHub GitHubConfig  `yaml:"github"`
+	Gerrit GerritConfig  `yaml:"gerrit"`
 	Agents []AgentConfig `yaml:"agents"`
 	Tasks  TasksConfig   `yaml:"tasks"`
 	Git    GitConfig     `yaml:"git"`
@@ -33,6 +34,14 @@
 	Repo  string `yaml:"repo"`
 }
 
+// GerritConfig represents Gerrit integration configuration
+type GerritConfig struct {
+	Username string `yaml:"username"`
+	Password string `yaml:"password"` // HTTP password or API token
+	BaseURL  string `yaml:"base_url"` // Gerrit server URL (e.g. https://gerrit.example.com)
+	Project  string `yaml:"project"`  // Gerrit project name
+}
+
 // AgentConfig represents individual agent configuration
 type AgentConfig struct {
 	Name             string   `yaml:"name"`
@@ -103,6 +112,18 @@
 	if repo := os.Getenv("GITHUB_REPO"); repo != "" {
 		config.GitHub.Repo = repo
 	}
+	if gerritUsername := os.Getenv("GERRIT_USERNAME"); gerritUsername != "" {
+		config.Gerrit.Username = gerritUsername
+	}
+	if gerritPassword := os.Getenv("GERRIT_PASSWORD"); gerritPassword != "" {
+		config.Gerrit.Password = gerritPassword
+	}
+	if gerritBaseURL := os.Getenv("GERRIT_BASE_URL"); gerritBaseURL != "" {
+		config.Gerrit.BaseURL = gerritBaseURL
+	}
+	if gerritProject := os.Getenv("GERRIT_PROJECT"); gerritProject != "" {
+		config.Gerrit.Project = gerritProject
+	}
 
 	// Re-validate after env overrides
 	if err := validateConfig(*config); err != nil {
@@ -183,15 +204,35 @@
 		return fmt.Errorf("openai.model is required")
 	}
 
-	// Validate GitHub config
-	if config.GitHub.Token == "" {
-		return fmt.Errorf("github.token is required")
+	// Validate that at least one Git provider is configured
+	hasGitHub := config.GitHub.Token != "" && config.GitHub.Owner != "" && config.GitHub.Repo != ""
+	hasGerrit := config.Gerrit.Username != "" && config.Gerrit.Password != "" && config.Gerrit.BaseURL != "" && config.Gerrit.Project != ""
+	
+	if !hasGitHub && !hasGerrit {
+		return fmt.Errorf("either GitHub or Gerrit configuration is required")
 	}
-	if config.GitHub.Owner == "" {
-		return fmt.Errorf("github.owner is required")
+	
+	// Validate GitHub config if provided
+	if config.GitHub.Token != "" {
+		if config.GitHub.Owner == "" {
+			return fmt.Errorf("github.owner is required when github.token is provided")
+		}
+		if config.GitHub.Repo == "" {
+			return fmt.Errorf("github.repo is required when github.token is provided")
+		}
 	}
-	if config.GitHub.Repo == "" {
-		return fmt.Errorf("github.repo is required")
+	
+	// Validate Gerrit config if provided
+	if config.Gerrit.Username != "" {
+		if config.Gerrit.Password == "" {
+			return fmt.Errorf("gerrit.password is required when gerrit.username is provided")
+		}
+		if config.Gerrit.BaseURL == "" {
+			return fmt.Errorf("gerrit.base_url is required when gerrit.username is provided")
+		}
+		if config.Gerrit.Project == "" {
+			return fmt.Errorf("gerrit.project is required when gerrit.username is provided")
+		}
 	}
 
 	// Validate agents
@@ -232,3 +273,25 @@
 	}
 	return names
 }
+
+// HasGitHubConfig returns true if GitHub is properly configured
+func (c *Config) HasGitHubConfig() bool {
+	return c.GitHub.Token != "" && c.GitHub.Owner != "" && c.GitHub.Repo != ""
+}
+
+// HasGerritConfig returns true if Gerrit is properly configured
+func (c *Config) HasGerritConfig() bool {
+	return c.Gerrit.Username != "" && c.Gerrit.Password != "" && c.Gerrit.BaseURL != "" && c.Gerrit.Project != ""
+}
+
+// GetPrimaryGitProvider returns the primary git provider type ("github" or "gerrit")
+// If both are configured, GitHub takes precedence for backward compatibility
+func (c *Config) GetPrimaryGitProvider() string {
+	if c.HasGitHubConfig() {
+		return "github"
+	}
+	if c.HasGerritConfig() {
+		return "gerrit"
+	}
+	return ""
+}
diff --git a/server/git/gerrit.go b/server/git/gerrit.go
index d795079..d1d029f 100644
--- a/server/git/gerrit.go
+++ b/server/git/gerrit.go
@@ -5,6 +5,7 @@
 	"context"
 	"encoding/json"
 	"fmt"
+	"log/slog"
 	"net/http"
 	"strconv"
 	"strings"
@@ -17,12 +18,14 @@
 	Password   string // Can be HTTP password or API token
 	BaseURL    string
 	HTTPClient *http.Client
+	Logger     *slog.Logger
 }
 
 // GerritPullRequestProvider implements PullRequestProvider for Gerrit
 type GerritPullRequestProvider struct {
 	config  GerritConfig
 	project string
+	logger  *slog.Logger
 }
 
 // NewGerritPullRequestProvider creates a new Gerrit pull request provider
@@ -30,10 +33,14 @@
 	if config.HTTPClient == nil {
 		config.HTTPClient = &http.Client{Timeout: 30 * time.Second}
 	}
+	if config.Logger == nil {
+		config.Logger = slog.Default()
+	}
 
 	return &GerritPullRequestProvider{
 		config:  config,
 		project: project,
+		logger:  config.Logger,
 	}
 }
 
@@ -145,6 +152,14 @@
 	}
 
 	url := fmt.Sprintf("%s/a/changes/", g.config.BaseURL)
+	
+	// Log change creation with structured data
+	g.logger.Info("Creating Gerrit change",
+		slog.String("url", url),
+		slog.String("project", g.project),
+		slog.String("subject", options.Title),
+		slog.String("branch", options.BaseBranch),
+		slog.String("topic", options.HeadBranch))
 	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
diff --git a/server/staff b/server/staff
index 701a6e8..cfd99cf 100755
--- a/server/staff
+++ b/server/staff
Binary files differ