@@ -55,6 +55,85 @@ func TestSourceMtimeWindsurfUsesProviderFingerprint(t *testing.T) {
5555 assert .Greater (t , after , before )
5656}
5757
58+ func TestProcessFileWindsurfSameMtimeHashChangeReparses (t * testing.T ) {
59+ for _ , tt := range []struct {
60+ name string
61+ seedCache bool
62+ freshSync bool
63+ }{
64+ {name : "skip cache" , seedCache : true },
65+ {name : "db freshness" , freshSync : true },
66+ } {
67+ t .Run (tt .name , func (t * testing.T ) {
68+ root := filepath .Join (t .TempDir (), "Windsurf" , "User" )
69+ workspaceDir := filepath .Join (root , "workspaceStorage" , "workspace-hash" )
70+ manifestPath := filepath .Join (workspaceDir , "workspace.json" )
71+ dbPath := filepath .Join (workspaceDir , "state.vscdb" )
72+ require .NoError (t , os .MkdirAll (workspaceDir , 0o755 ))
73+ require .NoError (t , os .WriteFile (manifestPath , []byte (`{"folder":"file:///work/demo"}` ), 0o644 ))
74+ writeSyncWindsurfStateDB (t , dbPath , windsurfSyncPayload ("hash-session" , "Alpha reply" ))
75+ virtualPath := dbPath + "#hash-session"
76+ database := dbtest .OpenTestDB (t )
77+ engine := NewEngine (database , EngineConfig {
78+ AgentDirs : map [parser.AgentType ][]string {
79+ parser .AgentWindsurf : {root },
80+ },
81+ Machine : "devbox" ,
82+ })
83+ defer engine .Close ()
84+
85+ first := engine .processFile (context .Background (), parser.DiscoveredFile {
86+ Path : virtualPath ,
87+ Agent : parser .AgentWindsurf ,
88+ })
89+ require .NoError (t , first .err )
90+ require .Len (t , first .results , 1 )
91+ require .Len (t , first .results [0 ].Messages , 2 )
92+ assert .Equal (t , "Alpha reply" , first .results [0 ].Messages [1 ].Content )
93+ initialMtime := first .results [0 ].Session .File .Mtime
94+ initialHash := first .results [0 ].Session .File .Hash
95+ require .NotZero (t , initialMtime )
96+ require .NotEmpty (t , initialHash )
97+ writeSyncWindsurfResult (t , engine , first )
98+
99+ infoBefore , err := os .Stat (dbPath )
100+ require .NoError (t , err )
101+ updateSyncWindsurfStateDB (t , dbPath , windsurfSyncPayload ("hash-session" , "Bravo reply" ))
102+ initialTime := time .Unix (0 , initialMtime )
103+ require .NoError (t , os .Chtimes (dbPath , initialTime , initialTime ))
104+ infoAfter , err := os .Stat (dbPath )
105+ require .NoError (t , err )
106+ require .Equal (t , infoBefore .Size (), infoAfter .Size (),
107+ "test must keep size stable so hash is the only freshness signal" )
108+
109+ if tt .seedCache {
110+ engine .cacheSkip (first .cacheKey , initialMtime )
111+ }
112+ if tt .freshSync {
113+ engine .Close ()
114+ engine = NewEngine (database , EngineConfig {
115+ AgentDirs : map [parser.AgentType ][]string {
116+ parser .AgentWindsurf : {root },
117+ },
118+ Machine : "devbox" ,
119+ })
120+ defer engine .Close ()
121+ }
122+
123+ second := engine .processFile (context .Background (), parser.DiscoveredFile {
124+ Path : virtualPath ,
125+ Agent : parser .AgentWindsurf ,
126+ })
127+ require .NoError (t , second .err )
128+ assert .False (t , second .skip )
129+ require .Len (t , second .results , 1 )
130+ require .Len (t , second .results [0 ].Messages , 2 )
131+ assert .Equal (t , "Bravo reply" , second .results [0 ].Messages [1 ].Content )
132+ assert .NotEqual (t , initialHash , second .results [0 ].Session .File .Hash )
133+ })
134+ }
135+ }
136+
58137func writeSyncWindsurfStateDB (t * testing.T , dbPath , payload string ) {
59138 t .Helper ()
60139 conn , err := sql .Open ("sqlite3" , dbPath )
@@ -69,3 +148,46 @@ func writeSyncWindsurfStateDB(t *testing.T, dbPath, payload string) {
69148 )
70149 require .NoError (t , err )
71150}
151+
152+ func updateSyncWindsurfStateDB (t * testing.T , dbPath , payload string ) {
153+ t .Helper ()
154+ conn , err := sql .Open ("sqlite3" , dbPath )
155+ require .NoError (t , err )
156+ defer conn .Close ()
157+ _ , err = conn .Exec (
158+ `UPDATE ItemTable SET value = ? WHERE key = ?` ,
159+ payload ,
160+ "workbench.panel.aichat.view.aichat.chatdata" ,
161+ )
162+ require .NoError (t , err )
163+ }
164+
165+ func writeSyncWindsurfResult (t * testing.T , engine * Engine , result processResult ) {
166+ t .Helper ()
167+ require .Len (t , result .results , 1 )
168+ written , _ , failed := engine .writeBatch (
169+ []pendingWrite {{
170+ sess : result .results [0 ].Session ,
171+ msgs : result .results [0 ].Messages ,
172+ usageEvents : result .results [0 ].UsageEvents ,
173+ forceReplace : result .forceReplace ,
174+ }},
175+ syncWriteDefault ,
176+ false ,
177+ )
178+ require .Equal (t , 0 , failed )
179+ require .Equal (t , 1 , written )
180+ }
181+
182+ func windsurfSyncPayload (sessionID , assistant string ) string {
183+ return `{
184+ "version": 1,
185+ "sessionId": "` + sessionID + `",
186+ "requests": [{
187+ "requestId": "request-1",
188+ "message": {"text": "Question"},
189+ "response": [{"value": "` + assistant + `"}],
190+ "timestamp": 1710000000000
191+ }]
192+ }`
193+ }
0 commit comments