Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d2c43ec
feat(visualizer): add zero-latency static analysis suite
Sidhant0707 May 29, 2026
5f61b5a
refactor(analyze): decompose page.tsx into modular hooks and UI compo…
Sidhant0707 May 29, 2026
5919ceb
Merge branch 'main' into feat/static-analysis
Sidhant0707 May 29, 2026
eaf7813
fix: restore clean refactored page.tsx, remove merge pollution
Sidhant0707 May 29, 2026
5f9c558
feat(visualizer): implement multi-source reverse BFS for PR blast radius
Sidhant0707 May 29, 2026
148e87e
fix: restore missing AiGate import in DoctorPanel
Sidhant0707 May 29, 2026
d16e2db
merge: sync with main, keep feat/pr-blast-radius changes
Sidhant0707 May 29, 2026
aad4d37
feat(analysis): implement pure DSA architecture insights and interact…
Sidhant0707 May 29, 2026
a9b3931
fix: resolve merge conflicts with main — keep pagerank feature, add c…
Sidhant0707 May 29, 2026
ada8623
feat: add Lemon Squeezy payment integration
Sidhant0707 May 30, 2026
726d72c
fix: replace auth-helpers-nextjs with supabase/ssr in checkout route
Sidhant0707 May 30, 2026
ed52cb4
fix: use correct SUPABASE_SERVICE_KEY env var in webhook
Sidhant0707 May 30, 2026
f88ae31
feat: wire up Specialist tier on pricing page
Sidhant0707 May 30, 2026
bee687b
fix: update free tier limit from 10 to 5 autopsies per day
Sidhant0707 May 30, 2026
f7ffc83
fix: resolve merge conflict in DoctorPanel
Sidhant0707 May 30, 2026
099023f
feat: update pricing constants - Specialist tier live at ₹99/mo
Sidhant0707 May 30, 2026
9abefa0
feat: reduce token usage and add deepseek-r1 for pro chat tier
Sidhant0707 May 30, 2026
679503e
fix: add missing isPro and diagnosticCount props to DebugInterface in…
Sidhant0707 May 30, 2026
4e9b6d6
Merge branch 'main' into feat/token-optimization
Sidhant0707 May 30, 2026
a4d341b
feat: gate Auto-Patch behind pro tier in RiskDashboard
Sidhant0707 May 30, 2026
840c63a
fix: replace amber pro accents with project-native slate/white palette
Sidhant0707 May 30, 2026
7df0a03
Merge branch 'main' into feat/token-optimization
Sidhant0707 May 30, 2026
fc17a29
fix: replace amber pro accents with project-native slate/white palette
Sidhant0707 May 30, 2026
a0b35d4
refactor(debug): optimise engine pipeline
Sidhant0707 May 30, 2026
6b7892c
refactor(arch-insights): fix AP algorithm, types, tests, and Pro gate UI
Sidhant0707 May 30, 2026
3171587
The graph computation layer — wherever computePageRank / findSCCs live
Sidhant0707 May 30, 2026
0001c25
fix(blueprint-map): compute PageRank internally when scores not passe…
Sidhant0707 May 31, 2026
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
200 changes: 143 additions & 57 deletions app/api/debug/analyze/route.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,54 @@
// app/api/debug/analyze/route.ts

import { NextRequest, NextResponse } from "next/server";
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { parseStackTrace, extractErrorInfo } from "@/lib/debug/stack-parser";
import { traverseFromCrash } from "@/lib/debug/graph-traversal";
import { analyzeDebugWithGemini } from "@/lib/debug/gemini-debug";
import { analyzeDebugWithGemini } from "@/lib/debug/groq-debug";
import { highlightDebugPath } from "@/lib/debug/mermaid-highlighter";
import { fetchMissingFiles, extractLineContext } from "@/lib/debug/file-fetcher";
import { getCachedDebug, cacheDebug, hashStackTrace } from "@/lib/debug/cache";
import { applyDebugHeuristics, calculateConfidence, requiresRuntimeCheck } from "@/lib/debug/heuristics";
import { parseRepoUrl } from "@/lib/github";

// ─── Constants ───────────────────────────────────────────────────────────────

const DAILY_LIMIT = 3;
const MAX_TRAVERSAL_NODES: Record<string, number> = {
TypeError: 5,
ReferenceError: 5,
SyntaxError: 3,
RangeError: 4,
default: 10,
};

// ─── Helpers ─────────────────────────────────────────────────────────────────

function getMaxNodes(errorType: string): number {
return MAX_TRAVERSAL_NODES[errorType] ?? MAX_TRAVERSAL_NODES.default;
}

function stripComments(content: string): string {
return content
.split("\n")
.map((line) => line.trim())
.filter(
(line) =>
line.length > 0 &&
!line.startsWith("//") &&
!line.startsWith("/*") &&
!line.startsWith("*")
)
.join("\n");
}

// ─── Route Handler ───────────────────────────────────────────────────────────

export async function POST(req: NextRequest) {
try {
const cookieStore = await cookies();

const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
Expand All @@ -25,55 +61,100 @@ export async function POST(req: NextRequest) {
}
);

let userId = undefined;
let providerToken = undefined;
// ── 1. Auth ───────────────────────────────────────────────────────────────
let userId: string | undefined;
let providerToken: string | undefined;

try {
const { data: { user } } = await supabase.auth.getUser();
const {
data: { user },
} = await supabase.auth.getUser();
userId = user?.id;

const { data: { session } } = await supabase.auth.getSession();

const {
data: { session },
} = await supabase.auth.getSession();
providerToken = session?.provider_token ?? undefined;
} catch {

if (!providerToken) {
console.warn(
"[debug/analyze] No provider token — GitHub file fetching will use unauthenticated API (60 req/hr limit)."
);
}
} catch (authErr) {
console.warn(
"[debug/analyze] Auth fetch failed, continuing as guest:",
authErr
);
}

// ── 2. Input validation ───────────────────────────────────────────────────
const body = await req.json();

const { repoUrl, stackTrace } = body;

if (!repoUrl || !stackTrace || stackTrace.trim() === "") {
return NextResponse.json(
{ error: `Missing data. Received repoUrl: ${!!repoUrl}, stackTrace: ${!!stackTrace}` },
{
error: `Missing data. Received repoUrl: ${!!repoUrl}, stackTrace: ${!!stackTrace}`,
},
{ status: 400 }
);
}

// ── 3. Validate repo URL before any DB/cache hits ─────────────────────────
const parsed = parseRepoUrl(repoUrl);
if (!parsed) {
return NextResponse.json(
{ error: "Invalid GitHub URL" },
{ status: 400 }
);
}

const { owner, repo } = parsed;

// ── 4. Rate limiting ──────────────────────────────────────────────────────
if (userId) {
const today = new Date().toISOString().split("T")[0];

const { count, error: countError } = await supabase
.from("debug_analyses")
.select("*", { count: "exact", head: true })
.eq("user_id", userId)
.gte("created_at", `${today}T00:00:00.000Z`);

if (countError) {
console.error("[debug/analyze] Rate limit check failed:", countError);
} else if ((count ?? 0) >= DAILY_LIMIT) {
return NextResponse.json(
{ error: `Daily limit of ${DAILY_LIMIT} diagnoses reached. Try again tomorrow.` },
{ status: 429 }
);
}
}

// ── 5. Extract error info ─────────────────────────────────────────────────
const { error_type, error_message } = extractErrorInfo(stackTrace);

// ── 6. Cache check ────────────────────────────────────────────────────────
const traceHash = hashStackTrace(stackTrace);
const cached = await getCachedDebug(repoUrl, traceHash);

if (cached) {
return NextResponse.json({ ...cached, cached: true });
}

const parsed = parseRepoUrl(repoUrl);
if (!parsed) {
return NextResponse.json({ error: "Invalid GitHub URL" }, { status: 400 });
}

const { owner, repo } = parsed;

// ── 7. Fetch latest analysis from DB (dynamic version) ────────────────────
const { data: analysis, error: dbError } = await supabase
.from("analyses")
.select("*")
.eq("repo_url", repoUrl)
.eq("analysis_version", 3)
.order("analysis_version", { ascending: false })
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();

if (dbError) {
console.error("[debug/analyze] DB fetch failed:", dbError);
return NextResponse.json(
{ error: "Failed to fetch analysis from database" },
{ status: 500 }
Expand All @@ -90,28 +171,30 @@ export async function POST(req: NextRequest) {
const { result_json } = analysis;
const { dependencyGraph, fanIn, mermaidDiagram } = result_json;

// ── 8. Parse stack trace ──────────────────────────────────────────────────
const allFiles = Object.keys(dependencyGraph);

const crashNode = parseStackTrace(stackTrace, allFiles);

if (!crashNode) {
return NextResponse.json(
{ error: "Could not extract crash location from stack trace. Make sure the file exists in the analyzed repository." },
{
error:
"Could not extract crash location from stack trace. Make sure the file exists in the analyzed repository.",
},
{ status: 400 }
);
}

let traversalPath = traverseFromCrash(
crashNode.file,
dependencyGraph,
fanIn
);

// ── 9. Graph traversal + heuristics ───────────────────────────────────────
let traversalPath = traverseFromCrash(crashNode.file, dependencyGraph, fanIn);
traversalPath = applyDebugHeuristics(traversalPath, error_type);

const fileContents = result_json.fileContents || [];
// ── 10. Fetch file contents ───────────────────────────────────────────────
const fileContents: { path: string; content: string }[] =
result_json.fileContents || [];

const existingContents = new Map<string, string>(
fileContents.map((f: { path: string; content: string }) => [f.path, f.content])
fileContents.map((f) => [f.path, f.content])
);

const allContents = await fetchMissingFiles(
Expand All @@ -122,41 +205,33 @@ export async function POST(req: NextRequest) {
providerToken
);

const relevantCode = traversalPath.slice(0, 10).map((node) => {
// ── 11. Build relevant code snapshot ─────────────────────────────────────
const maxNodes = getMaxNodes(error_type);

const relevantCode = traversalPath.slice(0, maxNodes).map((node) => {
const content = allContents.get(node.file) || "";
const is_crash_site = node.file === crashNode.file;

let finalContent = "";
if (is_crash_site) {
finalContent = extractLineContext(content, crashNode.line).snippet;
} else {
finalContent = content
.split("\n")
.map(line => line.trim())
.filter(line =>
line.length > 0 &&
!line.startsWith("//") &&
!line.startsWith("/*") &&
!line.startsWith("*")
)
.slice(0, 300)
.join("\n");
}
const isCrashSite = node.file === crashNode.file;

let line_context = undefined;
if (is_crash_site) {
const ctx = extractLineContext(content, crashNode.line);
line_context = { start: ctx.start, end: ctx.end };
}
const finalContent = isCrashSite
? extractLineContext(content, crashNode.line).snippet
: stripComments(content).split("\n").slice(0, 300).join("\n");

const lineContext = isCrashSite
? (() => {
const ctx = extractLineContext(content, crashNode.line);
return { start: ctx.start, end: ctx.end };
})()
: undefined;

return {
file: node.file,
content: finalContent,
is_crash_site,
line_context,
is_crash_site: isCrashSite,
line_context: lineContext,
};
});

// ── 12. AI analysis ───────────────────────────────────────────────────────
const debugInput = {
error_type,
error_message,
Expand All @@ -172,18 +247,23 @@ export async function POST(req: NextRequest) {

const debugResult = await analyzeDebugWithGemini(debugInput);

// ── 13. Post-processing ───────────────────────────────────────────────────
const confidence = calculateConfidence(traversalPath);
const requires_runtime = requiresRuntimeCheck(error_type, error_message);

const suspectedRootCause = traversalPath.find(n => n.relationship === "upstream")?.file;
const suspectedRootCause = traversalPath.find(
(n) => n.relationship === "upstream"
)?.file;

const highlightedMermaid = highlightDebugPath(
mermaidDiagram,
crashNode.file,
traversalPath,
suspectedRootCause
);

const { data: stored } = await supabase
// ── 14. Persist to DB ─────────────────────────────────────────────────────
const { data: stored, error: insertError } = await supabase
.from("debug_analyses")
.insert({
analysis_id: analysis.id,
Expand All @@ -201,14 +281,19 @@ export async function POST(req: NextRequest) {
.select()
.maybeSingle();

if (insertError) {
console.error("[debug/analyze] Failed to store debug result:", insertError);
}

// ── 15. Cache + respond ───────────────────────────────────────────────────
const finalResult = {
debug_id: stored?.id || "unknown",
crash_node: crashNode,
traversal_path: traversalPath,
root_cause_hypothesis: debugResult.root_cause_hypothesis,
fix_suggestions: debugResult.fix_suggestions,
verification_steps: debugResult.verification_steps,
confidence: confidence,
confidence,
requires_runtime_check: requires_runtime,
highlighted_mermaid: highlightedMermaid,
};
Expand All @@ -218,6 +303,7 @@ export async function POST(req: NextRequest) {
return NextResponse.json(finalResult);
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
console.error("[debug/analyze] Unhandled error:", message);
return NextResponse.json({ error: message }, { status: 500 });
}
}
Loading
Loading