Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Server/app/worker-service/services/rag.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,24 @@ export async function vectorDBExists(DBName: string): Promise<boolean> {
return indexes?.some((idx) => idx.name === DBName) ?? false;
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export async function waitForVectorIndex(
indexName: string,
options: { timeoutMs?: number; intervalMs?: number } = {},
): Promise<boolean> {
const { timeoutMs = 10 * 60 * 1000, intervalMs = 5000 } = options;
const deadline = Date.now() + timeoutMs;

while (Date.now() < deadline) {
if (await vectorDBExists(indexName)) return true;
console.log(`Waiting for vector index "${indexName}"...`);
await sleep(intervalMs);
}

return false;
}

export async function runRAGPipeline(data: object) {
const { installationId, owner, repo } = data as RAGPipelineInput;

Expand Down
53 changes: 29 additions & 24 deletions Server/app/worker-service/services/review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ import {
import { db } from "../../../package/db/prisma.js";
import {
vectorDBExists,
createEmbeddings,
saveToVectorDB,
searchRelevantEmbeddings,
waitForVectorIndex,
retrieveContextForDiff,
toIndexName,
} from "./rag.service.js";
import { findGIF } from "./gif.service.js";
Expand Down Expand Up @@ -137,17 +136,6 @@ async function resolveUserId(

export async function runPRReview(data: PRReviewJobData) {
const { installationId, owner, repo, prNumber, prTitle, prAuthor } = data;
const dbName = "";

const createVectorDB = (await vectorDBExists(dbName)) as boolean;

if (createVectorDB) {
const values = {};

const embeddings = (await createEmbeddings(values)) as any;
const finalSaved = await saveToVectorDB(embeddings);
console.log(finalSaved);
}

const octokit = await getOctokit(installationId);
const userId = await resolveUserId(owner, repo, data.userId);
Expand Down Expand Up @@ -180,17 +168,34 @@ export async function runPRReview(data: PRReviewJobData) {
const rules = await getReviewRules(octokit, owner, repo, prNumber);
const reviewType = (await getRevieType(difference, apiKey, tokenUsage)) as any;
const indexName = toIndexName(owner, repo);
const DBExsists = await vectorDBExists(indexName);
let relevantCode: any;
let relevantCode: Awaited<ReturnType<typeof retrieveContextForDiff>> | undefined;
const processedDiff = (await getCodeDiff(difference)) as any;
if (reviewType === "feature" && DBExsists) {
const searchQuery = await generateRelevantSearchQuery(processedDiff, apiKey, tokenUsage);
const query = {
text: searchQuery,
indexName: indexName,
topK: 5,
};
relevantCode = await searchRelevantEmbeddings(query);

if (reviewType === "feature") {
const indexReady =
(await vectorDBExists(indexName)) ||
(await waitForVectorIndex(indexName));

if (indexReady) {
const searchQuery = await generateRelevantSearchQuery(
processedDiff,
apiKey,
tokenUsage,
);
relevantCode = await retrieveContextForDiff({
diff: difference,
semanticQuery: searchQuery,
indexName,
topK: 8,
});
console.log(
`RAG context for ${owner}/${repo}#${prNumber}: ${relevantCode.length} AST chunks`,
);
} else {
console.log(
`Vector index "${indexName}" not ready — reviewing without RAG context`,
);
}
}

const prompt = reviewPrompt(difference, rules, relevantCode);
Expand Down
10 changes: 7 additions & 3 deletions Server/app/worker-service/workers/review.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ export async function workersOn(){
const client = await createWorkerRedisClient()
console.log("webhook for PR is on")

while(true){
const gettingData = await client.brPop("reviewQueue", 0)
processPRReviewJob(JSON.parse(gettingData!.element))
while (true) {
const gettingData = await client.brPop("reviewQueue", 0);
try {
await processPRReviewJob(JSON.parse(gettingData!.element));
} catch (err) {
console.error("PR review job error:", err);
}
}
}
5 changes: 3 additions & 2 deletions Server/package/ai/deployment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Anthropic from "@anthropic-ai/sdk";
import { CLAUDE_MODEL } from "./models.js";

const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
});

export async function analyzeDeploymentLogs(logs: string, provider: string) {
const res = await anthropic.messages.create({
model: "claude-sonnet-4-6",
model: CLAUDE_MODEL,
max_tokens: 1500,
system: `You are a senior DevOps engineer analyzing CI/CD deployment failure logs.

Expand Down Expand Up @@ -44,7 +45,7 @@ Do NOT include markdown or text outside the JSON.`,

export async function generateDeploymentFix(fileContent: string, cause: string, fix: string, fileName: string) {
const res = await anthropic.messages.create({
model: "claude-sonnet-4-6",
model: CLAUDE_MODEL,
max_tokens: 4000,
system: `You are a senior software engineer. You will be given a file and a description of a bug causing a deployment failure. Apply the fix and return the complete corrected file content.

Expand Down
3 changes: 2 additions & 1 deletion Server/package/ai/gif.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Anthropic from "@anthropic-ai/sdk";
import { addUsage, createTokenAccumulator, type TokenAccumulator } from "./review.js";
import { CLAUDE_MODEL } from "./models.js";

export async function getGifName(
summary: string | null | undefined,
Expand All @@ -13,7 +14,7 @@ export async function getGifName(

const anthropic = new Anthropic({ apiKey });
const res = await anthropic.messages.create({
model: "claude-sonnet-4-5",
model: CLAUDE_MODEL,
max_tokens: 40,
temperature: 0,
system:
Expand Down
2 changes: 2 additions & 0 deletions Server/package/ai/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CLAUDE_MODEL =
process.env.CLAUDE_MODEL?.trim() || "claude-haiku-4-5";
59 changes: 45 additions & 14 deletions Server/package/ai/review.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Anthropic from "@anthropic-ai/sdk";
import { CLAUDE_MODEL } from "./models.js";

export type TokenAccumulator = {
inputTokens: number;
Expand All @@ -21,15 +22,16 @@ function createClient(apiKey: string) {

export async function getRevieType(diff: string, apiKey: string, usage?: TokenAccumulator) {
const anthropic = createClient(apiKey);
const prompt = `You are supposed to see the Code Diff ${diff} and identify the PR Type if it is a Bug Fix / New Feature / Code Update , etc and only return the name of the type in response . "Return ONLY: feature | bugfix | refactor"`;
const diffPreview = diff.slice(0, 3000);
const prompt = `Identify the PR type from this code diff snippet. Return ONLY one word: feature | bugfix | refactor\n\n${diffPreview}`;
const res = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1500,
model: CLAUDE_MODEL,
max_tokens: 16,
temperature: 0,
messages: [
{
role: "user",
content: prompt ? prompt : "",
content: prompt,
},
],
});
Expand All @@ -53,8 +55,8 @@ export async function generateRelevantSearchQuery(
) {
const anthropic = createClient(apiKey);
const res = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1500,
model: CLAUDE_MODEL,
max_tokens: 100,
temperature: 0,
system:
"Convert the given git diff into a short semantic search query to find relevant code in a repository Return ONLY a short phrase.",
Expand Down Expand Up @@ -97,8 +99,8 @@ export async function getAIReview(
async function _callAI(prompt: string | null, apiKey: string, usage?: TokenAccumulator) {
const anthropic = createClient(apiKey);
const res = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1500,
model: CLAUDE_MODEL,
max_tokens: 4096,
temperature: 0,
system: `You are a strict senior software engineer performing code reviews.

Expand Down Expand Up @@ -167,6 +169,10 @@ Do NOT include markdown, explanations, or text outside JSON.`,
});
addUsage(usage ?? createTokenAccumulator(), res.usage);

if (res.stop_reason === "max_tokens") {
console.warn("AI review response was truncated (max_tokens reached)");
}

const block = res.content.find((b) => b.type === "text");
if (!block || block.type !== "text")
throw new Error("No text block in AI response");
Expand Down Expand Up @@ -199,6 +205,8 @@ function formatRelevantCode(matches: RelevantCodeMatch[]): string {
.join("\n\n---\n\n")
}

const MAX_DIFF_CHARS = 80_000;

export function reviewPrompt(
diff: string,
rules: any,
Expand All @@ -209,6 +217,11 @@ export function reviewPrompt(
? `\nEXISTING CODEBASE CONTEXT (retrieved for this feature):\nUse this to check for consistency, duplication, or conflicts with existing patterns.\n\n${formatRelevantCode(relevantCode)}\n`
: "";

const truncatedDiff =
diff.length > MAX_DIFF_CHARS
? `${diff.slice(0, MAX_DIFF_CHARS)}\n\n[... diff truncated at ${MAX_DIFF_CHARS} chars ...]`
: diff;

return `
REPOSITORY RULES (HIGHEST PRIORITY):
${JSON.stringify(rules, null, 2)}
Expand All @@ -222,19 +235,37 @@ Check for:
- Maintainability (naming, duplication)
${relevantSection}
GIT DIFF:
${diff}
${truncatedDiff}

Analyze ONLY the changed code.

Return strictly valid JSON.
`;
}

function extractJsonObject(text: string): string | null {
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (fenced?.[1]) return fenced[1].trim();

const start = text.indexOf("{");
const end = text.lastIndexOf("}");
if (start !== -1 && end > start) return text.slice(start, end + 1);

return null;
}

export function parseAIResponse(text: string) {
try {
return JSON.parse(text);
} catch (err) {
console.log("❌ Failed to parse AI response");
return null;
const candidates = [text.trim(), extractJsonObject(text)].filter(Boolean) as string[];

for (const candidate of candidates) {
try {
return JSON.parse(candidate);
} catch {
// try next candidate
}
}

console.log("❌ Failed to parse AI response");
console.log("Response preview:", text.slice(0, 500));
return null;
}
Loading