diff --git a/cmd/core-agent/commands.go b/cmd/core-agent/commands.go index 6cd1f14..b7585f5 100644 --- a/cmd/core-agent/commands.go +++ b/cmd/core-agent/commands.go @@ -6,6 +6,7 @@ import ( "bytes" "flag" + "dappco.re/go/agent/pkg/agentic" "dappco.re/go/core" ) @@ -95,7 +96,7 @@ func (commands appCommandSet) version(_ core.Options) core.Result { core.Print(nil, "core-agent %s", commands.core.App().Version) core.Print(nil, " go: %s", core.Env("GO")) core.Print(nil, " os: %s/%s", core.Env("OS"), core.Env("ARCH")) - core.Print(nil, " home: %s", core.Env("DIR_HOME")) + core.Print(nil, " home: %s", agentic.HomeDir()) core.Print(nil, " hostname: %s", core.Env("HOSTNAME")) core.Print(nil, " pid: %s", core.Env("PID")) core.Print(nil, " channel: %s", updateChannel()) @@ -108,14 +109,14 @@ func (commands appCommandSet) check(_ core.Options) core.Result { core.Print(nil, "") core.Print(nil, " binary: core-agent") - agentsPath := core.Path("Code", ".core", "agents.yaml") + agentsPath := core.JoinPath(agentic.CoreRoot(), "agents.yaml") if fs.IsFile(agentsPath) { core.Print(nil, " agents: %s (ok)", agentsPath) } else { core.Print(nil, " agents: %s (MISSING)", agentsPath) } - wsRoot := core.Path("Code", ".core", "workspace") + wsRoot := agentic.WorkspaceRoot() if fs.IsDir(wsRoot) { entries := core.PathGlob(core.JoinPath(wsRoot, "*")) core.Print(nil, " workspace: %s (%d entries)", wsRoot, len(entries)) diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index ebadfb0..7c3dac7 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -184,7 +184,7 @@ func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result { task = "Review and report findings" } - repoPath := core.JoinPath(core.Env("DIR_HOME"), "Code", org, repo) + repoPath := core.JoinPath(HomeDir(), "Code", org, repo) input := PrepInput{ Repo: repo, @@ -209,7 +209,7 @@ func (s *PrepSubsystem) cmdExtract(opts core.Options) core.Result { } target := opts.String("target") if target == "" { - target = core.Path("Code", ".core", "workspace", "test-extract") + target = core.JoinPath(WorkspaceRoot(), "test-extract") } data := &lib.WorkspaceData{ diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index b27a8b5..0a16405 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -172,7 +172,7 @@ func containerCommand(agentType, command string, args []string, repoDir, metaDir image = defaultDockerImage } - home := core.Env("DIR_HOME") + home := HomeDir() dockerArgs := []string{ "run", "--rm", diff --git a/pkg/agentic/ingest.go b/pkg/agentic/ingest.go index 513fd86..907bf4e 100644 --- a/pkg/agentic/ingest.go +++ b/pkg/agentic/ingest.go @@ -9,10 +9,7 @@ import ( ) func agentHomeDir() string { - if home := core.Env("HOME"); home != "" { - return home - } - return core.Env("DIR_HOME") + return HomeDir() } // ingestFindings reads the agent output log and creates issues via the API diff --git a/pkg/agentic/mirror.go b/pkg/agentic/mirror.go index 17fccaa..1a1673a 100644 --- a/pkg/agentic/mirror.go +++ b/pkg/agentic/mirror.go @@ -58,7 +58,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu basePath := s.codePath if basePath == "" { - basePath = core.JoinPath(core.Env("DIR_HOME"), "Code", "core") + basePath = core.JoinPath(HomeDir(), "Code", "core") } else { basePath = core.JoinPath(basePath, "core") } diff --git a/pkg/agentic/paths.go b/pkg/agentic/paths.go index f463677..6576baf 100644 --- a/pkg/agentic/paths.go +++ b/pkg/agentic/paths.go @@ -22,7 +22,7 @@ var fs = (&core.Fs{}).NewUnrestricted() func LocalFs() *core.Fs { return fs } // WorkspaceRoot returns the root directory for agent workspaces. -// Checks CORE_WORKSPACE env var first, falls back to ~/Code/.core/workspace. +// Checks CORE_WORKSPACE env var first, falls back to HomeDir()/Code/.core/workspace. // // wsDir := core.JoinPath(agentic.WorkspaceRoot(), "core", "go-io", "task-42") func WorkspaceRoot() string { @@ -58,14 +58,27 @@ func WorkspaceName(wsDir string) string { } // CoreRoot returns the root directory for core ecosystem files. -// Checks CORE_WORKSPACE env var first, falls back to ~/Code/.core. +// Checks CORE_WORKSPACE env var first, falls back to HomeDir()/Code/.core. // // root := agentic.CoreRoot() func CoreRoot() string { if root := core.Env("CORE_WORKSPACE"); root != "" { return root } - return core.JoinPath(core.Env("DIR_HOME"), "Code", ".core") + return core.JoinPath(HomeDir(), "Code", ".core") +} + +// HomeDir returns the user home directory used by agentic path helpers. +// +// home := agentic.HomeDir() +func HomeDir() string { + if home := core.Env("CORE_HOME"); home != "" { + return home + } + if home := core.Env("HOME"); home != "" { + return home + } + return core.Env("DIR_HOME") } func workspaceStatusPaths(wsRoot string) []string { diff --git a/pkg/agentic/paths_example_test.go b/pkg/agentic/paths_example_test.go index f62a6f4..4abbf9d 100644 --- a/pkg/agentic/paths_example_test.go +++ b/pkg/agentic/paths_example_test.go @@ -18,6 +18,12 @@ func ExampleCoreRoot() { // Output: true } +func ExampleHomeDir() { + home := HomeDir() + core.Println(home != "") + // Output: true +} + func ExamplePlansRoot() { root := PlansRoot() core.Println(core.HasSuffix(root, "plans")) diff --git a/pkg/agentic/paths_test.go b/pkg/agentic/paths_test.go index c149e3f..9234ae2 100644 --- a/pkg/agentic/paths_test.go +++ b/pkg/agentic/paths_test.go @@ -19,10 +19,30 @@ func TestPaths_CoreRoot_Good_EnvVar(t *testing.T) { func TestPaths_CoreRoot_Good_Fallback(t *testing.T) { t.Setenv("CORE_WORKSPACE", "") - home := core.Env("DIR_HOME") + home := HomeDir() assert.Equal(t, home+"/Code/.core", CoreRoot()) } +func TestPaths_CoreRoot_Good_CoreHome(t *testing.T) { + t.Setenv("CORE_WORKSPACE", "") + t.Setenv("CORE_HOME", "/tmp/core-home") + assert.Equal(t, "/tmp/core-home/Code/.core", CoreRoot()) +} + +func TestPaths_HomeDir_Good_CoreHome(t *testing.T) { + t.Setenv("CORE_HOME", "/tmp/core-home") + t.Setenv("HOME", "/tmp/home") + t.Setenv("DIR_HOME", "/tmp/dir-home") + assert.Equal(t, "/tmp/core-home", HomeDir()) +} + +func TestPaths_HomeDir_Good_HomeFallback(t *testing.T) { + t.Setenv("CORE_HOME", "") + t.Setenv("HOME", "/tmp/home") + t.Setenv("DIR_HOME", "/tmp/dir-home") + assert.Equal(t, "/tmp/home", HomeDir()) +} + func TestPaths_WorkspaceRoot_Good(t *testing.T) { t.Setenv("CORE_WORKSPACE", "/tmp/test-core") assert.Equal(t, "/tmp/test-core/workspace", WorkspaceRoot()) @@ -131,7 +151,7 @@ func TestPaths_LocalFs_Ugly_EmptyPath(t *testing.T) { func TestPaths_WorkspaceRoot_Bad_EmptyEnv(t *testing.T) { t.Setenv("CORE_WORKSPACE", "") - home := core.Env("DIR_HOME") + home := HomeDir() // Should fall back to ~/Code/.core/workspace assert.Equal(t, home+"/Code/.core/workspace", WorkspaceRoot()) } @@ -196,7 +216,7 @@ func TestPaths_CoreRoot_Ugly_UnicodeEnv(t *testing.T) { func TestPaths_PlansRoot_Bad_EmptyEnv(t *testing.T) { t.Setenv("CORE_WORKSPACE", "") - home := core.Env("DIR_HOME") + home := HomeDir() assert.Equal(t, home+"/Code/.core/plans", PlansRoot()) } diff --git a/pkg/agentic/prep.go b/pkg/agentic/prep.go index 7f99433..8fae4b7 100644 --- a/pkg/agentic/prep.go +++ b/pkg/agentic/prep.go @@ -52,7 +52,7 @@ var _ coremcp.Subsystem = (*PrepSubsystem)(nil) // sub := agentic.NewPrep() // sub.SetCompletionNotifier(monitor) func NewPrep() *PrepSubsystem { - home := core.Env("DIR_HOME") + home := HomeDir() forgeToken := core.Env("FORGE_TOKEN") if forgeToken == "" { diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go index 4942fe5..142f40e 100644 --- a/pkg/agentic/prep_test.go +++ b/pkg/agentic/prep_test.go @@ -178,6 +178,23 @@ func TestPrep_NewPrep_Good_EnvOverrides(t *testing.T) { assert.Equal(t, "/custom/code", s.codePath) } +func TestPrep_NewPrep_Good_CoreHomeOverride(t *testing.T) { + tmpHome := t.TempDir() + claudeDir := core.JoinPath(tmpHome, ".claude") + require.True(t, fs.EnsureDir(claudeDir).OK) + require.True(t, fs.Write(core.JoinPath(claudeDir, "brain.key"), "core-home-key").OK) + + t.Setenv("CORE_HOME", tmpHome) + t.Setenv("HOME", "/ignored-home") + t.Setenv("DIR_HOME", "/ignored-dir") + t.Setenv("CORE_BRAIN_KEY", "") + t.Setenv("CODE_PATH", "") + + s := NewPrep() + assert.Equal(t, core.JoinPath(tmpHome, "Code"), s.codePath) + assert.Equal(t, "core-home-key", s.brainKey) +} + func TestPrep_NewPrep_Good_GiteaTokenFallback(t *testing.T) { t.Setenv("FORGE_TOKEN", "") t.Setenv("GITEA_TOKEN", "gitea-fallback-token") diff --git a/pkg/agentic/remote.go b/pkg/agentic/remote.go index e002674..5d634d9 100644 --- a/pkg/agentic/remote.go +++ b/pkg/agentic/remote.go @@ -207,7 +207,7 @@ func remoteToken(host string) string { } // Try reading from file - home := core.Env("DIR_HOME") + home := HomeDir() 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 41d2dd3..dee21ef 100644 --- a/pkg/agentic/review_queue.go +++ b/pkg/agentic/review_queue.go @@ -76,7 +76,11 @@ func (s *PrepSubsystem) reviewQueue(ctx context.Context, _ *mcp.CallToolRequest, limit = 4 } - basePath := core.JoinPath(s.codePath, "core") + basePath := s.codePath + if basePath == "" { + basePath = core.JoinPath(HomeDir(), "Code") + } + basePath = core.JoinPath(basePath, "core") // Find repos with draft PRs (ahead of GitHub) candidates := s.findReviewCandidates(basePath) @@ -335,7 +339,7 @@ func (s *PrepSubsystem) buildReviewCommand(repoDir, reviewer string) (string, [] // storeReviewOutput saves raw review output for training data collection. func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string) { - dataDir := core.JoinPath(core.Env("DIR_HOME"), ".core", "training", "reviews") + dataDir := core.JoinPath(HomeDir(), ".core", "training", "reviews") fs.EnsureDir(dataDir) timestamp := time.Now().Format("2006-01-02T15-04-05") @@ -367,13 +371,13 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string // saveRateLimitState persists rate limit info for cross-run awareness. func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) { - path := core.JoinPath(core.Env("DIR_HOME"), ".core", "coderabbit-ratelimit.json") + path := core.JoinPath(HomeDir(), ".core", "coderabbit-ratelimit.json") fs.WriteAtomic(path, core.JSONMarshalString(info)) } // loadRateLimitState reads persisted rate limit info. func (s *PrepSubsystem) loadRateLimitState() *RateLimitInfo { - path := core.JoinPath(core.Env("DIR_HOME"), ".core", "coderabbit-ratelimit.json") + path := core.JoinPath(HomeDir(), ".core", "coderabbit-ratelimit.json") r := fs.Read(path) if !r.OK { return nil