From db1ecd1e64bf770516c17b54d86ebc3309950161 Mon Sep 17 00:00:00 2001 From: schreifuchs Date: Thu, 12 Feb 2026 20:58:55 +0100 Subject: [PATCH] feat: gitea client --- GEMINI.md | 77 ++++++++++ cmd/pierre/main.go | 82 ++++++---- config.example.yaml | 17 ++- go.mod | 10 +- go.sum | 187 ----------------------- internal/gitadapters/baseadapter/rest.go | 31 ++++ internal/gitadapters/bitbucket.go | 8 + internal/gitadapters/gitea/adapter.go | 46 ++++++ internal/gitadapters/resource.go | 1 - internal/pierre/judge.go | 12 +- internal/pierre/resource.go | 29 ++++ internal/pierre/review.go | 34 +++++ 12 files changed, 301 insertions(+), 233 deletions(-) create mode 100644 GEMINI.md delete mode 100644 go.sum create mode 100644 internal/gitadapters/baseadapter/rest.go create mode 100644 internal/gitadapters/gitea/adapter.go delete mode 100644 internal/gitadapters/resource.go create mode 100644 internal/pierre/resource.go create mode 100644 internal/pierre/review.go diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..a34759b --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,77 @@ +# Pierre Bot + +Pierre Bot is an intelligent, AI-powered code review assistant designed for Bitbucket Server/Data Center. It automates the initial pass of code review by analyzing Pull Request diffs and identifying potential bugs, logic errors, and style issues using modern LLMs (Google Gemini 2.0 Flash or Ollama). + +## Project Overview + +* **Type:** Go CLI Application +* **Core Function:** Fetches PR diffs from Bitbucket -> Sends to LLM -> Prints structured review comments. +* **Key Technologies:** + * **Language:** Go (1.25+) + * **AI SDKs:** `google/generative-ai-go`, `ollama/ollama` + * **CLI Framework:** `alecthomas/kong` + +## Architecture + +The project follows a standard Go project layout: + +* **`cmd/pierre/`**: Contains the `main.go` entry point. It handles configuration parsing (flags, env vars, file), initializes adapters, and orchestrates the application flow. +* **`internal/pierre/`**: Contains the core business logic. + * `judge.go`: Defines the `JudgePR` function which prepares the system prompt and context for the LLM. +* **`internal/chatter/`**: Abstraction layer for LLM providers. + * `gemini.go`: Implements the `ChatAdapter` interface for Google Gemini. notably includes **dynamic JSON schema generation** via reflection (`schemaFromType`) to enforce structured output from the model. + * `ollama.go`: Implements the `ChatAdapter` for Ollama (local models). +* **`internal/gitadapters/`**: Abstraction for Version Control Systems. + * `bitbucket.go`: Client for fetching PR diffs from Bitbucket Server. + +## Building and Running + +### Prerequisites + +* Go 1.25 or later +* Access to a Bitbucket Server instance +* API Key for Google Gemini (or a running Ollama instance) + +### Build + +```bash +go build -o pierre ./cmd/pierre/main.go +``` + +### Configuration + +Configuration is handled via `kong` and supports a hierarchy: **Flags > Env Vars > Config File**. + +**1. Environment Variables:** + +* `BITBUCKET_URL`: Base URL of the Bitbucket instance. +* `BITBUCKET_TOKEN`: Personal Access Token (HTTP) for Bitbucket. +* `LLM_PROVIDER`: `gemini` or `ollama`. +* `LLM_API_KEY`: API Key for Gemini. +* `LLM_MODEL`: Model name (e.g., `gemini-2.0-flash`). + +**2. Configuration File (`config.yaml`):** + +See `config.example.yaml` for a template. Place it in the current directory or `~/.pierre.yaml`. + +### Usage + +Run the bot against a specific Pull Request: + +```bash +# Syntax: ./pierre [flags] +./pierre --llm-provider=gemini --llm-model=gemini-2.0-flash MYPROJ my-repo 123 +``` + +## Development Conventions + +* **Structured Output:** The bot relies on the LLM returning valid JSON matching the `Comment` struct. This is enforced in `internal/chatter/gemini.go` by converting the Go struct definition into a `genai.Schema`. +* **Dependency Injection:** Adapters (`gitadapters`, `chatter`) are initialized in `main` and passed to the core logic, making testing easier. +* **Error Handling:** strict error checks are preferred; the bot will exit if it cannot fetch the diff or initialize the AI. + +## Key Files + +* **`cmd/pierre/main.go`**: Application entry point and config wiring. +* **`internal/pierre/judge.go`**: The "brain" that constructs the prompt for the AI. +* **`internal/chatter/gemini.go`**: Gemini integration logic, including the reflection-based schema generator. +* **`config.example.yaml`**: Reference configuration file. diff --git a/cmd/pierre/main.go b/cmd/pierre/main.go index f210e56..513fd7b 100644 --- a/cmd/pierre/main.go +++ b/cmd/pierre/main.go @@ -2,25 +2,32 @@ 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)" required:"" env:"BITBUCKET_URL"` + BaseURL string `help:"Bitbucket Base URL (e.g. https://bitbucket.example.com)" env:"BITBUCKET_URL"` Token string `help:"Bearer Token" env:"BITBUCKET_TOKEN"` - // Positional arguments - Project string `arg:"" help:"Project Key (e.g. PROJ)" env:"BITBUCKET_PROJECT"` - Repo string `arg:"" help:"Repository Slug" env:"BITBUCKET_REPO"` - PRID int `arg:"" help:"Pull Request ID" name:"pr"` +} + +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 { @@ -31,9 +38,12 @@ type LLMConfig struct { } type Config struct { - Bitbucket BitbucketConfig `embed:"" prefix:"bitbucket-"` - LLM LLMConfig `embed:"" prefix:"llm-"` - Config kong.ConfigFlag `help:"Path to a YAML config file"` + 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() { @@ -48,22 +58,46 @@ func main() { // Parse flags, env vars, and config files kong.Parse(cfg, kong.Name("pierre"), - kong.Description("AI-powered Pull Request reviewer for Bitbucket"), + kong.Description("AI-powered Pull Request reviewer"), kong.UsageOnError(), kong.Configuration(kongyaml.Loader, "config.yaml", defaultConfig), ) - // Initialize Bitbucket Adapter - bitbucket := gitadapters.NewBitbucket(cfg.Bitbucket.BaseURL, cfg.Bitbucket.Token) + // 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.") + } + } - // Fetch Diff using positional args - diff, err := bitbucket.GetDiff(cfg.Bitbucket.Project, cfg.Bitbucket.Repo, cfg.Bitbucket.PRID) - if err != nil { - log.Fatalf("Error fetching diff: %v", err) + var git pierre.GitAdapter + + switch provider { + case "bitbucket": + if cfg.Bitbucket.BaseURL == "" { + log.Fatal("Bitbucket Base URL is required when using bitbucket provider.") + } + git = 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.") + } + git, 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) } // Initialize AI Adapter - var ai pierre.ChatAdapter switch cfg.LLM.Provider { @@ -79,16 +113,6 @@ func main() { 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, "---") - } + pierreService := pierre.New(ai, git) + pierreService.MakeReview(context.Background(), cfg.Repo.Owner, cfg.Repo.Repo, cfg.Repo.PRID) } diff --git a/config.example.yaml b/config.example.yaml index 14f5a30..af5053f 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,12 +1,19 @@ +git-provider: "bitbucket" # Optional if only one is configured (bitbucket or gitea) + bitbucket: base-url: "https://bitbucket.your-org.ch" token: "BMTY4OTU0NjU3OTo..." - # Positional defaults (optional) - project: "APP" - repo: "api-gateway" - prid: 45 + +gitea: + base-url: "https://gitea.com" + token: "your-gitea-token" + +# Shared positional defaults (optional) +owner: "APP" +repo: "api-gateway" +prid: 45 llm: provider: "gemini" api-key: "AIzaSy..." - model: "gemini-2.0-flash" + model: "gemini-2.0-flash" \ No newline at end of file diff --git a/go.mod b/go.mod index 122402f..288a542 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot go 1.25.0 require ( + code.gitea.io/sdk/gitea v0.23.2 + github.com/alecthomas/kong v1.14.0 + github.com/alecthomas/kong-yaml v0.2.0 github.com/google/generative-ai-go v0.20.1 github.com/ollama/ollama v0.16.0 google.golang.org/api v0.186.0 @@ -15,11 +18,12 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/longrunning v0.5.7 // indirect - github.com/alecthomas/kong v1.14.0 // indirect - github.com/alecthomas/kong-yaml v0.2.0 // indirect + github.com/42wim/httpsig v1.2.3 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -28,7 +32,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect - github.com/invopop/jsonschema v0.13.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect diff --git a/go.sum b/go.sum deleted file mode 100644 index cf8de47..0000000 --- a/go.sum +++ /dev/null @@ -1,187 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= -cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= -cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= -cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g= -cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s= -github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I= -github.com/alecthomas/kong-yaml v0.2.0 h1:iiVVqVttmOsHKawlaW/TljPsjaEv1O4ODx6dloSA58Y= -github.com/alecthomas/kong-yaml v0.2.0/go.mod h1:vMvOIy+wpB49MCZ0TA3KMts38Mu9YfRP03Q1StN69/g= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ= -github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/ollama/ollama v0.16.0 h1:wDrjgZvx+ej1iYrD//q7crGRA4b4482WZodRYc7oQTI= -github.com/ollama/ollama v0.16.0/go.mod h1:FEk95NbAJJZk+t7cLh+bPGTul72j1O3PLLlYNV3FVZ0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug= -google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/gitadapters/baseadapter/rest.go b/internal/gitadapters/baseadapter/rest.go new file mode 100644 index 0000000..0c8f543 --- /dev/null +++ b/internal/gitadapters/baseadapter/rest.go @@ -0,0 +1,31 @@ +package baseadapter + +import ( + "fmt" + "io" + "net/http" + "net/url" +) + +type Rest struct { + baseURL string + bearerToken string +} + +func (b *Rest) createRequest(method string, body io.Reader, path ...string) (r *http.Request, err error) { + target, err := url.JoinPath(b.baseURL, path...) + if err != nil { + err = fmt.Errorf("can not parse path: %w", err) + return + } + req, err := http.NewRequest(method, target, body) + if err != nil { + return nil, err + } + + if b.bearerToken != "" { + req.Header.Set("Authorization", "Bearer "+b.bearerToken) + } + + return req, nil +} diff --git a/internal/gitadapters/bitbucket.go b/internal/gitadapters/bitbucket.go index 33df958..b0cc6fd 100644 --- a/internal/gitadapters/bitbucket.go +++ b/internal/gitadapters/bitbucket.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "net/http" + + "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/pierre" ) type BitbucketAdapter struct { @@ -36,3 +38,9 @@ func (b *BitbucketAdapter) GetDiff(projectKey, repositorySlug string, pullReques diff = response.Body return } + +func (b *BitbucketAdapter) AddComment(projectKey, repositorySlug string, pullRequestID int, comment pierre.Comment) error { + fmt.Printf("[MOCK BITBUCKET] Adding comment to PR %s/%s #%d: %s at %s:%d\n", + projectKey, repositorySlug, pullRequestID, comment.Message, comment.File, comment.Line) + return nil +} diff --git a/internal/gitadapters/gitea/adapter.go b/internal/gitadapters/gitea/adapter.go new file mode 100644 index 0000000..90b1b38 --- /dev/null +++ b/internal/gitadapters/gitea/adapter.go @@ -0,0 +1,46 @@ +package gitea + +import ( + "bytes" + "io" + + "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/pierre" + "code.gitea.io/sdk/gitea" +) + +type Adapter struct { + client *gitea.Client +} + +func New(baseURL, token string) (*Adapter, error) { + client, err := gitea.NewClient(baseURL, gitea.SetToken(token)) + if err != nil { + return nil, err + } + return &Adapter{ + client: client, + }, nil +} + +func (g *Adapter) GetDiff(owner, repo string, prID int) (io.Reader, error) { + diff, _, err := g.client.GetPullRequestDiff(owner, repo, int64(prID), gitea.PullRequestDiffOptions{}) + if err != nil { + return nil, err + } + return bytes.NewReader(diff), nil +} + +func (g *Adapter) AddComment(owner, repo string, prID int, comment pierre.Comment) error { + opts := gitea.CreatePullReviewOptions{ + State: gitea.ReviewStateComment, + Comments: []gitea.CreatePullReviewComment{ + { + Path: comment.File, + Body: comment.Message, + NewLineNum: int64(comment.Line), + }, + }, + } + _, _, err := g.client.CreatePullReview(owner, repo, int64(prID), opts) + return err +} diff --git a/internal/gitadapters/resource.go b/internal/gitadapters/resource.go deleted file mode 100644 index e40dd95..0000000 --- a/internal/gitadapters/resource.go +++ /dev/null @@ -1 +0,0 @@ -package gitadapters diff --git a/internal/pierre/judge.go b/internal/pierre/judge.go index de91b3c..8fd5dbe 100644 --- a/internal/pierre/judge.go +++ b/internal/pierre/judge.go @@ -14,28 +14,24 @@ type Comment struct { Message string `json:"message"` } -type ChatAdapter interface { - GenerateStructured(ctx context.Context, messages []chatter.Message, target interface{}) error -} - -func JudgePR(ctx context.Context, chat ChatAdapter, diff io.Reader) (comments []Comment, err error) { +func (s *Service) judgePR(ctx context.Context, diff io.Reader) (comments []Comment, err error) { diffBytes, err := io.ReadAll(diff) if err != nil { return nil, fmt.Errorf("failed to read diff: %w", err) } - err = chat.GenerateStructured(ctx, []chatter.Message{ + err = s.chat.GenerateStructured(ctx, []chatter.Message{ { Role: chatter.RoleSystem, Content: ` You are a very strict senior software architect. - You review **only** newly added or modified lines in a unified diff (lines prefixed with “+”), together with the immediate hunk context. + You review **only** newly added or modified lines in a unified diff, together with the immediate hunk context. You do **not** report issues that appear **solely** in deleted lines (“-”) or that have already been fixed by the change. No comments are made on pure formatting/whitespace changes or reordering that does not alter the program’s behavior. `, }, { Role: chatter.RoleUser, - Content: fmt.Sprintf("Hello please review my PR.\n Here is the git diff of it: %s", string(diffBytes)), + Content: fmt.Sprintf("Hello please review my PR. Write comments where improvements are necessary in new lines.\n Here is the git diff of it: %s", string(diffBytes)), }, }, &comments) diff --git a/internal/pierre/resource.go b/internal/pierre/resource.go new file mode 100644 index 0000000..5afc0fa --- /dev/null +++ b/internal/pierre/resource.go @@ -0,0 +1,29 @@ +package pierre + +import ( + "context" + "io" + + "bitbucket.bit.admin.ch/scm/~u80859501/pierre-bot/internal/chatter" +) + +type Service struct { + git GitAdapter + chat ChatAdapter +} + +func New(chat ChatAdapter, git GitAdapter) *Service { + return &Service{ + git: git, + chat: chat, + } +} + +type GitAdapter interface { + GetDiff(owner, repo string, prID int) (io.Reader, error) + AddComment(owner, repo string, prID int, comment Comment) error +} + +type ChatAdapter interface { + GenerateStructured(ctx context.Context, messages []chatter.Message, target interface{}) error +} diff --git a/internal/pierre/review.go b/internal/pierre/review.go new file mode 100644 index 0000000..a5a9940 --- /dev/null +++ b/internal/pierre/review.go @@ -0,0 +1,34 @@ +package pierre + +import ( + "context" + "fmt" + "log" +) + +func (s *Service) MakeReview(ctx context.Context, organisation string, repo string, prID int) error { + // Fetch Diff using positional args from shared RepoArgs + diff, err := s.git.GetDiff(organisation, repo, prID) + if err != nil { + log.Fatalf("Error fetching diff: %v", err) + } + + // Run Logic + comments, err := s.judgePR(context.Background(), 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, "---") + + if err := s.git.AddComment(organisation, repo, prID, c); err != nil { + log.Printf("Failed to add comment: %v", err) + } + } + + return nil +}