mirror of
https://github.com/Pull-Pal/pull-pal.git
synced 2024-11-10 06:16:45 -05:00
now it works somewhat reliably
This commit is contained in:
parent
e4918fcb36
commit
eea53db3a2
32
cmd/debug.go
Normal file
32
cmd/debug.go
Normal file
@ -0,0 +1,32 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var debugGitCmd = &cobra.Command{
|
||||
Use: "debug-git",
|
||||
Short: "debug git functionality",
|
||||
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.DebugGit()
|
||||
if err != nil {
|
||||
fmt.Println("err debugging git", err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(debugGitCmd)
|
||||
}
|
@ -74,8 +74,14 @@ func (res CodeChangeResponse) String() string {
|
||||
func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse {
|
||||
sections := strings.Split(llmResponse, "Notes:")
|
||||
|
||||
filesSection := sections[0]
|
||||
notes := strings.TrimSpace(sections[1])
|
||||
filesSection := ""
|
||||
if len(sections) > 0 {
|
||||
filesSection = sections[0]
|
||||
}
|
||||
notes := ""
|
||||
if len(sections) > 1 {
|
||||
notes = strings.TrimSpace(sections[1])
|
||||
}
|
||||
|
||||
files := parseFiles(filesSection)
|
||||
|
||||
@ -88,6 +94,9 @@ func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse {
|
||||
// parseFiles process the "files" subsection of the LLM's response. It is a helper for GetCodeChangeResponse.
|
||||
func parseFiles(filesSection string) []File {
|
||||
fileStringList := strings.Split(filesSection, "name:")
|
||||
if len(fileStringList) < 2 {
|
||||
return []File{}
|
||||
}
|
||||
// first item in the list is just gonna be "Files:"
|
||||
fileStringList = fileStringList[1:]
|
||||
|
||||
@ -99,6 +108,9 @@ func parseFiles(filesSection string) []File {
|
||||
fileList := make([]File, len(fileStringList))
|
||||
for i, f := range fileStringList {
|
||||
fileParts := strings.Split(f, "contents:")
|
||||
if len(fileParts) < 2 {
|
||||
continue
|
||||
}
|
||||
path := replacer.Replace(fileParts[0])
|
||||
path = strings.TrimSpace(path)
|
||||
|
||||
|
@ -41,7 +41,7 @@ func (oc *OpenAIClient) EvaluateCCR(ctx context.Context, req CodeChangeRequest)
|
||||
// TODO use different choices/different options in different branches/worktrees?
|
||||
choice := resp.Choices[0].Message.Content
|
||||
|
||||
oc.log.Debug("got response from llm", zap.String("output", choice))
|
||||
oc.log.Info("got response from llm", zap.String("output", choice))
|
||||
|
||||
return ParseCodeChangeResponse(choice), nil
|
||||
}
|
||||
|
5
main.go
5
main.go
@ -3,5 +3,10 @@ package main
|
||||
import "github.com/mobyvb/pull-pal/cmd"
|
||||
|
||||
func main() {
|
||||
/*
|
||||
response := "Files:\n\n - name: main.go\n contents:\n ```go\n package main\n\n import (\n \"fmt\"\n \"log\"\n \"net/http\"\n )\n\n func main() {\n http.Handle(\"/\", http.FileServer(http.Dir(\"./\")))\n fmt.Println(\"Server listening on :7777\")\n log.Fatal(http.ListenAndServe(\":7777\", nil))\n }\n ```\n\n - name: index.html\n contents:\n ```html\n <!DOCTYPE html>\n <html>\n <head>\n <title>Pull Pal</title>\n <style>\n body {\n font-family: sans-serif;\n }\n .content {\n background-color: #f2f2f2;\n border-radius: 10px;\n box-shadow: 2px 2px 10px rgba(0,0,0,0.2);\n padding: 20px;\n margin: 20px;\n }\n h1 {\n color: #3399cc;\n background-color: #f2f2f2;\n text-align: center;\n padding: 10px;\n border-radius: 10px;\n box-shadow: 2px 2px 10px rgba(0,0,0,0.2);\n }\n </style>\n </head>\n <body>\n <div class=\"content\">\n <h1>Introducing Pull Pal!</h1>\n <p>Pull Pal is a digital assistant that can monitor your Github repositories and create pull requests using the power of artificial intelligence. Say goodbye to manual pull request creation like it's 2005!</p>\n <p>Sign up now to start automating your workflow and get more done in less time. </p>\n </div>\n </body>\n </html>\n ```\n\nNotes:\n- Added a basic HTTP server in main.go which serves index.html from the root path on port 7777\n- Added basic HTML structure and CSS styling to index.html, using sans-serif font and a soft color scheme with blue as the main accent color\n- Added a content container with an off-white background, rounded corners, and a box shadow, and a centered heading with blue font color and an off-white background to draw attention to the product name and value proposition"
|
||||
res := llm.ParseCodeChangeResponse(response)
|
||||
fmt.Println(res.String())
|
||||
*/
|
||||
cmd.Execute()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -63,17 +64,35 @@ func (p *PullPal) Run() error {
|
||||
issues, err := p.ghClient.ListOpenIssues(p.listIssueOptions)
|
||||
if err != nil {
|
||||
p.log.Error("error listing issues", zap.Error(err))
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
// todo don't sleep
|
||||
p.log.Info("no issues found. sleeping for 5 mins")
|
||||
time.Sleep(5 * time.Minute)
|
||||
p.log.Info("no issues found. sleeping for 30 seconds")
|
||||
time.Sleep(30 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
issue := issues[0]
|
||||
issueNumber, err := strconv.Atoi(issue.ID)
|
||||
if err != nil {
|
||||
p.log.Error("error converting issue ID to int", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.ghClient.CommentOnIssue(issueNumber, "on it")
|
||||
if err != nil {
|
||||
p.log.Error("error commenting on issue", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
for _, label := range p.listIssueOptions.Labels {
|
||||
err = p.ghClient.RemoveLabelFromIssue(issueNumber, label)
|
||||
if err != nil {
|
||||
p.log.Error("error removing labels from issue", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// remove file list from issue body
|
||||
// TODO do this better and probably somewhere else
|
||||
@ -115,38 +134,43 @@ func (p *PullPal) Run() error {
|
||||
//codeChangeResponse := llm.ParseCodeChangeResponse(llmResponse)
|
||||
|
||||
// create commit with file changes
|
||||
newBranchName := fmt.Sprintf("fix-%s", changeRequest.IssueID)
|
||||
err = p.localGitClient.SwitchBranch(newBranchName)
|
||||
if err != nil {
|
||||
p.log.Error("error switching branch", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
err = p.localGitClient.StartCommit()
|
||||
//err = p.ghClient.StartCommit()
|
||||
if err != nil {
|
||||
p.log.Error("error starting commit", zap.Error(err))
|
||||
continue
|
||||
return err
|
||||
}
|
||||
newBranchName := fmt.Sprintf("fix-%s", changeRequest.IssueID)
|
||||
/*
|
||||
err = p.localGitClient.SwitchBranch(newBranchName)
|
||||
if err != nil {
|
||||
p.log.Error("error switching branch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
*/
|
||||
for _, f := range changeResponse.Files {
|
||||
p.log.Info("replacing or adding file", zap.String("path", f.Path), zap.String("contents", f.Contents))
|
||||
err = p.localGitClient.ReplaceOrAddLocalFile(f)
|
||||
// err = p.ghClient.ReplaceOrAddLocalFile(f)
|
||||
if err != nil {
|
||||
p.log.Error("error replacing or adding file", zap.Error(err))
|
||||
continue
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commitMessage := changeRequest.Subject + "\n\n" + changeResponse.Notes + "\n\nResolves: #" + changeRequest.IssueID
|
||||
p.log.Info("about to create commit", zap.String("message", commitMessage))
|
||||
err = p.localGitClient.FinishCommit(commitMessage)
|
||||
if err != nil {
|
||||
p.log.Error("error finshing commit", zap.Error(err))
|
||||
continue
|
||||
p.log.Error("error finishing commit", zap.Error(err))
|
||||
// TODO figure out why sometimes finish commit returns "already up-to-date error"
|
||||
// return err
|
||||
}
|
||||
|
||||
err = p.localGitClient.PushBranch(newBranchName)
|
||||
if err != nil {
|
||||
p.log.Error("error finshing commit", zap.Error(err))
|
||||
continue
|
||||
p.log.Error("error pushing branch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// open code change request
|
||||
@ -154,11 +178,12 @@ func (p *PullPal) Run() error {
|
||||
_, url, err := p.ghClient.OpenCodeChangeRequest(changeRequest, changeResponse, newBranchName, "main")
|
||||
if err != nil {
|
||||
p.log.Error("error opening PR", zap.Error(err))
|
||||
return 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)
|
||||
p.log.Info("going to sleep for thirty seconds")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
47
pullpal/debug.go
Normal file
47
pullpal/debug.go
Normal file
@ -0,0 +1,47 @@
|
||||
package pullpal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mobyvb/pull-pal/llm"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (p *PullPal) DebugGit() error {
|
||||
p.log.Info("Starting Pull Pal git debug")
|
||||
|
||||
// create commit with file changes
|
||||
err := p.localGitClient.StartCommit()
|
||||
//err = p.ghClient.StartCommit()
|
||||
if err != nil {
|
||||
p.log.Error("error starting commit", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
newBranchName := fmt.Sprintf("debug-branch")
|
||||
|
||||
for _, f := range []string{"a", "b"} {
|
||||
err = p.localGitClient.ReplaceOrAddLocalFile(llm.File{
|
||||
Path: f,
|
||||
Contents: "hello",
|
||||
})
|
||||
if err != nil {
|
||||
p.log.Error("error replacing or adding file", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commitMessage := "debug commit message"
|
||||
err = p.localGitClient.FinishCommit(commitMessage)
|
||||
if err != nil {
|
||||
p.log.Error("error finishing commit", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.localGitClient.PushBranch(newBranchName)
|
||||
if err != nil {
|
||||
p.log.Error("error pushing branch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
47
vc/git.go
47
vc/git.go
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
)
|
||||
@ -59,24 +58,44 @@ func NewLocalGitClient( /*ctx context.Context, log *zap.Logger, */ self Author,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (gc *LocalGitClient) SwitchBranch(branchName string) (err error) {
|
||||
branchRefName := plumbing.NewBranchReferenceName(branchName)
|
||||
remoteName := "origin"
|
||||
if gc.worktree == nil {
|
||||
return errors.New("worktree is nil - cannot check out a branch")
|
||||
}
|
||||
|
||||
err = gc.repo.localRepo.CreateBranch(&config.Branch{
|
||||
Name: branchName,
|
||||
Remote: remoteName,
|
||||
Merge: branchRefName,
|
||||
branchRefName := plumbing.NewBranchReferenceName(branchName)
|
||||
// remoteName := "origin"
|
||||
|
||||
err = gc.repo.localRepo.Fetch(&git.FetchOptions{
|
||||
RefSpecs: []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gc.worktree.Checkout(&git.CheckoutOptions{
|
||||
Branch: branchRefName,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gc.repo.localRepo.CreateBranch(&config.Branch{
|
||||
Name: branchName,
|
||||
Remote: remoteName,
|
||||
Merge: branchRefName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (gc *LocalGitClient) PushBranch(branchName string) (err error) {
|
||||
branchRefName := plumbing.NewBranchReferenceName(branchName)
|
||||
//branchRefName := plumbing.NewBranchReferenceName(branchName)
|
||||
remoteName := "origin"
|
||||
|
||||
// Push the new branch to the remote repository
|
||||
@ -86,7 +105,9 @@ func (gc *LocalGitClient) PushBranch(branchName string) (err error) {
|
||||
}
|
||||
|
||||
err = remote.Push(&git.PushOptions{
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(fmt.Sprintf("%s:refs/heads/%s", branchRefName, branchName))},
|
||||
RemoteName: remoteName,
|
||||
// TODO remove hardcoded "main"
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/heads/%s", "main", branchName))},
|
||||
Auth: &http.BasicAuth{
|
||||
Username: gc.self.Handle,
|
||||
Password: gc.self.Token,
|
||||
@ -146,9 +167,13 @@ func (gc *LocalGitClient) ReplaceOrAddLocalFile(newFile llm.File) error {
|
||||
if strings.HasSuffix(newFile.Path, ".go") {
|
||||
newContents, err := format.Source([]byte(newFile.Contents))
|
||||
if err != nil {
|
||||
return err
|
||||
// TODO also make logger accessible
|
||||
fmt.Println("go format error")
|
||||
// TODO handle this error
|
||||
// return err
|
||||
} else {
|
||||
newFile.Contents = string(newContents)
|
||||
}
|
||||
newFile.Contents = string(newContents)
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(gc.repo.LocalPath, newFile.Path)
|
||||
|
33
vc/github.go
33
vc/github.go
@ -208,6 +208,39 @@ func (gc *GithubClient) ListOpenIssues(options ListIssueOptions) ([]Issue, error
|
||||
return toReturn, nil
|
||||
}
|
||||
|
||||
// CommentOnIssue adds a comment to the issue provided.
|
||||
func (gc *GithubClient) CommentOnIssue(issueNumber int, comment string) error {
|
||||
ghComment := &github.IssueComment{
|
||||
Body: github.String(comment),
|
||||
}
|
||||
|
||||
_, _, err := gc.client.Issues.CreateComment(gc.ctx, gc.repo.Owner.Handle, gc.repo.Name, issueNumber, ghComment)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveLabelFromIssue removes the provided label from an issue if that label is applied.
|
||||
func (gc *GithubClient) RemoveLabelFromIssue(issueNumber int, label string) error {
|
||||
hasLabel := false
|
||||
labels, _, err := gc.client.Issues.ListLabelsByIssue(gc.ctx, gc.repo.Owner.Handle, gc.repo.Name, issueNumber, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range labels {
|
||||
if l.GetName() == label {
|
||||
hasLabel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasLabel {
|
||||
_, err = gc.client.Issues.RemoveLabelForIssue(gc.ctx, gc.repo.Owner.Handle, gc.repo.Name, issueNumber, label)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListOpenComments lists unresolved comments in the Github repository.
|
||||
func (gc *GithubClient) ListOpenComments(options ListCommentOptions) ([]Comment, error) {
|
||||
toReturn := []Comment{}
|
||||
|
Loading…
Reference in New Issue
Block a user