diff --git a/go.mod b/go.mod index 07694de..ed716de 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module dappco.re/go/agent go 1.26.0 require ( - dappco.re/go/core v0.5.0 + dappco.re/go/core v0.6.0 dappco.re/go/core/api v0.2.0 dappco.re/go/core/process v0.3.0 dappco.re/go/core/ws v0.3.0 forge.lthn.ai/core/api v0.1.6 forge.lthn.ai/core/cli v0.3.7 - forge.lthn.ai/core/mcp v0.4.4 + forge.lthn.ai/core/mcp v0.4.8 github.com/gin-gonic/gin v1.12.0 github.com/gorilla/websocket v1.5.3 github.com/modelcontextprotocol/go-sdk v1.4.1 diff --git a/go.sum b/go.sum index d7c347c..337ec30 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,18 @@ dappco.re/go/core v0.5.0 h1:P5DJoaCiK5Q+af5UiTdWqUIW4W4qYKzpgGK50thm21U= dappco.re/go/core v0.5.0/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= +dappco.re/go/core v0.6.0 h1:0wmuO/UmCWXxJkxQ6XvVLnqkAuWitbd49PhxjCsplyk= +dappco.re/go/core v0.6.0/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= dappco.re/go/core/api v0.2.0 h1:5OcN9nawpp18Jp6dB1OwI2CBfs0Tacb0y0zqxFB6TJ0= dappco.re/go/core/api v0.2.0/go.mod h1:AtgNAx8lDY+qhVObFdNQOjSUQrHX1BeiDdMuA6RIfzo= +dappco.re/go/core/i18n v0.2.0/go.mod h1:9eSVJXr3OpIGWQvDynfhqcp27xnLMwlYLgsByU+p7ok= dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4= dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E= dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= dappco.re/go/core/process v0.3.0 h1:BPF9R79+8ZWe34qCIy/sZy+P4HwbaO95js2oPJL7IqM= dappco.re/go/core/process v0.3.0/go.mod h1:qwx8kt6x+J9gn7fu8lavuess72Ye9jPBODqDZQ9K0as= +dappco.re/go/core/scm v0.4.0/go.mod h1:ufb7si6HBkaT6zC8L67kLm8zzBaD1aQoTn4OsVAM1aI= +dappco.re/go/core/store v0.2.0/go.mod h1:QQGJiruayjna3nywbf0N2gcO502q/oEkPoSpBpSKbLM= dappco.re/go/core/ws v0.3.0 h1:ZxR8y5pfrWvnCHVN7qExXz7fdP5a063uNqyqE0Ab8pQ= dappco.re/go/core/ws v0.3.0/go.mod h1:aLyXrJnbCOGL0SW9rC1EHAAIS83w3djO374gHIz4Nic= forge.lthn.ai/core/api v0.1.5 h1:NwZrcOyBjaiz5/cn0n0tnlMUodi8Or6FHMx59C7Kv2o= @@ -42,6 +47,12 @@ forge.lthn.ai/core/mcp v0.4.0 h1:t4HMTI6CpoGB/VmE1aTklSEM8EI4Z/uKWyjGHxa1f4M= forge.lthn.ai/core/mcp v0.4.0/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE= forge.lthn.ai/core/mcp v0.4.4 h1:VTCOA1Dj/L7S8JCRg9BfYw7KfowW/Vvrp39bxc0dYyw= forge.lthn.ai/core/mcp v0.4.4/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE= +forge.lthn.ai/core/mcp v0.4.6 h1:jZY72sfPiCppKU4YyX7Gwy7ynbgVzUto+3S6oAj5Qs4= +forge.lthn.ai/core/mcp v0.4.6/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE= +forge.lthn.ai/core/mcp v0.4.7 h1:Iy/83laUpkaH8W2EoDlVMJbyv60xJ4aMgQe6sOcwL7k= +forge.lthn.ai/core/mcp v0.4.7/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE= +forge.lthn.ai/core/mcp v0.4.8 h1:nd1x3AL8AkUfl0kziltoJUX96Nx1BeFWEbgHmfrkKz8= +forge.lthn.ai/core/mcp v0.4.8/go.mod h1:eU35WT/8Mc0oJDVWdKaXEtNp27+Hc8KvnTKPf4DAqXE= github.com/99designs/gqlgen v0.17.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc= github.com/99designs/gqlgen v0.17.88/go.mod h1:qeqYFEgOeSKqWedOjogPizimp2iu4E23bdPvl4jTYic= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index 0fd2e47..5ff978d 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -4,8 +4,6 @@ package agentic import ( "context" - "os" - "path/filepath" "syscall" "time" @@ -101,8 +99,7 @@ func agentCommand(agent, prompt string) (string, []string, error) { } return "coderabbit", args, nil case "local": - home, _ := os.UserHomeDir() - script := core.JoinPath(home, "Code", "core", "agent", "scripts", "local-agent.sh") + script := core.JoinPath(core.Env("DIR_HOME"), "Code", "core", "agent", "scripts", "local-agent.sh") return "bash", []string{script, prompt}, nil default: return "", nil, core.E("agentCommand", "unknown agent: "+agent, nil) @@ -191,7 +188,7 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir, srcDir string) (int, st } // Emit completion event with actual status - emitCompletionEvent(agent, filepath.Base(wsDir), finalStatus) + emitCompletionEvent(agent, core.PathBase(wsDir), finalStatus) // Notify monitor immediately (push to connected clients) if s.onComplete != nil { diff --git a/pkg/agentic/ingest.go b/pkg/agentic/ingest.go index 27b8eb7..d033258 100644 --- a/pkg/agentic/ingest.go +++ b/pkg/agentic/ingest.go @@ -6,8 +6,6 @@ import ( "bytes" "encoding/json" "net/http" - "os" - "path/filepath" core "dappco.re/go/core" ) @@ -21,7 +19,7 @@ func (s *PrepSubsystem) ingestFindings(wsDir string) { } // Read the log file - logFiles, _ := filepath.Glob(core.JoinPath(wsDir, "agent-*.log")) + logFiles := core.PathGlob(core.JoinPath(wsDir, "agent-*.log")) if len(logFiles) == 0 { return } @@ -92,8 +90,7 @@ func (s *PrepSubsystem) createIssueViaAPI(repo, title, description, issueType, p } // Read the agent API key from file - home, _ := os.UserHomeDir() - r := fs.Read(core.JoinPath(home, ".claude", "agent-api.key")) + r := fs.Read(core.JoinPath(core.Env("DIR_HOME"), ".claude", "agent-api.key")) if !r.OK { return } diff --git a/pkg/agentic/mirror.go b/pkg/agentic/mirror.go index bb57d74..5723389 100644 --- a/pkg/agentic/mirror.go +++ b/pkg/agentic/mirror.go @@ -60,8 +60,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu basePath := s.codePath if basePath == "" { - home, _ := os.UserHomeDir() - basePath = core.JoinPath(home, "Code", "core") + basePath = core.JoinPath(core.Env("DIR_HOME"), "Code", "core") } else { basePath = core.JoinPath(basePath, "core") } diff --git a/pkg/agentic/paths.go b/pkg/agentic/paths.go index 89c5657..4277a79 100644 --- a/pkg/agentic/paths.go +++ b/pkg/agentic/paths.go @@ -3,7 +3,6 @@ package agentic import ( - "os" "os/exec" "strconv" "unsafe" @@ -45,11 +44,10 @@ func WorkspaceRoot() string { // // root := agentic.CoreRoot() func CoreRoot() string { - if root := os.Getenv("CORE_WORKSPACE"); root != "" { + if root := core.Env("CORE_WORKSPACE"); root != "" { return root } - home, _ := os.UserHomeDir() - return core.JoinPath(home, "Code", ".core") + return core.JoinPath(core.Env("DIR_HOME"), "Code", ".core") } // PlansRoot returns the root directory for agent plans. @@ -64,11 +62,10 @@ func PlansRoot() string { // // name := agentic.AgentName() // "cladius" on Snider's Mac, "charon" elsewhere func AgentName() string { - if name := os.Getenv("AGENT_NAME"); name != "" { + if name := core.Env("AGENT_NAME"); name != "" { return name } - hostname, _ := os.Hostname() - h := core.Lower(hostname) + h := core.Lower(core.Env("HOSTNAME")) if core.Contains(h, "snider") || core.Contains(h, "studio") || core.Contains(h, "mac") { return "cladius" } @@ -102,7 +99,7 @@ func DefaultBranch(repoDir string) string { // // org := agentic.GitHubOrg() // "dAppCore" func GitHubOrg() string { - if org := os.Getenv("GITHUB_ORG"); org != "" { + if org := core.Env("GITHUB_ORG"); org != "" { return org } return "dAppCore" diff --git a/pkg/agentic/plan.go b/pkg/agentic/plan.go index d2fdde8..31bf276 100644 --- a/pkg/agentic/plan.go +++ b/pkg/agentic/plan.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "encoding/json" "os" - "path/filepath" "strings" "time" @@ -341,7 +340,7 @@ func (s *PrepSubsystem) planList(_ context.Context, _ *mcp.CallToolRequest, inpu func planPath(dir, id string) string { // Sanitise ID to prevent path traversal - safe := filepath.Base(id) + safe := core.PathBase(id) if safe == "." || safe == ".." || safe == "" { safe = "invalid" } diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index c8dc8f8..9de94ad 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -10,9 +10,7 @@ import ( "encoding/json" goio "io" "net/http" - "os" "os/exec" - "path/filepath" "strings" "sync" "time" @@ -56,14 +54,14 @@ var _ coremcp.Subsystem = (*PrepSubsystem)(nil) // sub.SetCompletionNotifier(monitor) // sub.RegisterTools(server) func NewPrep() *PrepSubsystem { - home, _ := os.UserHomeDir() + home := core.Env("DIR_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 r := fs.Read(core.JoinPath(home, ".claude", "brain.key")); r.OK { brainKey = core.Trim(r.Value.(string)) @@ -89,7 +87,7 @@ func (s *PrepSubsystem) SetCompletionNotifier(n CompletionNotifier) { } func envOr(key, fallback string) string { - if v := os.Getenv(key); v != "" { + if v := core.Env(key); v != "" { return v } return fallback @@ -192,7 +190,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques out := PrepOutput{WorkspaceDir: wsDir} // Source repo path — sanitise to prevent path traversal - repoName := filepath.Base(input.Repo) // strips ../ and absolute paths + repoName := core.PathBase(input.Repo) // strips ../ and absolute paths if repoName == "." || repoName == ".." || repoName == "" { return nil, PrepOutput{}, core.E("prep", "invalid repo name: "+input.Repo, nil) } @@ -573,7 +571,7 @@ func (s *PrepSubsystem) findConsumers(repo, wsDir string) int { } modData := mr.Value.(string) if core.Contains(modData, modulePath) && !core.HasPrefix(modData, "module "+modulePath) { - consumers = append(consumers, filepath.Base(dir)) + consumers = append(consumers, core.PathBase(dir)) } } diff --git a/pkg/agentic/remote.go b/pkg/agentic/remote.go index b3ec52a..3dcb55d 100644 --- a/pkg/agentic/remote.go +++ b/pkg/agentic/remote.go @@ -6,7 +6,6 @@ import ( "context" "encoding/json" "net/http" - "os" "time" core "dappco.re/go/core" @@ -180,17 +179,17 @@ func resolveHost(host string) string { func remoteToken(host string) string { // Check environment first envKey := core.Sprintf("AGENT_TOKEN_%s", core.Upper(host)) - if token := os.Getenv(envKey); token != "" { + if token := core.Env(envKey); token != "" { return token } // Fallback to shared agent token - if token := os.Getenv("MCP_AUTH_TOKEN"); token != "" { + if token := core.Env("MCP_AUTH_TOKEN"); token != "" { return token } // Try reading from file - home, _ := os.UserHomeDir() + home := core.Env("DIR_HOME") tokenFiles := []string{ core.Sprintf("%s/.core/tokens/%s.token", home, core.Lower(host)), core.Sprintf("%s/.core/agent-token", home), diff --git a/pkg/agentic/review_queue.go b/pkg/agentic/review_queue.go index c834092..2f8d4c8 100644 --- a/pkg/agentic/review_queue.go +++ b/pkg/agentic/review_queue.go @@ -339,8 +339,7 @@ func (s *PrepSubsystem) buildReviewCommand(ctx context.Context, repoDir, reviewe // storeReviewOutput saves raw review output for training data collection. func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string) { - home, _ := os.UserHomeDir() - dataDir := core.JoinPath(home, ".core", "training", "reviews") + dataDir := core.JoinPath(core.Env("DIR_HOME"), ".core", "training", "reviews") fs.EnsureDir(dataDir) timestamp := time.Now().Format("2006-01-02T15-04-05") @@ -374,16 +373,14 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string // saveRateLimitState persists rate limit info for cross-run awareness. func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) { - home, _ := os.UserHomeDir() - path := core.JoinPath(home, ".core", "coderabbit-ratelimit.json") + path := core.JoinPath(core.Env("DIR_HOME"), ".core", "coderabbit-ratelimit.json") data, _ := json.Marshal(info) fs.Write(path, string(data)) } // loadRateLimitState reads persisted rate limit info. func (s *PrepSubsystem) loadRateLimitState() *RateLimitInfo { - home, _ := os.UserHomeDir() - path := core.JoinPath(home, ".core", "coderabbit-ratelimit.json") + path := core.JoinPath(core.Env("DIR_HOME"), ".core", "coderabbit-ratelimit.json") r := fs.Read(path) if !r.OK { return nil diff --git a/pkg/agentic/verify.go b/pkg/agentic/verify.go index 05890c2..c2ca5d5 100644 --- a/pkg/agentic/verify.go +++ b/pkg/agentic/verify.go @@ -9,7 +9,6 @@ import ( "net/http" "os" "os/exec" - "path/filepath" "time" core "dappco.re/go/core" @@ -130,7 +129,7 @@ func (s *PrepSubsystem) rebaseBranch(srcDir, branch string) bool { } // Force-push the rebased branch to Forge (origin is local clone) - st, _ := readStatus(filepath.Dir(srcDir)) + st, _ := readStatus(core.PathDir(srcDir)) org := "core" repo := "" if st != nil { diff --git a/pkg/agentic/watch.go b/pkg/agentic/watch.go index f1c9a01..ac10bff 100644 --- a/pkg/agentic/watch.go +++ b/pkg/agentic/watch.go @@ -4,7 +4,6 @@ package agentic import ( "context" - "path/filepath" "time" core "dappco.re/go/core" @@ -192,20 +191,17 @@ func (s *PrepSubsystem) watch(ctx context.Context, req *mcp.CallToolRequest, inp // findActiveWorkspaces returns workspace names that are running or queued. func (s *PrepSubsystem) findActiveWorkspaces() []string { wsRoot := WorkspaceRoot() - entries, err := filepath.Glob(core.JoinPath(wsRoot, "*/status.json")) - if err != nil { - return nil - } + entries := core.PathGlob(core.JoinPath(wsRoot, "*/status.json")) var active []string for _, entry := range entries { - wsDir := filepath.Dir(entry) + wsDir := core.PathDir(entry) st, err := readStatus(wsDir) if err != nil { continue } if st.Status == "running" || st.Status == "queued" { - active = append(active, filepath.Base(wsDir)) + active = append(active, core.PathBase(wsDir)) } } return active @@ -213,7 +209,7 @@ func (s *PrepSubsystem) findActiveWorkspaces() []string { // resolveWorkspaceDir converts a workspace name to full path. func (s *PrepSubsystem) resolveWorkspaceDir(name string) string { - if filepath.IsAbs(name) { + if core.PathIsAbs(name) { return name } return core.JoinPath(WorkspaceRoot(), name)