refactor: AX compliance sweep — replace banned stdlib imports with core primitives

Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.

Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-04-13 09:32:00 +01:00
parent 65b686283f
commit 91803e32df
18 changed files with 318 additions and 291 deletions

View file

@ -4,17 +4,15 @@ package agentic
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -54,7 +52,7 @@ func (s *PrepSubsystem) registerDispatchTool(svc *coremcp.Service) {
// 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 {
@ -78,7 +76,7 @@ func agentCommand(agent, prompt string) (string, []string, error) {
return "claude", args, nil
case "local":
home, _ := os.UserHomeDir()
script := filepath.Join(home, "Code", "core", "agent", "scripts", "local-agent.sh")
script := core.Path(home, "Code", "core", "agent", "scripts", "local-agent.sh")
return "bash", []string{script, prompt}, nil
default:
return "", nil, coreerr.E("agentCommand", "unknown agent: "+agent, nil)
@ -119,14 +117,14 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
}
wsDir := prepOut.WorkspaceDir
srcDir := filepath.Join(wsDir, "src")
srcDir := core.Path(wsDir, "src")
// The prompt is just: read PROMPT.md and do the work
prompt := "Read PROMPT.md for instructions. All context files (CLAUDE.md, TODO.md, CONTEXT.md, CONSUMERS.md, RECENT.md) are in the parent directory. Work in this directory."
if input.DryRun {
// Read PROMPT.md for the dry run output
promptRaw, _ := coreio.Local.Read(filepath.Join(wsDir, "PROMPT.md"))
promptRaw, _ := coreio.Local.Read(core.Path(wsDir, "PROMPT.md"))
return nil, DispatchOutput{
Success: true,
Agent: input.Agent,
@ -181,7 +179,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
return nil, DispatchOutput{}, err
}
outputFile := filepath.Join(wsDir, fmt.Sprintf("agent-%s.log", input.Agent))
outputFile := core.Path(wsDir, core.Sprintf("agent-%s.log", input.Agent))
outFile, err := os.Create(outputFile)
if err != nil {
return nil, DispatchOutput{}, coreerr.E("dispatch", "failed to create log file", err)
@ -247,7 +245,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
status := "completed"
channel := coremcp.ChannelAgentComplete
payload := map[string]any{
"workspace": filepath.Base(wsDir),
"workspace": core.PathBase(wsDir),
"repo": input.Repo,
"org": input.Org,
"agent": input.Agent,
@ -257,11 +255,11 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
// Update status to completed or blocked.
if st, err := readStatus(wsDir); err == nil {
st.PID = 0
if data, err := coreio.Local.Read(filepath.Join(wsDir, "src", "BLOCKED.md")); err == nil {
if data, err := coreio.Local.Read(core.Path(wsDir, "src", "BLOCKED.md")); err == nil {
status = "blocked"
channel = coremcp.ChannelAgentBlocked
st.Status = status
st.Question = strings.TrimSpace(data)
st.Question = core.Trim(data)
if st.Question != "" {
payload["question"] = st.Question
}

View file

@ -3,10 +3,8 @@
package agentic
import (
"bytes"
"context"
"net/http"
"os"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
@ -96,26 +94,22 @@ func (s *PrepSubsystem) createIssueViaAPI(repo, title, description, issueType, p
}
// Read the agent API key from file
home, _ := os.UserHomeDir()
home := core.Env("HOME")
apiKeyData, err := coreio.Local.Read(core.Path(home, ".claude", "agent-api.key"))
if err != nil {
return false
}
apiKey := core.Trim(apiKeyData)
r := core.JSONMarshal(map[string]string{
payloadStr := core.JSONMarshalString(map[string]string{
"title": title,
"description": description,
"type": issueType,
"priority": priority,
"reporter": "cladius",
})
if !r.OK {
return false
}
payload := r.Value.([]byte)
req, err := http.NewRequest("POST", s.brainURL+"/v1/issues", bytes.NewReader(payload))
req, err := http.NewRequest("POST", s.brainURL+"/v1/issues", core.NewReader(payloadStr))
if err != nil {
return false
}

View file

@ -4,12 +4,11 @@ package agentic
import (
"context"
"fmt"
"os/exec"
"path/filepath"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -64,7 +63,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
skipped := make([]string, 0)
for _, repo := range repos {
repoDir := filepath.Join(basePath, repo)
repoDir := core.Path(basePath, repo)
if !hasRemote(repoDir, "github") {
skipped = append(skipped, repo+": no github remote")
continue
@ -88,7 +87,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
}
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
}

View file

@ -7,7 +7,6 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"strings"
"time"
core "dappco.re/go/core"
@ -430,33 +429,33 @@ func planPath(dir, id string) string {
}
func generatePlanID(title string) string {
slug := strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-' {
return r
b := core.NewBuilder()
b.Grow(len(title))
for _, r := range title {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '-':
b.WriteRune(r)
case r >= 'A' && r <= 'Z':
b.WriteRune(r + 32)
case r == ' ':
b.WriteByte('-')
}
if r >= 'A' && r <= 'Z' {
return r + 32
}
if r == ' ' {
return '-'
}
return -1
}, title)
}
slug := b.String()
// Trim consecutive dashes and cap length
// Collapse consecutive dashes and cap length
for core.Contains(slug, "--") {
slug = core.Replace(slug, "--", "-")
}
slug = strings.Trim(slug, "-")
slug = trimDashes(slug)
if len(slug) > 30 {
slug = slug[:30]
slug = trimDashes(slug[:30])
}
slug = strings.TrimRight(slug, "-")
// Append short random suffix for uniqueness
b := make([]byte, 3)
rand.Read(b)
return slug + "-" + hex.EncodeToString(b)
rnd := make([]byte, 3)
rand.Read(rnd)
return slug + "-" + hex.EncodeToString(rnd)
}
func readPlan(dir, id string) (*Plan, error) {

View file

@ -6,15 +6,13 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os/exec"
"path/filepath"
"strings"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -66,8 +64,8 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
return nil, CreatePROutput{}, coreerr.E("createPR", "no Forge token configured", nil)
}
wsDir := filepath.Join(s.workspaceRoot(), input.Workspace)
srcDir := filepath.Join(wsDir, "src")
wsDir := core.Path(s.workspaceRoot(), input.Workspace)
srcDir := core.Path(wsDir, "src")
if _, err := coreio.Local.List(srcDir); err != nil {
return nil, CreatePROutput{}, coreerr.E("createPR", "workspace not found: "+input.Workspace, nil)
@ -87,7 +85,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
@ -105,7 +103,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
@ -143,7 +141,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)
}
@ -159,17 +157,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()
}
@ -185,7 +183,7 @@ func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base
return "", 0, coreerr.E("forgeCreatePR", "failed to marshal PR payload", err)
}
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, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
if err != nil {
return "", 0, coreerr.E("forgeCreatePR", "failed to build PR request", err)
@ -202,10 +200,10 @@ func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base
if resp.StatusCode != 201 {
var errBody map[string]any
if err := json.NewDecoder(resp.Body).Decode(&errBody); err != nil {
return "", 0, coreerr.E("forgeCreatePR", fmt.Sprintf("HTTP %d with unreadable error body", resp.StatusCode), err)
return "", 0, coreerr.E("forgeCreatePR", core.Sprintf("HTTP %d with unreadable error body", resp.StatusCode), err)
}
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 {
@ -225,7 +223,7 @@ func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, is
return
}
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, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payload))
if err != nil {
return
@ -337,7 +335,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)
@ -348,7 +346,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 for "+repo, resp.StatusCode), nil)
return nil, coreerr.E("listRepoPRs", core.Sprintf("HTTP %d for "+repo, resp.StatusCode), nil)
}
var prs []struct {

View file

@ -8,18 +8,14 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
goio "io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
"gopkg.in/yaml.v3"
)
@ -46,17 +42,17 @@ var (
//
// prep := NewPrep()
func NewPrep() *PrepSubsystem {
home, _ := os.UserHomeDir()
home := core.Env("HOME")
forgeToken := os.Getenv("FORGE_TOKEN")
forgeToken := core.Env("FORGE_TOKEN")
if forgeToken == "" {
forgeToken = os.Getenv("GITEA_TOKEN")
forgeToken = core.Env("GITEA_TOKEN")
}
brainKey := os.Getenv("CORE_BRAIN_KEY")
brainKey := core.Env("CORE_BRAIN_KEY")
if brainKey == "" {
if data, err := coreio.Local.Read(filepath.Join(home, ".claude", "brain.key")); err == nil {
brainKey = strings.TrimSpace(data)
if data, err := coreio.Local.Read(core.Path(home, ".claude", "brain.key")); err == nil {
brainKey = core.Trim(data)
}
}
@ -65,8 +61,8 @@ func NewPrep() *PrepSubsystem {
forgeToken: forgeToken,
brainURL: envOr("CORE_BRAIN_URL", "https://api.lthn.sh"),
brainKey: brainKey,
specsPath: envOr("SPECS_PATH", filepath.Join(home, "Code", "host-uk", "specs")),
codePath: envOr("CODE_PATH", filepath.Join(home, "Code")),
specsPath: envOr("SPECS_PATH", core.Path(home, "Code", "host-uk", "specs")),
codePath: envOr("CODE_PATH", core.Path(home, "Code")),
client: &http.Client{Timeout: 30 * time.Second},
}
}
@ -84,24 +80,24 @@ func (s *PrepSubsystem) emitChannel(ctx context.Context, channel string, data an
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
if v := core.Env(key); v != "" {
return v
}
return fallback
}
func sanitizeRepoPathSegment(value, field string, allowSubdirs bool) (string, error) {
if strings.TrimSpace(value) != value {
if core.Trim(value) != value {
return "", coreerr.E("prepWorkspace", field+" contains whitespace", nil)
}
if value == "" {
return "", nil
}
if strings.Contains(value, "\\") {
if core.Contains(value, "\\") {
return "", coreerr.E("prepWorkspace", field+" contains invalid path separator", nil)
}
parts := strings.Split(value, "/")
parts := core.Split(value, "/")
if !allowSubdirs && len(parts) != 1 {
return "", coreerr.E("prepWorkspace", field+" may not contain subdirectories", nil)
}
@ -161,7 +157,7 @@ func (s *PrepSubsystem) Shutdown(_ context.Context) error { return nil }
// workspaceRoot returns the base directory for agent workspaces.
func (s *PrepSubsystem) workspaceRoot() string {
return filepath.Join(s.codePath, ".core", "workspace")
return core.Path(s.codePath, ".core", "workspace")
}
// --- Input/Output types ---
@ -227,8 +223,8 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
// Workspace root: .core/workspace/{repo}-{timestamp}/
wsRoot := s.workspaceRoot()
coreio.Local.EnsureDir(wsRoot)
wsName := fmt.Sprintf("%s-%d", input.Repo, time.Now().Unix())
wsDir := filepath.Join(wsRoot, wsName)
wsName := core.Sprintf("%s-%d", input.Repo, time.Now().Unix())
wsDir := core.Path(wsRoot, wsName)
// Create workspace structure
// kb/ and specs/ will be created inside src/ after clone
@ -236,10 +232,10 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
out := PrepOutput{WorkspaceDir: wsDir}
// Source repo path
repoPath := filepath.Join(s.codePath, "core", input.Repo)
repoPath := core.Path(s.codePath, "core", input.Repo)
// 1. Clone repo into src/ and create feature branch
srcDir := filepath.Join(wsDir, "src")
srcDir := core.Path(wsDir, "src")
cloneCmd := exec.CommandContext(ctx, "git", "clone", repoPath, srcDir)
if err := cloneCmd.Run(); err != nil {
return nil, PrepOutput{}, coreerr.E("prepWorkspace", "failed to clone repository", err)
@ -251,12 +247,12 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
taskSlug := branchSlug(input.Task)
if input.Issue > 0 {
issueSlug := branchSlug(input.Task)
branchName = fmt.Sprintf("agent/issue-%d", input.Issue)
branchName = core.Sprintf("agent/issue-%d", input.Issue)
if issueSlug != "" {
branchName += "-" + issueSlug
}
} else if taskSlug != "" {
branchName = fmt.Sprintf("agent/%s", taskSlug)
branchName = core.Sprintf("agent/%s", taskSlug)
}
}
if branchName != "" {
@ -269,29 +265,29 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
}
// Create context dirs inside src/
coreio.Local.EnsureDir(filepath.Join(srcDir, "kb"))
coreio.Local.EnsureDir(filepath.Join(srcDir, "specs"))
coreio.Local.EnsureDir(core.Path(srcDir, "kb"))
coreio.Local.EnsureDir(core.Path(srcDir, "specs"))
// Remote stays as local clone origin — agent cannot push to forge.
// Reviewer pulls changes from workspace and pushes after verification.
// 2. Copy CLAUDE.md and GEMINI.md to workspace
claudeMdPath := filepath.Join(repoPath, "CLAUDE.md")
claudeMdPath := core.Path(repoPath, "CLAUDE.md")
if data, err := coreio.Local.Read(claudeMdPath); err == nil {
_ = writeAtomic(filepath.Join(wsDir, "src", "CLAUDE.md"), data)
_ = writeAtomic(core.Path(wsDir, "src", "CLAUDE.md"), data)
out.ClaudeMd = true
}
// Copy GEMINI.md from core/agent (ethics framework for all agents)
agentGeminiMd := filepath.Join(s.codePath, "core", "agent", "GEMINI.md")
agentGeminiMd := core.Path(s.codePath, "core", "agent", "GEMINI.md")
if data, err := coreio.Local.Read(agentGeminiMd); err == nil {
_ = writeAtomic(filepath.Join(wsDir, "src", "GEMINI.md"), data)
_ = writeAtomic(core.Path(wsDir, "src", "GEMINI.md"), data)
}
// Copy persona if specified
if persona != "" {
personaPath := filepath.Join(s.codePath, "core", "agent", "prompts", "personas", persona+".md")
personaPath := core.Path(s.codePath, "core", "agent", "prompts", "personas", persona+".md")
if data, err := coreio.Local.Read(personaPath); err == nil {
_ = writeAtomic(filepath.Join(wsDir, "src", "PERSONA.md"), data)
_ = writeAtomic(core.Path(wsDir, "src", "PERSONA.md"), data)
}
}
@ -299,9 +295,9 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
if input.Issue > 0 {
s.generateTodo(ctx, input.Org, input.Repo, input.Issue, wsDir)
} else if input.Task != "" {
todo := fmt.Sprintf("# TASK: %s\n\n**Repo:** %s/%s\n**Status:** ready\n\n## Objective\n\n%s\n",
todo := core.Sprintf("# TASK: %s\n\n**Repo:** %s/%s\n**Status:** ready\n\n## Objective\n\n%s\n",
input.Task, input.Org, input.Repo, input.Task)
_ = writeAtomic(filepath.Join(wsDir, "src", "TODO.md"), todo)
_ = writeAtomic(core.Path(wsDir, "src", "TODO.md"), todo)
}
// 4. Generate CONTEXT.md from OpenBrain
@ -333,12 +329,12 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
// branchSlug converts a free-form string into a git-friendly branch suffix.
func branchSlug(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
value = core.Lower(core.Trim(value))
if value == "" {
return ""
}
var b strings.Builder
b := core.NewBuilder()
b.Grow(len(value))
lastDash := false
for _, r := range value {
@ -359,14 +355,42 @@ func branchSlug(value string) string {
}
}
slug := strings.Trim(b.String(), "-")
slug := trimDashes(b.String())
if len(slug) > 40 {
slug = slug[:40]
slug = strings.Trim(slug, "-")
slug = trimDashes(slug[:40])
}
return slug
}
// sanitizeFilename replaces non-alphanumeric characters (except - _ .) with dashes.
func sanitizeFilename(title string) string {
b := core.NewBuilder()
b.Grow(len(title))
for _, r := range title {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9',
r == '-', r == '_', r == '.':
b.WriteRune(r)
default:
b.WriteByte('-')
}
}
return b.String()
}
// trimDashes strips leading and trailing dash characters from a string.
func trimDashes(s string) string {
start := 0
for start < len(s) && s[start] == '-' {
start++
}
end := len(s)
for end > start && s[end-1] == '-' {
end--
}
return s[start:end]
}
// --- Prompt templates ---
func (s *PrepSubsystem) writePromptTemplate(template, wsDir string) {
@ -434,7 +458,7 @@ Do NOT push. Commit only — a reviewer will verify and push.
prompt = "Read TODO.md and complete the task. Work in src/.\n"
}
_ = writeAtomic(filepath.Join(wsDir, "src", "PROMPT.md"), prompt)
_ = writeAtomic(core.Path(wsDir, "src", "PROMPT.md"), prompt)
}
// --- Plan template rendering ---
@ -443,11 +467,11 @@ Do NOT push. Commit only — a reviewer will verify and push.
// and writes PLAN.md into the workspace src/ directory.
func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map[string]string, task string, wsDir string) {
// Look for template in core/agent/prompts/templates/
templatePath := filepath.Join(s.codePath, "core", "agent", "prompts", "templates", templateSlug+".yaml")
templatePath := core.Path(s.codePath, "core", "agent", "prompts", "templates", templateSlug+".yaml")
content, err := coreio.Local.Read(templatePath)
if err != nil {
// Try .yml extension
templatePath = filepath.Join(s.codePath, "core", "agent", "prompts", "templates", templateSlug+".yml")
templatePath = core.Path(s.codePath, "core", "agent", "prompts", "templates", templateSlug+".yml")
content, err = coreio.Local.Read(templatePath)
if err != nil {
return // Template not found, skip silently
@ -456,8 +480,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
@ -477,7 +501,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")
@ -495,7 +519,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")
}
@ -512,7 +536,7 @@ func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map
plan.WriteString("\n**Commit after completing this phase.**\n\n---\n\n")
}
_ = writeAtomic(filepath.Join(wsDir, "src", "PLAN.md"), plan.String())
_ = writeAtomic(core.Path(wsDir, "src", "PLAN.md"), plan.String())
}
// --- Helpers (unchanged) ---
@ -522,7 +546,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, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return 0
@ -553,7 +577,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, err := http.NewRequestWithContext(ctx, "GET", pageURL, nil)
if err != nil {
continue
@ -585,14 +609,9 @@ func (s *PrepSubsystem) pullWiki(ctx context.Context, org, repo, wsDir string) i
if err != nil {
continue
}
filename := strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.' {
return r
}
return '-'
}, page.Title) + ".md"
filename := sanitizeFilename(page.Title) + ".md"
_ = writeAtomic(filepath.Join(wsDir, "src", "kb", filename), string(content))
_ = writeAtomic(core.Path(wsDir, "src", "kb", filename), string(content))
count++
}
@ -604,9 +623,9 @@ func (s *PrepSubsystem) copySpecs(wsDir string) int {
count := 0
for _, file := range specFiles {
src := filepath.Join(s.specsPath, file)
src := core.Path(s.specsPath, file)
if data, err := coreio.Local.Read(src); err == nil {
_ = writeAtomic(filepath.Join(wsDir, "src", "specs", file), data)
_ = writeAtomic(core.Path(wsDir, "src", "specs", file), data)
count++
}
}
@ -629,7 +648,7 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string)
return 0
}
req, err := http.NewRequestWithContext(ctx, "POST", s.brainURL+"/v1/brain/recall", strings.NewReader(string(body)))
req, err := http.NewRequestWithContext(ctx, "POST", s.brainURL+"/v1/brain/recall", core.NewReader(string(body)))
if err != nil {
return 0
}
@ -646,18 +665,18 @@ func (s *PrepSubsystem) generateContext(ctx context.Context, repo, wsDir string)
return 0
}
respData, err := goio.ReadAll(resp.Body)
if err != nil {
readResult := core.ReadAll(resp.Body)
if !readResult.OK {
return 0
}
var result struct {
Memories []map[string]any `json:"memories"`
}
if err := json.Unmarshal(respData, &result); err != nil {
if ur := core.JSONUnmarshal([]byte(readResult.Value.(string)), &result); !ur.OK {
return 0
}
var content strings.Builder
content := core.NewBuilder()
content.WriteString("# Context — " + repo + "\n\n")
content.WriteString("> Relevant knowledge from OpenBrain.\n\n")
@ -666,15 +685,15 @@ 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))
}
_ = writeAtomic(filepath.Join(wsDir, "src", "CONTEXT.md"), content.String())
_ = writeAtomic(core.Path(wsDir, "src", "CONTEXT.md"), content.String())
return len(result.Memories)
}
func (s *PrepSubsystem) findConsumers(repo, wsDir string) int {
goWorkPath := filepath.Join(s.codePath, "go.work")
goWorkPath := core.Path(s.codePath, "go.work")
modulePath := "forge.lthn.ai/core/" + repo
workData, err := coreio.Local.Read(goWorkPath)
@ -683,19 +702,19 @@ 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, "./"))
goMod := filepath.Join(dir, "go.mod")
dir := core.Path(s.codePath, core.TrimPrefix(line, "./"))
goMod := core.Path(dir, "go.mod")
modData, err := coreio.Local.Read(goMod)
if err != nil {
continue
}
if strings.Contains(modData, modulePath) && !strings.HasPrefix(modData, "module "+modulePath) {
consumers = append(consumers, filepath.Base(dir))
if core.Contains(modData, modulePath) && !core.HasPrefix(modData, "module "+modulePath) {
consumers = append(consumers, core.PathBase(dir))
}
}
@ -705,8 +724,8 @@ 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))
_ = writeAtomic(filepath.Join(wsDir, "src", "CONSUMERS.md"), content)
content += core.Sprintf("\n**Breaking change risk: %d consumers.**\n", len(consumers))
_ = writeAtomic(core.Path(wsDir, "src", "CONSUMERS.md"), content)
}
return len(consumers)
@ -720,10 +739,10 @@ 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"
_ = writeAtomic(filepath.Join(wsDir, "src", "RECENT.md"), content)
_ = writeAtomic(core.Path(wsDir, "src", "RECENT.md"), content)
}
return len(lines)
@ -734,7 +753,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)
@ -753,11 +772,11 @@ 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 := core.Sprintf("# TASK: %s\n\n", issueData.Title)
content += core.Sprintf("**Status:** ready\n")
content += core.Sprintf("**Source:** %s/%s/%s/issues/%d\n", s.forgeURL, org, repo, issue)
content += core.Sprintf("**Repo:** %s/%s\n\n---\n\n", org, repo)
content += "## Objective\n\n" + issueData.Body + "\n"
_ = writeAtomic(filepath.Join(wsDir, "src", "TODO.md"), content)
_ = writeAtomic(core.Path(wsDir, "src", "TODO.md"), content)
}

View file

@ -3,18 +3,19 @@
package agentic
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
"gopkg.in/yaml.v3"
)
// os.Create, os.Open, os.DevNull, os.Environ, os.FindProcess are used for
// process spawning and management — no core equivalents for these OS primitives.
// DispatchConfig controls agent dispatch behaviour.
type DispatchConfig struct {
DefaultAgent string `yaml:"default_agent"`
@ -43,7 +44,7 @@ type AgentsConfig struct {
// loadAgentsConfig reads config/agents.yaml from the code path.
func (s *PrepSubsystem) loadAgentsConfig() *AgentsConfig {
paths := []string{
filepath.Join(s.codePath, ".core", "agents.yaml"),
core.Path(s.codePath, ".core", "agents.yaml"),
}
for _, path := range paths {
@ -79,9 +80,16 @@ func (s *PrepSubsystem) delayForAgent(agent string) time.Duration {
return 0
}
// Parse reset time
// Parse reset time (format: "HH:MM")
resetHour, resetMin := 6, 0
fmt.Sscanf(rate.ResetUTC, "%d:%d", &resetHour, &resetMin)
if parts := core.Split(rate.ResetUTC, ":"); len(parts) == 2 {
if h, ok := parseSimpleInt(parts[0]); ok {
resetHour = h
}
if m, ok := parseSimpleInt(parts[1]); ok {
resetMin = m
}
}
now := time.Now().UTC()
resetToday := time.Date(now.Year(), now.Month(), now.Day(), resetHour, resetMin, 0, 0, time.UTC)
@ -115,9 +123,9 @@ func (s *PrepSubsystem) listWorkspaceDirs() []string {
if !entry.IsDir() {
continue
}
path := filepath.Join(wsRoot, entry.Name())
path := core.Path(wsRoot, entry.Name())
// Check if this dir has a status.json (it's a workspace)
if coreio.Local.IsFile(filepath.Join(path, "status.json")) {
if coreio.Local.IsFile(core.Path(path, "status.json")) {
dirs = append(dirs, path)
continue
}
@ -128,8 +136,8 @@ func (s *PrepSubsystem) listWorkspaceDirs() []string {
}
for _, sub := range subEntries {
if sub.IsDir() {
subPath := filepath.Join(path, sub.Name())
if coreio.Local.IsFile(filepath.Join(subPath, "status.json")) {
subPath := core.Path(path, sub.Name())
if coreio.Local.IsFile(core.Path(subPath, "status.json")) {
dirs = append(dirs, subPath)
}
}
@ -146,7 +154,7 @@ func (s *PrepSubsystem) countRunningByAgent(agent string) int {
if err != nil || st.Status != "running" {
continue
}
stBase := strings.SplitN(st.Agent, ":", 2)[0]
stBase := core.SplitN(st.Agent, ":", 2)[0]
if stBase != agent {
continue
}
@ -162,7 +170,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.
@ -176,6 +184,23 @@ func (s *PrepSubsystem) canDispatchAgent(agent string) bool {
return s.countRunningByAgent(base) < limit
}
// parseSimpleInt parses a small non-negative integer from a string.
// Returns (value, true) on success, (0, false) on failure.
func parseSimpleInt(s string) (int, bool) {
s = core.Trim(s)
if s == "" {
return 0, false
}
n := 0
for _, r := range s {
if r < '0' || r > '9' {
return 0, false
}
n = n*10 + int(r-'0')
}
return n, true
}
// canDispatch is kept for backwards compat.
func (s *PrepSubsystem) canDispatch() bool {
return true
@ -205,7 +230,7 @@ func (s *PrepSubsystem) drainQueue() {
continue
}
srcDir := filepath.Join(wsDir, "src")
srcDir := core.Path(wsDir, "src")
prompt := "Read PROMPT.md for instructions. All context files (CLAUDE.md, TODO.md, CONTEXT.md, CONSUMERS.md, RECENT.md) are in the parent directory. Work in this directory."
command, args, err := agentCommand(st.Agent, prompt)
@ -213,7 +238,7 @@ func (s *PrepSubsystem) drainQueue() {
continue
}
outputFile := filepath.Join(wsDir, fmt.Sprintf("agent-%s.log", st.Agent))
outputFile := core.Path(wsDir, core.Sprintf("agent-%s.log", st.Agent))
outFile, err := os.Create(outputFile)
if err != nil {
continue

View file

@ -5,19 +5,18 @@ package agentic
import (
"context"
"encoding/json"
"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"
)
func listLocalRepos(basePath string) []string {
entries, err := os.ReadDir(basePath)
entries, err := coreio.Local.List(basePath)
if err != nil {
return nil
}
@ -35,7 +34,7 @@ func hasRemote(repoDir, remote string) bool {
cmd := exec.Command("git", "remote", "get-url", remote)
cmd.Dir = repoDir
if out, err := cmd.Output(); err == nil {
return strings.TrimSpace(string(out)) != ""
return core.Trim(string(out)) != ""
}
return false
}
@ -48,7 +47,7 @@ func commitsAhead(repoDir, baseRef, headRef string) int {
return 0
}
count, err := parsePositiveInt(strings.TrimSpace(string(out)))
count, err := parsePositiveInt(core.Trim(string(out)))
if err != nil {
return 0
}
@ -64,8 +63,8 @@ func filesChanged(repoDir, baseRef, headRef string) int {
}
count := 0
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
if strings.TrimSpace(line) != "" {
for _, line := range core.Split(core.Trim(string(out)), "\n") {
if core.Trim(line) != "" {
count++
}
}
@ -79,11 +78,11 @@ func gitOutput(repoDir string, args ...string) (string, error) {
if err != nil {
return "", coreerr.E("gitOutput", string(out), err)
}
return strings.TrimSpace(string(out)), nil
return core.Trim(string(out)), nil
}
func parsePositiveInt(value string) (int, error) {
value = strings.TrimSpace(value)
value = core.Trim(value)
if value == "" {
return 0, coreerr.E("parsePositiveInt", "empty value", nil)
}
@ -148,11 +147,11 @@ func createGitHubPR(ctx context.Context, repoDir, repo string, commits, files in
return "", coreerr.E("createGitHubPR", string(out), err)
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
lines := core.Split(core.Trim(string(out)), "\n")
if len(lines) == 0 {
return "", nil
}
return strings.TrimSpace(lines[len(lines)-1]), nil
return core.Trim(lines[len(lines)-1]), nil
}
func ensureDevBranch(repoDir string) error {
@ -194,7 +193,7 @@ func parseRetryAfter(detail string) time.Duration {
return 5 * time.Minute
}
switch strings.ToLower(match[2]) {
switch core.Lower(match[2]) {
case "hour", "hours":
return time.Duration(n) * time.Hour
case "second", "seconds":
@ -205,5 +204,5 @@ func parseRetryAfter(detail string) time.Duration {
}
func repoRootFromCodePath(codePath string) string {
return filepath.Join(codePath, "core")
return core.Path(codePath, "core")
}

View file

@ -4,16 +4,14 @@ package agentic
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -52,8 +50,8 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
return nil, ResumeOutput{}, coreerr.E("resume", "workspace is required", nil)
}
wsDir := filepath.Join(s.workspaceRoot(), input.Workspace)
srcDir := filepath.Join(wsDir, "src")
wsDir := core.Path(s.workspaceRoot(), input.Workspace)
srcDir := core.Path(wsDir, "src")
// Verify workspace exists
if _, err := coreio.Local.List(srcDir); err != nil {
@ -78,8 +76,8 @@ 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)
answerPath := core.Path(srcDir, "ANSWER.md")
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
if err := writeAtomic(answerPath, content); err != nil {
return nil, ResumeOutput{}, coreerr.E("resume", "failed to write ANSWER.md", err)
}
@ -102,7 +100,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
}
// Spawn agent as detached process (survives parent death)
outputFile := filepath.Join(wsDir, fmt.Sprintf("agent-%s-run%d.log", agent, st.Runs+1))
outputFile := core.Path(wsDir, core.Sprintf("agent-%s-run%d.log", agent, st.Runs+1))
command, args, err := agentCommand(agent, prompt)
if err != nil {
@ -154,10 +152,10 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
"branch": st.Branch,
}
if data, err := coreio.Local.Read(filepath.Join(srcDir, "BLOCKED.md")); err == nil {
if data, err := coreio.Local.Read(core.Path(srcDir, "BLOCKED.md")); err == nil {
status = "blocked"
channel = coremcp.ChannelAgentBlocked
st.Question = strings.TrimSpace(data)
st.Question = core.Trim(data)
if st.Question != "" {
payload["question"] = st.Question
}

View file

@ -5,16 +5,14 @@ package agentic
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -93,7 +91,7 @@ func (s *PrepSubsystem) reviewQueue(ctx context.Context, _ *mcp.CallToolRequest,
continue
}
repoDir := filepath.Join(basePath, repo)
repoDir := core.Path(basePath, repo)
reviewer := input.Reviewer
if reviewer == "" {
reviewer = "coderabbit"
@ -137,7 +135,7 @@ func (s *PrepSubsystem) findReviewCandidates(basePath string) []string {
if !entry.IsDir() {
continue
}
repoDir := filepath.Join(basePath, entry.Name())
repoDir := core.Path(basePath, entry.Name())
if !hasRemote(repoDir, "github") {
continue
}
@ -154,22 +152,22 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
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
}
cmd := reviewerCommand(ctx, repoDir, reviewer)
cmd.Dir = repoDir
out, err := cmd.CombinedOutput()
output := strings.TrimSpace(string(out))
output := core.Trim(string(out))
if strings.Contains(strings.ToLower(output), "rate limit") {
if core.Contains(core.Lower(output), "rate limit") {
result.Verdict = "rate_limited"
result.Detail = output
return result
}
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"
if output != "" {
result.Detail = output
@ -182,7 +180,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
s.storeReviewOutput(repoDir, repo, reviewer, output)
result.Findings = countFindingHints(output)
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"
if dryRun {
result.Action = "skipped (dry run)"
@ -198,7 +196,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
mergeCmd.Dir = repoDir
if mergeOut, err := mergeCmd.CombinedOutput(); err == nil {
result.Action = "merged"
result.Detail = strings.TrimSpace(string(mergeOut))
result.Detail = core.Trim(string(mergeOut))
return result
}
}
@ -219,7 +217,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string) {
home := reviewQueueHomeDir()
dataDir := filepath.Join(home, ".core", "training", "reviews")
dataDir := core.Path(home, ".core", "training", "reviews")
if err := coreio.Local.EnsureDir(dataDir); err != nil {
return
}
@ -235,13 +233,13 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string
return
}
name := fmt.Sprintf("%s-%s-%d.json", repo, reviewer, time.Now().Unix())
_ = writeAtomic(filepath.Join(dataDir, name), string(data))
name := core.Sprintf("%s-%s-%d.json", repo, reviewer, time.Now().Unix())
_ = writeAtomic(core.Path(dataDir, name), string(data))
}
func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) {
home := reviewQueueHomeDir()
path := filepath.Join(home, ".core", "coderabbit-ratelimit.json")
path := core.Path(home, ".core", "coderabbit-ratelimit.json")
data, err := json.Marshal(info)
if err != nil {
return
@ -251,7 +249,7 @@ func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) {
func (s *PrepSubsystem) loadRateLimitState() *RateLimitInfo {
home := reviewQueueHomeDir()
path := filepath.Join(home, ".core", "coderabbit-ratelimit.json")
path := core.Path(home, ".core", "coderabbit-ratelimit.json")
data, err := coreio.Local.Read(path)
if err != nil {
return nil

View file

@ -6,7 +6,6 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
@ -170,7 +169,7 @@ func (s *PrepSubsystem) listRepoIssues(ctx context.Context, org, repo, label str
Title: issue.Title,
Labels: labels,
Assignee: assignee,
URL: strings.Replace(issue.HTMLURL, "https://forge.lthn.ai", s.forgeURL, 1),
URL: core.Replace(issue.HTMLURL, "https://forge.lthn.ai", s.forgeURL),
})
}

View file

@ -15,6 +15,9 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// os.Stat and os.FindProcess are used for workspace age detection and PID
// liveness checks — these are OS-level queries with no core equivalent.
// Workspace status file convention:
//
// {workspace}/status.json — current state of the workspace

View file

@ -9,6 +9,9 @@ import (
coreio "dappco.re/go/core/io"
)
// os.CreateTemp, os.Remove, os.Rename are framework-boundary calls for
// atomic file writes — no core equivalent exists for temp file creation.
// writeAtomic writes content to path by staging it in a temporary file and
// renaming it into place.
//

View file

@ -3,20 +3,15 @@
package brain
import (
"bytes"
"context"
"encoding/json"
"fmt"
goio "io"
"net/http"
"net/url"
"os"
"strings"
"time"
coremcp "dappco.re/go/mcp/pkg/mcp"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
coremcp "dappco.re/go/mcp/pkg/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -58,15 +53,16 @@ func (s *DirectSubsystem) OnChannel(fn func(ctx context.Context, channel string,
// Reads CORE_BRAIN_URL and CORE_BRAIN_KEY from environment, or falls back
// to ~/.claude/brain.key for the API key.
func NewDirect() *DirectSubsystem {
apiURL := os.Getenv("CORE_BRAIN_URL")
apiURL := core.Env("CORE_BRAIN_URL")
if apiURL == "" {
apiURL = "https://api.lthn.sh"
}
apiKey := os.Getenv("CORE_BRAIN_KEY")
apiKey := core.Env("CORE_BRAIN_KEY")
if apiKey == "" {
if data, err := coreio.Local.Read(os.ExpandEnv("$HOME/.claude/brain.key")); err == nil {
apiKey = strings.TrimSpace(data)
home := core.Env("HOME")
if data, err := coreio.Local.Read(core.Path(home, ".claude", "brain.key")); err == nil {
apiKey = core.Trim(data)
}
}
@ -112,16 +108,12 @@ func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body
return nil, coreerr.E("brain.apiCall", "no API key (set CORE_BRAIN_KEY or create ~/.claude/brain.key)", nil)
}
var reqBody goio.Reader
var bodyStr string
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, coreerr.E("brain.apiCall", "marshal request", err)
}
reqBody = bytes.NewReader(data)
bodyStr = core.JSONMarshalString(body)
}
req, err := http.NewRequestWithContext(ctx, method, s.apiURL+path, reqBody)
req, err := http.NewRequestWithContext(ctx, method, s.apiURL+path, core.NewReader(bodyStr))
if err != nil {
return nil, coreerr.E("brain.apiCall", "create request", err)
}
@ -135,18 +127,22 @@ func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body
}
defer resp.Body.Close()
respData, err := goio.ReadAll(resp.Body)
if err != nil {
return nil, coreerr.E("brain.apiCall", "read response", err)
r := core.ReadAll(resp.Body)
if !r.OK {
if readErr, ok := r.Value.(error); ok {
return nil, coreerr.E("brain.apiCall", "read response", readErr)
}
return nil, coreerr.E("brain.apiCall", "read response failed", nil)
}
respData := r.Value.(string)
if resp.StatusCode >= 400 {
return nil, coreerr.E("brain.apiCall", "API returned "+string(respData), nil)
return nil, coreerr.E("brain.apiCall", "API returned "+respData, nil)
}
var result map[string]any
if err := json.Unmarshal(respData, &result); err != nil {
return nil, coreerr.E("brain.apiCall", "parse response", err)
if ur := core.JSONUnmarshal([]byte(respData), &result); !ur.OK {
return nil, coreerr.E("brain.apiCall", "parse response", nil)
}
return result, nil
@ -200,30 +196,7 @@ func (s *DirectSubsystem) recall(ctx context.Context, _ *mcp.CallToolRequest, in
return nil, RecallOutput{}, err
}
var memories []Memory
if mems, ok := result["memories"].([]any); ok {
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"]),
}
if id, ok := mm["id"].(string); ok {
mem.ID = id
}
if score, ok := mm["score"].(float64); ok {
mem.Confidence = score
}
if source, ok := mm["source"].(string); ok {
mem.Tags = append(mem.Tags, "source:"+source)
}
memories = append(memories, mem)
}
}
}
memories := memoriesFromResult(result)
if s.onChannel != nil {
s.onChannel(ctx, coremcp.ChannelBrainRecallDone, map[string]any{
@ -274,37 +247,14 @@ func (s *DirectSubsystem) list(ctx context.Context, _ *mcp.CallToolRequest, inpu
if input.AgentID != "" {
values.Set("agent_id", input.AgentID)
}
values.Set("limit", fmt.Sprintf("%d", limit))
values.Set("limit", core.Sprintf("%d", limit))
result, err := s.apiCall(ctx, http.MethodGet, "/v1/brain/list?"+values.Encode(), nil)
if err != nil {
return nil, ListOutput{}, err
}
var memories []Memory
if mems, ok := result["memories"].([]any); ok {
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"]),
}
if id, ok := mm["id"].(string); ok {
mem.ID = id
}
if score, ok := mm["score"].(float64); ok {
mem.Confidence = score
}
if source, ok := mm["source"].(string); ok {
mem.Tags = append(mem.Tags, "source:"+source)
}
memories = append(memories, mem)
}
}
}
memories := memoriesFromResult(result)
if s.onChannel != nil {
s.onChannel(ctx, coremcp.ChannelBrainListDone, map[string]any{
@ -321,3 +271,49 @@ func (s *DirectSubsystem) list(ctx context.Context, _ *mcp.CallToolRequest, inpu
Memories: memories,
}, nil
}
// memoriesFromResult extracts Memory entries from an API response map.
func memoriesFromResult(result map[string]any) []Memory {
var memories []Memory
mems, ok := result["memories"].([]any)
if !ok {
return memories
}
for _, m := range mems {
mm, ok := m.(map[string]any)
if !ok {
continue
}
mem := Memory{
Content: stringFromMap(mm, "content"),
Type: stringFromMap(mm, "type"),
Project: stringFromMap(mm, "project"),
AgentID: stringFromMap(mm, "agent_id"),
CreatedAt: stringFromMap(mm, "created_at"),
}
if id, ok := mm["id"].(string); ok {
mem.ID = id
}
if score, ok := mm["score"].(float64); ok {
mem.Confidence = score
}
if source, ok := mm["source"].(string); ok {
mem.Tags = append(mem.Tags, "source:"+source)
}
memories = append(memories, mem)
}
return memories
}
// stringFromMap extracts a string value from a map, returning "" if missing or wrong type.
func stringFromMap(m map[string]any, key string) string {
v, ok := m[key]
if !ok || v == nil {
return ""
}
s, ok := v.(string)
if !ok {
return core.Sprintf("%v", v)
}
return s
}

View file

@ -5,13 +5,13 @@
package mcp
import (
"cmp"
"context"
"iter"
"net/http"
"os"
"path/filepath"
"slices"
"sort"
"sync"
core "dappco.re/go/core"
@ -542,8 +542,8 @@ func (s *Service) listDirectory(ctx context.Context, req *mcp.CallToolRequest, i
if err != nil {
return nil, ListDirectoryOutput{}, log.E("mcp.listDirectory", "failed to list directory", err)
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
slices.SortFunc(entries, func(a, b os.DirEntry) int {
return cmp.Compare(a.Name(), b.Name())
})
result := make([]DirectoryEntry, 0, len(entries))
for _, e := range entries {

View file

@ -7,13 +7,13 @@
package mcp
import (
"cmp"
"context"
"io"
"iter"
"os"
"reflect"
"slices"
"sort"
"sync"
"unsafe"
@ -362,8 +362,8 @@ func snapshotSessions(server *mcp.Server) []*mcp.ServerSession {
}
}
sort.Slice(sessions, func(i, j int) bool {
return sessions[i].ID() < sessions[j].ID()
slices.SortFunc(sessions, func(a, b *mcp.ServerSession) int {
return cmp.Compare(a.ID(), b.ID())
})
return sessions

View file

@ -4,7 +4,6 @@ package mcp
import (
"context"
"strings"
"time"
core "dappco.re/go/core"
@ -58,12 +57,13 @@ func isTestProcess(command string, args []string) bool {
switch base {
case "go":
return len(args) > 0 && strings.EqualFold(args[0], "test")
return len(args) > 0 && core.Lower(args[0]) == "test"
case "cargo":
return len(args) > 0 && strings.EqualFold(args[0], "test")
return len(args) > 0 && core.Lower(args[0]) == "test"
case "npm", "pnpm", "yarn", "bun":
for _, arg := range args {
if strings.EqualFold(arg, "test") || core.HasPrefix(core.Lower(arg), "test:") {
lower := core.Lower(arg)
if lower == "test" || core.HasPrefix(lower, "test:") {
return true
}
}

View file

@ -7,7 +7,6 @@ import (
"crypto/subtle"
"net"
"net/http"
"os"
"time"
core "dappco.re/go/core"
@ -37,7 +36,7 @@ func (s *Service) ServeHTTP(ctx context.Context, addr string) error {
addr = DefaultHTTPAddr
}
authToken := os.Getenv("MCP_AUTH_TOKEN")
authToken := core.Env("MCP_AUTH_TOKEN")
handler := mcp.NewStreamableHTTPHandler(
func(r *http.Request) *mcp.Server {