Compare commits
1 Commits
ac5ff7aeeb
...
61d538d4a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61d538d4a5 |
@@ -37,18 +37,13 @@ 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 {
|
||||||
MaxChunkSize int `help:"Maximum diff chunk size in bytes" default:"60000"`
|
MaxChunkChars int `help:"Maximum diff chunk size in characters (default 60000)" default:"60000"`
|
||||||
Guidelines []string `help:"Project guidelines to prepend" sep:","`
|
Guidelines []string `help:"Project-specific review guidelines"`
|
||||||
DisableComments bool `help:"Disable posting comments (dry run)"`
|
DisableComments bool `help:"Do not post comments to the Git provider (dry‑run mode)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
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-"`
|
||||||
@@ -129,7 +124,7 @@ func main() {
|
|||||||
log.Fatalf("Error initializing AI: %v", err)
|
log.Fatalf("Error initializing AI: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pierreService := pierre.New(ai, git, cfg.Review.MaxChunkSize, cfg.Review.Guidelines, cfg.Review.DisableComments)
|
pierreService := pierre.New(ai, git, cfg.Review.MaxChunkChars, 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,8 +9,6 @@ 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"`
|
||||||
@@ -26,7 +24,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 = defaultChunkSize // default 60KB ~ 15k tokens
|
maxSize = 60000 // default 60KB ~ 15k tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks := splitDiffIntoChunks(diffBytes, maxSize)
|
chunks := splitDiffIntoChunks(diffBytes, maxSize)
|
||||||
@@ -35,7 +33,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 = "Project guidelines:\n"
|
guidelinesText = "\nProject guidelines:\n"
|
||||||
for _, g := range s.guidelines {
|
for _, g := range s.guidelines {
|
||||||
guidelinesText += "- " + g + "\n"
|
guidelinesText += "- " + g + "\n"
|
||||||
}
|
}
|
||||||
@@ -104,13 +102,9 @@ 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 {
|
||||||
var hseg string
|
hseg := h
|
||||||
if j == 0 {
|
if j != 0 {
|
||||||
// First hunk segment already contains the preceding content (including any needed newline)
|
hseg = "@@ " + h
|
||||||
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())
|
||||||
@@ -135,3 +129,4 @@ func splitDiffIntoChunks(diff []byte, maxSize int) []string {
|
|||||||
}
|
}
|
||||||
return chunks
|
return chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// mockChat implements the ChatAdapter interface for testing.
|
// mockChat implements the ChatAdapter interface for testing.
|
||||||
type mockChat struct{ callCount int }
|
type mockChat struct{}
|
||||||
|
|
||||||
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
|
||||||
@@ -107,12 +106,11 @@ 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: chatMock,
|
chat: &mockChat{},
|
||||||
}
|
}
|
||||||
diffReader, err := svc.git.GetDiff(context.Background(), "", "", 0)
|
diffReader, err := svc.git.GetDiff(context.Background(), "", "", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,7 +124,4 @@ 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,11 +7,6 @@ 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