From 93ef37bba603239081e395ca10cd01179482b85d Mon Sep 17 00:00:00 2001 From: William Petit Date: Sun, 23 Feb 2025 13:57:51 +0100 Subject: [PATCH] feat: include file content in llm prompt context (#13) --- internal/adapter/gitea/forge.go | 73 +++++++++++++------------- internal/core/port/forge.go | 1 + internal/core/service/issue_manager.go | 38 ++++++++++++++ 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/internal/adapter/gitea/forge.go b/internal/adapter/gitea/forge.go index 49b3565..2420eb9 100644 --- a/internal/adapter/gitea/forge.go +++ b/internal/adapter/gitea/forge.go @@ -18,14 +18,28 @@ type Forge struct { client *gitea.Client } -// GetProject implements port.Forge. -func (f *Forge) GetProject(ctx context.Context, rawProjectID string) (*model.Project, error) { - projectID, err := strconv.ParseInt(rawProjectID, 10, 64) +// GetFile implements port.Forge. +func (f *Forge) GetFile(ctx context.Context, rawProjectID string, path string) ([]byte, error) { + project, err := f.getProject(rawProjectID) if err != nil { return nil, errors.WithStack(err) } - project, _, err := f.client.GetRepoByID(projectID) + data, res, err := f.client.GetFile(project.Owner.UserName, project.Name, project.DefaultBranch, path) + if err != nil { + if res.StatusCode == http.StatusNotFound { + return nil, errors.WithStack(port.ErrFileNotFound) + } + + return nil, errors.WithStack(err) + } + + return data, nil +} + +// GetProject implements port.Forge. +func (f *Forge) GetProject(ctx context.Context, rawProjectID string) (*model.Project, error) { + project, err := f.getProject(rawProjectID) if err != nil { return nil, errors.WithStack(err) } @@ -39,12 +53,7 @@ func (f *Forge) GetProject(ctx context.Context, rawProjectID string) (*model.Pro // GetProjectLanguages implements port.Forge. func (f *Forge) GetProjectLanguages(ctx context.Context, rawProjectID string) ([]string, error) { - projectID, err := strconv.ParseInt(rawProjectID, 10, 64) - if err != nil { - return nil, errors.WithStack(err) - } - - project, _, err := f.client.GetRepoByID(projectID) + project, err := f.getProject(rawProjectID) if err != nil { return nil, errors.WithStack(err) } @@ -65,12 +74,7 @@ func (f *Forge) GetProjectLanguages(ctx context.Context, rawProjectID string) ([ // CreateIssue implements port.Forge. func (f *Forge) CreateIssue(ctx context.Context, rawProjectID string, title string, body string) (string, error) { - projectID, err := strconv.ParseInt(rawProjectID, 10, 64) - if err != nil { - return "", errors.WithStack(err) - } - - project, _, err := f.client.GetRepoByID(projectID) + project, err := f.getProject(rawProjectID) if err != nil { return "", errors.WithStack(err) } @@ -88,36 +92,17 @@ func (f *Forge) CreateIssue(ctx context.Context, rawProjectID string, title stri // GetIssueTemplate implements port.Forge. func (f *Forge) GetIssueTemplate(ctx context.Context, rawProjectID string) (string, error) { - projectID, err := strconv.ParseInt(rawProjectID, 10, 64) + data, err := f.GetFile(ctx, rawProjectID, ".gitea/issue_template.md") if err != nil { return "", errors.WithStack(err) } - project, _, err := f.client.GetRepoByID(projectID) - if err != nil { - return "", errors.WithStack(err) - } - - data, res, err := f.client.GetFile(project.Owner.UserName, project.Name, project.DefaultBranch, ".gitea/issue_template.md") - if err != nil { - if res.StatusCode == http.StatusNotFound { - return "", errors.WithStack(port.ErrFileNotFound) - } - - return "", errors.WithStack(err) - } - return string(data), nil } // GetIssues implements port.Forge. func (f *Forge) GetIssues(ctx context.Context, rawProjectID string, issueIDs ...string) ([]*model.Issue, error) { - projectID, err := strconv.ParseInt(rawProjectID, 10, 64) - if err != nil { - return nil, errors.WithStack(err) - } - - project, _, err := f.client.GetRepoByID(projectID) + project, err := f.getProject(rawProjectID) if err != nil { return nil, errors.WithStack(err) } @@ -186,6 +171,20 @@ func (f *Forge) GetAllProjects(ctx context.Context) ([]*model.Project, error) { return projects, nil } +func (f *Forge) getProject(rawProjectID string) (*gitea.Repository, error) { + projectID, err := strconv.ParseInt(rawProjectID, 10, 64) + if err != nil { + return nil, errors.WithStack(err) + } + + project, _, err := f.client.GetRepoByID(projectID) + if err != nil { + return nil, errors.WithStack(err) + } + + return project, nil +} + func NewForge(client *gitea.Client) *Forge { return &Forge{client: client} } diff --git a/internal/core/port/forge.go b/internal/core/port/forge.go index 02f563d..c742252 100644 --- a/internal/core/port/forge.go +++ b/internal/core/port/forge.go @@ -18,4 +18,5 @@ type Forge interface { GetIssueTemplate(ctx context.Context, projectID string) (string, error) GetProjectLanguages(ctx context.Context, projectID string) ([]string, error) GetProject(ctx context.Context, projectID string) (*model.Project, error) + GetFile(ctx context.Context, projectID string, path string) ([]byte, error) } diff --git a/internal/core/service/issue_manager.go b/internal/core/service/issue_manager.go index 4e71f3c..667a59f 100644 --- a/internal/core/service/issue_manager.go +++ b/internal/core/service/issue_manager.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "path/filepath" "regexp" "strings" "time" @@ -220,6 +221,13 @@ func (m *IssueManager) extractResources(ctx context.Context, forge port.Forge, p resources = append(resources, issues...) + files, err := m.extractFiles(ctx, forge, projectID, issueSummary) + if err != nil { + return nil, errors.Wrap(err, "could not extract files") + } + + resources = append(resources, files...) + return resources, nil } @@ -256,6 +264,36 @@ func (m *IssueManager) extractIssues(ctx context.Context, forge port.Forge, proj return resources, nil } +var fileRefRegExp = regexp.MustCompile(`(?i)(?:\/[^\/]+)+\/?[^\s]+(?:\.[^\s]+)+|[^\s]+(?:\.[^\s]+)+`) + +func (m *IssueManager) extractFiles(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) { + fileRefMatches := fileRefRegExp.FindAllStringSubmatch(issueSummary, -1) + + paths := make([]string, 0, len(fileRefMatches)) + for _, m := range fileRefMatches { + paths = append(paths, m[0]) + } + + resources := make([]*model.Resource, 0) + + for _, p := range paths { + data, err := forge.GetFile(ctx, projectID, p) + if err != nil { + slog.ErrorContext(ctx, "could not retrieve file", slog.Any("error", errors.WithStack(err)), slog.String("path", p)) + continue + } + + resources = append(resources, &model.Resource{ + Name: p, + Type: "File", + Syntax: strings.TrimPrefix(filepath.Ext(p), "."), + Content: string(data), + }) + } + + return resources, nil +} + func NewIssueManager(llmClient llm.Client, forgeFactories ...ForgeFactory) *IssueManager { return &IssueManager{ llmClient: llmClient,