add basic run command

This commit is contained in:
Moby von Briesen 2023-05-02 20:07:48 -04:00
parent 50db4d6d0e
commit 99bd1929b5
3 changed files with 154 additions and 29 deletions

View File

@ -61,13 +61,11 @@ func getConfig() config {
} }
func getPullPal(ctx context.Context, cfg config) (*pullpal.PullPal, error) { func getPullPal(ctx context.Context, cfg config) (*pullpal.PullPal, error) {
/*
log, err := zap.NewProduction() log, err := zap.NewProduction()
if err != nil { if err != nil {
panic(err) panic(err)
} }
*/ //log := zap.L()
log := zap.L()
author := vc.Author{ author := vc.Author{
Email: cfg.selfEmail, Email: cfg.selfEmail,
@ -82,7 +80,11 @@ func getPullPal(ctx context.Context, cfg config) (*pullpal.PullPal, error) {
Handle: cfg.repoHandle, Handle: cfg.repoHandle,
}, },
} }
p, err := pullpal.NewPullPal(ctx, log.Named("pullpal"), author, repo, cfg.openAIToken) listIssueOptions := vc.ListIssueOptions{
Handles: cfg.usersToListenTo,
Labels: cfg.requiredIssueLabels,
}
p, err := pullpal.NewPullPal(ctx, log.Named("pullpal"), listIssueOptions, author, repo, cfg.openAIToken)
return p, err return p, err
} }
@ -121,10 +123,7 @@ It can be used to:
var issue vc.Issue var issue vc.Issue
var changeRequest llm.CodeChangeRequest var changeRequest llm.CodeChangeRequest
if cfg.promptToClipboard { if cfg.promptToClipboard {
issue, changeRequest, err = p.PickIssueToClipboard(vc.ListIssueOptions{ issue, changeRequest, err = p.PickIssueToClipboard()
Handles: cfg.usersToListenTo,
Labels: cfg.requiredIssueLabels,
})
if err != nil { if err != nil {
if !errors.Is(err, pullpal.IssueNotFound) { if !errors.Is(err, pullpal.IssueNotFound) {
fmt.Println("error selecting issue and/or generating prompt", err) fmt.Println("error selecting issue and/or generating prompt", err)
@ -136,10 +135,7 @@ It can be used to:
fmt.Printf("Picked issue and copied prompt to clipboard. Issue #%s\n", issue.ID) fmt.Printf("Picked issue and copied prompt to clipboard. Issue #%s\n", issue.ID)
} }
} else { } else {
issue, changeRequest, err = p.PickIssueToFile(vc.ListIssueOptions{ issue, changeRequest, err = p.PickIssueToFile(cfg.promptPath)
Handles: cfg.usersToListenTo,
Labels: cfg.requiredIssueLabels,
}, cfg.promptPath)
if err != nil { if err != nil {
if !errors.Is(err, pullpal.IssueNotFound) { if !errors.Is(err, pullpal.IssueNotFound) {
fmt.Println("error selecting issue and/or generating prompt", err) fmt.Println("error selecting issue and/or generating prompt", err)

32
cmd/run.go Normal file
View File

@ -0,0 +1,32 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Runs a fully automated pull pal service",
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
p, err := getPullPal(cmd.Context(), cfg)
if err != nil {
fmt.Println("error creating new pull pal", err)
return
}
fmt.Println("Successfully initialized pull pal")
err = p.Run()
if err != nil {
fmt.Println("error running", err)
return
}
},
}
func init() {
rootCmd.AddCommand(runCmd)
}

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings" "strings"
"time"
"github.com/mobyvb/pull-pal/llm" "github.com/mobyvb/pull-pal/llm"
"github.com/mobyvb/pull-pal/vc" "github.com/mobyvb/pull-pal/vc"
@ -14,6 +15,9 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// IssueNotFound is returned when no issue can be found to generate a prompt for.
var IssueNotFound = errors.New("no issue found")
// PullPal is the service responsible for: // PullPal is the service responsible for:
// * Interacting with git server (e.g. reading issues and making PRs on Github) // * Interacting with git server (e.g. reading issues and making PRs on Github)
// * Generating LLM prompts // * Generating LLM prompts
@ -22,6 +26,7 @@ import (
type PullPal struct { type PullPal struct {
ctx context.Context ctx context.Context
log *zap.Logger log *zap.Logger
listIssueOptions vc.ListIssueOptions
vcClient vc.VCClient vcClient vc.VCClient
localGitClient *vc.LocalGitClient localGitClient *vc.LocalGitClient
@ -29,7 +34,7 @@ type PullPal struct {
} }
// NewPullPal creates a new "pull pal service", including setting up local version control and LLM integrations. // NewPullPal creates a new "pull pal service", including setting up local version control and LLM integrations.
func NewPullPal(ctx context.Context, log *zap.Logger, self vc.Author, repo vc.Repository, openAIToken string) (*PullPal, error) { func NewPullPal(ctx context.Context, log *zap.Logger, listIssueOptions vc.ListIssueOptions, self vc.Author, repo vc.Repository, openAIToken string) (*PullPal, error) {
ghClient, err := vc.NewGithubClient(ctx, log, self, repo) ghClient, err := vc.NewGithubClient(ctx, log, self, repo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -42,6 +47,7 @@ func NewPullPal(ctx context.Context, log *zap.Logger, self vc.Author, repo vc.Re
return &PullPal{ return &PullPal{
ctx: ctx, ctx: ctx,
log: log, log: log,
listIssueOptions: listIssueOptions,
vcClient: ghClient, vcClient: ghClient,
localGitClient: localGitClient, localGitClient: localGitClient,
@ -49,12 +55,103 @@ func NewPullPal(ctx context.Context, log *zap.Logger, self vc.Author, repo vc.Re
}, nil }, nil
} }
// IssueNotFound is returned when no issue can be found to generate a prompt for. // Run starts pull pal as a fully automated service that periodically requests changes and creates pull requests based on them.
var IssueNotFound = errors.New("no issue found") func (p *PullPal) Run() error {
p.log.Info("Starting Pull Pal")
// TODO gracefully handle context cancelation
for {
issues, err := p.vcClient.ListOpenIssues(p.listIssueOptions)
if err != nil {
p.log.Error("error listing issues", zap.Error(err))
continue
}
if len(issues) == 0 {
// todo don't sleep
p.log.Info("no issues found. sleeping for 5 mins")
time.Sleep(5 * time.Minute)
continue
}
issue := issues[0]
// remove file list from issue body
// TODO do this better and probably somewhere else
parts := strings.Split(issue.Body, "Files:")
issue.Body = parts[0]
fileList := []string{}
if len(parts) > 1 {
fileList = strings.Split(parts[1], ",")
}
// get file contents from local git repository
files := []llm.File{}
for _, path := range fileList {
path = strings.TrimSpace(path)
nextFile, err := p.vcClient.GetLocalFile(path)
if err != nil {
p.log.Error("error getting file from vcclient", zap.Error(err))
continue
}
files = append(files, nextFile)
}
changeRequest := llm.CodeChangeRequest{
Subject: issue.Subject,
Body: issue.Body,
IssueID: issue.ID,
Files: files,
}
changeResponse, err := p.openAIClient.EvaluateCCR(p.ctx, changeRequest)
if err != nil {
p.log.Error("error getting response from openai", zap.Error(err))
continue
}
// parse llm response
//codeChangeResponse := llm.ParseCodeChangeResponse(llmResponse)
// create commit with file changes
err = p.vcClient.StartCommit()
if err != nil {
p.log.Error("error starting commit", zap.Error(err))
continue
}
for _, f := range changeResponse.Files {
err = p.vcClient.ReplaceOrAddLocalFile(f)
if err != nil {
p.log.Error("error replacing or adding file", zap.Error(err))
continue
}
}
commitMessage := changeRequest.Subject + "\n\n" + changeResponse.Notes + "\n\nResolves: #" + changeRequest.IssueID
err = p.vcClient.FinishCommit(commitMessage)
if err != nil {
p.log.Error("error finshing commit", zap.Error(err))
continue
}
// open code change request
_, url, err := p.vcClient.OpenCodeChangeRequest(changeRequest, changeResponse)
if err != nil {
p.log.Error("error opening PR", zap.Error(err))
}
p.log.Info("successfully created PR", zap.String("URL", url))
p.log.Info("going to sleep for five mins")
time.Sleep(5 * time.Minute)
}
return nil
}
// PickIssueToFile is the same as PickIssue, but the changeRequest is converted to a string and written to a file. // PickIssueToFile is the same as PickIssue, but the changeRequest is converted to a string and written to a file.
func (p *PullPal) PickIssueToFile(listIssueOptions vc.ListIssueOptions, promptPath string) (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) { func (p *PullPal) PickIssueToFile(promptPath string) (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) {
issue, changeRequest, err = p.PickIssue(listIssueOptions) issue, changeRequest, err = p.PickIssue()
if err != nil { if err != nil {
return issue, changeRequest, err return issue, changeRequest, err
} }
@ -69,8 +166,8 @@ func (p *PullPal) PickIssueToFile(listIssueOptions vc.ListIssueOptions, promptPa
} }
// PickIssueToClipboard is the same as PickIssue, but the changeRequest is converted to a string and copied to the clipboard. // PickIssueToClipboard is the same as PickIssue, but the changeRequest is converted to a string and copied to the clipboard.
func (p *PullPal) PickIssueToClipboard(listIssueOptions vc.ListIssueOptions) (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) { func (p *PullPal) PickIssueToClipboard() (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) {
issue, changeRequest, err = p.PickIssue(listIssueOptions) issue, changeRequest, err = p.PickIssue()
if err != nil { if err != nil {
return issue, changeRequest, err return issue, changeRequest, err
} }
@ -85,9 +182,9 @@ func (p *PullPal) PickIssueToClipboard(listIssueOptions vc.ListIssueOptions) (is
} }
// PickIssue selects an issue from the version control server and returns the selected issue, as well as the LLM prompt needed to fulfill the request. // PickIssue selects an issue from the version control server and returns the selected issue, as well as the LLM prompt needed to fulfill the request.
func (p *PullPal) PickIssue(listIssueOptions vc.ListIssueOptions) (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) { func (p *PullPal) PickIssue() (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) {
// TODO I should be able to pass in settings for listing issues from here // TODO I should be able to pass in settings for listing issues from here
issues, err := p.vcClient.ListOpenIssues(listIssueOptions) issues, err := p.vcClient.ListOpenIssues(p.listIssueOptions)
if err != nil { if err != nil {
return issue, changeRequest, err return issue, changeRequest, err
} }