# Project export: gitRizz.ai

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: TreeHacks 2025
- Tagline: Why use Tinder when your Github commits speak for themselves? We analyzes your repos, PRs, and contributions to find your perfect match — no bios, no cringe, just pure algorithmic compatibility!
- Devpost: https://devpost.com/software/gitrizz-ai
- GitHub: https://github.com/aliceli465/treehacks25
- Video: https://www.youtube.com/embed/0_vUbWiVhP4?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — QuestForKnowledge (10 commits), Alice Li (8 commits)

## Devpost submission (written by the team)

### Inspiration

We realized that a person’s GitHub says more about them than any dating profile ever could—commit history, README vibes, even that one cursed 2 AM push. So why not let the code do the talking? With gitRizz.ai, we’re turning repositories into relationships, because nothing says “soulmate” like burnout and a mutual obsession with clean code ❤️

### What it does

gitRizz.ai is a Hinge-style matchmaking platform that curates GitHub profiles to find the most optimal matches - all you have to do is login with Github! We generate vector scores for each user, combining numerical metrics (commits, contributions, PRs) with contextual analysis (commit messages, READMEs, bios, profiles) to get a well-rounded view of their personality from both their technical contributions and textual expressions of themselves. Using vector search, we calculate similarity scores between users, ensuring meaningful connections. Lastly, we used OpenAI to generate personalized feedback for each profile since a lot of these Github profiles can have a lot of information all at once. If two users mutually swipe right, they can chat and collaborate—professionally or romantically!

### How we built it

Backend: We used the Github API and beautiful-soup to scrape most public data from a user’s profile—including their picture, READMEs, bio, and so on. We consolidate all the quantitative contributions of the user (number of contributions, commits, pull requests, PRs, etc.) as well as an analysis of their textual contributions such as the commit messages, READMEs, and PR content. Using OpenAI API, we are able to generate an 6-dimensional vector representing their characteristics ranging from commitment to collaboration style. Finally, using a cosine-similarity matching algorithm, we are able to generate an list of potential matches to show a specific user. Frontend: Firebase + Github authentication, and from there, it was all React + JavaScript + Flask.

### Challenges we ran into

For future scalability, it is important to minimize expensive API calls. At first, our output was generated way too slowly as calls were made repeatedly. Instead, by caching users vectors, we are able to reduce computation significantly. In the future, we would likely be able to utilize an ML algorithm that can perhaps avoid such calls for certain users all together and instead infer potential matches more indirectly (ex: A and B are a good match and A and C are a good match, so maybe B and C are a good match.) Also, deciding what metrics to hone in on was a challenge, one that we are still working on.

### Accomplishments we're proud of

We wanted the UI to highlight just how much of the work we do for you - after all, all you do is login with Github and we calculate everything from there. Thus, the interface had to be simplistic, modern, yet easy to understand and use when it came to finding matches and prospective candidates. Mapping this out and narrowing down on a design was difficult but worthwile. Also, we’re proud of simply ideating this project, which is the perfect balance of silly and serious. Beyond classes and random cool hackathon encounters, there really isn’t a systematic tool for meeting devs with a similar vibe as you!

### What we learned

Designing for scale from the beginning is important! Caching user data from the start would’ve made life so much easier. Also, sometimes, the best ideas come from silliness—this was inspired by Valentine’s Day!

### What's next

Adding more metrics on which to match people on. Adding more text and video prompts. Allow users to filter by platonic or romantic, sexuality, distance, etc. Reward and boost users who contribute to open-source repos, are rated well by peers, etc. Building out the chat + post-match interface. We wanted to focus on profile curation + statistics, so didn't implement this portion for the hackathon.

## README (from the GitHub repository)

# treehacks25

git rizz before you git commit.


## Detected evidence (automated analysis)

Indexed codebase: 20 recognized source files, 91 KB.
- CSS (language) — detected in the code
- Firebase (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
.gitignore
flask_app.py
gitrizz/.gitignore
gitrizz/package.json
gitrizz/public/index.html
gitrizz/public/manifest.json
gitrizz/public/profiles.json
gitrizz/public/robots.txt
gitrizz/README.md
gitrizz/src/App.css
gitrizz/src/App.js
gitrizz/src/App.test.js
gitrizz/src/components/about.jsx
gitrizz/src/components/generate.js
gitrizz/src/components/home.jsx
gitrizz/src/components/profile.jsx
gitrizz/src/components/profiles.jsx
gitrizz/src/components/userProfile.jsx
gitrizz/src/components/widgets.jsx
gitrizz/src/firebase.js
gitrizz/src/index.css
gitrizz/src/index.js
gitrizz/src/reportWebVitals.js
gitrizz/src/setupTests.js
main.py
README.md
```

### Dependencies

- gitrizz/package.json: @chakra-ui/react@^3.8.0, @emotion/react@^11.14.0, @emotion/styled@^11.14.0, @tailwindcss/postcss@^4.0.6, @tailwindcss/vite@^4.0.6, @testing-library/dom@^10.4.0, @testing-library/jest-dom@^6.6.3, @testing-library/react@^16.2.0, @testing-library/user-event@^13.5.0, autoprefixer@^10.4.20, cors@^2.8.5, firebase@^11.3.1, framer-motion@^12.4.3, lucide-react@^0.475.0, postcss@^8.5.2, react@^19.0.0, react-dom@^19.0.0, react-icons@^5.4.0, react-router-dom@^7.1.5, react-scripts@5.0.1, react-tooltip@^5.28.0, recharts@^2.15.1, tailwindcss@^4.0.6, web-vitals@^2.1.4

### Recent commits (newest first)

- messed around w groq
- tooltips user
- Merge branch 'main' of https://github.com/aliceli465/treehacks25
- profile
- flask.py added i think
- flask_app.py
- Merge branch 'main' of https://github.com/aliceli465/treehacks25
- firebase github auth
- Update README.md
- roasts added with examples
- cosine similarity
- cosine similarity done
- Merge pull request #1 from aliceli465/my-fix-branch
- fixed image analysis
- added image analysis
- Merge branch 'main' of https://github.com/aliceli465/treehacks25
- basic frontend + mock data
- Removed .env from tracking
- Initial commit

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

### gitrizz/package.json

```
{
  "name": "gitrizz",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@chakra-ui/react": "^3.8.0",
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.0",
    "@tailwindcss/postcss": "^4.0.6",
    "@tailwindcss/vite": "^4.0.6",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
    "@testing-library/user-event": "^13.5.0",
    "cors": "^2.8.5",
    "firebase": "^11.3.1",
    "framer-motion": "^12.4.3",
    "lucide-react": "^0.475.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-icons": "^5.4.0",
    "react-router-dom": "^7.1.5",
    "react-scripts": "5.0.1",
    "react-tooltip": "^5.28.0",
    "recharts": "^2.15.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "autoprefixer": "^10.4.20",
    "postcss": "^8.5.2",
    "tailwindcss": "^4.0.6"
  }
}

```

### main.py

```python
import os
import requests
import datetime
import numpy as np
from dotenv import load_dotenv
import openai
import base64
import re
from scipy.spatial.distance import cosine  # Import for cosine similarity

# Load environment variables from .env file
load_dotenv()

GITHUB_TOKEN = os.getenv("GITHUB_ACCESS_TOKEN")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")

# GitHub API request headers
HEADERS = {"Authorization": f"token {GITHUB_TOKEN}"} if GITHUB_TOKEN else {}

# === 1. Fetch User Profile Data ===
def get_user_profile(username):
    url = f"https://api.github.com/users/{username}"
    return requests.get(url, headers=HEADERS).json()

# === 2. Fetch User Repositories ===
def get_repos(username):
    url = f"https://api.github.com/users/{username}/repos?per_page=100"
    repos = requests.get(url, headers=HEADERS).json()
    
    repo_details = []
    for repo in repos:
        repo_info = {
            "name": repo["name"],
            "url": repo["html_url"],
            "description": repo.get("description", ""),
            "fork": repo["fork"],
            "stars": repo["stargazers_count"],
            "watchers": repo["watchers_count"],
            "created_at": repo["created_at"],
            "updated_at": repo["updated_at"],
            "pushed_at": repo["pushed_at"],
            "languages_url": repo["languages_url"],
            "has_readme": check_readme_presence(username, repo["name"]),
            "readme_length": get_readme_length(username, repo["name"])
        }
        
        # Fetch languages used in the repo
        repo_info["languages"] = requests.get(repo["languages_url"], headers=HEADERS).json()
        
        repo_details.append(repo_info)
    
    return repo_details

# === 3. Fetch Commit History & Quality ===
def get_commit_history(username, repos):
    commit_history = {}
    commit_message_quality = []

    for repo in repos:
        if repo["fork"]:
            continue  # Ignore forks

        repo_name = repo["name"]
        url = f"https://api.github.com/repos/{username}/{repo_name}/commits?per_page=100"
        commits = requests.get(url, headers=HEADERS).json()

        commit_dates = []
        messages = []
        for commit in commits:
            if "commit" in commit:
                commit_dates.append(commit["commit"]["author"]["date"])
                messages.append(commit["commit"]["message"])

        commit_history[repo_name] = commit_dates
        commit_message_quality.extend(messages)

    return commit_history

# === 4. Fetch Collaboration Data (PRs, Issues, Discussions, Code Reviews) ===
def get_collaboration_data(username):
    prs_url = f"https://api.github.com/search/issues?q=author:{username}+type:pr"
    issues_url = f"https://api.github.com/search/issues?q=author:{username}+type:issue"
    commented_prs_url = f"https://api.github.com/search/issues?q=commenter:{username}+type:pr"
    reviewed_prs_url = f"https://api.github.com/search/issues?q=reviewed-by:{username}+type:pr"

    prs_count = requests.get(prs_url, headers=HEADERS).json().get("total_count", 0)
    issues_count = requests.get(issues_url, headers=HEADERS).json().get("total_count", 0)
    commented_prs_count = requests.get(commented_prs_url, headers=HEADERS).json().get("total_count", 0)
    reviewed_prs_count = requests.get(reviewed_prs_url, headers=HEADERS).json().get("total_count", 0)

    return {
        "pull_requests": prs_count,
        "issues": issues_count,
        "commented_prs": commented_prs_count,
        "reviewed_prs": reviewed_prs_count
    }

def download_profile_picture(username, save_dir="profile_pictures"):
    """
    Downloads the GitHub profile picture of a user and saves it to the given directory.
    
    Args:
        username (str): GitHub username.
        save_dir (str): Directory to save the profile picture.

    Returns:
        str: Path to the saved image.
    """
    os.makedirs(save_dir, exist_ok=True)  # Ensure the directory exists

    save_path = os.path.join(save_dir, f"{username}.jpg")
    url = f"https://api.github.com/users/{username}"
    
    response = requests.get(url, headers=HEADERS).json()
    if "avatar_url" in response:
        avatar_url = response["avatar_url"]
        img_data = requests.get(avatar_url).content
        
        with open(save_path, "wb") as file:
            file.write(img_data)

        return save_path  # Return the saved image path

    return None  # Return None if download fails


# === 5. Profile Picture Analysis ===
def interpret_profile_picture(username, save_dir="profile_pictures"):
    image_path = os.path.join(save_dir, f"{username}.jpg")

    if not os.path.exists(image_path):
        return "Profile picture not found."

    prompt = (
        "Given this Github profile picture, interpret what it is in 3 detailed sentences, "
        "as if to someone who is visually impaired but can see. At the end, include a line break, and then a zingy, witty, specific software engineering roast of the profile picture. Make it Gen-Z core. No emojis, though, just a single sentence."
    )

    client = openai.OpenAI(api_key=OPENAI_API_KEY)

    with open(image_path, "rb") as image_file:
        image_base64 = base64.b64encode(image_file.read()).decode("utf-8")  

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "You are an assistant that describes images."},
            {"role": "user", "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}}
            ]}
        ]
    )

    return response.choices[0].message.content


# === 6. GitHub Profile Scoring ===
def score_github_profile(user_data):
    prompt = (
        "We are developing a dating app that matches users based on their GitHub profile data. "
        "Your task is to analyze and score a given GitHub profile 
[truncated — 10962 more characters]
```

### gitrizz/src/index.js

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "react-tooltip/dist/react-tooltip.css";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### gitrizz/src/App.js

```javascript
import React, { useState } from "react";
import "./App.css"; // Optional: Add any additional global styles
import Home from "./components/home";
import SwipeableProfileCards from "./components/profiles";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import UserProfile from "./components/userProfile";
import { auth } from "./firebase";

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/profiles" element={<SwipeableProfileCards />} />
        <Route path="/user-profile" element={<UserProfile />} />
      </Routes>
    </Router>
  );
};

export default App;

```

### flask_app.py

```python
from flask import Flask, request, jsonify
from flask_cors import CORS
import os

from main import (
    collect_github_data, 
    compute_similarity, 
    collect_github_data_only_vector, 
    collect_github_roasts_only
)

app = Flask(__name__)
CORS(app)  # Enable CORS for frontend requests

# === 🔥 Store GitHub Data Globally to Minimize OpenAI API Calls ===
stored_github_data = {}

@app.route('/')
def home():
    return jsonify({"message": "GitHub Dating API is running!"})

# === 1. Route to Collect GitHub Data (Run Only Once) ===
@app.route('/load-github-data', methods=['POST'])
def load_github_data():
    """
    Takes a list of GitHub usernames, collects their GitHub profile data ONCE, 
    and stores it in memory to avoid redundant OpenAI API calls.

    Expected JSON:
    {
        "usernames": ["username1", "username2", ...]
    }
    """
    global stored_github_data
    try:
        data = request.json
        usernames = data.get('usernames', [])

        if not usernames:
            return jsonify({"error": "No usernames provided"}), 400

        # Store the data once
        stored_github_data = collect_github_data(usernames)

        return jsonify({"message": "GitHub data loaded successfully!"})

    except Exception as e:
        return jsonify({"error": str(e)}), 500

# === 2. Route to Get Stored GitHub Data ===
@app.route('/github-data', methods=['GET'])
def get_github_data():
    """
    Returns the stored GitHub data (previously collected).
    """
    if not stored_github_data:
        return jsonify({"error": "No data available. Load data first using /load-github-data"}), 400

    return jsonify(stored_github_data)

# === 3. Route to Get Only Vectors ===
@app.route('/github-vectors', methods=['GET'])
def get_github_vectors():
    """
    Extracts only the GitHub score vectors (6 floats per user) from stored data.
    """
    if not stored_github_data:
        return jsonify({"error": "No data available. Load data first using /load-github-data"}), 400

    vector_output = collect_github_data_only_vector(stored_github_data)
    return jsonify(vector_output)

# === 4. Route to Compute Cosine Similarity ===
@app.route('/github-similarity', methods=['POST'])
def get_github_similarity():
    """
    Computes cosine similarity between a target user and all other users.

    Expected JSON:
    {
        "target_user": "username1"
    }
    """
    if not stored_github_data:
        return jsonify({"error": "No data available. Load data first using /load-github-data"}), 400

    try:
        data = request.json
        target_user = data.get('target_user', '')

        if not target_user:
            return jsonify({"error": "No target user provided"}), 400

        vector_output = collect_github_data_only_vector(stored_github_data)

        if target_user not in vector_output:
            return jsonify({"error": f"Target user {target_user} not found in data"}), 400

        similarity_results = compute_similarity(vector_output, target_user)
        return jsonify(similarity_results)

    except Exception as e:
        return jsonify({"error": str(e)}), 500

# === 5. Route to Fetch Profile Picture Roasts ===
@app.route('/github-roasts', methods=['GET'])
def get_github_roasts():
    """
    Extracts witty profile picture roasts for stored users.
    """
    if not stored_github_data:
        return jsonify({"error": "No data available. Load data first using /load-github-data"}), 400

    roasts_output = collect_github_roasts_only(stored_github_data)
    return jsonify(roasts_output)

# === 6. Route to Reset Stored Data ===
@app.route('/reset-data', methods=['POST'])
def reset_data():
    """
    Clears the stored GitHub data so new data can be fetched.
    """
    global stored_github_data
    stored_github_data = {}
    return jsonify({"message": "Stored GitHub data has been reset."})

if __name__ == "__main__":
    app.run(debug=True)

```

### gitrizz/src/setupTests.js

```javascript
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

```

### gitrizz/src/App.test.js

```javascript
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

```

### gitrizz/src/reportWebVitals.js

```javascript
const reportWebVitals = onPerfEntry => {
  if (onPerfEntry && onPerfEntry instanceof Function) {
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(onPerfEntry);
      getFID(onPerfEntry);
      getFCP(onPerfEntry);
      getLCP(onPerfEntry);
      getTTFB(onPerfEntry);
    });
  }
};

export default reportWebVitals;

```

### gitrizz/src/index.css

```css
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

```

### gitrizz/src/firebase.js

```javascript
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getAuth } from "firebase/auth";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyDNHB-FA4OqNtwEJDY1mKJPY5-q6STNZGQ",
  authDomain: "gitrizz.firebaseapp.com",
  projectId: "gitrizz",
  storageBucket: "gitrizz.firebasestorage.app",
  messagingSenderId: "256077011064",
  appId: "1:256077011064:web:db21ec59bd1b36f2f7358d",
  measurementId: "G-2KH9Y72BB8",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
const auth = getAuth(app);
export { app, auth };

```

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