diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6c31ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +pal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b17906b --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +SHELL := /bin/bash + +build: + go build -v -o pal ./cmd + +run: + go run ./cmd/ diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..d8fa929 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bc8c058 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/mobyvb/pull-pal + +go 1.20 diff --git a/pullpal/common.go b/pullpal/common.go new file mode 100644 index 0000000..9209c4f --- /dev/null +++ b/pullpal/common.go @@ -0,0 +1,115 @@ +package pullpal + +import ( + "bytes" + "strings" + "text/template" +) + +// PullPal is the service responsible for: +// * Interacting with git server (e.g. reading issues and making PRs on Github) +// * Generating LLM prompts +// * Parsing LLM responses +// * Interacting with LLM (e.g. with GPT via OpenAI API) +type PullPal struct { +} + +// File represents a file in a git repository. +type File struct { + Path string + Contents string +} + +// CodeChangeRequest contains all necessary information for generating a prompt for a LLM. +type CodeChangeRequest struct { + Files []File + Subject string + Body string +} + +// String is the string representation of a CodeChangeRequest. Functionally, it contains the LLM prompt. +func (req CodeChangeRequest) String() string { + prompt := req.MustGetPrompt() + return "START OF PROMPT\n" + prompt + "\nEND OF PROMPT" +} + +// MustGetPrompt only returns the prompt, but panics if the data in the request cannot populate the template. +func (req CodeChangeRequest) MustGetPrompt() string { + prompt, err := req.GetPrompt() + if err != nil { + panic(err) + } + return prompt +} + +// GetPrompt converts the information in the request to a prompt for an LLM. +func (req CodeChangeRequest) GetPrompt() (string, error) { + tmpl, err := template.ParseFiles("../template/code-change-request.tmpl") + if err != nil { + return "", err + } + + var result bytes.Buffer + err = tmpl.Execute(&result, req) + if err != nil { + return "", err + } + + return result.String(), nil +} + +// CodeChangeResponse contains data derived from an LLM response to a prompt generated via a CodeChangeRequest. +type CodeChangeResponse struct { + Files []File + Notes string +} + +// String is a string representation of CodeChangeResponse. +func (res CodeChangeResponse) String() string { + out := "Notes:\n" + out += res.Notes + "\n\n" + out += "Files:\n" + for _, f := range res.Files { + out += f.Path + ":\n```\n" + out += f.Contents + "\n```\n" + } + + return out +} + +// ParseCodeChangeResponse parses the LLM's response to CodeChangeRequest (string) into a CodeChangeResponse. +func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse { + sections := strings.Split(llmResponse, "Notes:") + + filesSection := sections[0] + notes := strings.TrimSpace(sections[1]) + + files := parseFiles(filesSection) + + return CodeChangeResponse{ + Files: files, + Notes: notes, + } +} + +// 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:") + // first item in the list is just gonna be "Files:" + fileStringList = fileStringList[1:] + + fileList := make([]File, len(fileStringList)) + for i, f := range fileStringList { + fileParts := strings.Split(f, "contents:") + path := strings.TrimSpace(fileParts[0]) + contents := strings.TrimSpace(fileParts[1]) + contents = strings.Trim(contents, "```") + + fileList[i] = File{ + Path: path, + Contents: contents, + } + } + + return fileList +} diff --git a/template/code-change-request.tmpl b/template/code-change-request.tmpl new file mode 100644 index 0000000..9648bbd --- /dev/null +++ b/template/code-change-request.tmpl @@ -0,0 +1,26 @@ +Files: +{{ range $index, $file := .Files }} + - name: {{ $file.Path }}: + contents: + ``` +{{ $file.Contents }} + ``` +{{ end }} + +Modify the files above to accomplish the following task: +Subject: {{ .Subject }} +Body: +{{ .Body }} + +Respond in the exact format: +Files: +{{ range $index, $file := .Files }} + - name: {{ $file.Path }} + contents: + ``` + [new {{ $file.Path }} contents] + ``` +{{ end }} + +Notes: +[additional context about your changes]