Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
88c979f
merge: integrate remote main with nerve-center pagerank fix
Sidhant0707 May 31, 2026
ad7f486
merge: use fix/nerve-center-pagerank version of ArchitectureMap
Sidhant0707 May 31, 2026
21969b1
fix: always insert new row for logged-in users on re-analysis
Sidhant0707 May 31, 2026
8319650
perf: reduce groq token spend with caching and model switch
Sidhant0707 May 31, 2026
0f89444
feat: integrate lemon squeezy, semantic caching, betweenness centrali…
Sidhant0707 Jun 1, 2026
31ccefd
seo: og-image, sitemap, robots.txt, schema markup
Sidhant0707 Jun 7, 2026
714cfb3
seo: add google search console verification
Sidhant0707 Jun 7, 2026
e9cfb04
changed pricing section
Sidhant0707 Jun 15, 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
1 change: 1 addition & 0 deletions app/analyze/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const AnalyzeContent = memo(() => {
mapView={mapView}
onMapViewChange={setMapView}
prChangedFiles={prChangedFiles}
betweennessScores={data.betweennessScores ?? {}}
/>
)}

Expand Down
89 changes: 86 additions & 3 deletions app/api/ai/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function POST(req: NextRequest) {
// ── 4. Parse body ─────────────────────────────────────────────────────────
const body = await req.json();
const {
repoUrl, // ← used for cache lookup
repoName,
description,
entryPoints,
Expand All @@ -68,7 +69,45 @@ export async function POST(req: NextRequest) {
healthMetrics,
} = body;

// ── 5. Stream AI response ─────────────────────────────────────────────────
// ── 5. Cache check — skip Groq entirely if AI was already run for this repo
if (repoUrl) {
const { data: cached } = await supabase
.from("analyses")
.select("ai_response")
.eq("repo_url", repoUrl)
.eq("user_id", user.id)
.not("ai_response", "is", null)
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();

if (cached?.ai_response) {
console.log("[ai] Cache hit — returning stored response, 0 tokens spent");
const encoder = new TextEncoder();
const cachedStream = new ReadableStream({
start(controller) {
controller.enqueue(
encoder.encode(
typeof cached.ai_response === "string"
? cached.ai_response
: JSON.stringify(cached.ai_response),
),
);
controller.close();
},
});

return new Response(cachedStream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
}
}

// ── 6. Stream AI response ─────────────────────────────────────────────────
const responseStream = await streamAnalyzeWithGemini(
repoName,
description,
Expand All @@ -79,13 +118,57 @@ export async function POST(req: NextRequest) {
healthMetrics,
);

// ── 6. Record usage after stream is kicked off ────────────────────────────
// ── 7. Tee the stream: client gets it live, we accumulate to cache ────────
const [clientStream, storeStream] = responseStream.body!.tee();

// Background: accumulate full response and store in analyses row
if (repoUrl) {
(async () => {
const reader = storeStream.getReader();
const decoder = new TextDecoder();
const chunks: string[] = [];

while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(decoder.decode(value, { stream: true }));
}

const fullResponse = chunks.join("");

try {
const { error } = await supabase
.from("analyses")
.update({ ai_response: fullResponse })
.eq("repo_url", repoUrl)
.eq("user_id", user.id)
.order("created_at", { ascending: false })
.limit(1);

if (error) console.error("[ai] Cache store failed:", error.message);
else console.log("[ai] AI response cached for future calls");
} catch (err) {
console.error("[ai] Cache store threw:", err);
}
})();
} else {
// No repoUrl — drain the tee'd branch so it doesn't block
storeStream.cancel();
}

// ── 8. Record usage after stream is kicked off ────────────────────────────
// Non-blocking — do not await, never fail the response
insertAiUsage(supabase, user.id, repoName ?? "unknown").catch((err) =>
console.error("[ai] Usage insert failed:", err),
);

return responseStream;
return new Response(clientStream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
} catch (error) {
console.error("AI Streaming Error:", error);
return NextResponse.json(
Expand Down
2 changes: 1 addition & 1 deletion app/api/analyze-pr/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Analyze these code changes and return ONLY a valid JSON object with EXACTLY this
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: "llama-3.3-70b-versatile",
model: "llama-3.1-8b-instant",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: "Analyze this PR and return ONLY the required JSON." },
Expand Down
41 changes: 39 additions & 2 deletions app/api/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "@/lib/pipeline/ast-pipeline";
import { GitHubAuthError } from "@/lib/github";

const ANALYSIS_VERSION = 10;
const ANALYSIS_VERSION = 14;

// ── SafeStream ────────────────────────────────────────────────────────────────
class SafeStream {
Expand Down Expand Up @@ -220,6 +220,7 @@ export async function POST(req: NextRequest) {
.eq("repo_url", repoUrl)
.eq("commit_sha", commitSha)
.eq("analysis_version", ANALYSIS_VERSION)
.eq("user_id", userId ?? "")
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
Expand All @@ -233,7 +234,11 @@ export async function POST(req: NextRequest) {
},
});

if (!isLocal && !result.cached) {
// ── Always insert a new row for logged-in users so the dashboard
// reflects every analysis, even re-runs of cached repos.
// For cached results, re-use the cached result_json so no
// re-processing is needed but the timestamp is always fresh.
if (!isLocal && userId) {
try {
const { error: insertError } = await supabase.from("analyses").insert({
repo_url: repoUrl,
Expand All @@ -250,6 +255,38 @@ export async function POST(req: NextRequest) {
console.error("[analyze] DB insert threw:", err);
}

// Only run RAG storage for fresh (non-cached) analyses
if (!result.cached) {
try {
await processAndStoreCodebase(
supabase,
repoUrl!,
result.fileContents ?? [],
);
} catch (err) {
console.error("[analyze] RAG storage failed:", err);
}
}
}

// For anonymous users, only insert if result is fresh (original behavior)
if (!isLocal && !userId && !result.cached) {
try {
const { error: insertError } = await supabase.from("analyses").insert({
repo_url: repoUrl,
repo_name: `${result.owner}/${result.repo}`.toLowerCase(),
commit_sha: result.branch,
analysis_version: ANALYSIS_VERSION,
result_json: result,
user_id: null,
});
if (insertError) {
console.error("[analyze] DB insert error (anon):", insertError.message);
}
} catch (err) {
console.error("[analyze] DB insert threw (anon):", err);
}

try {
await processAndStoreCodebase(
supabase,
Expand Down
2 changes: 1 addition & 1 deletion app/api/generate-test/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function POST(req: NextRequest) {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "llama-3.3-70b-versatile",
model: "llama-3.1-8b-instant",
messages: [{ role: "user", content: prompt }],
response_format: { type: "json_object" },
temperature: 0.1,
Expand Down
2 changes: 1 addition & 1 deletion app/api/v1/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ratelimitApiKey } from "@/lib/ratelimit";
import { parseRepoUrl, GitHubAuthError } from "@/lib/github";
import { runAstPipeline, PipelineError } from "@/lib/pipeline/ast-pipeline";

const ANALYSIS_VERSION = 10;
const ANALYSIS_VERSION = 14;

const PIPELINE_ERROR_STATUS: Record<string, number> = {
INVALID_REPO_URL: 400,
Expand Down
45 changes: 39 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Analytics } from "@vercel/analytics/next";
import { Analytics } from "@vercel/analytics/next";
import "./globals.css";

const geistSans = Geist({
Expand All @@ -15,11 +15,16 @@ const geistMono = Geist_Mono({

export const metadata: Metadata = {
title: "CodeAutopsy | AI-Powered Codebase Visualization",
description: "Stop wrestling with unfamiliar code. CodeAutopsy analyzes GitHub repositories to provide visual architecture, execution flows, and AI-driven insights in minutes.",
description:
"Stop wrestling with unfamiliar code. CodeAutopsy analyzes GitHub repositories to provide visual architecture, execution flows, and AI-driven insights in minutes.",
metadataBase: new URL("https://codeautopsy-lyart.vercel.app"),
verification: {
google: "c00IopCIPszhIWL0_m1d65gf9IbpT_CUR0gTQnafufQ",
},
openGraph: {
title: "CodeAutopsy | Dissect Any Codebase",
description: "AI-powered architecture maps and execution flows for any GitHub repository. Understand code faster.",
description:
"AI-powered architecture maps and execution flows for any GitHub repository. Understand code faster.",
url: "https://codeautopsy-lyart.vercel.app",
siteName: "CodeAutopsy",
images: [
Expand All @@ -36,7 +41,8 @@ export const metadata: Metadata = {
twitter: {
card: "summary_large_image",
title: "CodeAutopsy | Dissect Any Codebase",
description: "Understand any codebase in minutes. AI-powered visual architecture and execution flows.",
description:
"Understand any codebase in minutes. AI-powered visual architecture and execution flows.",
creator: "@Sidhant07",
images: ["/og-image.png"],
},
Expand All @@ -52,10 +58,37 @@ export default function RootLayout({
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "CodeAutopsy",
description:
"AI-powered GitHub repository analyzer. Visualize dependencies, detect fragile points, and understand codebases faster.",
applicationCategory: "DeveloperApplication",
operatingSystem: "Web",
url: "https://codeautopsy-lyart.vercel.app",
offers: {
"@type": "Offer",
price: "0",
priceCurrency: "USD",
},
author: {
"@type": "Person",
name: "Sidhant Kumar",
url: "https://sidcore.in",
},
}),
}}
/>
</head>
<body className="min-h-full flex flex-col">
{children}
<Analytics />
<Analytics />
</body>
</html>
);
}
}
9 changes: 5 additions & 4 deletions app/pricing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// app/pricing/page.tsx
"use client";

import React, { useState } from "react";
Expand Down Expand Up @@ -93,7 +94,7 @@ export default function PricingPage() {
</div>
<Link
href="/"
className="cursor-pointer block text-center py-3 rounded-xl bg-white text-black font-bold text-xs uppercase tracking-widest hover:scale-105 transition-transform active:scale-95"
className="cursor-pointer block text-center py-3 rounded-xl bg-white/10 text-slate-300 border border-white/10 font-bold text-xs uppercase tracking-widest hover:bg-white/15 hover:text-white transition-all active:scale-95"
>
Get Started
</Link>
Expand All @@ -116,7 +117,7 @@ export default function PricingPage() {
Specialist
</h3>
<div className="flex items-baseline gap-1 mb-1">
<span className="text-4xl font-black text-white">₹99</span>
<span className="text-4xl font-black text-white">$2</span>
<span className="text-slate-500 text-sm">/mo</span>
</div>
<p className="text-amber-400/70 text-xs mb-8 font-bold uppercase tracking-widest">
Expand Down Expand Up @@ -147,7 +148,7 @@ export default function PricingPage() {
whileTap={{ scale: 0.97 }}
onClick={handleUpgrade}
disabled={loading}
className="w-full py-3 rounded-xl bg-white text-black font-bold text-xs uppercase tracking-widest hover:scale-105 transition-transform active:scale-95 disabled:opacity-60 disabled:cursor-not-allowed disabled:scale-100"
className="w-full py-3 rounded-xl bg-amber-400 text-black font-bold text-xs uppercase tracking-widest hover:bg-amber-300 transition-colors active:scale-95 disabled:opacity-60 disabled:cursor-not-allowed disabled:scale-100"
>
{loading ? "Redirecting..." : "Upgrade to Specialist →"}
</motion.button>
Expand All @@ -161,7 +162,7 @@ export default function PricingPage() {
className="glass-card p-8 rounded-3xl border border-white/5 opacity-60 relative cursor-not-allowed flex flex-col justify-between bg-[#141414]"
>
<div className="absolute top-4 right-4 px-2 py-1 rounded bg-slate-800 border border-white/10">
<span className="text-[9px] font-black text-white uppercase tracking-widest">
<span className="text-[9px] font-black text-slate-400 uppercase tracking-widest">
Coming Soon
</span>
</div>
Expand Down
Loading
Loading