[agent/claude] Phase 2: Replace stdlib strings/fmt with Core primitives acr... #8

Closed
Virgil wants to merge 1 commit from agent/phase-2--replace-stdlib-strings-fmt-with into dev
29 changed files with 257 additions and 270 deletions

View file

@ -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")

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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,

View file

@ -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 ""

View file

@ -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
}

View file

@ -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"))
}

View file

@ -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 {

View file

@ -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, "&")

View file

@ -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 {

View file

@ -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)
}

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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"

View file

@ -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
}

View file

@ -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),
})
}
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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.

View file

@ -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()

View file

@ -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.

View file

@ -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")
}
}