Initial commit - MarketAlly.GitCommitEditor library
This commit is contained in:
commit
fc4bcf7b8c
72
.gitignore
vendored
Normal file
72
.gitignore
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
[Oo]ut/
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.rsuser
|
||||
*.userprefs
|
||||
launchSettings.json
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# NuGet
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
**/[Pp]ackages/*
|
||||
!**/[Pp]ackages/build/
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# MSBuild
|
||||
*.log
|
||||
*.binlog
|
||||
msbuild.binlog
|
||||
|
||||
# Test results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
coverage/
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.dmg
|
||||
._*
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Secrets
|
||||
appsettings.*.json
|
||||
!appsettings.json
|
||||
secrets.json
|
||||
*.pfx
|
||||
*.key
|
||||
1038
API_Reference.md
Normal file
1038
API_Reference.md
Normal file
File diff suppressed because it is too large
Load Diff
70
Extensions/ServiceCollectionExtensions.cs
Executable file
70
Extensions/ServiceCollectionExtensions.cs
Executable file
@ -0,0 +1,70 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Rewriters;
|
||||
using MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds GitMessageImprover services to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddGitMessageImprover(
|
||||
this IServiceCollection services,
|
||||
Action<GitImproverOptions> configureOptions)
|
||||
{
|
||||
var options = new GitImproverOptions();
|
||||
configureOptions(options);
|
||||
options.ValidateAndThrow();
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton(options.Rules);
|
||||
services.AddSingleton(options.Ai);
|
||||
|
||||
services.AddSingleton<IStateRepository>(sp =>
|
||||
new FileStateRepository(options.StateFilePath));
|
||||
|
||||
// Cost tracking service (singleton to accumulate costs across session)
|
||||
// Note: App should register ICostPersistenceProvider before calling AddGitMessageImprover
|
||||
// for persistence support, or costs will only be tracked for the current session
|
||||
services.AddSingleton<ICostTrackingService>(sp =>
|
||||
{
|
||||
var persistence = sp.GetService<ICostPersistenceProvider>();
|
||||
return new CostTrackingService(persistence);
|
||||
});
|
||||
|
||||
services.AddSingleton<ICommitMessageAnalyzer, CommitMessageAnalyzer>();
|
||||
services.AddSingleton<IGitOperationsService, GitOperationsService>();
|
||||
|
||||
// Use DynamicCommitRewriter which switches between AI and Mock at runtime
|
||||
// based on whether API key is configured
|
||||
services.AddSingleton<ICommitMessageRewriter>(sp => new DynamicCommitRewriter(
|
||||
sp.GetRequiredService<AiOptions>(),
|
||||
sp.GetRequiredService<ICostTrackingService>()));
|
||||
|
||||
// History health analysis services
|
||||
services.AddSingleton<HealthScoringWeights>();
|
||||
services.AddSingleton<ICommitAnalyzer>(sp =>
|
||||
new CommitAnalyzer(sp.GetRequiredService<CommitMessageRules>()));
|
||||
services.AddSingleton<IHistoryHealthAnalyzer, HistoryHealthAnalyzer>();
|
||||
services.AddSingleton<IHealthReportGenerator, HealthReportGenerator>();
|
||||
|
||||
// Cleanup executor
|
||||
services.AddSingleton<ICleanupExecutor>(sp => new CleanupExecutor(
|
||||
sp.GetRequiredService<IGitOperationsService>(),
|
||||
sp.GetRequiredService<ICommitMessageAnalyzer>(),
|
||||
sp.GetRequiredService<ICommitMessageRewriter>()));
|
||||
|
||||
// Git diagnostic service (AI-powered issue diagnosis)
|
||||
services.AddSingleton<IGitDiagnosticService>(sp =>
|
||||
new GitDiagnosticService(
|
||||
sp.GetRequiredService<AiOptions>(),
|
||||
sp.GetRequiredService<ICostTrackingService>()));
|
||||
|
||||
services.AddSingleton<IGitMessageImproverService, GitMessageImproverService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
94
MarketAlly.GitCommitEditor.csproj
Executable file
94
MarketAlly.GitCommitEditor.csproj
Executable file
@ -0,0 +1,94 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageOutputPath>C:\Users\logik\Dropbox\Nugets</PackageOutputPath>
|
||||
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>MarketAlly.GitCommitEditor</PackageId>
|
||||
<Title>MarketAlly Git Commit Editor - AI-Powered Commit Message Improvement</Title>
|
||||
<Version>1.0.0</Version>
|
||||
<Authors>David H Friedel Jr</Authors>
|
||||
<Company>MarketAlly</Company>
|
||||
<Description>A production-ready .NET 9 library for analyzing, improving, and rewriting git commit messages. Features include quality scoring against configurable rules, AI-powered suggestions via Claude/OpenAI/Gemini/Mistral/Qwen, commit history rewriting, push operations, and full DI support.</Description>
|
||||
<Copyright>Copyright © MarketAlly 2025</Copyright>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageTags>git;commit;message;ai;claude;openai;gemini;libgit2;conventional-commits;code-quality;devtools;dotnet9;net9</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/MarketAlly/GitCommitEditor</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/MarketAlly/GitCommitEditor</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageReleaseNotes>
|
||||
Version 1.0.0 - Initial Release:
|
||||
- Commit message quality analysis with configurable rules
|
||||
- AI-powered suggestion generation (Claude, OpenAI, Gemini, Mistral, Qwen)
|
||||
- Repository discovery and management
|
||||
- Commit rewriting (amend latest or reword older commits)
|
||||
- Push operations with force push support
|
||||
- LRU repository cache with TTL
|
||||
- State persistence between sessions
|
||||
- Full dependency injection support
|
||||
- Interface segregation (IRepositoryManager, ICommitAnalysisService, ISuggestionService, ICommitRewriteService, IGitPushService)
|
||||
</PackageReleaseNotes>
|
||||
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
|
||||
|
||||
<!-- Additional NuGet metadata -->
|
||||
<Summary>Analyze, improve, and rewrite git commit messages using configurable rules and AI-powered suggestions from Claude, OpenAI, Gemini, Mistral, or Qwen.</Summary>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
|
||||
<!-- Documentation and symbols for better debugging -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MarketAlly.AIPlugin" Version="2.6.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MarketAlly.LibGit2Sharp\MarketAlly.LibGit2Sharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="icon.png">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>true</Visible>
|
||||
</None>
|
||||
<None Include="README.md">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="API_Reference.md">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>docs\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Localization Resources -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Strings\LibStrings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>LibStrings.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Strings\LibStrings.*.resx">
|
||||
<DependentUpon>LibStrings.resx</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Strings\LibStrings.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>LibStrings.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
Models/BatchResult.cs
Normal file
10
Models/BatchResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class BatchResult
|
||||
{
|
||||
public int TotalProcessed { get; init; }
|
||||
public int Successful { get; init; }
|
||||
public int Failed { get; init; }
|
||||
public int Skipped { get; init; }
|
||||
public IReadOnlyList<RewriteOperation> Operations { get; init; } = [];
|
||||
}
|
||||
53
Models/BatchSuggestionResult.cs
Normal file
53
Models/BatchSuggestionResult.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of batch AI suggestion generation.
|
||||
/// </summary>
|
||||
public class BatchSuggestionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// All analyses that were processed.
|
||||
/// </summary>
|
||||
public IReadOnlyList<CommitAnalysis> Analyses { get; init; } = Array.Empty<CommitAnalysis>();
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits that got successful AI suggestions.
|
||||
/// </summary>
|
||||
public int SuccessCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits where AI failed to generate a different suggestion.
|
||||
/// </summary>
|
||||
public int FailedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Details of each failure for logging/display.
|
||||
/// </summary>
|
||||
public IReadOnlyList<SuggestionFailure> Failures { get; init; } = Array.Empty<SuggestionFailure>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details of a single suggestion failure.
|
||||
/// </summary>
|
||||
public class SuggestionFailure
|
||||
{
|
||||
/// <summary>
|
||||
/// The commit hash (short form).
|
||||
/// </summary>
|
||||
public string CommitHash { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The original message that couldn't be improved.
|
||||
/// </summary>
|
||||
public string OriginalMessage { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Why the suggestion failed.
|
||||
/// </summary>
|
||||
public string Reason { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Raw AI response for debugging (if available).
|
||||
/// </summary>
|
||||
public string? RawResponse { get; init; }
|
||||
}
|
||||
39
Models/BranchInfo.cs
Normal file
39
Models/BranchInfo.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a branch in a Git repository for display in TreeView.
|
||||
/// </summary>
|
||||
public class BranchInfo
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string FullName { get; init; } = string.Empty;
|
||||
public bool IsRemote { get; init; }
|
||||
public bool IsCurrentHead { get; init; }
|
||||
public string? LastCommitSha { get; init; }
|
||||
public DateTimeOffset? LastCommitDate { get; init; }
|
||||
public string? RemoteName { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hierarchical node for TreeView display - can represent a repo or a branch category.
|
||||
/// </summary>
|
||||
public class BranchTreeNode
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Icon { get; set; }
|
||||
public bool IsExpanded { get; set; } = true;
|
||||
public BranchInfo? Branch { get; set; }
|
||||
public ObservableCollection<BranchTreeNode> Children { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Display name including icon for current branch indicator
|
||||
/// </summary>
|
||||
public string DisplayName => Branch?.IsCurrentHead == true ? $"* {Name}" : Name;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node represents an actual branch (leaf node)
|
||||
/// </summary>
|
||||
public bool IsBranch => Branch != null;
|
||||
}
|
||||
23
Models/CommitAnalysis.cs
Normal file
23
Models/CommitAnalysis.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class CommitAnalysis
|
||||
{
|
||||
public required string RepoId { get; init; }
|
||||
public required string RepoName { get; init; }
|
||||
public required string RepoPath { get; init; }
|
||||
public required string CommitHash { get; init; }
|
||||
public string ShortHash => CommitHash.Length >= 7 ? CommitHash[..7] : CommitHash;
|
||||
public required string OriginalMessage { get; init; }
|
||||
public string? SuggestedMessage { get; set; }
|
||||
public required DateTimeOffset CommitDate { get; init; }
|
||||
public required string Author { get; init; }
|
||||
public required string AuthorEmail { get; init; }
|
||||
public required MessageQualityScore Quality { get; init; }
|
||||
public IReadOnlyList<string> FilesChanged { get; init; } = [];
|
||||
public string? DiffSummary { get; init; }
|
||||
public IReadOnlyDictionary<string, string> FileDiffs { get; init; } = new Dictionary<string, string>();
|
||||
public int LinesAdded { get; init; }
|
||||
public int LinesDeleted { get; init; }
|
||||
public AnalysisStatus Status { get; set; } = AnalysisStatus.Pending;
|
||||
public bool IsLatestCommit { get; init; }
|
||||
}
|
||||
17
Models/CommitContext.cs
Normal file
17
Models/CommitContext.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Context about the commit's actual changes for smarter analysis.
|
||||
/// </summary>
|
||||
public sealed class CommitContext
|
||||
{
|
||||
public int FilesChanged { get; init; }
|
||||
public int LinesAdded { get; init; }
|
||||
public int LinesDeleted { get; init; }
|
||||
public IReadOnlyList<string> FileNames { get; init; } = [];
|
||||
|
||||
public static CommitContext Empty => new();
|
||||
|
||||
public int TotalLinesChanged => LinesAdded + LinesDeleted;
|
||||
public bool HasSignificantChanges => FilesChanged > 0 || TotalLinesChanged > 0;
|
||||
}
|
||||
26
Models/Enums.cs
Normal file
26
Models/Enums.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public enum IssueSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public enum AnalysisStatus
|
||||
{
|
||||
Pending,
|
||||
Analyzed,
|
||||
Approved,
|
||||
Applied,
|
||||
Skipped,
|
||||
Failed
|
||||
}
|
||||
|
||||
public enum OperationStatus
|
||||
{
|
||||
Pending,
|
||||
Applied,
|
||||
Failed,
|
||||
RolledBack
|
||||
}
|
||||
207
Models/HistoryHealth/HealthEnums.cs
Normal file
207
Models/HistoryHealth/HealthEnums.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Overall health grade for a repository.
|
||||
/// </summary>
|
||||
public enum HealthGrade
|
||||
{
|
||||
/// <summary>90-100: Best practices followed.</summary>
|
||||
Excellent,
|
||||
/// <summary>70-89: Minor issues, generally healthy.</summary>
|
||||
Good,
|
||||
/// <summary>50-69: Noticeable issues, needs attention.</summary>
|
||||
Fair,
|
||||
/// <summary>30-49: Significant problems, cleanup recommended.</summary>
|
||||
Poor,
|
||||
/// <summary>0-29: Severe issues, immediate action required.</summary>
|
||||
Critical
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of duplicate commit detected.
|
||||
/// </summary>
|
||||
public enum DuplicateType
|
||||
{
|
||||
/// <summary>Same tree SHA (identical content).</summary>
|
||||
ExactTree,
|
||||
/// <summary>Same message, different trees.</summary>
|
||||
ExactMessage,
|
||||
/// <summary>Similar messages (fuzzy match).</summary>
|
||||
FuzzyMessage,
|
||||
/// <summary>Same patch ID (cherry-picked).</summary>
|
||||
CherryPick,
|
||||
/// <summary>Same commit rebased.</summary>
|
||||
RebasedCommit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Branch topology classification.
|
||||
/// </summary>
|
||||
public enum BranchTopologyType
|
||||
{
|
||||
/// <summary>Minimal branching, mostly linear.</summary>
|
||||
Linear,
|
||||
/// <summary>Standard develop + release branches.</summary>
|
||||
GitFlow,
|
||||
/// <summary>Healthy feature branches.</summary>
|
||||
Balanced,
|
||||
/// <summary>Excessive cross-merges.</summary>
|
||||
Tangled,
|
||||
/// <summary>Critical complexity.</summary>
|
||||
Spaghetti
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trend direction for metrics over time.
|
||||
/// </summary>
|
||||
public enum TrendDirection
|
||||
{
|
||||
Improving,
|
||||
Stable,
|
||||
Declining
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Severity of a health issue.
|
||||
/// </summary>
|
||||
public enum HealthIssueSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Level of automation for cleanup operations.
|
||||
/// </summary>
|
||||
public enum CleanupAutomationLevel
|
||||
{
|
||||
/// <summary>Can run with one click.</summary>
|
||||
FullyAutomated,
|
||||
/// <summary>Requires user review/approval.</summary>
|
||||
SemiAutomated,
|
||||
/// <summary>Requires manual git commands.</summary>
|
||||
Manual
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of cleanup operation.
|
||||
/// </summary>
|
||||
public enum CleanupType
|
||||
{
|
||||
SquashDuplicates,
|
||||
RewordMessages,
|
||||
SquashMerges,
|
||||
RebaseLinearize,
|
||||
ArchiveBranches,
|
||||
FixAuthorship,
|
||||
ConsolidateMerges
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Risk level for a cleanup operation.
|
||||
/// </summary>
|
||||
public enum RiskLevel
|
||||
{
|
||||
/// <summary>Safe, no history changes.</summary>
|
||||
None,
|
||||
/// <summary>Message-only changes.</summary>
|
||||
Low,
|
||||
/// <summary>Squashing, requires force push.</summary>
|
||||
Medium,
|
||||
/// <summary>Structural changes, potential conflicts.</summary>
|
||||
High,
|
||||
/// <summary>Major rewrite, backup required.</summary>
|
||||
VeryHigh
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status of a cleanup operation.
|
||||
/// </summary>
|
||||
public enum CleanupOperationStatus
|
||||
{
|
||||
Suggested,
|
||||
Approved,
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
Skipped
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estimated effort for a task.
|
||||
/// </summary>
|
||||
public enum EstimatedEffort
|
||||
{
|
||||
/// <summary>Less than 1 hour.</summary>
|
||||
Minimal,
|
||||
/// <summary>1-4 hours.</summary>
|
||||
Low,
|
||||
/// <summary>1-2 days.</summary>
|
||||
Medium,
|
||||
/// <summary>More than 2 days.</summary>
|
||||
High,
|
||||
/// <summary>More than 1 week.</summary>
|
||||
VeryHigh
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analysis depth for history scanning.
|
||||
/// </summary>
|
||||
public enum AnalysisDepth
|
||||
{
|
||||
/// <summary>Sample 200 commits, basic metrics only.</summary>
|
||||
Quick,
|
||||
/// <summary>1000 commits, all metrics.</summary>
|
||||
Standard,
|
||||
/// <summary>5000 commits, comprehensive.</summary>
|
||||
Deep,
|
||||
/// <summary>All commits (slow for large repos).</summary>
|
||||
Full
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report output format.
|
||||
/// </summary>
|
||||
public enum ReportFormat
|
||||
{
|
||||
Json,
|
||||
Markdown,
|
||||
Html,
|
||||
Console
|
||||
}
|
||||
|
||||
public static class HealthGradeExtensions
|
||||
{
|
||||
public static string GetDescription(this HealthGrade grade) => grade switch
|
||||
{
|
||||
HealthGrade.Excellent => "Repository follows git best practices. Minimal cleanup needed.",
|
||||
HealthGrade.Good => "Repository is generally healthy with minor issues.",
|
||||
HealthGrade.Fair => Str.HealthStatus_NeedsAttention,
|
||||
HealthGrade.Poor => "Repository has significant problems. Cleanup recommended.",
|
||||
HealthGrade.Critical => Str.HealthStatus_Critical,
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
public static string GetIcon(this HealthGrade grade) => grade switch
|
||||
{
|
||||
HealthGrade.Excellent => "✅",
|
||||
HealthGrade.Good => "👍",
|
||||
HealthGrade.Fair => "⚠️",
|
||||
HealthGrade.Poor => "❌",
|
||||
HealthGrade.Critical => "🚨",
|
||||
_ => "❓"
|
||||
};
|
||||
|
||||
public static HealthGrade FromScore(int score) => score switch
|
||||
{
|
||||
>= 90 => HealthGrade.Excellent,
|
||||
>= 70 => HealthGrade.Good,
|
||||
>= 50 => HealthGrade.Fair,
|
||||
>= 30 => HealthGrade.Poor,
|
||||
_ => HealthGrade.Critical
|
||||
};
|
||||
}
|
||||
144
Models/HistoryHealth/HealthMetrics.cs
Normal file
144
Models/HistoryHealth/HealthMetrics.cs
Normal file
@ -0,0 +1,144 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a group of duplicate commits.
|
||||
/// </summary>
|
||||
public sealed class DuplicateCommitGroup
|
||||
{
|
||||
public required string CanonicalMessage { get; init; }
|
||||
public required IReadOnlyList<string> CommitHashes { get; init; }
|
||||
public required DuplicateType Type { get; init; }
|
||||
public int InstanceCount => CommitHashes.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for duplicate commit detection.
|
||||
/// </summary>
|
||||
public sealed class DuplicateCommitMetrics
|
||||
{
|
||||
public int TotalCommitsAnalyzed { get; init; }
|
||||
public int TotalDuplicateGroups { get; init; }
|
||||
public int TotalDuplicateInstances { get; init; }
|
||||
public int ExactDuplicates { get; init; }
|
||||
public int CherryPicks { get; init; }
|
||||
public int FuzzyMatches { get; init; }
|
||||
public IReadOnlyList<DuplicateCommitGroup> DuplicateGroups { get; init; } = [];
|
||||
|
||||
public double DuplicateRatio => TotalCommitsAnalyzed > 0
|
||||
? (double)TotalDuplicateInstances / TotalCommitsAnalyzed * 100
|
||||
: 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for merge commit analysis.
|
||||
/// </summary>
|
||||
public sealed class MergeCommitMetrics
|
||||
{
|
||||
public int TotalCommits { get; init; }
|
||||
public int TotalMerges { get; init; }
|
||||
public int TrivialMerges { get; init; }
|
||||
public int ConflictMerges { get; init; }
|
||||
public int MergeFixCommits { get; init; }
|
||||
public int NullMerges { get; init; }
|
||||
public double AverageMergeComplexity { get; init; }
|
||||
public IReadOnlyList<string> MessyMergePatterns { get; init; } = [];
|
||||
public IReadOnlyList<string> MergeFixCommitHashes { get; init; } = [];
|
||||
|
||||
public int MergeRatio => TotalCommits > 0
|
||||
? (int)Math.Round((double)TotalMerges / TotalCommits * 100)
|
||||
: 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for branch complexity analysis.
|
||||
/// </summary>
|
||||
public sealed class BranchComplexityMetrics
|
||||
{
|
||||
public int TotalBranches { get; init; }
|
||||
public int ActiveBranches { get; init; }
|
||||
public int StaleBranches { get; init; }
|
||||
public int CrossMerges { get; init; }
|
||||
public double AverageBranchAge { get; init; }
|
||||
public double AverageBranchLength { get; init; }
|
||||
public int LongLivedBranches { get; init; }
|
||||
public BranchTopologyType Topology { get; init; }
|
||||
public IReadOnlyList<string> StaleBranchNames { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cluster of commits with similar quality scores.
|
||||
/// </summary>
|
||||
public sealed class QualityCluster
|
||||
{
|
||||
public DateTimeOffset StartDate { get; init; }
|
||||
public DateTimeOffset EndDate { get; init; }
|
||||
public int CommitCount { get; init; }
|
||||
public double AverageScore { get; init; }
|
||||
public string? PossibleCause { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distribution of commit message quality scores.
|
||||
/// </summary>
|
||||
public sealed class MessageQualityDistribution
|
||||
{
|
||||
public int TotalCommits { get; init; }
|
||||
public int Excellent { get; init; }
|
||||
public int Good { get; init; }
|
||||
public int Fair { get; init; }
|
||||
public int Poor { get; init; }
|
||||
public double AverageScore { get; init; }
|
||||
public double MedianScore { get; init; }
|
||||
public double StandardDeviation { get; init; }
|
||||
public TrendDirection Trend { get; init; }
|
||||
public IReadOnlyList<QualityCluster> Clusters { get; init; } = [];
|
||||
public IReadOnlyList<string> PoorCommitHashes { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for a single author.
|
||||
/// </summary>
|
||||
public sealed class AuthorStats
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public int CommitCount { get; init; }
|
||||
public double AverageMessageQuality { get; init; }
|
||||
public int MergeCommitCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for authorship analysis.
|
||||
/// </summary>
|
||||
public sealed class AuthorshipMetrics
|
||||
{
|
||||
public int TotalAuthors { get; init; }
|
||||
public int TotalCommits { get; init; }
|
||||
public int MissingEmailCount { get; init; }
|
||||
public int InvalidEmailCount { get; init; }
|
||||
public int BotCommits { get; init; }
|
||||
public IReadOnlyDictionary<string, AuthorStats> AuthorBreakdown { get; init; }
|
||||
= new Dictionary<string, AuthorStats>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component scores breakdown.
|
||||
/// </summary>
|
||||
public sealed class ComponentScores
|
||||
{
|
||||
public int DuplicateScore { get; init; }
|
||||
public int MergeScore { get; init; }
|
||||
public int BranchScore { get; init; }
|
||||
public int MessageScore { get; init; }
|
||||
public int AuthorshipScore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overall health score with breakdown.
|
||||
/// </summary>
|
||||
public sealed class HealthScore
|
||||
{
|
||||
public int OverallScore { get; init; }
|
||||
public HealthGrade Grade { get; init; }
|
||||
public required ComponentScores ComponentScores { get; init; }
|
||||
}
|
||||
148
Models/HistoryHealth/HealthReport.cs
Normal file
148
Models/HistoryHealth/HealthReport.cs
Normal file
@ -0,0 +1,148 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
/// <summary>
|
||||
/// A health issue detected in the repository.
|
||||
/// </summary>
|
||||
public sealed class HealthIssue
|
||||
{
|
||||
public required string Code { get; init; }
|
||||
public required string Category { get; init; }
|
||||
public required HealthIssueSeverity Severity { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public int ImpactScore { get; init; }
|
||||
public IReadOnlyList<string> AffectedCommits { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A recommendation for improving repository health.
|
||||
/// </summary>
|
||||
public sealed class HealthRecommendation
|
||||
{
|
||||
public required string Category { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required string Action { get; init; }
|
||||
public string? Rationale { get; init; }
|
||||
public int PriorityScore { get; init; }
|
||||
public EstimatedEffort Effort { get; init; }
|
||||
public int ExpectedScoreImprovement { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cleanup operation that can be performed.
|
||||
/// </summary>
|
||||
public sealed class CleanupOperation
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required CleanupType Type { get; init; }
|
||||
public required CleanupAutomationLevel AutomationLevel { get; init; }
|
||||
public EstimatedEffort Effort { get; init; }
|
||||
public RiskLevel Risk { get; init; }
|
||||
public int ExpectedScoreImprovement { get; init; }
|
||||
public IReadOnlyList<string> AffectedCommits { get; init; } = [];
|
||||
public string? GitCommand { get; init; }
|
||||
public CleanupOperationStatus Status { get; set; } = CleanupOperationStatus.Suggested;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup suggestions organized by automation level.
|
||||
/// </summary>
|
||||
public sealed class CleanupSuggestions
|
||||
{
|
||||
public IReadOnlyList<CleanupOperation> AutomatedOperations { get; init; } = [];
|
||||
public IReadOnlyList<CleanupOperation> SemiAutomatedOperations { get; init; } = [];
|
||||
public IReadOnlyList<CleanupOperation> ManualOperations { get; init; } = [];
|
||||
|
||||
public int TotalOperations =>
|
||||
AutomatedOperations.Count + SemiAutomatedOperations.Count + ManualOperations.Count;
|
||||
|
||||
public int TotalExpectedImprovement =>
|
||||
AutomatedOperations.Sum(o => o.ExpectedScoreImprovement) +
|
||||
SemiAutomatedOperations.Sum(o => o.ExpectedScoreImprovement) +
|
||||
ManualOperations.Sum(o => o.ExpectedScoreImprovement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete history health analysis results.
|
||||
/// </summary>
|
||||
public sealed class HistoryHealthAnalysis
|
||||
{
|
||||
public required string RepoPath { get; init; }
|
||||
public required string RepoName { get; init; }
|
||||
public required string CurrentBranch { get; init; }
|
||||
public DateTimeOffset AnalyzedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public int CommitsAnalyzed { get; init; }
|
||||
public DateTimeOffset? OldestCommitDate { get; init; }
|
||||
public DateTimeOffset? NewestCommitDate { get; init; }
|
||||
|
||||
// Metrics
|
||||
public required DuplicateCommitMetrics Duplicates { get; init; }
|
||||
public required MergeCommitMetrics MergeMetrics { get; init; }
|
||||
public required BranchComplexityMetrics BranchMetrics { get; init; }
|
||||
public required MessageQualityDistribution MessageDistribution { get; init; }
|
||||
public required AuthorshipMetrics AuthorshipMetrics { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete health report with scoring, issues, and recommendations.
|
||||
/// </summary>
|
||||
public sealed class HistoryHealthReport
|
||||
{
|
||||
public required string RepoId { get; init; }
|
||||
public required string RepoName { get; init; }
|
||||
public required string RepoPath { get; init; }
|
||||
public required string CurrentBranch { get; init; }
|
||||
public DateTimeOffset GeneratedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public int CommitsAnalyzed { get; init; }
|
||||
|
||||
// Summary
|
||||
public required HealthScore Score { get; init; }
|
||||
|
||||
// Detailed metrics
|
||||
public required DuplicateCommitMetrics DuplicateMetrics { get; init; }
|
||||
public required MergeCommitMetrics MergeMetrics { get; init; }
|
||||
public required BranchComplexityMetrics BranchMetrics { get; init; }
|
||||
public required MessageQualityDistribution MessageDistribution { get; init; }
|
||||
public required AuthorshipMetrics AuthorshipMetrics { get; init; }
|
||||
|
||||
// Issues and recommendations
|
||||
public IReadOnlyList<HealthIssue> Issues { get; init; } = [];
|
||||
public IReadOnlyList<HealthRecommendation> Recommendations { get; init; } = [];
|
||||
|
||||
// Cleanup opportunities
|
||||
public CleanupSuggestions? CleanupSuggestions { get; init; }
|
||||
|
||||
// Convenience properties
|
||||
public int CriticalIssueCount => Issues.Count(i => i.Severity == HealthIssueSeverity.Critical);
|
||||
public int ErrorCount => Issues.Count(i => i.Severity == HealthIssueSeverity.Error);
|
||||
public int WarningCount => Issues.Count(i => i.Severity == HealthIssueSeverity.Warning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a cleanup operation.
|
||||
/// </summary>
|
||||
public sealed class CleanupResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public int CommitsModified { get; init; }
|
||||
public int CommitsRemoved { get; init; }
|
||||
public string? BackupBranch { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
public HistoryHealthReport? UpdatedReport { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preview of what a cleanup operation will do.
|
||||
/// </summary>
|
||||
public sealed class CleanupPreview
|
||||
{
|
||||
public int CommitsAffected { get; init; }
|
||||
public int RefsAffected { get; init; }
|
||||
public IReadOnlyList<string> CommitsToModify { get; init; } = [];
|
||||
public IReadOnlyList<string> CommitsToRemove { get; init; } = [];
|
||||
public int ExpectedScoreImprovement { get; init; }
|
||||
public string Summary { get; init; } = string.Empty;
|
||||
}
|
||||
106
Models/HistoryHealth/HistoryAnalysisOptions.cs
Normal file
106
Models/HistoryHealth/HistoryAnalysisOptions.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Options for history health analysis.
|
||||
/// </summary>
|
||||
public sealed class HistoryAnalysisOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Analysis depth - affects number of commits analyzed.
|
||||
/// </summary>
|
||||
public AnalysisDepth Depth { get; set; } = AnalysisDepth.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum commits to analyze. Overrides Depth if set.
|
||||
/// </summary>
|
||||
public int? MaxCommitsToAnalyze { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only analyze commits since this date.
|
||||
/// </summary>
|
||||
public DateTimeOffset? AnalyzeSince { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include duplicate commit detection.
|
||||
/// </summary>
|
||||
public bool IncludeDuplicateDetection { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Include branch complexity analysis.
|
||||
/// </summary>
|
||||
public bool IncludeBranchAnalysis { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Include message quality distribution.
|
||||
/// </summary>
|
||||
public bool IncludeMessageDistribution { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Branches to exclude from analysis.
|
||||
/// </summary>
|
||||
public string[] ExcludeBranches { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Generate cleanup suggestions.
|
||||
/// </summary>
|
||||
public bool GenerateCleanupSuggestions { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective max commits based on depth.
|
||||
/// </summary>
|
||||
public int EffectiveMaxCommits => MaxCommitsToAnalyze ?? Depth switch
|
||||
{
|
||||
AnalysisDepth.Quick => 200,
|
||||
AnalysisDepth.Standard => 1000,
|
||||
AnalysisDepth.Deep => 5000,
|
||||
AnalysisDepth.Full => int.MaxValue,
|
||||
_ => 1000
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scoring weights for health calculation.
|
||||
/// </summary>
|
||||
public sealed class HealthScoringWeights
|
||||
{
|
||||
public double DuplicateWeight { get; set; } = 0.20;
|
||||
public double MergeWeight { get; set; } = 0.25;
|
||||
public double BranchWeight { get; set; } = 0.20;
|
||||
public double MessageWeight { get; set; } = 0.25;
|
||||
public double AuthorshipWeight { get; set; } = 0.10;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
var sum = DuplicateWeight + MergeWeight + BranchWeight + MessageWeight + AuthorshipWeight;
|
||||
if (Math.Abs(sum - 1.0) > 0.001)
|
||||
throw new InvalidOperationException(Str.Validation_WeightsSum(sum));
|
||||
}
|
||||
|
||||
public static HealthScoringWeights Default => new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for duplicate detection.
|
||||
/// </summary>
|
||||
public sealed class DuplicateDetectionOptions
|
||||
{
|
||||
public bool DetectExactTreeDuplicates { get; set; } = true;
|
||||
public bool DetectCherryPicks { get; set; } = true;
|
||||
public bool DetectFuzzyMatches { get; set; } = true;
|
||||
public int FuzzyMatchThreshold { get; set; } = 3;
|
||||
public int TimeWindowMinutes { get; set; } = 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress information during analysis.
|
||||
/// </summary>
|
||||
public sealed class AnalysisProgress
|
||||
{
|
||||
public string CurrentStage { get; init; } = string.Empty;
|
||||
public int PercentComplete { get; init; }
|
||||
public int CommitsProcessed { get; init; }
|
||||
public int TotalCommits { get; init; }
|
||||
public string? CurrentItem { get; init; }
|
||||
}
|
||||
51
Models/ImproverState.cs
Normal file
51
Models/ImproverState.cs
Normal file
@ -0,0 +1,51 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class ImproverState
|
||||
{
|
||||
private const int DefaultMaxHistorySize = 1000;
|
||||
private const int DefaultMaxHistoryAgeDays = 90;
|
||||
|
||||
public List<ManagedRepo> Repos { get; set; } = [];
|
||||
public List<RewriteOperation> History { get; set; } = [];
|
||||
public Dictionary<string, string> LastAnalyzedCommits { get; set; } = [];
|
||||
public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Prunes history to keep only recent entries within size and age limits.
|
||||
/// </summary>
|
||||
/// <param name="maxSize">Maximum number of history entries to retain.</param>
|
||||
/// <param name="maxAgeDays">Maximum age in days for history entries.</param>
|
||||
/// <returns>Number of entries removed.</returns>
|
||||
public int PruneHistory(int maxSize = DefaultMaxHistorySize, int maxAgeDays = DefaultMaxHistoryAgeDays)
|
||||
{
|
||||
var initialCount = History.Count;
|
||||
var cutoffDate = DateTimeOffset.UtcNow.AddDays(-maxAgeDays);
|
||||
|
||||
// Remove entries older than max age
|
||||
History.RemoveAll(h => h.CreatedAt < cutoffDate);
|
||||
|
||||
// If still over size limit, keep only the most recent entries
|
||||
if (History.Count > maxSize)
|
||||
{
|
||||
var sorted = History.OrderByDescending(h => h.CreatedAt).ToList();
|
||||
History.Clear();
|
||||
History.AddRange(sorted.Take(maxSize));
|
||||
}
|
||||
|
||||
return initialCount - History.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes history entries for repositories that are no longer registered.
|
||||
/// </summary>
|
||||
/// <returns>Number of orphaned entries removed.</returns>
|
||||
public int RemoveOrphanedHistory()
|
||||
{
|
||||
var repoIds = Repos.Select(r => r.Id).ToHashSet();
|
||||
var initialCount = History.Count;
|
||||
|
||||
History.RemoveAll(h => !repoIds.Contains(h.RepoId));
|
||||
|
||||
return initialCount - History.Count;
|
||||
}
|
||||
}
|
||||
17
Models/ManagedRepo.cs
Normal file
17
Models/ManagedRepo.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class ManagedRepo
|
||||
{
|
||||
public string Id { get; init; } = Guid.NewGuid().ToString("N")[..8];
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Path { get; init; } = string.Empty;
|
||||
public string? RemoteUrl { get; init; }
|
||||
public string? CurrentBranch { get; init; }
|
||||
public DateTimeOffset AddedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset? LastScannedAt { get; set; }
|
||||
public DateTimeOffset? LastAnalyzedAt { get; set; }
|
||||
public int TotalCommits { get; set; }
|
||||
public int CommitsNeedingImprovement { get; set; }
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
8
Models/MessageQualityScore.cs
Normal file
8
Models/MessageQualityScore.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class MessageQualityScore
|
||||
{
|
||||
public int OverallScore { get; init; }
|
||||
public IReadOnlyList<QualityIssue> Issues { get; init; } = [];
|
||||
public bool NeedsImprovement => OverallScore < 70 || Issues.Any();
|
||||
}
|
||||
8
Models/QualityIssue.cs
Normal file
8
Models/QualityIssue.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class QualityIssue
|
||||
{
|
||||
public required IssueSeverity Severity { get; init; }
|
||||
public required string Code { get; init; }
|
||||
public required string Message { get; init; }
|
||||
}
|
||||
57
Models/Results.cs
Normal file
57
Models/Results.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of a push operation.
|
||||
/// </summary>
|
||||
public sealed record GitPushResult(bool Success, string Message)
|
||||
{
|
||||
public static GitPushResult Ok(string? message = null) => new(true, message ?? Str.Service_PushSuccess);
|
||||
public static GitPushResult Fail(string message) => new(false, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracking information for a branch.
|
||||
/// </summary>
|
||||
public sealed record TrackingInfo(
|
||||
string? UpstreamBranch,
|
||||
int? AheadBy,
|
||||
int? BehindBy)
|
||||
{
|
||||
public static TrackingInfo None => new(null, null, null);
|
||||
public bool HasUpstream => UpstreamBranch != null;
|
||||
public bool IsInSync => AheadBy == 0 && BehindBy == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of an AI suggestion operation.
|
||||
/// </summary>
|
||||
public sealed record SuggestionResult(
|
||||
CommitAnalysis? Analysis,
|
||||
string? Suggestion,
|
||||
string? ErrorMessage = null,
|
||||
string? RawResponse = null,
|
||||
bool ReturnedOriginal = false,
|
||||
int InputTokens = 0,
|
||||
int OutputTokens = 0,
|
||||
decimal EstimatedCost = 0)
|
||||
{
|
||||
public bool Success => ErrorMessage == null && Suggestion != null;
|
||||
public int TotalTokens => InputTokens + OutputTokens;
|
||||
|
||||
public static SuggestionResult Ok(CommitAnalysis analysis, string suggestion, int inputTokens = 0, int outputTokens = 0, decimal cost = 0)
|
||||
=> new(analysis, suggestion, InputTokens: inputTokens, OutputTokens: outputTokens, EstimatedCost: cost);
|
||||
|
||||
public static SuggestionResult Succeeded(string suggestion, int inputTokens = 0, int outputTokens = 0, decimal cost = 0)
|
||||
=> new(null, suggestion, InputTokens: inputTokens, OutputTokens: outputTokens, EstimatedCost: cost);
|
||||
|
||||
public static SuggestionResult Fail(CommitAnalysis analysis, string error)
|
||||
=> new(analysis, null, error);
|
||||
|
||||
public static SuggestionResult Failed(string error, string? rawResponse = null)
|
||||
=> new(null, null, error, rawResponse);
|
||||
|
||||
public static SuggestionResult FailedWithOriginal(string originalMessage, string? rawResponse = null, int inputTokens = 0, int outputTokens = 0, decimal cost = 0)
|
||||
=> new(null, originalMessage, Str.Service_AiFallback, rawResponse, ReturnedOriginal: true, InputTokens: inputTokens, OutputTokens: outputTokens, EstimatedCost: cost);
|
||||
}
|
||||
17
Models/RewriteOperation.cs
Normal file
17
Models/RewriteOperation.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
public sealed class RewriteOperation
|
||||
{
|
||||
public string Id { get; init; } = Guid.NewGuid().ToString("N")[..12];
|
||||
public required string RepoId { get; init; }
|
||||
public required string RepoPath { get; init; }
|
||||
public required string CommitHash { get; init; }
|
||||
public required string OriginalMessage { get; init; }
|
||||
public required string NewMessage { get; init; }
|
||||
public string? NewCommitHash { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset? AppliedAt { get; set; }
|
||||
public OperationStatus Status { get; set; } = OperationStatus.Pending;
|
||||
public string? ErrorMessage { get; set; }
|
||||
public bool IsLatestCommit { get; init; }
|
||||
}
|
||||
171
Models/RewriteSafetyInfo.cs
Normal file
171
Models/RewriteSafetyInfo.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Safety information for a batch rewrite operation.
|
||||
/// </summary>
|
||||
public sealed class RewriteSafetyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the repository has uncommitted changes.
|
||||
/// </summary>
|
||||
public bool HasUncommittedChanges { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether any commits to be rewritten have been pushed to remote.
|
||||
/// </summary>
|
||||
public bool HasPushedCommits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits that have been pushed to remote.
|
||||
/// </summary>
|
||||
public int PushedCommitCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits that are local only.
|
||||
/// </summary>
|
||||
public int LocalOnlyCommitCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total commits to be rewritten.
|
||||
/// </summary>
|
||||
public int TotalCommitCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current branch tracks a remote branch.
|
||||
/// </summary>
|
||||
public bool HasRemoteTracking { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The remote tracking branch name, if any.
|
||||
/// </summary>
|
||||
public string? RemoteTrackingBranch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits ahead of remote.
|
||||
/// </summary>
|
||||
public int? AheadOfRemote { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits behind remote.
|
||||
/// </summary>
|
||||
public int? BehindRemote { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether a backup branch was created.
|
||||
/// </summary>
|
||||
public bool BackupBranchCreated { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the backup branch, if created.
|
||||
/// </summary>
|
||||
public string? BackupBranchName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the operation can proceed safely.
|
||||
/// </summary>
|
||||
public bool CanProceedSafely => !HasUncommittedChanges && !HasPushedCommits;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the operation can proceed with warnings.
|
||||
/// </summary>
|
||||
public bool CanProceedWithWarnings => !HasUncommittedChanges;
|
||||
|
||||
/// <summary>
|
||||
/// Gets warning messages based on the safety info.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> GetWarnings()
|
||||
{
|
||||
var warnings = new List<string>();
|
||||
|
||||
if (HasUncommittedChanges)
|
||||
{
|
||||
warnings.Add(Str.Safety_UncommittedChanges);
|
||||
}
|
||||
|
||||
if (HasPushedCommits)
|
||||
{
|
||||
warnings.Add(Str.Safety_PushedCommits(PushedCommitCount));
|
||||
}
|
||||
|
||||
if (BehindRemote > 0)
|
||||
{
|
||||
warnings.Add(Str.Safety_BehindRemote(BehindRemote.Value));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary description of the operation.
|
||||
/// </summary>
|
||||
public string GetSummary()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (LocalOnlyCommitCount > 0)
|
||||
{
|
||||
parts.Add($"{LocalOnlyCommitCount} local commit(s)");
|
||||
}
|
||||
|
||||
if (PushedCommitCount > 0)
|
||||
{
|
||||
parts.Add($"{PushedCommitCount} pushed commit(s)");
|
||||
}
|
||||
|
||||
return string.Join(" and ", parts) + " will be rewritten.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a batch rewrite execute operation.
|
||||
/// </summary>
|
||||
public sealed class BatchRewriteResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the overall operation succeeded.
|
||||
/// </summary>
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits successfully rewritten.
|
||||
/// </summary>
|
||||
public int SuccessCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits that failed to rewrite.
|
||||
/// </summary>
|
||||
public int FailedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of commits skipped.
|
||||
/// </summary>
|
||||
public int SkippedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed.
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether a force push is required.
|
||||
/// </summary>
|
||||
public bool RequiresForcePush { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The backup branch name if one was created.
|
||||
/// </summary>
|
||||
public string? BackupBranchName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Individual operation results.
|
||||
/// </summary>
|
||||
public IReadOnlyList<RewriteOperation> Operations { get; init; } = [];
|
||||
|
||||
public static BatchRewriteResult Failure(string error) => new()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = error
|
||||
};
|
||||
}
|
||||
65
Options/AiOptions.cs
Executable file
65
Options/AiOptions.cs
Executable file
@ -0,0 +1,65 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using MarketAlly.AIPlugin;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Model information for display in settings UI
|
||||
/// </summary>
|
||||
public sealed class ModelDisplayInfo
|
||||
{
|
||||
public string ModelId { get; init; } = string.Empty;
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string Tier { get; init; } = string.Empty;
|
||||
public decimal InputCostPer1MTokens { get; init; }
|
||||
public decimal OutputCostPer1MTokens { get; init; }
|
||||
public string Speed { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Formatted display string for picker: "DisplayName (Tier) - $X.XX/1M"
|
||||
/// </summary>
|
||||
public string FormattedDisplay => $"{DisplayName} ({Tier}) - ${InputCostPer1MTokens:F2}/1M in";
|
||||
|
||||
public override string ToString() => FormattedDisplay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI configuration options.
|
||||
/// For provider/model discovery, use IModelProviderService instead of static methods.
|
||||
/// </summary>
|
||||
public sealed class AiOptions
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// AI Provider: Claude, OpenAI, Gemini, Qwen
|
||||
/// </summary>
|
||||
public string Provider { get; set; } = "Claude";
|
||||
|
||||
/// <summary>
|
||||
/// Model name specific to the provider
|
||||
/// </summary>
|
||||
public string Model { get; set; } = ModelConstants.Claude.Sonnet4;
|
||||
|
||||
public bool IncludeDiffContext { get; set; } = true;
|
||||
public int MaxDiffLines { get; set; } = 200;
|
||||
public int MaxTokens { get; set; } = 500;
|
||||
public int RateLimitDelayMs { get; set; } = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Parses a provider string to AIProvider enum.
|
||||
/// </summary>
|
||||
public static AIProvider ParseProvider(string? provider)
|
||||
{
|
||||
return provider?.ToLowerInvariant() switch
|
||||
{
|
||||
"claude" or "anthropic" => AIProvider.Claude,
|
||||
"openai" or "gpt" => AIProvider.OpenAI,
|
||||
"gemini" or "google" => AIProvider.Gemini,
|
||||
"qwen" or "alibaba" => AIProvider.Qwen,
|
||||
_ => AIProvider.Claude
|
||||
};
|
||||
}
|
||||
}
|
||||
23
Options/CommitMessageRules.cs
Normal file
23
Options/CommitMessageRules.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace MarketAlly.GitCommitEditor.Options;
|
||||
|
||||
public sealed class CommitMessageRules
|
||||
{
|
||||
public int MinSubjectLength { get; set; } = 10;
|
||||
public int MaxSubjectLength { get; set; } = 72;
|
||||
public int MinBodyLength { get; set; } = 0;
|
||||
public bool RequireConventionalCommit { get; set; } = false;
|
||||
public bool RequireIssueReference { get; set; } = false;
|
||||
|
||||
public string[] BannedPhrases { get; set; } =
|
||||
[
|
||||
"fix", "wip", "temp", "asdf", "test", "stuff", "things",
|
||||
"changes", "update", "updates", "misc", "minor", "oops",
|
||||
"commit", "save", "checkpoint", "progress", "done", "finished"
|
||||
];
|
||||
|
||||
public string[] ConventionalTypes { get; set; } =
|
||||
[
|
||||
"feat", "fix", "docs", "style", "refactor", "perf",
|
||||
"test", "build", "ci", "chore", "revert"
|
||||
];
|
||||
}
|
||||
89
Options/GitImproverOptions.cs
Normal file
89
Options/GitImproverOptions.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Options;
|
||||
|
||||
public sealed class GitImproverOptions : IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Root directory to scan for git repositories
|
||||
/// </summary>
|
||||
public string WorkspaceRoot { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Path to persist state between sessions
|
||||
/// </summary>
|
||||
public string StateFilePath { get; set; } = "git-improver-state.json";
|
||||
|
||||
/// <summary>
|
||||
/// Rules for analyzing commit message quality
|
||||
/// </summary>
|
||||
public CommitMessageRules Rules { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// AI configuration for generating suggestions
|
||||
/// </summary>
|
||||
public AiOptions Ai { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of commits to analyze per repo
|
||||
/// </summary>
|
||||
public int MaxCommitsPerRepo { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Only analyze commits newer than this date
|
||||
/// </summary>
|
||||
public DateTimeOffset? AnalyzeSince { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skip commits by these authors (useful for excluding bots)
|
||||
/// </summary>
|
||||
public string[] ExcludedAuthors { get; set; } = [];
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(WorkspaceRoot))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
Str.Validation_WorkspaceRequired,
|
||||
[nameof(WorkspaceRoot)]);
|
||||
}
|
||||
else if (!Directory.Exists(WorkspaceRoot))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
Str.Validation_WorkspaceNotFound(WorkspaceRoot),
|
||||
[nameof(WorkspaceRoot)]);
|
||||
}
|
||||
|
||||
if (MaxCommitsPerRepo <= 0)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
Str.Validation_MaxCommitsPositive,
|
||||
[nameof(MaxCommitsPerRepo)]);
|
||||
}
|
||||
|
||||
if (Rules == null)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
Str.Validation_RulesNull,
|
||||
[nameof(Rules)]);
|
||||
}
|
||||
|
||||
if (Ai == null)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
Str.Validation_AiOptionsNull,
|
||||
[nameof(Ai)]);
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidateAndThrow()
|
||||
{
|
||||
var results = Validate(new ValidationContext(this)).ToList();
|
||||
if (results.Count > 0)
|
||||
{
|
||||
var messages = string.Join("; ", results.Select(r => r.ErrorMessage));
|
||||
throw new ValidationException(Str.Validation_InvalidOptions(messages));
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Plugins/GenerateCommitMessagePlugin.cs
Normal file
135
Plugins/GenerateCommitMessagePlugin.cs
Normal file
@ -0,0 +1,135 @@
|
||||
using MarketAlly.AIPlugin;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Valid conventional commit types
|
||||
/// </summary>
|
||||
public enum CommitType
|
||||
{
|
||||
/// <summary>New feature</summary>
|
||||
feat,
|
||||
/// <summary>Bug fix</summary>
|
||||
fix,
|
||||
/// <summary>Documentation only</summary>
|
||||
docs,
|
||||
/// <summary>Formatting, whitespace (no code change)</summary>
|
||||
style,
|
||||
/// <summary>Code change that neither fixes a bug nor adds a feature</summary>
|
||||
refactor,
|
||||
/// <summary>Performance improvement</summary>
|
||||
perf,
|
||||
/// <summary>Adding or updating tests</summary>
|
||||
test,
|
||||
/// <summary>Build system or dependencies</summary>
|
||||
build,
|
||||
/// <summary>CI/CD configuration</summary>
|
||||
ci,
|
||||
/// <summary>Maintenance tasks, logging, tooling</summary>
|
||||
chore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result model for the commit message generation
|
||||
/// </summary>
|
||||
public class CommitMessageResult
|
||||
{
|
||||
public CommitType Type { get; set; } = CommitType.chore;
|
||||
public string? Scope { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted subject line: type(scope): description
|
||||
/// </summary>
|
||||
public string Subject => string.IsNullOrEmpty(Scope)
|
||||
? $"{Type}: {Description}"
|
||||
: $"{Type}({Scope}): {Description}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI Plugin for generating improved commit messages following conventional commit format
|
||||
/// </summary>
|
||||
[AIPlugin("GenerateCommitMessage", "Generates an improved git commit message based on the original message, changed files, and diff content. Returns a structured result with subject line and optional body.")]
|
||||
public class GenerateCommitMessagePlugin : IAIPlugin
|
||||
{
|
||||
[AIParameter("The original commit message to improve", required: true)]
|
||||
public string OriginalMessage { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("List of file paths that were changed in the commit", required: true)]
|
||||
public List<string> FilesChanged { get; set; } = new();
|
||||
|
||||
[AIParameter("Number of lines added in the commit", required: true)]
|
||||
public int LinesAdded { get; set; }
|
||||
|
||||
[AIParameter("Number of lines deleted in the commit", required: true)]
|
||||
public int LinesDeleted { get; set; }
|
||||
|
||||
[AIParameter("Summary of the diff/changes (truncated)", required: false)]
|
||||
public string? DiffSummary { get; set; }
|
||||
|
||||
[AIParameter("List of quality issues detected in the original message", required: false)]
|
||||
public List<string>? QualityIssues { get; set; }
|
||||
|
||||
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
||||
{
|
||||
["originalMessage"] = typeof(string),
|
||||
["filesChanged"] = typeof(List<string>),
|
||||
["linesAdded"] = typeof(int),
|
||||
["linesDeleted"] = typeof(int),
|
||||
["diffSummary"] = typeof(string),
|
||||
["qualityIssues"] = typeof(List<string>)
|
||||
};
|
||||
|
||||
public Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
||||
{
|
||||
var result = new CommitMessageResult
|
||||
{
|
||||
Type = CommitType.chore,
|
||||
Description = OriginalMessage,
|
||||
Body = string.Empty
|
||||
};
|
||||
|
||||
return Task.FromResult(new AIPluginResult(result, "Commit message generated"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI Plugin that the AI calls to return the generated commit message
|
||||
/// </summary>
|
||||
[AIPlugin("ReturnCommitMessage", "Returns the generated commit message. Call this after analyzing the commit to provide the improved message in conventional commit format.")]
|
||||
public class ReturnCommitMessagePlugin : IAIPlugin
|
||||
{
|
||||
[AIParameter("The commit type", required: true)]
|
||||
public CommitType Type { get; set; } = CommitType.chore;
|
||||
|
||||
[AIParameter("Optional scope/area of the codebase affected (e.g., 'api', 'ui', 'auth'). Omit if not applicable.", required: false)]
|
||||
public string? Scope { get; set; }
|
||||
|
||||
[AIParameter("Short description of the change (max 60 chars, imperative mood, no period). Example: 'add user authentication'", required: true)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("Optional extended description explaining what changed and why. Leave empty if the subject is self-explanatory.", required: false)]
|
||||
public string Body { get; set; } = string.Empty;
|
||||
|
||||
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
||||
{
|
||||
["type"] = typeof(CommitType),
|
||||
["scope"] = typeof(string),
|
||||
["description"] = typeof(string),
|
||||
["body"] = typeof(string)
|
||||
};
|
||||
|
||||
public Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
||||
{
|
||||
var result = new CommitMessageResult
|
||||
{
|
||||
Type = Type,
|
||||
Scope = Scope,
|
||||
Description = Description,
|
||||
Body = Body ?? string.Empty
|
||||
};
|
||||
|
||||
return Task.FromResult(new AIPluginResult(result, "Commit message returned"));
|
||||
}
|
||||
}
|
||||
92
Plugins/GitDiagnosticPlugin.cs
Executable file
92
Plugins/GitDiagnosticPlugin.cs
Executable file
@ -0,0 +1,92 @@
|
||||
using MarketAlly.AIPlugin;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Risk level for git fix operations
|
||||
/// </summary>
|
||||
public enum RiskLevel
|
||||
{
|
||||
/// <summary>Safe operation, no data loss risk</summary>
|
||||
Low,
|
||||
/// <summary>Some risk, review before executing</summary>
|
||||
Medium,
|
||||
/// <summary>Potential data loss, use with caution</summary>
|
||||
High
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result model for git issue diagnosis
|
||||
/// </summary>
|
||||
public class GitDiagnosisResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Short summary of the problem (1-2 sentences)
|
||||
/// </summary>
|
||||
public string Problem { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Explanation of why this happened
|
||||
/// </summary>
|
||||
public string Cause { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The recommended fix command(s) to run
|
||||
/// </summary>
|
||||
public string FixCommand { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Warning about potential data loss or side effects (if any)
|
||||
/// </summary>
|
||||
public string Warning { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Risk level of the fix operation
|
||||
/// </summary>
|
||||
public RiskLevel RiskLevel { get; set; } = RiskLevel.Low;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI Plugin that the AI calls to return the git diagnosis
|
||||
/// </summary>
|
||||
[AIPlugin("ReturnGitDiagnosis", "Returns the diagnosis of a git issue with the problem summary, cause, fix command, and any warnings.")]
|
||||
public class ReturnGitDiagnosisPlugin : IAIPlugin
|
||||
{
|
||||
[AIParameter("Short summary of the problem (1-2 sentences)", required: true)]
|
||||
public string Problem { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("Explanation of why this happened", required: true)]
|
||||
public string Cause { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("The exact git command(s) to fix the issue. Use newlines to separate multiple commands.", required: true)]
|
||||
public string FixCommand { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("Warning about potential data loss or side effects. Leave empty if no warnings.", required: false)]
|
||||
public string Warning { get; set; } = string.Empty;
|
||||
|
||||
[AIParameter("Risk level of the fix operation", required: true)]
|
||||
public RiskLevel RiskLevel { get; set; } = RiskLevel.Low;
|
||||
|
||||
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
||||
{
|
||||
["problem"] = typeof(string),
|
||||
["cause"] = typeof(string),
|
||||
["fixCommand"] = typeof(string),
|
||||
["warning"] = typeof(string),
|
||||
["riskLevel"] = typeof(RiskLevel)
|
||||
};
|
||||
|
||||
public Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
||||
{
|
||||
var result = new GitDiagnosisResult
|
||||
{
|
||||
Problem = Problem,
|
||||
Cause = Cause,
|
||||
FixCommand = FixCommand,
|
||||
Warning = Warning ?? string.Empty,
|
||||
RiskLevel = RiskLevel
|
||||
};
|
||||
|
||||
return Task.FromResult(new AIPluginResult(result, "Git diagnosis returned"));
|
||||
}
|
||||
}
|
||||
290
README.md
Normal file
290
README.md
Normal file
@ -0,0 +1,290 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
dotnet add package MarketAlly.GitCommitEditor
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### With Dependency Injection (Recommended)
|
||||
|
||||
```csharp
|
||||
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)
|
||||
|
||||
```csharp
|
||||
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
|
||||
|
||||
```csharp
|
||||
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
|
||||
|
||||
```csharp
|
||||
// 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)
|
||||
|
||||
```csharp
|
||||
// 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)
|
||||
|
||||
```csharp
|
||||
// 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
|
||||
|
||||
```csharp
|
||||
// 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
|
||||
|
||||
```csharp
|
||||
// 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
|
||||
1145
Resources/Strings/LibStrings.Designer.cs
generated
Executable file
1145
Resources/Strings/LibStrings.Designer.cs
generated
Executable file
File diff suppressed because it is too large
Load Diff
421
Resources/Strings/LibStrings.de.resx
Normal file
421
Resources/Strings/LibStrings.de.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Commit-Nachricht ist leer</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Betreff hat {0} Zeichen, Minimum ist {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Betreff hat {0} Zeichen, empfohlenes Maximum ist {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Betreff verwendet nicht-aussagekräftige Formulierung: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Nachricht folgt nicht dem konventionellen Commit-Format (typ: betreff)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Unbekannter konventioneller Commit-Typ: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Keine Issue-Referenz gefunden (z.B. #123 oder JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Betreff sollte mit einem Großbuchstaben beginnen</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Betreff sollte nicht mit einem Punkt enden</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Verwenden Sie den Imperativ: '{0}' → '{1}' (z.B. 'Add' nicht 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Textkörper hat {0} Zeichen, Minimum ist {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Fügen Sie eine Leerzeile zwischen Betreff und Textkörper ein</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' beschreibt nicht, was in {1} Dateien geändert wurde</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Nachricht ist zu vage für {0} geänderte Dateien - beschreiben Sie WAS sich geändert hat</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Große Änderung ({0} Dateien, {1} Zeilen) verdient eine aussagekräftigere Nachricht</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Größere Änderung ({0} Dateien) sollte einen Textkörper enthalten, der das Warum erklärt</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Erwägen Sie zu erwähnen, welcher Bereich sich geändert hat (Dateien: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Keine Commits im Repository</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit nicht gefunden: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Ziel-Commit ist kein Vorgänger von HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Konnte keine Ziel-Commits im Repository finden</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Commit-Erstellung fehlgeschlagen: Eltern-Konflikt für Commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>HEAD konnte nicht auf neuen Commit {0} aktualisiert werden</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Datenträger-Verifizierung fehlgeschlagen: HEAD sollte {0} sein, ist aber {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Alter Commit {0} ist nach dem Umschreiben immer noch von HEAD aus erreichbar</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Git-Fehler: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' nicht gefunden</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Kein Upstream-Branch konfiguriert und kein 'origin' Remote gefunden.
|
||||
|
||||
Setzen Sie das Tracking manuell mit: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force-Push erfolgreich</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force-Push zu origin/{0} erfolgreich</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Git-Prozess konnte nicht gestartet werden</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force-Push erfolgreich (über Git-Befehl)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push fehlgeschlagen: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Git-Befehl konnte nicht ausgeführt werden: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Kein Upstream-Branch konfiguriert. Setzen Sie das Tracking mit: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push abgelehnt: non-fast-forward. Ziehen Sie zuerst Änderungen oder verwenden Sie Force-Push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push erfolgreich (über Git-Befehl)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Einige Commits wurden bereits gepusht. Aktivieren Sie 'AllowPushedCommits', um fortzufahren.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Bereinigungstyp '{0}' ist noch nicht implementiert</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Commit-Historie wird neu aufgebaut...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>{0} Commits werden neu aufgebaut...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} wird verarbeitet...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Branch-Referenz wird aktualisiert...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Merge-Commits werden zusammengeführt...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Duplikat-Commits konnten nicht entfernt werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Benötige mindestens 2 Commits zum Zusammenführen</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>Keine Commits auf dem aktuellen Branch gefunden</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Keine passenden Commits zum Entfernen gefunden</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Keine Commits zum Zusammenführen angegeben</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Keine passenden Merge-Commits zum Zusammenführen gefunden</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Merge-Commits konnten nicht zusammengeführt werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Keine Commits zum Korrigieren angegeben</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Autorenschaft konnte nicht korrigiert werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Merge-Fix-Commits werden konsolidiert...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Keine Fix-Commits zum Konsolidieren</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Keine passenden Fix-Commits zum Konsolidieren gefunden</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Merge-Fix-Commits konnten nicht konsolidiert werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Veraltete Branches werden archiviert...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Branch {0} wird verarbeitet...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archivierung abgeschlossen</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Branches konnten nicht archiviert werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Branch-Struktur wird analysiert...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>{0} Commits zum Linearisieren gefunden...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>{0} Commits werden linearisiert ({1} Merges werden entfernt)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} wird neu aufgebaut...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Endzustand wird abgeglichen...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>Historie ist bereits linear - keine Merge-Commits gefunden</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearisierung abgeschlossen</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Historie konnte nicht linearisiert werden: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Es werden {0} Commit-Nachricht(en) umformuliert, um die Qualität zu verbessern.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Es werden {0} doppelte Commits zu 1 zusammengeführt.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Es werden {0} Merge-Fix-Commits konsolidiert.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Autorenschaft wird bei {0} Commit(s) korrigiert.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Es werden {0} triviale Merges konsolidiert.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Veraltete Branches werden archiviert (löschen wenn gemergt, sonst taggen).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Historie wird durch Entfernen von Merge-Commits und Sortierung nach Datum linearisiert.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Es werden {0} Commit(s) verarbeitet.</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Abgleich: Endzustand nach Linearisierung mergen</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot ist erforderlich</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRoot-Verzeichnis existiert nicht: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo muss größer als 0 sein</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Regeln dürfen nicht null sein</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>KI-Optionen dürfen nicht null sein</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>Ungültige GitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Gewichtungen müssen sich zu 1,0 summieren (aktuell: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Unbekannter Fehler</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repository nicht registriert</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Commits können nicht umgeschrieben werden, wenn es nicht committete Änderungen gibt. Bitte committen oder stashen Sie Ihre Änderungen zuerst.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repository nicht gefunden: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Keine vorgeschlagene Nachricht verfügbar</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repository nicht registriert: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>API-Schlüssel ist nicht konfiguriert. Bitte setzen Sie Ihren API-Schlüssel in den Einstellungen.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>KI-Analyse fehlgeschlagen</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>KI hat keine strukturierte Ausgabe zurückgegeben - auf ursprüngliche Nachricht zurückgefallen</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push erfolgreich</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Commits werden geladen</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Duplikate werden erkannt</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Merge-Commits werden analysiert</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Branch-Komplexität wird analysiert</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Nachrichtenqualität wird analysiert</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Autorenschaft wird analysiert</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Abgeschlossen</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Doppelte Commits mit identischem Inhalt</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>{0} Gruppen von Commits mit identischem Dateiinhalt gefunden ({1} redundante Commits). Diese können sicher zusammengeführt werden, da sie denselben Tree-SHA haben.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits mit doppelten Nachrichten</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>{0} Gruppen von Commits mit identischen Nachrichten aber unterschiedlichen Code-Änderungen gefunden ({1} Commits). Erwägen Sie aussagekräftigere Nachrichten zur Unterscheidung der Änderungen.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Übermäßig viele Merge-Commits</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Hoher Merge-Commit-Anteil</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Ihr Repository hat einen Merge-Commit-Anteil von {0}% ({1}/{2} Commits). Erwägen Sie einen Rebase-Workflow oder Squash-Merges.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Merge-Fix-Commits erkannt</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>{0} Commits mit Nachrichten wie 'fix merge' nach Merges erkannt.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-Merges zwischen Branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>{0} Cross-Merges zwischen Feature-Branches erkannt. Verwenden Sie Feature-Branches, die nur in main gemergt werden.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Veraltete Branches</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>{0} Branches ohne Aktivität in den letzten 30+ Tagen gefunden.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>Sie haben nicht committete Änderungen. Bitte committen oder stashen Sie diese zuerst.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} Commit(s) wurden bereits auf das Remote gepusht. Das Umschreiben erfordert einen Force-Push und kann Mitarbeiter beeinträchtigen.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Ihr Branch ist {0} Commit(s) hinter dem Remote zurück. Erwägen Sie zuerst zu pullen, um Konflikte zu vermeiden.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Repository hat erkennbare Probleme, die behoben werden sollten.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Repository erfordert sofortige Aufmerksamkeit. Historie ist schwer beschädigt.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.es.resx
Normal file
421
Resources/Strings/LibStrings.es.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>El mensaje del commit está vacío</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>El asunto tiene {0} caracteres, el mínimo es {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>El asunto tiene {0} caracteres, el máximo recomendado es {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>El asunto usa una frase poco descriptiva: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>El mensaje no sigue el formato conventional commit (tipo: asunto)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Tipo de conventional commit desconocido: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>No se encontró referencia a issue (ej., #123 o JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>El asunto debe comenzar con letra mayúscula</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>El asunto no debe terminar con punto</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Use modo imperativo: '{0}' → '{1}' (ej., 'Add' no 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>El cuerpo tiene {0} caracteres, el mínimo es {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Agregue una línea en blanco entre el asunto y el cuerpo</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' no describe lo que cambió en {1} archivos</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>El mensaje es demasiado vago para {0} archivos modificados - describa QUÉ cambió</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Cambio grande ({0} archivos, {1} líneas) merece un mensaje más descriptivo</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Cambio importante ({0} archivos) debe incluir un cuerpo explicando por qué</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Considere mencionar qué área cambió (archivos: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>No hay commits en el repositorio</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit no encontrado: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>El commit objetivo no es un ancestro de HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>No se pudieron encontrar commits objetivo en el repositorio</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Falló la creación del commit: discrepancia de parent para el commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>No se pudo actualizar HEAD al nuevo commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Falló la verificación en disco: HEAD debería ser {0} pero es {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>El commit antiguo {0} sigue siendo alcanzable desde HEAD después de la reescritura</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Error de git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' no encontrado</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>No hay branch upstream configurado y no se encontró remote 'origin'.
|
||||
|
||||
Configure el seguimiento manualmente con: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push exitoso</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force push realizado a origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>No se pudo iniciar el proceso de git</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push exitoso (vía comando git)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push falló: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>No se pudo ejecutar el comando git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>No hay branch upstream configurado. Configure el seguimiento con: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push rechazado: non-fast-forward. Obtenga los cambios primero o use force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push exitoso (vía comando git)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Algunos commits han sido pusheados. Habilite 'AllowPushedCommits' para continuar.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>El tipo de limpieza '{0}' aún no está implementado</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Reconstruyendo historial de commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Reconstruyendo {0} commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Procesando commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Actualizando referencia del branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Realizando squash de merge commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>No se pudieron eliminar commits duplicados: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Se necesitan al menos 2 commits para hacer squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>No se encontraron commits en el branch actual</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>No se encontraron commits coincidentes para eliminar</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>No se especificaron commits para hacer squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>No se encontraron merge commits coincidentes para hacer squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>No se pudo hacer squash de merge commits: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>No se especificaron commits para corregir</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>No se pudo corregir la autoría: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Consolidando commits de corrección de merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>No hay commits de corrección para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>No se encontraron commits de corrección coincidentes para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>No se pudieron consolidar commits de corrección de merge: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Archivando branches obsoletos...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Procesando branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archivo completado</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>No se pudieron archivar branches: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Analizando estructura del branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>Se encontraron {0} commits para linearizar...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Linearizando {0} commits (eliminando {1} merges)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Reconstruyendo commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Reconciliando estado final...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>El historial ya es lineal - no se encontraron merge commits</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearización completada</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>No se pudo linearizar el historial: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Se reescribirán {0} mensaje(s) de commit para mejorar la calidad.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Se hará squash de {0} commits duplicados en 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Se consolidarán {0} commits de corrección de merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Se corregirá la autoría en {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Se consolidarán {0} merges triviales.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Se archivarán branches obsoletos (eliminar si están merged, etiquetar en caso contrario).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Se linearizará el historial eliminando merge commits y ordenando por fecha.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Se procesarán {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Reconciliar: merge del estado final después de la linearización</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot es requerido</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>El directorio WorkspaceRoot no existe: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo debe ser mayor que 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules no puede ser null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Las opciones Ai no pueden ser null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>GitImproverOptions inválidas: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Los pesos deben sumar 1.0 (actual: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Error desconocido</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repositorio no registrado</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>No se pueden reescribir commits con cambios sin commitear. Por favor, haga commit o stash de sus cambios primero.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repositorio no encontrado: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>No hay mensaje sugerido disponible</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repositorio no registrado: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>La clave API no está configurada. Por favor, configure su clave API en Configuración.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>Falló el análisis de IA</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>La IA no devolvió salida estructurada - se retornó al mensaje original</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push exitoso</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Cargando commits</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Detectando duplicados</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Analizando merge commits</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Analizando complejidad de branches</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Analizando calidad de mensajes</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Analizando autoría</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Completado</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Commits duplicados con contenido idéntico</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>Se encontraron {0} grupos de commits con contenido de archivo idéntico ({1} commits redundantes). Es seguro hacer squash ya que tienen el mismo tree SHA.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits con mensajes duplicados</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>Se encontraron {0} grupos de commits con mensajes idénticos pero cambios de código diferentes ({1} commits). Considere usar mensajes más descriptivos para diferenciar los cambios.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Merge commits excesivos</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Proporción alta de merge commits</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Su repositorio tiene una proporción de {0}% de merge commits ({1}/{2} commits). Considere usar flujo de trabajo de rebase o squash merges.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Commits de corrección de merge detectados</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Se encontraron {0} commits con mensajes como 'fix merge' detectados después de merges.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merges entre branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Se detectaron {0} cross-merges entre feature branches. Use feature branches que solo hagan merge a main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Branches obsoletos</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>Se encontraron {0} branches sin actividad en más de 30 días.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>Tiene cambios sin commitear. Por favor, haga commit o stash de ellos primero.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) ya han sido pusheados al remote. Reescribirlos requerirá un force push y puede afectar a colaboradores.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Su branch está {0} commit(s) detrás del remote. Considere hacer pull primero para evitar conflictos.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>El repositorio tiene problemas notables que deben abordarse.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>El repositorio requiere atención inmediata. El historial está severamente degradado.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.fr.resx
Normal file
421
Resources/Strings/LibStrings.fr.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Le message de commit est vide</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Le sujet contient {0} caractères, le minimum est {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Le sujet contient {0} caractères, le maximum recommandé est {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Le sujet utilise une expression non descriptive : '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Le message ne suit pas le format de commit conventionnel (type: sujet)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Type de commit conventionnel inconnu : {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Aucune référence de ticket trouvée (par exemple, #123 ou JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Le sujet devrait commencer par une majuscule</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Le sujet ne devrait pas se terminer par un point</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Utilisez le mode impératif : '{0}' → '{1}' (par exemple, 'Ajoute' et non 'Ajouté')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Le corps contient {0} caractères, le minimum est {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Ajoutez une ligne vide entre le sujet et le corps</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' ne décrit pas ce qui a changé dans {1} fichiers</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Le message est trop vague pour {0} fichiers modifiés - décrivez CE QUI a changé</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Un changement important ({0} fichiers, {1} lignes) mérite un message plus descriptif</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Un changement majeur ({0} fichiers) devrait inclure un corps expliquant pourquoi</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Envisagez de mentionner la zone modifiée (fichiers : {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Aucun commit dans le dépôt</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit introuvable : {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Le commit cible n'est pas un ancêtre de HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Impossible de trouver des commits cibles dans le dépôt</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Échec de la création du commit : incohérence de parent pour le commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Échec de la mise à jour de HEAD vers le nouveau commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Échec de la vérification du disque : HEAD devrait être {0} mais est {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>L'ancien commit {0} est toujours accessible depuis HEAD après la réécriture</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Erreur Git : {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Dépôt distant '{0}' introuvable</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Aucune branche amont configurée et aucun dépôt distant 'origin' trouvé.
|
||||
|
||||
Définissez le suivi manuellement avec : git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Push forcé réussi</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Push forcé vers origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Échec du démarrage du processus git</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Push forcé réussi (via commande git)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Échec du push : {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Échec de l'exécution de la commande git : {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Aucune branche amont configurée. Définissez le suivi avec : git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push rejeté : non-fast-forward. Récupérez d'abord les modifications ou utilisez le push forcé.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push réussi (via commande git)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Certains commits ont été poussés. Activez 'AllowPushedCommits' pour continuer.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Le type de nettoyage '{0}' n'est pas encore implémenté</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Reconstruction de l'historique des commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Reconstruction de {0} commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Traitement du commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Mise à jour de la référence de branche...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Écrasement des commits de merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Échec de la suppression des commits en double : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Au moins 2 commits sont nécessaires pour écraser</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>Aucun commit trouvé sur la branche actuelle</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Aucun commit correspondant trouvé à supprimer</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Aucun commit spécifié à écraser</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Aucun commit de merge correspondant trouvé à écraser</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Échec de l'écrasement des commits de merge : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Aucun commit spécifié à corriger</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Échec de la correction de la paternité : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Consolidation des commits de correction de merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Aucun commit de correction à consolider</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Aucun commit de correction correspondant trouvé à consolider</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Échec de la consolidation des commits de correction de merge : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Archivage des branches obsolètes...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Traitement de la branche {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archivage terminé</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Échec de l'archivage des branches : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Analyse de la structure des branches...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>{0} commits trouvés à linéariser...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Linéarisation de {0} commits (suppression de {1} merges)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Reconstruction du commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Réconciliation de l'état final...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>L'historique est déjà linéaire - aucun commit de merge trouvé</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linéarisation terminée</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Échec de la linéarisation de l'historique : {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Reformulera {0} message(s) de commit pour améliorer la qualité.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Écrasera {0} commits en double en 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Consolidera {0} commits de correction de merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Corrigera la paternité sur {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Consolidera {0} merges triviaux.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Archivera les branches obsolètes (suppression si fusionnées, étiquetage sinon).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Linéarisera l'historique en supprimant les commits de merge et en triant par date.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Traitera {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Réconciliation : fusion de l'état final après linéarisation</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot est requis</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>Le répertoire WorkspaceRoot n'existe pas : {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo doit être supérieur à 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules ne peut pas être null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Les options Ai ne peuvent pas être null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>GitImproverOptions invalides : {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Les poids doivent totaliser 1.0 (actuel : {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Erreur inconnue</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Dépôt non enregistré</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Impossible de réécrire les commits avec des modifications non validées. Veuillez d'abord valider ou mettre en réserve vos modifications.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Dépôt introuvable : {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Aucun message suggéré disponible</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Dépôt non enregistré : {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>La clé API n'est pas configurée. Veuillez définir votre clé API dans les Paramètres.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>Échec de l'analyse IA</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>L'IA n'a pas retourné de sortie structurée - retour au message original</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push réussi</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Chargement des commits</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Détection des doublons</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Analyse des commits de merge</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Analyse de la complexité des branches</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Analyse de la qualité des messages</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Analyse de la paternité</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Terminé</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Commits en double avec un contenu identique</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>{0} groupes de commits avec un contenu de fichier identique trouvés ({1} commits redondants). Ceux-ci peuvent être écrasés en toute sécurité car ils ont le même SHA d'arborescence.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits avec des messages en double</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>{0} groupes de commits avec des messages identiques mais des modifications de code différentes trouvés ({1} commits). Envisagez d'utiliser des messages plus descriptifs pour différencier les changements.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Commits de merge excessifs</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Ratio élevé de commits de merge</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Votre dépôt a un ratio de commits de merge de {0}% ({1}/{2} commits). Envisagez d'utiliser le flux de travail rebase ou les merges écrasés.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Commits de correction de merge détectés</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>{0} commits avec des messages du type 'fix merge' détectés après des merges.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merges entre branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>{0} cross-merges entre branches de fonctionnalité détectés. Utilisez des branches de fonctionnalité qui fusionnent uniquement dans main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Branches obsolètes</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>{0} branches sans activité depuis plus de 30 jours trouvées.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>Vous avez des modifications non validées. Veuillez d'abord les valider ou les mettre en réserve.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) ont déjà été poussés vers le dépôt distant. Les réécrire nécessitera un push forcé et pourrait affecter les collaborateurs.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Votre branche est en retard de {0} commit(s) par rapport au dépôt distant. Envisagez de faire un pull d'abord pour éviter les conflits.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Le dépôt présente des problèmes notables qui devraient être traités.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Le dépôt nécessite une attention immédiate. L'historique est gravement dégradé.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.hi.resx
Normal file
421
Resources/Strings/LibStrings.hi.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Commit संदेश खाली है</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>विषय {0} वर्णों का है, न्यूनतम {1} होना चाहिए</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>विषय {0} वर्णों का है, अनुशंसित अधिकतम {1} है</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>विषय में गैर-वर्णनात्मक वाक्यांश का उपयोग: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>संदेश conventional commit प्रारूप का पालन नहीं करता (type: subject)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>अज्ञात conventional commit प्रकार: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>कोई issue संदर्भ नहीं मिला (जैसे, #123 या JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>विषय बड़े अक्षर से शुरू होना चाहिए</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>विषय पूर्ण विराम के साथ समाप्त नहीं होना चाहिए</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>आज्ञार्थक मूड का उपयोग करें: '{0}' → '{1}' (जैसे, 'Add' न कि 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>मुख्य भाग {0} वर्णों का है, न्यूनतम {1} होना चाहिए</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>विषय और मुख्य भाग के बीच एक रिक्त पंक्ति जोड़ें</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' यह वर्णन नहीं करता कि {1} फ़ाइलों में क्या बदला</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>संदेश {0} बदली गई फ़ाइलों के लिए बहुत अस्पष्ट है - वर्णन करें कि क्या बदला</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>बड़े परिवर्तन ({0} फ़ाइलें, {1} पंक्तियाँ) के लिए अधिक विस्तृत संदेश की आवश्यकता है</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>प्रमुख परिवर्तन ({0} फ़ाइलें) में मुख्य भाग शामिल होना चाहिए जो बताए कि क्यों</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>किस क्षेत्र में परिवर्तन हुआ इसका उल्लेख करने पर विचार करें (फ़ाइलें: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Repository में कोई commit नहीं</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit नहीं मिला: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>लक्ष्य commit HEAD का पूर्वज नहीं है</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Repository में कोई लक्ष्य commit नहीं मिला</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Commit निर्माण विफल: commit {0} के लिए parent बेमेल</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>नए commit {0} में HEAD अपडेट करना विफल</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>डिस्क सत्यापन विफल: HEAD {0} होना चाहिए लेकिन {1} है</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>पुराना commit {0} rewrite के बाद भी HEAD से पहुंचने योग्य है</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Git त्रुटि: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' नहीं मिला</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>कोई upstream branch कॉन्फ़िगर नहीं है और कोई 'origin' remote नहीं मिला।
|
||||
|
||||
इसके साथ मैन्युअल रूप से tracking सेट करें: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push सफल</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>origin/{0} में force push किया गया</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Git प्रक्रिया शुरू करना विफल</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push सफल (git command के माध्यम से)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Git command चलाना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>कोई upstream branch कॉन्फ़िगर नहीं है। इसके साथ tracking सेट करें: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push अस्वीकृत: non-fast-forward। पहले परिवर्तन pull करें या force push का उपयोग करें।</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push सफल (git command के माध्यम से)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>कुछ commits push किए जा चुके हैं। आगे बढ़ने के लिए 'AllowPushedCommits' सक्षम करें।</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Cleanup प्रकार '{0}' अभी तक लागू नहीं किया गया है</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Commit इतिहास का पुनर्निर्माण हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>{0} commits का पुनर्निर्माण हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} प्रोसेस हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Branch संदर्भ अपडेट हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Merge commits squash हो रहे हैं...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>डुप्लिकेट commits हटाना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Squash करने के लिए कम से कम 2 commits की आवश्यकता है</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>वर्तमान branch पर कोई commit नहीं मिला</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>हटाने के लिए कोई मिलान करने वाले commits नहीं मिले</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Squash करने के लिए कोई commits निर्दिष्ट नहीं</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Squash करने के लिए कोई मिलान करने वाले merge commits नहीं मिले</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Merge commits squash करना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>ठीक करने के लिए कोई commits निर्दिष्ट नहीं</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Authorship ठीक करना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Merge fix commits समेकित हो रहे हैं...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>समेकित करने के लिए कोई fix commits नहीं</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>समेकित करने के लिए कोई मिलान करने वाले fix commits नहीं मिले</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Merge fix commits समेकित करना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>पुरानी branches संग्रहीत हो रही हैं...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Branch {0} प्रोसेस हो रही है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>संग्रह पूर्ण</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Branches संग्रहीत करना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Branch संरचना का विश्लेषण हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>रैखिक बनाने के लिए {0} commits मिले...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>{0} commits रैखिक हो रहे हैं ({1} merges हटाए जा रहे हैं)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} का पुनर्निर्माण हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>अंतिम स्थिति का समाधान हो रहा है...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>इतिहास पहले से ही रैखिक है - कोई merge commits नहीं मिले</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>रैखिकरण पूर्ण</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>इतिहास रैखिक बनाना विफल: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>गुणवत्ता सुधारने के लिए {0} commit संदेश(शों) को फिर से शब्दित किया जाएगा।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>{0} डुप्लिकेट commits को 1 में squash किया जाएगा।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>{0} merge-fix commits समेकित किए जाएंगे।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>{0} commit(s) पर authorship ठीक की जाएगी।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>{0} तुच्छ merges समेकित किए जाएंगे।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>पुरानी branches संग्रहीत की जाएंगी (merge होने पर हटाएं, अन्यथा tag करें)।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Merge commits हटाकर और तारीख के अनुसार क्रमबद्ध करके इतिहास रैखिक किया जाएगा।</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>{0} commit(s) प्रोसेस किए जाएंगे।</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>समाधान: रैखिकरण के बाद अंतिम स्थिति merge करें</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot आवश्यक है</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRoot निर्देशिका मौजूद नहीं है: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo 0 से अधिक होना चाहिए</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules null नहीं हो सकते</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Ai options null नहीं हो सकते</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>अमान्य GitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Weights का योग 1.0 होना चाहिए (वर्तमान: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>अज्ञात त्रुटि</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repository पंजीकृत नहीं है</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>असंबद्ध परिवर्तनों के साथ commits को फिर से नहीं लिखा जा सकता। कृपया पहले अपने परिवर्तनों को commit या stash करें।</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repository नहीं मिली: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>कोई सुझाया गया संदेश उपलब्ध नहीं है</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repository पंजीकृत नहीं है: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>API key कॉन्फ़िगर नहीं है। कृपया सेटिंग्स में अपनी API key सेट करें।</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>AI विश्लेषण विफल</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AI ने संरचित आउटपुट वापस नहीं किया - मूल संदेश पर वापस आ गए</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push सफल</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Commits लोड हो रहे हैं</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>डुप्लिकेट का पता लगाया जा रहा है</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Merge commits का विश्लेषण हो रहा है</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Branch जटिलता का विश्लेषण हो रहा है</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>संदेश गुणवत्ता का विश्लेषण हो रहा है</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Authorship का विश्लेषण हो रहा है</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>पूर्ण</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>समान सामग्री वाले डुप्लिकेट commits</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>समान फ़ाइल सामग्री वाले commits के {0} समूह मिले ({1} अनावश्यक commits)। इन्हें squash करना सुरक्षित है क्योंकि इनका tree SHA समान है।</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>डुप्लिकेट संदेशों वाले commits</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>समान संदेशों लेकिन विभिन्न कोड परिवर्तनों वाले commits के {0} समूह मिले ({1} commits)। परिवर्तनों को अलग करने के लिए अधिक वर्णनात्मक संदेशों का उपयोग करने पर विचार करें।</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>अत्यधिक merge commits</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>उच्च merge commit अनुपात</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>आपकी repository में {0}% merge commit अनुपात है ({1}/{2} commits)। Rebase workflow या squash merges का उपयोग करने पर विचार करें।</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Merge fix commits का पता चला</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Merges के बाद 'fix merge' जैसे संदेशों वाले {0} commits मिले।</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Branches के बीच cross-merges</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Feature branches के बीच {0} cross-merges का पता चला। केवल main में merge होने वाली feature branches का उपयोग करें।</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>पुरानी branches</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>30+ दिनों में कोई गतिविधि नहीं वाली {0} branches मिलीं।</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>आपके पास असंबद्ध परिवर्तन हैं। कृपया पहले उन्हें commit या stash करें।</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) पहले ही remote में push किए जा चुके हैं। उन्हें फिर से लिखने के लिए force push की आवश्यकता होगी और यह सहयोगियों को प्रभावित कर सकता है।</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>आपकी branch remote से {0} commit(s) पीछे है। संघर्षों से बचने के लिए पहले pull करने पर विचार करें।</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Repository में ध्यान देने योग्य समस्याएं हैं जिन्हें हल किया जाना चाहिए।</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Repository को तत्काल ध्यान देने की आवश्यकता है। इतिहास गंभीर रूप से क्षतिग्रस्त है।</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.it.resx
Normal file
421
Resources/Strings/LibStrings.it.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Il messaggio del commit è vuoto</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>L'oggetto è di {0} caratteri, il minimo è {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>L'oggetto è di {0} caratteri, il massimo consigliato è {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>L'oggetto usa una frase non descrittiva: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Il messaggio non segue il formato conventional commit (tipo: oggetto)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Tipo di conventional commit sconosciuto: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Nessun riferimento a issue trovato (es. #123 o JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>L'oggetto dovrebbe iniziare con una lettera maiuscola</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>L'oggetto non dovrebbe terminare con un punto</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Usa l'imperativo: '{0}' → '{1}' (es. 'Add' non 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Il corpo è di {0} caratteri, il minimo è {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Aggiungi una riga vuota tra oggetto e corpo</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' non descrive cosa è cambiato in {1} file</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Il messaggio è troppo vago per {0} file modificati - descrivi COSA è cambiato</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Un cambiamento importante ({0} file, {1} righe) merita un messaggio più descrittivo</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Un cambiamento importante ({0} file) dovrebbe includere un corpo che spiega il perché</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Considera di menzionare quale area è cambiata (file: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Nessun commit nel repository</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit non trovato: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Il commit di destinazione non è un antenato di HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Impossibile trovare commit di destinazione nel repository</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Creazione commit fallita: mancata corrispondenza del genitore per il commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Aggiornamento di HEAD al nuovo commit {0} fallito</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Verifica disco fallita: HEAD dovrebbe essere {0} ma è {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Il vecchio commit {0} è ancora raggiungibile da HEAD dopo la riscrittura</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Errore git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' non trovato</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Nessun branch upstream configurato e nessun remote 'origin' trovato.
|
||||
|
||||
Imposta il tracking manualmente con: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push eseguito con successo</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force push eseguito su origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Impossibile avviare il processo git</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push eseguito con successo (tramite comando git)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push fallito: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Esecuzione comando git fallita: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Nessun branch upstream configurato. Imposta il tracking con: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push rifiutato: non fast-forward. Esegui pull delle modifiche prima o usa force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push eseguito con successo (tramite comando git)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Alcuni commit sono stati pubblicati. Abilita 'AllowPushedCommits' per procedere.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Il tipo di pulizia '{0}' non è ancora implementato</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Ricostruzione della cronologia dei commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Ricostruzione di {0} commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Elaborazione commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Aggiornamento del riferimento al branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Squash dei commit di merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Eliminazione dei commit duplicati fallita: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Servono almeno 2 commit per eseguire lo squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>Nessun commit trovato sul branch corrente</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Nessun commit corrispondente trovato da eliminare</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Nessun commit specificato per lo squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Nessun commit di merge corrispondente trovato per lo squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Squash dei commit di merge fallito: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Nessun commit specificato da correggere</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Correzione dell'autore fallita: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Consolidamento dei commit di correzione merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Nessun commit di correzione da consolidare</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Nessun commit di correzione corrispondente trovato da consolidare</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Consolidamento dei commit di correzione merge fallito: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Archiviazione dei branch obsoleti...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Elaborazione branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archiviazione completata</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Archiviazione dei branch fallita: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Analisi della struttura del branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>Trovati {0} commit da linearizzare...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Linearizzazione di {0} commit (rimozione di {1} merge)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Ricostruzione commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Riconciliazione dello stato finale...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>La cronologia è già lineare - nessun commit di merge trovato</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearizzazione completata</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Linearizzazione della cronologia fallita: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Verrà riformulato il messaggio di {0} commit per migliorarne la qualità.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Verranno uniti {0} commit duplicati in 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Verranno consolidati {0} commit di correzione merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Verrà corretta l'autorialità su {0} commit.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Verranno consolidati {0} merge banali.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Verranno archiviati i branch obsoleti (eliminati se merged, altrimenti taggati).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Verrà linearizzata la cronologia rimuovendo i commit di merge e ordinando per data.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Verranno elaborati {0} commit.</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Riconciliazione: merge dello stato finale dopo la linearizzazione</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot è obbligatorio</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>La directory WorkspaceRoot non esiste: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo deve essere maggiore di 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules non può essere null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Le opzioni Ai non possono essere null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>GitImproverOptions non valide: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>I pesi devono sommare a 1.0 (corrente: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Errore sconosciuto</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repository non registrato</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Impossibile riscrivere i commit con modifiche non salvate. Esegui prima il commit o lo stash delle modifiche.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repository non trovato: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Nessun messaggio suggerito disponibile</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repository non registrato: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>La chiave API non è configurata. Imposta la tua chiave API nelle Impostazioni.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>Analisi AI fallita</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>L'AI non ha restituito un output strutturato - ripiegato sul messaggio originale</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push eseguito con successo</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Caricamento commit</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Rilevamento duplicati</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Analisi dei commit di merge</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Analisi della complessità dei branch</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Analisi della qualità dei messaggi</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Analisi dell'autorialità</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Completato</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Commit duplicati con contenuto identico</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>Trovati {0} gruppi di commit con contenuto di file identico ({1} commit ridondanti). È sicuro eseguire lo squash poiché hanno lo stesso SHA dell'albero.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commit con messaggi duplicati</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>Trovati {0} gruppi di commit con messaggi identici ma modifiche al codice diverse ({1} commit). Considera l'uso di messaggi più descrittivi per differenziare le modifiche.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Commit di merge eccessivi</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Rapporto elevato di commit di merge</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Il tuo repository ha un rapporto di commit di merge del {0}% ({1}/{2} commit). Considera l'uso del workflow rebase o dello squash merge.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Rilevati commit di correzione merge</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Trovati {0} commit con messaggi come 'fix merge' rilevati dopo i merge.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merge tra branch</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Rilevati {0} cross-merge tra branch di funzionalità. Usa branch di funzionalità che si uniscono solo al main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Branch obsoleti</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>Trovati {0} branch senza attività negli ultimi 30+ giorni.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>Hai modifiche non salvate. Esegui prima il commit o lo stash.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit sono già stati pubblicati sul remote. Riscriverli richiederà un force push e potrebbe influire sui collaboratori.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Il tuo branch è {0} commit indietro rispetto al remote. Considera di eseguire pull prima per evitare conflitti.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Il repository presenta problemi evidenti che dovrebbero essere risolti.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Il repository richiede attenzione immediata. La cronologia è gravemente degradata.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.ja.resx
Normal file
421
Resources/Strings/LibStrings.ja.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>commitメッセージが空です</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>件名が{0}文字です。最小値は{1}文字です</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>件名が{0}文字です。推奨最大値は{1}文字です</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>件名に説明不足のフレーズが使用されています: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>メッセージがconventional commit形式に従っていません (type: subject)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>不明なconventional commitタイプ: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>課題参照が見つかりません (例: #123 または JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>件名は大文字で始める必要があります</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>件名の末尾にピリオドを付けないでください</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>命令形を使用してください: '{0}' → '{1}' (例: 'Add' であって 'Added' ではありません)</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>本文が{0}文字です。最小値は{1}文字です</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>件名と本文の間に空白行を追加してください</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}'は{1}個のファイルで何が変更されたかを説明していません</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>変更された{0}個のファイルに対してメッセージが曖昧すぎます - 何が変更されたかを説明してください</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>大規模な変更 ({0}個のファイル、{1}行) にはより詳細なメッセージが必要です</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>主要な変更 ({0}個のファイル) には理由を説明する本文を含める必要があります</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>変更された領域の記載を検討してください (ファイル: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>リポジトリにcommitがありません</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>commitが見つかりません: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>対象のcommitはHEADの祖先ではありません</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>リポジトリで対象のcommitが見つかりませんでした</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>commit作成に失敗しました: commit {0}の親が一致しません</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>HEADを新しいcommit {0}に更新できませんでした</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>ディスク検証に失敗しました: HEADは{0}であるべきですが、{1}です</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>書き換え後、古いcommit {0}がHEADから到達可能です</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Gitエラー: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>リモート'{0}'が見つかりません</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>upstreamブランチが設定されておらず、'origin'リモートも見つかりません。
|
||||
|
||||
次のコマンドで手動で追跡を設定してください: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>force pushが成功しました</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>origin/{0}にforce pushしました</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>gitプロセスの起動に失敗しました</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>force pushが成功しました (gitコマンド経由)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>pushに失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>gitコマンドの実行に失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>upstreamブランチが設定されていません。次のコマンドで追跡を設定してください: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>pushが拒否されました: non-fast-forward。先に変更をpullするか、force pushを使用してください。</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>pushが成功しました (gitコマンド経由)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>一部のcommitがpushされています。続行するには'AllowPushedCommits'を有効にしてください。</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>クリーンアップタイプ'{0}'はまだ実装されていません</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>commit履歴を再構築しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>{0}個のcommitを再構築しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>commit {0}/{1}を処理しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>branch参照を更新しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>merge commitをsquashしています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>重複commitの削除に失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>squashするには少なくとも2つのcommitが必要です</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>現在のbranchにcommitが見つかりません</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>削除する一致するcommitが見つかりません</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>squashするcommitが指定されていません</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>squashする一致するmerge commitが見つかりません</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>merge commitのsquashに失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>修正するcommitが指定されていません</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>作成者の修正に失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>merge修正commitを統合しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>統合する修正commitがありません</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>統合する一致する修正commitが見つかりません</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>merge修正commitの統合に失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>古いbranchをアーカイブしています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>branch {0}を処理しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>アーカイブが完了しました</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>branchのアーカイブに失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>branch構造を分析しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>線形化する{0}個のcommitが見つかりました...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>{0}個のcommitを線形化しています ({1}個のmergeを削除)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>commit {0}/{1}を再構築しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>最終状態を調整しています...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>履歴はすでに線形です - merge commitが見つかりません</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>線形化が完了しました</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>履歴の線形化に失敗しました: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>品質向上のため、{0}個のcommitメッセージを書き直します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>{0}個の重複commitを1つにsquashします。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>{0}個のmerge修正commitを統合します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>{0}個のcommitの作成者を修正します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>{0}個の些細なmergeを統合します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>古いbranchをアーカイブします (mergeされている場合は削除、そうでない場合はタグ付け)。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>merge commitを削除し、日付順にソートして履歴を線形化します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>{0}個のcommitを処理します。</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>調整: 線形化後の最終状態をmerge</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRootは必須です</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRootディレクトリが存在しません: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepoは0より大きい値である必要があります</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rulesをnullにすることはできません</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Aiオプションをnullにすることはできません</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>無効なGitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>重みの合計は1.0である必要があります (現在: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>不明なエラー</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>リポジトリが登録されていません</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>コミットされていない変更があるため、commitを書き換えることができません。先に変更をcommitまたはstashしてください。</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>リポジトリが見つかりません: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>提案されたメッセージがありません</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>リポジトリが登録されていません: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>APIキーが設定されていません。設定でAPIキーを設定してください。</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>AI分析に失敗しました</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AIが構造化された出力を返しませんでした - 元のメッセージにフォールバックしました</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>pushが成功しました</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>commitを読み込んでいます</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>重複を検出しています</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>merge commitを分析しています</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>branchの複雑さを分析しています</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>メッセージの品質を分析しています</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>作成者を分析しています</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>完了</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>内容が同一の重複commit</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>同一のファイル内容を持つcommitのグループが{0}個見つかりました ({1}個の冗長なcommit)。これらは同じツリーSHAを持つため、安全にsquashできます。</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>重複したメッセージを持つcommit</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>同一のメッセージを持つが、異なるコード変更があるcommitのグループが{0}個見つかりました ({1}個のcommit)。変更を区別するために、より説明的なメッセージの使用を検討してください。</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>過剰なmerge commit</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>merge commitの比率が高い</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>リポジトリのmerge commit比率が{0}%です ({1}/{2}個のcommit)。rebaseワークフローまたはsquash mergeの使用を検討してください。</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>merge修正commitが検出されました</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>merge後に'fix merge'のようなメッセージを持つcommitが{0}個見つかりました。</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>branch間のクロスmerge</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>機能branch間のクロスmergeが{0}個検出されました。mainにのみmergeする機能branchを使用してください。</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>古いbranch</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>30日以上活動がないbranchが{0}個見つかりました。</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>コミットされていない変更があります。先にcommitまたはstashしてください。</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0}個のcommitがすでにリモートにpushされています。これらを書き換えるにはforce pushが必要で、共同作業者に影響を与える可能性があります。</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>branchがリモートより{0}個のcommit分遅れています。競合を避けるため、先にpullすることを検討してください。</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>リポジトリに対処すべき目立った問題があります。</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>リポジトリは緊急の対応が必要です。履歴が著しく劣化しています。</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.nl.resx
Normal file
421
Resources/Strings/LibStrings.nl.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Commit-bericht is leeg</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Onderwerp is {0} tekens, minimum is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Onderwerp is {0} tekens, aanbevolen maximum is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Onderwerp gebruikt niet-beschrijvende zin: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Bericht volgt niet het conventionele commit-formaat (type: onderwerp)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Onbekend conventioneel commit-type: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Geen issue-referentie gevonden (bijv. #123 of JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Onderwerp moet beginnen met een hoofdletter</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Onderwerp mag niet eindigen met een punt</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Gebruik gebiedende wijs: '{0}' → '{1}' (bijv. 'Voeg toe' niet 'Toegevoegd')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Body is {0} tekens, minimum is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Voeg een lege regel toe tussen onderwerp en body</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' beschrijft niet wat er is gewijzigd in {1} bestanden</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Bericht is te vaag voor {0} gewijzigde bestanden - beschrijf WAT er is gewijzigd</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Grote wijziging ({0} bestanden, {1} regels) verdient een meer beschrijvend bericht</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Grote wijziging ({0} bestanden) moet een body bevatten die uitlegt waarom</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Overweeg te vermelden welk gebied is gewijzigd (bestanden: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Geen commits in repository</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit niet gevonden: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Doel-commit is geen voorouder van HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Kon geen doel-commits vinden in repository</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Aanmaken van commit mislukt: parent komt niet overeen voor commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Bijwerken van HEAD naar nieuwe commit {0} mislukt</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Schijfverificatie mislukt: HEAD zou {0} moeten zijn maar is {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Oude commit {0} nog steeds bereikbaar vanaf HEAD na herschrijven</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Git-fout: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' niet gevonden</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Geen upstream branch geconfigureerd en geen 'origin' remote gevonden.
|
||||
|
||||
Stel tracking handmatig in met: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push succesvol</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force push naar origin/{0} uitgevoerd</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Starten van git-proces mislukt</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push succesvol (via git-commando)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Uitvoeren van git-commando mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Geen upstream branch geconfigureerd. Stel tracking in met: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push geweigerd: non-fast-forward. Haal eerst wijzigingen op of gebruik force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push succesvol (via git-commando)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Sommige commits zijn gepusht. Schakel 'AllowPushedCommits' in om door te gaan.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Opruimtype '{0}' is nog niet geïmplementeerd</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Commit-geschiedenis wordt opnieuw opgebouwd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>{0} commits worden opnieuw opgebouwd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} wordt verwerkt...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Branch-referentie wordt bijgewerkt...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Merge-commits worden samengevoegd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Verwijderen van dubbele commits mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Minimaal 2 commits nodig om samen te voegen</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>Geen commits gevonden op huidige branch</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Geen overeenkomende commits gevonden om te verwijderen</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Geen commits opgegeven om samen te voegen</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Geen overeenkomende merge-commits gevonden om samen te voegen</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Samenvoegen van merge-commits mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Geen commits opgegeven om te corrigeren</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Corrigeren van auteurschap mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Merge-fix commits worden geconsolideerd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Geen fix-commits om te consolideren</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Geen overeenkomende fix-commits gevonden om te consolideren</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Consolideren van merge-fix commits mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Verouderde branches worden gearchiveerd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Branch {0} wordt verwerkt...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archivering voltooid</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Archiveren van branches mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Branch-structuur wordt geanalyseerd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>{0} commits gevonden om te lineariseren...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>{0} commits worden gelineariseerd ({1} merges worden verwijderd)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Commit {0}/{1} wordt opnieuw opgebouwd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Eindstaat wordt gereconcilieerd...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>Geschiedenis is al lineair - geen merge-commits gevonden</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearisatie voltooid</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Lineariseren van geschiedenis mislukt: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>{0} commit-bericht(en) zullen worden herschreven om de kwaliteit te verbeteren.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>{0} dubbele commits zullen worden samengevoegd tot 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>{0} merge-fix commits zullen worden geconsolideerd.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Auteurschap zal worden gecorrigeerd op {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>{0} triviale merges zullen worden geconsolideerd.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Verouderde branches zullen worden gearchiveerd (verwijderen indien gemerged, anders taggen).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Geschiedenis zal worden gelineariseerd door merge-commits te verwijderen en op datum te sorteren.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>{0} commit(s) zullen worden verwerkt.</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Reconcile: eindstaat samenvoegen na linearisatie</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot is verplicht</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRoot-map bestaat niet: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo moet groter zijn dan 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules kunnen niet null zijn</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Ai-opties kunnen niet null zijn</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>Ongeldige GitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Gewichten moeten optellen tot 1.0 (huidig: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Onbekende fout</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repository niet geregistreerd</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Kan commits niet herschrijven met niet-gecommitte wijzigingen. Commit of stash eerst uw wijzigingen.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repository niet gevonden: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Geen gesuggereerd bericht beschikbaar</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repository niet geregistreerd: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>API-sleutel is niet geconfigureerd. Stel uw API-sleutel in via Instellingen.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>AI-analyse mislukt</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AI heeft geen gestructureerde output gegeven - teruggevallen op origineel bericht</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push succesvol</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Commits worden geladen</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Duplicaten worden gedetecteerd</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Merge-commits worden geanalyseerd</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Branch-complexiteit wordt geanalyseerd</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Berichtkwaliteit wordt geanalyseerd</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Auteurschap wordt geanalyseerd</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Voltooid</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Dubbele commits met identieke inhoud</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>{0} groepen van commits gevonden met identieke bestandsinhoud ({1} redundante commits). Deze kunnen veilig worden samengevoegd omdat ze dezelfde tree SHA hebben.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits met dubbele berichten</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>{0} groepen van commits gevonden met identieke berichten maar verschillende codewijzigingen ({1} commits). Overweeg meer beschrijvende berichten te gebruiken om wijzigingen te onderscheiden.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Buitensporig veel merge-commits</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Hoge merge-commit ratio</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Uw repository heeft een {0}% merge-commit ratio ({1}/{2} commits). Overweeg een rebase-workflow of squash merges te gebruiken.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Merge-fix commits gedetecteerd</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>{0} commits gevonden met berichten zoals 'fix merge' gedetecteerd na merges.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merges tussen branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>{0} cross-merges tussen feature branches gedetecteerd. Gebruik feature branches die alleen naar main mergen.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Verouderde branches</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>{0} branches gevonden zonder activiteit in 30+ dagen.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>U heeft niet-gecommitte wijzigingen. Commit of stash ze eerst.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) zijn al gepusht naar de remote. Het herschrijven ervan vereist een force push en kan andere medewerkers beïnvloeden.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Uw branch loopt {0} commit(s) achter op de remote. Overweeg eerst te pullen om conflicten te voorkomen.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Repository heeft merkbare problemen die moeten worden aangepakt.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Repository vereist onmiddellijke aandacht. Geschiedenis is ernstig gedegradeerd.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.pt.resx
Normal file
421
Resources/Strings/LibStrings.pt.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Mensagem de commit está vazia</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Assunto tem {0} caracteres, mínimo é {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Assunto tem {0} caracteres, máximo recomendado é {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Assunto usa frase não descritiva: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Mensagem não segue o formato de commit convencional (tipo: assunto)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Tipo de commit convencional desconhecido: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Nenhuma referência a issue encontrada (ex: #123 ou JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Assunto deve começar com letra maiúscula</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Assunto não deve terminar com ponto final</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Use modo imperativo: '{0}' → '{1}' (ex: 'Add' não 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Corpo tem {0} caracteres, mínimo é {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Adicione uma linha em branco entre o assunto e o corpo</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' não descreve o que mudou em {1} arquivos</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Mensagem muito vaga para {0} arquivos alterados - descreva O QUE mudou</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Mudança grande ({0} arquivos, {1} linhas) merece uma mensagem mais descritiva</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Mudança importante ({0} arquivos) deve incluir um corpo explicando o porquê</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Considere mencionar qual área mudou (arquivos: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>Nenhum commit no repositório</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit não encontrado: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Commit de destino não é um ancestral de HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Não foi possível encontrar commits de destino no repositório</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Falha na criação do commit: incompatibilidade de pai para o commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Falha ao atualizar HEAD para o novo commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Falha na verificação do disco: HEAD deveria ser {0} mas é {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Commit antigo {0} ainda acessível a partir de HEAD após reescrita</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Erro do Git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remoto '{0}' não encontrado</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Nenhum branch upstream configurado e nenhum remoto 'origin' encontrado.
|
||||
|
||||
Configure o rastreamento manualmente com: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push bem-sucedido</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force push realizado para origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Falha ao iniciar o processo git</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push bem-sucedido (via comando git)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push falhou: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Falha ao executar comando git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Nenhum branch upstream configurado. Configure o rastreamento com: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push rejeitado: non-fast-forward. Faça pull das mudanças primeiro ou use force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push bem-sucedido (via comando git)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Alguns commits já foram enviados. Habilite 'AllowPushedCommits' para prosseguir.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Tipo de limpeza '{0}' ainda não foi implementado</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Reconstruindo histórico de commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Reconstruindo {0} commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Processando commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Atualizando referência do branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Consolidando commits de merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Falha ao remover commits duplicados: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>São necessários pelo menos 2 commits para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>Nenhum commit encontrado no branch atual</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Nenhum commit correspondente encontrado para remover</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Nenhum commit especificado para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Nenhum commit de merge correspondente encontrado para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Falha ao consolidar commits de merge: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Nenhum commit especificado para corrigir</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Falha ao corrigir autoria: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Consolidando commits de correção de merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Nenhum commit de correção para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Nenhum commit de correção correspondente encontrado para consolidar</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Falha ao consolidar commits de correção de merge: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Arquivando branches obsoletos...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Processando branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Arquivamento concluído</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Falha ao arquivar branches: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Analisando estrutura do branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>Encontrados {0} commits para linearizar...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Linearizando {0} commits (removendo {1} merges)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Reconstruindo commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Reconciliando estado final...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>Histórico já é linear - nenhum commit de merge encontrado</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearização concluída</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Falha ao linearizar histórico: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Irá reescrever {0} mensagem(ns) de commit para melhorar a qualidade.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Irá consolidar {0} commits duplicados em 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Irá consolidar {0} commits de correção de merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Irá corrigir autoria em {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Irá consolidar {0} merges triviais.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Irá arquivar branches obsoletos (excluir se mesclado, marcar com tag caso contrário).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Irá linearizar histórico removendo commits de merge e ordenando por data.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Irá processar {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Reconciliar: mesclar estado final após linearização</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot é obrigatório</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>Diretório WorkspaceRoot não existe: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo deve ser maior que 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules não pode ser nulo</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Opções de Ai não podem ser nulas</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>GitImproverOptions inválido: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Pesos devem somar 1.0 (atual: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Erro desconhecido</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repositório não registrado</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Não é possível reescrever commits com alterações não confirmadas. Por favor, faça commit ou stash das suas alterações primeiro.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repositório não encontrado: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Nenhuma mensagem sugerida disponível</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repositório não registrado: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>Chave de API não está configurada. Por favor, defina sua chave de API nas Configurações.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>Falha na análise de IA</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>IA não retornou saída estruturada - revertido para mensagem original</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push bem-sucedido</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Carregando commits</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Detectando duplicatas</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Analisando commits de merge</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Analisando complexidade dos branches</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Analisando qualidade das mensagens</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Analisando autoria</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Concluído</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Commits duplicados com conteúdo idêntico</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>Encontrados {0} grupos de commits com conteúdo de arquivo idêntico ({1} commits redundantes). É seguro consolidá-los pois têm o mesmo SHA de árvore.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits com mensagens duplicadas</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>Encontrados {0} grupos de commits com mensagens idênticas mas alterações de código diferentes ({1} commits). Considere usar mensagens mais descritivas para diferenciar as mudanças.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Commits de merge excessivos</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Alta proporção de commits de merge</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Seu repositório tem uma proporção de {0}% de commits de merge ({1}/{2} commits). Considere usar fluxo de trabalho com rebase ou squash merges.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Commits de correção de merge detectados</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Encontrados {0} commits com mensagens como 'fix merge' detectados após merges.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merges entre branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Detectados {0} cross-merges entre branches de funcionalidade. Use branches de funcionalidade que mesclam apenas para main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Branches obsoletos</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>Encontrados {0} branches sem atividade em mais de 30 dias.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>Você tem alterações não confirmadas. Por favor, faça commit ou stash delas primeiro.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) já foram enviados para o remoto. Reescrevê-los exigirá um force push e pode afetar colaboradores.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Seu branch está {0} commit(s) atrás do remoto. Considere fazer pull primeiro para evitar conflitos.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Repositório tem problemas notáveis que devem ser abordados.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Repositório requer atenção imediata. Histórico está severamente degradado.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.resx
Normal file
421
Resources/Strings/LibStrings.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Commit message is empty</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Subject is {0} chars, minimum is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Subject is {0} chars, recommended max is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Subject uses non-descriptive phrase: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Message doesn't follow conventional commit format (type: subject)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Unknown conventional commit type: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>No issue reference found (e.g., #123 or JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Subject should start with a capital letter</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Subject should not end with a period</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Use imperative mood: '{0}' → '{1}' (e.g., 'Add' not 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Body is {0} chars, minimum is {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Add a blank line between subject and body</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' doesn't describe what changed in {1} files</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Message is too vague for {0} changed files - describe WHAT changed</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Large change ({0} files, {1} lines) deserves a more descriptive message</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Major change ({0} files) should include a body explaining why</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Consider mentioning what area changed (files: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>No commits in repository</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit not found: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Target commit is not an ancestor of HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Could not find any target commits in repository</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Commit creation failed: parent mismatch for commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Failed to update HEAD to new commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Disk verification failed: HEAD should be {0} but is {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Old commit {0} still reachable from HEAD after rewrite</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Git error: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Remote '{0}' not found</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>No upstream branch configured and no 'origin' remote found.
|
||||
|
||||
Set tracking manually with: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push successful</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force pushed to origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Failed to start git process</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push successful (via git command)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push failed: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Failed to run git command: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>No upstream branch configured. Set tracking with: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push rejected: non-fast-forward. Pull changes first or use force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push successful (via git command)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Some commits have been pushed. Enable 'AllowPushedCommits' to proceed.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Cleanup type '{0}' is not yet implemented</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Rebuilding commit history...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Rebuilding {0} commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Processing commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Updating branch reference...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Squashing merge commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Failed to drop duplicate commits: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Need at least 2 commits to squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>No commits found on current branch</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>No matching commits found to drop</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>No commits specified to squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>No matching merge commits found to squash</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Failed to squash merge commits: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>No commits specified to fix</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Failed to fix authorship: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Consolidating merge fix commits...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>No fix commits to consolidate</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>No matching fix commits found to consolidate</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Failed to consolidate merge fix commits: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Archiving stale branches...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Processing branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Archive complete</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Failed to archive branches: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Analyzing branch structure...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>Found {0} commits to linearize...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Linearizing {0} commits (removing {1} merges)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Rebuilding commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Reconciling final state...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>History is already linear - no merge commits found</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Linearization complete</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Failed to linearize history: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Will reword {0} commit message(s) to improve quality.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Will squash {0} duplicate commits into 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Will consolidate {0} merge-fix commits.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Will fix authorship on {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Will consolidate {0} trivial merges.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Will archive stale branches (delete if merged, tag otherwise).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Will linearize history by removing merge commits and sorting by date.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Will process {0} commit(s).</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Reconcile: merge final state after linearization</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot is required</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRoot directory does not exist: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo must be greater than 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules cannot be null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Ai options cannot be null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>Invalid GitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Weights must sum to 1.0 (current: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Unknown error</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Repository not registered</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Cannot rewrite commits with uncommitted changes. Please commit or stash your changes first.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Repository not found: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>No suggested message available</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Repository not registered: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>API key is not configured. Please set your API key in Settings.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>AI analysis failed</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AI did not return structured output - fell back to original message</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push successful</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Loading commits</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Detecting duplicates</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Analyzing merge commits</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Analyzing branch complexity</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Analyzing message quality</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Analyzing authorship</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Complete</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Duplicate commits with identical content</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>Found {0} groups of commits with identical file content ({1} redundant commits). These are safe to squash as they have the same tree SHA.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commits with duplicate messages</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>Found {0} groups of commits with identical messages but different code changes ({1} commits). Consider using more descriptive messages to differentiate changes.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Excessive merge commits</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>High merge commit ratio</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Your repository has a {0}% merge commit ratio ({1}/{2} commits). Consider using rebase workflow or squash merges.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Merge fix commits detected</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Found {0} commits with messages like 'fix merge' detected after merges.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Cross-merges between branches</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Detected {0} cross-merges between feature branches. Use feature branches that only merge into main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Stale branches</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>Found {0} branches with no activity in 30+ days.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>You have uncommitted changes. Please commit or stash them first.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit(s) have already been pushed to the remote. Rewriting them will require a force push and may affect collaborators.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Your branch is {0} commit(s) behind the remote. Consider pulling first to avoid conflicts.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>Repository has noticeable issues that should be addressed.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Repository requires immediate attention. History is severely degraded.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.ru.resx
Normal file
421
Resources/Strings/LibStrings.ru.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Сообщение commit пустое</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>Заголовок содержит {0} символов, минимум {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>Заголовок содержит {0} символов, рекомендуемый максимум {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>Заголовок использует неинформативную фразу: '{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>Сообщение не соответствует формату conventional commit (тип: описание)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>Неизвестный тип conventional commit: {0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>Не найдена ссылка на задачу (например, #123 или JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>Заголовок должен начинаться с заглавной буквы</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>Заголовок не должен заканчиваться точкой</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>Используйте повелительное наклонение: '{0}' → '{1}' (например, 'Add' а не 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>Тело содержит {0} символов, минимум {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>Добавьте пустую строку между заголовком и телом</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' не описывает изменения в {1} файлах</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>Сообщение слишком расплывчатое для {0} измененных файлов - опишите ЧТО изменилось</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>Большое изменение ({0} файлов, {1} строк) заслуживает более подробного описания</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>Значительное изменение ({0} файлов) должно включать тело с объяснением причин</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>Рассмотрите возможность указать, какая область изменилась (файлы: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>В репозитории нет commit</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>Commit не найден: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>Целевой commit не является предком HEAD</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>Не удалось найти целевые commit в репозитории</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Не удалось создать commit: несоответствие родителя для commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>Не удалось обновить HEAD на новый commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>Не удалось выполнить проверку на диске: HEAD должен быть {0}, но является {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>Старый commit {0} все еще доступен из HEAD после перезаписи</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Ошибка Git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>Удаленный репозиторий '{0}' не найден</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>Upstream branch не настроен и удаленный репозиторий 'origin' не найден.
|
||||
|
||||
Установите отслеживание вручную: git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>Force push выполнен успешно</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>Force push выполнен в origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>Не удалось запустить процесс git</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>Force push выполнен успешно (через команду git)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push не выполнен: {0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>Не удалось выполнить команду git: {0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>Upstream branch не настроен. Установите отслеживание: git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push отклонен: non-fast-forward. Сначала выполните pull изменений или используйте force push.</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push выполнен успешно (через команду git)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>Некоторые commit были отправлены. Включите 'AllowPushedCommits' для продолжения.</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>Тип очистки '{0}' еще не реализован</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>Перестроение истории commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>Перестроение {0} commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>Обработка commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>Обновление ссылки на branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>Объединение merge commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>Не удалось удалить дублирующиеся commit: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>Для объединения требуется как минимум 2 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>В текущей branch не найдено commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>Не найдено подходящих commit для удаления</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>Не указаны commit для объединения</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>Не найдено подходящих merge commit для объединения</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>Не удалось объединить merge commit: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>Не указаны commit для исправления</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>Не удалось исправить авторство: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>Консолидация commit с исправлениями merge...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>Нет commit с исправлениями для консолидации</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>Не найдено подходящих commit с исправлениями для консолидации</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>Не удалось консолидировать commit с исправлениями merge: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>Архивирование устаревших branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>Обработка branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>Архивирование завершено</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>Не удалось заархивировать branch: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>Анализ структуры branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>Найдено {0} commit для линеаризации...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>Линеаризация {0} commit (удаление {1} merge)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>Перестроение commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>Согласование окончательного состояния...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>История уже линейна - merge commit не найдены</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>Линеаризация завершена</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>Не удалось линеаризовать историю: {0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>Будет переписано {0} сообщений commit для улучшения качества.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>Будет объединено {0} дублирующихся commit в 1.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>Будет консолидировано {0} commit с исправлениями merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>Будет исправлено авторство в {0} commit.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>Будет консолидировано {0} тривиальных merge.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>Будут заархивированы устаревшие branch (удалены при слиянии, иначе помечены тегом).</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>Будет линеаризована история путем удаления merge commit и сортировки по дате.</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>Будет обработано {0} commit.</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>Согласование: merge окончательного состояния после линеаризации</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>WorkspaceRoot обязателен</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>Каталог WorkspaceRoot не существует: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo должен быть больше 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules не может быть null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Параметры Ai не могут быть null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>Недопустимые GitImproverOptions: {0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>Сумма весов должна быть равна 1.0 (текущая: {0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>Неизвестная ошибка</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>Репозиторий не зарегистрирован</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>Невозможно перезаписать commit с незафиксированными изменениями. Пожалуйста, сначала выполните commit или stash изменений.</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>Репозиторий не найден: {0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>Предлагаемое сообщение недоступно</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>Репозиторий не зарегистрирован: {0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>API ключ не настроен. Пожалуйста, установите API ключ в настройках.</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>Не удалось выполнить анализ AI</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AI не вернул структурированный вывод - использовано исходное сообщение</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push выполнен успешно</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>Загрузка commit</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>Обнаружение дубликатов</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>Анализ merge commit</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>Анализ сложности branch</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>Анализ качества сообщений</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>Анализ авторства</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>Завершено</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>Дублирующиеся commit с идентичным содержимым</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>Найдено {0} групп commit с идентичным содержимым файлов ({1} избыточных commit). Их можно безопасно объединить, так как они имеют одинаковый SHA дерева.</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>Commit с дублирующимися сообщениями</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>Найдено {0} групп commit с идентичными сообщениями, но разными изменениями кода ({1} commit). Рассмотрите возможность использования более описательных сообщений для различения изменений.</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>Избыточное количество merge commit</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Высокое соотношение merge commit</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>Ваш репозиторий имеет {0}% соотношение merge commit ({1}/{2} commit). Рассмотрите использование rebase workflow или squash merge.</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>Обнаружены commit с исправлениями merge</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>Найдено {0} commit с сообщениями типа 'fix merge', обнаруженных после merge.</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Перекрестные merge между branch</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>Обнаружено {0} перекрестных merge между feature branch. Используйте feature branch, которые сливаются только в main.</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>Устаревшие branch</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>Найдено {0} branch без активности более 30 дней.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>У вас есть незафиксированные изменения. Пожалуйста, сначала выполните commit или stash их.</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} commit уже были отправлены на удаленный сервер. Их перезапись потребует force push и может повлиять на других участников.</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>Ваша branch отстает от удаленной на {0} commit. Рассмотрите возможность сначала выполнить pull, чтобы избежать конфликтов.</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>В репозитории есть заметные проблемы, которые следует устранить.</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>Репозиторий требует немедленного внимания. История сильно ухудшена.</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
421
Resources/Strings/LibStrings.zh.resx
Normal file
421
Resources/Strings/LibStrings.zh.resx
Normal file
@ -0,0 +1,421 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<!-- ==================== Commit Message Analyzer ==================== -->
|
||||
<data name="Analyzer_MessageEmpty" xml:space="preserve">
|
||||
<value>Commit 消息为空</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooShort" xml:space="preserve">
|
||||
<value>主题长度为 {0} 个字符,最小长度为 {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_SubjectTooLong" xml:space="preserve">
|
||||
<value>主题长度为 {0} 个字符,建议最大长度为 {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BannedPhrase" xml:space="preserve">
|
||||
<value>主题使用了无意义的短语:'{0}'</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotConventional" xml:space="preserve">
|
||||
<value>消息未遵循约定式提交格式(类型: 主题)</value>
|
||||
</data>
|
||||
<data name="Analyzer_UnknownType" xml:space="preserve">
|
||||
<value>未知的约定式提交类型:{0}</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoIssueRef" xml:space="preserve">
|
||||
<value>未找到问题引用(例如 #123 或 JIRA-123)</value>
|
||||
</data>
|
||||
<data name="Analyzer_CapitalLetter" xml:space="preserve">
|
||||
<value>主题应以大写字母开头</value>
|
||||
</data>
|
||||
<data name="Analyzer_NoPeriod" xml:space="preserve">
|
||||
<value>主题不应以句号结尾</value>
|
||||
</data>
|
||||
<data name="Analyzer_ImperativeMood" xml:space="preserve">
|
||||
<value>使用祈使语气:'{0}' → '{1}'(例如,使用 'Add' 而不是 'Added')</value>
|
||||
</data>
|
||||
<data name="Analyzer_BodyTooShort" xml:space="preserve">
|
||||
<value>正文长度为 {0} 个字符,最小长度为 {1}</value>
|
||||
</data>
|
||||
<data name="Analyzer_BlankLine" xml:space="preserve">
|
||||
<value>在主题和正文之间添加一个空行</value>
|
||||
</data>
|
||||
<data name="Analyzer_NotDescriptive" xml:space="preserve">
|
||||
<value>'{0}' 未描述 {1} 个文件中的更改内容</value>
|
||||
</data>
|
||||
<data name="Analyzer_TooVague" xml:space="preserve">
|
||||
<value>消息对于 {0} 个已更改的文件来说过于模糊 - 请描述具体更改了什么</value>
|
||||
</data>
|
||||
<data name="Analyzer_LargeChange" xml:space="preserve">
|
||||
<value>较大的更改({0} 个文件,{1} 行)需要更详细的消息</value>
|
||||
</data>
|
||||
<data name="Analyzer_MajorChange" xml:space="preserve">
|
||||
<value>重大更改({0} 个文件)应包含解释原因的正文</value>
|
||||
</data>
|
||||
<data name="Analyzer_MentionArea" xml:space="preserve">
|
||||
<value>考虑提及更改的区域(文件:{0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Git Operations Service ==================== -->
|
||||
<data name="Git_NoCommits" xml:space="preserve">
|
||||
<value>仓库中没有 commit</value>
|
||||
</data>
|
||||
<data name="Git_CommitNotFound" xml:space="preserve">
|
||||
<value>未找到 commit:{0}</value>
|
||||
</data>
|
||||
<data name="Git_NotAncestor" xml:space="preserve">
|
||||
<value>目标 commit 不是 HEAD 的祖先</value>
|
||||
</data>
|
||||
<data name="Git_NoTargetCommits" xml:space="preserve">
|
||||
<value>仓库中未找到任何目标 commit</value>
|
||||
</data>
|
||||
<data name="Git_ParentMismatch" xml:space="preserve">
|
||||
<value>Commit 创建失败:commit {0} 的父提交不匹配</value>
|
||||
</data>
|
||||
<data name="Git_HeadUpdateFailed" xml:space="preserve">
|
||||
<value>无法将 HEAD 更新到新的 commit {0}</value>
|
||||
</data>
|
||||
<data name="Git_VerificationFailed" xml:space="preserve">
|
||||
<value>磁盘验证失败:HEAD 应为 {0} 但实际为 {1}</value>
|
||||
</data>
|
||||
<data name="Git_OldCommitReachable" xml:space="preserve">
|
||||
<value>重写后旧的 commit {0} 仍可从 HEAD 访问</value>
|
||||
</data>
|
||||
<data name="Git_Error" xml:space="preserve">
|
||||
<value>Git 错误:{0}</value>
|
||||
</data>
|
||||
<data name="Git_RemoteNotFound" xml:space="preserve">
|
||||
<value>未找到远程仓库 '{0}'</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstreamNoOrigin" xml:space="preserve">
|
||||
<value>未配置上游 branch 且未找到 'origin' 远程仓库。
|
||||
|
||||
请手动设置跟踪:git push -u origin {0}</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccess" xml:space="preserve">
|
||||
<value>强制 push 成功</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushedTo" xml:space="preserve">
|
||||
<value>已强制 push 到 origin/{0}</value>
|
||||
</data>
|
||||
<data name="Git_ProcessFailed" xml:space="preserve">
|
||||
<value>无法启动 git 进程</value>
|
||||
</data>
|
||||
<data name="Git_ForcePushSuccessCmd" xml:space="preserve">
|
||||
<value>强制 push 成功(通过 git 命令)</value>
|
||||
</data>
|
||||
<data name="Git_PushFailed" xml:space="preserve">
|
||||
<value>Push 失败:{0}</value>
|
||||
</data>
|
||||
<data name="Git_CommandFailed" xml:space="preserve">
|
||||
<value>无法运行 git 命令:{0}</value>
|
||||
</data>
|
||||
<data name="Git_NoUpstream" xml:space="preserve">
|
||||
<value>未配置上游 branch。请设置跟踪:git push -u origin <branch></value>
|
||||
</data>
|
||||
<data name="Git_NonFastForward" xml:space="preserve">
|
||||
<value>Push 被拒绝:非快进式。请先 pull 更改或使用强制 push。</value>
|
||||
</data>
|
||||
<data name="Git_PushSuccessCmd" xml:space="preserve">
|
||||
<value>Push 成功(通过 git 命令)</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Cleanup Executor ==================== -->
|
||||
<data name="Cleanup_PushedCommitsBlocked" xml:space="preserve">
|
||||
<value>部分 commit 已被 push。启用 'AllowPushedCommits' 以继续。</value>
|
||||
</data>
|
||||
<data name="Cleanup_NotImplemented" xml:space="preserve">
|
||||
<value>清理类型 '{0}' 尚未实现</value>
|
||||
</data>
|
||||
<data name="Cleanup_Rebuilding" xml:space="preserve">
|
||||
<value>正在重建 commit 历史...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCount" xml:space="preserve">
|
||||
<value>正在重建 {0} 个 commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingCommit" xml:space="preserve">
|
||||
<value>正在处理 commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_UpdatingBranch" xml:space="preserve">
|
||||
<value>正在更新 branch 引用...</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashingMerges" xml:space="preserve">
|
||||
<value>正在压缩 merge commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_DropDuplicatesFailed" xml:space="preserve">
|
||||
<value>删除重复 commit 失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NeedTwoCommits" xml:space="preserve">
|
||||
<value>至少需要 2 个 commit 才能压缩</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsOnBranch" xml:space="preserve">
|
||||
<value>当前 branch 上未找到 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingCommits" xml:space="preserve">
|
||||
<value>未找到要删除的匹配 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToSquash" xml:space="preserve">
|
||||
<value>未指定要压缩的 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMergeCommits" xml:space="preserve">
|
||||
<value>未找到要压缩的匹配 merge commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_SquashMergeFailed" xml:space="preserve">
|
||||
<value>压缩 merge commit 失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoCommitsToFix" xml:space="preserve">
|
||||
<value>未指定要修复的 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_FixAuthorFailed" xml:space="preserve">
|
||||
<value>修复作者信息失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidatingFixes" xml:space="preserve">
|
||||
<value>正在合并 merge 修复 commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoFixCommits" xml:space="preserve">
|
||||
<value>没有要合并的修复 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_NoMatchingFixes" xml:space="preserve">
|
||||
<value>未找到要合并的匹配修复 commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_ConsolidateFailed" xml:space="preserve">
|
||||
<value>合并 merge 修复 commit 失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchivingBranches" xml:space="preserve">
|
||||
<value>正在归档陈旧的 branch...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ProcessingBranch" xml:space="preserve">
|
||||
<value>正在处理 branch {0}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveComplete" xml:space="preserve">
|
||||
<value>归档完成</value>
|
||||
</data>
|
||||
<data name="Cleanup_ArchiveFailed" xml:space="preserve">
|
||||
<value>归档 branch 失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_AnalyzingStructure" xml:space="preserve">
|
||||
<value>正在分析 branch 结构...</value>
|
||||
</data>
|
||||
<data name="Cleanup_FoundCommits" xml:space="preserve">
|
||||
<value>找到 {0} 个要线性化的 commit...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Linearizing" xml:space="preserve">
|
||||
<value>正在线性化 {0} 个 commit(移除 {1} 个 merge)...</value>
|
||||
</data>
|
||||
<data name="Cleanup_RebuildingCommit" xml:space="preserve">
|
||||
<value>正在重建 commit {0}/{1}...</value>
|
||||
</data>
|
||||
<data name="Cleanup_Reconciling" xml:space="preserve">
|
||||
<value>正在协调最终状态...</value>
|
||||
</data>
|
||||
<data name="Cleanup_AlreadyLinear" xml:space="preserve">
|
||||
<value>历史已是线性的 - 未找到 merge commit</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeComplete" xml:space="preserve">
|
||||
<value>线性化完成</value>
|
||||
</data>
|
||||
<data name="Cleanup_LinearizeFailed" xml:space="preserve">
|
||||
<value>线性化历史失败:{0}</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescReword" xml:space="preserve">
|
||||
<value>将重写 {0} 条 commit 消息以提高质量。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescSquash" xml:space="preserve">
|
||||
<value>将把 {0} 个重复的 commit 压缩为 1 个。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescConsolidate" xml:space="preserve">
|
||||
<value>将合并 {0} 个 merge 修复 commit。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescAuthorship" xml:space="preserve">
|
||||
<value>将修复 {0} 个 commit 的作者信息。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescTrivialMerges" xml:space="preserve">
|
||||
<value>将合并 {0} 个简单的 merge。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescArchive" xml:space="preserve">
|
||||
<value>将归档陈旧的 branch(如已合并则删除,否则添加标签)。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescLinearize" xml:space="preserve">
|
||||
<value>将通过移除 merge commit 并按日期排序来线性化历史。</value>
|
||||
</data>
|
||||
<data name="Cleanup_DescGeneric" xml:space="preserve">
|
||||
<value>将处理 {0} 个 commit。</value>
|
||||
</data>
|
||||
<data name="Cleanup_ReconcileMerge" xml:space="preserve">
|
||||
<value>协调:在线性化后合并最终状态</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Validation ==================== -->
|
||||
<data name="Validation_WorkspaceRequired" xml:space="preserve">
|
||||
<value>需要 WorkspaceRoot</value>
|
||||
</data>
|
||||
<data name="Validation_WorkspaceNotFound" xml:space="preserve">
|
||||
<value>WorkspaceRoot 目录不存在:{0}</value>
|
||||
</data>
|
||||
<data name="Validation_MaxCommitsPositive" xml:space="preserve">
|
||||
<value>MaxCommitsPerRepo 必须大于 0</value>
|
||||
</data>
|
||||
<data name="Validation_RulesNull" xml:space="preserve">
|
||||
<value>Rules 不能为 null</value>
|
||||
</data>
|
||||
<data name="Validation_AiOptionsNull" xml:space="preserve">
|
||||
<value>Ai 选项不能为 null</value>
|
||||
</data>
|
||||
<data name="Validation_InvalidOptions" xml:space="preserve">
|
||||
<value>无效的 GitImproverOptions:{0}</value>
|
||||
</data>
|
||||
<data name="Validation_WeightsSum" xml:space="preserve">
|
||||
<value>权重总和必须为 1.0(当前:{0})</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Service Messages ==================== -->
|
||||
<data name="Service_UnknownError" xml:space="preserve">
|
||||
<value>未知错误</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegistered" xml:space="preserve">
|
||||
<value>仓库未注册</value>
|
||||
</data>
|
||||
<data name="Service_UncommittedChanges" xml:space="preserve">
|
||||
<value>存在未提交的更改,无法重写 commit。请先提交或暂存您的更改。</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotFound" xml:space="preserve">
|
||||
<value>未找到仓库:{0}</value>
|
||||
</data>
|
||||
<data name="Service_NoSuggestion" xml:space="preserve">
|
||||
<value>没有可用的建议消息</value>
|
||||
</data>
|
||||
<data name="Service_RepoNotRegisteredPath" xml:space="preserve">
|
||||
<value>仓库未注册:{0}</value>
|
||||
</data>
|
||||
<data name="Service_ApiKeyNotConfigured" xml:space="preserve">
|
||||
<value>未配置 API 密钥。请在设置中设置您的 API 密钥。</value>
|
||||
</data>
|
||||
<data name="Service_AiAnalysisFailed" xml:space="preserve">
|
||||
<value>AI 分析失败</value>
|
||||
</data>
|
||||
<data name="Service_AiFallback" xml:space="preserve">
|
||||
<value>AI 未返回结构化输出 - 已回退到原始消息</value>
|
||||
</data>
|
||||
<data name="Service_PushSuccess" xml:space="preserve">
|
||||
<value>Push 成功</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Analyzer Status ==================== -->
|
||||
<data name="Health_LoadingCommits" xml:space="preserve">
|
||||
<value>正在加载 commit</value>
|
||||
</data>
|
||||
<data name="Health_DetectingDuplicates" xml:space="preserve">
|
||||
<value>正在检测重复项</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMerges" xml:space="preserve">
|
||||
<value>正在分析 merge commit</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingBranches" xml:space="preserve">
|
||||
<value>正在分析 branch 复杂度</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingMessages" xml:space="preserve">
|
||||
<value>正在分析消息质量</value>
|
||||
</data>
|
||||
<data name="Health_AnalyzingAuthorship" xml:space="preserve">
|
||||
<value>正在分析作者信息</value>
|
||||
</data>
|
||||
<data name="Health_Complete" xml:space="preserve">
|
||||
<value>完成</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Report Issues ==================== -->
|
||||
<data name="Report_DuplicateContent" xml:space="preserve">
|
||||
<value>内容相同的重复 commit</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateContentDesc" xml:space="preserve">
|
||||
<value>找到 {0} 组内容相同的 commit({1} 个冗余 commit)。这些可以安全压缩,因为它们具有相同的树 SHA。</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessages" xml:space="preserve">
|
||||
<value>消息重复的 commit</value>
|
||||
</data>
|
||||
<data name="Report_DuplicateMessagesDesc" xml:space="preserve">
|
||||
<value>找到 {0} 组消息相同但代码更改不同的 commit({1} 个 commit)。考虑使用更具描述性的消息来区分更改。</value>
|
||||
</data>
|
||||
<data name="Report_ExcessiveMerges" xml:space="preserve">
|
||||
<value>过多的 merge commit</value>
|
||||
</data>
|
||||
<data name="Report_HighMergeRatio" xml:space="preserve">
|
||||
<value>Merge commit 比例过高</value>
|
||||
</data>
|
||||
<data name="Report_MergeRatioDesc" xml:space="preserve">
|
||||
<value>您的仓库有 {0}% 的 merge commit 比例({1}/{2} 个 commit)。考虑使用 rebase 工作流或压缩 merge。</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixCommits" xml:space="preserve">
|
||||
<value>检测到 merge 修复 commit</value>
|
||||
</data>
|
||||
<data name="Report_MergeFixDesc" xml:space="preserve">
|
||||
<value>在 merge 后找到 {0} 个类似 'fix merge' 的消息的 commit。</value>
|
||||
</data>
|
||||
<data name="Report_CrossMerges" xml:space="preserve">
|
||||
<value>Branch 之间的交叉 merge</value>
|
||||
</data>
|
||||
<data name="Report_CrossMergesDesc" xml:space="preserve">
|
||||
<value>检测到 {0} 个功能 branch 之间的交叉 merge。使用仅 merge 到主分支的功能 branch。</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranches" xml:space="preserve">
|
||||
<value>陈旧的 branch</value>
|
||||
</data>
|
||||
<data name="Report_StaleBranchesDesc" xml:space="preserve">
|
||||
<value>找到 {0} 个超过 30 天没有活动的 branch。</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Safety Warnings ==================== -->
|
||||
<data name="Safety_UncommittedChanges" xml:space="preserve">
|
||||
<value>您有未提交的更改。请先提交或暂存它们。</value>
|
||||
</data>
|
||||
<data name="Safety_PushedCommits" xml:space="preserve">
|
||||
<value>{0} 个 commit 已被 push 到远程仓库。重写它们将需要强制 push,并可能影响协作者。</value>
|
||||
</data>
|
||||
<data name="Safety_BehindRemote" xml:space="preserve">
|
||||
<value>您的 branch 落后远程仓库 {0} 个 commit。考虑先 pull 以避免冲突。</value>
|
||||
</data>
|
||||
|
||||
<!-- ==================== Health Status ==================== -->
|
||||
<data name="HealthStatus_NeedsAttention" xml:space="preserve">
|
||||
<value>仓库存在应该解决的明显问题。</value>
|
||||
</data>
|
||||
<data name="HealthStatus_Critical" xml:space="preserve">
|
||||
<value>仓库需要立即关注。历史严重退化。</value>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
192
Resources/Strings/Strings.cs
Normal file
192
Resources/Strings/Strings.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
/// <summary>
|
||||
/// Provides localized strings for the GitCommitEditor library.
|
||||
/// </summary>
|
||||
public static class Str
|
||||
{
|
||||
private static readonly ResourceManager ResourceManager =
|
||||
new("MarketAlly.GitCommitEditor.Resources.Strings.LibStrings", typeof(Str).Assembly);
|
||||
|
||||
private static CultureInfo? _culture;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the culture used for resource lookups.
|
||||
/// If null, uses the current UI culture.
|
||||
/// </summary>
|
||||
public static CultureInfo? Culture
|
||||
{
|
||||
get => _culture;
|
||||
set => _culture = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localized string by key.
|
||||
/// </summary>
|
||||
public static string Get(string key)
|
||||
{
|
||||
return ResourceManager.GetString(key, _culture) ?? key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localized string by key with format arguments.
|
||||
/// </summary>
|
||||
public static string Get(string key, params object[] args)
|
||||
{
|
||||
var format = ResourceManager.GetString(key, _culture) ?? key;
|
||||
return string.Format(format, args);
|
||||
}
|
||||
|
||||
// ==================== Commit Message Analyzer ====================
|
||||
|
||||
public static string Analyzer_MessageEmpty => Get("Analyzer_MessageEmpty");
|
||||
public static string Analyzer_SubjectTooShort(int length, int min) => Get("Analyzer_SubjectTooShort", length, min);
|
||||
public static string Analyzer_SubjectTooLong(int length, int max) => Get("Analyzer_SubjectTooLong", length, max);
|
||||
public static string Analyzer_BannedPhrase(string phrase) => Get("Analyzer_BannedPhrase", phrase);
|
||||
public static string Analyzer_NotConventional => Get("Analyzer_NotConventional");
|
||||
public static string Analyzer_UnknownType(string type) => Get("Analyzer_UnknownType", type);
|
||||
public static string Analyzer_NoIssueRef => Get("Analyzer_NoIssueRef");
|
||||
public static string Analyzer_CapitalLetter => Get("Analyzer_CapitalLetter");
|
||||
public static string Analyzer_NoPeriod => Get("Analyzer_NoPeriod");
|
||||
public static string Analyzer_ImperativeMood(string word, string suggestion) => Get("Analyzer_ImperativeMood", word, suggestion);
|
||||
public static string Analyzer_BodyTooShort(int length, int min) => Get("Analyzer_BodyTooShort", length, min);
|
||||
public static string Analyzer_BlankLine => Get("Analyzer_BlankLine");
|
||||
public static string Analyzer_NotDescriptive(string subject, int fileCount) => Get("Analyzer_NotDescriptive", subject, fileCount);
|
||||
public static string Analyzer_TooVague(int fileCount) => Get("Analyzer_TooVague", fileCount);
|
||||
public static string Analyzer_LargeChange(int fileCount, int lineCount) => Get("Analyzer_LargeChange", fileCount, lineCount);
|
||||
public static string Analyzer_MajorChange(int fileCount) => Get("Analyzer_MajorChange", fileCount);
|
||||
public static string Analyzer_MentionArea(string extensions) => Get("Analyzer_MentionArea", extensions);
|
||||
|
||||
// ==================== Git Operations Service ====================
|
||||
|
||||
public static string Git_NoCommits => Get("Git_NoCommits");
|
||||
public static string Git_CommitNotFound(string hash) => Get("Git_CommitNotFound", hash);
|
||||
public static string Git_NotAncestor => Get("Git_NotAncestor");
|
||||
public static string Git_NoTargetCommits => Get("Git_NoTargetCommits");
|
||||
public static string Git_ParentMismatch(string sha) => Get("Git_ParentMismatch", sha);
|
||||
public static string Git_HeadUpdateFailed(string sha) => Get("Git_HeadUpdateFailed", sha);
|
||||
public static string Git_VerificationFailed(string expected, string actual) => Get("Git_VerificationFailed", expected, actual);
|
||||
public static string Git_OldCommitReachable(string sha) => Get("Git_OldCommitReachable", sha);
|
||||
public static string Git_Error(string message) => Get("Git_Error", message);
|
||||
public static string Git_RemoteNotFound(string remote) => Get("Git_RemoteNotFound", remote);
|
||||
public static string Git_NoUpstreamNoOrigin(string branch) => Get("Git_NoUpstreamNoOrigin", branch);
|
||||
public static string Git_ForcePushSuccess => Get("Git_ForcePushSuccess");
|
||||
public static string Git_ForcePushedTo(string branch) => Get("Git_ForcePushedTo", branch);
|
||||
public static string Git_ProcessFailed => Get("Git_ProcessFailed");
|
||||
public static string Git_ForcePushSuccessCmd => Get("Git_ForcePushSuccessCmd");
|
||||
public static string Git_PushFailed(string message) => Get("Git_PushFailed", message);
|
||||
public static string Git_CommandFailed(string message) => Get("Git_CommandFailed", message);
|
||||
public static string Git_NoUpstream => Get("Git_NoUpstream");
|
||||
public static string Git_NonFastForward => Get("Git_NonFastForward");
|
||||
public static string Git_PushSuccessCmd => Get("Git_PushSuccessCmd");
|
||||
|
||||
// ==================== Cleanup Executor ====================
|
||||
|
||||
public static string Cleanup_PushedCommitsBlocked => Get("Cleanup_PushedCommitsBlocked");
|
||||
public static string Cleanup_NotImplemented(string type) => Get("Cleanup_NotImplemented", type);
|
||||
public static string Cleanup_Rebuilding => Get("Cleanup_Rebuilding");
|
||||
public static string Cleanup_RebuildingCount(int count) => Get("Cleanup_RebuildingCount", count);
|
||||
public static string Cleanup_ProcessingCommit(int current, int total) => Get("Cleanup_ProcessingCommit", current, total);
|
||||
public static string Cleanup_UpdatingBranch => Get("Cleanup_UpdatingBranch");
|
||||
public static string Cleanup_SquashingMerges => Get("Cleanup_SquashingMerges");
|
||||
public static string Cleanup_DropDuplicatesFailed(string message) => Get("Cleanup_DropDuplicatesFailed", message);
|
||||
public static string Cleanup_NeedTwoCommits => Get("Cleanup_NeedTwoCommits");
|
||||
public static string Cleanup_NoCommitsOnBranch => Get("Cleanup_NoCommitsOnBranch");
|
||||
public static string Cleanup_NoMatchingCommits => Get("Cleanup_NoMatchingCommits");
|
||||
public static string Cleanup_NoCommitsToSquash => Get("Cleanup_NoCommitsToSquash");
|
||||
public static string Cleanup_NoMergeCommits => Get("Cleanup_NoMergeCommits");
|
||||
public static string Cleanup_SquashMergeFailed(string message) => Get("Cleanup_SquashMergeFailed", message);
|
||||
public static string Cleanup_NoCommitsToFix => Get("Cleanup_NoCommitsToFix");
|
||||
public static string Cleanup_FixAuthorFailed(string message) => Get("Cleanup_FixAuthorFailed", message);
|
||||
public static string Cleanup_ConsolidatingFixes => Get("Cleanup_ConsolidatingFixes");
|
||||
public static string Cleanup_NoFixCommits => Get("Cleanup_NoFixCommits");
|
||||
public static string Cleanup_NoMatchingFixes => Get("Cleanup_NoMatchingFixes");
|
||||
public static string Cleanup_ConsolidateFailed(string message) => Get("Cleanup_ConsolidateFailed", message);
|
||||
public static string Cleanup_ArchivingBranches => Get("Cleanup_ArchivingBranches");
|
||||
public static string Cleanup_ProcessingBranch(string branch) => Get("Cleanup_ProcessingBranch", branch);
|
||||
public static string Cleanup_ArchiveComplete => Get("Cleanup_ArchiveComplete");
|
||||
public static string Cleanup_ArchiveFailed(string message) => Get("Cleanup_ArchiveFailed", message);
|
||||
public static string Cleanup_AnalyzingStructure => Get("Cleanup_AnalyzingStructure");
|
||||
public static string Cleanup_FoundCommits(int count) => Get("Cleanup_FoundCommits", count);
|
||||
public static string Cleanup_Linearizing(int commits, int merges) => Get("Cleanup_Linearizing", commits, merges);
|
||||
public static string Cleanup_RebuildingCommit(int current, int total) => Get("Cleanup_RebuildingCommit", current, total);
|
||||
public static string Cleanup_Reconciling => Get("Cleanup_Reconciling");
|
||||
public static string Cleanup_AlreadyLinear => Get("Cleanup_AlreadyLinear");
|
||||
public static string Cleanup_LinearizeComplete => Get("Cleanup_LinearizeComplete");
|
||||
public static string Cleanup_LinearizeFailed(string message) => Get("Cleanup_LinearizeFailed", message);
|
||||
public static string Cleanup_DescReword(int count) => Get("Cleanup_DescReword", count);
|
||||
public static string Cleanup_DescSquash(int count) => Get("Cleanup_DescSquash", count);
|
||||
public static string Cleanup_DescSquashMerges(int count) => Get("Cleanup_DescTrivialMerges", count);
|
||||
public static string Cleanup_DescConsolidate(int count) => Get("Cleanup_DescConsolidate", count);
|
||||
public static string Cleanup_DescAuthorship(int count) => Get("Cleanup_DescAuthorship", count);
|
||||
public static string Cleanup_DescFixAuthor(int count) => Get("Cleanup_DescAuthorship", count);
|
||||
public static string Cleanup_DescTrivialMerges(int count) => Get("Cleanup_DescTrivialMerges", count);
|
||||
public static string Cleanup_DescArchive => Get("Cleanup_DescArchive");
|
||||
public static string Cleanup_DescLinearize => Get("Cleanup_DescLinearize");
|
||||
public static string Cleanup_DescGeneric(int count) => Get("Cleanup_DescGeneric", count);
|
||||
public static string Cleanup_DescDefault(int count) => Get("Cleanup_DescGeneric", count);
|
||||
public static string Cleanup_ReconcileMerge => Get("Cleanup_ReconcileMerge");
|
||||
|
||||
// ==================== Validation ====================
|
||||
|
||||
public static string Validation_WorkspaceRequired => Get("Validation_WorkspaceRequired");
|
||||
public static string Validation_WorkspaceNotFound(string path) => Get("Validation_WorkspaceNotFound", path);
|
||||
public static string Validation_MaxCommitsPositive => Get("Validation_MaxCommitsPositive");
|
||||
public static string Validation_RulesNull => Get("Validation_RulesNull");
|
||||
public static string Validation_AiOptionsNull => Get("Validation_AiOptionsNull");
|
||||
public static string Validation_InvalidOptions(string messages) => Get("Validation_InvalidOptions", messages);
|
||||
public static string Validation_WeightsSum(double sum) => Get("Validation_WeightsSum", sum);
|
||||
|
||||
// ==================== Service Messages ====================
|
||||
|
||||
public static string Service_UnknownError => Get("Service_UnknownError");
|
||||
public static string Service_RepoNotRegistered => Get("Service_RepoNotRegistered");
|
||||
public static string Service_UncommittedChanges => Get("Service_UncommittedChanges");
|
||||
public static string Service_RepoNotFound(string id) => Get("Service_RepoNotFound", id);
|
||||
public static string Service_NoSuggestion => Get("Service_NoSuggestion");
|
||||
public static string Service_RepoNotRegisteredPath(string path) => Get("Service_RepoNotRegisteredPath", path);
|
||||
public static string Service_ApiKeyNotConfigured => Get("Service_ApiKeyNotConfigured");
|
||||
public static string Service_AiAnalysisFailed => Get("Service_AiAnalysisFailed");
|
||||
public static string Service_AiFallback => Get("Service_AiFallback");
|
||||
public static string Service_PushSuccess => Get("Service_PushSuccess");
|
||||
|
||||
// ==================== Health Analyzer Status ====================
|
||||
|
||||
public static string Health_LoadingCommits => Get("Health_LoadingCommits");
|
||||
public static string Health_DetectingDuplicates => Get("Health_DetectingDuplicates");
|
||||
public static string Health_AnalyzingMerges => Get("Health_AnalyzingMerges");
|
||||
public static string Health_AnalyzingBranches => Get("Health_AnalyzingBranches");
|
||||
public static string Health_AnalyzingMessages => Get("Health_AnalyzingMessages");
|
||||
public static string Health_AnalyzingAuthorship => Get("Health_AnalyzingAuthorship");
|
||||
public static string Health_Complete => Get("Health_Complete");
|
||||
|
||||
// ==================== Health Report Issues ====================
|
||||
|
||||
public static string Report_DuplicateContent => Get("Report_DuplicateContent");
|
||||
public static string Report_DuplicateContentDesc(int groups, int redundant) => Get("Report_DuplicateContentDesc", groups, redundant);
|
||||
public static string Report_DuplicateMessages => Get("Report_DuplicateMessages");
|
||||
public static string Report_DuplicateMessagesDesc(int groups, int commits) => Get("Report_DuplicateMessagesDesc", groups, commits);
|
||||
public static string Report_ExcessiveMerges => Get("Report_ExcessiveMerges");
|
||||
public static string Report_HighMergeRatio => Get("Report_HighMergeRatio");
|
||||
public static string Report_MergeRatioDesc(int ratio, int merges, int total) => Get("Report_MergeRatioDesc", ratio, merges, total);
|
||||
public static string Report_MergeFixCommits => Get("Report_MergeFixCommits");
|
||||
public static string Report_MergeFixDesc(int count) => Get("Report_MergeFixDesc", count);
|
||||
public static string Report_CrossMerges => Get("Report_CrossMerges");
|
||||
public static string Report_CrossMergesDesc(int count) => Get("Report_CrossMergesDesc", count);
|
||||
public static string Report_StaleBranches => Get("Report_StaleBranches");
|
||||
public static string Report_StaleBranchesDesc(int count) => Get("Report_StaleBranchesDesc", count);
|
||||
|
||||
// ==================== Safety Warnings ====================
|
||||
|
||||
public static string Safety_UncommittedChanges => Get("Safety_UncommittedChanges");
|
||||
public static string Safety_PushedCommits(int count) => Get("Safety_PushedCommits", count);
|
||||
public static string Safety_BehindRemote(int count) => Get("Safety_BehindRemote", count);
|
||||
|
||||
// ==================== Health Status ====================
|
||||
|
||||
public static string HealthStatus_NeedsAttention => Get("HealthStatus_NeedsAttention");
|
||||
public static string HealthStatus_Critical => Get("HealthStatus_Critical");
|
||||
}
|
||||
199
Rewriters/AiCommitRewriter.cs
Executable file
199
Rewriters/AiCommitRewriter.cs
Executable file
@ -0,0 +1,199 @@
|
||||
using System.Text;
|
||||
using MarketAlly.AIPlugin;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Plugins;
|
||||
using MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Rewriters;
|
||||
|
||||
/// <summary>
|
||||
/// Commit message rewriter using MarketAlly.AIPlugin's AIConversation API.
|
||||
/// Supports multiple AI providers (Claude, OpenAI, Gemini, Mistral, Qwen).
|
||||
/// </summary>
|
||||
public sealed class AICommitRewriter : ICommitMessageRewriter, IDisposable
|
||||
{
|
||||
private const string OperationType = "CommitSuggestion";
|
||||
private readonly AiOptions _options;
|
||||
private readonly AIConversation _conversation;
|
||||
private readonly ICostTrackingService? _costTracker;
|
||||
|
||||
public AICommitRewriter(AiOptions options, ICostTrackingService? costTracker = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_costTracker = costTracker;
|
||||
|
||||
var provider = ParseProvider(options.Provider);
|
||||
|
||||
_conversation = AIConversationBuilder.Create()
|
||||
.UseProvider(provider, options.ApiKey)
|
||||
.UseModel(options.Model)
|
||||
.WithSystemPrompt(BuildSystemPrompt())
|
||||
.WithTemperature(0.3) // Lower temperature for more consistent output
|
||||
.WithMaxTokens(options.MaxTokens)
|
||||
.WithToolExecutionLimit(3)
|
||||
.RegisterPlugin<ReturnCommitMessagePlugin>()
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static AIProvider ParseProvider(string? providerName)
|
||||
{
|
||||
return providerName?.ToLowerInvariant() switch
|
||||
{
|
||||
"claude" or "anthropic" => AIProvider.Claude,
|
||||
"openai" or "gpt" => AIProvider.OpenAI,
|
||||
"gemini" or "google" => AIProvider.Gemini,
|
||||
"mistral" => AIProvider.Mistral,
|
||||
"qwen" or "alibaba" => AIProvider.Qwen,
|
||||
_ => AIProvider.Claude // Default to Claude
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildSystemPrompt()
|
||||
{
|
||||
return """
|
||||
You are a git commit message expert. Your task is to improve commit messages following conventional commit format.
|
||||
|
||||
RULES:
|
||||
1. Description: max 60 chars, imperative mood ("add" not "added"), no period, lowercase start
|
||||
2. Scope is optional - use it for the area of codebase (e.g., "api", "ui", "auth")
|
||||
3. Be specific about WHAT changed based on the files and diff
|
||||
4. Add a body ONLY if the change is complex and needs explanation
|
||||
5. Body should explain WHY, not repeat WHAT
|
||||
|
||||
When you receive commit information, call ReturnCommitMessage with the improved message.
|
||||
""";
|
||||
}
|
||||
|
||||
public async Task<SuggestionResult> SuggestImprovedMessageAsync(CommitAnalysis analysis, CancellationToken ct = default)
|
||||
{
|
||||
var prompt = BuildPrompt(analysis);
|
||||
|
||||
var response = await _conversation.SendForStructuredOutputAsync<CommitMessageResult>(
|
||||
prompt,
|
||||
"ReturnCommitMessage",
|
||||
ct);
|
||||
|
||||
// Extract cost information from response
|
||||
var inputTokens = response.InputTokens;
|
||||
var outputTokens = response.OutputTokens;
|
||||
var cost = response.EstimatedCost ?? 0m;
|
||||
|
||||
// Record cost if tracking service is available
|
||||
_costTracker?.RecordOperation(OperationType, inputTokens, outputTokens, cost);
|
||||
|
||||
if (response.StructuredOutput is CommitMessageResult result)
|
||||
{
|
||||
// Combine subject and body
|
||||
var suggestion = string.IsNullOrWhiteSpace(result.Body)
|
||||
? result.Subject.Trim()
|
||||
: $"{result.Subject.Trim()}\n\n{result.Body.Trim()}";
|
||||
|
||||
// Check if the suggestion is actually different from original
|
||||
if (string.Equals(suggestion, analysis.OriginalMessage, StringComparison.Ordinal))
|
||||
{
|
||||
return SuggestionResult.FailedWithOriginal(
|
||||
analysis.OriginalMessage,
|
||||
$"AI returned same message as original. Raw: {response.FinalMessage}",
|
||||
inputTokens, outputTokens, cost);
|
||||
}
|
||||
|
||||
return SuggestionResult.Succeeded(suggestion, inputTokens, outputTokens, cost);
|
||||
}
|
||||
|
||||
// AI failed to return structured output
|
||||
return SuggestionResult.FailedWithOriginal(
|
||||
analysis.OriginalMessage,
|
||||
$"No structured output. FinalMessage: {response.FinalMessage ?? "(null)"}",
|
||||
inputTokens, outputTokens, cost);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<(CommitAnalysis Analysis, SuggestionResult Result)>> SuggestBatchAsync(
|
||||
IEnumerable<CommitAnalysis> analyses,
|
||||
IProgress<int>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var results = new List<(CommitAnalysis, SuggestionResult)>();
|
||||
var analysisList = analyses.ToList();
|
||||
var processed = 0;
|
||||
|
||||
foreach (var analysis in analysisList)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await SuggestImprovedMessageAsync(analysis, ct);
|
||||
results.Add((analysis, result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
results.Add((analysis, SuggestionResult.Failed(ex.Message)));
|
||||
}
|
||||
|
||||
processed++;
|
||||
progress?.Report(processed);
|
||||
|
||||
// Rate limiting delay between requests
|
||||
if (processed < analysisList.Count && _options.RateLimitDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(_options.RateLimitDelayMs, ct);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private string BuildPrompt(CommitAnalysis analysis)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Please improve this commit message:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"ORIGINAL MESSAGE: {analysis.OriginalMessage}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("FILES CHANGED:");
|
||||
|
||||
var files = analysis.FilesChanged.Take(20).ToList();
|
||||
foreach (var file in files)
|
||||
{
|
||||
sb.AppendLine($" - {file}");
|
||||
}
|
||||
if (analysis.FilesChanged.Count > 20)
|
||||
{
|
||||
sb.AppendLine($" ... and {analysis.FilesChanged.Count - 20} more files");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"STATS: +{analysis.LinesAdded} -{analysis.LinesDeleted} lines");
|
||||
|
||||
if (!string.IsNullOrEmpty(analysis.DiffSummary) && _options.IncludeDiffContext)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("DIFF SUMMARY:");
|
||||
var diffLines = analysis.DiffSummary.Split('\n').Take(_options.MaxDiffLines);
|
||||
sb.AppendLine(string.Join('\n', diffLines));
|
||||
}
|
||||
|
||||
if (analysis.Quality.Issues.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("ISSUES WITH CURRENT MESSAGE:");
|
||||
foreach (var issue in analysis.Quality.Issues)
|
||||
{
|
||||
sb.AppendLine($" - [{issue.Severity}] {issue.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Call ReturnCommitMessage with the improved message.");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// AIConversation handles its own disposal
|
||||
}
|
||||
}
|
||||
87
Rewriters/DynamicCommitRewriter.cs
Normal file
87
Rewriters/DynamicCommitRewriter.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
using MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Rewriters;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when AI features are used without an API key configured.
|
||||
/// </summary>
|
||||
public class ApiKeyNotConfiguredException : InvalidOperationException
|
||||
{
|
||||
public ApiKeyNotConfiguredException()
|
||||
: base(Str.Service_ApiKeyNotConfigured)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic commit rewriter that uses AI when configured, or throws if not.
|
||||
/// </summary>
|
||||
public sealed class DynamicCommitRewriter : ICommitMessageRewriter, IDisposable
|
||||
{
|
||||
private readonly AiOptions _options;
|
||||
private readonly ICostTrackingService? _costTracker;
|
||||
private AICommitRewriter? _aiRewriter;
|
||||
private string? _lastApiKey;
|
||||
private string? _lastProvider;
|
||||
private string? _lastModel;
|
||||
|
||||
public DynamicCommitRewriter(AiOptions options, ICostTrackingService? costTracker = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_costTracker = costTracker;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an API key is configured and AI features are available.
|
||||
/// </summary>
|
||||
public bool IsConfigured => !string.IsNullOrEmpty(_options.ApiKey);
|
||||
|
||||
private AICommitRewriter GetRewriter()
|
||||
{
|
||||
// If no API key, throw exception
|
||||
if (string.IsNullOrEmpty(_options.ApiKey))
|
||||
{
|
||||
throw new ApiKeyNotConfiguredException();
|
||||
}
|
||||
|
||||
// Check if we need to create/recreate the AI rewriter
|
||||
// (API key, provider, or model changed)
|
||||
if (_aiRewriter == null ||
|
||||
_lastApiKey != _options.ApiKey ||
|
||||
_lastProvider != _options.Provider ||
|
||||
_lastModel != _options.Model)
|
||||
{
|
||||
// Dispose old rewriter if exists
|
||||
_aiRewriter?.Dispose();
|
||||
|
||||
// Create new rewriter with current settings
|
||||
_aiRewriter = new AICommitRewriter(_options, _costTracker);
|
||||
_lastApiKey = _options.ApiKey;
|
||||
_lastProvider = _options.Provider;
|
||||
_lastModel = _options.Model;
|
||||
}
|
||||
|
||||
return _aiRewriter;
|
||||
}
|
||||
|
||||
public Task<SuggestionResult> SuggestImprovedMessageAsync(CommitAnalysis analysis, CancellationToken ct = default)
|
||||
{
|
||||
return GetRewriter().SuggestImprovedMessageAsync(analysis, ct);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<(CommitAnalysis Analysis, SuggestionResult Result)>> SuggestBatchAsync(
|
||||
IEnumerable<CommitAnalysis> analyses,
|
||||
IProgress<int>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return GetRewriter().SuggestBatchAsync(analyses, progress, ct);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_aiRewriter?.Dispose();
|
||||
}
|
||||
}
|
||||
13
Rewriters/ICommitMessageRewriter.cs
Normal file
13
Rewriters/ICommitMessageRewriter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Rewriters;
|
||||
|
||||
public interface ICommitMessageRewriter
|
||||
{
|
||||
Task<SuggestionResult> SuggestImprovedMessageAsync(CommitAnalysis analysis, CancellationToken ct = default);
|
||||
|
||||
Task<IReadOnlyList<(CommitAnalysis Analysis, SuggestionResult Result)>> SuggestBatchAsync(
|
||||
IEnumerable<CommitAnalysis> analyses,
|
||||
IProgress<int>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
1319
Services/CleanupExecutor.cs
Executable file
1319
Services/CleanupExecutor.cs
Executable file
File diff suppressed because it is too large
Load Diff
337
Services/CommitMessageAnalyzer.cs
Executable file
337
Services/CommitMessageAnalyzer.cs
Executable file
@ -0,0 +1,337 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public sealed partial class CommitMessageAnalyzer : ICommitMessageAnalyzer
|
||||
{
|
||||
private readonly CommitMessageRules _rules;
|
||||
|
||||
// Vague/lazy words that don't describe what actually changed
|
||||
private static readonly string[] VagueWords =
|
||||
[
|
||||
"updates", "update", "changes", "change", "stuff", "things",
|
||||
"misc", "various", "some", "minor", "edits", "tweaks", "wip",
|
||||
"work in progress", "modifications", "adjustments"
|
||||
];
|
||||
|
||||
// Pattern: "ProjectName updates" or "ProjectName changes" - lazy naming
|
||||
[GeneratedRegex(@"^\w+\s+(updates?|changes?|edits?|tweaks?|modifications?)$", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex LazyProjectNamePattern();
|
||||
|
||||
[GeneratedRegex(@"^(?<type>\w+)(?:\((?<scope>[^)]+)\))?!?:\s*(?<subject>.+)$", RegexOptions.Singleline)]
|
||||
private static partial Regex ConventionalCommitPattern();
|
||||
|
||||
[GeneratedRegex(@"#\d+|[A-Z]{2,}-\d+", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex IssueReferencePattern();
|
||||
|
||||
[GeneratedRegex(@"\w+\([^)]+\):\s+.+")]
|
||||
private static partial Regex ScopePattern();
|
||||
|
||||
public CommitMessageAnalyzer(CommitMessageRules rules)
|
||||
{
|
||||
_rules = rules ?? throw new ArgumentNullException(nameof(rules));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyze without context - uses default context with 0 files.
|
||||
/// </summary>
|
||||
public MessageQualityScore Analyze(string message) => Analyze(message, new CommitContext());
|
||||
|
||||
/// <summary>
|
||||
/// Analyze with full context about the changes for smarter detection.
|
||||
/// </summary>
|
||||
public MessageQualityScore Analyze(string message, CommitContext context)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
return new MessageQualityScore
|
||||
{
|
||||
OverallScore = 0,
|
||||
Issues =
|
||||
[
|
||||
new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "EMPTY_MESSAGE",
|
||||
Message = Str.Analyzer_MessageEmpty
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
var issues = new List<QualityIssue>();
|
||||
var lines = message.Split('\n', StringSplitOptions.None);
|
||||
var subject = lines[0].Trim();
|
||||
var body = lines.Length > 2 ? string.Join('\n', lines.Skip(2)).Trim() : string.Empty;
|
||||
|
||||
int score = 100;
|
||||
|
||||
// Subject length checks
|
||||
if (subject.Length < _rules.MinSubjectLength)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "SUBJECT_TOO_SHORT",
|
||||
Message = Str.Analyzer_SubjectTooShort(subject.Length, _rules.MinSubjectLength)
|
||||
});
|
||||
score -= 25;
|
||||
}
|
||||
|
||||
if (subject.Length > _rules.MaxSubjectLength)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "SUBJECT_TOO_LONG",
|
||||
Message = Str.Analyzer_SubjectTooLong(subject.Length, _rules.MaxSubjectLength)
|
||||
});
|
||||
score -= 10;
|
||||
}
|
||||
|
||||
// Banned phrases check
|
||||
// For conventional commits (type: subject), check the subject part after the colon
|
||||
// For non-conventional commits, check the whole subject
|
||||
var lowerSubject = subject.ToLowerInvariant();
|
||||
var conventionalMatch = ConventionalCommitPattern().Match(subject);
|
||||
var textToCheck = conventionalMatch.Success
|
||||
? conventionalMatch.Groups["subject"].Value.ToLowerInvariant().Trim()
|
||||
: lowerSubject;
|
||||
|
||||
foreach (var banned in _rules.BannedPhrases)
|
||||
{
|
||||
var bannedLower = banned.ToLowerInvariant();
|
||||
if (textToCheck == bannedLower ||
|
||||
textToCheck.StartsWith(bannedLower + " ") ||
|
||||
textToCheck.StartsWith(bannedLower + ":") ||
|
||||
textToCheck.StartsWith(bannedLower + "."))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "BANNED_PHRASE",
|
||||
Message = Str.Analyzer_BannedPhrase(banned)
|
||||
});
|
||||
score -= 30;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Conventional commit check (conventionalMatch already computed above)
|
||||
if (_rules.RequireConventionalCommit)
|
||||
{
|
||||
if (!conventionalMatch.Success)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "NOT_CONVENTIONAL",
|
||||
Message = Str.Analyzer_NotConventional
|
||||
});
|
||||
score -= 20;
|
||||
}
|
||||
else if (!_rules.ConventionalTypes.Contains(conventionalMatch.Groups["type"].Value.ToLower()))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "UNKNOWN_TYPE",
|
||||
Message = Str.Analyzer_UnknownType(conventionalMatch.Groups["type"].Value)
|
||||
});
|
||||
score -= 10;
|
||||
}
|
||||
}
|
||||
|
||||
// Issue reference check
|
||||
if (_rules.RequireIssueReference)
|
||||
{
|
||||
if (!IssueReferencePattern().IsMatch(message))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "NO_ISSUE_REF",
|
||||
Message = Str.Analyzer_NoIssueRef
|
||||
});
|
||||
score -= 10;
|
||||
}
|
||||
}
|
||||
|
||||
// Capitalization (skip if conventional commit)
|
||||
if (subject.Length > 0 && !conventionalMatch.Success)
|
||||
{
|
||||
if (char.IsLower(subject[0]))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Info,
|
||||
Code = "LOWERCASE_START",
|
||||
Message = Str.Analyzer_CapitalLetter
|
||||
});
|
||||
score -= 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing period
|
||||
if (subject.EndsWith('.'))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Info,
|
||||
Code = "TRAILING_PERIOD",
|
||||
Message = Str.Analyzer_NoPeriod
|
||||
});
|
||||
score -= 5;
|
||||
}
|
||||
|
||||
// Imperative mood check (simple heuristic)
|
||||
var pastTenseIndicators = new[] { "added", "fixed", "updated", "changed", "removed", "deleted", "created" };
|
||||
var firstWord = subject.Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.ToLower() ?? "";
|
||||
if (pastTenseIndicators.Contains(firstWord))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Info,
|
||||
Code = "NOT_IMPERATIVE",
|
||||
Message = Str.Analyzer_ImperativeMood(firstWord, firstWord.TrimEnd('d', 'e'))
|
||||
});
|
||||
score -= 5;
|
||||
}
|
||||
|
||||
// Body length check
|
||||
if (_rules.MinBodyLength > 0 && body.Length < _rules.MinBodyLength)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "BODY_TOO_SHORT",
|
||||
Message = Str.Analyzer_BodyTooShort(body.Length, _rules.MinBodyLength)
|
||||
});
|
||||
score -= 10;
|
||||
}
|
||||
|
||||
// Blank line between subject and body
|
||||
if (lines.Length > 1 && !string.IsNullOrWhiteSpace(lines[1]))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Info,
|
||||
Code = "NO_BLANK_LINE",
|
||||
Message = Str.Analyzer_BlankLine
|
||||
});
|
||||
score -= 5;
|
||||
}
|
||||
|
||||
// === CONTEXT-AWARE CHECKS ===
|
||||
// These checks consider the actual scope of changes
|
||||
|
||||
if (context.FilesChanged > 0)
|
||||
{
|
||||
var totalLinesChanged = context.LinesAdded + context.LinesDeleted;
|
||||
|
||||
// Check for lazy "ProjectName updates" pattern
|
||||
if (LazyProjectNamePattern().IsMatch(subject))
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "LAZY_MESSAGE",
|
||||
Message = Str.Analyzer_NotDescriptive(subject, context.FilesChanged)
|
||||
});
|
||||
score -= 35;
|
||||
}
|
||||
|
||||
// Check for vague words with significant changes
|
||||
var subjectWords = subject.ToLowerInvariant().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var vagueWordCount = subjectWords.Count(w => VagueWords.Contains(w));
|
||||
var hasOnlyVagueContent = vagueWordCount > 0 && subjectWords.Length <= 3;
|
||||
|
||||
if (hasOnlyVagueContent && context.FilesChanged >= 3)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Error,
|
||||
Code = "VAGUE_FOR_SCOPE",
|
||||
Message = Str.Analyzer_TooVague(context.FilesChanged)
|
||||
});
|
||||
score -= 30;
|
||||
}
|
||||
|
||||
// Large changes need descriptive messages
|
||||
if (context.FilesChanged >= 10 && subject.Length < 30)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "LARGE_CHANGE_SHORT_MSG",
|
||||
Message = Str.Analyzer_LargeChange(context.FilesChanged, totalLinesChanged)
|
||||
});
|
||||
score -= 15;
|
||||
}
|
||||
|
||||
// Very large changes should have a body explaining context
|
||||
if (context.FilesChanged >= 20 && body.Length < 20)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Warning,
|
||||
Code = "LARGE_CHANGE_NO_BODY",
|
||||
Message = Str.Analyzer_MajorChange(context.FilesChanged)
|
||||
});
|
||||
score -= 10;
|
||||
}
|
||||
|
||||
// Message doesn't mention any file types or areas changed
|
||||
// Skip if message already has a scope pattern like feat(api): description
|
||||
if (context.FilesChanged >= 5 && context.FileNames.Count > 0)
|
||||
{
|
||||
// Check: word(scope): description AND body has content
|
||||
var hasScope = ScopePattern().IsMatch(subject);
|
||||
var bodyWords = body.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
|
||||
var hasBody = bodyWords >= 3;
|
||||
|
||||
// Only check for scope hint if no proper scope+body present
|
||||
if (!(hasScope && hasBody))
|
||||
{
|
||||
var extensions = context.FileNames
|
||||
.Select(f => Path.GetExtension(f).TrimStart('.').ToLower())
|
||||
.Where(e => !string.IsNullOrEmpty(e))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var folders = context.FileNames
|
||||
.Select(f => Path.GetDirectoryName(f)?.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).LastOrDefault())
|
||||
.Where(d => !string.IsNullOrEmpty(d))
|
||||
.Distinct()
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
// Check if message mentions any relevant context
|
||||
var messageLower = message.ToLowerInvariant();
|
||||
var mentionsFileType = extensions.Any(e => messageLower.Contains(e));
|
||||
var mentionsFolder = folders.Any(f => messageLower.Contains(f!.ToLowerInvariant()));
|
||||
|
||||
if (!mentionsFileType && !mentionsFolder && extensions.Count > 1)
|
||||
{
|
||||
issues.Add(new QualityIssue
|
||||
{
|
||||
Severity = IssueSeverity.Info,
|
||||
Code = "NO_SCOPE_HINT",
|
||||
Message = Str.Analyzer_MentionArea(string.Join(", ", extensions.Take(3)))
|
||||
});
|
||||
score -= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new MessageQualityScore
|
||||
{
|
||||
OverallScore = Math.Max(0, score),
|
||||
Issues = issues
|
||||
};
|
||||
}
|
||||
}
|
||||
191
Services/CostTrackingService.cs
Normal file
191
Services/CostTrackingService.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Persistence provider interface for cost tracking
|
||||
/// </summary>
|
||||
public interface ICostPersistenceProvider
|
||||
{
|
||||
string? GetValue(string key);
|
||||
void SetValue(string key, string value);
|
||||
void Remove(string key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks AI operation costs for the session with persistence support
|
||||
/// </summary>
|
||||
public class CostTrackingService : ICostTrackingService
|
||||
{
|
||||
private const string KeyLifetimeCost = "cost_lifetime_total";
|
||||
private const string KeyLifetimeOperations = "cost_lifetime_operations";
|
||||
private const string KeyLifetimeInputTokens = "cost_lifetime_input_tokens";
|
||||
private const string KeyLifetimeOutputTokens = "cost_lifetime_output_tokens";
|
||||
|
||||
private readonly ConcurrentDictionary<string, OperationCostSummary> _costBreakdown = new();
|
||||
private readonly ICostPersistenceProvider? _persistence;
|
||||
private readonly object _costLock = new();
|
||||
|
||||
private decimal _sessionCost;
|
||||
private int _totalInputTokens;
|
||||
private int _totalOutputTokens;
|
||||
private int _operationCount;
|
||||
|
||||
private decimal _lifetimeCost;
|
||||
private int _lifetimeInputTokens;
|
||||
private int _lifetimeOutputTokens;
|
||||
private int _lifetimeOperationCount;
|
||||
|
||||
public decimal SessionCost => _sessionCost;
|
||||
public decimal LifetimeCost => _lifetimeCost;
|
||||
public int TotalInputTokens => _totalInputTokens;
|
||||
public int TotalOutputTokens => _totalOutputTokens;
|
||||
public int OperationCount => _operationCount;
|
||||
public int LifetimeOperationCount => _lifetimeOperationCount;
|
||||
|
||||
public event EventHandler<CostUpdatedEventArgs>? CostUpdated;
|
||||
|
||||
public CostTrackingService(ICostPersistenceProvider? persistence = null)
|
||||
{
|
||||
_persistence = persistence;
|
||||
LoadState();
|
||||
}
|
||||
|
||||
public void RecordOperation(string operationType, int inputTokens, int outputTokens, decimal cost)
|
||||
{
|
||||
// Update session totals
|
||||
Interlocked.Add(ref _totalInputTokens, inputTokens);
|
||||
Interlocked.Add(ref _totalOutputTokens, outputTokens);
|
||||
Interlocked.Increment(ref _operationCount);
|
||||
|
||||
// Update lifetime totals
|
||||
Interlocked.Add(ref _lifetimeInputTokens, inputTokens);
|
||||
Interlocked.Add(ref _lifetimeOutputTokens, outputTokens);
|
||||
Interlocked.Increment(ref _lifetimeOperationCount);
|
||||
|
||||
// Thread-safe decimal addition using lock
|
||||
lock (_costLock)
|
||||
{
|
||||
_sessionCost += cost;
|
||||
_lifetimeCost += cost;
|
||||
}
|
||||
|
||||
// Update breakdown
|
||||
_costBreakdown.AddOrUpdate(
|
||||
operationType,
|
||||
_ => new OperationCostSummary
|
||||
{
|
||||
OperationType = operationType,
|
||||
Count = 1,
|
||||
TotalInputTokens = inputTokens,
|
||||
TotalOutputTokens = outputTokens,
|
||||
TotalCost = cost
|
||||
},
|
||||
(_, existing) =>
|
||||
{
|
||||
existing.Count++;
|
||||
existing.TotalInputTokens += inputTokens;
|
||||
existing.TotalOutputTokens += outputTokens;
|
||||
existing.TotalCost += cost;
|
||||
return existing;
|
||||
});
|
||||
|
||||
// Auto-save after each operation
|
||||
SaveState();
|
||||
|
||||
// Raise event
|
||||
CostUpdated?.Invoke(this, new CostUpdatedEventArgs(
|
||||
operationType,
|
||||
cost,
|
||||
_sessionCost,
|
||||
inputTokens,
|
||||
outputTokens));
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, OperationCostSummary> GetCostBreakdown()
|
||||
{
|
||||
return _costBreakdown.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
}
|
||||
|
||||
public void ResetSession()
|
||||
{
|
||||
_costBreakdown.Clear();
|
||||
_sessionCost = 0;
|
||||
_totalInputTokens = 0;
|
||||
_totalOutputTokens = 0;
|
||||
_operationCount = 0;
|
||||
|
||||
CostUpdated?.Invoke(this, new CostUpdatedEventArgs("SessionReset", 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
public void ResetAll()
|
||||
{
|
||||
ResetSession();
|
||||
_lifetimeCost = 0;
|
||||
_lifetimeInputTokens = 0;
|
||||
_lifetimeOutputTokens = 0;
|
||||
_lifetimeOperationCount = 0;
|
||||
|
||||
// Clear persisted data
|
||||
_persistence?.Remove(KeyLifetimeCost);
|
||||
_persistence?.Remove(KeyLifetimeOperations);
|
||||
_persistence?.Remove(KeyLifetimeInputTokens);
|
||||
_persistence?.Remove(KeyLifetimeOutputTokens);
|
||||
|
||||
CostUpdated?.Invoke(this, new CostUpdatedEventArgs("AllReset", 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
public void SaveState()
|
||||
{
|
||||
if (_persistence == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_persistence.SetValue(KeyLifetimeCost, _lifetimeCost.ToString("G"));
|
||||
_persistence.SetValue(KeyLifetimeOperations, _lifetimeOperationCount.ToString());
|
||||
_persistence.SetValue(KeyLifetimeInputTokens, _lifetimeInputTokens.ToString());
|
||||
_persistence.SetValue(KeyLifetimeOutputTokens, _lifetimeOutputTokens.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail - persistence is optional
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadState()
|
||||
{
|
||||
if (_persistence == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var costStr = _persistence.GetValue(KeyLifetimeCost);
|
||||
if (!string.IsNullOrEmpty(costStr) && decimal.TryParse(costStr, out var cost))
|
||||
{
|
||||
_lifetimeCost = cost;
|
||||
}
|
||||
|
||||
var opsStr = _persistence.GetValue(KeyLifetimeOperations);
|
||||
if (!string.IsNullOrEmpty(opsStr) && int.TryParse(opsStr, out var ops))
|
||||
{
|
||||
_lifetimeOperationCount = ops;
|
||||
}
|
||||
|
||||
var inputStr = _persistence.GetValue(KeyLifetimeInputTokens);
|
||||
if (!string.IsNullOrEmpty(inputStr) && int.TryParse(inputStr, out var input))
|
||||
{
|
||||
_lifetimeInputTokens = input;
|
||||
}
|
||||
|
||||
var outputStr = _persistence.GetValue(KeyLifetimeOutputTokens);
|
||||
if (!string.IsNullOrEmpty(outputStr) && int.TryParse(outputStr, out var output))
|
||||
{
|
||||
_lifetimeOutputTokens = output;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail - start fresh if persistence fails
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Services/FileStateRepository.cs
Normal file
49
Services/FileStateRepository.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public sealed class FileStateRepository : IStateRepository
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
|
||||
public FileStateRepository(string filePath)
|
||||
{
|
||||
_filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
public async Task<ImproverState> LoadAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
return new ImproverState();
|
||||
|
||||
try
|
||||
{
|
||||
var json = await File.ReadAllTextAsync(_filePath, ct);
|
||||
return JsonSerializer.Deserialize<ImproverState>(json) ?? new ImproverState();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return new ImproverState();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new ImproverState();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveAsync(ImproverState state, CancellationToken ct = default)
|
||||
{
|
||||
// Ensure directory exists
|
||||
var directory = Path.GetDirectoryName(_filePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
state.LastUpdated = DateTimeOffset.UtcNow;
|
||||
var json = JsonSerializer.Serialize(state, JsonOptions);
|
||||
await File.WriteAllTextAsync(_filePath, json, ct);
|
||||
}
|
||||
}
|
||||
312
Services/GitDiagnosticService.cs
Executable file
312
Services/GitDiagnosticService.cs
Executable file
@ -0,0 +1,312 @@
|
||||
using System.Text;
|
||||
using MarketAlly.AIPlugin;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Plugins;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Result of a git diagnosis operation including cost information
|
||||
/// </summary>
|
||||
public sealed record DiagnosisOperationResult(
|
||||
GitDiagnosisResult Diagnosis,
|
||||
int InputTokens = 0,
|
||||
int OutputTokens = 0,
|
||||
decimal EstimatedCost = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Service for diagnosing git issues using AI
|
||||
/// </summary>
|
||||
public interface IGitDiagnosticService
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnose a git issue based on the current repository state
|
||||
/// </summary>
|
||||
Task<GitDiagnosisResult> DiagnoseAsync(string repoPath, string? errorMessage = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Diagnose a git issue and return cost information
|
||||
/// </summary>
|
||||
Task<DiagnosisOperationResult> DiagnoseWithCostAsync(string repoPath, string? errorMessage = null, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI-powered git diagnostic service using MarketAlly.AIPlugin
|
||||
/// </summary>
|
||||
public sealed class GitDiagnosticService : IGitDiagnosticService, IDisposable
|
||||
{
|
||||
private const string OperationType = "GitDiagnosis";
|
||||
private readonly AiOptions _options;
|
||||
private readonly AIConversation _conversation;
|
||||
private readonly ICostTrackingService? _costTracker;
|
||||
|
||||
public GitDiagnosticService(AiOptions options, ICostTrackingService? costTracker = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_costTracker = costTracker;
|
||||
|
||||
var provider = ParseProvider(options.Provider);
|
||||
|
||||
_conversation = AIConversationBuilder.Create()
|
||||
.UseProvider(provider, options.ApiKey)
|
||||
.UseModel(options.Model)
|
||||
.WithSystemPrompt(BuildSystemPrompt())
|
||||
.WithTemperature(0.2) // Low temperature for consistent diagnostic output
|
||||
.WithMaxTokens(options.MaxTokens)
|
||||
.WithToolExecutionLimit(3)
|
||||
.RegisterPlugin<ReturnGitDiagnosisPlugin>()
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static AIProvider ParseProvider(string? providerName)
|
||||
{
|
||||
return providerName?.ToLowerInvariant() switch
|
||||
{
|
||||
"claude" or "anthropic" => AIProvider.Claude,
|
||||
"openai" or "gpt" => AIProvider.OpenAI,
|
||||
"gemini" or "google" => AIProvider.Gemini,
|
||||
"mistral" => AIProvider.Mistral,
|
||||
"qwen" or "alibaba" => AIProvider.Qwen,
|
||||
_ => AIProvider.Claude
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildSystemPrompt()
|
||||
{
|
||||
return """
|
||||
You are a git expert who diagnoses and fixes git issues. When given git status, log, and error information, you analyze the problem and provide a clear diagnosis with the exact commands to fix it.
|
||||
|
||||
COMMON ISSUES YOU CAN DIAGNOSE:
|
||||
- Diverged branches (local and remote have different commits)
|
||||
- Unrelated histories (refusing to merge unrelated histories)
|
||||
- Merge conflicts
|
||||
- Detached HEAD state
|
||||
- Failed rebases
|
||||
- Uncommitted changes blocking operations
|
||||
- Authentication issues
|
||||
- Remote tracking issues
|
||||
- Corrupt repository state
|
||||
|
||||
RULES:
|
||||
1. Always provide the exact command(s) needed to fix the issue
|
||||
2. Warn about any potential data loss
|
||||
3. Explain WHY the issue happened so the user can avoid it
|
||||
4. If multiple solutions exist, recommend the safest one
|
||||
5. Set appropriate risk level: low (safe), medium (some risk), high (potential data loss)
|
||||
|
||||
When you receive git information, analyze it and call ReturnGitDiagnosis with your findings.
|
||||
""";
|
||||
}
|
||||
|
||||
public async Task<GitDiagnosisResult> DiagnoseAsync(string repoPath, string? errorMessage = null, CancellationToken ct = default)
|
||||
{
|
||||
var result = await DiagnoseWithCostAsync(repoPath, errorMessage, ct);
|
||||
return result.Diagnosis;
|
||||
}
|
||||
|
||||
public async Task<DiagnosisOperationResult> DiagnoseWithCostAsync(string repoPath, string? errorMessage = null, CancellationToken ct = default)
|
||||
{
|
||||
var gitInfo = await GatherGitInfoAsync(repoPath);
|
||||
var prompt = BuildPrompt(gitInfo, errorMessage);
|
||||
|
||||
var response = await _conversation.SendForStructuredOutputAsync<GitDiagnosisResult>(
|
||||
prompt,
|
||||
"ReturnGitDiagnosis",
|
||||
ct);
|
||||
|
||||
// Extract cost information
|
||||
var inputTokens = response.InputTokens;
|
||||
var outputTokens = response.OutputTokens;
|
||||
var cost = response.EstimatedCost ?? 0m;
|
||||
|
||||
// Record cost if tracking service is available
|
||||
_costTracker?.RecordOperation(OperationType, inputTokens, outputTokens, cost);
|
||||
|
||||
if (response.StructuredOutput is GitDiagnosisResult result)
|
||||
{
|
||||
return new DiagnosisOperationResult(result, inputTokens, outputTokens, cost);
|
||||
}
|
||||
|
||||
// Fallback if structured output failed
|
||||
var fallback = new GitDiagnosisResult
|
||||
{
|
||||
Problem = "Unable to diagnose the issue",
|
||||
Cause = response.FinalMessage ?? Str.Service_AiAnalysisFailed,
|
||||
FixCommand = "git status",
|
||||
Warning = "Please review the git state manually",
|
||||
RiskLevel = RiskLevel.Low
|
||||
};
|
||||
return new DiagnosisOperationResult(fallback, inputTokens, outputTokens, cost);
|
||||
}
|
||||
|
||||
private async Task<GitInfo> GatherGitInfoAsync(string repoPath)
|
||||
{
|
||||
var info = new GitInfo();
|
||||
|
||||
try
|
||||
{
|
||||
// Get git status
|
||||
info.Status = await RunGitCommandAsync(repoPath, "status");
|
||||
|
||||
// Get recent log
|
||||
info.Log = await RunGitCommandAsync(repoPath, "log --oneline -10");
|
||||
|
||||
// Get branch info
|
||||
info.BranchInfo = await RunGitCommandAsync(repoPath, "branch -vv");
|
||||
|
||||
// Get remote info
|
||||
info.RemoteInfo = await RunGitCommandAsync(repoPath, "remote -v");
|
||||
|
||||
// Check for ongoing operations
|
||||
info.OngoingOps = await CheckOngoingOperationsAsync(repoPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.Error = ex.Message;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private static async Task<string> RunGitCommandAsync(string repoPath, string arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = repoPath,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = System.Diagnostics.Process.Start(startInfo);
|
||||
if (process == null) return "[Failed to start git]";
|
||||
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
var error = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return string.IsNullOrEmpty(error) ? output : $"{output}\n{error}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"[Error: {ex.Message}]";
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> CheckOngoingOperationsAsync(string repoPath)
|
||||
{
|
||||
var ops = new List<string>();
|
||||
|
||||
var gitDir = Path.Combine(repoPath, ".git");
|
||||
if (!Directory.Exists(gitDir))
|
||||
{
|
||||
// Might be a worktree, try to find the git dir
|
||||
var gitFile = Path.Combine(repoPath, ".git");
|
||||
if (File.Exists(gitFile))
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(gitFile);
|
||||
if (content.StartsWith("gitdir:"))
|
||||
{
|
||||
gitDir = content.Substring(7).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Directory.Exists(gitDir))
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(gitDir, "rebase-merge")) ||
|
||||
Directory.Exists(Path.Combine(gitDir, "rebase-apply")))
|
||||
{
|
||||
ops.Add("REBASE IN PROGRESS");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(gitDir, "MERGE_HEAD")))
|
||||
{
|
||||
ops.Add("MERGE IN PROGRESS");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(gitDir, "CHERRY_PICK_HEAD")))
|
||||
{
|
||||
ops.Add("CHERRY-PICK IN PROGRESS");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(gitDir, "REVERT_HEAD")))
|
||||
{
|
||||
ops.Add("REVERT IN PROGRESS");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(gitDir, "BISECT_LOG")))
|
||||
{
|
||||
ops.Add("BISECT IN PROGRESS");
|
||||
}
|
||||
}
|
||||
|
||||
return ops.Count > 0 ? string.Join(", ", ops) : "None";
|
||||
}
|
||||
|
||||
private static string BuildPrompt(GitInfo info, string? errorMessage)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Please diagnose this git issue and provide the fix:");
|
||||
sb.AppendLine();
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
sb.AppendLine("ERROR MESSAGE:");
|
||||
sb.AppendLine(errorMessage);
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("GIT STATUS:");
|
||||
sb.AppendLine(info.Status);
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("RECENT COMMITS:");
|
||||
sb.AppendLine(info.Log);
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("BRANCH INFO:");
|
||||
sb.AppendLine(info.BranchInfo);
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("REMOTE INFO:");
|
||||
sb.AppendLine(info.RemoteInfo);
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine($"ONGOING OPERATIONS: {info.OngoingOps}");
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Error))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"ADDITIONAL ERROR: {info.Error}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Call ReturnGitDiagnosis with the problem, cause, fix command, any warnings, and risk level.");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// AIConversation handles its own disposal
|
||||
}
|
||||
|
||||
private class GitInfo
|
||||
{
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public string Log { get; set; } = string.Empty;
|
||||
public string BranchInfo { get; set; } = string.Empty;
|
||||
public string RemoteInfo { get; set; } = string.Empty;
|
||||
public string OngoingOps { get; set; } = string.Empty;
|
||||
public string Error { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
759
Services/GitMessageImproverService.cs
Executable file
759
Services/GitMessageImproverService.cs
Executable file
@ -0,0 +1,759 @@
|
||||
using System.Text;
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
using MarketAlly.GitCommitEditor.Rewriters;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public sealed class GitMessageImproverService : IGitMessageImproverService
|
||||
{
|
||||
private readonly GitImproverOptions _options;
|
||||
private readonly IGitOperationsService _gitOps;
|
||||
private readonly ICommitMessageAnalyzer _analyzer;
|
||||
private readonly ICommitMessageRewriter _rewriter;
|
||||
private readonly IStateRepository _stateRepo;
|
||||
private readonly IHistoryHealthAnalyzer _healthAnalyzer;
|
||||
private readonly IHealthReportGenerator _reportGenerator;
|
||||
private readonly ICleanupExecutor _cleanupExecutor;
|
||||
private ImproverState _state;
|
||||
private bool _disposed;
|
||||
|
||||
public GitMessageImproverService(
|
||||
GitImproverOptions options,
|
||||
IGitOperationsService gitOps,
|
||||
ICommitMessageAnalyzer analyzer,
|
||||
ICommitMessageRewriter rewriter,
|
||||
IStateRepository stateRepo,
|
||||
IHistoryHealthAnalyzer? healthAnalyzer = null,
|
||||
IHealthReportGenerator? reportGenerator = null,
|
||||
ICleanupExecutor? cleanupExecutor = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(gitOps);
|
||||
ArgumentNullException.ThrowIfNull(analyzer);
|
||||
ArgumentNullException.ThrowIfNull(rewriter);
|
||||
ArgumentNullException.ThrowIfNull(stateRepo);
|
||||
|
||||
_options = options;
|
||||
_gitOps = gitOps;
|
||||
_analyzer = analyzer;
|
||||
_rewriter = rewriter;
|
||||
_stateRepo = stateRepo;
|
||||
|
||||
// Create default health analysis components if not provided
|
||||
var commitAnalyzer = new CommitAnalyzer(options.Rules);
|
||||
_healthAnalyzer = healthAnalyzer ?? new HistoryHealthAnalyzer(commitAnalyzer);
|
||||
_reportGenerator = reportGenerator ?? new HealthReportGenerator();
|
||||
_cleanupExecutor = cleanupExecutor ?? new CleanupExecutor(gitOps, analyzer, rewriter);
|
||||
_state = new ImproverState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a service with default implementations. For simple usage without DI.
|
||||
/// </summary>
|
||||
public static async Task<GitMessageImproverService> CreateAsync(GitImproverOptions options)
|
||||
{
|
||||
options.ValidateAndThrow();
|
||||
|
||||
var gitOps = new GitOperationsService();
|
||||
var analyzer = new CommitMessageAnalyzer(options.Rules);
|
||||
var stateRepo = new FileStateRepository(options.StateFilePath);
|
||||
|
||||
// Use DynamicCommitRewriter which handles API key changes at runtime
|
||||
ICommitMessageRewriter rewriter = new DynamicCommitRewriter(options.Ai);
|
||||
|
||||
var service = new GitMessageImproverService(options, gitOps, analyzer, rewriter, stateRepo);
|
||||
await service.LoadStateAsync();
|
||||
return service;
|
||||
}
|
||||
|
||||
public async Task LoadStateAsync(CancellationToken ct = default)
|
||||
{
|
||||
_state = await _stateRepo.LoadAsync(ct);
|
||||
|
||||
// Prune old history entries to prevent unbounded growth
|
||||
var pruned = _state.PruneHistory();
|
||||
var orphaned = _state.RemoveOrphanedHistory();
|
||||
|
||||
if (pruned > 0 || orphaned > 0)
|
||||
{
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ManagedRepo> Repos => _state.Repos;
|
||||
public IReadOnlyList<RewriteOperation> History => _state.History;
|
||||
|
||||
public async Task<IReadOnlyList<ManagedRepo>> ScanAndRegisterReposAsync(CancellationToken ct = default)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[GitMessageImproverService] Scanning WorkspaceRoot: {_options.WorkspaceRoot}");
|
||||
var discovered = _gitOps.DiscoverRepositories(_options.WorkspaceRoot);
|
||||
var newRepos = new List<ManagedRepo>();
|
||||
|
||||
foreach (var repoPath in discovered)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
if (_state.Repos.Any(r => r.Path.Equals(repoPath, StringComparison.OrdinalIgnoreCase)))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var managed = _gitOps.CreateManagedRepo(repoPath);
|
||||
_state.Repos.Add(managed);
|
||||
newRepos.Add(managed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip repos that can't be opened
|
||||
}
|
||||
}
|
||||
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
return newRepos;
|
||||
}
|
||||
|
||||
public async Task<ManagedRepo> RegisterRepoAsync(string repoPath)
|
||||
{
|
||||
var existing = _state.Repos.FirstOrDefault(r =>
|
||||
r.Path.Equals(repoPath, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existing != null)
|
||||
return existing;
|
||||
|
||||
var managed = _gitOps.CreateManagedRepo(repoPath);
|
||||
_state.Repos.Add(managed);
|
||||
await _stateRepo.SaveAsync(_state);
|
||||
return managed;
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterRepoAsync(string repoIdOrPath)
|
||||
{
|
||||
var repo = _state.Repos.FirstOrDefault(r =>
|
||||
r.Id == repoIdOrPath ||
|
||||
r.Path.Equals(repoIdOrPath, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (repo == null) return false;
|
||||
|
||||
_state.Repos.Remove(repo);
|
||||
await _stateRepo.SaveAsync(_state);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<BranchInfo> GetBranches(string repoPath)
|
||||
{
|
||||
return _gitOps.GetBranches(repoPath);
|
||||
}
|
||||
|
||||
public Task<bool> CheckoutBranchAsync(ManagedRepo repo, string branchName)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var repository = new LibGit2Sharp.Repository(repo.Path);
|
||||
|
||||
// Find the branch
|
||||
var branch = repository.Branches[branchName]
|
||||
?? repository.Branches[$"refs/heads/{branchName}"];
|
||||
|
||||
if (branch == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for uncommitted changes
|
||||
var status = repository.RetrieveStatus(new LibGit2Sharp.StatusOptions());
|
||||
if (status.IsDirty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checkout the branch
|
||||
LibGit2Sharp.Commands.Checkout(repository, branch);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<BackupBranchInfo> GetBackupBranches(string repoPath)
|
||||
{
|
||||
return _gitOps.GetBackupBranches(repoPath);
|
||||
}
|
||||
|
||||
public bool DeleteBranch(string repoPath, string branchName)
|
||||
{
|
||||
return _gitOps.DeleteBranch(repoPath, branchName);
|
||||
}
|
||||
|
||||
public int DeleteAllBackupBranches(string repoPath)
|
||||
{
|
||||
var backupBranches = _gitOps.GetBackupBranches(repoPath).ToList();
|
||||
var deletedCount = 0;
|
||||
|
||||
foreach (var branch in backupBranches)
|
||||
{
|
||||
if (_gitOps.DeleteBranch(repoPath, branch.Name))
|
||||
{
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<CommitAnalysis>> AnalyzeAllReposAsync(
|
||||
bool onlyNeedsImprovement = true,
|
||||
IProgress<(string Repo, int Processed)>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var allAnalyses = new List<CommitAnalysis>();
|
||||
|
||||
foreach (var repo in _state.Repos)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var analyses = AnalyzeRepo(repo);
|
||||
|
||||
if (onlyNeedsImprovement)
|
||||
{
|
||||
analyses = analyses.Where(a => a.Quality.NeedsImprovement);
|
||||
}
|
||||
|
||||
var list = analyses.ToList();
|
||||
allAnalyses.AddRange(list);
|
||||
|
||||
repo.LastScannedAt = DateTimeOffset.UtcNow;
|
||||
repo.LastAnalyzedAt = DateTimeOffset.UtcNow;
|
||||
repo.CommitsNeedingImprovement = list.Count(a => a.Quality.NeedsImprovement);
|
||||
|
||||
progress?.Report((repo.Name, list.Count));
|
||||
}
|
||||
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
return allAnalyses;
|
||||
}
|
||||
|
||||
public IEnumerable<CommitAnalysis> AnalyzeRepo(ManagedRepo repo)
|
||||
{
|
||||
return _gitOps.AnalyzeCommits(
|
||||
repo,
|
||||
_analyzer,
|
||||
_options.MaxCommitsPerRepo,
|
||||
_options.AnalyzeSince,
|
||||
_options.ExcludedAuthors);
|
||||
}
|
||||
|
||||
public CommitAnalysis AnalyzeCommit(string repoPath, string commitHash)
|
||||
{
|
||||
var repo = _state.Repos.FirstOrDefault(r =>
|
||||
r.Path.Equals(repoPath, StringComparison.OrdinalIgnoreCase))
|
||||
?? _gitOps.CreateManagedRepo(repoPath);
|
||||
|
||||
return _gitOps.AnalyzeCommits(repo, _analyzer, 1000)
|
||||
.First(c => c.CommitHash.StartsWith(commitHash, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task UpdateRepoAnalysisAsync(ManagedRepo repo, int totalCommits, int commitsNeedingImprovement, CancellationToken ct = default)
|
||||
{
|
||||
repo.LastAnalyzedAt = DateTimeOffset.UtcNow;
|
||||
repo.LastScannedAt = DateTimeOffset.UtcNow;
|
||||
repo.TotalCommits = totalCommits;
|
||||
repo.CommitsNeedingImprovement = commitsNeedingImprovement;
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
}
|
||||
|
||||
public async Task<BatchSuggestionResult> GenerateSuggestionsAsync(
|
||||
IEnumerable<CommitAnalysis> analyses,
|
||||
IProgress<int>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var results = await _rewriter.SuggestBatchAsync(analyses, progress, ct);
|
||||
var failures = new List<SuggestionFailure>();
|
||||
var successCount = 0;
|
||||
|
||||
foreach (var (analysis, result) in results)
|
||||
{
|
||||
analysis.Status = AnalysisStatus.Analyzed;
|
||||
|
||||
if (result.Success && !string.IsNullOrEmpty(result.Suggestion))
|
||||
{
|
||||
analysis.SuggestedMessage = result.Suggestion;
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't set SuggestedMessage for failures - leave it empty
|
||||
failures.Add(new SuggestionFailure
|
||||
{
|
||||
CommitHash = analysis.ShortHash,
|
||||
OriginalMessage = analysis.OriginalMessage,
|
||||
Reason = result.ErrorMessage ?? Str.Service_UnknownError,
|
||||
RawResponse = result.RawResponse
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new BatchSuggestionResult
|
||||
{
|
||||
Analyses = results.Select(r => r.Analysis).ToList(),
|
||||
SuccessCount = successCount,
|
||||
FailedCount = failures.Count,
|
||||
Failures = failures
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SuggestionResult> GenerateSuggestionAsync(CommitAnalysis analysis, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _rewriter.SuggestImprovedMessageAsync(analysis, ct);
|
||||
analysis.Status = AnalysisStatus.Analyzed;
|
||||
|
||||
if (result.Success && !string.IsNullOrEmpty(result.Suggestion))
|
||||
{
|
||||
analysis.SuggestedMessage = result.Suggestion;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IReadOnlyList<RewriteOperation> PreviewChanges(IEnumerable<CommitAnalysis> analyses)
|
||||
{
|
||||
return analyses
|
||||
.Where(a => !string.IsNullOrEmpty(a.SuggestedMessage))
|
||||
.Select(a => new RewriteOperation
|
||||
{
|
||||
RepoId = a.RepoId,
|
||||
RepoPath = a.RepoPath,
|
||||
CommitHash = a.CommitHash,
|
||||
OriginalMessage = a.OriginalMessage,
|
||||
NewMessage = a.SuggestedMessage!,
|
||||
IsLatestCommit = a.IsLatestCommit,
|
||||
Status = OperationStatus.Pending
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<BatchResult> ApplyChangesAsync(
|
||||
IEnumerable<RewriteOperation> operations,
|
||||
bool dryRun = true,
|
||||
IProgress<(int Processed, int Total)>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var opList = operations.ToList();
|
||||
var results = new List<RewriteOperation>();
|
||||
var processed = 0;
|
||||
var successful = 0;
|
||||
var failed = 0;
|
||||
var skipped = 0;
|
||||
|
||||
var byRepo = opList.GroupBy(o => o.RepoPath);
|
||||
|
||||
foreach (var repoGroup in byRepo)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var repo = _state.Repos.FirstOrDefault(r => r.Path == repoGroup.Key);
|
||||
if (repo == null)
|
||||
{
|
||||
foreach (var op in repoGroup)
|
||||
{
|
||||
op.Status = OperationStatus.Failed;
|
||||
op.ErrorMessage = Str.Service_RepoNotRegistered;
|
||||
results.Add(op);
|
||||
failed++;
|
||||
processed++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var orderedOps = repoGroup.OrderBy(o => o.IsLatestCommit ? 1 : 0).ToList();
|
||||
|
||||
foreach (var op in orderedOps)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
op.Status = OperationStatus.Pending;
|
||||
skipped++;
|
||||
results.Add(op);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dryRun)
|
||||
{
|
||||
op.Status = OperationStatus.Pending;
|
||||
results.Add(op);
|
||||
skipped++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = op.IsLatestCommit
|
||||
? _gitOps.AmendLatestCommit(repo, op.NewMessage)
|
||||
: _gitOps.RewordOlderCommit(repo, op.CommitHash, op.NewMessage);
|
||||
|
||||
op.NewCommitHash = result.NewCommitHash;
|
||||
op.Status = result.Status;
|
||||
op.ErrorMessage = result.ErrorMessage;
|
||||
op.AppliedAt = result.AppliedAt;
|
||||
|
||||
results.Add(op);
|
||||
_state.History.Add(op);
|
||||
|
||||
if (result.Status == OperationStatus.Applied)
|
||||
successful++;
|
||||
else
|
||||
failed++;
|
||||
}
|
||||
|
||||
processed++;
|
||||
progress?.Report((processed, opList.Count));
|
||||
}
|
||||
}
|
||||
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
|
||||
return new BatchResult
|
||||
{
|
||||
TotalProcessed = processed,
|
||||
Successful = successful,
|
||||
Failed = failed,
|
||||
Skipped = skipped,
|
||||
Operations = results
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<RewriteOperation> ApplyChangeAsync(CommitAnalysis analysis, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(analysis.SuggestedMessage))
|
||||
throw new InvalidOperationException(Str.Service_NoSuggestion);
|
||||
|
||||
var repo = _state.Repos.FirstOrDefault(r => r.Id == analysis.RepoId)
|
||||
?? throw new InvalidOperationException(Str.Service_RepoNotFound(analysis.RepoId));
|
||||
|
||||
var operation = analysis.IsLatestCommit
|
||||
? _gitOps.AmendLatestCommit(repo, analysis.SuggestedMessage)
|
||||
: _gitOps.RewordOlderCommit(repo, analysis.CommitHash, analysis.SuggestedMessage);
|
||||
|
||||
_state.History.Add(operation);
|
||||
analysis.Status = operation.Status == OperationStatus.Applied
|
||||
? AnalysisStatus.Applied
|
||||
: AnalysisStatus.Failed;
|
||||
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
return operation;
|
||||
}
|
||||
|
||||
public bool UndoCommitAmend(string repoPath, string originalCommitHash)
|
||||
{
|
||||
return _gitOps.UndoCommitAmend(repoPath, originalCommitHash);
|
||||
}
|
||||
|
||||
public bool IsCommitPushed(string repoPath, string commitHash)
|
||||
{
|
||||
return _gitOps.IsCommitPushed(repoPath, commitHash);
|
||||
}
|
||||
|
||||
public TrackingInfo GetTrackingInfo(string repoPath)
|
||||
{
|
||||
return _gitOps.GetTrackingInfo(repoPath);
|
||||
}
|
||||
|
||||
public GitPushResult ForcePush(string repoPath)
|
||||
{
|
||||
return _gitOps.ForcePush(repoPath);
|
||||
}
|
||||
|
||||
public GitPushResult Push(string repoPath)
|
||||
{
|
||||
return _gitOps.Push(repoPath);
|
||||
}
|
||||
|
||||
public string GenerateSummaryReport()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("═══════════════════════════════════════════════════════════════");
|
||||
sb.AppendLine("GIT MESSAGE IMPROVER - SUMMARY REPORT");
|
||||
sb.AppendLine($"Generated: {DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine("═══════════════════════════════════════════════════════════════");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine($"Total Repositories: {_state.Repos.Count}");
|
||||
sb.AppendLine($"Total Operations in History: {_state.History.Count}");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var repo in _state.Repos.OrderBy(r => r.Name))
|
||||
{
|
||||
sb.AppendLine($"┌─ {repo.Name}");
|
||||
sb.AppendLine($"│ Path: {repo.Path}");
|
||||
sb.AppendLine($"│ Branch: {repo.CurrentBranch}");
|
||||
sb.AppendLine($"│ Last Scanned: {repo.LastScannedAt?.ToString("yyyy-MM-dd HH:mm") ?? "Never"}");
|
||||
sb.AppendLine($"│ Commits Needing Improvement: {repo.CommitsNeedingImprovement}");
|
||||
sb.AppendLine($"└────────────────────────────────────────");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
var recentOps = _state.History
|
||||
.OrderByDescending(h => h.CreatedAt)
|
||||
.Take(10);
|
||||
|
||||
if (recentOps.Any())
|
||||
{
|
||||
sb.AppendLine("RECENT OPERATIONS:");
|
||||
sb.AppendLine("─────────────────────────────────────────");
|
||||
foreach (var op in recentOps)
|
||||
{
|
||||
var status = op.Status switch
|
||||
{
|
||||
OperationStatus.Applied => "✓",
|
||||
OperationStatus.Failed => "✗",
|
||||
_ => "○"
|
||||
};
|
||||
sb.AppendLine($"{status} [{op.CreatedAt:MM-dd HH:mm}] {op.CommitHash[..7]}: {op.Status}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// IHistoryHealthService implementation
|
||||
|
||||
public async Task<HistoryHealthReport> AnalyzeHistoryHealthAsync(
|
||||
string repoPath,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var analysis = await _healthAnalyzer.AnalyzeAsync(repoPath, options, progress, ct);
|
||||
return _reportGenerator.GenerateReport(analysis);
|
||||
}
|
||||
|
||||
public async Task<HistoryHealthReport> AnalyzeHistoryHealthAsync(
|
||||
ManagedRepo repo,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var analysis = await _healthAnalyzer.AnalyzeAsync(repo, options, progress, ct);
|
||||
return _reportGenerator.GenerateReport(analysis);
|
||||
}
|
||||
|
||||
public Task<string> ExportHealthReportAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return _reportGenerator.ExportReportAsync(report, format, ct);
|
||||
}
|
||||
|
||||
public Task ExportHealthReportToFileAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
string outputPath,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return _reportGenerator.ExportReportToFileAsync(report, format, outputPath, ct);
|
||||
}
|
||||
|
||||
public Task<CleanupExecutionResult> ExecuteCleanupAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupOperation operation,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return _cleanupExecutor.ExecuteAsync(repo, operation, options, progress, ct);
|
||||
}
|
||||
|
||||
public async Task<BatchCleanupResult> ExecuteAllCleanupsAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupSuggestions suggestions,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// Execute operations in order: automated first, then semi-automated
|
||||
// Skip manual operations as they require human intervention
|
||||
var operations = suggestions.AutomatedOperations
|
||||
.Concat(suggestions.SemiAutomatedOperations)
|
||||
.ToList();
|
||||
|
||||
return await _cleanupExecutor.ExecuteBatchAsync(repo, operations, options, progress, ct);
|
||||
}
|
||||
|
||||
public Task<string> CreateBackupBranchAsync(
|
||||
ManagedRepo repo,
|
||||
string? branchName = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return _cleanupExecutor.CreateBackupBranchAsync(repo, branchName, ct);
|
||||
}
|
||||
|
||||
public RewriteSafetyInfo GetRewriteSafetyInfo(string repoPath, IEnumerable<CommitAnalysis> commits)
|
||||
{
|
||||
var commitList = commits.ToList();
|
||||
if (!commitList.Any())
|
||||
{
|
||||
return new RewriteSafetyInfo
|
||||
{
|
||||
TotalCommitCount = 0,
|
||||
LocalOnlyCommitCount = 0,
|
||||
PushedCommitCount = 0
|
||||
};
|
||||
}
|
||||
|
||||
// Check for uncommitted changes
|
||||
bool hasUncommittedChanges;
|
||||
using (var repo = new LibGit2Sharp.Repository(repoPath))
|
||||
{
|
||||
var status = repo.RetrieveStatus(new LibGit2Sharp.StatusOptions());
|
||||
hasUncommittedChanges = status.IsDirty;
|
||||
}
|
||||
|
||||
// Get tracking info
|
||||
var trackingInfo = _gitOps.GetTrackingInfo(repoPath);
|
||||
|
||||
// Check which commits have been pushed
|
||||
var pushedCount = 0;
|
||||
var localCount = 0;
|
||||
|
||||
foreach (var commit in commitList)
|
||||
{
|
||||
if (_gitOps.IsCommitPushed(repoPath, commit.CommitHash))
|
||||
{
|
||||
pushedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
localCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return new RewriteSafetyInfo
|
||||
{
|
||||
HasUncommittedChanges = hasUncommittedChanges,
|
||||
HasPushedCommits = pushedCount > 0,
|
||||
PushedCommitCount = pushedCount,
|
||||
LocalOnlyCommitCount = localCount,
|
||||
TotalCommitCount = commitList.Count,
|
||||
HasRemoteTracking = trackingInfo.HasUpstream,
|
||||
RemoteTrackingBranch = trackingInfo.UpstreamBranch,
|
||||
AheadOfRemote = trackingInfo.AheadBy,
|
||||
BehindRemote = trackingInfo.BehindBy
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<BatchRewriteResult> ExecuteBatchRewriteAsync(
|
||||
string repoPath,
|
||||
IEnumerable<CommitAnalysis> commits,
|
||||
bool createBackup = true,
|
||||
IProgress<(int Current, int Total, string CommitHash)>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var commitList = commits
|
||||
.Where(c => !string.IsNullOrEmpty(c.SuggestedMessage))
|
||||
.ToList();
|
||||
|
||||
if (!commitList.Any())
|
||||
{
|
||||
return BatchRewriteResult.Failure("No commits with suggestions to apply.");
|
||||
}
|
||||
|
||||
// Get safety info
|
||||
var safetyInfo = GetRewriteSafetyInfo(repoPath, commitList);
|
||||
|
||||
// Block if uncommitted changes
|
||||
if (safetyInfo.HasUncommittedChanges)
|
||||
{
|
||||
return BatchRewriteResult.Failure(Str.Service_UncommittedChanges);
|
||||
}
|
||||
|
||||
// Get the managed repo
|
||||
var repo = _state.Repos.FirstOrDefault(r => r.Path.Equals(repoPath, StringComparison.OrdinalIgnoreCase));
|
||||
if (repo == null)
|
||||
{
|
||||
return BatchRewriteResult.Failure(Str.Service_RepoNotRegisteredPath(repoPath));
|
||||
}
|
||||
|
||||
string? backupBranch = null;
|
||||
|
||||
// Create backup branch if requested
|
||||
if (createBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
backupBranch = await CreateBackupBranchAsync(repo, null, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BatchRewriteResult.Failure($"Failed to create backup branch: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
var operations = new List<RewriteOperation>();
|
||||
var successCount = 0;
|
||||
var failedCount = 0;
|
||||
var total = commitList.Count;
|
||||
|
||||
// Process ALL commits (including HEAD) in a single batch operation
|
||||
// This ensures consistent rewriting without stale references
|
||||
System.Diagnostics.Debug.WriteLine($"[BatchRewrite] Processing {commitList.Count} commits in single batch");
|
||||
|
||||
var rewrites = commitList.ToDictionary(c => c.CommitHash, c => c.SuggestedMessage!);
|
||||
var batchOperations = _gitOps.RewordMultipleCommits(repo, rewrites);
|
||||
|
||||
foreach (var operation in batchOperations)
|
||||
{
|
||||
var commit = commitList.First(c => c.CommitHash == operation.CommitHash);
|
||||
progress?.Report((operations.Count + 1, total, commit.CommitHash[..7]));
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[BatchRewrite] Commit {commit.CommitHash[..7]}: Status={operation.Status}, NewHash={operation.NewCommitHash?[..7] ?? "null"}");
|
||||
|
||||
operations.Add(operation);
|
||||
_state.History.Add(operation);
|
||||
|
||||
if (operation.Status == OperationStatus.Applied)
|
||||
{
|
||||
successCount++;
|
||||
commit.Status = AnalysisStatus.Applied;
|
||||
}
|
||||
else
|
||||
{
|
||||
failedCount++;
|
||||
commit.Status = AnalysisStatus.Failed;
|
||||
System.Diagnostics.Debug.WriteLine($"[BatchRewrite] FAILED: {operation.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate the repository cache to ensure subsequent analysis sees the new commits
|
||||
_gitOps.InvalidateCache(repoPath);
|
||||
System.Diagnostics.Debug.WriteLine($"[BatchRewrite] Cache invalidated for {repoPath}");
|
||||
|
||||
await _stateRepo.SaveAsync(_state, ct);
|
||||
|
||||
return new BatchRewriteResult
|
||||
{
|
||||
Success = successCount > 0,
|
||||
SuccessCount = successCount,
|
||||
FailedCount = failedCount,
|
||||
SkippedCount = total - successCount - failedCount,
|
||||
RequiresForcePush = safetyInfo.HasPushedCommits && successCount > 0,
|
||||
BackupBranchName = backupBranch,
|
||||
Operations = operations
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_gitOps.Dispose();
|
||||
(_rewriter as IDisposable)?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
1000
Services/GitOperationsService.cs
Executable file
1000
Services/GitOperationsService.cs
Executable file
File diff suppressed because it is too large
Load Diff
761
Services/HealthReportGenerator.cs
Normal file
761
Services/HealthReportGenerator.cs
Normal file
@ -0,0 +1,761 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Generates health reports and calculates scores.
|
||||
/// </summary>
|
||||
public sealed class HealthReportGenerator : IHealthReportGenerator
|
||||
{
|
||||
private readonly HealthScoringWeights _weights;
|
||||
|
||||
public HealthReportGenerator(HealthScoringWeights? weights = null)
|
||||
{
|
||||
_weights = weights ?? HealthScoringWeights.Default;
|
||||
}
|
||||
|
||||
public HistoryHealthReport GenerateReport(HistoryHealthAnalysis analysis)
|
||||
{
|
||||
var score = CalculateScore(analysis);
|
||||
var issues = DetectIssues(analysis);
|
||||
var recommendations = GenerateRecommendations(issues, analysis);
|
||||
var cleanup = GenerateCleanupSuggestions(issues, analysis);
|
||||
|
||||
return new HistoryHealthReport
|
||||
{
|
||||
RepoId = analysis.RepoPath,
|
||||
RepoName = analysis.RepoName,
|
||||
RepoPath = analysis.RepoPath,
|
||||
CurrentBranch = analysis.CurrentBranch,
|
||||
CommitsAnalyzed = analysis.CommitsAnalyzed,
|
||||
Score = score,
|
||||
DuplicateMetrics = analysis.Duplicates,
|
||||
MergeMetrics = analysis.MergeMetrics,
|
||||
BranchMetrics = analysis.BranchMetrics,
|
||||
MessageDistribution = analysis.MessageDistribution,
|
||||
AuthorshipMetrics = analysis.AuthorshipMetrics,
|
||||
Issues = issues,
|
||||
Recommendations = recommendations,
|
||||
CleanupSuggestions = cleanup
|
||||
};
|
||||
}
|
||||
|
||||
private HealthScore CalculateScore(HistoryHealthAnalysis analysis)
|
||||
{
|
||||
var duplicateScore = CalculateDuplicateScore(analysis.Duplicates);
|
||||
var mergeScore = CalculateMergeScore(analysis.MergeMetrics);
|
||||
var branchScore = CalculateBranchScore(analysis.BranchMetrics);
|
||||
var messageScore = CalculateMessageScore(analysis.MessageDistribution);
|
||||
var authorshipScore = CalculateAuthorshipScore(analysis.AuthorshipMetrics);
|
||||
|
||||
var overallScore = (int)Math.Round(
|
||||
duplicateScore * _weights.DuplicateWeight +
|
||||
mergeScore * _weights.MergeWeight +
|
||||
branchScore * _weights.BranchWeight +
|
||||
messageScore * _weights.MessageWeight +
|
||||
authorshipScore * _weights.AuthorshipWeight
|
||||
);
|
||||
|
||||
overallScore = Math.Clamp(overallScore, 0, 100);
|
||||
|
||||
return new HealthScore
|
||||
{
|
||||
OverallScore = overallScore,
|
||||
Grade = HealthGradeExtensions.FromScore(overallScore),
|
||||
ComponentScores = new ComponentScores
|
||||
{
|
||||
DuplicateScore = duplicateScore,
|
||||
MergeScore = mergeScore,
|
||||
BranchScore = branchScore,
|
||||
MessageScore = messageScore,
|
||||
AuthorshipScore = authorshipScore
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private int CalculateDuplicateScore(DuplicateCommitMetrics metrics)
|
||||
{
|
||||
if (metrics.TotalDuplicateGroups == 0)
|
||||
return 100;
|
||||
|
||||
var duplicateRatio = metrics.DuplicateRatio;
|
||||
var score = 100 - (int)(duplicateRatio * 10);
|
||||
var exactPenalty = metrics.ExactDuplicates * 5;
|
||||
|
||||
return Math.Max(0, score - exactPenalty);
|
||||
}
|
||||
|
||||
private int CalculateMergeScore(MergeCommitMetrics metrics)
|
||||
{
|
||||
var mergeRatio = metrics.MergeRatio;
|
||||
var baseScore = mergeRatio switch
|
||||
{
|
||||
<= 10 => 100,
|
||||
<= 25 => 90,
|
||||
<= 35 => 75,
|
||||
<= 50 => 50,
|
||||
_ => 25
|
||||
};
|
||||
|
||||
// Additional penalty for merge fix commits
|
||||
var fixPenalty = Math.Min(20, metrics.MergeFixCommits * 3);
|
||||
|
||||
return Math.Max(0, baseScore - fixPenalty);
|
||||
}
|
||||
|
||||
private int CalculateBranchScore(BranchComplexityMetrics metrics)
|
||||
{
|
||||
var baseScore = metrics.Topology switch
|
||||
{
|
||||
BranchTopologyType.Linear => 100,
|
||||
BranchTopologyType.Balanced => 90,
|
||||
BranchTopologyType.GitFlow => 75,
|
||||
BranchTopologyType.Tangled => 50,
|
||||
BranchTopologyType.Spaghetti => 25,
|
||||
_ => 50
|
||||
};
|
||||
|
||||
var stalePenalty = Math.Min(20, metrics.StaleBranches * 2);
|
||||
var crossMergePenalty = Math.Min(25, metrics.CrossMerges * 2);
|
||||
|
||||
return Math.Max(0, baseScore - stalePenalty - crossMergePenalty);
|
||||
}
|
||||
|
||||
private int CalculateMessageScore(MessageQualityDistribution distribution)
|
||||
{
|
||||
if (distribution.TotalCommits == 0)
|
||||
return 100;
|
||||
|
||||
var avgScore = (int)distribution.AverageScore;
|
||||
var poorRatio = (double)distribution.Poor / distribution.TotalCommits;
|
||||
var poorPenalty = (int)(poorRatio * 30);
|
||||
|
||||
return Math.Max(0, avgScore - poorPenalty);
|
||||
}
|
||||
|
||||
private int CalculateAuthorshipScore(AuthorshipMetrics metrics)
|
||||
{
|
||||
if (metrics.TotalCommits == 0)
|
||||
return 100;
|
||||
|
||||
var missingEmailRatio = (double)metrics.MissingEmailCount / metrics.TotalCommits;
|
||||
var invalidEmailRatio = (double)metrics.InvalidEmailCount / metrics.TotalCommits;
|
||||
|
||||
var score = 100 - (int)((missingEmailRatio + invalidEmailRatio) * 100);
|
||||
return Math.Max(0, score);
|
||||
}
|
||||
|
||||
private List<HealthIssue> DetectIssues(HistoryHealthAnalysis analysis)
|
||||
{
|
||||
var issues = new List<HealthIssue>();
|
||||
|
||||
// Duplicate issues - separate ExactTree (true duplicates) from ExactMessage (just same message)
|
||||
var exactTreeGroups = analysis.Duplicates.DuplicateGroups
|
||||
.Where(g => g.Type == DuplicateType.ExactTree)
|
||||
.ToList();
|
||||
var exactMessageGroups = analysis.Duplicates.DuplicateGroups
|
||||
.Where(g => g.Type == DuplicateType.ExactMessage)
|
||||
.ToList();
|
||||
|
||||
// ExactTree duplicates - these are TRUE duplicates that can be safely squashed
|
||||
if (exactTreeGroups.Count > 0)
|
||||
{
|
||||
var exactTreeInstances = exactTreeGroups.Sum(g => g.InstanceCount - 1);
|
||||
var severity = exactTreeInstances > 5
|
||||
? HealthIssueSeverity.Error
|
||||
: exactTreeGroups.Count > 2
|
||||
? HealthIssueSeverity.Warning
|
||||
: HealthIssueSeverity.Info;
|
||||
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "DUPLICATE_COMMITS",
|
||||
Category = "Duplicates",
|
||||
Severity = severity,
|
||||
Title = Str.Report_DuplicateContent,
|
||||
Description = Str.Report_DuplicateContentDesc(exactTreeGroups.Count, exactTreeInstances),
|
||||
ImpactScore = exactTreeInstances * 5 + exactTreeGroups.Count * 2,
|
||||
AffectedCommits = exactTreeGroups
|
||||
.SelectMany(g => g.CommitHashes)
|
||||
.Take(20)
|
||||
.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
// ExactMessage duplicates - these have same message but DIFFERENT code, DO NOT squash
|
||||
if (exactMessageGroups.Count > 0)
|
||||
{
|
||||
var messageInstances = exactMessageGroups.Sum(g => g.InstanceCount);
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "SIMILAR_MESSAGES",
|
||||
Category = "Messages",
|
||||
Severity = HealthIssueSeverity.Info,
|
||||
Title = Str.Report_DuplicateMessages,
|
||||
Description = Str.Report_DuplicateMessagesDesc(exactMessageGroups.Count, messageInstances),
|
||||
ImpactScore = exactMessageGroups.Count, // Low impact - just informational
|
||||
AffectedCommits = exactMessageGroups
|
||||
.SelectMany(g => g.CommitHashes)
|
||||
.Take(20)
|
||||
.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
// Merge issues
|
||||
var mergeRatio = analysis.MergeMetrics.MergeRatio;
|
||||
if (mergeRatio > 35)
|
||||
{
|
||||
var severity = mergeRatio > 50
|
||||
? HealthIssueSeverity.Error
|
||||
: HealthIssueSeverity.Warning;
|
||||
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = mergeRatio > 50 ? "EXCESSIVE_MERGES" : "HIGH_MERGE_RATIO",
|
||||
Category = "Merges",
|
||||
Severity = severity,
|
||||
Title = mergeRatio > 50 ? Str.Report_ExcessiveMerges : Str.Report_HighMergeRatio,
|
||||
Description = Str.Report_MergeRatioDesc(mergeRatio, analysis.MergeMetrics.TotalMerges, analysis.MergeMetrics.TotalCommits),
|
||||
ImpactScore = (mergeRatio - 25) / 2
|
||||
});
|
||||
}
|
||||
|
||||
// Merge fix commits
|
||||
if (analysis.MergeMetrics.MergeFixCommits > 0)
|
||||
{
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "MERGE_FIX_COMMITS",
|
||||
Category = "Merges",
|
||||
Severity = HealthIssueSeverity.Warning,
|
||||
Title = Str.Report_MergeFixCommits,
|
||||
Description = Str.Report_MergeFixDesc(analysis.MergeMetrics.MergeFixCommits),
|
||||
ImpactScore = analysis.MergeMetrics.MergeFixCommits * 3,
|
||||
AffectedCommits = analysis.MergeMetrics.MergeFixCommitHashes.Take(10).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
// Branch complexity
|
||||
if (analysis.BranchMetrics.Topology >= BranchTopologyType.Tangled)
|
||||
{
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "TANGLED_BRANCHES",
|
||||
Category = "Branches",
|
||||
Severity = analysis.BranchMetrics.Topology == BranchTopologyType.Spaghetti
|
||||
? HealthIssueSeverity.Error
|
||||
: HealthIssueSeverity.Warning,
|
||||
Title = Str.Report_CrossMerges,
|
||||
Description = Str.Report_CrossMergesDesc(analysis.BranchMetrics.CrossMerges),
|
||||
ImpactScore = analysis.BranchMetrics.CrossMerges * 2
|
||||
});
|
||||
}
|
||||
|
||||
// Stale branches
|
||||
if (analysis.BranchMetrics.StaleBranches > 3)
|
||||
{
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "STALE_BRANCHES",
|
||||
Category = "Branches",
|
||||
Severity = HealthIssueSeverity.Info,
|
||||
Title = Str.Report_StaleBranches,
|
||||
Description = Str.Report_StaleBranchesDesc(analysis.BranchMetrics.StaleBranches),
|
||||
ImpactScore = analysis.BranchMetrics.StaleBranches
|
||||
});
|
||||
}
|
||||
|
||||
// Message quality
|
||||
if (analysis.MessageDistribution.AverageScore < 50)
|
||||
{
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "LOW_MESSAGE_QUALITY",
|
||||
Category = "Messages",
|
||||
Severity = HealthIssueSeverity.Error,
|
||||
Title = "Low average message quality",
|
||||
Description = $"Average commit message score is {analysis.MessageDistribution.AverageScore:F0}/100. " +
|
||||
$"{analysis.MessageDistribution.Poor} commits have poor quality messages.",
|
||||
ImpactScore = (int)(50 - analysis.MessageDistribution.AverageScore),
|
||||
AffectedCommits = analysis.MessageDistribution.PoorCommitHashes.Take(20).ToList()
|
||||
});
|
||||
}
|
||||
else if (analysis.MessageDistribution.Poor > analysis.MessageDistribution.TotalCommits * 0.3)
|
||||
{
|
||||
issues.Add(new HealthIssue
|
||||
{
|
||||
Code = "MANY_POOR_MESSAGES",
|
||||
Category = "Messages",
|
||||
Severity = HealthIssueSeverity.Warning,
|
||||
Title = "Many poor quality messages",
|
||||
Description = $"{analysis.MessageDistribution.Poor} commits ({analysis.MessageDistribution.Poor * 100 / Math.Max(1, analysis.MessageDistribution.TotalCommits)}%) " +
|
||||
"have poor quality messages (score < 50).",
|
||||
ImpactScore = analysis.MessageDistribution.Poor / 2,
|
||||
AffectedCommits = analysis.MessageDistribution.PoorCommitHashes.Take(20).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return issues.OrderByDescending(i => i.Severity).ThenByDescending(i => i.ImpactScore).ToList();
|
||||
}
|
||||
|
||||
private List<HealthRecommendation> GenerateRecommendations(
|
||||
List<HealthIssue> issues,
|
||||
HistoryHealthAnalysis analysis)
|
||||
{
|
||||
var recommendations = new List<HealthRecommendation>();
|
||||
|
||||
foreach (var issue in issues.Where(i => i.Severity >= HealthIssueSeverity.Warning))
|
||||
{
|
||||
// ExpectedScoreImprovement should match the issue's ImpactScore
|
||||
var rec = issue.Code switch
|
||||
{
|
||||
"DUPLICATE_COMMITS" => new HealthRecommendation
|
||||
{
|
||||
Category = "Duplicates",
|
||||
Title = "Squash duplicate commits",
|
||||
Description = "Remove duplicate commits to clean up history",
|
||||
Action = "Use interactive rebase to squash or drop duplicate commits",
|
||||
Rationale = "Duplicates make history harder to understand and can cause merge conflicts",
|
||||
PriorityScore = 80,
|
||||
Effort = EstimatedEffort.Medium,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
"EXCESSIVE_MERGES" or "HIGH_MERGE_RATIO" => new HealthRecommendation
|
||||
{
|
||||
Category = "Workflow",
|
||||
Title = "Switch to rebase workflow",
|
||||
Description = "Use rebase instead of merge for feature branches",
|
||||
Action = "Configure git to use rebase by default: git config pull.rebase true",
|
||||
Rationale = "Linear history is easier to understand and bisect",
|
||||
PriorityScore = 70,
|
||||
Effort = EstimatedEffort.Low,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
"MERGE_FIX_COMMITS" => new HealthRecommendation
|
||||
{
|
||||
Category = "Merges",
|
||||
Title = "Consolidate merge fix commits",
|
||||
Description = "Squash fix commits into their parent merge",
|
||||
Action = "Use interactive rebase to combine fix commits with merges",
|
||||
Rationale = "Fix commits indicate problematic merges that should be cleaned up",
|
||||
PriorityScore = 60,
|
||||
Effort = EstimatedEffort.Medium,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
"TANGLED_BRANCHES" => new HealthRecommendation
|
||||
{
|
||||
Category = "Branches",
|
||||
Title = "Linearize branch structure",
|
||||
Description = "Rebase feature branches onto main instead of cross-merging",
|
||||
Action = "For future work: always branch from and merge to main only",
|
||||
Rationale = "Cross-merges create complex dependencies and merge conflicts",
|
||||
PriorityScore = 65,
|
||||
Effort = EstimatedEffort.High,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
"STALE_BRANCHES" => new HealthRecommendation
|
||||
{
|
||||
Category = "Branches",
|
||||
Title = "Archive stale branches",
|
||||
Description = "Delete or tag old branches that are no longer needed",
|
||||
Action = "git branch -d <branch> for merged branches, or create archive tags",
|
||||
Rationale = "Stale branches clutter the repository and can cause confusion",
|
||||
PriorityScore = 30,
|
||||
Effort = EstimatedEffort.Minimal,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
"LOW_MESSAGE_QUALITY" or "MANY_POOR_MESSAGES" => new HealthRecommendation
|
||||
{
|
||||
Category = "Messages",
|
||||
Title = "Rewrite poor commit messages",
|
||||
Description = "Use AI to generate better commit messages",
|
||||
Action = "Use GitCleaner's AI suggestion feature to reword commits",
|
||||
Rationale = "Good commit messages are essential for maintainability",
|
||||
PriorityScore = 75,
|
||||
Effort = EstimatedEffort.Low,
|
||||
ExpectedScoreImprovement = issue.ImpactScore
|
||||
},
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (rec != null)
|
||||
recommendations.Add(rec);
|
||||
}
|
||||
|
||||
return recommendations.OrderByDescending(r => r.PriorityScore).ToList();
|
||||
}
|
||||
|
||||
private CleanupSuggestions GenerateCleanupSuggestions(
|
||||
List<HealthIssue> issues,
|
||||
HistoryHealthAnalysis analysis)
|
||||
{
|
||||
var automated = new List<CleanupOperation>();
|
||||
var semiAutomated = new List<CleanupOperation>();
|
||||
var manual = new List<CleanupOperation>();
|
||||
|
||||
// Message rewriting - fully automated with existing feature
|
||||
if (analysis.MessageDistribution.Poor > 0)
|
||||
{
|
||||
// ImpactScore for MANY_POOR_MESSAGES = Poor / 2
|
||||
// ImpactScore for LOW_MESSAGE_QUALITY = (50 - AverageScore)
|
||||
var messageImpact = Math.Max(
|
||||
analysis.MessageDistribution.Poor / 2,
|
||||
(int)Math.Max(0, 50 - analysis.MessageDistribution.AverageScore));
|
||||
|
||||
automated.Add(new CleanupOperation
|
||||
{
|
||||
Id = "rewrite-messages",
|
||||
Title = "Rewrite poor commit messages",
|
||||
Description = $"Use AI to improve {analysis.MessageDistribution.Poor} commit messages with score < 50",
|
||||
Type = CleanupType.RewordMessages,
|
||||
AutomationLevel = CleanupAutomationLevel.FullyAutomated,
|
||||
Effort = EstimatedEffort.Low,
|
||||
Risk = RiskLevel.Low,
|
||||
ExpectedScoreImprovement = messageImpact,
|
||||
AffectedCommits = analysis.MessageDistribution.PoorCommitHashes.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
// Duplicate squashing - semi-automated
|
||||
// IMPORTANT: Only squash ExactTree duplicates (identical file content)
|
||||
// ExactMessage duplicates have the same message but DIFFERENT code changes - squashing would lose code!
|
||||
var exactTreeGroups = analysis.Duplicates.DuplicateGroups
|
||||
.Where(g => g.Type == DuplicateType.ExactTree)
|
||||
.ToList();
|
||||
|
||||
if (exactTreeGroups.Count > 0)
|
||||
{
|
||||
var exactTreeInstances = exactTreeGroups.Sum(g => g.InstanceCount - 1);
|
||||
// Impact score based only on exact tree duplicates (safe to squash)
|
||||
var duplicateImpact = exactTreeInstances * 5 + exactTreeGroups.Count * 2;
|
||||
|
||||
semiAutomated.Add(new CleanupOperation
|
||||
{
|
||||
Id = "squash-duplicates",
|
||||
Title = "Squash duplicate commits",
|
||||
Description = $"Consolidate {exactTreeGroups.Count} duplicate commit groups " +
|
||||
$"with identical content ({exactTreeInstances} redundant commits)",
|
||||
Type = CleanupType.SquashDuplicates,
|
||||
AutomationLevel = CleanupAutomationLevel.SemiAutomated,
|
||||
Effort = EstimatedEffort.Medium,
|
||||
Risk = RiskLevel.Medium,
|
||||
ExpectedScoreImprovement = duplicateImpact,
|
||||
AffectedCommits = exactTreeGroups
|
||||
.SelectMany(g => g.CommitHashes)
|
||||
.ToList(),
|
||||
GitCommand = "git rebase -i HEAD~N # Mark duplicates as 'drop' or 'fixup'"
|
||||
});
|
||||
}
|
||||
|
||||
// Stale branch cleanup - semi-automated
|
||||
if (analysis.BranchMetrics.StaleBranches > 0)
|
||||
{
|
||||
// ImpactScore for STALE_BRANCHES = StaleBranches
|
||||
semiAutomated.Add(new CleanupOperation
|
||||
{
|
||||
Id = "archive-stale-branches",
|
||||
Title = "Archive stale branches",
|
||||
Description = $"Delete or tag {analysis.BranchMetrics.StaleBranches} stale branches",
|
||||
Type = CleanupType.ArchiveBranches,
|
||||
AutomationLevel = CleanupAutomationLevel.SemiAutomated,
|
||||
Effort = EstimatedEffort.Minimal,
|
||||
Risk = RiskLevel.None,
|
||||
ExpectedScoreImprovement = analysis.BranchMetrics.StaleBranches,
|
||||
GitCommand = "git branch -d <branch> # For merged branches\n" +
|
||||
"git tag archive/<branch> <branch> && git branch -D <branch> # For archiving"
|
||||
});
|
||||
}
|
||||
|
||||
// Merge consolidation - semi-automated (we can execute this)
|
||||
if (analysis.MergeMetrics.MergeFixCommits > 0)
|
||||
{
|
||||
// ImpactScore for MERGE_FIX_COMMITS = MergeFixCommits * 3
|
||||
semiAutomated.Add(new CleanupOperation
|
||||
{
|
||||
Id = "consolidate-merges",
|
||||
Title = "Consolidate merge fix commits",
|
||||
Description = $"Squash {analysis.MergeMetrics.MergeFixCommits} merge fix commits into their parent merges",
|
||||
Type = CleanupType.ConsolidateMerges,
|
||||
AutomationLevel = CleanupAutomationLevel.SemiAutomated,
|
||||
Effort = EstimatedEffort.Medium,
|
||||
Risk = RiskLevel.High,
|
||||
ExpectedScoreImprovement = analysis.MergeMetrics.MergeFixCommits * 3,
|
||||
AffectedCommits = analysis.MergeMetrics.MergeFixCommitHashes.ToList(),
|
||||
GitCommand = "git rebase -i <merge-commit>^ # Squash fix commits into merge"
|
||||
});
|
||||
}
|
||||
|
||||
// History linearization - semi-automated (we can execute this)
|
||||
if (analysis.BranchMetrics.Topology >= BranchTopologyType.Tangled)
|
||||
{
|
||||
semiAutomated.Add(new CleanupOperation
|
||||
{
|
||||
Id = "linearize-history",
|
||||
Title = "Linearize branch structure",
|
||||
Description = "Remove merge commits and create a cleaner, linear history",
|
||||
Type = CleanupType.RebaseLinearize,
|
||||
AutomationLevel = CleanupAutomationLevel.SemiAutomated,
|
||||
Effort = EstimatedEffort.Medium,
|
||||
Risk = RiskLevel.High,
|
||||
ExpectedScoreImprovement = 15,
|
||||
GitCommand = "git rebase main # Alternative manual approach"
|
||||
});
|
||||
}
|
||||
|
||||
return new CleanupSuggestions
|
||||
{
|
||||
AutomatedOperations = automated,
|
||||
SemiAutomatedOperations = semiAutomated,
|
||||
ManualOperations = manual
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> ExportReportAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ReportFormat.Json => ExportToJson(report),
|
||||
ReportFormat.Markdown => ExportToMarkdown(report),
|
||||
ReportFormat.Html => ExportToHtml(report),
|
||||
ReportFormat.Console => ExportToConsole(report),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ExportReportToFileAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
string outputPath,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var content = await ExportReportAsync(report, format, ct);
|
||||
await File.WriteAllTextAsync(outputPath, content, ct);
|
||||
}
|
||||
|
||||
private string ExportToJson(HistoryHealthReport report)
|
||||
{
|
||||
return JsonSerializer.Serialize(report, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
}
|
||||
|
||||
private string ExportToMarkdown(HistoryHealthReport report)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("# Git History Health Report");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Repository:** {report.RepoName}");
|
||||
sb.AppendLine($"**Branch:** {report.CurrentBranch}");
|
||||
sb.AppendLine($"**Generated:** {report.GeneratedAt:yyyy-MM-dd HH:mm:ss} UTC");
|
||||
sb.AppendLine($"**Commits Analyzed:** {report.CommitsAnalyzed}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("---");
|
||||
sb.AppendLine();
|
||||
|
||||
// Overall score
|
||||
var gradeIcon = report.Score.Grade.GetIcon();
|
||||
sb.AppendLine($"## Overall Health Score: {report.Score.OverallScore}/100 ({report.Score.Grade}) {gradeIcon}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(report.Score.Grade.GetDescription());
|
||||
sb.AppendLine();
|
||||
|
||||
// Component scores
|
||||
sb.AppendLine("### Component Scores");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"| Component | Score | Status |");
|
||||
sb.AppendLine($"|-----------|-------|--------|");
|
||||
sb.AppendLine($"| Messages | {report.Score.ComponentScores.MessageScore}/100 | {GetStatusIcon(report.Score.ComponentScores.MessageScore)} |");
|
||||
sb.AppendLine($"| Merges | {report.Score.ComponentScores.MergeScore}/100 | {GetStatusIcon(report.Score.ComponentScores.MergeScore)} |");
|
||||
sb.AppendLine($"| Duplicates | {report.Score.ComponentScores.DuplicateScore}/100 | {GetStatusIcon(report.Score.ComponentScores.DuplicateScore)} |");
|
||||
sb.AppendLine($"| Branches | {report.Score.ComponentScores.BranchScore}/100 | {GetStatusIcon(report.Score.ComponentScores.BranchScore)} |");
|
||||
sb.AppendLine($"| Authorship | {report.Score.ComponentScores.AuthorshipScore}/100 | {GetStatusIcon(report.Score.ComponentScores.AuthorshipScore)} |");
|
||||
sb.AppendLine();
|
||||
|
||||
// Issues
|
||||
if (report.Issues.Count > 0)
|
||||
{
|
||||
sb.AppendLine("---");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"## Issues Found ({report.Issues.Count})");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var issue in report.Issues)
|
||||
{
|
||||
var severityIcon = issue.Severity switch
|
||||
{
|
||||
HealthIssueSeverity.Critical => "🚨",
|
||||
HealthIssueSeverity.Error => "❌",
|
||||
HealthIssueSeverity.Warning => "⚠️",
|
||||
_ => "ℹ️"
|
||||
};
|
||||
|
||||
sb.AppendLine($"### {severityIcon} {issue.Title}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Code:** `{issue.Code}` | **Category:** {issue.Category} | **Impact:** -{issue.ImpactScore} points");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(issue.Description);
|
||||
sb.AppendLine();
|
||||
|
||||
if (issue.AffectedCommits.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"**Affected commits:** `{string.Join("`, `", issue.AffectedCommits.Take(5))}`" +
|
||||
(issue.AffectedCommits.Count > 5 ? $" and {issue.AffectedCommits.Count - 5} more..." : ""));
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
if (report.Recommendations.Count > 0)
|
||||
{
|
||||
sb.AppendLine("---");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Recommendations");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var rec in report.Recommendations)
|
||||
{
|
||||
sb.AppendLine($"### {rec.Title}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Priority:** {rec.PriorityScore}/100 | **Effort:** {rec.Effort} | **Expected Improvement:** +{rec.ExpectedScoreImprovement} points");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(rec.Description);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Action:** {rec.Action}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup suggestions
|
||||
if (report.CleanupSuggestions != null && report.CleanupSuggestions.TotalOperations > 0)
|
||||
{
|
||||
sb.AppendLine("---");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Cleanup Operations");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Total expected improvement:** +{report.CleanupSuggestions.TotalExpectedImprovement} points");
|
||||
sb.AppendLine();
|
||||
|
||||
if (report.CleanupSuggestions.AutomatedOperations.Count > 0)
|
||||
{
|
||||
sb.AppendLine("### Automated (Safe)");
|
||||
foreach (var op in report.CleanupSuggestions.AutomatedOperations)
|
||||
{
|
||||
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (report.CleanupSuggestions.SemiAutomatedOperations.Count > 0)
|
||||
{
|
||||
sb.AppendLine("### Semi-Automated (Review Required)");
|
||||
foreach (var op in report.CleanupSuggestions.SemiAutomatedOperations)
|
||||
{
|
||||
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
|
||||
if (!string.IsNullOrEmpty(op.GitCommand))
|
||||
{
|
||||
sb.AppendLine($" ```bash");
|
||||
sb.AppendLine($" {op.GitCommand}");
|
||||
sb.AppendLine($" ```");
|
||||
}
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (report.CleanupSuggestions.ManualOperations.Count > 0)
|
||||
{
|
||||
sb.AppendLine("### Manual (High Risk)");
|
||||
foreach (var op in report.CleanupSuggestions.ManualOperations)
|
||||
{
|
||||
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
|
||||
if (!string.IsNullOrEmpty(op.GitCommand))
|
||||
{
|
||||
sb.AppendLine($" ```bash");
|
||||
sb.AppendLine($" {op.GitCommand}");
|
||||
sb.AppendLine($" ```");
|
||||
}
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string ExportToHtml(HistoryHealthReport report)
|
||||
{
|
||||
// Simple HTML wrapper around markdown content
|
||||
var markdown = ExportToMarkdown(report);
|
||||
return $@"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Git Health Report - {report.RepoName}</title>
|
||||
<style>
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; }}
|
||||
h1 {{ color: #333; }}
|
||||
h2 {{ color: #444; border-bottom: 1px solid #ddd; padding-bottom: 8px; }}
|
||||
h3 {{ color: #555; }}
|
||||
table {{ border-collapse: collapse; width: 100%; }}
|
||||
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
||||
th {{ background-color: #f5f5f5; }}
|
||||
code {{ background-color: #f5f5f5; padding: 2px 6px; border-radius: 3px; }}
|
||||
pre {{ background-color: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre>{System.Web.HttpUtility.HtmlEncode(markdown)}</pre>
|
||||
</body>
|
||||
</html>";
|
||||
}
|
||||
|
||||
private string ExportToConsole(HistoryHealthReport report)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($"╔══════════════════════════════════════════════════════════════╗");
|
||||
sb.AppendLine($"║ GIT HISTORY HEALTH REPORT ║");
|
||||
sb.AppendLine($"╠══════════════════════════════════════════════════════════════╣");
|
||||
sb.AppendLine($"║ Repository: {report.RepoName,-48} ║");
|
||||
sb.AppendLine($"║ Branch: {report.CurrentBranch,-52} ║");
|
||||
sb.AppendLine($"║ Commits: {report.CommitsAnalyzed,-51} ║");
|
||||
sb.AppendLine($"╠══════════════════════════════════════════════════════════════╣");
|
||||
sb.AppendLine($"║ OVERALL SCORE: {report.Score.OverallScore,3}/100 Grade: {report.Score.Grade,-22} ║");
|
||||
sb.AppendLine($"╠══════════════════════════════════════════════════════════════╣");
|
||||
sb.AppendLine($"║ Components: ║");
|
||||
sb.AppendLine($"║ Messages: {report.Score.ComponentScores.MessageScore,3}/100 {GetBar(report.Score.ComponentScores.MessageScore),-30} ║");
|
||||
sb.AppendLine($"║ Merges: {report.Score.ComponentScores.MergeScore,3}/100 {GetBar(report.Score.ComponentScores.MergeScore),-30} ║");
|
||||
sb.AppendLine($"║ Duplicates: {report.Score.ComponentScores.DuplicateScore,3}/100 {GetBar(report.Score.ComponentScores.DuplicateScore),-30} ║");
|
||||
sb.AppendLine($"║ Branches: {report.Score.ComponentScores.BranchScore,3}/100 {GetBar(report.Score.ComponentScores.BranchScore),-30} ║");
|
||||
sb.AppendLine($"║ Authorship: {report.Score.ComponentScores.AuthorshipScore,3}/100 {GetBar(report.Score.ComponentScores.AuthorshipScore),-30} ║");
|
||||
sb.AppendLine($"╠══════════════════════════════════════════════════════════════╣");
|
||||
sb.AppendLine($"║ Issues: {report.CriticalIssueCount} critical, {report.ErrorCount} errors, {report.WarningCount} warnings ║");
|
||||
sb.AppendLine($"╚══════════════════════════════════════════════════════════════╝");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetStatusIcon(int score) => score switch
|
||||
{
|
||||
>= 90 => "✅ Excellent",
|
||||
>= 70 => "👍 Good",
|
||||
>= 50 => "⚠️ Fair",
|
||||
>= 30 => "❌ Poor",
|
||||
_ => "🚨 Critical"
|
||||
};
|
||||
|
||||
private static string GetBar(int score)
|
||||
{
|
||||
var filled = score / 5;
|
||||
var empty = 20 - filled;
|
||||
return $"[{new string('█', filled)}{new string('░', empty)}]";
|
||||
}
|
||||
}
|
||||
665
Services/HistoryHealthAnalyzer.cs
Normal file
665
Services/HistoryHealthAnalyzer.cs
Normal file
@ -0,0 +1,665 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
using MarketAlly.GitCommitEditor.Resources;
|
||||
using MarketAlly.LibGit2Sharp;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes git repository history health.
|
||||
/// </summary>
|
||||
public sealed class HistoryHealthAnalyzer : IHistoryHealthAnalyzer
|
||||
{
|
||||
private readonly ICommitAnalyzer _commitAnalyzer;
|
||||
private readonly HealthScoringWeights _weights;
|
||||
|
||||
// Patterns that indicate merge fix commits
|
||||
private static readonly string[] MergeFixPatterns =
|
||||
[
|
||||
"fix merge",
|
||||
"merge fix",
|
||||
"resolve conflict",
|
||||
"fix conflict",
|
||||
"meerge", // Common typo
|
||||
"megre", // Common typo
|
||||
"fixed merge"
|
||||
];
|
||||
|
||||
public HistoryHealthAnalyzer(ICommitAnalyzer commitAnalyzer, HealthScoringWeights? weights = null)
|
||||
{
|
||||
_commitAnalyzer = commitAnalyzer;
|
||||
_weights = weights ?? HealthScoringWeights.Default;
|
||||
}
|
||||
|
||||
public Task<HistoryHealthAnalysis> AnalyzeAsync(
|
||||
string repoPath,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var repo = new ManagedRepo
|
||||
{
|
||||
Path = repoPath,
|
||||
Name = Path.GetFileName(repoPath)
|
||||
};
|
||||
return AnalyzeAsync(repo, options, progress, ct);
|
||||
}
|
||||
|
||||
public async Task<HistoryHealthAnalysis> AnalyzeAsync(
|
||||
ManagedRepo managedRepo,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
options ??= new HistoryAnalysisOptions();
|
||||
|
||||
using var repo = new Repository(managedRepo.Path);
|
||||
|
||||
// Get commits to analyze
|
||||
progress?.Report(new AnalysisProgress { CurrentStage = Str.Health_LoadingCommits, PercentComplete = 0 });
|
||||
|
||||
var commits = GetCommitsToAnalyze(repo, options).ToList();
|
||||
var totalCommits = commits.Count;
|
||||
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_LoadingCommits,
|
||||
PercentComplete = 10,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
|
||||
// Analyze duplicates
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_DetectingDuplicates,
|
||||
PercentComplete = 20,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
var duplicateMetrics = options.IncludeDuplicateDetection
|
||||
? await Task.Run(() => AnalyzeDuplicates(commits), ct)
|
||||
: CreateEmptyDuplicateMetrics(totalCommits);
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Analyze merges
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_AnalyzingMerges,
|
||||
PercentComplete = 40,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
var mergeMetrics = await Task.Run(() => AnalyzeMerges(commits), ct);
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Analyze branches
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_AnalyzingBranches,
|
||||
PercentComplete = 50,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
var branchMetrics = options.IncludeBranchAnalysis
|
||||
? await Task.Run(() => AnalyzeBranches(repo, commits), ct)
|
||||
: CreateEmptyBranchMetrics();
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Analyze message quality
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_AnalyzingMessages,
|
||||
PercentComplete = 60,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
var messageDistribution = options.IncludeMessageDistribution
|
||||
? await Task.Run(() => AnalyzeMessageQuality(commits, progress, totalCommits), ct)
|
||||
: CreateEmptyMessageDistribution(totalCommits);
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Analyze authorship
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_AnalyzingAuthorship,
|
||||
PercentComplete = 90,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
var authorshipMetrics = await Task.Run(() => AnalyzeAuthorship(commits), ct);
|
||||
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_Complete,
|
||||
PercentComplete = 100,
|
||||
TotalCommits = totalCommits,
|
||||
CommitsProcessed = totalCommits
|
||||
});
|
||||
|
||||
return new HistoryHealthAnalysis
|
||||
{
|
||||
RepoPath = managedRepo.Path,
|
||||
RepoName = managedRepo.Name,
|
||||
CurrentBranch = repo.Head.FriendlyName,
|
||||
CommitsAnalyzed = totalCommits,
|
||||
OldestCommitDate = commits.LastOrDefault()?.Author.When,
|
||||
NewestCommitDate = commits.FirstOrDefault()?.Author.When,
|
||||
Duplicates = duplicateMetrics,
|
||||
MergeMetrics = mergeMetrics,
|
||||
BranchMetrics = branchMetrics,
|
||||
MessageDistribution = messageDistribution,
|
||||
AuthorshipMetrics = authorshipMetrics
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<Commit> GetCommitsToAnalyze(Repository repo, HistoryAnalysisOptions options)
|
||||
{
|
||||
var filter = new CommitFilter
|
||||
{
|
||||
SortBy = CommitSortStrategies.Topological | CommitSortStrategies.Time
|
||||
};
|
||||
|
||||
IEnumerable<Commit> commits = repo.Commits.QueryBy(filter);
|
||||
|
||||
if (options.AnalyzeSince.HasValue)
|
||||
{
|
||||
commits = commits.Where(c => c.Author.When >= options.AnalyzeSince.Value);
|
||||
}
|
||||
|
||||
return commits.Take(options.EffectiveMaxCommits);
|
||||
}
|
||||
|
||||
private DuplicateCommitMetrics AnalyzeDuplicates(List<Commit> commits)
|
||||
{
|
||||
var duplicateGroups = new List<DuplicateCommitGroup>();
|
||||
var messageGroups = new Dictionary<string, List<Commit>>();
|
||||
var treeGroups = new Dictionary<string, List<Commit>>();
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
// Skip merge commits for duplicate detection
|
||||
if (commit.Parents.Count() > 1) continue;
|
||||
|
||||
var message = NormalizeMessage(commit.MessageShort);
|
||||
var treeSha = commit.Tree.Sha;
|
||||
|
||||
// Group by normalized message
|
||||
if (!messageGroups.ContainsKey(message))
|
||||
messageGroups[message] = [];
|
||||
messageGroups[message].Add(commit);
|
||||
|
||||
// Group by tree SHA
|
||||
if (!treeGroups.ContainsKey(treeSha))
|
||||
treeGroups[treeSha] = [];
|
||||
treeGroups[treeSha].Add(commit);
|
||||
}
|
||||
|
||||
// Find exact tree duplicates
|
||||
foreach (var group in treeGroups.Where(g => g.Value.Count > 1))
|
||||
{
|
||||
duplicateGroups.Add(new DuplicateCommitGroup
|
||||
{
|
||||
CanonicalMessage = group.Value.First().MessageShort,
|
||||
CommitHashes = group.Value.Select(c => c.Sha).ToList(),
|
||||
Type = DuplicateType.ExactTree
|
||||
});
|
||||
}
|
||||
|
||||
// Find exact message duplicates (not already in tree duplicates)
|
||||
var treeDuplicateHashes = new HashSet<string>(
|
||||
duplicateGroups.SelectMany(g => g.CommitHashes));
|
||||
|
||||
foreach (var group in messageGroups.Where(g => g.Value.Count > 1))
|
||||
{
|
||||
var nonTreeDuplicates = group.Value
|
||||
.Where(c => !treeDuplicateHashes.Contains(c.Sha))
|
||||
.ToList();
|
||||
|
||||
if (nonTreeDuplicates.Count > 1)
|
||||
{
|
||||
duplicateGroups.Add(new DuplicateCommitGroup
|
||||
{
|
||||
CanonicalMessage = group.Key,
|
||||
CommitHashes = nonTreeDuplicates.Select(c => c.Sha).ToList(),
|
||||
Type = DuplicateType.ExactMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var exactDuplicates = duplicateGroups
|
||||
.Where(g => g.Type == DuplicateType.ExactTree)
|
||||
.Sum(g => g.InstanceCount - 1);
|
||||
|
||||
return new DuplicateCommitMetrics
|
||||
{
|
||||
TotalCommitsAnalyzed = commits.Count,
|
||||
TotalDuplicateGroups = duplicateGroups.Count,
|
||||
TotalDuplicateInstances = duplicateGroups.Sum(g => g.InstanceCount - 1),
|
||||
ExactDuplicates = exactDuplicates,
|
||||
CherryPicks = 0, // Would need patch-id comparison
|
||||
FuzzyMatches = duplicateGroups.Count(g => g.Type == DuplicateType.FuzzyMessage),
|
||||
DuplicateGroups = duplicateGroups
|
||||
};
|
||||
}
|
||||
|
||||
private MergeCommitMetrics AnalyzeMerges(List<Commit> commits)
|
||||
{
|
||||
var mergeCommits = commits.Where(c => c.Parents.Count() > 1).ToList();
|
||||
var nonMergeCommits = commits.Where(c => c.Parents.Count() <= 1).ToList();
|
||||
|
||||
var mergeFixCommits = new List<string>();
|
||||
var messyPatterns = new List<string>();
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
var msgLower = commit.MessageShort.ToLowerInvariant();
|
||||
|
||||
foreach (var pattern in MergeFixPatterns)
|
||||
{
|
||||
if (msgLower.Contains(pattern))
|
||||
{
|
||||
if (commit.Parents.Count() <= 1)
|
||||
{
|
||||
mergeFixCommits.Add(commit.Sha);
|
||||
}
|
||||
if (!messyPatterns.Contains(pattern))
|
||||
{
|
||||
messyPatterns.Add(pattern);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count null merges (empty merge commits)
|
||||
var nullMerges = mergeCommits.Count(m =>
|
||||
{
|
||||
var parents = m.Parents.ToList();
|
||||
return parents.Count == 2 && m.Tree.Sha == parents[0].Tree.Sha;
|
||||
});
|
||||
|
||||
return new MergeCommitMetrics
|
||||
{
|
||||
TotalCommits = commits.Count,
|
||||
TotalMerges = mergeCommits.Count,
|
||||
TrivialMerges = mergeCommits.Count - nullMerges,
|
||||
ConflictMerges = 0, // Hard to detect reliably
|
||||
MergeFixCommits = mergeFixCommits.Count,
|
||||
NullMerges = nullMerges,
|
||||
AverageMergeComplexity = 0, // Would need tree diff
|
||||
MessyMergePatterns = messyPatterns,
|
||||
MergeFixCommitHashes = mergeFixCommits
|
||||
};
|
||||
}
|
||||
|
||||
private BranchComplexityMetrics AnalyzeBranches(Repository repo, List<Commit> commits)
|
||||
{
|
||||
var branches = repo.Branches.Where(b => !b.IsRemote).ToList();
|
||||
var remoteBranches = repo.Branches.Where(b => b.IsRemote).ToList();
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var staleDays = 30;
|
||||
|
||||
var staleBranches = new List<string>();
|
||||
var activeBranches = 0;
|
||||
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
if (branch.Tip == null) continue;
|
||||
|
||||
var age = now - branch.Tip.Author.When;
|
||||
if (age.TotalDays > staleDays)
|
||||
{
|
||||
staleBranches.Add(branch.FriendlyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
activeBranches++;
|
||||
}
|
||||
}
|
||||
|
||||
// Count merge commits to determine cross-merges
|
||||
var mergeCommits = commits.Where(c => c.Parents.Count() > 1).ToList();
|
||||
var mainBranchNames = new[] { "main", "master", "develop", "dev" };
|
||||
|
||||
// Simplified cross-merge detection
|
||||
var crossMerges = 0;
|
||||
foreach (var merge in mergeCommits)
|
||||
{
|
||||
var msg = merge.MessageShort.ToLowerInvariant();
|
||||
// If merge message doesn't mention main branches, it's likely a cross-merge
|
||||
if (!mainBranchNames.Any(b => msg.Contains(b)))
|
||||
{
|
||||
crossMerges++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine topology
|
||||
var mergeRatio = commits.Count > 0 ? (double)mergeCommits.Count / commits.Count : 0;
|
||||
var crossMergeRatio = mergeCommits.Count > 0 ? (double)crossMerges / mergeCommits.Count : 0;
|
||||
|
||||
var topology = DetermineTopology(mergeRatio, crossMergeRatio, staleBranches.Count);
|
||||
|
||||
return new BranchComplexityMetrics
|
||||
{
|
||||
TotalBranches = branches.Count, // Only count local branches
|
||||
ActiveBranches = activeBranches,
|
||||
StaleBranches = staleBranches.Count,
|
||||
CrossMerges = crossMerges,
|
||||
AverageBranchAge = 0, // Would need more calculation
|
||||
AverageBranchLength = 0,
|
||||
LongLivedBranches = 0,
|
||||
Topology = topology,
|
||||
StaleBranchNames = staleBranches
|
||||
};
|
||||
}
|
||||
|
||||
private BranchTopologyType DetermineTopology(double mergeRatio, double crossMergeRatio, int staleBranches)
|
||||
{
|
||||
if (mergeRatio < 0.1)
|
||||
return BranchTopologyType.Linear;
|
||||
|
||||
if (crossMergeRatio > 0.5 || (mergeRatio > 0.5 && staleBranches > 5))
|
||||
return BranchTopologyType.Spaghetti;
|
||||
|
||||
if (crossMergeRatio > 0.2 || mergeRatio > 0.4)
|
||||
return BranchTopologyType.Tangled;
|
||||
|
||||
if (mergeRatio > 0.2)
|
||||
return BranchTopologyType.GitFlow;
|
||||
|
||||
return BranchTopologyType.Balanced;
|
||||
}
|
||||
|
||||
private MessageQualityDistribution AnalyzeMessageQuality(
|
||||
List<Commit> commits,
|
||||
IProgress<AnalysisProgress>? progress,
|
||||
int totalCommits)
|
||||
{
|
||||
var scores = new List<(string Hash, int Score, DateTimeOffset Date)>();
|
||||
var poorCommits = new List<string>();
|
||||
var processed = 0;
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
// Skip merge commits for message quality
|
||||
if (commit.Parents.Count() > 1) continue;
|
||||
|
||||
var analysis = _commitAnalyzer.Analyze(commit.Message);
|
||||
scores.Add((commit.Sha, analysis.OverallScore, commit.Author.When));
|
||||
|
||||
if (analysis.OverallScore < 50)
|
||||
{
|
||||
poorCommits.Add(commit.Sha);
|
||||
}
|
||||
|
||||
processed++;
|
||||
if (processed % 100 == 0)
|
||||
{
|
||||
progress?.Report(new AnalysisProgress
|
||||
{
|
||||
CurrentStage = Str.Health_AnalyzingMessages,
|
||||
PercentComplete = 60 + (int)(30.0 * processed / totalCommits),
|
||||
CommitsProcessed = processed,
|
||||
TotalCommits = totalCommits
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (scores.Count == 0)
|
||||
{
|
||||
return CreateEmptyMessageDistribution(totalCommits);
|
||||
}
|
||||
|
||||
var sortedScores = scores.Select(s => s.Score).OrderBy(s => s).ToList();
|
||||
var avg = sortedScores.Average();
|
||||
var median = sortedScores[sortedScores.Count / 2];
|
||||
|
||||
// Calculate standard deviation
|
||||
var sumSquaredDiff = sortedScores.Sum(s => Math.Pow(s - avg, 2));
|
||||
var stdDev = Math.Sqrt(sumSquaredDiff / sortedScores.Count);
|
||||
|
||||
// Determine trend (compare first half vs second half)
|
||||
var midpoint = scores.Count / 2;
|
||||
var olderAvg = scores.Skip(midpoint).Average(s => s.Score);
|
||||
var newerAvg = scores.Take(midpoint).Average(s => s.Score);
|
||||
var trend = newerAvg > olderAvg + 5 ? TrendDirection.Improving
|
||||
: newerAvg < olderAvg - 5 ? TrendDirection.Declining
|
||||
: TrendDirection.Stable;
|
||||
|
||||
return new MessageQualityDistribution
|
||||
{
|
||||
TotalCommits = scores.Count,
|
||||
Excellent = scores.Count(s => s.Score >= 90),
|
||||
Good = scores.Count(s => s.Score >= 70 && s.Score < 90),
|
||||
Fair = scores.Count(s => s.Score >= 50 && s.Score < 70),
|
||||
Poor = scores.Count(s => s.Score < 50),
|
||||
AverageScore = avg,
|
||||
MedianScore = median,
|
||||
StandardDeviation = stdDev,
|
||||
Trend = trend,
|
||||
Clusters = [], // Could add cluster detection
|
||||
PoorCommitHashes = poorCommits
|
||||
};
|
||||
}
|
||||
|
||||
private AuthorshipMetrics AnalyzeAuthorship(List<Commit> commits)
|
||||
{
|
||||
// Track running totals for calculating averages
|
||||
var authorData = new Dictionary<string, (string Name, string Email, int CommitCount, double TotalQuality, int MergeCount)>();
|
||||
var emailToCanonical = new Dictionary<string, string>(); // Maps raw email to canonical key
|
||||
var nameToCanonical = new Dictionary<string, string>(); // Maps normalized name to canonical key
|
||||
var missingEmail = 0;
|
||||
var invalidEmail = 0;
|
||||
var botCommits = 0;
|
||||
|
||||
var botPatterns = new[] { "[bot]", "dependabot", "renovate", "github-actions", "noreply" };
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
var email = commit.Author.Email ?? "";
|
||||
var name = commit.Author.Name ?? "Unknown";
|
||||
var normalizedEmail = NormalizeEmail(email);
|
||||
var normalizedName = NormalizeName(name);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
missingEmail++;
|
||||
}
|
||||
else if (!email.Contains('@'))
|
||||
{
|
||||
invalidEmail++;
|
||||
}
|
||||
|
||||
var isMerge = commit.Parents.Count() > 1;
|
||||
var isBot = botPatterns.Any(p =>
|
||||
name.Contains(p, StringComparison.OrdinalIgnoreCase) ||
|
||||
email.Contains(p, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (isBot) botCommits++;
|
||||
|
||||
// Analyze message quality for this commit
|
||||
var quality = _commitAnalyzer.Analyze(commit.Message);
|
||||
var qualityScore = quality.OverallScore;
|
||||
|
||||
// Find or create canonical key for this author
|
||||
var canonicalKey = FindOrCreateCanonicalKey(
|
||||
normalizedEmail, normalizedName, name, email,
|
||||
emailToCanonical, nameToCanonical, authorData);
|
||||
|
||||
if (!authorData.TryGetValue(canonicalKey, out var data))
|
||||
{
|
||||
data = (name, email, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Update running totals
|
||||
authorData[canonicalKey] = (
|
||||
data.Name,
|
||||
data.Email,
|
||||
data.CommitCount + 1,
|
||||
data.TotalQuality + qualityScore,
|
||||
data.MergeCount + (isMerge ? 1 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
// Convert to AuthorStats with calculated averages
|
||||
var authorStats = authorData.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => new AuthorStats
|
||||
{
|
||||
Name = kvp.Value.Name,
|
||||
Email = kvp.Value.Email,
|
||||
CommitCount = kvp.Value.CommitCount,
|
||||
AverageMessageQuality = kvp.Value.CommitCount > 0
|
||||
? kvp.Value.TotalQuality / kvp.Value.CommitCount
|
||||
: 0,
|
||||
MergeCommitCount = kvp.Value.MergeCount
|
||||
});
|
||||
|
||||
return new AuthorshipMetrics
|
||||
{
|
||||
TotalAuthors = authorStats.Count,
|
||||
TotalCommits = commits.Count,
|
||||
MissingEmailCount = missingEmail,
|
||||
InvalidEmailCount = invalidEmail,
|
||||
BotCommits = botCommits,
|
||||
AuthorBreakdown = authorStats
|
||||
};
|
||||
}
|
||||
|
||||
private static string FindOrCreateCanonicalKey<T>(
|
||||
string normalizedEmail,
|
||||
string normalizedName,
|
||||
string rawName,
|
||||
string rawEmail,
|
||||
Dictionary<string, string> emailToCanonical,
|
||||
Dictionary<string, string> nameToCanonical,
|
||||
Dictionary<string, T> authorData)
|
||||
{
|
||||
// First, check if we've seen this exact email before
|
||||
if (!string.IsNullOrEmpty(normalizedEmail) && emailToCanonical.TryGetValue(normalizedEmail, out var existingKey))
|
||||
{
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// Next, check if we've seen this name before (for matching when emails differ)
|
||||
if (!string.IsNullOrEmpty(normalizedName) && nameToCanonical.TryGetValue(normalizedName, out existingKey))
|
||||
{
|
||||
// Also map this email to the same canonical key
|
||||
if (!string.IsNullOrEmpty(normalizedEmail))
|
||||
{
|
||||
emailToCanonical[normalizedEmail] = existingKey;
|
||||
}
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// Create new canonical key - prefer email if valid, otherwise use name
|
||||
var canonicalKey = !string.IsNullOrEmpty(normalizedEmail) ? normalizedEmail : normalizedName;
|
||||
|
||||
// Register mappings
|
||||
if (!string.IsNullOrEmpty(normalizedEmail))
|
||||
{
|
||||
emailToCanonical[normalizedEmail] = canonicalKey;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(normalizedName))
|
||||
{
|
||||
nameToCanonical[normalizedName] = canonicalKey;
|
||||
}
|
||||
|
||||
return canonicalKey;
|
||||
}
|
||||
|
||||
private static string NormalizeEmail(string email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email)) return "";
|
||||
|
||||
email = email.ToLowerInvariant().Trim();
|
||||
|
||||
// Remove + aliases (e.g., john+test@gmail.com -> john@gmail.com)
|
||||
var atIndex = email.IndexOf('@');
|
||||
if (atIndex > 0)
|
||||
{
|
||||
var plusIndex = email.IndexOf('+');
|
||||
if (plusIndex > 0 && plusIndex < atIndex)
|
||||
{
|
||||
email = email[..plusIndex] + email[atIndex..];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize common noreply patterns
|
||||
if (email.Contains("noreply") || email.Contains("no-reply"))
|
||||
{
|
||||
// Extract username from GitHub noreply format: 12345678+username@users.noreply.github.com
|
||||
var match = System.Text.RegularExpressions.Regex.Match(email, @"\d+\+([^@]+)@users\.noreply\.github\.com");
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value + "@github";
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
private static string NormalizeName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return "";
|
||||
|
||||
// Lowercase and trim
|
||||
name = name.ToLowerInvariant().Trim();
|
||||
|
||||
// Remove common suffixes/prefixes
|
||||
name = name.Replace("[bot]", "").Trim();
|
||||
|
||||
// Normalize whitespace
|
||||
name = System.Text.RegularExpressions.Regex.Replace(name, @"\s+", " ");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static string NormalizeMessage(string message)
|
||||
{
|
||||
return message
|
||||
.ToLowerInvariant()
|
||||
.Trim()
|
||||
.Replace("\r", "")
|
||||
.Replace("\n", " ");
|
||||
}
|
||||
|
||||
private static DuplicateCommitMetrics CreateEmptyDuplicateMetrics(int totalCommits) => new()
|
||||
{
|
||||
TotalCommitsAnalyzed = totalCommits,
|
||||
TotalDuplicateGroups = 0,
|
||||
TotalDuplicateInstances = 0,
|
||||
ExactDuplicates = 0,
|
||||
CherryPicks = 0,
|
||||
FuzzyMatches = 0,
|
||||
DuplicateGroups = []
|
||||
};
|
||||
|
||||
private static BranchComplexityMetrics CreateEmptyBranchMetrics() => new()
|
||||
{
|
||||
TotalBranches = 0,
|
||||
ActiveBranches = 0,
|
||||
StaleBranches = 0,
|
||||
CrossMerges = 0,
|
||||
AverageBranchAge = 0,
|
||||
AverageBranchLength = 0,
|
||||
LongLivedBranches = 0,
|
||||
Topology = BranchTopologyType.Linear,
|
||||
StaleBranchNames = []
|
||||
};
|
||||
|
||||
private static MessageQualityDistribution CreateEmptyMessageDistribution(int totalCommits) => new()
|
||||
{
|
||||
TotalCommits = totalCommits,
|
||||
Excellent = 0,
|
||||
Good = 0,
|
||||
Fair = 0,
|
||||
Poor = 0,
|
||||
AverageScore = 0,
|
||||
MedianScore = 0,
|
||||
StandardDeviation = 0,
|
||||
Trend = TrendDirection.Stable,
|
||||
Clusters = [],
|
||||
PoorCommitHashes = []
|
||||
};
|
||||
}
|
||||
36
Services/IApiKeyProvider.cs
Executable file
36
Services/IApiKeyProvider.cs
Executable file
@ -0,0 +1,36 @@
|
||||
using MarketAlly.AIPlugin;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to API keys for AI providers.
|
||||
/// Implement this interface in platform-specific code (e.g., using SecureStorage in MAUI).
|
||||
/// </summary>
|
||||
public interface IApiKeyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the API key for the specified provider, or null if not configured.
|
||||
/// </summary>
|
||||
string? GetApiKey(AIProvider provider);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an API key is configured for the specified provider.
|
||||
/// </summary>
|
||||
bool HasApiKey(AIProvider provider);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the API key for the specified provider.
|
||||
/// </summary>
|
||||
Task SetApiKeyAsync(AIProvider provider, string apiKey);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the API key for the specified provider.
|
||||
/// </summary>
|
||||
Task RemoveApiKeyAsync(AIProvider provider);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all providers that have API keys configured.
|
||||
/// </summary>
|
||||
IReadOnlyList<AIProvider> GetConfiguredProviders();
|
||||
}
|
||||
124
Services/ICleanupExecutor.cs
Normal file
124
Services/ICleanupExecutor.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Executes cleanup operations on git repositories.
|
||||
/// </summary>
|
||||
public interface ICleanupExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a single cleanup operation.
|
||||
/// </summary>
|
||||
Task<CleanupExecutionResult> ExecuteAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupOperation operation,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Executes multiple cleanup operations in sequence.
|
||||
/// </summary>
|
||||
Task<BatchCleanupResult> ExecuteBatchAsync(
|
||||
ManagedRepo repo,
|
||||
IEnumerable<CleanupOperation> operations,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Previews what a cleanup operation will do without executing it.
|
||||
/// </summary>
|
||||
Task<CleanupPreview> PreviewAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupOperation operation,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a backup branch before cleanup operations.
|
||||
/// </summary>
|
||||
Task<string> CreateBackupBranchAsync(
|
||||
ManagedRepo repo,
|
||||
string? branchName = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for cleanup execution.
|
||||
/// </summary>
|
||||
public sealed record CleanupExecutionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a backup branch before making changes.
|
||||
/// </summary>
|
||||
public bool CreateBackup { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Custom backup branch name. Auto-generated if null.
|
||||
/// </summary>
|
||||
public string? BackupBranchName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Allow operations on pushed commits (requires force push).
|
||||
/// </summary>
|
||||
public bool AllowPushedCommits { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically force push after rewriting history.
|
||||
/// </summary>
|
||||
public bool AutoForcePush { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Use AI to generate improved commit messages.
|
||||
/// </summary>
|
||||
public bool UseAiForMessages { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of executing a single cleanup operation.
|
||||
/// </summary>
|
||||
public sealed record CleanupExecutionResult
|
||||
{
|
||||
public required string OperationId { get; init; }
|
||||
public required CleanupType Type { get; init; }
|
||||
public bool Success { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
public int CommitsModified { get; init; }
|
||||
public int CommitsRemoved { get; init; }
|
||||
public string? BackupBranch { get; init; }
|
||||
public bool RequiresForcePush { get; init; }
|
||||
public IReadOnlyList<string> ModifiedCommitHashes { get; init; } = [];
|
||||
public IReadOnlyList<string> NewCommitHashes { get; init; } = [];
|
||||
public TimeSpan Duration { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of executing multiple cleanup operations.
|
||||
/// </summary>
|
||||
public sealed class BatchCleanupResult
|
||||
{
|
||||
public int TotalOperations { get; init; }
|
||||
public int Successful { get; init; }
|
||||
public int Failed { get; init; }
|
||||
public int Skipped { get; init; }
|
||||
public string? BackupBranch { get; init; }
|
||||
public bool RequiresForcePush { get; init; }
|
||||
public IReadOnlyList<CleanupExecutionResult> Results { get; init; } = [];
|
||||
public TimeSpan TotalDuration { get; init; }
|
||||
|
||||
public bool AllSucceeded => Failed == 0 && Skipped == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress information for cleanup operations.
|
||||
/// </summary>
|
||||
public sealed class CleanupProgress
|
||||
{
|
||||
public required string CurrentOperation { get; init; }
|
||||
public int CurrentIndex { get; init; }
|
||||
public int TotalOperations { get; init; }
|
||||
public int PercentComplete { get; init; }
|
||||
public string? CurrentCommit { get; init; }
|
||||
}
|
||||
18
Services/ICommitAnalysisService.cs
Normal file
18
Services/ICommitAnalysisService.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides commit analysis functionality.
|
||||
/// </summary>
|
||||
public interface ICommitAnalysisService
|
||||
{
|
||||
Task<IReadOnlyList<CommitAnalysis>> AnalyzeAllReposAsync(
|
||||
bool onlyNeedsImprovement = true,
|
||||
IProgress<(string Repo, int Processed)>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
IEnumerable<CommitAnalysis> AnalyzeRepo(ManagedRepo repo);
|
||||
CommitAnalysis AnalyzeCommit(string repoPath, string commitHash);
|
||||
Task UpdateRepoAnalysisAsync(ManagedRepo repo, int totalCommits, int commitsNeedingImprovement, CancellationToken ct = default);
|
||||
}
|
||||
33
Services/ICommitAnalyzer.cs
Normal file
33
Services/ICommitAnalyzer.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Simple commit message analyzer for health analysis.
|
||||
/// </summary>
|
||||
public interface ICommitAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes a commit message and returns quality information.
|
||||
/// </summary>
|
||||
MessageQualityScore Analyze(string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation using the existing analyzer.
|
||||
/// </summary>
|
||||
public sealed class CommitAnalyzer : ICommitAnalyzer
|
||||
{
|
||||
private readonly ICommitMessageAnalyzer _analyzer;
|
||||
|
||||
public CommitAnalyzer(CommitMessageRules? rules = null)
|
||||
{
|
||||
_analyzer = new CommitMessageAnalyzer(rules ?? new CommitMessageRules());
|
||||
}
|
||||
|
||||
public MessageQualityScore Analyze(string message)
|
||||
{
|
||||
return _analyzer.Analyze(message);
|
||||
}
|
||||
}
|
||||
14
Services/ICommitMessageAnalyzer.cs
Normal file
14
Services/ICommitMessageAnalyzer.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public interface ICommitMessageAnalyzer
|
||||
{
|
||||
MessageQualityScore Analyze(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes a commit message with context about the actual changes.
|
||||
/// This allows detection of vague messages that don't describe significant changes.
|
||||
/// </summary>
|
||||
MessageQualityScore Analyze(string message, CommitContext context);
|
||||
}
|
||||
48
Services/ICommitRewriteService.cs
Normal file
48
Services/ICommitRewriteService.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles commit message rewriting operations.
|
||||
/// </summary>
|
||||
public interface ICommitRewriteService
|
||||
{
|
||||
IReadOnlyList<RewriteOperation> History { get; }
|
||||
|
||||
IReadOnlyList<RewriteOperation> PreviewChanges(IEnumerable<CommitAnalysis> analyses);
|
||||
|
||||
Task<BatchResult> ApplyChangesAsync(
|
||||
IEnumerable<RewriteOperation> operations,
|
||||
bool dryRun = true,
|
||||
IProgress<(int Processed, int Total)>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<RewriteOperation> ApplyChangeAsync(CommitAnalysis analysis, CancellationToken ct = default);
|
||||
|
||||
bool UndoCommitAmend(string repoPath, string originalCommitHash);
|
||||
|
||||
/// <summary>
|
||||
/// Gets safety information for a batch rewrite operation.
|
||||
/// Checks for uncommitted changes, pushed commits, and remote tracking status.
|
||||
/// </summary>
|
||||
/// <param name="repoPath">The repository path.</param>
|
||||
/// <param name="commits">The commits to be rewritten.</param>
|
||||
/// <returns>Safety information about the proposed operation.</returns>
|
||||
RewriteSafetyInfo GetRewriteSafetyInfo(string repoPath, IEnumerable<CommitAnalysis> commits);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a batch rewrite operation with full safety checks and backup creation.
|
||||
/// </summary>
|
||||
/// <param name="repoPath">The repository path.</param>
|
||||
/// <param name="commits">The commits to rewrite (must have SuggestedMessage populated).</param>
|
||||
/// <param name="createBackup">Whether to create a backup branch before rewriting.</param>
|
||||
/// <param name="progress">Progress reporter.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Result of the batch rewrite operation.</returns>
|
||||
Task<BatchRewriteResult> ExecuteBatchRewriteAsync(
|
||||
string repoPath,
|
||||
IEnumerable<CommitAnalysis> commits,
|
||||
bool createBackup = true,
|
||||
IProgress<(int Current, int Total, string CommitHash)>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
110
Services/ICostTrackingService.cs
Normal file
110
Services/ICostTrackingService.cs
Normal file
@ -0,0 +1,110 @@
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks AI operation costs for the session
|
||||
/// </summary>
|
||||
public interface ICostTrackingService
|
||||
{
|
||||
/// <summary>
|
||||
/// Total cost for the current session
|
||||
/// </summary>
|
||||
decimal SessionCost { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total cost across all sessions (lifetime)
|
||||
/// </summary>
|
||||
decimal LifetimeCost { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total input tokens used this session
|
||||
/// </summary>
|
||||
int TotalInputTokens { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total output tokens used this session
|
||||
/// </summary>
|
||||
int TotalOutputTokens { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of AI operations performed this session
|
||||
/// </summary>
|
||||
int OperationCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of AI operations performed across all sessions (lifetime)
|
||||
/// </summary>
|
||||
int LifetimeOperationCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Records an AI operation's cost
|
||||
/// </summary>
|
||||
/// <param name="operationType">Type of operation (e.g., "CommitSuggestion", "GitDiagnosis")</param>
|
||||
/// <param name="inputTokens">Input tokens used</param>
|
||||
/// <param name="outputTokens">Output tokens used</param>
|
||||
/// <param name="cost">Calculated cost</param>
|
||||
void RecordOperation(string operationType, int inputTokens, int outputTokens, decimal cost);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cost breakdown by operation type
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, OperationCostSummary> GetCostBreakdown();
|
||||
|
||||
/// <summary>
|
||||
/// Resets session tracking (keeps lifetime totals)
|
||||
/// </summary>
|
||||
void ResetSession();
|
||||
|
||||
/// <summary>
|
||||
/// Resets all tracking including lifetime totals
|
||||
/// </summary>
|
||||
void ResetAll();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current state to persistent storage
|
||||
/// </summary>
|
||||
void SaveState();
|
||||
|
||||
/// <summary>
|
||||
/// Loads state from persistent storage
|
||||
/// </summary>
|
||||
void LoadState();
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when costs are updated
|
||||
/// </summary>
|
||||
event EventHandler<CostUpdatedEventArgs>? CostUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of costs for a specific operation type
|
||||
/// </summary>
|
||||
public class OperationCostSummary
|
||||
{
|
||||
public string OperationType { get; set; } = string.Empty;
|
||||
public int Count { get; set; }
|
||||
public int TotalInputTokens { get; set; }
|
||||
public int TotalOutputTokens { get; set; }
|
||||
public decimal TotalCost { get; set; }
|
||||
public decimal AverageCost => Count > 0 ? TotalCost / Count : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for cost updates
|
||||
/// </summary>
|
||||
public class CostUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public string OperationType { get; }
|
||||
public decimal OperationCost { get; }
|
||||
public decimal SessionTotal { get; }
|
||||
public int InputTokens { get; }
|
||||
public int OutputTokens { get; }
|
||||
|
||||
public CostUpdatedEventArgs(string operationType, decimal operationCost, decimal sessionTotal, int inputTokens, int outputTokens)
|
||||
{
|
||||
OperationType = operationType;
|
||||
OperationCost = operationCost;
|
||||
SessionTotal = sessionTotal;
|
||||
InputTokens = inputTokens;
|
||||
OutputTokens = outputTokens;
|
||||
}
|
||||
}
|
||||
25
Services/IGitMessageImproverService.cs
Executable file
25
Services/IGitMessageImproverService.cs
Executable file
@ -0,0 +1,25 @@
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Unified facade for all git commit message improvement operations.
|
||||
/// Composes smaller, focused interfaces for consumers who need full functionality.
|
||||
/// </summary>
|
||||
public interface IGitMessageImproverService :
|
||||
IRepositoryManager,
|
||||
ICommitAnalysisService,
|
||||
ISuggestionService,
|
||||
ICommitRewriteService,
|
||||
IGitPushService,
|
||||
IHistoryHealthService,
|
||||
IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Load saved state from disk (repos, history, etc.)
|
||||
/// </summary>
|
||||
Task LoadStateAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a summary report of all repositories and recent operations.
|
||||
/// </summary>
|
||||
string GenerateSummaryReport();
|
||||
}
|
||||
79
Services/IGitOperationsService.cs
Executable file
79
Services/IGitOperationsService.cs
Executable file
@ -0,0 +1,79 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.LibGit2Sharp;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public interface IGitOperationsService : IDisposable
|
||||
{
|
||||
IEnumerable<string> DiscoverRepositories(string rootPath, int maxDepth = 3);
|
||||
ManagedRepo CreateManagedRepo(string repoPath);
|
||||
IEnumerable<BranchInfo> GetBranches(string repoPath);
|
||||
IEnumerable<CommitAnalysis> AnalyzeCommits(
|
||||
ManagedRepo managedRepo,
|
||||
ICommitMessageAnalyzer analyzer,
|
||||
int maxCommits = 100,
|
||||
DateTimeOffset? since = null,
|
||||
string[]? excludeAuthors = null);
|
||||
RewriteOperation AmendLatestCommit(ManagedRepo managedRepo, string newMessage);
|
||||
RewriteOperation RewordOlderCommit(ManagedRepo managedRepo, string commitHash, string newMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Rewords multiple commits in a single pass through history.
|
||||
/// This is more efficient and reliable than multiple individual RewordOlderCommit calls,
|
||||
/// because it handles the hash changes that occur when rewriting commits.
|
||||
/// </summary>
|
||||
/// <param name="managedRepo">The repository.</param>
|
||||
/// <param name="rewrites">Dictionary mapping original commit hash to new message.</param>
|
||||
/// <returns>List of rewrite operations with results.</returns>
|
||||
List<RewriteOperation> RewordMultipleCommits(ManagedRepo managedRepo, Dictionary<string, string> rewrites);
|
||||
|
||||
/// <summary>
|
||||
/// Undoes a commit amend by resetting to the original commit hash from the reflog.
|
||||
/// </summary>
|
||||
bool UndoCommitAmend(string repoPath, string originalCommitHash);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a commit has been pushed to the remote tracking branch.
|
||||
/// </summary>
|
||||
bool IsCommitPushed(string repoPath, string commitHash);
|
||||
|
||||
/// <summary>
|
||||
/// Gets tracking information for the current branch.
|
||||
/// </summary>
|
||||
TrackingInfo GetTrackingInfo(string repoPath);
|
||||
|
||||
/// <summary>
|
||||
/// Force pushes the current branch to the remote.
|
||||
/// </summary>
|
||||
GitPushResult ForcePush(string repoPath, PushOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// Regular push (non-force) to the remote.
|
||||
/// </summary>
|
||||
GitPushResult Push(string repoPath, PushOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all backup branches (matching backup/* pattern).
|
||||
/// </summary>
|
||||
IEnumerable<BackupBranchInfo> GetBackupBranches(string repoPath);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a local branch.
|
||||
/// </summary>
|
||||
bool DeleteBranch(string repoPath, string branchName);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cached Repository for the given path.
|
||||
/// Call after operations that modify git history to ensure fresh state on next access.
|
||||
/// </summary>
|
||||
void InvalidateCache(string path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a backup branch.
|
||||
/// </summary>
|
||||
public record BackupBranchInfo(
|
||||
string Name,
|
||||
string FullName,
|
||||
DateTimeOffset? CreatedAt,
|
||||
string? LastCommitSha);
|
||||
14
Services/IGitPushService.cs
Normal file
14
Services/IGitPushService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles git push operations and remote tracking.
|
||||
/// </summary>
|
||||
public interface IGitPushService
|
||||
{
|
||||
bool IsCommitPushed(string repoPath, string commitHash);
|
||||
TrackingInfo GetTrackingInfo(string repoPath);
|
||||
GitPushResult Push(string repoPath);
|
||||
GitPushResult ForcePush(string repoPath);
|
||||
}
|
||||
31
Services/IHealthReportGenerator.cs
Normal file
31
Services/IHealthReportGenerator.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for generating health reports from analysis results.
|
||||
/// </summary>
|
||||
public interface IHealthReportGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a comprehensive health report from analysis results.
|
||||
/// </summary>
|
||||
HistoryHealthReport GenerateReport(HistoryHealthAnalysis analysis);
|
||||
|
||||
/// <summary>
|
||||
/// Exports a report to the specified format.
|
||||
/// </summary>
|
||||
Task<string> ExportReportAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Exports a report to a file.
|
||||
/// </summary>
|
||||
Task ExportReportToFileAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
string outputPath,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
33
Services/IHistoryHealthAnalyzer.cs
Normal file
33
Services/IHistoryHealthAnalyzer.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for analyzing git repository history health.
|
||||
/// </summary>
|
||||
public interface IHistoryHealthAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs a comprehensive health analysis of a repository.
|
||||
/// </summary>
|
||||
/// <param name="repoPath">Path to the repository.</param>
|
||||
/// <param name="options">Analysis options.</param>
|
||||
/// <param name="progress">Progress reporter.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Analysis results.</returns>
|
||||
Task<HistoryHealthAnalysis> AnalyzeAsync(
|
||||
string repoPath,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a comprehensive health analysis of a managed repository.
|
||||
/// </summary>
|
||||
Task<HistoryHealthAnalysis> AnalyzeAsync(
|
||||
ManagedRepo repo,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
78
Services/IHistoryHealthService.cs
Normal file
78
Services/IHistoryHealthService.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
using MarketAlly.GitCommitEditor.Models.HistoryHealth;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for analyzing and reporting on git repository history health.
|
||||
/// </summary>
|
||||
public interface IHistoryHealthService
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes repository history health and generates a comprehensive report.
|
||||
/// </summary>
|
||||
/// <param name="repoPath">Path to the repository.</param>
|
||||
/// <param name="options">Analysis options.</param>
|
||||
/// <param name="progress">Progress reporter.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Complete health report with scores, issues, and recommendations.</returns>
|
||||
Task<HistoryHealthReport> AnalyzeHistoryHealthAsync(
|
||||
string repoPath,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes history health for a managed repository.
|
||||
/// </summary>
|
||||
Task<HistoryHealthReport> AnalyzeHistoryHealthAsync(
|
||||
ManagedRepo repo,
|
||||
HistoryAnalysisOptions? options = null,
|
||||
IProgress<AnalysisProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Exports a health report to the specified format.
|
||||
/// </summary>
|
||||
Task<string> ExportHealthReportAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Exports a health report to a file.
|
||||
/// </summary>
|
||||
Task ExportHealthReportToFileAsync(
|
||||
HistoryHealthReport report,
|
||||
ReportFormat format,
|
||||
string outputPath,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a single cleanup operation.
|
||||
/// </summary>
|
||||
Task<CleanupExecutionResult> ExecuteCleanupAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupOperation operation,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Executes all cleanup operations from a report.
|
||||
/// </summary>
|
||||
Task<BatchCleanupResult> ExecuteAllCleanupsAsync(
|
||||
ManagedRepo repo,
|
||||
CleanupSuggestions suggestions,
|
||||
CleanupExecutionOptions? options = null,
|
||||
IProgress<CleanupProgress>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a backup branch before cleanup operations.
|
||||
/// </summary>
|
||||
Task<string> CreateBackupBranchAsync(
|
||||
ManagedRepo repo,
|
||||
string? branchName = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
157
Services/IModelProviderService.cs
Executable file
157
Services/IModelProviderService.cs
Executable file
@ -0,0 +1,157 @@
|
||||
using MarketAlly.AIPlugin;
|
||||
using MarketAlly.AIPlugin.Agentic;
|
||||
using MarketAlly.AIPlugin.Agentic.Models;
|
||||
using MarketAlly.AIPlugin.Conversation;
|
||||
using MarketAlly.GitCommitEditor.Options;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing AI model providers dynamically based on configured API keys.
|
||||
/// Only shows providers that the user has actually configured.
|
||||
/// </summary>
|
||||
public interface IModelProviderService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the providers that have API keys configured.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> AvailableProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets models for a provider that support function calling.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> GetModelsForProvider(string provider);
|
||||
|
||||
/// <summary>
|
||||
/// Gets models with display information for a provider.
|
||||
/// </summary>
|
||||
IReadOnlyList<ModelDisplayInfo> GetModelsWithInfoForProvider(string provider);
|
||||
|
||||
/// <summary>
|
||||
/// Gets model info by ID.
|
||||
/// </summary>
|
||||
ModelDisplayInfo? GetModelInfo(string modelId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the API key for a provider.
|
||||
/// </summary>
|
||||
string? GetApiKey(AIProvider provider);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a provider has an API key configured.
|
||||
/// </summary>
|
||||
bool HasApiKey(AIProvider provider);
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the available providers based on current API key configuration.
|
||||
/// Call this after adding or removing an API key.
|
||||
/// </summary>
|
||||
void RefreshProviders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IModelProviderService that uses IApiKeyProvider to determine available providers.
|
||||
/// Uses the static ModelLibrary.Default for efficient querying.
|
||||
/// </summary>
|
||||
public class ModelProviderService : IModelProviderService
|
||||
{
|
||||
private readonly IApiKeyProvider _apiKeyProvider;
|
||||
|
||||
public ModelProviderService(IApiKeyProvider apiKeyProvider)
|
||||
{
|
||||
_apiKeyProvider = apiKeyProvider;
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> AvailableProviders =>
|
||||
ModelLibrary.Default.GetModelsWithFunctionCalling()
|
||||
.Where(m => m.Type != ModelType.Legacy)
|
||||
.Select(m => m.Provider.ToString())
|
||||
.Distinct()
|
||||
.OrderBy(p => p)
|
||||
.ToList();
|
||||
|
||||
public void RefreshProviders()
|
||||
{
|
||||
// No-op - static library doesn't need refreshing
|
||||
// API key changes are handled separately by IApiKeyProvider
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetModelsForProvider(string provider)
|
||||
{
|
||||
var aiProvider = ParseProvider(provider);
|
||||
var models = ModelLibrary.Default.GetModelsByProvider(aiProvider)
|
||||
.Where(m => m.SupportsFunctionCalling && m.Type != ModelType.Legacy)
|
||||
.OrderByDescending(m => m.Tier)
|
||||
.ThenBy(m => m.DisplayName)
|
||||
.Select(m => m.ModelId)
|
||||
.ToList();
|
||||
|
||||
return models.Count > 0 ? models : new[] { ModelConstants.Claude.Sonnet4 };
|
||||
}
|
||||
|
||||
public IReadOnlyList<ModelDisplayInfo> GetModelsWithInfoForProvider(string provider)
|
||||
{
|
||||
var aiProvider = ParseProvider(provider);
|
||||
var models = ModelLibrary.Default.GetModelsByProvider(aiProvider)
|
||||
.Where(m => m.SupportsFunctionCalling && m.Type != ModelType.Legacy)
|
||||
.OrderByDescending(m => m.Tier)
|
||||
.ThenBy(m => m.DisplayName)
|
||||
.Select(m => new ModelDisplayInfo
|
||||
{
|
||||
ModelId = m.ModelId,
|
||||
DisplayName = m.DisplayName,
|
||||
Tier = m.Tier.ToString(),
|
||||
InputCostPer1MTokens = m.InputCostPer1MTokens,
|
||||
OutputCostPer1MTokens = m.OutputCostPer1MTokens,
|
||||
Speed = m.Speed.ToString()
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return models.Count > 0 ? models : GetDefaultModelInfo();
|
||||
}
|
||||
|
||||
public ModelDisplayInfo? GetModelInfo(string modelId)
|
||||
{
|
||||
var model = ModelLibrary.Default.GetModel(modelId);
|
||||
if (model == null) return null;
|
||||
|
||||
return new ModelDisplayInfo
|
||||
{
|
||||
ModelId = model.ModelId,
|
||||
DisplayName = model.DisplayName,
|
||||
Tier = model.Tier.ToString(),
|
||||
InputCostPer1MTokens = model.InputCostPer1MTokens,
|
||||
OutputCostPer1MTokens = model.OutputCostPer1MTokens,
|
||||
Speed = model.Speed.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public string? GetApiKey(AIProvider provider) => _apiKeyProvider.GetApiKey(provider);
|
||||
|
||||
public bool HasApiKey(AIProvider provider) => _apiKeyProvider.HasApiKey(provider);
|
||||
|
||||
private static IReadOnlyList<ModelDisplayInfo> GetDefaultModelInfo() => new[]
|
||||
{
|
||||
new ModelDisplayInfo
|
||||
{
|
||||
ModelId = ModelConstants.Claude.Sonnet4,
|
||||
DisplayName = "Claude Sonnet 4",
|
||||
Tier = "Balanced",
|
||||
InputCostPer1MTokens = 3.00m,
|
||||
OutputCostPer1MTokens = 15.00m,
|
||||
Speed = "Fast"
|
||||
}
|
||||
};
|
||||
|
||||
private static AIProvider ParseProvider(string? provider)
|
||||
{
|
||||
return provider?.ToLowerInvariant() switch
|
||||
{
|
||||
"claude" or "anthropic" => AIProvider.Claude,
|
||||
"openai" or "gpt" => AIProvider.OpenAI,
|
||||
"gemini" or "google" => AIProvider.Gemini,
|
||||
"qwen" or "alibaba" => AIProvider.Qwen,
|
||||
_ => AIProvider.Claude
|
||||
};
|
||||
}
|
||||
}
|
||||
39
Services/IRepositoryManager.cs
Normal file
39
Services/IRepositoryManager.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Manages repository discovery and registration.
|
||||
/// </summary>
|
||||
public interface IRepositoryManager
|
||||
{
|
||||
IReadOnlyList<ManagedRepo> Repos { get; }
|
||||
|
||||
Task<IReadOnlyList<ManagedRepo>> ScanAndRegisterReposAsync(CancellationToken ct = default);
|
||||
Task<ManagedRepo> RegisterRepoAsync(string repoPath);
|
||||
Task<bool> UnregisterRepoAsync(string repoIdOrPath);
|
||||
IEnumerable<BranchInfo> GetBranches(string repoPath);
|
||||
|
||||
/// <summary>
|
||||
/// Checkout a branch in the specified repository.
|
||||
/// </summary>
|
||||
/// <param name="repo">The repository to switch branches in.</param>
|
||||
/// <param name="branchName">The name of the branch to checkout.</param>
|
||||
/// <returns>True if checkout succeeded, false otherwise.</returns>
|
||||
Task<bool> CheckoutBranchAsync(ManagedRepo repo, string branchName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all backup branches in the specified repository.
|
||||
/// </summary>
|
||||
IEnumerable<BackupBranchInfo> GetBackupBranches(string repoPath);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a local branch.
|
||||
/// </summary>
|
||||
bool DeleteBranch(string repoPath, string branchName);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all backup branches in the specified repository.
|
||||
/// </summary>
|
||||
int DeleteAllBackupBranches(string repoPath);
|
||||
}
|
||||
9
Services/IStateRepository.cs
Normal file
9
Services/IStateRepository.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
public interface IStateRepository
|
||||
{
|
||||
Task<ImproverState> LoadAsync(CancellationToken ct = default);
|
||||
Task SaveAsync(ImproverState state, CancellationToken ct = default);
|
||||
}
|
||||
24
Services/ISuggestionService.cs
Normal file
24
Services/ISuggestionService.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using MarketAlly.GitCommitEditor.Models;
|
||||
|
||||
namespace MarketAlly.GitCommitEditor.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides AI-powered suggestion generation for commit messages.
|
||||
/// </summary>
|
||||
public interface ISuggestionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate AI suggestions for a batch of commits.
|
||||
/// Returns detailed success/failure information for each commit.
|
||||
/// </summary>
|
||||
Task<BatchSuggestionResult> GenerateSuggestionsAsync(
|
||||
IEnumerable<CommitAnalysis> analyses,
|
||||
IProgress<int>? progress = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Generate an AI suggestion for a single commit.
|
||||
/// Returns detailed result including success/failure and error information.
|
||||
/// </summary>
|
||||
Task<SuggestionResult> GenerateSuggestionAsync(CommitAnalysis analysis, CancellationToken ct = default);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user