mirror of
https://github.com/Pull-Pal/pull-pal.git
synced 2024-09-07 12:34:16 -04:00
add basic run command
This commit is contained in:
parent
50db4d6d0e
commit
99bd1929b5
28
cmd/root.go
28
cmd/root.go
@ -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
32
cmd/run.go
Normal 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)
|
||||||
|
}
|
@ -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,14 +15,18 @@ 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
|
||||||
// * Parsing LLM responses
|
// * Parsing LLM responses
|
||||||
// * Interacting with LLM (e.g. with GPT via OpenAI API)
|
// * Interacting with LLM (e.g. with GPT via OpenAI API)
|
||||||
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
|
||||||
@ -40,8 +45,9 @@ 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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user