@@ -180,37 +180,35 @@ export class AISuggestionTracker {
180180 }
181181 case 'Secrets' : {
182182 const secretsItem = item as SecretsHoverData ;
183- const secretType = secretsItem . title || 'unknown' ;
184- const secretValue = secretsItem . secretValue || 'unknown' ;
185- return `secrets:${ secretType } :${ secretValue } :${ filePath } ` ;
183+ const startIndex = secretsItem . location ?. startIndex ?? 0 ;
184+ return `secrets:${ line } :${ startIndex } :${ filePath } ` ;
186185 }
187186 case 'Asca' : {
188187 const ascaItem = item as AscaHoverData ;
189188 const ruleId = ascaItem . ruleId || ascaItem . ruleName ;
190- return `asca:${ ruleId } :${ filePath } ` ;
189+ return `asca:${ ruleId } :${ line } : ${ filePath } ` ;
191190 }
192191 case 'Containers' : {
193192 const containersItem = item as ContainersHoverData ;
194193 return `containers:${ containersItem . imageName } :${ containersItem . imageTag } :${ filePath } ` ;
195194 }
196195 case 'IaC' : {
197196 const iacItem = item as IacHoverData ;
198- return `iac:${ iacItem . similarityId } :${ iacItem } ` ;
197+ return `iac:${ iacItem . similarityId } :${ filePath } ` ;
199198 }
200199 default :
201200 return `unknown:${ scannerType } :${ line } :${ filePath } ` ;
202201 }
203202 }
204203
205204 private getFilePath ( item : AnyHoverData ) : string {
206-
207- if ( isIacHoverData ( item ) && item . originalFilePath ) {
205+ if ( this . getScannerType ( item ) === 'IaC' && 'originalFilePath' in item && item . originalFilePath ) {
208206 return item . originalFilePath ;
209207 }
210208 if ( 'filePath' in item && item . filePath ) {
211209 return item . filePath ;
212210 }
213- return vscode . window . activeTextEditor ?. document . uri . fsPath || '' ;
211+
214212 }
215213
216214 private getLine ( item : AnyHoverData ) : number {
@@ -253,6 +251,11 @@ export class AISuggestionTracker {
253251
254252 this . logs . info ( `User requested AI fix for ${ scannerType } vulnerability` ) ;
255253
254+ this . pendingConfirmation . set ( vulnKey , {
255+ detectedAt : Date . now ( ) ,
256+ detectedValue : null
257+ } ) ;
258+
256259 // Check for duplicate request
257260 const existing = this . pendingFixes . get ( vulnKey ) ;
258261 if ( existing ) {
@@ -270,12 +273,7 @@ export class AISuggestionTracker {
270273 return existing . id ;
271274 }
272275
273- let validatorState : unknown ;
274- try {
275- validatorState = await this . validator . captureInitialState ( filePath ) ;
276- } catch ( error ) {
277- throw error ;
278- }
276+ const validatorState = await this . validator . captureInitialState ( filePath ) ;
279277
280278 const fix : PendingAIFix = {
281279 id : randomUUID ( ) ,
@@ -308,6 +306,8 @@ export class AISuggestionTracker {
308306 }
309307
310308 private async checkFixOutcome ( fix : PendingAIFix ) : Promise < void > {
309+
310+ await new Promise ( resolve => setTimeout ( resolve , 30000 ) ) ;
311311 const currentValue = await this . getCurrentValue ( fix ) ;
312312 const isFixed = currentValue === null ;
313313
@@ -329,66 +329,45 @@ export class AISuggestionTracker {
329329 // Check if ghost text disappeared
330330 const fileNotActive = ! activeEditor || activeEditor ?. document . uri . fsPath !== fix . filePath ;
331331 const vulnerabilityBack = vulnerabilityStatus !== null ;
332- const timeout = attempts >= 150 ;
332+ const timeout = attempts >= 300 ;
333333
334334 if ( fileNotActive || vulnerabilityBack || timeout ) {
335335 clearInterval ( checkInterval ) ;
336336 this . activeIntervals . delete ( intervalKey ) ;
337337
338- if ( timeout ) {
339- return ;
340- }
341-
342- if ( fix . scannerType === 'Secrets' ) {
343- if ( vulnerabilityBack && fileNotActive ) {
344- await this . finalizeFix ( fix , 'changes_rejected' ) ;
345- } if ( ! vulnerabilityBack && fileNotActive ) {
346- await this . finalizeFix ( fix , 'changes_accepted' ) ;
347- }
348- } else {
349- const fileChanged = await this . validator . validate ( fix . filePath , fix . validatorState ) ;
350- const vulnerabilityGone = await this . getCurrentValue ( fix ) === null ;
351- const changesAccepted = fileChanged && vulnerabilityGone ;
352- const outcome = changesAccepted ? 'changes_accepted' : 'changes_rejected' ;
353- await this . finalizeFix ( fix , outcome ) ;
354- }
338+ const reason = fileNotActive ? 'file closed' :
339+ vulnerabilityBack ? 'changes rejected' :
340+ 'timeout' ;
341+ this . logs . info ( `[AITracker] Ghost text DISAPPEARED (${ reason } )` ) ;
342+
343+ const changesAccepted = await this . validator . validate ( fix . filePath , fix . validatorState ) ;
344+ const outcome = changesAccepted ? 'changes_accepted' : 'changes_rejected' ;
345+ await this . finalizeFix ( fix , outcome ) ;
355346 }
356347 } , 2000 ) ;
357348
358349 this . activeIntervals . set ( intervalKey , checkInterval ) ;
359350 }
360351 }
361352
362-
363- private isFileOpen ( uri : vscode . Uri ) : boolean {
364- return vscode . window . visibleTextEditors . some ( e => e . document . uri . fsPath === uri . fsPath )
365- || vscode . window . activeTextEditor ?. document . uri . fsPath === uri . fsPath ;
366- }
367-
368-
369353 private async hasActiveInlineSuggestion ( uri : vscode . Uri , fix : PendingAIFix ) : Promise < boolean > {
370354 try {
371- if ( ! this . isFileOpen ( uri ) ) {
355+ const activeEditor = vscode . window . activeTextEditor ;
356+ if ( ! activeEditor ) {
372357 return false ;
373358 }
374359
375- const timeSinceRequest = Date . now ( ) - fix . requestedAt ;
376- const gracePeriodMs = 5 * 1000 ; // 5 seconds (adjust as needed)
377-
378- if ( timeSinceRequest < gracePeriodMs ) {
379- return true ;
380- }
381-
382360 const pendingConf = this . pendingConfirmation . get ( fix . vulnerabilityKey ) ;
383361 if ( pendingConf ) {
384362 const timeSinceDetection = Date . now ( ) - pendingConf . detectedAt ;
385- if ( timeSinceDetection < 2000 ) {
363+ if ( timeSinceDetection < 10000 ) {
386364 return true ;
387365 }
388366 }
389367
390368 return false ;
391369 } catch ( error ) {
370+ this . logs . warn ( `[AITracker] Error checking for active suggestions: ${ error } ` ) ;
392371 return false ;
393372 }
394373 }
@@ -403,16 +382,13 @@ export class AISuggestionTracker {
403382 }
404383 const diagnostics = vscode . languages . getDiagnostics ( uri ) ;
405384
406- let matchingDiagnosticCount = 0 ;
407-
408385 for ( const diagnostic of diagnostics ) {
409386 const data = ( diagnostic as vscode . Diagnostic & { data ?: CxDiagnosticData } ) . data ;
410387 if ( ! data ?. item ) {
411388 continue ;
412389 }
413390
414391 if ( this . diagnosticMatchesFix ( data , fix ) ) {
415- matchingDiagnosticCount ++ ;
416392 const currentValue = this . extractValueFromDiagnostic ( data , fix . scannerType ) ;
417393 return currentValue ;
418394 }
@@ -435,31 +411,24 @@ export class AISuggestionTracker {
435411
436412 const packageManager = keyParts [ 1 ] ;
437413 const packageName = keyParts [ 2 ] ;
438- const expectedVersion = keyParts [ 3 ] ;
439-
440- const matches = ossItem . packageManager === packageManager &&
441- ossItem . packageName === packageName &&
442- ossItem . version === expectedVersion ;
443-
414+ const matches = ossItem . packageName === packageName && ossItem . packageManager === packageManager ;
415+ if ( matches ) {
416+ this . logs . info ( `[AITracker] OSS Match: ${ ossItem . packageName } @${ ossItem . version } ` ) ;
417+ }
444418 return matches ;
445419 }
446420 case 'Secrets' : {
447421 if ( ! ( 'secretValue' in item ) ) {
448422 return false ;
449423 }
450424 const secretsItem = item as SecretsHoverData ;
451-
452- const expectedSecretType = keyParts [ 1 ] ;
453- const expectedLine = parseInt ( keyParts [ 2 ] ) ;
454- const expectedSecretValue = keyParts [ 3 ] ;
455-
456- const actualSecretType = secretsItem . title || 'unknown' ;
457- const actualLine = secretsItem . location ?. line ?? 0 ;
458- const actualSecretValue = secretsItem . secretValue || 'unknown' ;
459-
460- const matches = actualSecretType === expectedSecretType &&
461- actualLine === expectedLine &&
462- actualSecretValue === expectedSecretValue ;
425+ const line = parseInt ( keyParts [ 1 ] ) ;
426+ const startIndex = parseInt ( keyParts [ 2 ] ) ;
427+ const matches = secretsItem . location ?. line === line &&
428+ secretsItem . location ?. startIndex === startIndex ;
429+ if ( matches ) {
430+ this . logs . info ( `[AITracker] Secrets Match: line ${ line } , index ${ startIndex } ` ) ;
431+ }
463432 return matches ;
464433 }
465434 case 'Asca' : {
@@ -468,7 +437,12 @@ export class AISuggestionTracker {
468437 }
469438 const ascaItem = item as AscaHoverData ;
470439 const ruleId = keyParts [ 1 ] ;
471- const matches = String ( ascaItem . ruleId ) === ruleId || ascaItem . ruleName === ruleId ;
440+ const line = parseInt ( keyParts [ 2 ] ) ;
441+ const matches = ( String ( ascaItem . ruleId ) === ruleId || ascaItem . ruleName === ruleId ) &&
442+ ascaItem . location ?. line === line ;
443+ if ( matches ) {
444+ this . logs . info ( `[AITracker] ASCA Match: ${ ascaItem . ruleName } at line ${ line } ` ) ;
445+ }
472446 return matches ;
473447 }
474448 case 'Containers' : {
@@ -527,43 +501,60 @@ export class AISuggestionTracker {
527501 }
528502
529503 private async finalizeFix ( fix : PendingAIFix , status : FixOutcome [ 'status' ] ) : Promise < void > {
530- // Checking no active inline suggestions
531- const uri = vscode . Uri . file ( fix . filePath ) ;
532- const hasActiveSuggestion = await this . hasActiveInlineSuggestion ( uri , fix ) ;
504+ const eventName = this . getEventNameFromStatus ( status ) ;
505+ let telemetryData : FixOutcomeTelemetry | null = null ;
506+ try {
507+ // Checking no active inline suggestions
508+ const uri = vscode . Uri . file ( fix . filePath ) ;
509+ const hasActiveSuggestion = await this . hasActiveInlineSuggestion ( uri , fix ) ;
533510
534- if ( hasActiveSuggestion ) {
535- return ;
536- }
511+ if ( hasActiveSuggestion ) {
512+ return ;
513+ }
537514
538- const relativePath = this . getRelativePath ( fix . filePath ) ;
539- const itemName = this . getItemName ( fix ) ;
515+ const relativePath = this . getRelativePath ( fix . filePath ) ;
516+ const itemName = this . getItemName ( fix ) ;
540517
541- let finalState : unknown ;
542- let validatorMetadata : Record < string , unknown > ;
543- try {
544- finalState = await this . validator . captureFinalState ( fix . filePath ) ;
545- validatorMetadata = this . validator . getMetadata ( fix . validatorState , finalState ) ;
518+ let finalState : unknown ;
519+ let validatorMetadata : Record < string , unknown > ;
520+ try {
521+ finalState = await this . validator . captureFinalState ( fix . filePath ) ;
522+ validatorMetadata = this . validator . getMetadata ( fix . validatorState , finalState ) ;
523+ } catch ( error ) {
524+ return ;
525+ }
526+ telemetryData = {
527+ status,
528+ scannerType : fix . scannerType ,
529+ severity : fix . severity ,
530+ filePath : relativePath ,
531+ packageName : itemName ,
532+ duplicateRequests : fix . requestCount - 1 ,
533+ initialFileHash : ( validatorMetadata . initialFileHash as string ) || '' ,
534+ finalFileHash : ( validatorMetadata . finalFileHash as string ) || '' ,
535+ hashesMatch : ( validatorMetadata . hashesMatch as boolean ) || false
536+ } ;
546537 } catch ( error ) {
547- return ;
538+ this . logs . warn ( `[AITracker] Validation failed: ${ error } ` ) ;
539+ } finally {
540+ if ( ! telemetryData ) {
541+ // Create basic telemetry data as fallback
542+ telemetryData = {
543+ status,
544+ scannerType : fix . scannerType ,
545+ severity : fix . severity ,
546+ filePath : 'fallback' ,
547+ packageName : 'unknown' ,
548+ duplicateRequests : fix . requestCount - 1 ,
549+ initialFileHash : 'error' ,
550+ finalFileHash : 'error' ,
551+ hashesMatch : false
552+ } ;
553+ }
554+ await this . sendTelemetry ( eventName , telemetryData ) ;
555+ this . pendingFixes . delete ( fix . vulnerabilityKey ) ;
556+ this . logs . info ( `[AITracker] Deferred telemetry sent: ${ eventName } ` ) ;
548557 }
549- const eventName = this . getEventNameFromStatus ( status ) ;
550-
551- const telemetryData : FixOutcomeTelemetry = {
552- status,
553- scannerType : fix . scannerType ,
554- severity : fix . severity ,
555- filePath : relativePath ,
556- packageName : itemName ,
557- duplicateRequests : fix . requestCount - 1 ,
558- initialFileHash : ( validatorMetadata . initialFileHash as string ) || '' ,
559- finalFileHash : ( validatorMetadata . finalFileHash as string ) || '' ,
560- hashesMatch : ( validatorMetadata . hashesMatch as boolean ) || false
561- } ;
562-
563- await this . sendTelemetry ( eventName , telemetryData ) ;
564- this . pendingFixes . delete ( fix . vulnerabilityKey ) ;
565-
566- this . logs . info ( `User ${ status === 'changes_accepted' ? 'accepted' : 'rejected' } AI suggestion for ${ fix . scannerType } vulnerability` ) ;
567558 }
568559
569560 // Get relative path from absolute path for Iac scans
0 commit comments