@@ -14,6 +14,7 @@ import { FormatterService } from '../services/formatter';
1414import { TokenTracker } from './token-tracker' ;
1515import { loadRepoConfig } from './config' ;
1616import { getWebhookDelivery } from '@server/db/webhook-deliveries' ;
17+ import { sendTelemetryEvent } from './telemetry' ;
1718
1819type PersistedReviewJob = ReturnType < typeof mapJob > ;
1920
@@ -694,6 +695,31 @@ async function runFinalizePhase(
694695
695696 if ( fileSummaries . length > 0 && fileSummaries . every ( ( file ) => file . verdict === 'failed' ) ) {
696697 await updateJobStep ( env , job . id , 'Generating Summary' , { status : 'failed' , error : 'All files failed to review' } ) ;
698+
699+ // Send anonymous telemetry for the failed job
700+ const fileInputTokens = reviews . reduce ( ( sum , review ) => sum + ( review . input_tokens ?? 0 ) , 0 ) ;
701+ const fileOutputTokens = reviews . reduce ( ( sum , review ) => sum + ( review . output_tokens ?? 0 ) , 0 ) ;
702+ const modelsUsed = Array . from ( new Set ( reviews . map ( r => r . model_used ) . filter ( Boolean ) ) ) ;
703+ const fileExtensions = Array . from ( new Set ( files . map ( f => {
704+ const parts = f . path . split ( '.' ) ;
705+ return parts . length > 1 ? parts . pop ( ) || '' : '' ;
706+ } ) . filter ( Boolean ) ) ) ;
707+ const reviewDurationMs = Math . max ( 0 , Date . now ( ) - new Date ( job . createdAt ) . getTime ( ) ) ;
708+
709+ await sendTelemetryEvent ( env , {
710+ linesReviewed : files . reduce ( ( sum , file ) => sum + file . lineCount , 0 ) ,
711+ findingsReported : 0 ,
712+ inputTokens : fileInputTokens ,
713+ outputTokens : fileOutputTokens ,
714+ modelsUsed,
715+ fileExtensions,
716+ triggerType : job . trigger ,
717+ reviewDurationMs,
718+ filesReviewed : files . length ,
719+ verdict : 'failed' ,
720+ severityDistribution : { } ,
721+ } ) ;
722+
697723 throw new Error ( 'All files failed to review' ) ;
698724 }
699725
@@ -762,6 +788,20 @@ async function runFinalizePhase(
762788
763789 const fileInputTokens = reviews . reduce ( ( sum , review ) => sum + ( review . input_tokens ?? 0 ) , 0 ) ;
764790 const fileOutputTokens = reviews . reduce ( ( sum , review ) => sum + ( review . output_tokens ?? 0 ) , 0 ) ;
791+
792+ const modelsUsed = Array . from ( new Set ( reviews . map ( r => r . model_used ) . filter ( Boolean ) ) ) ;
793+ const fileExtensions = Array . from ( new Set ( files . map ( f => {
794+ const parts = f . path . split ( '.' ) ;
795+ return parts . length > 1 ? parts . pop ( ) || '' : '' ;
796+ } ) . filter ( Boolean ) ) ) ;
797+ const reviewDurationMs = Math . max ( 0 , Date . now ( ) - new Date ( job . createdAt ) . getTime ( ) ) ;
798+
799+ const severityDistribution : Record < string , number > = { } ;
800+ for ( const comment of finalComments ) {
801+ const sev = comment . severity || 'unknown' ;
802+ severityDistribution [ sev ] = ( severityDistribution [ sev ] || 0 ) + 1 ;
803+ }
804+
765805 const partialErrorMessage = hasFailures
766806 ? `Partial review: ${ failedFileCount } of ${ files . length } file${ files . length === 1 ? '' : 's' } could not be reviewed after repeated model/provider outages.`
767807 : null ;
@@ -777,6 +817,21 @@ async function runFinalizePhase(
777817 errorMessage : partialErrorMessage ,
778818 } ) ;
779819 logger . info ( `Review job completed: ${ job . owner } /${ job . repo } PR #${ job . prNumber } ` ) ;
820+
821+ // Send anonymous telemetry
822+ await sendTelemetryEvent ( env , {
823+ linesReviewed : files . reduce ( ( sum , file ) => sum + file . lineCount , 0 ) ,
824+ findingsReported : finalComments . length ,
825+ inputTokens : fileInputTokens ,
826+ outputTokens : fileOutputTokens ,
827+ modelsUsed,
828+ fileExtensions,
829+ triggerType : job . trigger ,
830+ reviewDurationMs,
831+ filesReviewed : files . length ,
832+ verdict : verdictSummary . verdict ,
833+ severityDistribution,
834+ } ) ;
780835}
781836
782837async function heartbeatAndCheckSuperseded ( env : AppBindings , jobId : string , leaseOwner : string ) {
0 commit comments