Skip to content

Commit 14ef572

Browse files
authored
fix(parser): bound Gemini reconciliation work (#1259)
Lost-event recovery rehydrates discovered Gemini sources in bounded pages. Gemini previously routed every candidate back through changed-path classification, rebuilding root-wide project metadata and repeating filesystem and Git resolution once per session. Large recoveries could therefore keep an otherwise idle daemon CPU-bound long after discovery completed. Teach the Gemini provider to reconstruct the exact discovered source from its spooled path and project hint. This preserves the project selected by authoritative discovery while keeping reconciliation work proportional to the candidate batch. A scaling regression covers both small and larger archives. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
1 parent 0d7e061 commit 14ef572

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)