diff --git a/pkg/agentic/brain_seed_memory.go b/pkg/agentic/brain_seed_memory.go index cc6f565..2935fc7 100644 --- a/pkg/agentic/brain_seed_memory.go +++ b/pkg/agentic/brain_seed_memory.go @@ -12,7 +12,7 @@ import ( const brainSeedMemoryDefaultAgent = "virgil" -const brainSeedMemoryDefaultPath = "~/.claude/projects" +const brainSeedMemoryDefaultPath = "~/.claude/projects/*/memory/" type BrainSeedMemoryInput struct { WorkspaceID int @@ -192,17 +192,35 @@ func brainSeedMemoryFiles(scanPath string) []string { if scanPath == "" { return nil } - if fs.IsFile(scanPath) { - if core.PathBase(scanPath) == "MEMORY.md" { - return []string{scanPath} - } - return nil - } var files []string + seen := map[string]struct{}{} + + add := func(path string) { + if path == "" { + return + } + if _, ok := seen[path]; ok { + return + } + seen[path] = struct{}{} + files = append(files, path) + } + var walk func(string) walk = func(dir string) { + if fs.IsFile(dir) { + if core.PathBase(dir) == "MEMORY.md" { + add(dir) + } + return + } + + if !fs.IsDir(dir) { + return + } + r := fs.List(dir) if !r.OK { return @@ -220,16 +238,26 @@ func brainSeedMemoryFiles(scanPath string) []string { continue } if core.PathBase(next) == "MEMORY.md" { - files = append(files, next) + add(next) } } } - walk(scanPath) + if brainSeedMemoryHasGlobMeta(scanPath) { + for _, path := range core.PathGlob(scanPath) { + walk(path) + } + } else { + walk(scanPath) + } sort.Strings(files) return files } +func brainSeedMemoryHasGlobMeta(path string) bool { + return core.Contains(path, "*") || core.Contains(path, "?") || core.Contains(path, "[") +} + func brainSeedMemorySections(content string) []brainSeedMemorySection { lines := core.Split(content, "\n") var sections []brainSeedMemorySection diff --git a/pkg/agentic/brain_seed_memory_test.go b/pkg/agentic/brain_seed_memory_test.go index c526adb..5b8f837 100644 --- a/pkg/agentic/brain_seed_memory_test.go +++ b/pkg/agentic/brain_seed_memory_test.go @@ -68,6 +68,52 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Good(t *testing.T) { assert.Equal(t, []any{"memory-import"}, bodies[1]["tags"]) } +func TestBrainSeedMemory_CmdBrainSeedMemory_Good_GlobPath(t *testing.T) { + home := t.TempDir() + t.Setenv("CORE_HOME", home) + + projectsDir := core.JoinPath(home, ".claude", "projects") + firstMemoryDir := core.JoinPath(projectsDir, "-Users-snider-Code-eaas", "memory") + secondMemoryDir := core.JoinPath(projectsDir, "-Users-snider-Code-agent", "memory") + require.True(t, fs.EnsureDir(firstMemoryDir).OK) + require.True(t, fs.EnsureDir(secondMemoryDir).OK) + require.True(t, fs.Write(core.JoinPath(firstMemoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().").OK) + require.True(t, fs.Write(core.JoinPath(secondMemoryDir, "MEMORY.md"), "# Memory\n\n## Decision\nPrefer named actions.").OK) + + var bodies []map[string]any + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/brain/remember", r.URL.Path) + bodyResult := core.ReadAll(r.Body) + require.True(t, bodyResult.OK) + var payload map[string]any + require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + bodies = append(bodies, payload) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"success":true,"memory":{"id":"mem-1"}}`)) + })) + defer srv.Close() + + subsystem := &PrepSubsystem{ + brainURL: srv.URL, + brainKey: "brain-key", + } + + result := subsystem.cmdBrainSeedMemory(core.NewOptions( + core.Option{Key: "workspace", Value: "42"}, + core.Option{Key: "path", Value: core.JoinPath(projectsDir, "*", "memory")}, + )) + + require.True(t, result.OK) + output, ok := result.Value.(BrainSeedMemoryOutput) + require.True(t, ok) + assert.Equal(t, 2, output.Files) + assert.Equal(t, 2, output.Imported) + assert.Equal(t, 0, output.Skipped) + assert.Equal(t, core.JoinPath(projectsDir, "*", "memory"), output.Path) + require.Len(t, bodies, 2) + assert.ElementsMatch(t, []any{"architecture", "decision"}, []any{bodies[0]["type"], bodies[1]["type"]}) +} + func TestBrainSeedMemory_CmdBrainIngest_Good(t *testing.T) { home := t.TempDir() t.Setenv("CORE_HOME", home)