Skip to content

Commit 64a3cd1

Browse files
fix(parser): include vscode workspace metadata freshness
VS Code Copilot project names come from workspace.json, so classifying manifest writes is not enough if the source fingerprint still only reflects the chat transcript. An unchanged chat file could skip the parse that refreshes Session.Project. Fold workspace.json size, mtime, and content hash into workspace chat fingerprints while leaving global chat fingerprints unchanged, and cover metadata-only freshness in the provider tests.
1 parent 8231cb9 commit 64a3cd1

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

internal/parser/copilot_ide_provider_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ func TestVSCodeCopilotProviderClassifiesDeletedAndMetadataPaths(t *testing.T) {
136136
[]string{jsonlPath, jsonPath},
137137
sourceDisplayPaths(metadataChanged),
138138
)
139+
require.Len(t, metadataChanged, 2)
140+
beforeMetadata, err := provider.Fingerprint(context.Background(), metadataChanged[0])
141+
require.NoError(t, err)
142+
writeSourceFile(t, workspacePath,
143+
`{"folder":"file:///Users/alice/code/copilot-renamed-app"}`)
144+
afterMetadata, err := provider.Fingerprint(context.Background(), metadataChanged[0])
145+
require.NoError(t, err)
146+
assert.NotEqual(t, beforeMetadata.Hash, afterMetadata.Hash)
139147

140148
require.NoError(t, os.Remove(jsonlPath))
141149
deletedJSONL, err := provider.SourcesForChangedPath(

internal/parser/vscode_copilot_provider.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package parser
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"fmt"
67
"os"
78
"path/filepath"
@@ -238,16 +239,25 @@ func (s vscodeCopilotSourceSet) Fingerprint(
238239
if info.IsDir() {
239240
return SourceFingerprint{}, fmt.Errorf("stat %s: source is a directory", path)
240241
}
241-
hash, err := hashJSONLSourceFile(path)
242-
if err != nil {
243-
return SourceFingerprint{}, err
244-
}
245-
return SourceFingerprint{
242+
fingerprint := SourceFingerprint{
246243
Key: firstNonEmptyJSONLString(source.FingerprintKey, source.Key, path),
247244
Size: info.Size(),
248245
MTimeNS: info.ModTime().UnixNano(),
249-
Hash: hash,
250-
}, nil
246+
}
247+
workspacePath := s.workspaceManifestForSource(path)
248+
if workspacePath != "" {
249+
if workspaceInfo, err := os.Stat(workspacePath); err == nil {
250+
fingerprint.Size += workspaceInfo.Size()
251+
if mtime := workspaceInfo.ModTime().UnixNano(); mtime > fingerprint.MTimeNS {
252+
fingerprint.MTimeNS = mtime
253+
}
254+
}
255+
}
256+
fingerprint.Hash, err = vscodeCopilotSourceHash(path, workspacePath)
257+
if err != nil {
258+
return SourceFingerprint{}, err
259+
}
260+
return fingerprint, nil
251261
}
252262

253263
func (s vscodeCopilotSourceSet) pathFromSource(source SourceRef) (string, string, bool) {
@@ -397,6 +407,32 @@ func (s vscodeCopilotSourceSet) sourcesForWorkspaceManifest(
397407
return sources
398408
}
399409

410+
func (s vscodeCopilotSourceSet) workspaceManifestForSource(path string) string {
411+
for _, root := range s.roots {
412+
root = filepath.Clean(root)
413+
rel, ok := relUnder(root, path)
414+
if !ok {
415+
continue
416+
}
417+
parts := strings.Split(filepath.ToSlash(rel), "/")
418+
if len(parts) == 4 &&
419+
parts[0] == "workspaceStorage" &&
420+
parts[2] == "chatSessions" &&
421+
isVSCodeCopilotSessionPath(parts[3]) {
422+
workspacePath := filepath.Join(
423+
root,
424+
"workspaceStorage",
425+
parts[1],
426+
"workspace.json",
427+
)
428+
if IsRegularFile(workspacePath) {
429+
return workspacePath
430+
}
431+
}
432+
}
433+
return ""
434+
}
435+
400436
func (s vscodeCopilotSourceSet) newSourceRef(root, path, project string) SourceRef {
401437
return SourceRef{
402438
Provider: AgentVSCodeCopilot,
@@ -435,6 +471,23 @@ func vscodeCopilotPreferredExistingPath(path string) string {
435471
return ""
436472
}
437473

474+
func vscodeCopilotSourceHash(path, workspacePath string) (string, error) {
475+
hash, err := hashJSONLSourceFile(path)
476+
if err != nil {
477+
return "", err
478+
}
479+
if workspacePath == "" {
480+
return hash, nil
481+
}
482+
workspaceHash, err := hashJSONLSourceFile(workspacePath)
483+
if err != nil {
484+
return "", err
485+
}
486+
h := sha256.New()
487+
_, _ = h.Write([]byte("chat\x00" + hash + "\x00workspace\x00" + workspaceHash))
488+
return fmt.Sprintf("%x", h.Sum(nil)), nil
489+
}
490+
438491
func vscodeCopilotProviderCapabilities() Capabilities {
439492
return Capabilities{
440493
Source: SourceCapabilities{

0 commit comments

Comments
 (0)