Skip to content

Commit 6c89efc

Browse files
committed
fix(parser): bound Gemini reconciliation work
Rehydrate exact Gemini sources from the project hint captured during streamed discovery. This avoids rebuilding root-wide project metadata for every candidate after lost filesystem events.
1 parent 0d7e061 commit 6c89efc

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

internal/parser/gemini_copilot_provider_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,55 @@ func TestGeminiProviderSourceMethods(t *testing.T) {
8787
require.Empty(t, fingerprint)
8888
}
8989

90+
func TestGeminiProviderReconciliationProjectMapWorkIsArchiveBounded(t *testing.T) {
91+
var projectMapBuilds int
92+
orig := buildGeminiProjectMap
93+
buildGeminiProjectMap = func(string) map[string]string {
94+
projectMapBuilds++
95+
return map[string]string{}
96+
}
97+
t.Cleanup(func() { buildGeminiProjectMap = orig })
98+
99+
for _, sourceCount := range []int{1, 64} {
100+
t.Run(fmt.Sprintf("sources_%d", sourceCount), func(t *testing.T) {
101+
root := t.TempDir()
102+
sourcePaths := make([]string, sourceCount)
103+
for i := range sourcePaths {
104+
sourcePaths[i] = filepath.Join(
105+
root,
106+
"tmp",
107+
fmt.Sprintf("project-hash-%03d", i),
108+
geminiChatsDir,
109+
fmt.Sprintf("session-2026-06-19T12-%02d-source.json", i),
110+
)
111+
writeSourceFile(t, sourcePaths[i], "{}")
112+
}
113+
114+
provider, ok := NewProvider(AgentGemini, ProviderConfig{
115+
Roots: []string{root},
116+
})
117+
require.True(t, ok)
118+
resolver, ok := provider.(ReconciliationSourceResolver)
119+
require.True(t, ok)
120+
121+
projectMapBuilds = 0
122+
for i, path := range sourcePaths {
123+
project := fmt.Sprintf("spooled_project_%d", i)
124+
source, found, err := resolver.SourceForReconciliation(
125+
t.Context(), path, project,
126+
)
127+
require.NoError(t, err)
128+
require.True(t, found)
129+
assert.Equal(t, path, source.DisplayPath)
130+
assert.Equal(t, path, source.FingerprintKey)
131+
assert.Equal(t, project, source.ProjectHint)
132+
}
133+
assert.Zero(t, projectMapBuilds,
134+
"exact reconciliation must not rebuild root-wide metadata per source")
135+
})
136+
}
137+
}
138+
90139
func TestGeminiProviderProjectMetadataChangesClassifyAndFingerprint(t *testing.T) {
91140
root := t.TempDir()
92141
sessionID := "gemini-project-metadata"

internal/parser/gemini_provider.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ func (p *geminiProvider) SourcesForChangedPath(
6666
return p.sources.SourcesForChangedPath(ctx, req)
6767
}
6868

69+
func (p *geminiProvider) SourceForReconciliation(
70+
ctx context.Context,
71+
path, project string,
72+
) (SourceRef, bool, error) {
73+
return p.sources.SourceForReconciliation(ctx, path, project)
74+
}
75+
6976
func (p *geminiProvider) FindSource(
7077
ctx context.Context,
7178
req FindSourceRequest,
@@ -498,6 +505,30 @@ func (s geminiSourceSet) sourceRef(root, path string) (SourceRef, bool) {
498505
return s.sourceRefForPath(root, path, true)
499506
}
500507

508+
// SourceForReconciliation rebuilds an exact source already admitted by
509+
// streaming discovery. The discovered project hint is authoritative here, so
510+
// rehydration must not rebuild root-wide Gemini project metadata per source.
511+
func (s geminiSourceSet) SourceForReconciliation(
512+
ctx context.Context, path, project string,
513+
) (SourceRef, bool, error) {
514+
if err := ctx.Err(); err != nil {
515+
return SourceRef{}, false, err
516+
}
517+
for _, root := range s.roots {
518+
source, ok := s.sourceRefForPathWithProjectMap(
519+
root, path, true, map[string]string{},
520+
)
521+
if !ok {
522+
continue
523+
}
524+
if project != "" {
525+
source.ProjectHint = project
526+
}
527+
return source, true, nil
528+
}
529+
return SourceRef{}, false, nil
530+
}
531+
501532
func (s geminiSourceSet) sourceRefForPath(
502533
root, path string,
503534
requireRegular bool,

0 commit comments

Comments
 (0)