diff --git a/pkg/agentic/pr.go b/pkg/agentic/pr.go index 1406365..986fedb 100644 --- a/pkg/agentic/pr.go +++ b/pkg/agentic/pr.go @@ -3,13 +3,12 @@ package agentic import ( - "bytes" "context" - "encoding/json" - "net/http" "os/exec" core "dappco.re/go/core" + "dappco.re/go/core/forge" + forge_types "dappco.re/go/core/forge/types" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -164,53 +163,20 @@ func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string { } func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base, title, body string) (string, int, error) { - payload, _ := json.Marshal(map[string]any{ - "title": title, - "body": body, - "head": head, - "base": base, + pr, err := s.forge.Pulls.Create(ctx, forge.Params{"owner": org, "repo": repo}, &forge_types.CreatePullRequestOption{ + Title: title, + Body: body, + Head: head, + Base: base, }) - - url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls", s.forgeURL, org, repo) - req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) if err != nil { - return "", 0, core.E("forgeCreatePR", "request failed", err) + return "", 0, core.E("forgeCreatePR", "create PR failed", err) } - defer resp.Body.Close() - - if resp.StatusCode != 201 { - var errBody map[string]any - json.NewDecoder(resp.Body).Decode(&errBody) - msg, _ := errBody["message"].(string) - return "", 0, core.E("forgeCreatePR", core.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil) - } - - var pr struct { - Number int `json:"number"` - HTMLURL string `json:"html_url"` - } - json.NewDecoder(resp.Body).Decode(&pr) - - return pr.HTMLURL, pr.Number, nil + return pr.HTMLURL, int(pr.Index), nil } func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) { - payload, _ := json.Marshal(map[string]string{"body": comment}) - - url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/comments", s.forgeURL, org, repo, issue) - req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) - if err != nil { - return - } - resp.Body.Close() + s.forge.Issues.CreateComment(ctx, org, repo, int64(issue), comment) } // --- agentic_list_prs --- @@ -309,54 +275,30 @@ func (s *PrepSubsystem) listPRs(ctx context.Context, _ *mcp.CallToolRequest, inp } func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string) ([]PRInfo, error) { - url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls?state=%s&limit=10", - s.forgeURL, org, repo, state) - req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) + prs, err := s.forge.Pulls.ListAll(ctx, forge.Params{"owner": org, "repo": repo}) if err != nil { return nil, core.E("listRepoPRs", "failed to list PRs for "+repo, err) } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, core.E("listRepoPRs", core.Sprintf("HTTP %d listing PRs for %s", resp.StatusCode, repo), nil) - } - - var prs []struct { - Number int `json:"number"` - Title string `json:"title"` - State string `json:"state"` - Mergeable bool `json:"mergeable"` - HTMLURL string `json:"html_url"` - Head struct { - Ref string `json:"ref"` - } `json:"head"` - Base struct { - Ref string `json:"ref"` - } `json:"base"` - User struct { - Login string `json:"login"` - } `json:"user"` - Labels []struct { - Name string `json:"name"` - } `json:"labels"` - } - json.NewDecoder(resp.Body).Decode(&prs) var result []PRInfo for _, pr := range prs { + if state != "" && state != "all" && string(pr.State) != state { + continue + } var labels []string for _, l := range pr.Labels { labels = append(labels, l.Name) } + author := "" + if pr.User != nil { + author = pr.User.UserName + } result = append(result, PRInfo{ Repo: repo, - Number: pr.Number, + Number: int(pr.Index), Title: pr.Title, - State: pr.State, - Author: pr.User.Login, + State: string(pr.State), + Author: author, Branch: pr.Head.Ref, Base: pr.Base.Ref, Labels: labels, diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index 8b8c018..6ba1566 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -16,6 +16,7 @@ import ( "dappco.re/go/agent/pkg/lib" core "dappco.re/go/core" + "dappco.re/go/core/forge" coremcp "forge.lthn.ai/core/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" "gopkg.in/yaml.v3" @@ -34,6 +35,7 @@ type CompletionNotifier interface { // sub := agentic.NewPrep() // sub.RegisterTools(server) type PrepSubsystem struct { + forge *forge.Forge forgeURL string forgeToken string brainURL string @@ -65,8 +67,11 @@ func NewPrep() *PrepSubsystem { } } + forgeURL := envOr("FORGE_URL", "https://forge.lthn.ai") + return &PrepSubsystem{ - forgeURL: envOr("FORGE_URL", "https://forge.lthn.ai"), + forge: forge.NewForge(forgeURL, forgeToken), + forgeURL: forgeURL, forgeToken: forgeToken, brainURL: envOr("CORE_BRAIN_URL", "https://api.lthn.sh"), brainKey: brainKey, @@ -257,6 +262,22 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques return nil, out, nil } +// --- Public API for CLI testing --- + +// TestPrepWorkspace exposes prepWorkspace for CLI testing. +// +// _, out, err := prep.TestPrepWorkspace(ctx, input) +func (s *PrepSubsystem) TestPrepWorkspace(ctx context.Context, input PrepInput) (*mcp.CallToolResult, PrepOutput, error) { + return s.prepWorkspace(ctx, nil, input) +} + +// TestBuildPrompt exposes buildPrompt for CLI testing. +// +// prompt, memories, consumers := prep.TestBuildPrompt(ctx, input, "dev", repoPath) +func (s *PrepSubsystem) TestBuildPrompt(ctx context.Context, input PrepInput, branch, repoPath string) (string, int, int) { + return s.buildPrompt(ctx, input, branch, repoPath) +} + // --- Prompt Building --- // buildPrompt assembles all context into a single prompt string. @@ -348,30 +369,12 @@ func (s *PrepSubsystem) buildPrompt(ctx context.Context, input PrepInput, branch // --- Context Helpers (return strings, not write files) --- func (s *PrepSubsystem) getIssueBody(ctx context.Context, org, repo string, issue int) string { - if s.forgeToken == "" { + idx := core.Sprintf("%d", issue) + iss, err := s.forge.Issues.Get(ctx, forge.Params{"owner": org, "repo": repo, "index": idx}) + if err != nil { return "" } - - url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", s.forgeURL, org, repo, issue) - req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { - if resp != nil { - resp.Body.Close() - } - return "" - } - defer resp.Body.Close() - - var issueData struct { - Title string `json:"title"` - Body string `json:"body"` - } - json.NewDecoder(resp.Body).Decode(&issueData) - - return core.Sprintf("# %s\n\n%s", issueData.Title, issueData.Body) + return core.Sprintf("# %s\n\n%s", iss.Title, iss.Body) } func (s *PrepSubsystem) brainRecall(ctx context.Context, repo string) (string, int) { @@ -473,64 +476,26 @@ func (s *PrepSubsystem) getGitLog(repoPath string) string { } func (s *PrepSubsystem) pullWikiContent(ctx context.Context, org, repo string) string { - if s.forgeToken == "" { + pages, err := s.forge.Wiki.ListPages(ctx, org, repo) + if err != nil || len(pages) == 0 { return "" } - url := core.Sprintf("%s/api/v1/repos/%s/%s/wiki/pages", s.forgeURL, org, repo) - req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { - if resp != nil { - resp.Body.Close() - } - return "" - } - defer resp.Body.Close() - - var pages []struct { - Title string `json:"title"` - SubURL string `json:"sub_url"` - } - json.NewDecoder(resp.Body).Decode(&pages) - b := core.NewBuilder() - for _, page := range pages { - subURL := page.SubURL - if subURL == "" { - subURL = page.Title + for _, meta := range pages { + name := meta.SubURL + if name == "" { + name = meta.Title } - - pageURL := core.Sprintf("%s/api/v1/repos/%s/%s/wiki/page/%s", s.forgeURL, org, repo, subURL) - pageReq, _ := http.NewRequestWithContext(ctx, "GET", pageURL, nil) - pageReq.Header.Set("Authorization", "token "+s.forgeToken) - - pageResp, pErr := s.client.Do(pageReq) - if pErr != nil || pageResp.StatusCode != 200 { - if pageResp != nil { - pageResp.Body.Close() - } + page, pErr := s.forge.Wiki.GetPage(ctx, org, repo, name) + if pErr != nil || page.ContentBase64 == "" { continue } - - var pageData struct { - ContentBase64 string `json:"content_base64"` - } - json.NewDecoder(pageResp.Body).Decode(&pageData) - pageResp.Body.Close() - - if pageData.ContentBase64 == "" { - continue - } - - content, _ := base64.StdEncoding.DecodeString(pageData.ContentBase64) - b.WriteString("### " + page.Title + "\n\n") + content, _ := base64.StdEncoding.DecodeString(page.ContentBase64) + b.WriteString("### " + meta.Title + "\n\n") b.WriteString(string(content)) b.WriteString("\n\n") } - return b.String() } diff --git a/pkg/agentic/scan.go b/pkg/agentic/scan.go index 91c3ee8..5e3cf5b 100644 --- a/pkg/agentic/scan.go +++ b/pkg/agentic/scan.go @@ -104,42 +104,14 @@ func (s *PrepSubsystem) scan(ctx context.Context, _ *mcp.CallToolRequest, input } func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string, error) { + repos, err := s.forge.Repos.ListOrgRepos(ctx, org) + if err != nil { + return nil, core.E("scan.listOrgRepos", "failed to list repos", err) + } + var allNames []string - page := 1 - - for { - u := core.Sprintf("%s/api/v1/orgs/%s/repos?limit=50&page=%d", s.forgeURL, org, page) - req, err := http.NewRequestWithContext(ctx, "GET", u, nil) - if err != nil { - return nil, core.E("scan.listOrgRepos", "failed to create request", err) - } - req.Header.Set("Authorization", "token "+s.forgeToken) - - resp, err := s.client.Do(req) - if err != nil { - return nil, core.E("scan.listOrgRepos", "failed to list repos", err) - } - - if resp.StatusCode != 200 { - resp.Body.Close() - return nil, core.E("scan.listOrgRepos", core.Sprintf("HTTP %d listing repos", resp.StatusCode), nil) - } - - var repos []struct { - Name string `json:"name"` - } - json.NewDecoder(resp.Body).Decode(&repos) - resp.Body.Close() - - for _, r := range repos { - allNames = append(allNames, r.Name) - } - - // If we got fewer than the limit, we've reached the last page - if len(repos) < 50 { - break - } - page++ + for _, r := range repos { + allNames = append(allNames, r.Name) } return allNames, nil }