# Project export: GitLit

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: An AI powered GitHub assistant that reviews your codebase for security, design, and readability and even tests your APIs all in one place.
- Devpost: https://devpost.com/software/gitlit-twc6s1
- GitHub: https://github.com/Jacobshin04/CalHacks12.0
- Video: https://www.youtube.com/embed/qG_8-gutTYc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Jacobshin04 (6 commits), MauricioYeverino2003 (4 commits), Dan Wu (2 commits)

## Devpost submission (written by the team)

### Inspiration

We were inspired by how time consuming it is for developers to review code, check security, and test APIs across multiple tools. We wanted to create an all-in-one AI assistant that automatically analyzes any GitHub repo for code quality, design, and security while letting users test APIs in one place.

### What it does

Our web app lets users log in with GitHub, fetch any repository, and automatically analyze it using Claude AI and CodeRabbit for code readability, security, errors, and system design. It then gives an overall score and provides an integrated Postman-style API tester all in one intuitive dashboard.

### How we built it

We built our web app using Next.js and Node.js for the frontend and backend, designed the UI in Figma, and styled it with CSS for a clean, developer-friendly look. We integrated CodeRabbit to fetch GitHub code review comments, Claude AI to analyze readability, security, and system design, and Postman’s API tools to let users test endpoints directly within the app.

### Challenges we ran into

We ran into challenges integrating with the CodeRabbit API, since it required a specific setup and authentication flow we didn’t initially know about. The CodeRabbit IP address also didn’t work at first we even reached out to their team on Slack and met them in person to troubleshoot the issue. We also struggled to retrieve the bearer token for Claude AI while making API calls, all while racing against time to get everything working before the deadline.

### Accomplishments we're proud of

We’re proud of our teamwork, resilience, and collaboration throughout the hackathon. With the help of tools like Cursor and our determination to overcome every challenge, we managed to build a functional, AI-powered web app despite the technical hurdles and we’re proud of how far we’ve come as a team.

### What we learned

We learned the importance of teamwork, grit, and perseverance how to work hard, stay positive, and have fun even under pressure. Most importantly, we learned to never give up and always believe in ourselves no matter how tough the challenges get.

### What's next

Next, we plan to build a more complete and accurate grading system for analyzing codebases and improve the overall structure and UI to make the platform more engaging and user-friendly. We also aim to develop a structured database to track repo history and gather developer feedback and usage data to continuously improve GitLit into a smarter, more powerful AI development assistant.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 49 recognized source files, 249 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (55 of 55)

```
.DS_Store
calhacks12.0/.gitignore
calhacks12.0/API_TESTING_SETUP.md
calhacks12.0/app/api/analyze/endpoints/route.ts
calhacks12.0/app/api/auth/[...nextauth]/route.ts
calhacks12.0/app/api/auth/providers/route.ts
calhacks12.0/app/api/claude/audit/route.ts
calhacks12.0/app/api/claude/design/route.ts
calhacks12.0/app/api/claude/quality/route.ts
calhacks12.0/app/api/claude/rate/route.ts
calhacks12.0/app/api/codrabbit/review/route.ts
calhacks12.0/app/api/github/contents/route.ts
calhacks12.0/app/api/github/pulls/route.ts
calhacks12.0/app/api/github/repos/route.ts
calhacks12.0/app/api/postman/collection/route.ts
calhacks12.0/app/api/runner/docker/route.ts
calhacks12.0/app/api/runner/start/route.ts
calhacks12.0/app/api/runner/stop/route.ts
calhacks12.0/app/api/setup/env/route.ts
calhacks12.0/app/api/test/execute/route.ts
calhacks12.0/app/api/test/map-endpoints/route.ts
calhacks12.0/app/dashboard/page.tsx
calhacks12.0/app/globals.css
calhacks12.0/app/layout.tsx
calhacks12.0/app/page.tsx
calhacks12.0/app/review/[owner]/[repo]/analysis/page.tsx
calhacks12.0/app/review/[owner]/[repo]/page.tsx
calhacks12.0/auth.ts
calhacks12.0/components/ApiTestResults.tsx
calhacks12.0/components/CountUp.tsx
calhacks12.0/components/FadeInText.tsx
calhacks12.0/components/GitLitLogo.tsx
calhacks12.0/components/GradientText.tsx
calhacks12.0/components/Icons.tsx
calhacks12.0/components/LandingPage.tsx
calhacks12.0/components/LiquidEther.css
calhacks12.0/components/LiquidEther.tsx
calhacks12.0/components/LoadingState.tsx
calhacks12.0/components/MetricsDetailModal.tsx
calhacks12.0/components/ReviewDetailsModal.tsx
calhacks12.0/components/TypeWriter.tsx
calhacks12.0/DOCKER_SETUP.md
calhacks12.0/eslint.config.mjs
calhacks12.0/IMPLEMENTATION_SUMMARY.md
calhacks12.0/next.config.ts
calhacks12.0/package.json
calhacks12.0/postcss.config.mjs
calhacks12.0/POSTMAN_SETUP.md
calhacks12.0/README.md
calhacks12.0/SETUP.md
calhacks12.0/START_HERE.md
calhacks12.0/start-test-server.sh
calhacks12.0/TESTING_GUIDE.md
calhacks12.0/tsconfig.json
calhacks12.0/types/next-auth.d.ts
```

### Dependencies

- calhacks12.0/package.json: @auth/core@^0.41.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^18, @types/react-dom@^18, @types/three@^0.180.0, cross-env@^10.1.0, eslint@^9, eslint-config-next@^15.0.3, lucide-react@^0.548.0, motion@^12.23.24, next@^15.0.3, next-auth@^5.0.0-beta.29, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^4, three@^0.180.0, typescript@^5

### Recent commits (newest first)

- Merge pull request #5 from Jacobshin04/merge_postman_claude
- time to sleep
- postman ready
- feature with claude ai
- postman done
- frontend done
- github pr request:
- review fetches code into console
- merged
- More changez
- Merge pull request #4 from Jacobshin04/feature-branch
- dependency fixed
- test if this run on your guys computer
- Merge pull request #3 from Jacobshin04/github_repo
- fix node
- Merge pull request #2 from Jacobshin04/github_repo
- added repo
- Merge pull request #1 from Jacobshin04/github
- able to switch account
- github oatuh

## Key source files (fetched from GitHub, selected and truncated for size)

### calhacks12.0/POSTMAN_SETUP.md

```markdown
# Using Postman with Test Server

## Import Postman Collection

1. Open Postman
2. Click **Import**
3. Select `example-test-repo/postman-collection.json`
4. Your collection is now ready!

## Start the Test Server

```bash
cd example-test-repo
npm install
npm start
```

Server will run on `http://localhost:3001`

## Test Endpoints

Now you can test in Postman:

### Health Check

- `GET http://localhost:3001/api/health`
- Should return: `{"status":"healthy","timestamp":"..."}`

### Users

- `GET http://localhost:3001/api/users`
- `POST http://localhost:3001/api/users` (with body)

### Posts

- `GET http://localhost:3001/api/posts`
- `POST http://localhost:3001/api/posts` (with body)

### Auth

- `POST http://localhost:3001/api/auth/login`

### Ping

- `GET http://localhost:3001/ping`

## All Endpoints in One Collection!

The Postman collection includes:

- ✅ Pre-configured URLs
- ✅ Sample request bodies
- ✅ Proper headers
- ✅ Organized folders

Just import and start testing! 🚀

```

### calhacks12.0/DOCKER_SETUP.md

```markdown
# 🐳 Docker-Based Real API Testing

## How It Works

Now you can test APIs **for real** using Docker! Here's how:

### **Option 1: Docker Mode** (Automatic)

1. ✅ Check the "🐳 Use Docker" checkbox
2. Click "🧪 Test API Endpoints"
3. System automatically:
   - Builds Docker image from `example-test-repo`
   - Starts container on port 3001
   - Makes real HTTP requests
   - Shows actual results

### **Option 2: Local Mode** (Manual)

1. ❌ Leave "🐳 Use Docker" unchecked
2. Start server manually:
   ```bash
   cd example-test-repo
   npm install
   npm start
   ```
3. Click "🧪 Test API Endpoints"
4. Tests against running server

## What Happens in Docker Mode

```bash
# 1. Build Docker image
docker build -t example-test-repo .

# 2. Start container
docker run -d -p 3001:3001 example-test-repo

# 3. Wait for health check
curl http://localhost:3001/api/health

# 4. Run tests against real server
# All endpoints tested with actual HTTP requests
```

## Expected Results

With Docker enabled, you'll see:

- ✅ **Real status codes** (200, 201, 204, etc.)
- ✅ **Real response times** (network latency)
- ✅ **Real response bodies** (actual data from server)
- ✅ **Connection errors** if something fails

## Troubleshooting

### "Docker container failed to start"

- Make sure Docker is installed: `docker --version`
- Make sure Docker daemon is running
- Check logs in terminal

### "Build failed"

- Check `example-test-repo/Dockerfile` exists
- Make sure Dockerfile is valid

### Want to test locally instead?

Just uncheck "🐳 Use Docker" and run `npm start` manually!

---

**Now you have REAL API testing with Docker!** 🎉

```

### calhacks12.0/package.json

```
{
  "name": "calhacks12.0",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "cross-env NEXT_DISABLE_TURBOPACK=1 next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@auth/core": "^0.41.0",
    "@types/three": "^0.180.0",
    "lucide-react": "^0.548.0",
    "motion": "^12.23.24",
    "next": "^15.0.3",
    "next-auth": "^5.0.0-beta.29",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "three": "^0.180.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "cross-env": "^10.1.0",
    "eslint": "^9",
    "eslint-config-next": "^15.0.3",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### calhacks12.0/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { SessionProvider } from "next-auth/react";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "GitLit - AI-Powered Code Reviews",
  description:
    "Get instant AI code reviews and repo health scores for every pull request",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <SessionProvider>{children}</SessionProvider>
      </body>
    </html>
  );
}

```

### calhacks12.0/app/page.tsx

```typescript
"use client";

import { useSession, signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { LandingPage } from "../components/LandingPage";
import { GitLitLogo } from "../components/GitLitLogo";

export default function Home() {
  const { data: session, status } = useSession();
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);
  const [isConfigured, setIsConfigured] = useState(true);

  useEffect(() => {
    if (status === "loading") return; // Still loading

    if (session) {
      router.push("/dashboard");
    }

    // Check if GitHub OAuth is configured
    fetch("/api/auth/providers")
      .then((res) => res.json())
      .then((data) => {
        setIsConfigured(Object.keys(data).length > 0);
      })
      .catch(() => {
        setIsConfigured(false);
      });
  }, [session, status, router]);

  const handleConnectGitHub = async () => {
    setIsLoading(true);
    try {
      await signIn("github", {
        callbackUrl: "/dashboard",
      });
    } catch (error) {
      console.error("Sign in error:", error);
      alert(
        "GitHub OAuth is not configured. Please check your environment variables."
      );
      setIsLoading(false);
    }
  };

  // Show loading while checking session
  if (status === "loading") {
    return (
      <div
        className="min-h-screen flex items-center justify-center"
        style={{
          background:
            "linear-gradient(135deg, rgba(248, 249, 251, 0.95) 0%, rgba(238, 242, 255, 0.95) 100%)",
        }}
      >
        <div className="text-center">
          <div className="mb-8">
            <GitLitLogo className="w-96 h-28" animate={true} />
          </div>
          <p className="text-2xl text-gray-600 font-medium">Loading...</p>
        </div>
      </div>
    );
  }

  // Show landing page if not authenticated
  if (!session) {
    return (
      <LandingPage
        onConnectGitHub={handleConnectGitHub}
        isLoading={isLoading}
        isConfigured={isConfigured}
      />
    );
  }

  // This should not be reached due to useEffect redirect, but just in case
  return null;
}

```

### calhacks12.0/app/dashboard/page.tsx

```typescript
"use client";

import { useSession, signOut, signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { LoadingState } from "../../components/LoadingState";
import { GitLitLogo } from "../../components/GitLitLogo";
import LiquidEther from "../../components/LiquidEther";
import Link from "next/link";

interface RepoScore {
  name: string;
  fullName?: string;
  description?: string;
  private?: boolean;
  url?: string;
  language?: string;
  stars?: number;
  forks?: number;
  updatedAt?: string;
  score: number;
  lastReview: string;
  issues: number;
  security: number;
  performance: number;
}

export default function Dashboard() {
  const { data: session, status } = useSession();
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(true);
  const [repoScores, setRepoScores] = useState<RepoScore[]>([]);
  const [rateOpen, setRateOpen] = useState(false);
  const [rateLoading, setRateLoading] = useState(false);
  const [rateError, setRateError] = useState<string | null>(null);
  const [rateResult, setRateResult] = useState<string>("");
  const [rateRepoLabel, setRateRepoLabel] = useState<string>("");
  const [designOpen, setDesignOpen] = useState(false);
  const [designLoading, setDesignLoading] = useState(false);
  const [designError, setDesignError] = useState<string | null>(null);
  const [designResult, setDesignResult] = useState<string>("");
  const [designRepoLabel, setDesignRepoLabel] = useState<string>("");

  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/");
    } else if (status === "authenticated") {
      // Fetch actual GitHub repositories
      fetch("/api/github/repos")
        .then((res) => {
          if (!res.ok) {
            throw new Error("Failed to fetch repositories");
          }
          return res.json();
        })
        .then((data) => {
          // Overlay scores from localStorage if present
          let repos = data.repos || [];
          try {
            if (typeof window !== "undefined") {
              const raw = window.localStorage.getItem("repoScores");
              const store = raw ? JSON.parse(raw) : {};
              repos = repos.map((r: any) => {
                const key = r.fullName ?? `unknown/${r.name}`;
                const overlay = store[key];
                if (overlay && typeof overlay.averageScore === "number") {
                  return { ...r, score: overlay.averageScore };
                }
                return r;
              });
            }
          } catch {}
          setRepoScores(repos);
          setIsLoading(false);
        })
        .catch((error) => {
          console.error("Error fetching repositories:", error);
          setIsLoading(false);
          // Fallback to mock data on error
          setRepoScores([
            {
              name: "my-awesome-project",
              score: 0,
              lastReview: "Never",
              issues: 0,
              security: 0,
              performance: 0,
            },
            {
              name: "react-dashboard",
              score: 0,
              lastReview: "Never",
              issues: 0,
              security: 0,
              performance: 0,
            },
          ]);
        });
    }
  }, [status, router]);

  if (status === "loading" || isLoading) {
    return <LoadingState />;
  }

  if (!session) {
    return null;
  }

  const runCodeRating = async (fullName: string) => {
    try {
      setRateOpen(true);
      setRateLoading(true);
      setRateError(null);
      setRateResult("");
      setRateRepoLabel(fullName);

      const [owner, repoName] = (fullName ?? "unknown/unknown").split("/");
      console.log("[ui:rate] Requesting code rating", {
        fullName,
        owner,
        repoName,
      });
      const res = await fetch("/api/claude/rate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ owner, repo: repoName }),
      });

      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err?.error || "Failed to get code rating");
      }

      const data = await res.json();
      console.log("[ui:rate] Received response", {
        ok: res.ok,
        resultLen: (data?.result || "").length,
        preview: (data?.result || "").slice(0, 200),
      });
      setRateResult(data.result || "No content returned");
    } catch (e: any) {
      console.error("[ui:rate] Error", e);
      setRateError(e?.message || "An error occurred while getting code rating");
    } finally {
      setRateLoading(false);
    }
  };

  const runSystemDesign = async (fullName: string) => {
    try {
      setDesignOpen(true);
      setDesignLoading(true);
      setDesignError(null);
      setDesignResult("");
      setDesignRepoLabel(fullName);

      const [owner, repoName] = (fullName ?? "unknown/unknown").split("/");
      console.log("[ui:design] Requesting system design", {
        fullName,
        owner,
        repoName,
      });
      const res = await fetch("/api/claude/design", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ owner, repo: repoName }),
      });

      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err?.error || "Failed to get system design analysis");
      }

      const data = await res.json();
      console.log("[ui:design] Received response", {
        ok: res.ok,
        resultLen: (data?.result || "").length,
        preview: (data?.result || "").slice(0, 200),
      });
      setDesignResult(data.result || "No content returned");
    } catch (e: any) {
      console.error("[ui:design] Error", e);
      setDesignError(
        e?.message || "An error occurred while getting system design analysis"
      );
    } finally {
      setDesignLoading(false);
    }
  };

  
[truncated — 14115 more characters]
```

### calhacks12.0/app/api/auth/[...nextauth]/route.ts

```typescript
import { handlers } from "../../../../auth";

// ✅ Must use handlers.GET / handlers.POST now
export const GET = handlers.GET;
export const POST = handlers.POST;

```

### calhacks12.0/app/api/auth/providers/route.ts

```typescript
import { NextResponse } from "next/server";

export async function GET() {
  try {
    // Check if GitHub OAuth is configured
    const isConfigured =
      process.env.GITHUB_ID &&
      process.env.GITHUB_SECRET &&
      process.env.GITHUB_ID !== "test" &&
      process.env.GITHUB_SECRET !== "test";

    if (isConfigured) {
      return NextResponse.json({ github: "configured" });
    } else {
      return NextResponse.json({});
    }
  } catch (error) {
    console.error("Error checking providers:", error);
    return NextResponse.json({});
  }
}

```

### calhacks12.0/app/api/runner/stop/route.ts

```typescript
import { NextRequest, NextResponse } from "next/server";

interface StopRequest {
  containerId?: string;
  owner: string;
  repo: string;
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { containerId, owner, repo } = body;

    if (!owner || !repo) {
      return NextResponse.json(
        { error: "Missing owner or repo" },
        { status: 400 }
      );
    }

    // In a real implementation, you would:
    // 1. Stop the Docker container if containerId is provided
    // 2. Kill any running server processes
    // 3. Clean up temporary files
    // 4. Free up the port

    // Simulate cleanup
    console.log(
      `Stopping server for ${owner}/${repo}${
        containerId ? ` (container: ${containerId})` : ""
      }`
    );

    return NextResponse.json({
      success: true,
      message: "Server stopped successfully",
      owner,
      repo,
      containerId,
      timestamp: new Date().toISOString(),
    });
  } catch (error) {
    console.error("Error stopping server:", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  }
}

```

### calhacks12.0/app/api/github/contents/route.ts

```typescript
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth"; // adjust if your auth path differs

export async function GET(req: NextRequest) {
  try {
    const session = await auth();
    if (!session?.accessToken) {
      return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
    }

    const { searchParams } = new URL(req.url);
    const owner = searchParams.get("owner");
    const repo = searchParams.get("repo");
    const path = searchParams.get("path") ?? ""; // root by default

    if (!owner || !repo) {
      return NextResponse.json({ error: "Missing owner/repo" }, { status: 400 });
    }

    const ghUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`;

    const res = await fetch(ghUrl, {
      headers: {
        Authorization: `Bearer ${session.accessToken}`,
        Accept: "application/vnd.github.v3+json",
        "User-Agent": "GitLit",
      },
      cache: "no-store",
    });

    const data = await res.json();

    if (!res.ok) {
      console.error("GitHub contents error:", data);
      return NextResponse.json(
        { error: "Failed to fetch contents", details: data },
        { status: res.status }
      );
    }

    // 🔵 Server log so you can see it in your terminal
    console.log(`[contents] ${owner}/${repo} path="${path}" ->`, Array.isArray(data) ? `${data.length} entries` : data?.name);

    // For files, GitHub returns base64 content. Return as-is (client can decode or just inspect).
    return NextResponse.json({ data });
  } catch (err) {
    console.error("API /contents error:", err);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}

```

[38 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]