[agent/claude] Phase 2: Replace stdlib strings/fmt with Core primitives acr... #8
29 changed files with 257 additions and 270 deletions
|
|
@ -4,11 +4,11 @@ package agentic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// autoCreatePR pushes the agent's branch and creates a PR on Forge
|
||||
|
|
@ -28,12 +28,12 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
diffCmd := exec.Command("git", "log", "--oneline", "origin/"+base+"..HEAD")
|
||||
diffCmd.Dir = srcDir
|
||||
out, err := diffCmd.Output()
|
||||
if err != nil || len(strings.TrimSpace(string(out))) == 0 {
|
||||
if err != nil || len(core.Trim(string(out))) == 0 {
|
||||
// No commits — nothing to PR
|
||||
return
|
||||
}
|
||||
|
||||
commitCount := len(strings.Split(strings.TrimSpace(string(out)), "\n"))
|
||||
commitCount := len(core.Split(core.Trim(string(out)), "\n"))
|
||||
|
||||
// Get the repo's forge remote URL to extract org/repo
|
||||
org := st.Org
|
||||
|
|
@ -42,20 +42,20 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
}
|
||||
|
||||
// Push the branch to forge
|
||||
forgeRemote := fmt.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
pushCmd := exec.Command("git", "push", forgeRemote, st.Branch)
|
||||
pushCmd.Dir = srcDir
|
||||
if pushErr := pushCmd.Run(); pushErr != nil {
|
||||
// Push failed — update status with error but don't block
|
||||
if st2, err := readStatus(wsDir); err == nil {
|
||||
st2.Question = fmt.Sprintf("PR push failed: %v", pushErr)
|
||||
st2.Question = core.Sprintf("PR push failed: %v", pushErr)
|
||||
writeStatus(wsDir, st2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create PR via Forge API
|
||||
title := fmt.Sprintf("[agent/%s] %s", st.Agent, truncate(st.Task, 60))
|
||||
title := core.Sprintf("[agent/%s] %s", st.Agent, truncate(st.Task, 60))
|
||||
body := s.buildAutoPRBody(st, commitCount)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
|
@ -64,7 +64,7 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
prURL, _, err := s.forgeCreatePR(ctx, org, st.Repo, st.Branch, base, title, body)
|
||||
if err != nil {
|
||||
if st2, err := readStatus(wsDir); err == nil {
|
||||
st2.Question = fmt.Sprintf("PR creation failed: %v", err)
|
||||
st2.Question = core.Sprintf("PR creation failed: %v", err)
|
||||
writeStatus(wsDir, st2)
|
||||
}
|
||||
return
|
||||
|
|
@ -78,13 +78,13 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) buildAutoPRBody(st *WorkspaceStatus, commits int) string {
|
||||
var b strings.Builder
|
||||
b := core.NewBuilder()
|
||||
b.WriteString("## Task\n\n")
|
||||
b.WriteString(st.Task)
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString(fmt.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(fmt.Sprintf("**Commits:** %d\n", commits))
|
||||
b.WriteString(fmt.Sprintf("**Branch:** `%s`\n", st.Branch))
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(core.Sprintf("**Commits:** %d\n", commits))
|
||||
b.WriteString(core.Sprintf("**Branch:** `%s`\n", st.Branch))
|
||||
b.WriteString("\n---\n")
|
||||
b.WriteString("Auto-created by core-agent dispatch system.\n")
|
||||
b.WriteString("Co-Authored-By: Virgil <virgil@lethean.io>\n")
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ package agentic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"dappco.re/go/core/process"
|
||||
|
|
@ -52,7 +51,7 @@ func (s *PrepSubsystem) registerDispatchTool(server *mcp.Server) {
|
|||
// agentCommand returns the command and args for a given agent type.
|
||||
// Supports model variants: "gemini", "gemini:flash", "gemini:pro", "claude", "claude:haiku".
|
||||
func agentCommand(agent, prompt string) (string, []string, error) {
|
||||
parts := strings.SplitN(agent, ":", 2)
|
||||
parts := core.SplitN(agent, ":", 2)
|
||||
base := parts[0]
|
||||
model := ""
|
||||
if len(parts) > 1 {
|
||||
|
|
@ -120,7 +119,7 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir, srcDir string) (int, st
|
|||
return 0, "", err
|
||||
}
|
||||
|
||||
outputFile := filepath.Join(wsDir, fmt.Sprintf("agent-%s.log", agent))
|
||||
outputFile := filepath.Join(wsDir, core.Sprintf("agent-%s.log", agent))
|
||||
|
||||
// Clean up stale BLOCKED.md from previous runs so it doesn't
|
||||
// prevent this run from completing
|
||||
|
|
@ -172,13 +171,13 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir, srcDir string) (int, st
|
|||
question := ""
|
||||
|
||||
blockedPath := filepath.Join(wsDir, "src", "BLOCKED.md")
|
||||
if blockedContent, err := coreio.Local.Read(blockedPath); err == nil && strings.TrimSpace(blockedContent) != "" {
|
||||
if blockedContent, err := coreio.Local.Read(blockedPath); err == nil && core.Trim(blockedContent) != "" {
|
||||
finalStatus = "blocked"
|
||||
question = strings.TrimSpace(blockedContent)
|
||||
question = core.Trim(blockedContent)
|
||||
} else if exitCode != 0 || procStatus == "failed" || procStatus == "killed" {
|
||||
finalStatus = "failed"
|
||||
if exitCode != 0 {
|
||||
question = fmt.Sprintf("Agent exited with code %d", exitCode)
|
||||
question = core.Sprintf("Agent exited with code %d", exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -99,14 +98,14 @@ func (s *PrepSubsystem) createEpic(ctx context.Context, req *mcp.CallToolRequest
|
|||
}
|
||||
|
||||
// Step 2: Build epic body with checklist
|
||||
var body strings.Builder
|
||||
body := core.NewBuilder()
|
||||
if input.Body != "" {
|
||||
body.WriteString(input.Body)
|
||||
body.WriteString("\n\n")
|
||||
}
|
||||
body.WriteString("## Tasks\n\n")
|
||||
for _, child := range children {
|
||||
body.WriteString(fmt.Sprintf("- [ ] #%d %s\n", child.Number, child.Title))
|
||||
body.WriteString(core.Sprintf("- [ ] #%d %s\n", child.Number, child.Title))
|
||||
}
|
||||
|
||||
// Step 3: Create epic issue
|
||||
|
|
@ -156,7 +155,7 @@ func (s *PrepSubsystem) createIssue(ctx context.Context, org, repo, title, body
|
|||
}
|
||||
|
||||
data, _ := json.Marshal(payload)
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues", s.forgeURL, org, repo)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues", s.forgeURL, org, repo)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
|
@ -168,7 +167,7 @@ func (s *PrepSubsystem) createIssue(ctx context.Context, org, repo, title, body
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 201 {
|
||||
return ChildRef{}, coreerr.E("createIssue", fmt.Sprintf("create issue returned %d", resp.StatusCode), nil)
|
||||
return ChildRef{}, coreerr.E("createIssue", core.Sprintf("create issue returned %d", resp.StatusCode), nil)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
|
|
@ -191,7 +190,7 @@ func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, n
|
|||
}
|
||||
|
||||
// Fetch existing labels
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/labels?limit=50", s.forgeURL, org, repo)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels?limit=50", s.forgeURL, org, repo)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
||||
|
|
@ -250,7 +249,7 @@ func (s *PrepSubsystem) createLabel(ctx context.Context, org, repo, name string)
|
|||
"color": colour,
|
||||
})
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", 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)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ package agentic
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ func (s *PrepSubsystem) ingestFindings(wsDir string) {
|
|||
body := contentStr
|
||||
|
||||
// Skip quota errors
|
||||
if strings.Contains(body, "QUOTA_EXHAUSTED") || strings.Contains(body, "QuotaError") {
|
||||
if core.Contains(body, "QUOTA_EXHAUSTED") || core.Contains(body, "QuotaError") {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +48,13 @@ func (s *PrepSubsystem) ingestFindings(wsDir string) {
|
|||
// Determine issue type from the template used
|
||||
issueType := "task"
|
||||
priority := "normal"
|
||||
if strings.Contains(body, "security") || strings.Contains(body, "Security") {
|
||||
if core.Contains(body, "security") || core.Contains(body, "Security") {
|
||||
issueType = "bug"
|
||||
priority = "high"
|
||||
}
|
||||
|
||||
// Create a single issue per repo with all findings in the body
|
||||
title := fmt.Sprintf("Scan findings for %s (%d items)", st.Repo, findings)
|
||||
title := core.Sprintf("Scan findings for %s (%d items)", st.Repo, findings)
|
||||
|
||||
// Truncate body to reasonable size for issue description
|
||||
description := body
|
||||
|
|
@ -78,7 +77,7 @@ func countFileRefs(body string) int {
|
|||
}
|
||||
if j < len(body) && body[j] == '`' {
|
||||
ref := body[i+1 : j]
|
||||
if strings.Contains(ref, ".go:") || strings.Contains(ref, ".php:") {
|
||||
if core.Contains(ref, ".go:") || core.Contains(ref, ".php:") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +98,7 @@ func (s *PrepSubsystem) createIssueViaAPI(repo, title, description, issueType, p
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
apiKey := strings.TrimSpace(apiKeyStr)
|
||||
apiKey := core.Trim(apiKeyStr)
|
||||
|
||||
payload, _ := json.Marshal(map[string]string{
|
||||
"title": title,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -105,7 +106,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
|
||||
// Skip if too many files for one PR
|
||||
if files > maxFiles {
|
||||
sync.Skipped = fmt.Sprintf("%d files exceeds limit of %d", files, maxFiles)
|
||||
sync.Skipped = core.Sprintf("%d files exceeds limit of %d", files, maxFiles)
|
||||
synced = append(synced, sync)
|
||||
continue
|
||||
}
|
||||
|
|
@ -124,7 +125,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
pushCmd := exec.CommandContext(ctx, "git", "push", "github", base+":refs/heads/dev", "--force")
|
||||
pushCmd.Dir = repoDir
|
||||
if err := pushCmd.Run(); err != nil {
|
||||
sync.Skipped = fmt.Sprintf("push failed: %v", err)
|
||||
sync.Skipped = core.Sprintf("push failed: %v", err)
|
||||
synced = append(synced, sync)
|
||||
continue
|
||||
}
|
||||
|
|
@ -133,7 +134,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
// Create PR: dev → main on GitHub
|
||||
prURL, err := s.createGitHubPR(ctx, repoDir, repo, ahead, files)
|
||||
if err != nil {
|
||||
sync.Skipped = fmt.Sprintf("PR creation failed: %v", err)
|
||||
sync.Skipped = core.Sprintf("PR creation failed: %v", err)
|
||||
} else {
|
||||
sync.PRURL = prURL
|
||||
}
|
||||
|
|
@ -152,11 +153,11 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
// createGitHubPR creates a PR from dev → main using the gh CLI.
|
||||
func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string, commits, files int) (string, error) {
|
||||
// Check if there's already an open PR from dev
|
||||
ghRepo := fmt.Sprintf("%s/%s", GitHubOrg(), repo)
|
||||
ghRepo := core.Sprintf("%s/%s", GitHubOrg(), repo)
|
||||
checkCmd := exec.CommandContext(ctx, "gh", "pr", "list", "--repo", ghRepo, "--head", "dev", "--state", "open", "--json", "url", "--limit", "1")
|
||||
checkCmd.Dir = repoDir
|
||||
out, err := checkCmd.Output()
|
||||
if err == nil && strings.Contains(string(out), "url") {
|
||||
if err == nil && core.Contains(string(out), "url") {
|
||||
// PR already exists — extract URL
|
||||
// Format: [{"url":"https://..."}]
|
||||
url := extractJSONField(string(out), "url")
|
||||
|
|
@ -166,7 +167,7 @@ func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string
|
|||
}
|
||||
|
||||
// Build PR body
|
||||
body := fmt.Sprintf("## Forge → GitHub Sync\n\n"+
|
||||
body := core.Sprintf("## Forge → GitHub Sync\n\n"+
|
||||
"**Commits:** %d\n"+
|
||||
"**Files changed:** %d\n\n"+
|
||||
"Automated sync from Forge (forge.lthn.ai) to GitHub mirror.\n"+
|
||||
|
|
@ -175,7 +176,7 @@ func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string
|
|||
"Co-Authored-By: Virgil <virgil@lethean.io>",
|
||||
commits, files)
|
||||
|
||||
title := fmt.Sprintf("[sync] %s: %d commits, %d files", repo, commits, files)
|
||||
title := core.Sprintf("[sync] %s: %d commits, %d files", repo, commits, files)
|
||||
|
||||
prCmd := exec.CommandContext(ctx, "gh", "pr", "create",
|
||||
"--repo", ghRepo,
|
||||
|
|
@ -191,7 +192,7 @@ func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string
|
|||
}
|
||||
|
||||
// gh pr create outputs the PR URL on the last line
|
||||
lines := strings.Split(strings.TrimSpace(string(prOut)), "\n")
|
||||
lines := core.Split(core.Trim(string(prOut)), "\n")
|
||||
if len(lines) > 0 {
|
||||
return lines[len(lines)-1], nil
|
||||
}
|
||||
|
|
@ -223,7 +224,7 @@ func commitsAhead(repoDir, base, head string) int {
|
|||
return 0
|
||||
}
|
||||
var n int
|
||||
fmt.Sscanf(strings.TrimSpace(string(out)), "%d", &n)
|
||||
fmt.Sscanf(core.Trim(string(out)), "%d", &n)
|
||||
return n
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +236,7 @@ func filesChanged(repoDir, base, head string) int {
|
|||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
lines := core.Split(core.Trim(string(out)), "\n")
|
||||
if len(lines) == 1 && lines[0] == "" {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -264,7 +265,7 @@ func (s *PrepSubsystem) listLocalRepos(basePath string) []string {
|
|||
// extractJSONField extracts a simple string field from JSON array output.
|
||||
func extractJSONField(jsonStr, field string) string {
|
||||
// Quick and dirty — works for gh CLI output like [{"url":"https://..."}]
|
||||
key := fmt.Sprintf(`"%s":"`, field)
|
||||
key := core.Sprintf(`"%s":"`, field)
|
||||
idx := strings.Index(jsonStr, key)
|
||||
if idx < 0 {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// WorkspaceRoot returns the root directory for agent workspaces.
|
||||
|
|
@ -37,8 +38,8 @@ func AgentName() string {
|
|||
return name
|
||||
}
|
||||
hostname, _ := os.Hostname()
|
||||
h := strings.ToLower(hostname)
|
||||
if strings.Contains(h, "snider") || strings.Contains(h, "studio") || strings.Contains(h, "mac") {
|
||||
h := core.Lower(hostname)
|
||||
if core.Contains(h, "snider") || core.Contains(h, "studio") || core.Contains(h, "mac") {
|
||||
return "cladius"
|
||||
}
|
||||
return "charon"
|
||||
|
|
@ -49,9 +50,9 @@ func gitDefaultBranch(repoDir string) string {
|
|||
cmd := exec.Command("git", "symbolic-ref", "refs/remotes/origin/HEAD", "--short")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.Output(); err == nil {
|
||||
ref := strings.TrimSpace(string(out))
|
||||
if strings.HasPrefix(ref, "origin/") {
|
||||
return strings.TrimPrefix(ref, "origin/")
|
||||
ref := core.Trim(string(out))
|
||||
if core.HasPrefix(ref, "origin/") {
|
||||
return core.TrimPrefix(ref, "origin/")
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ package agentic
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -130,5 +130,5 @@ func TestValidPlanStatus_Bad(t *testing.T) {
|
|||
func TestGeneratePlanID_Good(t *testing.T) {
|
||||
id := generatePlanID("Fix the login bug in auth service")
|
||||
assert.True(t, len(id) > 0)
|
||||
assert.True(t, strings.Contains(id, "fix-the-login-bug"))
|
||||
assert.True(t, core.Contains(id, "fix-the-login-bug"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -282,11 +283,11 @@ func (s *PrepSubsystem) planList(_ context.Context, _ *mcp.CallToolRequest, inpu
|
|||
|
||||
var plans []Plan
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
|
||||
if entry.IsDir() || !core.HasSuffix(entry.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
|
||||
id := strings.TrimSuffix(entry.Name(), ".json")
|
||||
id := core.TrimSuffix(entry.Name(), ".json")
|
||||
plan, err := readPlan(dir, id)
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
@ -336,8 +337,8 @@ func generatePlanID(title string) string {
|
|||
}, title)
|
||||
|
||||
// Trim consecutive dashes and cap length
|
||||
for strings.Contains(slug, "--") {
|
||||
slug = strings.ReplaceAll(slug, "--", "-")
|
||||
for core.Contains(slug, "--") {
|
||||
slug = core.Replace(slug, "--", "-")
|
||||
}
|
||||
slug = strings.Trim(slug, "-")
|
||||
if len(slug) > 30 {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -137,9 +138,9 @@ func TestWriteReadPlan_Good_Roundtrip(t *testing.T) {
|
|||
|
||||
func TestGeneratePlanID_Good_Slugifies(t *testing.T) {
|
||||
id := generatePlanID("Add Unit Tests for Agentic")
|
||||
assert.True(t, strings.HasPrefix(id, "add-unit-tests-for-agentic"), "got: %s", id)
|
||||
assert.True(t, core.HasPrefix(id, "add-unit-tests-for-agentic"), "got: %s", id)
|
||||
// Should have random suffix
|
||||
parts := strings.Split(id, "-")
|
||||
parts := core.Split(id, "-")
|
||||
assert.True(t, len(parts) >= 5, "expected slug with random suffix, got: %s", id)
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +154,7 @@ func TestGeneratePlanID_Good_TruncatesLong(t *testing.T) {
|
|||
|
||||
func TestGeneratePlanID_Good_HandlesSpecialChars(t *testing.T) {
|
||||
id := generatePlanID("Fix bug #123: auth & session!")
|
||||
assert.True(t, strings.Contains(id, "fix-bug"), "got: %s", id)
|
||||
assert.True(t, core.Contains(id, "fix-bug"), "got: %s", id)
|
||||
assert.NotContains(t, id, "#")
|
||||
assert.NotContains(t, id, "!")
|
||||
assert.NotContains(t, id, "&")
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -75,7 +74,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
if err != nil {
|
||||
return nil, CreatePROutput{}, coreerr.E("createPR", "failed to detect branch", err)
|
||||
}
|
||||
st.Branch = strings.TrimSpace(string(out))
|
||||
st.Branch = core.Trim(string(out))
|
||||
}
|
||||
|
||||
org := st.Org
|
||||
|
|
@ -93,7 +92,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
title = st.Task
|
||||
}
|
||||
if title == "" {
|
||||
title = fmt.Sprintf("Agent work on %s", st.Branch)
|
||||
title = core.Sprintf("Agent work on %s", st.Branch)
|
||||
}
|
||||
|
||||
// Build PR body
|
||||
|
|
@ -112,7 +111,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
}
|
||||
|
||||
// Push branch to Forge (origin is the local clone, not Forge)
|
||||
forgeRemote := fmt.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
pushCmd := exec.CommandContext(ctx, "git", "push", forgeRemote, st.Branch)
|
||||
pushCmd.Dir = srcDir
|
||||
pushOut, err := pushCmd.CombinedOutput()
|
||||
|
|
@ -132,7 +131,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
|
||||
// Comment on issue if tracked
|
||||
if st.Issue > 0 {
|
||||
comment := fmt.Sprintf("Pull request created: %s", prURL)
|
||||
comment := core.Sprintf("Pull request created: %s", prURL)
|
||||
s.commentOnIssue(ctx, org, st.Repo, st.Issue, comment)
|
||||
}
|
||||
|
||||
|
|
@ -148,17 +147,17 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string {
|
||||
var b strings.Builder
|
||||
b := core.NewBuilder()
|
||||
b.WriteString("## Summary\n\n")
|
||||
if st.Task != "" {
|
||||
b.WriteString(st.Task)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
if st.Issue > 0 {
|
||||
b.WriteString(fmt.Sprintf("Closes #%d\n\n", st.Issue))
|
||||
b.WriteString(core.Sprintf("Closes #%d\n\n", st.Issue))
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(fmt.Sprintf("**Runs:** %d\n", st.Runs))
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(core.Sprintf("**Runs:** %d\n", st.Runs))
|
||||
b.WriteString("\n---\n*Created by agentic dispatch*\n")
|
||||
return b.String()
|
||||
}
|
||||
|
|
@ -171,7 +170,7 @@ func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base
|
|||
"base": base,
|
||||
})
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/pulls", s.forgeURL, org, repo)
|
||||
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)
|
||||
|
|
@ -186,7 +185,7 @@ func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base
|
|||
var errBody map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&errBody)
|
||||
msg, _ := errBody["message"].(string)
|
||||
return "", 0, coreerr.E("forgeCreatePR", fmt.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil)
|
||||
return "", 0, coreerr.E("forgeCreatePR", core.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil)
|
||||
}
|
||||
|
||||
var pr struct {
|
||||
|
|
@ -201,7 +200,7 @@ func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base
|
|||
func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) {
|
||||
payload, _ := json.Marshal(map[string]string{"body": comment})
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/comments", s.forgeURL, org, repo, issue)
|
||||
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)
|
||||
|
|
@ -303,7 +302,7 @@ func (s *PrepSubsystem) listPRs(ctx context.Context, _ *mcp.CallToolRequest, inp
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string) ([]PRInfo, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/pulls?state=%s&limit=10",
|
||||
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)
|
||||
|
|
@ -315,7 +314,7 @@ func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, coreerr.E("listRepoPRs", fmt.Sprintf("HTTP %d listing PRs for %s", resp.StatusCode, repo), nil)
|
||||
return nil, coreerr.E("listRepoPRs", core.Sprintf("HTTP %d listing PRs for %s", resp.StatusCode, repo), nil)
|
||||
}
|
||||
|
||||
var prs []struct {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -19,6 +18,7 @@ import (
|
|||
"time"
|
||||
|
||||
"dappco.re/go/agent/pkg/lib"
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -56,7 +56,7 @@ func NewPrep() *PrepSubsystem {
|
|||
brainKey := os.Getenv("CORE_BRAIN_KEY")
|
||||
if brainKey == "" {
|
||||
if data, err := coreio.Local.Read(filepath.Join(home, ".claude", "brain.key")); err == nil {
|
||||
brainKey = strings.TrimSpace(data)
|
||||
brainKey = core.Trim(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
|
||||
// Workspace root: .core/workspace/{repo}-{timestamp}/
|
||||
wsRoot := WorkspaceRoot()
|
||||
wsName := fmt.Sprintf("%s-%d", input.Repo, time.Now().UnixNano())
|
||||
wsName := core.Sprintf("%s-%d", input.Repo, time.Now().UnixNano())
|
||||
wsDir := filepath.Join(wsRoot, wsName)
|
||||
|
||||
// Create workspace structure
|
||||
|
|
@ -199,17 +199,17 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
taskSlug = strings.Trim(taskSlug, "-")
|
||||
if taskSlug == "" {
|
||||
// Fallback for issue-only dispatches with no task text
|
||||
taskSlug = fmt.Sprintf("issue-%d", input.Issue)
|
||||
taskSlug = core.Sprintf("issue-%d", input.Issue)
|
||||
if input.Issue == 0 {
|
||||
taskSlug = fmt.Sprintf("work-%d", time.Now().Unix())
|
||||
taskSlug = core.Sprintf("work-%d", time.Now().Unix())
|
||||
}
|
||||
}
|
||||
branchName := fmt.Sprintf("agent/%s", taskSlug)
|
||||
branchName := core.Sprintf("agent/%s", taskSlug)
|
||||
|
||||
branchCmd := exec.CommandContext(ctx, "git", "checkout", "-b", branchName)
|
||||
branchCmd.Dir = srcDir
|
||||
if err := branchCmd.Run(); err != nil {
|
||||
return nil, PrepOutput{}, coreerr.E("prep.branch", fmt.Sprintf("failed to create branch %q", branchName), err)
|
||||
return nil, PrepOutput{}, coreerr.E("prep.branch", core.Sprintf("failed to create branch %q", branchName), err)
|
||||
}
|
||||
out.Branch = branchName
|
||||
|
||||
|
|
@ -324,8 +324,8 @@ func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map
|
|||
|
||||
// Substitute variables ({{variable_name}} → value)
|
||||
for key, value := range variables {
|
||||
content = strings.ReplaceAll(content, "{{"+key+"}}", value)
|
||||
content = strings.ReplaceAll(content, "{{ "+key+" }}", value)
|
||||
content = core.Replace(content, "{{"+key+"}}", value)
|
||||
content = core.Replace(content, "{{ "+key+" }}", value)
|
||||
}
|
||||
|
||||
// Parse the YAML to render as markdown
|
||||
|
|
@ -345,7 +345,7 @@ func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map
|
|||
}
|
||||
|
||||
// Render as PLAN.md
|
||||
var plan strings.Builder
|
||||
plan := core.NewBuilder()
|
||||
plan.WriteString("# Plan: " + tmpl.Name + "\n\n")
|
||||
if task != "" {
|
||||
plan.WriteString("**Task:** " + task + "\n\n")
|
||||
|
|
@ -363,7 +363,7 @@ func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map
|
|||
}
|
||||
|
||||
for i, phase := range tmpl.Phases {
|
||||
plan.WriteString(fmt.Sprintf("## Phase %d: %s\n\n", i+1, phase.Name))
|
||||
plan.WriteString(core.Sprintf("## Phase %d: %s\n\n", i+1, phase.Name))
|
||||
if phase.Description != "" {
|
||||
plan.WriteString(phase.Description + "\n\n")
|
||||
}
|
||||
|
|
@ -390,7 +390,7 @@ func (s *PrepSubsystem) pullWiki(ctx context.Context, org, repo, wsDir string) i
|
|||
return 0
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/wiki/pages", s.forgeURL, org, repo)
|
||||
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)
|
||||
|
||||
|
|
@ -417,7 +417,7 @@ func (s *PrepSubsystem) pullWiki(ctx context.Context, org, repo, wsDir string) i
|
|||
subURL = page.Title
|
||||
}
|
||||
|
||||
pageURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/wiki/page/%s", s.forgeURL, org, repo, subURL)
|
||||
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)
|
||||
|
||||
|
|
@ -482,7 +482,7 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string)
|
|||
"agent_id": "cladius",
|
||||
})
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", s.brainURL+"/v1/brain/recall", strings.NewReader(string(body)))
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", s.brainURL+"/v1/brain/recall", core.NewReader(string(body)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+s.brainKey)
|
||||
|
|
@ -503,7 +503,7 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string)
|
|||
}
|
||||
json.Unmarshal(respData, &result)
|
||||
|
||||
var content strings.Builder
|
||||
content := core.NewBuilder()
|
||||
content.WriteString("# Context — " + repo + "\n\n")
|
||||
content.WriteString("> Relevant knowledge from OpenBrain.\n\n")
|
||||
|
||||
|
|
@ -512,7 +512,7 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string)
|
|||
memContent, _ := mem["content"].(string)
|
||||
memProject, _ := mem["project"].(string)
|
||||
score, _ := mem["score"].(float64)
|
||||
content.WriteString(fmt.Sprintf("### %d. %s [%s] (score: %.3f)\n\n%s\n\n", i+1, memProject, memType, score, memContent))
|
||||
content.WriteString(core.Sprintf("### %d. %s [%s] (score: %.3f)\n\n%s\n\n", i+1, memProject, memType, score, memContent))
|
||||
}
|
||||
|
||||
coreio.Local.Write(filepath.Join(wsDir, "src", "CONTEXT.md"), content.String())
|
||||
|
|
@ -529,18 +529,18 @@ func (s *PrepSubsystem) findConsumers(repo, wsDir string) int {
|
|||
}
|
||||
|
||||
var consumers []string
|
||||
for _, line := range strings.Split(workData, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "./") {
|
||||
for _, line := range core.Split(workData, "\n") {
|
||||
line = core.Trim(line)
|
||||
if !core.HasPrefix(line, "./") {
|
||||
continue
|
||||
}
|
||||
dir := filepath.Join(s.codePath, strings.TrimPrefix(line, "./"))
|
||||
dir := filepath.Join(s.codePath, core.TrimPrefix(line, "./"))
|
||||
goMod := filepath.Join(dir, "go.mod")
|
||||
modData, err := coreio.Local.Read(goMod)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(modData, modulePath) && !strings.HasPrefix(modData, "module "+modulePath) {
|
||||
if core.Contains(modData, modulePath) && !core.HasPrefix(modData, "module "+modulePath) {
|
||||
consumers = append(consumers, filepath.Base(dir))
|
||||
}
|
||||
}
|
||||
|
|
@ -551,7 +551,7 @@ func (s *PrepSubsystem) findConsumers(repo, wsDir string) int {
|
|||
for _, c := range consumers {
|
||||
content += "- " + c + "\n"
|
||||
}
|
||||
content += fmt.Sprintf("\n**Breaking change risk: %d consumers.**\n", len(consumers))
|
||||
content += core.Sprintf("\n**Breaking change risk: %d consumers.**\n", len(consumers))
|
||||
coreio.Local.Write(filepath.Join(wsDir, "src", "CONSUMERS.md"), content)
|
||||
}
|
||||
|
||||
|
|
@ -566,7 +566,7 @@ func (s *PrepSubsystem) gitLog(repoPath, wsDir string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
lines := core.Split(core.Trim(string(output)), "\n")
|
||||
if len(lines) > 0 && lines[0] != "" {
|
||||
content := "# Recent Changes\n\n```\n" + string(output) + "```\n"
|
||||
coreio.Local.Write(filepath.Join(wsDir, "src", "RECENT.md"), content)
|
||||
|
|
@ -580,7 +580,7 @@ func (s *PrepSubsystem) generateTodo(ctx context.Context, org, repo string, issu
|
|||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d", s.forgeURL, org, repo, issue)
|
||||
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)
|
||||
|
||||
|
|
@ -600,11 +600,8 @@ func (s *PrepSubsystem) generateTodo(ctx context.Context, org, repo string, issu
|
|||
}
|
||||
json.NewDecoder(resp.Body).Decode(&issueData)
|
||||
|
||||
content := fmt.Sprintf("# TASK: %s\n\n", issueData.Title)
|
||||
content += fmt.Sprintf("**Status:** ready\n")
|
||||
content += fmt.Sprintf("**Source:** %s/%s/%s/issues/%d\n", s.forgeURL, org, repo, issue)
|
||||
content += fmt.Sprintf("**Repo:** %s/%s\n\n---\n\n", org, repo)
|
||||
content += "## Objective\n\n" + issueData.Body + "\n"
|
||||
content := core.Sprintf("# TASK: %s\n\n**Status:** ready\n**Source:** %s/%s/%s/issues/%d\n**Repo:** %s/%s\n\n---\n\n## Objective\n\n%s\n",
|
||||
issueData.Title, s.forgeURL, org, repo, issue, org, repo, issueData.Body)
|
||||
|
||||
coreio.Local.Write(filepath.Join(wsDir, "src", "TODO.md"), content)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
|
@ -139,7 +140,7 @@ func (s *PrepSubsystem) countRunningByAgent(agent string) int {
|
|||
|
||||
// baseAgent strips the model variant (gemini:flash → gemini).
|
||||
func baseAgent(agent string) string {
|
||||
return strings.SplitN(agent, ":", 2)[0]
|
||||
return core.SplitN(agent, ":", 2)[0]
|
||||
}
|
||||
|
||||
// canDispatchAgent checks if we're under the concurrency limit for a specific agent type.
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ package agentic
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -96,7 +95,7 @@ func (s *PrepSubsystem) dispatchRemote(ctx context.Context, _ *mcp.CallToolReque
|
|||
},
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s/mcp", addr)
|
||||
url := core.Sprintf("http://%s/mcp", addr)
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// Step 1: Initialize session
|
||||
|
|
@ -104,7 +103,7 @@ func (s *PrepSubsystem) dispatchRemote(ctx context.Context, _ *mcp.CallToolReque
|
|||
if err != nil {
|
||||
return nil, RemoteDispatchOutput{
|
||||
Host: input.Host,
|
||||
Error: fmt.Sprintf("init failed: %v", err),
|
||||
Error: core.Sprintf("init failed: %v", err),
|
||||
}, coreerr.E("dispatchRemote", "MCP initialize failed", err)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +113,7 @@ func (s *PrepSubsystem) dispatchRemote(ctx context.Context, _ *mcp.CallToolReque
|
|||
if err != nil {
|
||||
return nil, RemoteDispatchOutput{
|
||||
Host: input.Host,
|
||||
Error: fmt.Sprintf("call failed: %v", err),
|
||||
Error: core.Sprintf("call failed: %v", err),
|
||||
}, coreerr.E("dispatchRemote", "tool call failed", err)
|
||||
}
|
||||
|
||||
|
|
@ -163,12 +162,12 @@ func resolveHost(host string) string {
|
|||
"local": "127.0.0.1:9101",
|
||||
}
|
||||
|
||||
if addr, ok := aliases[strings.ToLower(host)]; ok {
|
||||
if addr, ok := aliases[core.Lower(host)]; ok {
|
||||
return addr
|
||||
}
|
||||
|
||||
// If no port specified, add default
|
||||
if !strings.Contains(host, ":") {
|
||||
if !core.Contains(host, ":") {
|
||||
return host + ":9101"
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +177,7 @@ func resolveHost(host string) string {
|
|||
// remoteToken gets the auth token for a remote agent.
|
||||
func remoteToken(host string) string {
|
||||
// Check environment first
|
||||
envKey := fmt.Sprintf("AGENT_TOKEN_%s", strings.ToUpper(host))
|
||||
envKey := core.Sprintf("AGENT_TOKEN_%s", core.Upper(host))
|
||||
if token := os.Getenv(envKey); token != "" {
|
||||
return token
|
||||
}
|
||||
|
|
@ -191,12 +190,12 @@ func remoteToken(host string) string {
|
|||
// Try reading from file
|
||||
home, _ := os.UserHomeDir()
|
||||
tokenFiles := []string{
|
||||
fmt.Sprintf("%s/.core/tokens/%s.token", home, strings.ToLower(host)),
|
||||
fmt.Sprintf("%s/.core/agent-token", home),
|
||||
core.Sprintf("%s/.core/tokens/%s.token", home, core.Lower(host)),
|
||||
core.Sprintf("%s/.core/agent-token", home),
|
||||
}
|
||||
for _, f := range tokenFiles {
|
||||
if data, err := coreio.Local.Read(f); err == nil {
|
||||
return strings.TrimSpace(data)
|
||||
return core.Trim(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
|
|
@ -46,7 +45,7 @@ func mcpInitialize(ctx context.Context, client *http.Client, url, token string)
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", coreerr.E("mcpInitialize", fmt.Sprintf("HTTP %d", resp.StatusCode), nil)
|
||||
return "", coreerr.E("mcpInitialize", core.Sprintf("HTTP %d", resp.StatusCode), nil)
|
||||
}
|
||||
|
||||
sessionID := resp.Header.Get("Mcp-Session-Id")
|
||||
|
|
@ -88,7 +87,7 @@ func mcpCall(ctx context.Context, client *http.Client, url, token, sessionID str
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, coreerr.E("mcpCall", fmt.Sprintf("HTTP %d", resp.StatusCode), nil)
|
||||
return nil, coreerr.E("mcpCall", core.Sprintf("HTTP %d", resp.StatusCode), nil)
|
||||
}
|
||||
|
||||
// Parse SSE response — extract data: lines
|
||||
|
|
@ -100,8 +99,8 @@ func readSSEData(resp *http.Response) ([]byte, error) {
|
|||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
return []byte(strings.TrimPrefix(line, "data: ")), nil
|
||||
if core.HasPrefix(line, "data: ") {
|
||||
return []byte(core.TrimPrefix(line, "data: ")), nil
|
||||
}
|
||||
}
|
||||
return nil, coreerr.E("readSSEData", "no data in SSE response", nil)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ package agentic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -70,7 +70,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
// Write ANSWER.md if answer provided
|
||||
if input.Answer != "" {
|
||||
answerPath := filepath.Join(srcDir, "ANSWER.md")
|
||||
content := fmt.Sprintf("# Answer\n\n%s\n", input.Answer)
|
||||
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
|
||||
if err := coreio.Local.Write(answerPath, content); err != nil {
|
||||
return nil, ResumeOutput{}, coreerr.E("resume", "failed to write ANSWER.md", err)
|
||||
}
|
||||
|
|
@ -110,6 +110,6 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
Workspace: input.Workspace,
|
||||
Agent: agent,
|
||||
PID: pid,
|
||||
OutputFile: filepath.Join(wsDir, fmt.Sprintf("agent-%s.log", agent)),
|
||||
OutputFile: filepath.Join(wsDir, core.Sprintf("agent-%s.log", agent)),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@ package agentic
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -160,7 +159,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
|
|||
// Check saved rate limit
|
||||
if rl := s.loadRateLimitState(); rl != nil && rl.Limited && time.Now().Before(rl.RetryAt) {
|
||||
result.Verdict = "rate_limited"
|
||||
result.Detail = fmt.Sprintf("retry after %s", rl.RetryAt.Format(time.RFC3339))
|
||||
result.Detail = core.Sprintf("retry after %s", rl.RetryAt.Format(time.RFC3339))
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -173,14 +172,14 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
|
|||
output := string(out)
|
||||
|
||||
// Parse rate limit (both reviewers use similar patterns)
|
||||
if strings.Contains(output, "Rate limit exceeded") || strings.Contains(output, "rate limit") {
|
||||
if core.Contains(output, "Rate limit exceeded") || core.Contains(output, "rate limit") {
|
||||
result.Verdict = "rate_limited"
|
||||
result.Detail = output
|
||||
return result
|
||||
}
|
||||
|
||||
// Parse error
|
||||
if err != nil && !strings.Contains(output, "No findings") && !strings.Contains(output, "no issues") {
|
||||
if err != nil && !core.Contains(output, "No findings") && !core.Contains(output, "no issues") {
|
||||
result.Verdict = "error"
|
||||
result.Detail = output
|
||||
return result
|
||||
|
|
@ -190,7 +189,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
|
|||
s.storeReviewOutput(repoDir, repo, reviewer, output)
|
||||
|
||||
// Parse verdict
|
||||
if strings.Contains(output, "No findings") || strings.Contains(output, "no issues") || strings.Contains(output, "LGTM") {
|
||||
if core.Contains(output, "No findings") || core.Contains(output, "no issues") || core.Contains(output, "LGTM") {
|
||||
result.Verdict = "clean"
|
||||
result.Findings = 0
|
||||
|
||||
|
|
@ -226,7 +225,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
|
|||
coreio.Local.Write(findingsFile, output)
|
||||
|
||||
// Dispatch fix agent with the findings
|
||||
task := fmt.Sprintf("Fix CodeRabbit findings. The review output is in .core/coderabbit-findings.txt. "+
|
||||
task := core.Sprintf("Fix CodeRabbit findings. The review output is in .core/coderabbit-findings.txt. "+
|
||||
"Read it, verify each finding against the code, fix what's valid. Run tests. "+
|
||||
"Commit: fix(coderabbit): address review findings\n\nFindings summary (%d issues):\n%s",
|
||||
result.Findings, truncate(output, 1500))
|
||||
|
|
@ -288,15 +287,15 @@ func (s *PrepSubsystem) dispatchFixFromQueue(ctx context.Context, repo, task str
|
|||
func countFindings(output string) int {
|
||||
// Count lines that look like findings
|
||||
count := 0
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "- ") || strings.HasPrefix(trimmed, "* ") ||
|
||||
strings.Contains(trimmed, "Issue:") || strings.Contains(trimmed, "Finding:") ||
|
||||
strings.Contains(trimmed, "⚠") || strings.Contains(trimmed, "❌") {
|
||||
for _, line := range core.Split(output, "\n") {
|
||||
trimmed := core.Trim(line)
|
||||
if core.HasPrefix(trimmed, "- ") || core.HasPrefix(trimmed, "* ") ||
|
||||
core.Contains(trimmed, "Issue:") || core.Contains(trimmed, "Finding:") ||
|
||||
core.Contains(trimmed, "⚠") || core.Contains(trimmed, "❌") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count == 0 && !strings.Contains(output, "No findings") {
|
||||
if count == 0 && !core.Contains(output, "No findings") {
|
||||
count = 1 // At least one finding if not clean
|
||||
}
|
||||
return count
|
||||
|
|
@ -339,7 +338,7 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string
|
|||
coreio.Local.EnsureDir(dataDir)
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02T15-04-05")
|
||||
filename := fmt.Sprintf("%s_%s_%s.txt", repo, reviewer, timestamp)
|
||||
filename := core.Sprintf("%s_%s_%s.txt", repo, reviewer, timestamp)
|
||||
|
||||
// Write raw output
|
||||
coreio.Local.Write(filepath.Join(dataDir, filename), output)
|
||||
|
|
@ -352,7 +351,7 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string
|
|||
"output": output,
|
||||
"verdict": "clean",
|
||||
}
|
||||
if !strings.Contains(output, "No findings") && !strings.Contains(output, "no issues") {
|
||||
if !core.Contains(output, "No findings") && !core.Contains(output, "no issues") {
|
||||
entry["verdict"] = "findings"
|
||||
}
|
||||
jsonLine, _ := json.Marshal(entry)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ package agentic
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -81,7 +81,7 @@ func (s *PrepSubsystem) scan(ctx context.Context, _ *mcp.CallToolRequest, input
|
|||
seen := make(map[string]bool)
|
||||
var unique []ScanIssue
|
||||
for _, issue := range allIssues {
|
||||
key := fmt.Sprintf("%s#%d", issue.Repo, issue.Number)
|
||||
key := core.Sprintf("%s#%d", issue.Repo, issue.Number)
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
unique = append(unique, issue)
|
||||
|
|
@ -104,7 +104,7 @@ func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string,
|
|||
page := 1
|
||||
|
||||
for {
|
||||
u := fmt.Sprintf("%s/api/v1/orgs/%s/repos?limit=50&page=%d", s.forgeURL, org, page)
|
||||
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, coreerr.E("scan.listOrgRepos", "failed to create request", err)
|
||||
|
|
@ -118,7 +118,7 @@ func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string,
|
|||
|
||||
if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
return nil, coreerr.E("scan.listOrgRepos", fmt.Sprintf("HTTP %d listing repos", resp.StatusCode), nil)
|
||||
return nil, coreerr.E("scan.listOrgRepos", core.Sprintf("HTTP %d listing repos", resp.StatusCode), nil)
|
||||
}
|
||||
|
||||
var repos []struct {
|
||||
|
|
@ -141,10 +141,10 @@ func (s *PrepSubsystem) listOrgRepos(ctx context.Context, org string) ([]string,
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) listRepoIssues(ctx context.Context, org, repo, label string) ([]ScanIssue, error) {
|
||||
u := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues?state=open&limit=10&type=issues",
|
||||
u := core.Sprintf("%s/api/v1/repos/%s/%s/issues?state=open&limit=10&type=issues",
|
||||
s.forgeURL, org, repo)
|
||||
if label != "" {
|
||||
u += "&labels=" + strings.ReplaceAll(strings.ReplaceAll(label, " ", "%20"), "&", "%26")
|
||||
u += "&labels=" + core.Replace(core.Replace(label, " ", "%20"), "&", "%26")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
|
||||
if err != nil {
|
||||
|
|
@ -159,7 +159,7 @@ func (s *PrepSubsystem) listRepoIssues(ctx context.Context, org, repo, label str
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, coreerr.E("scan.listRepoIssues", fmt.Sprintf("HTTP %d listing issues for %s", resp.StatusCode, repo), nil)
|
||||
return nil, coreerr.E("scan.listRepoIssues", core.Sprintf("HTTP %d listing issues for %s", resp.StatusCode, repo), nil)
|
||||
}
|
||||
|
||||
var issues []struct {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ package agentic
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -154,13 +153,13 @@ func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
blockedPath := filepath.Join(wsDir, "src", "BLOCKED.md")
|
||||
if data, err := coreio.Local.Read(blockedPath); err == nil {
|
||||
info.Status = "blocked"
|
||||
info.Question = strings.TrimSpace(data)
|
||||
info.Question = core.Trim(data)
|
||||
st.Status = "blocked"
|
||||
st.Question = info.Question
|
||||
} else {
|
||||
// Dead PID without BLOCKED.md — check exit code from log
|
||||
// If no evidence of success, mark as failed (not completed)
|
||||
logFile := filepath.Join(wsDir, fmt.Sprintf("agent-%s.log", st.Agent))
|
||||
logFile := filepath.Join(wsDir, core.Sprintf("agent-%s.log", st.Agent))
|
||||
if _, err := coreio.Local.Read(logFile); err != nil {
|
||||
info.Status = "failed"
|
||||
st.Status = "failed"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
|
@ -89,7 +89,7 @@ func (s *PrepSubsystem) attemptVerifyAndMerge(srcDir, org, repo, branch string,
|
|||
testResult := s.runVerification(srcDir)
|
||||
|
||||
if !testResult.passed {
|
||||
comment := fmt.Sprintf("## Verification Failed\n\n**Command:** `%s`\n\n```\n%s\n```\n\n**Exit code:** %d",
|
||||
comment := core.Sprintf("## Verification Failed\n\n**Command:** `%s`\n\n```\n%s\n```\n\n**Exit code:** %d",
|
||||
testResult.testCmd, truncate(testResult.output, 2000), testResult.exitCode)
|
||||
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
|
||||
return testFailed
|
||||
|
|
@ -100,12 +100,12 @@ func (s *PrepSubsystem) attemptVerifyAndMerge(srcDir, org, repo, branch string,
|
|||
defer cancel()
|
||||
|
||||
if err := s.forgeMergePR(ctx, org, repo, prNum); err != nil {
|
||||
comment := fmt.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed: %v", testResult.testCmd, err)
|
||||
comment := core.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed: %v", testResult.testCmd, err)
|
||||
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
|
||||
return mergeConflict
|
||||
}
|
||||
|
||||
comment := fmt.Sprintf("## Auto-Verified & Merged\n\n**Tests:** `%s` — PASS\n\nAuto-merged by core-agent dispatch system.", testResult.testCmd)
|
||||
comment := core.Sprintf("## Auto-Verified & Merged\n\n**Tests:** `%s` — PASS\n\nAuto-merged by core-agent dispatch system.", testResult.testCmd)
|
||||
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
|
||||
return mergeSuccess
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ func (s *PrepSubsystem) rebaseBranch(srcDir, branch string) bool {
|
|||
}
|
||||
repo = st.Repo
|
||||
}
|
||||
forgeRemote := fmt.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
|
||||
push := exec.Command("git", "push", "--force-with-lease", forgeRemote, branch)
|
||||
push.Dir = srcDir
|
||||
return push.Run() == nil
|
||||
|
|
@ -160,7 +160,7 @@ func (s *PrepSubsystem) flagForReview(org, repo string, prNum int, result mergeR
|
|||
payload, _ := json.Marshal(map[string]any{
|
||||
"labels": []int{s.getLabelID(ctx, org, repo, "needs-review")},
|
||||
})
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/labels", s.forgeURL, org, repo, prNum)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/labels", s.forgeURL, org, repo, prNum)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
|
@ -174,7 +174,7 @@ func (s *PrepSubsystem) flagForReview(org, repo string, prNum int, result mergeR
|
|||
if result == mergeConflict {
|
||||
reason = "Merge conflict persists after rebase"
|
||||
}
|
||||
comment := fmt.Sprintf("## Needs Review\n\n%s. Auto-merge gave up after retry.\n\nLabelled `needs-review` for human attention.", reason)
|
||||
comment := core.Sprintf("## Needs Review\n\n%s. Auto-merge gave up after retry.\n\nLabelled `needs-review` for human attention.", reason)
|
||||
s.commentOnIssue(ctx, org, repo, prNum, comment)
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +184,7 @@ func (s *PrepSubsystem) ensureLabel(ctx context.Context, org, repo, name, colour
|
|||
"name": name,
|
||||
"color": "#" + colour,
|
||||
})
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", 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)
|
||||
|
|
@ -196,7 +196,7 @@ func (s *PrepSubsystem) ensureLabel(ctx context.Context, org, repo, name, colour
|
|||
|
||||
// getLabelID fetches the ID of a label by name.
|
||||
func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string) int {
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
resp, err := s.client.Do(req)
|
||||
|
|
@ -318,7 +318,7 @@ func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, prNu
|
|||
"delete_branch_after_merge": true,
|
||||
})
|
||||
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/merge", s.forgeURL, org, repo, prNum)
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/merge", s.forgeURL, org, repo, prNum)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "token "+s.forgeToken)
|
||||
|
|
@ -333,7 +333,7 @@ func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, prNu
|
|||
var errBody map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&errBody)
|
||||
msg, _ := errBody["message"].(string)
|
||||
return coreerr.E("forgeMergePR", fmt.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil)
|
||||
return coreerr.E("forgeMergePR", core.Sprintf("HTTP %d: %s", resp.StatusCode, msg), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -341,7 +341,7 @@ func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, prNu
|
|||
|
||||
// extractPRNumber gets the PR number from a Forge PR URL.
|
||||
func extractPRNumber(prURL string) int {
|
||||
parts := strings.Split(prURL, "/")
|
||||
parts := core.Split(prURL, "/")
|
||||
if len(parts) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ package agentic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -128,7 +128,7 @@ func (s *PrepSubsystem) watch(ctx context.Context, req *mcp.CallToolRequest, inp
|
|||
ProgressToken: progressToken,
|
||||
Progress: progressCount,
|
||||
Total: total,
|
||||
Message: fmt.Sprintf("%s completed (%s)", st.Repo, st.Agent),
|
||||
Message: core.Sprintf("%s completed (%s)", st.Repo, st.Agent),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ func (s *PrepSubsystem) watch(ctx context.Context, req *mcp.CallToolRequest, inp
|
|||
ProgressToken: progressToken,
|
||||
Progress: progressCount,
|
||||
Total: total,
|
||||
Message: fmt.Sprintf("%s %s (%s)", st.Repo, st.Status, st.Agent),
|
||||
Message: core.Sprintf("%s %s (%s)", st.Repo, st.Status, st.Agent),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ func (s *PrepSubsystem) watch(ctx context.Context, req *mcp.CallToolRequest, inp
|
|||
ProgressToken: progressToken,
|
||||
Progress: progressCount,
|
||||
Total: total,
|
||||
Message: fmt.Sprintf("%s %s (%s)", st.Repo, st.Status, st.Agent),
|
||||
Message: core.Sprintf("%s %s (%s)", st.Repo, st.Status, st.Agent),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
ws "dappco.re/go/core/ws"
|
||||
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
|
||||
"github.com/gorilla/websocket"
|
||||
|
|
@ -44,7 +44,7 @@ func testBridge(t *testing.T) *ide.Bridge {
|
|||
t.Helper()
|
||||
srv := testWSServer(t)
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
|
||||
wsURL := "ws" + core.TrimPrefix(srv.URL, "http")
|
||||
hub := ws.NewHub()
|
||||
bridge := ide.NewBridge(hub, ide.Config{
|
||||
LaravelWSURL: wsURL,
|
||||
|
|
|
|||
|
|
@ -6,25 +6,19 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// agentName returns the identity of this agent.
|
||||
func agentName() string {
|
||||
return agentic.AgentName()
|
||||
}
|
||||
|
||||
// DirectSubsystem implements mcp.Subsystem for OpenBrain via direct HTTP calls.
|
||||
// Unlike Subsystem (which uses the IDE WebSocket bridge), this calls the
|
||||
// Laravel API directly — suitable for standalone core-mcp usage.
|
||||
|
|
@ -47,7 +41,7 @@ func NewDirect() *DirectSubsystem {
|
|||
if apiKey == "" {
|
||||
home, _ := os.UserHomeDir()
|
||||
if data, err := coreio.Local.Read(filepath.Join(home, ".claude", "brain.key")); err == nil {
|
||||
apiKey = strings.TrimSpace(data)
|
||||
apiKey = core.Trim(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +113,7 @@ func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body
|
|||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, coreerr.E("brain.apiCall", fmt.Sprintf("API returned %d: %s", resp.StatusCode, string(respData)), nil)
|
||||
return nil, coreerr.E("brain.apiCall", core.Sprintf("API returned %d: %s", resp.StatusCode, string(respData)), nil)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
|
|
@ -136,7 +130,7 @@ func (s *DirectSubsystem) remember(ctx context.Context, _ *mcp.CallToolRequest,
|
|||
"type": input.Type,
|
||||
"tags": input.Tags,
|
||||
"project": input.Project,
|
||||
"agent_id": agentName(),
|
||||
"agent_id": agentic.AgentName(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, RememberOutput{}, err
|
||||
|
|
@ -179,11 +173,11 @@ func (s *DirectSubsystem) recall(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
for _, m := range mems {
|
||||
if mm, ok := m.(map[string]any); ok {
|
||||
mem := Memory{
|
||||
Content: fmt.Sprintf("%v", mm["content"]),
|
||||
Type: fmt.Sprintf("%v", mm["type"]),
|
||||
Project: fmt.Sprintf("%v", mm["project"]),
|
||||
AgentID: fmt.Sprintf("%v", mm["agent_id"]),
|
||||
CreatedAt: fmt.Sprintf("%v", mm["created_at"]),
|
||||
Content: core.Sprintf("%v", mm["content"]),
|
||||
Type: core.Sprintf("%v", mm["type"]),
|
||||
Project: core.Sprintf("%v", mm["project"]),
|
||||
AgentID: core.Sprintf("%v", mm["agent_id"]),
|
||||
CreatedAt: core.Sprintf("%v", mm["created_at"]),
|
||||
}
|
||||
if id, ok := mm["id"].(string); ok {
|
||||
mem.ID = id
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ package brain
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -80,7 +81,7 @@ func (s *DirectSubsystem) sendMessage(ctx context.Context, _ *mcp.CallToolReques
|
|||
|
||||
result, err := s.apiCall(ctx, "POST", "/v1/messages/send", map[string]any{
|
||||
"to": input.To,
|
||||
"from": agentName(),
|
||||
"from": agentic.AgentName(),
|
||||
"content": input.Content,
|
||||
"subject": input.Subject,
|
||||
})
|
||||
|
|
@ -101,7 +102,7 @@ func (s *DirectSubsystem) sendMessage(ctx context.Context, _ *mcp.CallToolReques
|
|||
func (s *DirectSubsystem) inbox(ctx context.Context, _ *mcp.CallToolRequest, input InboxInput) (*mcp.CallToolResult, InboxOutput, error) {
|
||||
agent := input.Agent
|
||||
if agent == "" {
|
||||
agent = agentName()
|
||||
agent = agentic.AgentName()
|
||||
}
|
||||
result, err := s.apiCall(ctx, "GET", "/v1/messages/inbox?agent="+url.QueryEscape(agent), nil)
|
||||
if err != nil {
|
||||
|
|
@ -119,7 +120,7 @@ func (s *DirectSubsystem) conversation(ctx context.Context, _ *mcp.CallToolReque
|
|||
return nil, ConversationOutput{}, coreerr.E("brain.conversation", "agent is required", nil)
|
||||
}
|
||||
|
||||
result, err := s.apiCall(ctx, "GET", "/v1/messages/conversation/"+url.PathEscape(input.Agent)+"?me="+url.QueryEscape(agentName()), nil)
|
||||
result, err := s.apiCall(ctx, "GET", "/v1/messages/conversation/"+url.PathEscape(input.Agent)+"?me="+url.QueryEscape(agentic.AgentName()), nil)
|
||||
if err != nil {
|
||||
return nil, ConversationOutput{}, err
|
||||
}
|
||||
|
|
@ -137,12 +138,12 @@ func parseMessages(result map[string]any) []MessageItem {
|
|||
mm, _ := m.(map[string]any)
|
||||
messages = append(messages, MessageItem{
|
||||
ID: toInt(mm["id"]),
|
||||
From: fmt.Sprintf("%v", mm["from"]),
|
||||
To: fmt.Sprintf("%v", mm["to"]),
|
||||
Subject: fmt.Sprintf("%v", mm["subject"]),
|
||||
Content: fmt.Sprintf("%v", mm["content"]),
|
||||
From: core.Sprintf("%v", mm["from"]),
|
||||
To: core.Sprintf("%v", mm["to"]),
|
||||
Subject: core.Sprintf("%v", mm["subject"]),
|
||||
Content: core.Sprintf("%v", mm["content"]),
|
||||
Read: mm["read"] == true,
|
||||
CreatedAt: fmt.Sprintf("%v", mm["created_at"]),
|
||||
CreatedAt: core.Sprintf("%v", mm["created_at"]),
|
||||
})
|
||||
}
|
||||
return messages
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
//go:embed prompt/*.md
|
||||
|
|
@ -160,8 +161,8 @@ func ExtractWorkspace(tmplName, targetDir string, data *WorkspaceData) error {
|
|||
|
||||
// Process .tmpl files through text/template
|
||||
outputName := name
|
||||
if strings.HasSuffix(name, ".tmpl") {
|
||||
outputName = strings.TrimSuffix(name, ".tmpl")
|
||||
if core.HasSuffix(name, ".tmpl") {
|
||||
outputName = core.TrimSuffix(name, ".tmpl")
|
||||
tmpl, err := template.New(name).Parse(string(content))
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -193,9 +194,9 @@ func ListTasks() []string {
|
|||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
rel := strings.TrimPrefix(path, "task/")
|
||||
rel := core.TrimPrefix(path, "task/")
|
||||
ext := filepath.Ext(rel)
|
||||
slugs = append(slugs, strings.TrimSuffix(rel, ext))
|
||||
slugs = append(slugs, core.TrimSuffix(rel, ext))
|
||||
return nil
|
||||
})
|
||||
return slugs
|
||||
|
|
@ -207,9 +208,9 @@ func ListPersonas() []string {
|
|||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if strings.HasSuffix(path, ".md") {
|
||||
rel := strings.TrimPrefix(path, "persona/")
|
||||
rel = strings.TrimSuffix(rel, ".md")
|
||||
if core.HasSuffix(path, ".md") {
|
||||
rel := core.TrimPrefix(path, "persona/")
|
||||
rel = core.TrimSuffix(rel, ".md")
|
||||
paths = append(paths, rel)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -224,14 +225,12 @@ func listDir(fsys embed.FS, dir string) []string {
|
|||
}
|
||||
var slugs []string
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if e.IsDir() {
|
||||
name := e.Name()
|
||||
slugs = append(slugs, name)
|
||||
continue
|
||||
}
|
||||
name := e.Name()
|
||||
ext := filepath.Ext(name)
|
||||
slugs = append(slugs, strings.TrimSuffix(name, ext))
|
||||
slugs = append(slugs, core.TrimSuffix(name, filepath.Ext(name)))
|
||||
}
|
||||
return slugs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
|
@ -57,7 +57,7 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
var parts []string
|
||||
for _, h := range harvested {
|
||||
if h.rejected != "" {
|
||||
parts = append(parts, fmt.Sprintf("%s: REJECTED (%s)", h.repo, h.rejected))
|
||||
parts = append(parts, core.Sprintf("%s: REJECTED (%s)", h.repo, h.rejected))
|
||||
if m.notifier != nil {
|
||||
m.notifier.ChannelSend(context.Background(), "harvest.rejected", map[string]any{
|
||||
"repo": h.repo,
|
||||
|
|
@ -66,7 +66,7 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
parts = append(parts, fmt.Sprintf("%s: ready-for-review %s (%d files)", h.repo, h.branch, h.files))
|
||||
parts = append(parts, core.Sprintf("%s: ready-for-review %s (%d files)", h.repo, h.branch, h.files))
|
||||
if m.notifier != nil {
|
||||
m.notifier.ChannelSend(context.Background(), "harvest.complete", map[string]any{
|
||||
"repo": h.repo,
|
||||
|
|
@ -76,7 +76,7 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
}
|
||||
}
|
||||
}
|
||||
return "Harvested: " + strings.Join(parts, ", ")
|
||||
return "Harvested: " + core.Join(", ", parts...)
|
||||
}
|
||||
|
||||
// harvestWorkspace checks a single workspace and pushes if ready.
|
||||
|
|
@ -146,7 +146,7 @@ func detectBranch(srcDir string) string {
|
|||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
return core.Trim(string(out))
|
||||
}
|
||||
|
||||
// defaultBranch detects the default branch of the repo (main, master, etc.).
|
||||
|
|
@ -155,10 +155,10 @@ func defaultBranch(srcDir string) string {
|
|||
cmd := exec.Command("git", "symbolic-ref", "refs/remotes/origin/HEAD", "--short")
|
||||
cmd.Dir = srcDir
|
||||
if out, err := cmd.Output(); err == nil {
|
||||
ref := strings.TrimSpace(string(out))
|
||||
ref := core.Trim(string(out))
|
||||
// returns "origin/main" — strip prefix
|
||||
if strings.HasPrefix(ref, "origin/") {
|
||||
return strings.TrimPrefix(ref, "origin/")
|
||||
if core.HasPrefix(ref, "origin/") {
|
||||
return core.TrimPrefix(ref, "origin/")
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
|
@ -186,14 +186,14 @@ func countUnpushed(srcDir, branch string) int {
|
|||
if err2 != nil {
|
||||
return 0
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(out2)), "\n")
|
||||
lines := core.Split(core.Trim(string(out2)), "\n")
|
||||
if len(lines) == 1 && lines[0] == "" {
|
||||
return 0
|
||||
}
|
||||
return len(lines)
|
||||
}
|
||||
var count int
|
||||
fmt.Sscanf(strings.TrimSpace(string(out)), "%d", &count)
|
||||
fmt.Sscanf(core.Trim(string(out)), "%d", &count)
|
||||
return count
|
||||
}
|
||||
|
||||
|
|
@ -220,20 +220,20 @@ func checkSafety(srcDir string) string {
|
|||
".db": true, ".sqlite": true, ".sqlite3": true,
|
||||
}
|
||||
|
||||
for _, file := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
for _, file := range core.Split(core.Trim(string(out)), "\n") {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(file))
|
||||
ext := core.Lower(filepath.Ext(file))
|
||||
if binaryExts[ext] {
|
||||
return fmt.Sprintf("binary file added: %s", file)
|
||||
return core.Sprintf("binary file added: %s", file)
|
||||
}
|
||||
|
||||
// Check file size (reject > 1MB)
|
||||
fullPath := filepath.Join(srcDir, file)
|
||||
info, err := os.Stat(fullPath)
|
||||
if err == nil && info.Size() > 1024*1024 {
|
||||
return fmt.Sprintf("large file: %s (%d bytes)", file, info.Size())
|
||||
return core.Sprintf("large file: %s (%d bytes)", file, info.Size())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ func countChangedFiles(srcDir string) int {
|
|||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
lines := core.Split(core.Trim(string(out)), "\n")
|
||||
if len(lines) == 1 && lines[0] == "" {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -262,7 +262,7 @@ func pushBranch(srcDir, branch string) error {
|
|||
cmd.Dir = srcDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return coreerr.E("harvest.pushBranch", strings.TrimSpace(string(out)), err)
|
||||
return coreerr.E("harvest.pushBranch", core.Trim(string(out)), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
|
@ -83,6 +83,19 @@ func New(opts ...Options) *Subsystem {
|
|||
}
|
||||
}
|
||||
|
||||
// loadBrainKey returns the API key from CORE_BRAIN_KEY env or ~/.claude/brain.key.
|
||||
func loadBrainKey() string {
|
||||
if key := os.Getenv("CORE_BRAIN_KEY"); key != "" {
|
||||
return key
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
data, err := coreio.Local.Read(filepath.Join(home, ".claude", "brain.key"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return core.Trim(data)
|
||||
}
|
||||
|
||||
// debugChannel sends a debug message via the notifier so it arrives as a channel event.
|
||||
func (m *Subsystem) debugChannel(msg string) {
|
||||
if m.notifier != nil {
|
||||
|
|
@ -194,7 +207,7 @@ func (m *Subsystem) check(ctx context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
combined := strings.Join(messages, "\n")
|
||||
combined := core.Join("\n", messages...)
|
||||
m.notify(ctx, combined)
|
||||
|
||||
// Notify resource subscribers that agent status changed
|
||||
|
|
@ -242,7 +255,7 @@ func (m *Subsystem) checkCompletions() string {
|
|||
if !m.seenCompleted[wsName] {
|
||||
m.seenCompleted[wsName] = true
|
||||
if seeded {
|
||||
newlyCompleted = append(newlyCompleted, fmt.Sprintf("%s (%s)", st.Repo, st.Agent))
|
||||
newlyCompleted = append(newlyCompleted, core.Sprintf("%s (%s)", st.Repo, st.Agent))
|
||||
}
|
||||
}
|
||||
case "running":
|
||||
|
|
@ -253,7 +266,7 @@ func (m *Subsystem) checkCompletions() string {
|
|||
if !m.seenCompleted[wsName] {
|
||||
m.seenCompleted[wsName] = true
|
||||
if seeded {
|
||||
newlyCompleted = append(newlyCompleted, fmt.Sprintf("%s (%s) [%s]", st.Repo, st.Agent, st.Status))
|
||||
newlyCompleted = append(newlyCompleted, core.Sprintf("%s (%s) [%s]", st.Repo, st.Agent, st.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -275,27 +288,21 @@ func (m *Subsystem) checkCompletions() string {
|
|||
})
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%d agent(s) completed", len(newlyCompleted))
|
||||
msg := core.Sprintf("%d agent(s) completed", len(newlyCompleted))
|
||||
if running > 0 {
|
||||
msg += fmt.Sprintf(", %d still running", running)
|
||||
msg += core.Sprintf(", %d still running", running)
|
||||
}
|
||||
if queued > 0 {
|
||||
msg += fmt.Sprintf(", %d queued", queued)
|
||||
msg += core.Sprintf(", %d queued", queued)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// checkInbox checks for unread messages.
|
||||
func (m *Subsystem) checkInbox() string {
|
||||
apiKeyStr := os.Getenv("CORE_BRAIN_KEY")
|
||||
apiKeyStr := loadBrainKey()
|
||||
if apiKeyStr == "" {
|
||||
home, _ := os.UserHomeDir()
|
||||
keyFile := filepath.Join(home, ".claude", "brain.key")
|
||||
data, err := coreio.Local.Read(keyFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
apiKeyStr = data
|
||||
return ""
|
||||
}
|
||||
|
||||
// Call the API to check inbox
|
||||
|
|
@ -307,7 +314,7 @@ func (m *Subsystem) checkInbox() string {
|
|||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(apiKeyStr))
|
||||
req.Header.Set("Authorization", "Bearer "+core.Trim(apiKeyStr))
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
httpResp, err := client.Do(req)
|
||||
|
|
@ -397,7 +404,7 @@ func (m *Subsystem) checkInbox() string {
|
|||
})
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d unread message(s) in inbox", unread)
|
||||
return core.Sprintf("%d unread message(s) in inbox", unread)
|
||||
}
|
||||
|
||||
// notify sends a log notification to all connected MCP sessions.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ package monitor
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
|
@ -13,6 +12,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -126,9 +126,9 @@ func TestCheckCompletions_Good_NewCompletions(t *testing.T) {
|
|||
t.Setenv("CORE_WORKSPACE", wsRoot)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
writeWorkspaceStatus(t, wsRoot, fmt.Sprintf("ws-%d", i), map[string]any{
|
||||
writeWorkspaceStatus(t, wsRoot, core.Sprintf("ws-%d", i), map[string]any{
|
||||
"status": "completed",
|
||||
"repo": fmt.Sprintf("repo-%d", i),
|
||||
"repo": core.Sprintf("repo-%d", i),
|
||||
"agent": "claude:sonnet",
|
||||
})
|
||||
}
|
||||
|
|
@ -152,9 +152,9 @@ func TestCheckCompletions_Good_MixedStatuses(t *testing.T) {
|
|||
t.Setenv("CORE_WORKSPACE", wsRoot)
|
||||
|
||||
for i, status := range []string{"completed", "running", "queued"} {
|
||||
writeWorkspaceStatus(t, wsRoot, fmt.Sprintf("ws-%d", i), map[string]any{
|
||||
writeWorkspaceStatus(t, wsRoot, core.Sprintf("ws-%d", i), map[string]any{
|
||||
"status": status,
|
||||
"repo": fmt.Sprintf("repo-%d", i),
|
||||
"repo": core.Sprintf("repo-%d", i),
|
||||
"agent": "claude:sonnet",
|
||||
})
|
||||
}
|
||||
|
|
@ -563,9 +563,9 @@ func TestAgentStatusResource_Good(t *testing.T) {
|
|||
t.Setenv("CORE_WORKSPACE", wsRoot)
|
||||
|
||||
for i, status := range []string{"completed", "running"} {
|
||||
writeWorkspaceStatus(t, wsRoot, fmt.Sprintf("ws-%d", i), map[string]any{
|
||||
writeWorkspaceStatus(t, wsRoot, core.Sprintf("ws-%d", i), map[string]any{
|
||||
"status": status,
|
||||
"repo": fmt.Sprintf("repo-%d", i),
|
||||
"repo": core.Sprintf("repo-%d", i),
|
||||
"agent": "claude:sonnet",
|
||||
})
|
||||
}
|
||||
|
|
@ -767,10 +767,10 @@ func TestHarvestCompleted_Good_MultipleWorkspaces(t *testing.T) {
|
|||
t.Setenv("CORE_WORKSPACE", wsRoot)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
name := fmt.Sprintf("ws-%d", i)
|
||||
name := core.Sprintf("ws-%d", i)
|
||||
wsDir := filepath.Join(wsRoot, "workspace", name)
|
||||
|
||||
sourceDir := filepath.Join(wsRoot, fmt.Sprintf("source-%d", i))
|
||||
sourceDir := filepath.Join(wsRoot, core.Sprintf("source-%d", i))
|
||||
require.NoError(t, os.MkdirAll(sourceDir, 0755))
|
||||
run(t, sourceDir, "git", "init")
|
||||
run(t, sourceDir, "git", "checkout", "-b", "main")
|
||||
|
|
@ -786,7 +786,7 @@ func TestHarvestCompleted_Good_MultipleWorkspaces(t *testing.T) {
|
|||
run(t, srcDir, "git", "add", ".")
|
||||
run(t, srcDir, "git", "commit", "-m", "agent work")
|
||||
|
||||
writeStatus(t, wsDir, "completed", fmt.Sprintf("repo-%d", i), "agent/test-task")
|
||||
writeStatus(t, wsDir, "completed", core.Sprintf("repo-%d", i), "agent/test-task")
|
||||
}
|
||||
|
||||
mon := New()
|
||||
|
|
|
|||
|
|
@ -4,17 +4,15 @@ package monitor
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/agent/pkg/agentic"
|
||||
coreio "dappco.re/go/core/io"
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// CheckinResponse is what the API returns for an agent checkin.
|
||||
|
|
@ -42,7 +40,7 @@ func (m *Subsystem) syncRepos() string {
|
|||
|
||||
agentName := agentic.AgentName()
|
||||
|
||||
checkinURL := fmt.Sprintf("%s/v1/agent/checkin?agent=%s&since=%d", apiURL, neturl.QueryEscape(agentName), m.lastSyncTimestamp)
|
||||
checkinURL := core.Sprintf("%s/v1/agent/checkin?agent=%s&since=%d", apiURL, neturl.QueryEscape(agentName), m.lastSyncTimestamp)
|
||||
|
||||
req, err := http.NewRequest("GET", checkinURL, nil)
|
||||
if err != nil {
|
||||
|
|
@ -50,14 +48,7 @@ func (m *Subsystem) syncRepos() string {
|
|||
}
|
||||
|
||||
// Use brain key for auth
|
||||
brainKey := os.Getenv("CORE_BRAIN_KEY")
|
||||
if brainKey == "" {
|
||||
home, _ := os.UserHomeDir()
|
||||
if data, err := coreio.Local.Read(filepath.Join(home, ".claude", "brain.key")); err == nil {
|
||||
brainKey = strings.TrimSpace(data)
|
||||
}
|
||||
}
|
||||
if brainKey != "" {
|
||||
if brainKey := loadBrainKey(); brainKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+brainKey)
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +102,7 @@ func (m *Subsystem) syncRepos() string {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
current := strings.TrimSpace(string(currentBranch))
|
||||
current := core.Trim(string(currentBranch))
|
||||
|
||||
// Determine which branch to pull — use server-reported branch,
|
||||
// fall back to current if server didn't specify
|
||||
|
|
@ -128,7 +119,7 @@ func (m *Subsystem) syncRepos() string {
|
|||
statusCmd := exec.Command("git", "status", "--porcelain")
|
||||
statusCmd.Dir = repoDir
|
||||
status, _ := statusCmd.Output()
|
||||
if len(strings.TrimSpace(string(status))) > 0 {
|
||||
if len(core.Trim(string(status))) > 0 {
|
||||
continue // Don't pull if dirty
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +145,7 @@ func (m *Subsystem) syncRepos() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Synced %d repo(s): %s", len(pulled), strings.Join(pulled, ", "))
|
||||
return core.Sprintf("Synced %d repo(s): %s", len(pulled), core.Join(", ", pulled...))
|
||||
}
|
||||
|
||||
// lastSyncTimestamp is stored on the subsystem — add it via the check cycle.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/agent/pkg/lib"
|
||||
)
|
||||
|
||||
|
|
@ -138,17 +139,17 @@ func detectGitRemote() string {
|
|||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
url := strings.TrimSpace(string(output))
|
||||
url := core.Trim(string(output))
|
||||
|
||||
// SSH: git@github.com:owner/repo.git or ssh://git@forge.lthn.ai:2223/core/agent.git
|
||||
if strings.Contains(url, ":") {
|
||||
parts := strings.SplitN(url, ":", 2)
|
||||
if core.Contains(url, ":") {
|
||||
parts := core.SplitN(url, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
repo := parts[1]
|
||||
repo = strings.TrimSuffix(repo, ".git")
|
||||
repo = core.TrimSuffix(repo, ".git")
|
||||
// Handle port in SSH URL (ssh://git@host:port/path)
|
||||
if strings.Contains(repo, "/") {
|
||||
segments := strings.SplitN(repo, "/", 2)
|
||||
if core.Contains(repo, "/") {
|
||||
segments := core.SplitN(repo, "/", 2)
|
||||
if len(segments) == 2 && strings.ContainsAny(segments[0], "0123456789") {
|
||||
repo = segments[1]
|
||||
}
|
||||
|
|
@ -161,7 +162,7 @@ func detectGitRemote() string {
|
|||
for _, host := range []string{"github.com/", "forge.lthn.ai/"} {
|
||||
if idx := strings.Index(url, host); idx >= 0 {
|
||||
repo := url[idx+len(host):]
|
||||
return strings.TrimSuffix(repo, ".git")
|
||||
return core.TrimSuffix(repo, ".git")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue