diff --git a/google/mcp/README.md b/google/mcp/README.md deleted file mode 100644 index e776021..0000000 --- a/google/mcp/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Core CLI MCP Server - -This directory contains an MCP server that exposes the core CLI commands as tools for AI agents. - -## Tools - -### `core_go_test` - -Run Go tests. - -**Parameters:** - -- `filter` (string, optional): Filter tests by name. -- `coverage` (boolean, optional): Enable code coverage. Defaults to `false`. - -**Example:** - -```json -{ - "tool": "core_go_test", - "parameters": { - "filter": "TestMyFunction", - "coverage": true - } -} -``` - -### `core_dev_health` - -Check the health of the monorepo. - -**Parameters:** - -None. - -**Example:** - -```json -{ - "tool": "core_dev_health", - "parameters": {} -} -``` - -### `core_dev_commit` - -Commit changes across repositories. - -**Parameters:** - -- `message` (string, required): The commit message. -- `repos` (array of strings, optional): A list of repositories to commit to. - -**Example:** - -```json -{ - "tool": "core_dev_commit", - "parameters": { - "message": "feat: Implement new feature", - "repos": [ - "core-agent", - "another-repo" - ] - } -} -``` diff --git a/google/mcp/main.go b/google/mcp/main.go deleted file mode 100644 index 3ac576b..0000000 --- a/google/mcp/main.go +++ /dev/null @@ -1,131 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os/exec" - "strings" -) - -type GoTestRequest struct { - Filter string `json:"filter,omitempty"` - Coverage bool `json:"coverage,omitempty"` -} - -type GoTestResponse struct { - Output string `json:"output"` - Error string `json:"error,omitempty"` -} - -type DevHealthResponse struct { - Output string `json:"output"` - Error string `json:"error,omitempty"` -} - -type DevCommitRequest struct { - Message string `json:"message"` - Repos []string `json:"repos,omitempty"` -} - -type DevCommitResponse struct { - Output string `json:"output"` - Error string `json:"error,omitempty"` -} - -func goTestHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) - return - } - var req GoTestRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid request body", http.StatusBadRequest) - return - } - - args := []string{"go", "test"} - if req.Filter != "" { - args = append(args, "-run", req.Filter) - } - if req.Coverage { - args = append(args, "-cover") - } - - cmd := exec.Command("core", args...) - output, err := cmd.CombinedOutput() - - resp := GoTestResponse{ - Output: string(output), - } - if err != nil { - resp.Error = err.Error() - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} - -func devHealthHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) - return - } - cmd := exec.Command("core", "dev", "health") - output, err := cmd.CombinedOutput() - - resp := DevHealthResponse{ - Output: string(output), - } - if err != nil { - resp.Error = err.Error() - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} - -func devCommitHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) - return - } - var req DevCommitRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid request body", http.StatusBadRequest) - return - } - - args := []string{"dev", "commit", "-m", req.Message} - if len(req.Repos) > 0 { - args = append(args, "--repos", strings.Join(req.Repos, ",")) - } - - cmd := exec.Command("core", args...) - output, err := cmd.CombinedOutput() - - resp := DevCommitResponse{ - Output: string(output), - } - if err != nil { - resp.Error = err.Error() - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} - -func main() { - http.HandleFunc("/core_go_test", goTestHandler) - http.HandleFunc("/core_dev_health", devHealthHandler) - http.HandleFunc("/core_dev_commit", devCommitHandler) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "MCP Server is running") - }) - - log.Println("Starting MCP server on :8080") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Fatalf("could not start server: %s\n", err) - } -} diff --git a/google/mcp/main_test.go b/google/mcp/main_test.go deleted file mode 100644 index 6921d43..0000000 --- a/google/mcp/main_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" -) - -func TestMain(m *testing.M) { - // Get the absolute path to the testdata directory - wd, err := os.Getwd() - if err != nil { - panic(err) - } - testdataPath := filepath.Join(wd, "testdata") - - // Add the absolute path to the PATH - os.Setenv("PATH", testdataPath+":"+os.Getenv("PATH")) - m.Run() -} -func TestGoTestHandler(t *testing.T) { - reqBody := GoTestRequest{ - Filter: "TestMyFunction", - Coverage: true, - } - body, _ := json.Marshal(reqBody) - - req, err := http.NewRequest("POST", "/core_go_test", bytes.NewBuffer(body)) - if err != nil { - t.Fatal(err) - } - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(goTestHandler) - - handler.ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - var resp GoTestResponse - if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { - t.Fatalf("could not decode response: %v", err) - } - - if resp.Error != "" { - t.Errorf("handler returned an unexpected error: %v", resp.Error) - } -} - -func TestDevHealthHandler(t *testing.T) { - req, err := http.NewRequest("POST", "/core_dev_health", nil) - if err != nil { - t.Fatal(err) - } - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(devHealthHandler) - - handler.ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - var resp DevHealthResponse - if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { - t.Fatalf("could not decode response: %v", err) - } - - if resp.Error != "" { - t.Errorf("handler returned an unexpected error: %v", resp.Error) - } -} - -func TestDevCommitHandler(t *testing.T) { - reqBody := DevCommitRequest{ - Message: "test commit", - } - body, _ := json.Marshal(reqBody) - - req, err := http.NewRequest("POST", "/core_dev_commit", bytes.NewBuffer(body)) - if err != nil { - t.Fatal(err) - } - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(devCommitHandler) - - handler.ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - var resp DevCommitResponse - if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { - t.Fatalf("could not decode response: %v", err) - } - - if resp.Error != "" { - t.Errorf("handler returned an unexpected error: %v", resp.Error) - } -} diff --git a/google/mcp/testdata/core b/google/mcp/testdata/core deleted file mode 100755 index 06bd986..0000000 --- a/google/mcp/testdata/core +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 0 diff --git a/pkg/agentic/epic.go b/pkg/agentic/epic.go index e7ffd78..1aad521 100644 --- a/pkg/agentic/epic.go +++ b/pkg/agentic/epic.go @@ -196,7 +196,12 @@ func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, n req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return nil + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return nil } defer resp.Body.Close() @@ -252,7 +257,12 @@ func (s *PrepSubsystem) createLabel(ctx context.Context, org, repo, name string) req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 201 { + if err != nil { + return 0 + } + defer resp.Body.Close() + + if resp.StatusCode != 201 { return 0 } defer resp.Body.Close() diff --git a/pkg/agentic/events.go b/pkg/agentic/events.go index 57c2273..bf03c35 100644 --- a/pkg/agentic/events.go +++ b/pkg/agentic/events.go @@ -4,10 +4,11 @@ package agentic import ( "encoding/json" + "os" "path/filepath" "time" - coreio "forge.lthn.ai/core/go-io" + ) // CompletionEvent is emitted when a dispatched agent finishes. @@ -39,6 +40,10 @@ func emitCompletionEvent(agent, workspace string) { } // Append to events log - existing, _ := coreio.Local.Read(eventsFile) - coreio.Local.Write(eventsFile, existing+string(data)+"\n") + f, err := os.OpenFile(eventsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return + } + defer f.Close() + f.Write(append(data, '\n')) } diff --git a/pkg/agentic/paths.go b/pkg/agentic/paths.go index c058376..063c3a5 100644 --- a/pkg/agentic/paths.go +++ b/pkg/agentic/paths.go @@ -42,3 +42,11 @@ func AgentName() string { } return "charon" } + +// GitHubOrg returns the GitHub org for mirror operations. +func GitHubOrg() string { + if org := os.Getenv("GITHUB_ORG"); org != "" { + return org + } + return "dAppCore" +} diff --git a/pkg/agentic/pr.go b/pkg/agentic/pr.go index d4f8d9d..226f2ba 100644 --- a/pkg/agentic/pr.go +++ b/pkg/agentic/pr.go @@ -308,7 +308,12 @@ func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return nil, coreerr.E("listRepoPRs", "failed to list PRs for "+repo, err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return nil, coreerr.E("listRepoPRs", "failed to list PRs for "+repo, err) } defer resp.Body.Close() diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index 2e747a0..45b7d95 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -167,7 +167,9 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques // 1. Clone repo into src/ and create feature branch srcDir := filepath.Join(wsDir, "src") cloneCmd := exec.CommandContext(ctx, "git", "clone", repoPath, srcDir) - cloneCmd.Run() + if err := cloneCmd.Run(); err != nil { + return nil, PrepOutput{}, coreerr.E("prep", "git clone failed for "+input.Repo, err) + } // Create feature branch taskSlug := strings.Map(func(r rune) rune { @@ -459,7 +461,12 @@ func (s *PrepSubsystem) pullWiki(ctx context.Context, org, repo, wsDir string) i req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return 0 + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return 0 } defer resp.Body.Close() @@ -544,7 +551,12 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string) req.Header.Set("Authorization", "Bearer "+s.brainKey) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return 0 + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return 0 } defer resp.Body.Close() @@ -637,7 +649,12 @@ func (s *PrepSubsystem) generateTodo(ctx context.Context, org, repo string, issu req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return } defer resp.Body.Close() diff --git a/pkg/agentic/review_queue.go b/pkg/agentic/review_queue.go index 06e7009..fd278ff 100644 --- a/pkg/agentic/review_queue.go +++ b/pkg/agentic/review_queue.go @@ -242,7 +242,7 @@ func (s *PrepSubsystem) pushAndMerge(ctx context.Context, repoDir, repo string) } // Mark PR ready if draft - readyCmd := exec.CommandContext(ctx, "gh", "pr", "ready", "--repo", "dAppCore/"+repo) + readyCmd := exec.CommandContext(ctx, "gh", "pr", "ready", "--repo", GitHubOrg()+"/"+repo) readyCmd.Dir = repoDir readyCmd.Run() // Ignore error — might already be ready @@ -339,8 +339,11 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string jsonLine, _ := json.Marshal(entry) jsonlPath := filepath.Join(dataDir, "reviews.jsonl") - existing, _ := coreio.Local.Read(jsonlPath) - coreio.Local.Write(jsonlPath, existing+string(jsonLine)+"\n") + f, err := os.OpenFile(jsonlPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + defer f.Close() + f.Write(append(jsonLine, '\n')) + } } // saveRateLimitState persists rate limit info for cross-run awareness. diff --git a/pkg/agentic/scan.go b/pkg/agentic/scan.go index 6aabe82..e637b7c 100644 --- a/pkg/agentic/scan.go +++ b/pkg/agentic/scan.go @@ -105,7 +105,12 @@ func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string, req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return nil, coreerr.E("scan.listOrgRepos", "failed to list repos", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return nil, coreerr.E("scan.listOrgRepos", "failed to list repos", err) } defer resp.Body.Close() @@ -129,7 +134,12 @@ func (s *PrepSubsystem) listRepoIssues(ctx context.Context, org, repo, label str req.Header.Set("Authorization", "token "+s.forgeToken) resp, err := s.client.Do(req) - if err != nil || resp.StatusCode != 200 { + if err != nil { + return nil, coreerr.E("scan.listRepoIssues", "failed to list issues for "+repo, err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { return nil, coreerr.E("scan.listRepoIssues", "failed to list issues for "+repo, err) } defer resp.Body.Close() diff --git a/pkg/monitor/monitor.go b/pkg/monitor/monitor.go index 8083df4..dcff291 100644 --- a/pkg/monitor/monitor.go +++ b/pkg/monitor/monitor.go @@ -14,7 +14,7 @@ import ( "encoding/json" "fmt" "os" - "os/exec" + "net/http" "path/filepath" "strings" "sync" @@ -238,14 +238,26 @@ func (m *Subsystem) checkInbox() string { } // Call the API to check inbox - cmd := exec.Command("curl", "-sf", - "-H", "Authorization: Bearer "+strings.TrimSpace(apiKeyStr), - "https://api.lthn.sh/v1/messages/inbox?agent="+agentic.AgentName(), - ) - out, err := cmd.Output() + apiURL := os.Getenv("CORE_API_URL") + if apiURL == "" { + apiURL = "https://api.lthn.sh" + } + req, err := http.NewRequest("GET", apiURL+"/v1/messages/inbox?agent="+agentic.AgentName(), nil) if err != nil { return "" } + req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(apiKeyStr)) + + client := &http.Client{Timeout: 10 * time.Second} + httpResp, err := client.Do(req) + if err != nil { + return "" + } + defer httpResp.Body.Close() + + if httpResp.StatusCode != 200 { + return "" + } var resp struct { Data []struct { @@ -254,7 +266,7 @@ func (m *Subsystem) checkInbox() string { Subject string `json:"subject"` } `json:"data"` } - if json.Unmarshal(out, &resp) != nil { + if json.NewDecoder(httpResp.Body).Decode(&resp) != nil { return "" }