From ab037cb813bcbaca9f9d8d9eeb317b9bec447a7d Mon Sep 17 00:00:00 2001 From: Alexander Bonin Date: Wed, 6 May 2026 01:43:38 +0300 Subject: [PATCH] Load workspace config when indexing tracked repos Load each tracked repo's .gortex.yaml before IndexAll builds its per-repo Indexer. This makes server --track honor repo-local excludes and other workspace config without treating the workspace config as global state.\n\nCo-Authored-By: Mastra Code (openai/gpt-5.5) --- internal/indexer/multi.go | 12 ++++++ internal/indexer/multi_test.go | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/internal/indexer/multi.go b/internal/indexer/multi.go index 704df14..2ad9489 100644 --- a/internal/indexer/multi.go +++ b/internal/indexer/multi.go @@ -163,6 +163,7 @@ func (mi *MultiIndexer) indexSingleRepo(entry config.RepoEntry) (map[string]*Ind } prefix := config.ResolvePrefix(entry) + mi.configMgr.LoadWorkspaceConfig(prefix, absPath) cfg := mi.configMgr.GetRepoConfig(prefix) idx := New(mi.graph, mi.registry, cfg.Index, mi.logger) @@ -170,6 +171,9 @@ func (mi *MultiIndexer) indexSingleRepo(entry config.RepoEntry) (map[string]*Ind if mi.embedder != nil { idx.SetEmbedder(mi.embedder) } + entryCopy := entry + idx.SetWorkspaceID(resolveWorkspaceID(&entryCopy, cfg, prefix)) + idx.SetProjectID(resolveProjectID(&entryCopy, cfg, prefix)) // No repo prefix in single-repo mode. result, err := idx.Index(absPath) @@ -249,6 +253,7 @@ func (mi *MultiIndexer) indexMultiRepo(repos []config.RepoEntry) (map[string]*In return } + mi.configMgr.LoadWorkspaceConfig(prefix, absPath) cfg := mi.configMgr.GetRepoConfig(prefix) idx := New(mi.graph, mi.registry, cfg.Index, mi.logger) idx.search = mi.search @@ -256,6 +261,9 @@ func (mi *MultiIndexer) indexMultiRepo(repos []config.RepoEntry) (map[string]*In idx.SetEmbedder(mi.embedder) } idx.SetRepoPrefix(prefix) + entryCopy := e + idx.SetWorkspaceID(resolveWorkspaceID(&entryCopy, cfg, prefix)) + idx.SetProjectID(resolveProjectID(&entryCopy, cfg, prefix)) idx.SetTrackedRepoModules(trackedModules) // Defer the cross-cutting passes (ResolveAll, InferImplements, // semantic enrich, contract extract+commit) so they don't race @@ -356,6 +364,7 @@ func (mi *MultiIndexer) IndexRepo(repoPrefix string) (*IndexResult, error) { mi.graph.EvictRepo(repoPrefix) } + mi.configMgr.LoadWorkspaceConfig(repoPrefix, meta.RootPath) cfg := mi.configMgr.GetRepoConfig(repoPrefix) idx := New(mi.graph, mi.registry, cfg.Index, mi.logger) idx.search = mi.search @@ -365,6 +374,9 @@ func (mi *MultiIndexer) IndexRepo(repoPrefix string) (*IndexResult, error) { if mi.IsMultiRepo() { idx.SetRepoPrefix(repoPrefix) } + entry := mi.configMgr.Global().FindRepoByPrefix(repoPrefix) + idx.SetWorkspaceID(resolveWorkspaceID(entry, cfg, repoPrefix)) + idx.SetProjectID(resolveProjectID(entry, cfg, repoPrefix)) result, err := idx.Index(meta.RootPath) if err != nil { diff --git a/internal/indexer/multi_test.go b/internal/indexer/multi_test.go index 5eebed5..3cc88ad 100644 --- a/internal/indexer/multi_test.go +++ b/internal/indexer/multi_test.go @@ -143,9 +143,76 @@ func TestMultiIndexer_IndexAll_MultiRepo(t *testing.T) { assert.Len(t, mi.AllMetadata(), 2) } +func TestMultiIndexer_IndexAll_SingleRepoLoadsWorkspaceExclude(t *testing.T) { + dir := setupRepoDir(t, "myrepo") + require.NoError(t, os.MkdirAll(filepath.Join(dir, "ignored"), 0o755)) + writeFile(t, filepath.Join(dir, "ignored", "ignored.go"), "package main\nfunc Ignored() {}\n") + writeFile(t, filepath.Join(dir, ".gortex.yaml"), "workspace: shared\nproject: app\nexclude:\n - ignored/**\n") + + tmpCfg := filepath.Join(t.TempDir(), "config.yaml") + gc := &config.GlobalConfig{Repos: []config.RepoEntry{{Path: dir, Name: "myrepo"}}} + gc.SetConfigPath(tmpCfg) + require.NoError(t, gc.Save()) + + cm, err := config.NewConfigManager(tmpCfg) + require.NoError(t, err) + + g := graph.New() + mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop()) + + _, err = mi.IndexAll() + require.NoError(t, err) + + for _, n := range g.AllNodes() { + assert.NotContains(t, n.FilePath, "ignored/ignored.go") + assert.NotContains(t, n.ID, "Ignored") + if n.Kind != graph.KindFile && n.Kind != graph.KindImport { + assert.Equal(t, "shared", n.WorkspaceID) + assert.Equal(t, "app", n.ProjectID) + } + } +} + +func TestMultiIndexer_IndexAll_MultiRepoLoadsWorkspaceExclude(t *testing.T) { + repoA := setupRepoDir(t, "repo-a") + repoB := setupRepoDir(t, "repo-b") + require.NoError(t, os.MkdirAll(filepath.Join(repoA, "ignored"), 0o755)) + writeFile(t, filepath.Join(repoA, "ignored", "ignored.go"), "package main\nfunc Ignored() {}\n") + writeFile(t, filepath.Join(repoA, ".gortex.yaml"), "workspace: shared\nproject: api\nexclude:\n - ignored/**\n") + + tmpCfg := filepath.Join(t.TempDir(), "config.yaml") + gc := &config.GlobalConfig{ + Repos: []config.RepoEntry{ + {Path: repoA, Name: "repo-a"}, + {Path: repoB, Name: "repo-b"}, + }, + } + gc.SetConfigPath(tmpCfg) + require.NoError(t, gc.Save()) + + cm, err := config.NewConfigManager(tmpCfg) + require.NoError(t, err) + + g := graph.New() + mi := NewMultiIndexer(g, newTestRegistry(), search.NewBM25(), cm, zap.NewNop()) + + _, err = mi.IndexAll() + require.NoError(t, err) + + for _, n := range g.AllNodes() { + assert.NotContains(t, n.FilePath, "ignored/ignored.go") + assert.NotContains(t, n.ID, "Ignored") + if n.RepoPrefix == "repo-a" && n.Kind != graph.KindFile && n.Kind != graph.KindImport { + assert.Equal(t, "shared", n.WorkspaceID) + assert.Equal(t, "api", n.ProjectID) + } + } +} + func TestMultiIndexer_IndexRepo(t *testing.T) { repoA := setupRepoDir(t, "repo-a") repoB := setupRepoDir(t, "repo-b") + writeFile(t, filepath.Join(repoA, ".gortex.yaml"), "workspace: shared\nproject: api\n") tmpCfg := filepath.Join(t.TempDir(), "config.yaml") gc := &config.GlobalConfig{ @@ -178,6 +245,12 @@ func TestMultiIndexer_IndexRepo(t *testing.T) { assert.Equal(t, repoBNodes, len(g.GetRepoNodes("repo-b"))) // Total should be roughly the same (re-indexed, not duplicated). assert.InDelta(t, nodesBefore, g.NodeCount(), 2) + for _, n := range g.GetRepoNodes("repo-a") { + if n.Kind != graph.KindFile && n.Kind != graph.KindImport { + assert.Equal(t, "shared", n.WorkspaceID) + assert.Equal(t, "api", n.ProjectID) + } + } } func TestMultiIndexer_IndexRepo_NotFound(t *testing.T) {