docs/github_repo_recap_service.md
The Ai::GithubRepoRecap service generates AI-powered summaries of GitHub repository activity over a specified timeframe. It fetches pull requests and commits from a repository and creates a markdown-formatted recap that highlights significant changes while aggregating minor updates.
This service is useful for:
{% embed %} tags for important pull requestsnil when there's no activity to report# Generate a weekly recap
recap_service = Ai::GithubRepoRecap.new("forem/forem", days_ago: 7)
result = recap_service.generate
if result
puts result.title
puts result.body
else
puts "No activity in this timeframe"
end
# Monthly recap
monthly = Ai::GithubRepoRecap.new("rails/rails", days_ago: 30)
result = monthly.generate
# Two-week recap
biweekly = Ai::GithubRepoRecap.new("user/repo", days_ago: 14)
result = biweekly.generate
# Use an authenticated GitHub client to access private repositories
user = User.find_by(github_username: "username")
client = Github::OauthClient.for_user(user)
recap = Ai::GithubRepoRecap.new(
"organization/private-repo",
days_ago: 7,
github_client: client
)
result = recap.generate
# Inject custom clients for testing
mock_github = double("GithubClient")
mock_ai = double("AiClient")
recap = Ai::GithubRepoRecap.new(
"test/repo",
days_ago: 7,
github_client: mock_github,
ai_client: mock_ai
)
The service returns either:
nil if there's no activityAi::GithubRepoRecap::RecapResult struct with:
title (String): A compelling title for the recapbody (String): Markdown-formatted body with embedded PR linksresult = recap.generate
result.title
# => "Weekly Recap: Major Performance Improvements and New Features"
result.body
# => "This week saw significant progress on the Forem repository!
#
# ## Major Changes
#
# {% embed https://github.com/forem/forem/pull/12345 %}
#
# The team shipped a complete rewrite of the notification system...
#
# ## Minor Updates
#
# - 15 bug fixes across various components
# - Documentation improvements
# - Dependency updates"
The service requires the following environment variables:
GEMINI_API_KEY: API key for Google Gemini AI (required)GEMINI_API_MODEL: Model to use (optional, defaults to "gemini-2.5-pro")For GitHub access:
Github::OauthClient.for_user(user)nil if no activity foundThe service instructs the AI to:
{% embed URL %} syntax for important PRs (typically 3-7 embeds)The service handles various error scenarios:
All errors are logged and the service returns nil gracefully.
# In a Sidekiq worker or scheduled task
class WeeklyRepoDigestWorker
include Sidekiq::Job
def perform(repo_name)
recap = Ai::GithubRepoRecap.new(repo_name, days_ago: 7)
result = recap.generate
return unless result
# Create an article or send via email
Article.create!(
title: result.title,
body_markdown: result.body,
user: User.admin.first
)
end
end
# In a controller
class Api::GithubRecapsController < ApplicationController
def show
recap = Ai::GithubRepoRecap.new(
params[:repo],
days_ago: params[:days]&.to_i || 7
)
result = recap.generate
if result
render json: {
title: result.title,
body: result.body
}
else
render json: { message: "No activity found" }, status: :not_found
end
end
end
The service is fully tested with RSpec. See spec/services/ai/github_repo_recap_spec.rb for comprehensive test coverage including:
Potential improvements:
Ai::Base - Base AI client for Gemini APIGithub::OauthClient - GitHub API client wrapperapp/services/ai/For issues or questions: