How the Hiring-Agent System Extracts and Evaluates GitHub Repository Data from Resumes

The Hiring-Agent system normalizes GitHub URLs into canonical usernames, enriches profile data via the GitHub API, filters and scores repositories by contribution metrics, and employs a large language model to curate exactly 7 top projects for recruiter review.

The interviewstreet/hiring-agent repository automates technical candidate screening by transforming raw GitHub links found in résumés into structured, ranked project portfolios. The extraction pipeline lives primarily in github.py and orchestrates API calls, caching layers, and LLM-based evaluation to extract and evaluate GitHub repository data from resumes with minimal manual intervention.

Username Extraction and Profile Retrieval

The pipeline begins by sanitizing unstructured input from résumés. The extract_github_username() function in github.py (lines 16-27) handles multiple URL patterns—including https://github.com/user, @user, and bare user strings—and returns a canonical username.

Once normalized, fetch_github_profile() constructs the endpoint https://api.github.com/users/<username> and invokes the internal _fetch_github_api() helper. The resulting JSON maps to a Pydantic GitHubProfile model defined in models.py, providing typed access to fields like bio, location, and public repository count.

Repository Fetching and Filtering

The fetch_all_github_repos() function (lines 18-89) retrieves the user’s repositories from /users/<username>/repos, sorted by recent activity and capped by the max_repos parameter. The system applies an immediate quality filter: forked repositories with fewer than five forks are discarded to eliminate low-effort copies.

For each retained repository, the pipeline calls fetch_repo_contributors() to retrieve the full contributor list. This data feeds into fetch_contributions_count() (lines 87-99), which calculates two critical metrics:

  • author_commit_count: Total commits made by the repository owner
  • total_commit_count: Aggregate commits across all contributors

These statistics reveal the candidate’s actual ownership stake versus minor participation.

Enriched Project Payload Assembly

Before LLM evaluation, the system assembles a comprehensive dictionary for every repository. This payload includes the project name, description, URLs, primary language, classification (open-source vs. self-project), the computed contribution metrics, and a nested github_details object containing stars, forks, and topics. The entire list is sorted by star count in descending order to prioritize high-impact work.

LLM-Driven Project Selection

The generate_projects_json() function (lines 34-73) handles the core evaluation logic. It serializes the enriched project list into JSON and injects it into the github_project_selection.jinja template located in prompts/templates/. The rendered prompt is sent to the LLM provider initialized via initialize_llm_provider() from llm_utils.py.

The system message strictly constrains the model to return exactly 7 unique projects. The raw LLM response passes through extract_json_from_response() to strip markdown fences and parse valid JSON back into Python objects.

Fallback Handling and Validation

If the LLM response is malformed, contains duplicate entries, or fails to provide 7 distinct projects, the pipeline executes a deterministic fallback. It selects the first 7 entries from the pre-sorted star-count list, ensuring the recruiter always receives a complete portfolio even when the LLM misbehaves. The fetch_and_display_github_info() function (lines 59-78) orchestrates this end-to-end flow, returning a final structure containing the profile, curated projects, and total_projects count.

Caching and Rate-Limit Handling

The internal _fetch_github_api() implementation (lines 29-84) includes production-ready resilience features. When DEVELOPMENT_MODE is enabled in config.py, responses cache to cache/gh_githubcache_…json, eliminating redundant API calls during iterative development.

For production deployments, the function monitors the X-RateLimit-Remaining header. When fewer than 10 requests remain, the system sleeps until the reset timestamp (capped at one hour) and logs a warning prompting the operator to set GITHUB_TOKEN to upgrade from the unauthenticated limit of 60 requests/hour to 5,000 requests/hour.

Implementation Code Examples

Basic Profile and Repository Extraction

from github import fetch_github_profile, fetch_all_github_repos

profile = fetch_github_profile("https://github.com/awesome-dev")
print(profile)                         # → GitHubProfile Pydantic object

repos = fetch_all_github_repos("awesome-dev", max_repos=20)
print(f"Found {len(repos)} repos")    # → list of enriched repo dicts

End-to-End Pipeline Invocation

from github import fetch_and_display_github_info

result = fetch_and_display_github_info("https://github.com/awesome-dev")

# `result` structure:

# {

#   "profile": {...},          # basic user fields

#   "projects": [...],         # 7 best projects selected by the LLM

#   "total_projects": 7

# }

print(result["projects"])           # display the curated projects

Direct LLM Selector Testing

from github import generate_projects_json, fetch_all_github_repos

all_projects = fetch_all_github_repos("awesome-dev")
top_projects = generate_projects_json(all_projects)   # returns list of 7 dicts

for p in top_projects:
    print(f"{p['name']} – ⭐ {p['github_details']['stars']}")

Core Files and Architecture

File Role
github.py Central logic for username extraction, API fetching, contribution metrics, caching, rate-limit handling, and LLM project selection.
prompts/templates/github_project_selection.jinja Jinja template formatting repository data and enforcing the 7-project selection constraint for the LLM.
prompts/template_manager.py Runtime template loading and rendering utilities used by the GitHub module.
llm_utils.py Provider initialization (initialize_llm_provider) and response sanitization (extract_json_from_response).
models.py Pydantic schemas including GitHubProfile for type-safe API responses.
config.py Feature flags such as DEVELOPMENT_MODE that toggle caching behavior.

Summary

  • Username normalization via extract_github_username() handles diverse URL formats found in résumés.
  • Quality filtering removes forked repositories with fewer than five forks before heavy processing.
  • Contribution metrics distinguish between project ownership and minor contributions using commit statistics.
  • LLM curation enforces exactly 7 unique projects, with deterministic fallback to star-sorted results if the model fails.
  • Rate-limit protection includes caching for development and automatic backoff with token-upgrade hints for production.
  • Modular architecture separates concerns across github.py, Jinja templates, and Pydantic models for maintainability.

Frequently Asked Questions

How does the system handle GitHub API rate limits?

The _fetch_github_api() helper checks the X-RateLimit-Remaining header on every request. When fewer than 10 requests remain, it pauses execution until the rate limit resets (maximum one hour). In development mode, responses cache to disk under cache/gh_githubcache_…json, while production deployments can set GITHUB_TOKEN to increase the quota from 60 to 5,000 requests per hour.

What criteria does the LLM use to select the top 7 projects?

The LLM receives a structured JSON payload containing repository names, descriptions, languages, star counts, fork counts, and contribution statistics via the github_project_selection.jinja template. The system prompt instructs the model to identify the most technically impressive and impactful work, forcing the return of exactly 7 unique entries. If the LLM hallucinates or returns invalid JSON, the system falls back to the top 7 repositories sorted by star count.

How does the system extract GitHub usernames from messy résumé text?

The extract_github_username() function in github.py (lines 16-27) uses pattern matching to parse multiple input formats, including full URLs (https://github.com/user), prefixed handles (@user), and bare usernames. It validates the extracted string against GitHub’s username constraints and returns a canonical identifier suitable for API calls.

What happens if a candidate has fewer than 7 public repositories?

If the total number of available repositories after filtering (removing low-fork forks) is fewer than 7, the LLM receives all available projects and the system returns that smaller set. The fallback logic only triggers when the LLM fails to parse or when it returns duplicates; it does not invent additional repositories. The total_projects field in the response accurately reflects the actual count returned.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →