package main import ( "context" "fmt" "log" "os" "path/filepath" "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/chatter" "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/gitadapters" "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/gitadapters/gitea" "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/pierre" "github.com/alecthomas/kong" kongyaml "github.com/alecthomas/kong-yaml" ) type BitbucketConfig struct { BaseURL string `help:"Bitbucket Base URL (e.g. https://bitbucket.example.com)" env:"BITBUCKET_URL"` Token string `help:"Bearer Token" env:"BITBUCKET_TOKEN"` } type GiteaConfig struct { BaseURL string `help:"Gitea Base URL (e.g. https://gitea.com)" env:"GITEA_URL"` Token string `help:"API Token" env:"GITEA_TOKEN"` } type RepoArgs struct { Owner string `arg:"" help:"Project Key or Owner" env:"PIERRE_OWNER"` Repo string `arg:"" help:"Repository Slug" env:"PIERRE_REPO"` PRID int `arg:"" help:"Pull Request ID" name:"pr"` } type LLMConfig struct { Provider string `help:"Provider for llm (ollama or gemini)" required:"" env:"LLM_PROVIDER"` Endpoint string `help:"Endpoint for provider (only for ollama)" env:"LLM_ENDPOINT"` APIKey string `help:"APIKey for provider" env:"LLM_API_KEY"` Model string `help:"Model to use" env:"LLM_MODEL"` } type Config struct { GitProvider string `help:"Git provider (bitbucket or gitea)" env:"GIT_PROVIDER"` Bitbucket BitbucketConfig `embed:"" prefix:"bitbucket-"` Gitea GiteaConfig `embed:"" prefix:"gitea-"` Repo RepoArgs `embed:""` LLM LLMConfig `embed:"" prefix:"llm-"` Config kong.ConfigFlag `help:"Path to a YAML config file"` } func main() { cfg := &Config{} home, err := os.UserHomeDir() if err != nil { log.Fatalf("could not find home directory: %v", err) } defaultConfig := filepath.Join(home, ".config", "pierre", "config.yaml") // Parse flags, env vars, and config files ctx := kong.Parse(cfg, kong.Name("pierre"), kong.Description("AI-powered Pull Request reviewer"), kong.UsageOnError(), kong.Configuration(kongyaml.Loader, "config.yaml", defaultConfig), ) // Auto-detect provider provider := cfg.GitProvider if provider == "" { if cfg.Bitbucket.BaseURL != "" && cfg.Gitea.BaseURL == "" { provider = "bitbucket" } else if cfg.Gitea.BaseURL != "" && cfg.Bitbucket.BaseURL == "" { provider = "gitea" } else if cfg.Bitbucket.BaseURL != "" && cfg.Gitea.BaseURL != "" { log.Fatal("Multiple git providers configured. Please specify one using --git-provider.") } else { log.Fatal("No git provider configured. Please provide Bitbucket or Gitea configuration.") } } var adapter gitadapters.Adapter switch provider { case "bitbucket": if cfg.Bitbucket.BaseURL == "" { log.Fatal("Bitbucket Base URL is required when using bitbucket provider.") } adapter = gitadapters.NewBitbucket(cfg.Bitbucket.BaseURL, cfg.Bitbucket.Token) case "gitea": if cfg.Gitea.BaseURL == "" { log.Fatal("Gitea Base URL is required when using gitea provider.") } adapter, err = gitea.New(cfg.Gitea.BaseURL, cfg.Gitea.Token) if err != nil { log.Fatalf("Error initializing Gitea adapter: %v", err) } default: log.Fatalf("Unknown git provider: %s", provider) } // Fetch Diff using positional args from shared RepoArgs diff, err := adapter.GetDiff(cfg.Repo.Owner, cfg.Repo.Repo, cfg.Repo.PRID) if err != nil { log.Fatalf("Error fetching diff: %v", err) } // Initialize AI Adapter var ai chatter.ChatAdapter switch cfg.LLM.Provider { case "gemini": ai, err = chatter.NewGeminiAdapter(context.Background(), cfg.LLM.APIKey, cfg.LLM.Model) case "ollama": ai, err = chatter.NewOllamaAdapter(cfg.LLM.Endpoint, cfg.LLM.Model) default: log.Fatalf("%s is not a valid llm provider", cfg.LLM.Provider) } if err != nil { log.Fatalf("Error initializing AI: %v", err) } // Run Logic comments, err := pierre.JudgePR(context.Background(), ai, diff) if err != nil { log.Fatalf("Error judging PR: %v", err) } fmt.Printf("Analysis complete. Found %d issues.\n---\n", len(comments)) for _, c := range comments { fmt.Printf("File: %s\nLine: %d\nMessage: %s\n%s\n", c.File, c.Line, c.Message, "---") } _ = ctx }