236139ca37a7c8ac426a9195a5f2ad35ff2d5626
MarketAlly.GitCommitEditor
A production-ready .NET 9 library for analyzing, improving, and rewriting git commit messages using configurable quality rules and AI-powered suggestions.
Features
- Commit Message Analysis - Score commit messages against configurable quality rules (subject length, conventional commits, banned phrases, etc.)
- AI-Powered Suggestions - Generate improved commit messages using Claude, OpenAI, Gemini, or Qwen
- Repository Management - Discover, register, and manage multiple git repositories
- Commit Rewriting - Amend latest commits or reword older commits in history with automatic backup
- Batch Operations - Process multiple commits across multiple repositories efficiently
- History Health Analysis - Comprehensive repository health scoring with cleanup recommendations
- Push Operations - Check push status, get tracking info, and perform force pushes
- State Persistence - Save and restore analysis state between sessions
- Dependency Injection - First-class support for Microsoft.Extensions.DependencyInjection
Installation
dotnet add package MarketAlly.GitCommitEditor
Quick Start
With Dependency Injection (Recommended)
using MarketAlly.GitCommitEditor.Extensions;
services.AddGitMessageImprover(options =>
{
options.WorkspaceRoot = @"C:\Projects";
options.StateFilePath = "git-state.json";
options.MaxCommitsPerRepo = 100;
options.AnalyzeSince = DateTimeOffset.Now.AddMonths(-3);
options.ExcludedAuthors = ["dependabot[bot]@users.noreply.github.com"];
// Configure commit message rules
options.Rules.MinSubjectLength = 10;
options.Rules.MaxSubjectLength = 72;
options.Rules.RequireConventionalCommit = true;
options.Rules.RequireIssueReference = false;
// Configure AI provider
options.Ai.Provider = "Claude";
options.Ai.Model = "claude-sonnet-4-20250514";
options.Ai.ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!;
options.Ai.IncludeDiffContext = true;
});
Without DI (Simple Usage)
using MarketAlly.GitCommitEditor.Options;
using MarketAlly.GitCommitEditor.Services;
var options = new GitImproverOptions
{
WorkspaceRoot = @"C:\Projects",
Ai = { ApiKey = "your-api-key", Provider = "Claude" }
};
await using var service = await GitMessageImproverService.CreateAsync(options);
Core Workflows
1. Scan and Analyze Repositories
IGitMessageImproverService improver = ...;
// Load persisted state
await improver.LoadStateAsync();
// Discover git repos in workspace
var newRepos = await improver.ScanAndRegisterReposAsync();
Console.WriteLine($"Found {newRepos.Count} new repositories");
// Analyze all repos for commit message issues
var analyses = await improver.AnalyzeAllReposAsync(
onlyNeedsImprovement: true,
progress: new Progress<(string Repo, int Count)>(p =>
Console.WriteLine($"Analyzed {p.Count} commits in {p.Repo}")));
Console.WriteLine($"Found {analyses.Count} commits needing improvement");
2. Generate AI Suggestions
// Generate suggestions for commits that need improvement
var commitsToImprove = analyses.Where(a => a.Quality.NeedsImprovement).ToList();
var result = await improver.GenerateSuggestionsAsync(
commitsToImprove,
progress: new Progress<int>(count =>
Console.WriteLine($"Generated {count}/{commitsToImprove.Count} suggestions")));
Console.WriteLine($"Success: {result.SuccessCount}, Failed: {result.FailedCount}");
// Or generate for a single commit
var suggestion = await improver.GenerateSuggestionAsync(analyses.First());
if (suggestion.Success)
Console.WriteLine($"Suggested: {suggestion.Suggestion}");
3. Safe Batch Rewrite (Recommended)
// Check safety before rewriting
var safetyInfo = improver.GetRewriteSafetyInfo(repoPath, commitsToRewrite);
if (!safetyInfo.CanProceedSafely)
{
foreach (var warning in safetyInfo.GetWarnings())
Console.WriteLine($"Warning: {warning}");
}
// Execute batch rewrite with automatic backup
var result = await improver.ExecuteBatchRewriteAsync(
repoPath,
commitsToRewrite,
createBackup: true,
progress: new Progress<(int Current, int Total, string Hash)>(p =>
Console.WriteLine($"Rewriting {p.Current}/{p.Total}: {p.Hash}")));
if (result.Success)
{
Console.WriteLine($"Rewrote {result.SuccessCount} commits");
if (result.BackupBranchName != null)
Console.WriteLine($"Backup branch: {result.BackupBranchName}");
if (result.RequiresForcePush)
Console.WriteLine("Force push required to update remote");
}
4. Preview and Apply Changes (Granular Control)
// Preview changes before applying
var operations = improver.PreviewChanges(commitsToImprove);
foreach (var op in operations)
{
Console.WriteLine($"{op.CommitHash[..7]}: {op.OriginalMessage}");
Console.WriteLine($" -> {op.NewMessage}");
}
// Apply changes (dryRun: false to actually modify commits)
var result = await improver.ApplyChangesAsync(
operations,
dryRun: false,
progress: new Progress<(int Processed, int Total)>(p =>
Console.WriteLine($"Applied {p.Processed}/{p.Total}")));
Console.WriteLine($"Success: {result.Successful}, Failed: {result.Failed}");
5. Repository History Health Analysis
// Analyze repository health
var healthReport = await improver.AnalyzeHistoryHealthAsync(
repoPath,
new HistoryAnalysisOptions { Depth = AnalysisDepth.Standard },
progress: new Progress<AnalysisProgress>(p =>
Console.WriteLine($"Analyzing: {p.PercentComplete}%")));
Console.WriteLine($"Health Score: {healthReport.Score.OverallScore}/100 ({healthReport.Score.Grade})");
Console.WriteLine($"Issues: {healthReport.CriticalIssueCount} critical, {healthReport.ErrorCount} errors");
// Export report
var markdown = await improver.ExportHealthReportAsync(healthReport, ReportFormat.Markdown);
// Execute cleanup suggestions
if (healthReport.CleanupSuggestions?.AutomatedOperations.Any() == true)
{
var cleanupResult = await improver.ExecuteAllCleanupsAsync(
repo,
healthReport.CleanupSuggestions,
new CleanupExecutionOptions { CreateBackup = true });
Console.WriteLine($"Cleaned up {cleanupResult.Successful} issues");
}
6. Handle Pushed Commits
// Check if commit is already pushed
bool isPushed = improver.IsCommitPushed(repoPath, commitHash);
if (isPushed)
Console.WriteLine("Warning: Commit has been pushed. Force push required after amending.");
// Get tracking info
var tracking = improver.GetTrackingInfo(repoPath);
Console.WriteLine($"Tracking: {tracking.UpstreamBranch}, Ahead: {tracking.AheadBy}, Behind: {tracking.BehindBy}");
// After amending a pushed commit, force push
var pushResult = improver.ForcePush(repoPath);
if (pushResult.Success)
Console.WriteLine("Force push successful");
else
Console.WriteLine($"Push failed: {pushResult.Message}");
Configuration
GitImproverOptions
| Property | Type | Default | Description |
|---|---|---|---|
WorkspaceRoot |
string | required | Root directory to scan for git repositories |
StateFilePath |
string | "git-improver-state.json" |
Path to persist state between sessions |
MaxCommitsPerRepo |
int | 100 |
Maximum commits to analyze per repository |
AnalyzeSince |
DateTimeOffset? | null |
Only analyze commits after this date |
ExcludedAuthors |
string[] | [] |
Author emails to exclude (e.g., bots) |
Rules |
CommitMessageRules | default | Commit message quality rules |
Ai |
AiOptions | default | AI provider configuration |
CommitMessageRules
| Property | Type | Default | Description |
|---|---|---|---|
MinSubjectLength |
int | 10 |
Minimum subject line length |
MaxSubjectLength |
int | 72 |
Maximum subject line length |
MinBodyLength |
int | 0 |
Minimum body length (0 = optional) |
RequireConventionalCommit |
bool | false |
Require conventional commit format |
RequireIssueReference |
bool | false |
Require issue/ticket reference |
BannedPhrases |
string[] | see below | Phrases that trigger warnings |
ConventionalTypes |
string[] | see below | Valid conventional commit types |
Default Banned Phrases: fix, wip, temp, asdf, test, stuff, things, changes, update, misc, minor, oops, commit, save, checkpoint, progress, done, finished
Default Conventional Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
AiOptions
| Property | Type | Default | Description |
|---|---|---|---|
ApiKey |
string | "" |
API key for the selected provider |
Provider |
string | "Claude" |
AI provider (Claude, OpenAI, Gemini, Qwen) |
Model |
string | "claude-sonnet-4-20250514" |
Model name for the provider |
IncludeDiffContext |
bool | true |
Include diff in AI prompt |
MaxDiffLines |
int | 200 |
Maximum diff lines to include |
MaxTokens |
int | 500 |
Maximum tokens in AI response |
RateLimitDelayMs |
int | 500 |
Delay between AI requests |
Interfaces
The library follows Interface Segregation Principle with focused interfaces:
| Interface | Purpose |
|---|---|
IRepositoryManager |
Repository discovery and registration |
ICommitAnalysisService |
Commit message analysis |
ISuggestionService |
AI suggestion generation |
ICommitRewriteService |
Commit message rewriting |
IGitPushService |
Push operations and tracking |
IHistoryHealthService |
Repository health analysis and cleanup |
IGitMessageImproverService |
Unified facade (composes all above) |
Supported AI Providers
| Provider | Models |
|---|---|
| Claude | claude-sonnet-4-20250514, claude-opus-4-20250514, claude-3-5-haiku-20241022 |
| OpenAI | gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo |
| Gemini | gemini-1.5-pro, gemini-1.5-flash, gemini-pro |
| Qwen | qwen-turbo, qwen-plus, qwen-max |
Thread Safety
GitOperationsServiceuses an LRU cache with TTL for repository handles- State operations are async and support cancellation tokens
- The service implements
IDisposable- ensure proper disposal
Dependencies
- .NET 9.0
- MarketAlly.LibGit2Sharp (git operations)
- MarketAlly.AIPlugin (AI provider abstraction)
- Microsoft.Extensions.DependencyInjection.Abstractions
License
MIT License - Copyright 2025 MarketAlly
Description
Languages
C#
100%