2025-03-12 22:30:10 +00:00
import { determineValidHunks , fileDiffsToMap , isDiffContentsWellFormed } from "../diff_utils.js"
import { userNickname } from "../auth0-mgmt.js"
2025-12-15 16:02:20 +00:00
import { logger } from "../utils/logger.js"
2025-03-12 22:30:10 +00:00
import { getInstallationOctokitByOwner , isUserCollaborator } from "../github/github-utils.js"
import { githubApp } from "../github/github-app.js"
2025-10-29 07:22:41 +00:00
import {
buildDependentPrTitle ,
buildPrCommentBody ,
generateOptimizationReviewTemplate ,
2025-11-19 21:39:02 +00:00
originalPRComment ,
2025-10-29 07:22:41 +00:00
} from "../github/pr-changes-utils.js"
2024-02-21 00:39:58 +00:00
import {
createDependentPullRequest ,
createNewBranchFromDiffContents ,
2025-03-12 22:30:10 +00:00
} from "../github/create-pr-from-diffcontents.js"
import { posthog } from "../analytics.js"
2026-04-13 16:03:05 +00:00
import type { FileDiffContent } from "@codeflash-ai/code-suggester/build/src/types.js"
2024-07-13 01:05:09 +00:00
import { PrismaClient } from "@prisma/client"
2026-04-13 16:03:05 +00:00
import { prisma } from "@codeflash-ai/common"
2025-03-12 22:30:10 +00:00
import { sendSlackMessage } from "../github/slack_util.js"
2025-06-24 22:06:26 +00:00
import { Response } from "express"
2025-06-11 21:34:07 +00:00
import {
requestApproval ,
2025-07-18 23:41:11 +00:00
requiresApprovalForRepo ,
2025-06-11 21:34:07 +00:00
isQualityMonitoringRepo ,
sendQualityMonitoringNotification ,
} from "../github/optimization_approval.js"
2025-06-10 21:21:12 +00:00
import { registerRepositoryAndMember } from "./utils/github-repo-setup.js"
2025-11-19 21:39:02 +00:00
import { saveStagingReview } from "./create-staging.js"
import { AnyOctokit , AuthorizedUserReq , PullRequestDB } from "../types.js"
import { OptimizationReview } from "../OptimizationReview.js"
2026-01-19 17:33:57 +00:00
import {
missingRequiredFields ,
validationFailure ,
unauthorized ,
githubInstallationError ,
githubNotCollaborator ,
optimizationRejected ,
unprocessableEntity ,
internalServerError ,
} from "../exceptions/index.js"
2024-07-13 01:05:09 +00:00
2025-06-24 22:06:26 +00:00
// Dependencies interface for easier testing
export interface SuggestPrChangesDependencies {
prisma : PrismaClient
userNickname : typeof userNickname
getInstallationOctokitByOwner : typeof getInstallationOctokitByOwner
isUserCollaborator : typeof isUserCollaborator
2025-07-18 23:41:11 +00:00
requiresApproval : typeof requiresApprovalForRepo
2025-06-24 22:06:26 +00:00
requestApproval : typeof requestApproval
posthog : typeof posthog
githubApp : typeof githubApp
isDiffContentsWellFormed : typeof isDiffContentsWellFormed
fileDiffsToMap : typeof fileDiffsToMap
determineValidHunks : typeof determineValidHunks
buildDependentPrTitle : typeof buildDependentPrTitle
buildPrCommentBody : typeof buildPrCommentBody
createNewBranchFromDiffContents : typeof createNewBranchFromDiffContents
createDependentPullRequest : typeof createDependentPullRequest
sendSlackMessage : typeof sendSlackMessage
updateOptimizationEvent : typeof updateOptimizationEvent
2026-02-09 18:08:47 +00:00
saveStagingReview : typeof saveStagingReview
2025-06-24 22:06:26 +00:00
}
// Default dependencies
let dependencies : SuggestPrChangesDependencies = {
2026-04-13 16:03:05 +00:00
prisma ,
2025-06-24 22:06:26 +00:00
userNickname ,
getInstallationOctokitByOwner ,
isUserCollaborator ,
2025-07-18 23:41:11 +00:00
requiresApproval : requiresApprovalForRepo ,
2025-06-24 22:06:26 +00:00
requestApproval ,
posthog ,
githubApp ,
isDiffContentsWellFormed ,
fileDiffsToMap ,
determineValidHunks ,
buildDependentPrTitle ,
buildPrCommentBody ,
createNewBranchFromDiffContents ,
createDependentPullRequest ,
sendSlackMessage ,
updateOptimizationEvent ,
2026-02-09 18:08:47 +00:00
saveStagingReview ,
2025-06-24 22:06:26 +00:00
}
// For testing - allow dependency injection
export function setSuggestPrChangesDependencies ( deps : Partial < SuggestPrChangesDependencies > ) {
dependencies = { . . . dependencies , . . . deps }
}
export function resetSuggestPrChangesDependencies() {
dependencies = {
2026-04-13 16:03:05 +00:00
prisma ,
2025-06-24 22:06:26 +00:00
userNickname ,
getInstallationOctokitByOwner ,
isUserCollaborator ,
2025-07-18 23:41:11 +00:00
requiresApproval : requiresApprovalForRepo ,
2025-06-24 22:06:26 +00:00
requestApproval ,
posthog ,
githubApp ,
isDiffContentsWellFormed ,
fileDiffsToMap ,
determineValidHunks ,
buildDependentPrTitle ,
buildPrCommentBody ,
createNewBranchFromDiffContents ,
createDependentPullRequest ,
sendSlackMessage ,
updateOptimizationEvent ,
2026-02-09 18:08:47 +00:00
saveStagingReview ,
2025-06-24 22:06:26 +00:00
}
2025-01-04 18:57:09 +00:00
}
2024-02-21 00:39:58 +00:00
2024-12-19 19:17:18 +00:00
const slackNotificationConfig = {
"Future-House" : [ "aviary" , "paper-qa" ] ,
"langflow-ai" : [ "langflow" ] ,
2025-03-07 01:53:20 +00:00
"albumentations-team" : [ "albumentations" ] ,
2025-03-19 23:19:09 +00:00
"Skyvern-AI" : [ "skyvern" ] ,
2025-04-15 22:25:24 +00:00
roboflow : [ "inference" ] ,
2025-05-27 13:34:13 +00:00
gdsfactory : [ "gdsfactory" ] ,
2025-08-13 22:38:28 +00:00
stackai : [ "stackend" ] ,
2025-05-27 13:34:13 +00:00
}
2025-06-24 22:06:26 +00:00
2025-12-24 14:59:17 +00:00
// Helper function to parse speedup values (same as create-staging.ts)
function parseSpeedupValue ( value : unknown , suffix : "x" | "%" ) : number | null {
if ( value === undefined || value === null || value === "" ) {
return null
}
let stringValue = String ( value ) . trim ( ) . toLowerCase ( )
if ( stringValue . endsWith ( suffix ) ) {
stringValue = stringValue . slice ( 0 , - 1 )
}
const parsed = parseFloat ( stringValue . replace ( "," , "" ) )
return isNaN ( parsed ) ? null : parsed
}
2025-05-27 13:34:13 +00:00
// Utility function to update optimization event in the database
2025-12-24 14:59:17 +00:00
export async function updateOptimizationEvent (
traceId : string ,
prId? : string ,
prCommentFields? : any ,
2026-01-28 16:36:54 +00:00
originalLineProfiler? : string ,
optimizedLineProfiler? : string ,
2025-12-24 14:59:17 +00:00
) {
2025-05-27 13:34:13 +00:00
if ( traceId !== "" ) {
try {
2025-12-24 14:59:17 +00:00
// Check existing data first (preserve staging data)
const existing = await dependencies . prisma . optimization_events . findUnique ( {
where : { trace_id : traceId } ,
2026-01-28 16:36:54 +00:00
select : {
function_name : true ,
speedup_x : true ,
file_path : true ,
speedup_pct : true ,
metadata : true ,
} ,
2025-12-24 14:59:17 +00:00
} )
// Extract data from prCommentFields if provided
const updateData : any = {
. . . ( prId ? { pr_id : String ( prId ) } : { } ) ,
is_optimization_found : true ,
event_type : prId ? "pr_created" : "no-pr" ,
}
// Only add optimization data if prCommentFields provided AND not already set
if ( prCommentFields ) {
if ( ! existing ? . function_name && prCommentFields . function_name ) {
updateData . function_name = prCommentFields . function_name
}
if ( ! existing ? . file_path && prCommentFields . file_path ) {
updateData . file_path = prCommentFields . file_path
}
if ( existing ? . speedup_x == null && prCommentFields . speedup_x ) {
updateData . speedup_x = parseSpeedupValue ( prCommentFields . speedup_x , "x" )
}
if ( existing ? . speedup_pct == null && prCommentFields . speedup_pct ) {
updateData . speedup_pct = parseSpeedupValue ( prCommentFields . speedup_pct , "%" )
}
}
2026-01-28 16:36:54 +00:00
// Add line profiler data to metadata if provided
if ( originalLineProfiler || optimizedLineProfiler ) {
const currentMetadata = ( existing ? . metadata ? ? { } ) as Record < string , unknown >
if ( originalLineProfiler && ! currentMetadata . originalLineProfiler ) {
currentMetadata . originalLineProfiler = originalLineProfiler
}
if ( optimizedLineProfiler && ! currentMetadata . optimizedLineProfiler ) {
currentMetadata . optimizedLineProfiler = optimizedLineProfiler
}
updateData . metadata = currentMetadata
}
2025-06-24 22:06:26 +00:00
await dependencies . prisma . optimization_events . update ( {
2025-05-27 13:34:13 +00:00
where : { trace_id : traceId } ,
2025-12-24 14:59:17 +00:00
data : updateData ,
2025-05-27 13:34:13 +00:00
} )
} catch ( eventError ) {
2025-12-15 16:02:20 +00:00
logger . error (
"Failed to update optimization event:" ,
{ endpoint : "/cfapi/suggest-pr-changes" , operation : "update_optimization_event" } ,
{ } ,
eventError as Error ,
)
2025-05-27 13:34:13 +00:00
}
}
2024-12-19 19:17:18 +00:00
}
2025-06-24 22:06:26 +00:00
2025-03-12 22:30:10 +00:00
export async function suggestPrChanges (
req : AuthorizedUserReq ,
res : Response ,
) : Promise < Response | undefined > {
2024-02-21 00:39:58 +00:00
try {
2024-02-21 23:37:50 +00:00
const {
owner ,
repo ,
pullNumber ,
diffContents ,
prCommentFields ,
existingTests ,
generatedTests ,
2024-11-27 01:01:00 +00:00
coverage_message ,
2025-08-09 00:26:10 +00:00
replayTests ,
concolicTests ,
2025-10-22 05:06:12 +00:00
optimizationReview ,
2026-01-28 16:36:54 +00:00
originalLineProfiler ,
optimizedLineProfiler ,
2024-02-21 23:37:50 +00:00
} = req . body
2025-10-21 00:27:44 +00:00
2025-01-06 22:01:20 +00:00
const userId = req . userId
2024-07-13 01:05:09 +00:00
const traceId = req . body . traceId || ""
2025-12-15 16:02:20 +00:00
logger . info ( ` traceId: ${ traceId } ` , req )
2024-02-21 00:39:58 +00:00
2025-06-24 22:06:26 +00:00
if ( ! repo || ! owner || ! pullNumber || ! dependencies . isDiffContentsWellFormed ( diffContents ) ) {
2026-01-19 17:33:57 +00:00
throw validationFailure ( "Missing or malformed fields: repo, owner, pullNumber, diffContents" )
2024-02-21 00:39:58 +00:00
}
2025-06-24 22:06:26 +00:00
const nickname = await dependencies . userNickname ( userId )
2024-02-21 00:39:58 +00:00
if ( nickname == null ) {
2026-01-19 17:33:57 +00:00
throw unauthorized ( "" )
2024-02-21 00:39:58 +00:00
}
2025-06-24 22:06:26 +00:00
const installationOctokit = await dependencies . getInstallationOctokitByOwner (
dependencies . githubApp ,
owner ,
repo ,
2026-03-07 21:24:32 +00:00
userId ,
2025-06-24 22:06:26 +00:00
)
2024-02-21 00:39:58 +00:00
if ( installationOctokit instanceof Error ) {
2026-01-19 17:33:57 +00:00
throw githubInstallationError ( installationOctokit . message )
2024-02-21 00:39:58 +00:00
}
2025-06-24 22:06:26 +00:00
const isCollaborator = await dependencies . isUserCollaborator (
installationOctokit ,
owner ,
repo ,
nickname ,
)
2024-02-21 00:39:58 +00:00
if ( ! isCollaborator ) {
2025-12-15 16:02:20 +00:00
logger . info ( ` ${ nickname } is not a collaborator on ${ owner } / ${ repo } ` , req )
2026-01-19 17:33:57 +00:00
throw githubNotCollaborator ( ` ${ owner } / ${ repo } ` )
2024-02-21 00:39:58 +00:00
}
2025-12-15 16:02:20 +00:00
logger . info ( ` ${ nickname } is a collaborator on ${ owner } / ${ repo } ` , req )
2025-06-10 21:21:12 +00:00
// TODO: Remove this background upsert logic after ensuring all old repositories have been saved.
registerRepositoryAndMember ( owner , repo , nickname , userId , installationOctokit )
2026-04-13 16:03:05 +00:00
. then ( ( ) = > {
logger . info ( ` Background repo and member upsert completed for ${ owner } / ${ repo } ` , req )
} )
2025-06-10 21:21:12 +00:00
. catch ( err = > {
2026-01-19 17:33:57 +00:00
logger . errorWithSentry (
2026-02-09 18:08:47 +00:00
` Error in background upsertRepoAndCreateMember ` ,
2026-01-19 17:33:57 +00:00
req ,
2026-02-09 18:08:47 +00:00
{ owner , repo , nickname , userId } ,
2026-01-19 17:33:57 +00:00
err as Error ,
)
2025-06-10 21:21:12 +00:00
} )
2025-05-19 11:00:18 +00:00
// Check if approval is required
2025-06-24 22:06:26 +00:00
if ( traceId && dependencies . requiresApproval ( owner , repo ) ) {
const optimization = await dependencies . prisma . optimization_features . findUnique ( {
2025-05-19 11:00:18 +00:00
where : { trace_id : traceId } ,
select : {
approval_required : true ,
approval_status : true ,
} ,
} )
if ( optimization ? . approval_status === "rejected" ) {
2026-01-19 17:33:57 +00:00
throw optimizationRejected ( "This optimization request was rejected" )
2025-05-19 11:00:18 +00:00
}
if ( optimization ? . approval_status === "approved" ) {
2026-01-19 17:33:57 +00:00
logger . info (
` Request ${ traceId } was previously approved, continuing with PR suggestion ` ,
req ,
)
2025-05-19 11:00:18 +00:00
const result = await triggerSuggestPrChanges (
owner ,
repo ,
pullNumber ,
diffContents ,
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
userId ,
nickname ,
installationOctokit ,
2025-08-09 00:26:10 +00:00
replayTests ,
concolicTests ,
2025-05-19 11:00:18 +00:00
traceId ,
2025-10-22 05:06:12 +00:00
optimizationReview ,
2025-11-19 21:39:02 +00:00
res ,
2026-01-28 16:36:54 +00:00
originalLineProfiler ,
optimizedLineProfiler ,
2025-05-19 11:00:18 +00:00
)
2025-11-19 21:39:02 +00:00
if ( result && typeof result === "object" && "status" in result ) {
2026-04-13 16:03:05 +00:00
return result
2025-11-19 21:39:02 +00:00
}
2025-05-19 11:00:18 +00:00
return res . json ( result )
2025-06-24 22:06:26 +00:00
} else {
2025-05-19 11:00:18 +00:00
const requestData = {
type : "suggest-pr-changes" ,
owner ,
repo ,
pullNumber ,
diffContents ,
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
userId ,
2025-10-24 23:56:25 +00:00
optimizationReview ,
2026-01-28 16:36:54 +00:00
originalLineProfiler ,
optimizedLineProfiler ,
2025-05-19 11:00:18 +00:00
}
2025-06-24 22:06:26 +00:00
await dependencies . requestApproval (
2025-05-19 11:00:18 +00:00
traceId ,
owner ,
repo ,
prCommentFields . function_name ,
userId ,
requestData ,
)
return res . status ( 202 ) . json ( {
status : "pending_approval" ,
message :
"This PR suggestion requires approval. You will be notified when it is processed." ,
} )
}
}
// No approval required, proceed with PR suggestion
2025-06-11 21:34:07 +00:00
const result = await triggerSuggestPrChanges (
2025-05-19 11:00:18 +00:00
owner ,
repo ,
pullNumber ,
diffContents ,
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
userId ,
nickname ,
installationOctokit ,
2025-08-09 00:26:10 +00:00
replayTests ,
concolicTests ,
2025-05-19 11:00:18 +00:00
traceId ,
2025-10-22 05:06:12 +00:00
optimizationReview ,
2025-11-19 21:39:02 +00:00
res ,
2026-01-28 16:36:54 +00:00
originalLineProfiler ,
optimizedLineProfiler ,
2025-06-11 21:34:07 +00:00
)
// Check if this is a quality monitoring repo and send notification
if ( traceId && isQualityMonitoringRepo ( owner , repo ) && result && typeof result === "number" ) {
const requestData = {
type : "suggest-pr-changes" ,
owner ,
repo ,
pullNumber ,
diffContents ,
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
userId ,
2025-10-03 19:37:34 +00:00
replayTests ,
concolicTests ,
2025-10-24 23:56:25 +00:00
optimizationReview ,
2026-01-28 16:36:54 +00:00
originalLineProfiler ,
optimizedLineProfiler ,
2025-06-11 21:34:07 +00:00
}
// Send quality monitoring notification (non-blocking)
sendQualityMonitoringNotification (
traceId ,
owner ,
repo ,
prCommentFields . function_name ,
userId ,
requestData ,
) . catch ( error = > {
2025-12-15 16:02:20 +00:00
logger . error ( ` Failed to send quality monitoring notification: ` , req , { } , error as Error )
2025-06-11 21:34:07 +00:00
} )
2025-12-15 16:02:20 +00:00
logger . info (
2025-06-11 21:34:07 +00:00
` Quality monitoring notification triggered for ${ owner } / ${ repo } PR # ${ pullNumber } ` ,
2025-12-15 16:02:20 +00:00
req ,
2025-06-11 21:34:07 +00:00
)
}
2025-11-19 21:39:02 +00:00
// Don't call res.json(result) if result is already a Response object
if ( result && typeof result === "object" && "status" in result ) {
2026-04-13 16:03:05 +00:00
return result
2025-11-19 21:39:02 +00:00
}
2025-06-11 21:34:07 +00:00
return res . json ( result )
2025-05-19 11:00:18 +00:00
} catch ( error : any ) {
2026-02-09 18:08:47 +00:00
// Try to fallback to staging if we have a traceId
const traceId = req . body . traceId || ""
if ( traceId ) {
logger . info ( ` PR suggestion failed, falling back to staging for traceId: ${ traceId } ` , req )
try {
2026-04-13 16:03:05 +00:00
const stagingResult = await dependencies . saveStagingReview (
req . body ,
req . userId ,
req . organizationId ,
req . subscriptionInfo ,
)
2026-02-09 18:08:47 +00:00
if ( stagingResult . status === 200 ) {
return res . status ( 200 ) . json ( {
message : "PR suggestion failed, staging created as fallback" ,
. . . stagingResult . data ,
} )
}
// Handle non-200 staging response - return staging's actual status/error
logger . errorWithSentry (
` Staging fallback returned status ${ stagingResult . status } ` ,
req ,
{ reqBody : req.body , userId : req.userId , traceId , stagingResult } ,
2026-04-13 16:03:05 +00:00
new Error ( ` Staging fallback returned status ${ stagingResult . status } ` ) ,
2026-02-09 18:08:47 +00:00
)
return res . status ( stagingResult . status ) . json ( {
message : "PR suggestion failed and staging fallback also failed" ,
. . . stagingResult . data ,
} )
} catch ( stagingError ) {
logger . errorWithSentry (
` Staging fallback threw an exception ` ,
req ,
{ reqBody : req.body , userId : req.userId , traceId } ,
2026-04-13 16:03:05 +00:00
stagingError as Error ,
2026-02-09 18:08:47 +00:00
)
return res . status ( 500 ) . json ( {
message : "PR suggestion failed and staging fallback threw an error" ,
error : stagingError instanceof Error ? stagingError.message : String ( stagingError ) ,
} )
}
2026-01-19 17:33:57 +00:00
}
2026-02-09 18:08:47 +00:00
2026-04-13 16:03:05 +00:00
logger . errorWithSentry (
` Error in /cfapi/suggest-pr-changes: ${ error } ` ,
req ,
{ errorMessage : error.message } ,
error as Error ,
)
2026-01-19 17:33:57 +00:00
dependencies . posthog . capture ( {
2025-05-19 11:00:18 +00:00
distinctId : req.userId ,
event : ` cfapi-suggest-pr-changes-failed-error ` ,
2026-02-09 18:08:47 +00:00
properties : { error : error.message , stack : error.stack } ,
2025-05-19 11:00:18 +00:00
} )
2026-01-19 17:33:57 +00:00
throw internalServerError ( ` Error creating pull request: ${ error . message } ` )
2025-05-19 11:00:18 +00:00
}
}
2025-05-14 17:38:36 +00:00
2025-05-19 11:00:18 +00:00
export async function triggerSuggestPrChanges (
owner : string ,
repo : string ,
pullNumber : number ,
diffContents : any ,
prCommentFields : any ,
existingTests : string ,
generatedTests : string ,
coverage_message : string ,
userId : string ,
nickname : string ,
installationOctokit : any ,
2025-08-11 22:15:43 +00:00
replayTests : string = "" ,
concolicTests : string = "" ,
2025-05-19 11:00:18 +00:00
traceId : string = "" ,
2025-10-22 05:06:12 +00:00
optimizationReview : string = "" ,
2025-05-19 11:00:18 +00:00
res? : Response ,
2026-01-28 16:36:54 +00:00
originalLineProfiler : string = "" ,
optimizedLineProfiler : string = "" ,
2025-05-19 11:00:18 +00:00
) : Promise < Response | number | null > {
try {
2025-06-24 22:06:26 +00:00
const diffContentsMap : Map < string , FileDiffContent > = dependencies . fileDiffsToMap ( diffContents )
2024-02-21 00:39:58 +00:00
2025-06-24 22:06:26 +00:00
const { validHunks , invalidHunks } = await dependencies . determineValidHunks (
2026-04-13 16:03:05 +00:00
installationOctokit . rest ,
2024-02-21 00:39:58 +00:00
{ owner , repo } ,
pullNumber ,
100 ,
diffContentsMap ,
)
2025-02-21 05:18:55 +00:00
const timestamp = new Date ( )
. toISOString ( )
. replace ( /:/g , "." )
. replace ( /\.\d+Z$/ , "" )
const newBranchName = ` codeflash/optimize-pr ${ pullNumber } - ${ timestamp } `
const originalPrData = await installationOctokit . rest . pulls . get ( {
owner ,
repo ,
pull_number : pullNumber ,
} )
2026-02-20 19:53:55 +00:00
// Check if the PR is merged or closed - we can't suggest changes on merged/closed PRs
if ( originalPrData . data . merged ) {
2026-04-13 16:03:05 +00:00
logger . info ( ` PR # ${ pullNumber } is already merged, cannot suggest changes ` , {
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "pr_merged_check" ,
owner ,
repo ,
userId ,
} )
2026-02-20 19:53:55 +00:00
throw unprocessableEntity (
` Cannot suggest changes on merged PR # ${ pullNumber } . The PR was already merged. ` ,
)
}
if ( originalPrData . data . state === "closed" ) {
2026-04-13 16:03:05 +00:00
logger . info ( ` PR # ${ pullNumber } is closed, cannot suggest changes ` , {
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "pr_closed_check" ,
owner ,
repo ,
userId ,
} )
2026-02-20 19:53:55 +00:00
throw unprocessableEntity (
` Cannot suggest changes on closed PR # ${ pullNumber } . The PR is no longer open. ` ,
)
}
2025-02-21 05:18:55 +00:00
const baseBranch = originalPrData . data . head . ref
2026-01-19 17:33:57 +00:00
logger . info ( ` Attempting to access ref for: ${ owner } / ${ repo } , branch: ${ baseBranch } ` , {
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "access_ref" ,
owner ,
repo ,
userId ,
} )
2025-06-24 22:06:26 +00:00
2025-09-11 14:39:12 +00:00
const commitMessage = ` Optimize ${ prCommentFields . function_name } \ n \ n ${ prCommentFields . optimization_explanation } `
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
let hasMultipleHunksInSameFile = false
2026-04-13 16:03:05 +00:00
const hasMultipleFiles = validHunks . size > 1
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
for ( const [ filePath , hunks ] of validHunks . entries ( ) ) {
if ( hunks . length > 1 ) {
2025-12-15 16:02:20 +00:00
logger . info (
2025-04-02 01:19:57 +00:00
` File ${ filePath } has ${ hunks . length } hunks, using dependent PR instead of review comments ` ,
2026-01-19 17:33:57 +00:00
{
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "multiple_hunks" ,
owner ,
repo ,
userId ,
} ,
2025-04-02 01:19:57 +00:00
)
hasMultipleHunksInSameFile = true
break
}
}
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
if ( hasMultipleFiles ) {
2025-12-15 16:02:20 +00:00
logger . info (
2025-04-02 01:19:57 +00:00
` Found ${ validHunks . size } files with changes, using dependent PR instead of review comments ` ,
2025-12-15 16:02:20 +00:00
{ endpoint : "/cfapi/suggest-pr-changes" , operation : "multiple_files" , owner , repo , userId } ,
2025-04-02 01:19:57 +00:00
)
}
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
if (
invalidHunks . size > 0 ||
validHunks . size > 1 ||
hasMultipleFiles ||
hasMultipleHunksInSameFile
) {
2025-12-15 16:02:20 +00:00
logger . info (
2025-03-21 01:07:15 +00:00
` Creating a dependent PR because there are ${ invalidHunks . size > 0 ? "invalid hunks" : "multiple valid hunks" } . ` ,
2026-01-19 17:33:57 +00:00
{
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "create_dependent_pr" ,
owner ,
repo ,
} ,
2024-02-21 00:39:58 +00:00
)
2025-12-15 16:02:20 +00:00
logger . info ( ` Making a new dependent PR... ` , {
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "create_dependent_pr" ,
owner ,
repo ,
} )
2025-11-19 21:39:02 +00:00
if (
OptimizationReview . MEDIUM === optimizationReview &&
! dependencies . requiresApproval ( owner , repo ) &&
traceId
) {
2026-02-09 18:08:47 +00:00
const result = await dependencies . saveStagingReview (
2025-11-19 21:39:02 +00:00
{
owner ,
repo ,
pullNumber ,
baseBranch ,
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
replayTests ,
concolicTests ,
optimizationReview ,
traceId ,
diffContents ,
} ,
userId ,
2026-01-07 20:23:45 +00:00
null , // organizationId - not available in this context
2025-11-19 21:39:02 +00:00
)
if ( result . status == 200 ) {
const commentBody : string = originalPRComment (
prCommentFields ,
traceId , //We will send trace Id instead of PRNumber
baseBranch ,
optimizationReview ,
)
await installationOctokit . rest . issues . createComment ( {
owner ,
repo ,
issue_number : pullNumber ,
body : commentBody ,
} )
if ( res ) {
return res . status ( 200 ) . json ( { message : "staging created successfully" } )
}
}
}
const branchCreated = await dependencies . createNewBranchFromDiffContents (
installationOctokit ,
owner ,
repo ,
newBranchName ,
baseBranch ,
diffContentsMap ,
commitMessage ,
)
2024-02-21 00:39:58 +00:00
2025-11-19 21:39:02 +00:00
if ( ! branchCreated ) {
throw new Error ( ` Failed to create branch ${ newBranchName } ` )
}
2025-06-24 22:06:26 +00:00
const newPrData = await dependencies . createDependentPullRequest (
2026-04-13 16:03:05 +00:00
installationOctokit ,
2024-02-21 00:39:58 +00:00
owner ,
repo ,
pullNumber ,
newBranchName ,
baseBranch ,
prCommentFields ,
2024-02-21 23:37:50 +00:00
existingTests ,
2024-02-21 00:39:58 +00:00
generatedTests ,
2025-05-30 23:15:18 +00:00
coverage_message ,
2025-08-09 00:26:10 +00:00
replayTests ,
concolicTests ,
2025-10-22 05:06:12 +00:00
optimizationReview ,
2024-02-21 00:39:58 +00:00
)
2025-12-15 16:02:20 +00:00
logger . info (
2024-02-21 00:39:58 +00:00
` Created new dependent PR # ${ newPrData . data . number } from branch ${ newPrData . data . head . ref } ` ,
2026-01-19 17:33:57 +00:00
{
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "dependent_pr_created" ,
owner ,
repo ,
} ,
2024-02-21 00:39:58 +00:00
)
2024-09-14 03:12:16 +00:00
2025-01-04 18:57:09 +00:00
if ( slackNotificationConfig [ owner as keyof typeof slackNotificationConfig ] ? . includes ( repo ) ) {
2025-06-24 22:06:26 +00:00
await dependencies . sendSlackMessage (
2025-01-02 19:12:30 +00:00
` new dependent PR created: ${ newPrData . data . html_url } for ${ owner } / ${ repo } ` ,
)
2024-09-14 03:12:16 +00:00
}
2024-11-14 23:58:34 +00:00
2025-12-31 00:29:06 +00:00
dependencies . posthog ? . capture ( {
2024-02-21 00:39:58 +00:00
distinctId : userId ,
event : ` cfapi-suggest-pr-changes-success-dependent-pr-created ` ,
properties : {
2024-03-01 01:10:15 +00:00
owner ,
repo ,
2024-02-21 00:39:58 +00:00
newPrNumber : newPrData.data.number ,
newPrBranch : newPrData.data.head.ref ,
2024-06-07 19:26:05 +00:00
PRURL : newPrData.data.html_url ,
2024-02-21 00:39:58 +00:00
} ,
} )
2025-06-24 22:06:26 +00:00
2024-07-13 01:05:09 +00:00
if ( traceId !== "" ) {
2026-04-13 16:03:05 +00:00
const pull_request_db = await dependencies . prisma . optimization_features . findUnique ( {
2024-07-13 01:05:09 +00:00
where : {
trace_id : traceId ,
} ,
select : {
pull_request : true ,
} ,
} )
2025-06-24 22:06:26 +00:00
2024-07-20 05:15:57 +00:00
if ( pull_request_db ) {
2025-01-06 22:01:20 +00:00
if ( pull_request_db . pull_request === null || pull_request_db . pull_request === undefined ) {
2024-07-20 05:15:57 +00:00
pull_request_db . pull_request = { }
}
2025-03-12 22:30:10 +00:00
; ( pull_request_db as PullRequestDB ) . pull_request . dependent_pr_url =
newPrData . data . html_url
2024-07-13 01:05:09 +00:00
2025-06-24 22:06:26 +00:00
await dependencies . prisma . optimization_features . update ( {
2024-07-13 01:05:09 +00:00
where : {
trace_id : traceId ,
} ,
2025-05-19 11:00:18 +00:00
data : {
pull_request : pull_request_db.pull_request ,
} ,
2024-07-13 01:05:09 +00:00
} )
}
}
2025-05-19 11:00:18 +00:00
2026-01-28 16:36:54 +00:00
await dependencies . updateOptimizationEvent (
traceId ,
newPrData . data . id ,
prCommentFields ,
originalLineProfiler ,
optimizedLineProfiler ,
)
2025-06-24 22:06:26 +00:00
2025-05-19 11:00:18 +00:00
if ( res ) {
res . json ( newPrData . data . number )
return res
}
return newPrData . data . number
2024-02-21 00:39:58 +00:00
} else {
2025-12-15 16:02:20 +00:00
logger . info ( ` Creating unified review for a single valid hunk. ` , {
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "create_unified_review" ,
owner ,
repo ,
} )
2025-06-24 22:06:26 +00:00
const prCommentBody = dependencies . buildPrCommentBody (
2025-01-04 18:57:09 +00:00
prCommentFields ,
existingTests ,
generatedTests ,
coverage_message ,
2025-02-21 05:18:55 +00:00
newBranchName ,
2025-08-09 00:26:10 +00:00
replayTests ,
concolicTests ,
2026-01-06 00:15:48 +00:00
traceId ,
2025-06-05 21:16:25 +00:00
{ isUnifiedReview : true , includeHeader : false , isCollapsed : true } ,
2025-01-04 18:57:09 +00:00
)
2025-10-29 07:22:41 +00:00
let optReviewBadge = generateOptimizationReviewTemplate ( optimizationReview )
2026-04-13 16:03:05 +00:00
optReviewBadge && = ` \ n \ n ${ optReviewBadge } \ n `
const reviewComments = [ ]
2025-04-02 01:19:57 +00:00
let foundInvalidHunk = false
2024-02-21 00:39:58 +00:00
2025-03-26 03:55:12 +00:00
for ( const [ filePath , hunks ] of validHunks . entries ( ) ) {
for ( const hunk of hunks ) {
2026-01-26 15:50:16 +00:00
if ( hunk . oldStart <= hunk . oldEnd ) {
2025-04-02 01:19:57 +00:00
const newContent = hunk . newContent . join ( "\n" )
const isLongDiff = newContent . length > 500
let commentBody
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
if ( isLongDiff ) {
commentBody =
2026-04-13 16:03:05 +00:00
` ${ prCommentBody } \ n \ n ` +
` <details> \ n ` +
` <summary>Click to see suggested changes</summary> \ n \ n ` +
` \` \` \` suggestion \ n ${ newContent } \ n \` \` \` \ n ` +
` </details> ` +
` \ n ${ optReviewBadge } `
2025-04-02 01:19:57 +00:00
} else {
2025-10-29 07:22:41 +00:00
commentBody =
2026-04-13 16:03:05 +00:00
` ${ prCommentBody } \ n \ n ` +
` \` \` \` suggestion \ n ${ newContent } \ n \` \` \` ` +
` \ n ${ optReviewBadge } `
2025-04-02 01:19:57 +00:00
}
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
reviewComments . push ( {
path : filePath ,
line : hunk.oldEnd ,
start_line : hunk.oldStart ,
side : "RIGHT" ,
2025-06-24 22:06:26 +00:00
body : commentBody ,
2025-04-02 01:19:57 +00:00
} )
} else {
2025-12-15 16:02:20 +00:00
logger . warn (
2026-01-26 15:50:16 +00:00
` Found invalid review range for ${ filePath } : start_line ( ${ hunk . oldStart } ) must be less than or equal to line ( ${ hunk . oldEnd } ) with content ${ hunk . newContent . join ( "\n" ) } ` ,
2026-01-19 17:33:57 +00:00
{
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "invalid_review_range" ,
owner ,
repo ,
} ,
2025-04-02 01:19:57 +00:00
)
foundInvalidHunk = true
break
}
2025-03-26 03:55:12 +00:00
}
2024-02-21 00:39:58 +00:00
2025-04-02 01:19:57 +00:00
if ( foundInvalidHunk ) {
break
}
2024-09-14 03:12:16 +00:00
}
2025-04-02 01:19:57 +00:00
if ( ! foundInvalidHunk && reviewComments . length > 0 ) {
const review = await installationOctokit . rest . pulls . createReview ( {
2024-03-01 01:10:15 +00:00
owner ,
repo ,
2025-04-02 01:19:57 +00:00
pull_number : pullNumber ,
commit_id : originalPrData.data.head.sha ,
event : "COMMENT" ,
comments : reviewComments ,
} )
2025-06-24 22:06:26 +00:00
2025-12-15 16:02:20 +00:00
logger . info ( ` Added review comment to PR # ${ pullNumber } : ${ review . data . html_url } ` , {
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "review_comment_added" ,
owner ,
repo ,
} )
2024-09-14 03:12:16 +00:00
2025-04-02 01:19:57 +00:00
if (
slackNotificationConfig [ owner as keyof typeof slackNotificationConfig ] ? . includes ( repo )
) {
2025-06-24 22:06:26 +00:00
await dependencies . sendSlackMessage (
` Suggestions made for ${ review . data . html_url } in ${ owner } / ${ repo } ` ,
)
2025-04-02 01:19:57 +00:00
}
2025-06-24 22:06:26 +00:00
2025-12-31 00:29:06 +00:00
dependencies . posthog ? . capture ( {
2025-04-02 01:19:57 +00:00
distinctId : userId ,
event : ` cfapi-suggest-pr-changes-success-suggestions-made ` ,
properties : {
owner ,
repo ,
reviewId : review.data.id ,
PRURL : review.data.html_url ,
2024-07-13 01:05:09 +00:00
} ,
} )
2025-04-02 01:19:57 +00:00
if ( traceId !== "" ) {
2026-04-13 16:03:05 +00:00
const pull_request_db = await dependencies . prisma . optimization_features . findUnique ( {
2024-07-13 01:05:09 +00:00
where : {
trace_id : traceId ,
} ,
2025-04-02 01:19:57 +00:00
select : {
pull_request : true ,
} ,
2024-07-13 01:05:09 +00:00
} )
2025-06-24 22:06:26 +00:00
2025-04-02 01:19:57 +00:00
if ( pull_request_db ) {
if (
pull_request_db . pull_request === null ||
pull_request_db . pull_request === undefined
) {
pull_request_db . pull_request = { }
}
; ( pull_request_db as PullRequestDB ) . pull_request . review_suggestion_pr_url =
review . data . html_url
2025-06-24 22:06:26 +00:00
await dependencies . prisma . optimization_features . update ( {
2025-04-02 01:19:57 +00:00
where : {
trace_id : traceId ,
} ,
2025-05-19 11:00:18 +00:00
data : {
pull_request : pull_request_db.pull_request ,
} ,
2025-04-02 01:19:57 +00:00
} )
}
2024-07-13 01:05:09 +00:00
}
2025-05-19 11:00:18 +00:00
2026-01-28 16:36:54 +00:00
await dependencies . updateOptimizationEvent (
traceId ,
undefined ,
prCommentFields ,
originalLineProfiler ,
optimizedLineProfiler ,
)
2025-06-24 22:06:26 +00:00
2025-05-19 11:00:18 +00:00
if ( res ) {
res . json ( review . data . id )
return res
}
return review . data . id
2025-04-02 01:19:57 +00:00
} else {
const reason = foundInvalidHunk
? "invalid line ordering in hunks"
: "no valid review comments could be created"
2025-12-15 16:02:20 +00:00
logger . warn ( ` Cannot create review due to ${ reason } ` , {
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "cannot_create_review" ,
owner ,
repo ,
} )
2025-04-02 01:19:57 +00:00
2026-01-19 17:33:57 +00:00
logger . error ( ` Cannot create review comments due to ${ reason } ` , {
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "cannot_create_review" ,
owner ,
repo ,
} )
throw unprocessableEntity (
` Cannot create review comments due to ${ reason } . Please consider creating a dependent PR instead ` ,
)
2024-07-13 01:05:09 +00:00
}
2024-02-21 00:39:58 +00:00
}
} catch ( error : any ) {
2026-01-19 17:33:57 +00:00
// Re-throw AppExceptions to be handled by GlobalExceptionHandler
if ( error && typeof error === "object" && "getHttpStatus" in error ) {
throw error
2025-05-19 11:00:18 +00:00
}
2026-01-19 17:33:57 +00:00
logger . errorWithSentry (
` Error in triggerSuggestPrChanges: ${ error } ` ,
{
userId ,
endpoint : "/cfapi/suggest-pr-changes" ,
operation : "trigger_suggest_pr_changes" ,
owner ,
repo ,
} ,
{
errorMessage : error.message ,
} ,
error as Error ,
)
throw internalServerError ( ` Error creating pull request: ${ error . message } ` )
2025-01-06 23:25:54 +00:00
}
2025-02-21 05:18:55 +00:00
}