Skip to content

Commit 05de6ee

Browse files
updated the script.js
1 parent f67f6fc commit 05de6ee

1 file changed

Lines changed: 47 additions & 42 deletions

File tree

‎.github/scripts/review.js‎

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async function fetchWithBackoff(func, maxRetries = MAX_RETRIES, initialDelay = I
2727
// These should not be retried as the input/model is fundamentally wrong.
2828
if (error.status === 400 || error.status === 404) {
2929
console.error(`Fatal error (${error.status}) encountered. Aborting immediately.`);
30-
throw error;
30+
throw error;
3131
}
3232

3333
// Check for transient errors (e.g., 429 Rate Limit, 5xx Server Error)
@@ -93,10 +93,10 @@ async function splitDiffIntoTokens(aiClient, diff, maxTokens = MAX_CHUNK_TOKENS)
9393
async function updateReadme(octokit, owner, repo, aiGeneratedContent, pull_number) {
9494
const readmePath = "README.md";
9595
let readmeSha;
96-
96+
9797
// context.payload.pull_request is guaranteed to exist here
9898
const headRef = context.payload.pull_request.head.ref;
99-
99+
100100
console.log("Attempting to read existing README.md...");
101101
try {
102102
const { data } = await octokit.rest.repos.getContents({
@@ -167,8 +167,8 @@ async function performPRReview(octokit, diffContent, pull_number, aiClient) {
167167

168168
for (let i = 0; i < chunks.length; i++) {
169169
const chunk = chunks[i];
170-
171-
const chunkPrompt = `You are a helpful and expert AI code reviewer. Analyze the following Git diff chunk. Your response must be highly structured and strictly focused on identifying issues and providing solutions.
170+
171+
const chunkPrompt = `You are a helpful and expert AI code reviewer. Analyze the following Git diff chunk. Your response must be highly structured and strictly focused on identifying issues and providing solutions.
172172
173173
For each issue found, format your finding with a clear bullet point and, if the fix is simple, provide the recommended code change directly beneath it in a code block.
174174
@@ -195,7 +195,7 @@ async function performPRReview(octokit, diffContent, pull_number, aiClient) {
195195
} catch (error) {
196196
// Fatal error occurred, stop processing chunks
197197
console.error(`Fatal error encountered during review chunk processing. Aborting review.`);
198-
198+
199199
// Post a single error message and return immediately
200200
const errorMessage = `❌ **AI Review Failed** ❌\n\nA critical error occurred during the review process (likely due to an incorrect model configuration, service key issue, or a malformed request). The first error encountered was:\n\n\`\`\`\n${error.message}\n\`\`\`\n\n**Action Required:** Please check the model configuration, service key, and retry the review.`;
201201
await octokit.rest.issues.createComment({
@@ -231,7 +231,7 @@ async function performPRReview(octokit, diffContent, pull_number, aiClient) {
231231
Partial Reviews to Synthesize:
232232
${chunkReviews.join('\n\n---\n\n')}
233233
`;
234-
234+
235235
let reviewBody = "Review generation failed.";
236236
try {
237237
const finalReviewResult = await fetchWithBackoff(() => aiClient.chatCompletion({
@@ -372,16 +372,16 @@ async function handleNewIssue(octokit, owner, repo, issueNumber, issueTitle, iss
372372
await octokit.rest.issues.addLabels({ owner, repo, issue_number: issueNumber, labels: ["duplicate"] });
373373
const status = similar.state === "closed" ? "closed" : "in progress";
374374
const resolution = extractResolution(similar.body);
375-
376-
const duplicateComment = `### 🔁 Potential Duplicate Detected (Semantic Match)\nBased on **issue context and body**, a related issue appears to already exist: **#${similar.number} - ${similar.title}** (${status}).\n\n**Link:** ${similar.html_url}\n\n**Summary (Truncated):**\n${truncate(similar.body || '(no body)', 800)}\n\n${resolution ? `**Extracted Resolution / Status Notes:**\n${resolution}\n\n` : ''}If this is a duplicate, please consolidate discussion there and consider closing this one. If not, comment with \`not a duplicate\` and I will proceed with fresh root-cause analysis.`; await octokit.rest.issues.createComment({ owner, repo, issue_number: issueNumber, body: duplicateComment });
375+
376+
const duplicateComment = `### 🔁 Potential Duplicate Detected (Semantic Match)\nBased on **issue context and body**, a related issue appears to already exist: **#${similar.number} - ${similar.title}** (${status}).\n\n**Link:** ${similar.html_url}\n\n**Summary (Truncated):**\n${truncate(similar.body || '(no body)', 800)}\n\n${resolution ? `**Extracted Resolution / Status Notes:**\n${resolution}\n\n` : ''}If this is a duplicate, please consolidate discussion there and consider closing this one. If not, comment with \`not a duplicate\` and I will proceed with fresh root-cause analysis.`; await octokit.rest.issues.createComment({ owner, repo, issue_number: issueNumber, body: duplicateComment });
377377
return;
378378
}
379379

380380
// No match: scan repository for potential causes
381381
const repoScan = scanRepositoryForIssue(issueTitle, issueBody, process.cwd());
382382
console.log(`Repository scan complete. Matched contexts: ${repoScan.matches.length}`);
383383
const joinedContexts = repoScan.matches.map(m => `File: ${m.file}\n${m.snippet}`).join("\n---\n");
384-
384+
385385
const recPrompt = `You are an expert senior engineer. A new issue was filed. Use the code contexts to hypothesize root causes and generate a detailed, prioritized remediation checklist. Your output must strictly follow the required markdown structure below.
386386
387387
Crucially, for the most likely and actionable remediation steps, you **must include the exact code snippet** showing the required change in a markdown code block. Do not just describe the fix—show the code.
@@ -421,7 +421,7 @@ async function handleNewIssue(octokit, owner, repo, issueNumber, issueTitle, iss
421421
Relevant Code Contexts (truncated):
422422
${truncate(joinedContexts, 12000)}
423423
`;
424-
424+
425425
let recommendations = "Failed to generate recommendations.";
426426
try {
427427
const recResult = await fetchWithBackoff(() => aiClient.chatCompletion({
@@ -433,7 +433,7 @@ async function handleNewIssue(octokit, owner, repo, issueNumber, issueTitle, iss
433433
// Post a single error message for the issue handler
434434
recommendations = `❌ **AI Analysis Failed** ❌\n\nA critical error occurred while generating the initial analysis (likely due to an incorrect model configuration, service key issue, or a malformed request). The error was:\n\n\`\`\`\n${error.message}\n\`\`\`\n\n**Action Required:** Please check the model configuration and service key.`;
435435
}
436-
436+
437437
await ensureLabel(octokit, owner, repo, "awaiting-confirmation", { description: "Pending maintainer confirmation for remediation", color: "5319e7" });
438438
await octokit.rest.issues.addLabels({ owner, repo, issue_number: issueNumber, labels: ["awaiting-confirmation"] });
439439
const confirmComment = `${recommendations}\n\n**Next Step:** Reply with \`confirm remediation\` to approve moving forward (e.g., drafting a PR or creating task list). Reply with \`refine analysis\` for a deeper pass, or \`discard recommendations\` to remove them.`;
@@ -483,11 +483,11 @@ async function findSimilarIssueSemantic(title, body, pastIssues, aiClient) {
483483
const MAX_EMBED_ISSUES = safeParseInt(process.env.MAX_SEMANTIC_ISSUES, 150);
484484
// SAP AI SDK embeddings client
485485
let embeddingModel = null;
486-
try {
487-
embeddingModel = new AzureOpenAiEmbeddingClient({ modelName: 'text-embedding-ada-002' });
488-
} catch (e) {
486+
try {
487+
embeddingModel = new AzureOpenAiEmbeddingClient({ modelName: 'text-embedding-ada-002' });
488+
} catch (e) {
489489
console.warn("Embedding model initialization failed", e.message);
490-
embeddingModel = null;
490+
embeddingModel = null;
491491
}
492492
const newTitleTokens = tokenize(title); const newBodyTokens = tokenize(body);
493493
const newText = `${title}\n${body}`;
@@ -496,20 +496,20 @@ async function findSimilarIssueSemantic(title, body, pastIssues, aiClient) {
496496
try { newEmbedding = await getEmbeddingSafe(embeddingModel, newText); } catch (e) { console.warn("New issue embedding failed", e.message); }
497497
}
498498
const scored = pastIssues.map(i => ({ issue: i, score: lexicalSimilarityCandidate(newTitleTokens, newBodyTokens, i) }))
499-
.sort((a,b)=>b.score-a.score).slice(0, MAX_EMBED_ISSUES);
499+
.sort((a, b) => b.score - a.score).slice(0, MAX_EMBED_ISSUES);
500500
let best = null;
501501
if (newEmbedding) {
502502
for (const candidate of scored) {
503503
let emb; try { emb = await getEmbeddingSafe(embeddingModel, `${candidate.issue.title}\n${candidate.issue.body}`); } catch { continue; }
504504
const cosine = cosineSimilarity(newEmbedding, emb);
505505
// Semantic match is weighted much higher here for better accuracy based on content
506-
const combined = (cosine * 0.85) + (candidate.score * 0.15);
506+
const combined = (cosine * 0.85) + (candidate.score * 0.15);
507507
if (!best || combined > best.score) best = { ...candidate.issue, score: combined };
508508
}
509509
// Higher threshold for semantic duplicate since it's based on content vectors
510-
if (best && best.score >= 0.78) return best;
510+
if (best && best.score >= 0.78) return best;
511511
}
512-
512+
513513
// Fallback: If no embedding model or no strong semantic match, use LLM for refinement on best lexical match
514514
const lexicalBest = scored[0];
515515
if (lexicalBest && lexicalBest.score >= 0.5) {
@@ -531,15 +531,15 @@ async function getEmbeddingSafe(embeddingModel, text) {
531531
return vector;
532532
}
533533

534-
function cosineSimilarity(a,b){
535-
if(!a||!b||a.length!==b.length) return 0;
536-
let dot=0; let na=0; let nb=0;
537-
for(let i=0;i<a.length;i++){
538-
dot += a[i]*b[i];
539-
na += a[i]*a[i];
540-
nb += b[i]*b[i];
534+
function cosineSimilarity(a, b) {
535+
if (!a || !b || a.length !== b.length) return 0;
536+
let dot = 0; let na = 0; let nb = 0;
537+
for (let i = 0; i < a.length; i++) {
538+
dot += a[i] * b[i];
539+
na += a[i] * a[i];
540+
nb += b[i] * b[i];
541541
}
542-
return dot/((Math.sqrt(na)*Math.sqrt(nb))||1);
542+
return dot / ((Math.sqrt(na) * Math.sqrt(nb)) || 1);
543543
}
544544
/**
545545
* Scan the repository for lines possibly related to an issue by keyword intersection.
@@ -598,7 +598,7 @@ async function ensureLabel(octokit, owner, repo, name, meta) {
598598
async function run() {
599599
try {
600600
const octokit = getOctokit(process.env.GITHUB_TOKEN);
601-
601+
602602
// Initialize SAP AI Core SDK client
603603
// The SDK will automatically read AICORE_SERVICE_KEY from environment
604604
const aiClient = new OrchestrationClient({
@@ -608,9 +608,14 @@ async function run() {
608608
max_tokens: 4096,
609609
temperature: 0.7
610610
}
611+
},
612+
templating: {
613+
template: [
614+
{ role: 'user', content: '{{?content}}' }
615+
]
611616
}
612617
});
613-
618+
614619
const { owner, repo } = context.repo;
615620

616621
// Determine the number based on the event payload (only issue number is available from comment event)
@@ -629,33 +634,33 @@ async function run() {
629634

630635
if (context.payload.issue.pull_request) {
631636
// This is a comment on a Pull Request (PR)
632-
637+
633638
// CRITICAL FIX: Fetch the full PR object for use in subsequent functions (labels, head.ref)
634639
const { data: pullRequest } = await octokit.rest.pulls.get({
635640
owner,
636641
repo,
637642
pull_number: number,
638643
});
639644
context.payload.pull_request = pullRequest; // Attach full PR object to context
640-
645+
641646
// 1. Check for explicit review command
642647
if (commentBody.includes('review this pr') || commentBody.includes('gemini review') || commentBody.includes('ai review')) {
643648
console.log(`Explicit review command detected on PR #${number}. Initiating full review.`);
644-
649+
645650
const diffContent = await getDiff(octokit, owner, repo, number);
646651
await performPRReview(octokit, diffContent, number, aiClient);
647-
648-
// 2. Check for general "Hey AI" question
649-
} else if (commentBody.startsWith("hey gemini,") || commentBody.startsWith("hey ai,")) {
652+
653+
// 2. Check for general "Hey AI" question
654+
} else if (commentBody.startsWith("hey gemini,") || commentBody.startsWith("hey ai,")) {
650655
console.log(`"Hey AI," question detected on PR #${number}. Initiating response.`);
651656
await handleCommentResponse(octokit, context.payload.comment.body, number, aiClient);
652657
} else {
653658
console.log(`Comment on PR #${number} did not contain a review or question command. No action taken.`);
654659
}
655-
660+
656661
} else {
657662
// This is a comment on a regular Issue
658-
if (commentBody === 'confirm remediation') {
663+
if (commentBody === 'confirm remediation') {
659664
// Maintainer confirmation flow
660665
const issueLabels = context.payload.issue.labels.map(l => l.name);
661666
if (issueLabels.includes('awaiting-confirmation')) {
@@ -664,8 +669,8 @@ async function run() {
664669
await octokit.rest.issues.addLabels({ owner, repo, issue_number: number, labels: ['remediation-approved'] });
665670
// Remove awaiting-confirmation label
666671
const remaining = issueLabels.filter(l => l !== 'awaiting-confirmation');
667-
try { await octokit.rest.issues.removeLabel({ owner, repo, issue_number: number, name: 'awaiting-confirmation' }); } catch {}
668-
await octokit.rest.issues.createComment({ owner, repo, issue_number: number, body: '✅ Remediation confirmed. Automated follow-up actions may proceed (none implemented yet).'});
672+
try { await octokit.rest.issues.removeLabel({ owner, repo, issue_number: number, name: 'awaiting-confirmation' }); } catch { }
673+
await octokit.rest.issues.createComment({ owner, repo, issue_number: number, body: '✅ Remediation confirmed. Automated follow-up actions may proceed (none implemented yet).' });
669674
} else {
670675
console.log('Confirmation comment received but issue not in awaiting-confirmation state.');
671676
}
@@ -676,7 +681,7 @@ async function run() {
676681
await handleNewIssue(octokit, owner, repo, number, issueTitle, issueBody, aiClient); // Re-run with fresh model pass
677682
} else if (commentBody === 'discard recommendations') {
678683
console.log('Discard recommendations requested.');
679-
try { await octokit.rest.issues.removeLabel({ owner, repo, issue_number: number, name: 'awaiting-confirmation' }); } catch {}
684+
try { await octokit.rest.issues.removeLabel({ owner, repo, issue_number: number, name: 'awaiting-confirmation' }); } catch { }
680685
await octokit.rest.issues.createComment({ owner, repo, issue_number: number, body: '🗑️ Recommendations discarded. Provide new details or ask for re-analysis if needed.' });
681686
} else if (commentBody.startsWith("hey gemini,") || commentBody.startsWith("hey ai,")) {
682687
console.log(`"Hey AI," comment detected on Issue #${number}. Initiating response.`);
@@ -685,7 +690,7 @@ async function run() {
685690
console.log(`Comment on Issue #${number} did not contain a question command. No action taken.`);
686691
}
687692
}
688-
693+
689694
} else if (context.eventName === 'issues' && context.payload.action === 'opened') {
690695
// New Issue Handling
691696
console.log(`New Issue event detected for #${number}. Generating summary.`);

0 commit comments

Comments
 (0)