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

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}");
// 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

  • GitOperationsService uses 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
No description provided
http://www.marketally.com
Readme 194 KiB
Languages
C# 100%