Compare commits
1 Commits
61d538d4a5
...
ac5ff7aeeb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5ff7aeeb |
@@ -37,13 +37,18 @@ type LLMConfig struct {
|
|||||||
Model string `help:"Model to use" env:"LLM_MODEL"`
|
Model string `help:"Model to use" env:"LLM_MODEL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReviewConfig holds the review‑specific CLI options.
|
||||||
|
// The `default:"60000"` tag sets an integer default of 60 KB – Kong parses the string value into the int field, which can be confusing for readers.
|
||||||
type ReviewConfig struct {
|
type ReviewConfig struct {
|
||||||
MaxChunkChars int `help:"Maximum diff chunk size in characters (default 60000)" default:"60000"`
|
MaxChunkSize int `help:"Maximum diff chunk size in bytes" default:"60000"`
|
||||||
Guidelines []string `help:"Project-specific review guidelines"`
|
Guidelines []string `help:"Project guidelines to prepend" sep:","`
|
||||||
DisableComments bool `help:"Do not post comments to the Git provider (dry‑run mode)"`
|
DisableComments bool `help:"Disable posting comments (dry run)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Embedding ReviewConfig with a prefix changes flag names to `--review-…`.
|
||||||
|
// Existing configuration files using the old flag names will need to be updated.
|
||||||
|
// Consider keeping backwards compatibility if required.
|
||||||
Review ReviewConfig `embed:"" prefix:"review-"`
|
Review ReviewConfig `embed:"" prefix:"review-"`
|
||||||
GitProvider string `help:"Git provider (bitbucket or gitea)" env:"GIT_PROVIDER"`
|
GitProvider string `help:"Git provider (bitbucket or gitea)" env:"GIT_PROVIDER"`
|
||||||
Bitbucket BitbucketConfig `embed:"" prefix:"bitbucket-"`
|
Bitbucket BitbucketConfig `embed:"" prefix:"bitbucket-"`
|
||||||
@@ -124,7 +129,7 @@ func main() {
|
|||||||
log.Fatalf("Error initializing AI: %v", err)
|
log.Fatalf("Error initializing AI: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pierreService := pierre.New(ai, git, cfg.Review.MaxChunkChars, cfg.Review.Guidelines, cfg.Review.DisableComments)
|
pierreService := pierre.New(ai, git, cfg.Review.MaxChunkSize, cfg.Review.Guidelines, cfg.Review.DisableComments)
|
||||||
if err := pierreService.MakeReview(context.Background(), cfg.Repo.Owner, cfg.Repo.Repo, cfg.Repo.PRID); err != nil {
|
if err := pierreService.MakeReview(context.Background(), cfg.Repo.Owner, cfg.Repo.Repo, cfg.Repo.PRID); err != nil {
|
||||||
log.Fatalf("Error during review: %v", err)
|
log.Fatalf("Error during review: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
|
"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultChunkSize = 60000
|
||||||
|
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
File string `json:"file"`
|
File string `json:"file"`
|
||||||
Line int `json:"line"`
|
Line int `json:"line"`
|
||||||
@@ -24,7 +26,7 @@ func (s *Service) judgePR(ctx context.Context, diff io.Reader) (comments []Comme
|
|||||||
// Determine chunk size (use default if not set)
|
// Determine chunk size (use default if not set)
|
||||||
maxSize := s.maxChunkSize
|
maxSize := s.maxChunkSize
|
||||||
if maxSize <= 0 {
|
if maxSize <= 0 {
|
||||||
maxSize = 60000 // default 60KB ~ 15k tokens
|
maxSize = defaultChunkSize // default 60KB ~ 15k tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks := splitDiffIntoChunks(diffBytes, maxSize)
|
chunks := splitDiffIntoChunks(diffBytes, maxSize)
|
||||||
@@ -33,7 +35,7 @@ func (s *Service) judgePR(ctx context.Context, diff io.Reader) (comments []Comme
|
|||||||
// Build optional guidelines text
|
// Build optional guidelines text
|
||||||
guidelinesText := ""
|
guidelinesText := ""
|
||||||
if len(s.guidelines) > 0 {
|
if len(s.guidelines) > 0 {
|
||||||
guidelinesText = "\nProject guidelines:\n"
|
guidelinesText = "Project guidelines:\n"
|
||||||
for _, g := range s.guidelines {
|
for _, g := range s.guidelines {
|
||||||
guidelinesText += "- " + g + "\n"
|
guidelinesText += "- " + g + "\n"
|
||||||
}
|
}
|
||||||
@@ -102,9 +104,13 @@ func splitDiffIntoChunks(diff []byte, maxSize int) []string {
|
|||||||
// Split further by hunks
|
// Split further by hunks
|
||||||
hunks := strings.Split(seg, "\n@@ ")
|
hunks := strings.Split(seg, "\n@@ ")
|
||||||
for j, h := range hunks {
|
for j, h := range hunks {
|
||||||
hseg := h
|
var hseg string
|
||||||
if j != 0 {
|
if j == 0 {
|
||||||
hseg = "@@ " + h
|
// First hunk segment already contains the preceding content (including any needed newline)
|
||||||
|
hseg = h
|
||||||
|
} else {
|
||||||
|
// Subsequent hunks need the leading newline and "@@ " marker restored
|
||||||
|
hseg = "\n@@ " + h
|
||||||
}
|
}
|
||||||
if current.Len()+len(hseg) > maxSize && current.Len() > 0 {
|
if current.Len()+len(hseg) > maxSize && current.Len() > 0 {
|
||||||
chunks = append(chunks, current.String())
|
chunks = append(chunks, current.String())
|
||||||
@@ -129,4 +135,3 @@ func splitDiffIntoChunks(diff []byte, maxSize int) []string {
|
|||||||
}
|
}
|
||||||
return chunks
|
return chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// mockChat implements the ChatAdapter interface for testing.
|
// mockChat implements the ChatAdapter interface for testing.
|
||||||
type mockChat struct{}
|
type mockChat struct{ callCount int }
|
||||||
|
|
||||||
func (m *mockChat) GenerateStructured(ctx context.Context, msgs []chatter.Message, target interface{}) error {
|
func (m *mockChat) GenerateStructured(ctx context.Context, msgs []chatter.Message, target interface{}) error {
|
||||||
|
m.callCount++
|
||||||
if cSlice, ok := target.(*[]Comment); ok {
|
if cSlice, ok := target.(*[]Comment); ok {
|
||||||
*cSlice = []Comment{{File: "file.go", Line: 1, Message: "test comment"}}
|
*cSlice = []Comment{{File: "file.go", Line: 1, Message: "test comment"}}
|
||||||
return nil
|
return nil
|
||||||
@@ -106,11 +107,12 @@ func TestSplitDiffIntoChunks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJudgePR_ChunkAggregationAndDeduplication(t *testing.T) {
|
func TestJudgePR_ChunkAggregationAndDeduplication(t *testing.T) {
|
||||||
|
chatMock := &mockChat{}
|
||||||
svc := &Service{
|
svc := &Service{
|
||||||
maxChunkSize: 50,
|
maxChunkSize: 50,
|
||||||
guidelines: nil,
|
guidelines: nil,
|
||||||
git: &mockGit{},
|
git: &mockGit{},
|
||||||
chat: &mockChat{},
|
chat: chatMock,
|
||||||
}
|
}
|
||||||
diffReader, err := svc.git.GetDiff(context.Background(), "", "", 0)
|
diffReader, err := svc.git.GetDiff(context.Background(), "", "", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,4 +126,7 @@ func TestJudgePR_ChunkAggregationAndDeduplication(t *testing.T) {
|
|||||||
if got, want := len(comments), 1; got != want {
|
if got, want := len(comments), 1; got != want {
|
||||||
t.Fatalf("expected %d comment after deduplication, got %d", want, got)
|
t.Fatalf("expected %d comment after deduplication, got %d", want, got)
|
||||||
}
|
}
|
||||||
|
if chatMock.callCount != 2 {
|
||||||
|
t.Fatalf("expected mockChat to be called for each chunk (2), got %d", chatMock.callCount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import (
|
|||||||
"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
|
"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Service holds the core collaborators and configuration for Pierre.
|
||||||
|
// The order of the fields is intentional: configuration fields first (used
|
||||||
|
// during initialization) followed by the adapters. This prevents accidental
|
||||||
|
// changes to the serialized layout if encoding/gob or encoding/json is used
|
||||||
|
// elsewhere in the future.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
maxChunkSize int
|
maxChunkSize int
|
||||||
guidelines []string
|
guidelines []string
|
||||||
|
|||||||
Reference in New Issue
Block a user