KrishnaCosmic commited on
Commit
4f889a3
·
1 Parent(s): 4a0e5a2

apply changes

Browse files
src/app/api/triage/route.ts CHANGED
@@ -69,21 +69,64 @@ export async function POST(request: NextRequest) {
69
  });
70
  }
71
 
72
- // Build AI prompt for classification
73
- const prompt = `Analyze this GitHub issue and provide a classification.
 
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  Title: ${issue.title}
76
  Body: ${issue.body || "(empty)"}
 
 
 
 
77
 
78
  Respond with valid JSON only:
79
  {
80
  "classification": "BUG" | "CRITICAL_BUG" | "FEATURE_REQUEST" | "QUESTION" | "DOCS" | "DUPLICATE" | "NEEDS_INFO" | "SPAM",
81
  "sentiment": "POSITIVE" | "NEUTRAL" | "NEGATIVE" | "FRUSTRATED",
82
- "summary": "One-line summary of the issue",
83
- "suggestedLabel": "Suggested GitHub label (lowercase, hyphenated)"
 
84
  }`;
85
 
86
- // Call OpenRouter API
87
  const aiResponse = await fetch(
88
  "https://openrouter.ai/api/v1/chat/completions",
89
  {
@@ -93,9 +136,10 @@ Respond with valid JSON only:
93
  "Content-Type": "application/json",
94
  },
95
  body: JSON.stringify({
96
- model: "anthropic/claude-3-haiku",
97
- messages: [{ role: "user", content: prompt }],
98
  max_tokens: 500,
 
99
  }),
100
  }
101
  );
@@ -108,6 +152,7 @@ Respond with valid JSON only:
108
  let sentiment = "NEUTRAL";
109
  let summary = issue.title;
110
  let suggestedLabel = "needs-triage";
 
111
 
112
  try {
113
  const parsed = JSON.parse(aiContent);
@@ -119,11 +164,31 @@ Respond with valid JSON only:
119
  : "NEUTRAL";
120
  summary = parsed.summary || issue.title;
121
  suggestedLabel = parsed.suggestedLabel || "needs-triage";
 
122
  } catch {
123
  console.error("Failed to parse AI response:", aiContent);
124
  }
125
 
126
- // Save triage data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  const triage = {
128
  id: generateId(),
129
  issueId,
@@ -131,14 +196,50 @@ Respond with valid JSON only:
131
  summary,
132
  suggestedLabel,
133
  sentiment,
 
134
  analyzedAt: now(),
135
  };
136
 
137
  await db.insert(triageData).values(triage);
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  return NextResponse.json({
140
  message: "Issue triaged successfully",
141
  triage,
 
 
142
  });
143
  } catch (error) {
144
  console.error("POST /api/triage error:", error);
 
69
  });
70
  }
71
 
72
+ // ===== NEW: Get RAG context from project history =====
73
+ let ragContext = "";
74
+ let ragSources: any[] = [];
75
 
76
+ if (issue.repoName) {
77
+ try {
78
+ const aiEngineUrl = process.env.AI_ENGINE_URL || "http://localhost:7860";
79
+ const ragResponse = await fetch(`${aiEngineUrl}/rag/chat`, {
80
+ method: "POST",
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ "X-API-Key": process.env.API_KEY || "",
84
+ },
85
+ body: JSON.stringify({
86
+ question: `What is the context for this ${issue.isPR ? "pull request" : "issue"}: "${issue.title}"? ${issue.body?.slice(0, 500) || ""}`,
87
+ repo_name: issue.repoName,
88
+ top_k: 5,
89
+ }),
90
+ signal: AbortSignal.timeout(10000), // 10s timeout
91
+ });
92
+
93
+ if (ragResponse.ok) {
94
+ const ragData = await ragResponse.json();
95
+ ragContext = ragData.answer || "";
96
+ ragSources = ragData.sources || [];
97
+ console.log(`[Triage] RAG provided ${ragSources.length} sources for context`);
98
+ } else {
99
+ console.warn(`[Triage] RAG query failed with status ${ragResponse.status}`);
100
+ }
101
+ } catch (err) {
102
+ console.error("[Triage] RAG query error:", err);
103
+ // Continue without RAG context
104
+ }
105
+ }
106
+
107
+ // Build ENHANCED AI prompt with RAG context
108
+ const enhancedPrompt = `You are an expert GitHub ${issue.isPR ? "pull request" : "issue"} triaging assistant with access to project history and documentation.
109
+
110
+ ${ragContext ? `**Project Context from Documentation & Past Issues:**\n${ragContext}\n\n---\n\n` : ""}
111
+
112
+ **Current ${issue.isPR ? "Pull Request" : "Issue"} to Classify:**
113
  Title: ${issue.title}
114
  Body: ${issue.body || "(empty)"}
115
+ Author: ${issue.authorName || "unknown"}
116
+ Type: ${issue.isPR ? "Pull Request" : "Issue"}
117
+
118
+ Using the project context above (if available), provide a precise classification.
119
 
120
  Respond with valid JSON only:
121
  {
122
  "classification": "BUG" | "CRITICAL_BUG" | "FEATURE_REQUEST" | "QUESTION" | "DOCS" | "DUPLICATE" | "NEEDS_INFO" | "SPAM",
123
  "sentiment": "POSITIVE" | "NEUTRAL" | "NEGATIVE" | "FRUSTRATED",
124
+ "summary": "One-line summary explaining the classification",
125
+ "suggestedLabel": "Suggested GitHub label (lowercase, hyphenated)",
126
+ "confidence": 0.0-1.0
127
  }`;
128
 
129
+ // Call OpenRouter API with upgraded model
130
  const aiResponse = await fetch(
131
  "https://openrouter.ai/api/v1/chat/completions",
132
  {
 
136
  "Content-Type": "application/json",
137
  },
138
  body: JSON.stringify({
139
+ model: "meta-llama/llama-3.3-70b-instruct:free", // Upgraded from claude-haiku
140
+ messages: [{ role: "user", content: enhancedPrompt }],
141
  max_tokens: 500,
142
+ temperature: 0.3, // Lower temp for more deterministic classification
143
  }),
144
  }
145
  );
 
152
  let sentiment = "NEUTRAL";
153
  let summary = issue.title;
154
  let suggestedLabel = "needs-triage";
155
+ let confidence = 0.5;
156
 
157
  try {
158
  const parsed = JSON.parse(aiContent);
 
164
  : "NEUTRAL";
165
  summary = parsed.summary || issue.title;
166
  suggestedLabel = parsed.suggestedLabel || "needs-triage";
167
+ confidence = parsed.confidence || 0.5;
168
  } catch {
169
  console.error("Failed to parse AI response:", aiContent);
170
  }
171
 
172
+ // ===== NEW: Quality Assessment for PRs (estimate bug risk) =====
173
+ let bugRiskScore: number | null = null;
174
+
175
+ if (issue.isPR) {
176
+ // Estimate bug risk based on classification
177
+ // TODO: Ideally we'd use QualityAssessmentService with actual PR diff
178
+ // For now, use classification as a heuristic
179
+ if (classification === "CRITICAL_BUG") {
180
+ bugRiskScore = 9;
181
+ } else if (classification === "BUG") {
182
+ bugRiskScore = 6;
183
+ } else {
184
+ // Even non-bug PRs have some risk
185
+ bugRiskScore = Math.max(3, Math.floor((1 - confidence) * 5));
186
+ }
187
+
188
+ console.log(`[Triage] Assigned bug risk score: ${bugRiskScore} for PR #${issue.number}`);
189
+ }
190
+
191
+ // Save triage data with new fields
192
  const triage = {
193
  id: generateId(),
194
  issueId,
 
196
  summary,
197
  suggestedLabel,
198
  sentiment,
199
+ bugRiskScore, // NEW
200
  analyzedAt: now(),
201
  };
202
 
203
  await db.insert(triageData).values(triage);
204
 
205
+ // ===== NEW: Auto-dispatch Agent for CRITICAL_BUG =====
206
+ let agentDispatched = false;
207
+
208
+ if (classification === "CRITICAL_BUG" && user.githubAccessToken && issue.owner && issue.repo) {
209
+ console.log(`[Triage] CRITICAL_BUG detected, auto-dispatching Agent for ${issue.repoName}#${issue.number}`);
210
+
211
+ try {
212
+ // Fire-and-forget Agent analysis (don't await)
213
+ const baseUrl = process.env.NEXTAUTH_URL || "http://localhost:3001";
214
+ fetch(`${baseUrl}/api/agent/analyze`, {
215
+ method: "POST",
216
+ headers: {
217
+ "Content-Type": "application/json",
218
+ Authorization: request.headers.get("Authorization") || "",
219
+ },
220
+ body: JSON.stringify({
221
+ owner: issue.owner,
222
+ repo: issue.repo,
223
+ issueNumber: issue.isPR ? undefined : issue.number,
224
+ prNumber: issue.isPR ? issue.number : undefined,
225
+ goalType: issue.isPR ? "bug_hunt" : "issue_triage",
226
+ sessionId: `auto-${issueId}-${Date.now()}`,
227
+ }),
228
+ }).catch((err) => {
229
+ console.error("[Triage] Agent auto-dispatch failed:", err.message);
230
+ });
231
+
232
+ agentDispatched = true;
233
+ } catch (err) {
234
+ console.error("[Triage] Agent dispatch setup failed:", err);
235
+ }
236
+ }
237
+
238
  return NextResponse.json({
239
  message: "Issue triaged successfully",
240
  triage,
241
+ ragSources, // NEW: return sources used for transparency
242
+ agentDispatched, // NEW: indicate if Agent was triggered
243
  });
244
  } catch (error) {
245
  console.error("POST /api/triage error:", error);
src/lib/sync/github-sync.ts CHANGED
@@ -453,7 +453,57 @@ export async function syncSingleRepository(
453
  options: SyncOptions = {}
454
  ): Promise<SyncResult> {
455
  const octokit = new Octokit({ auth: accessToken });
456
- return syncRepository(octokit, repoId, owner, repo, options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  }
458
 
459
  // =============================================================================
 
453
  options: SyncOptions = {}
454
  ): Promise<SyncResult> {
455
  const octokit = new Octokit({ auth: accessToken });
456
+ const result = await syncRepository(octokit, repoId, owner, repo, options);
457
+
458
+ // ===== Auto-trigger RAG indexing after successful sync =====
459
+ if (result.success && (result.created > 0 || result.updated > 0)) {
460
+ try {
461
+ const aiEngineUrl = process.env.AI_ENGINE_URL || "http://localhost:7860";
462
+ const repoName = `${owner}/${repo}`;
463
+
464
+ // Check if we need to index (avoid redundant indexing)
465
+ const checkResponse = await fetch(
466
+ `${aiEngineUrl}/rag/check-index?repo_name=${encodeURIComponent(repoName)}`,
467
+ {
468
+ headers: {
469
+ "X-API-Key": process.env.API_KEY || "",
470
+ "Content-Type": "application/json"
471
+ }
472
+ }
473
+ );
474
+
475
+ if (checkResponse.ok) {
476
+ const checkData = await checkResponse.json();
477
+ const chunkCount = checkData.chunk_count || 0;
478
+
479
+ if (chunkCount < 100) {
480
+ console.log(`[Sync] Triggering RAG indexing for ${repoName} (current chunks: ${chunkCount})`);
481
+
482
+ // Fire-and-forget indexing (don't await to avoid blocking sync)
483
+ fetch(`${aiEngineUrl}/rag/index`, {
484
+ method: "POST",
485
+ headers: {
486
+ "Content-Type": "application/json",
487
+ "X-API-Key": process.env.API_KEY || "",
488
+ },
489
+ body: JSON.stringify({
490
+ repo_name: repoName,
491
+ github_access_token: accessToken,
492
+ }),
493
+ }).catch((err) => {
494
+ console.error(`[Sync] RAG indexing failed for ${repoName}:`, err.message);
495
+ });
496
+ } else {
497
+ console.log(`[Sync] Skipping RAG indexing for ${repoName}, already has ${chunkCount} chunks`);
498
+ }
499
+ }
500
+ } catch (err) {
501
+ // Log but don't fail the sync if RAG indexing check errors
502
+ console.error("[Sync] RAG index check failed:", err);
503
+ }
504
+ }
505
+
506
+ return result;
507
  }
508
 
509
  // =============================================================================