From 3111a508c7087f21e7e222d3db1ff5e83455b2ce Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 10:37:21 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #98 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Bot/issues/98 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..4e0a1549 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Bot/issues/98 +Your prepared branch: issue-98-b1d93110 +Your prepared working directory: /tmp/gh-issue-solver-1757749036677 + +Proceed. \ No newline at end of file From 329c4a02c15fc565b1451def84a11070f571518c Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 10:49:21 +0300 Subject: [PATCH 2/3] Add automatic release generation for dependency-only changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements GitHub Bot feature to automatically create releases when commits contain only dependency updates. Features: - New CommitTracker to monitor commits on main branch - DependencyOnlyReleaseTrigger detects dependabot commits affecting only dependency files - Automatic release creation with descriptive tags (deps-YYYY.MM.DD-shortsha) - Support for multiple dependency file types (C#, Node.js, Python, Rust, Go, Ruby) - Duplicate prevention through processed commit tracking šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- csharp/Platform.Bot/Program.cs | 2 + csharp/Platform.Bot/Trackers/CommitTracker.cs | 91 +++++++++ .../Triggers/DependencyOnlyReleaseTrigger.cs | 176 ++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 csharp/Platform.Bot/Trackers/CommitTracker.cs create mode 100644 csharp/Platform.Bot/Triggers/DependencyOnlyReleaseTrigger.cs diff --git a/csharp/Platform.Bot/Program.cs b/csharp/Platform.Bot/Program.cs index 521a6b95..14a3e8ae 100644 --- a/csharp/Platform.Bot/Program.cs +++ b/csharp/Platform.Bot/Program.cs @@ -97,6 +97,7 @@ private static async Task Main(string[] args) var githubStorage = new GitHubStorage(githubUserName, githubApiToken, githubApplicationName); var issueTracker = new IssueTracker(githubStorage, new HelloWorldTrigger(githubStorage, dbContext, fileSetName), new OrganizationLastMonthActivityTrigger(githubStorage), new LastCommitActivityTrigger(githubStorage), new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage)); var pullRequenstTracker = new PullRequestTracker(githubStorage, new MergeDependabotBumpsTrigger(githubStorage)); + var commitTracker = new CommitTracker(githubStorage, new DependencyOnlyReleaseTrigger(githubStorage)); var timestampTracker = new DateTimeTracker(githubStorage, new CreateAndSaveOrganizationRepositoriesMigrationTrigger(githubStorage, dbContext, Path.Combine(Directory.GetCurrentDirectory(), "/github-migrations"))); var cancellation = new CancellationTokenSource(); while (true) @@ -105,6 +106,7 @@ private static async Task Main(string[] args) { await issueTracker.Start(cancellation.Token); await pullRequenstTracker.Start(cancellation.Token); + await commitTracker.Start(cancellation.Token); // timestampTracker.Start(cancellation.Token); Thread.Sleep(minimumInteractionInterval); } diff --git a/csharp/Platform.Bot/Trackers/CommitTracker.cs b/csharp/Platform.Bot/Trackers/CommitTracker.cs new file mode 100644 index 00000000..3113026c --- /dev/null +++ b/csharp/Platform.Bot/Trackers/CommitTracker.cs @@ -0,0 +1,91 @@ +using Interfaces; +using Octokit; +using Storage.Remote.GitHub; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Platform.Collections.Lists; +using Platform.Threading; + +namespace Platform.Bot.Trackers +{ + /// + /// + /// Represents the commit tracker. + /// + /// + /// + public class CommitTracker : ITracker + { + /// + /// + /// The git hub api. + /// + /// + /// + private GitHubStorage _storage; + + /// + /// + /// The triggers. + /// + /// + /// + private IList> _triggers; + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A triggers. + /// + /// + /// + /// A git hub api. + /// + /// + public CommitTracker(GitHubStorage storage, params ITrigger[] triggers) + { + _storage = storage; + _triggers = triggers; + } + + /// + /// + /// Starts the cancellation token. + /// + /// + /// + /// + /// The cancellation token. + /// + /// + public async Task Start(CancellationToken cancellationToken) + { + foreach (var trigger in _triggers) + { + foreach (var repository in _storage.Client.Repository.GetAllForOrg("linksplatform").AwaitResult()) + { + // Get commits from the main branch only + var commits = _storage.GetCommits(repository.Id, new CommitRequest { Sha = repository.DefaultBranch }).AwaitResult(); + + foreach (var commit in commits) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + if (await trigger.Condition(commit)) + { + await trigger.Action(commit); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Bot/Triggers/DependencyOnlyReleaseTrigger.cs b/csharp/Platform.Bot/Triggers/DependencyOnlyReleaseTrigger.cs new file mode 100644 index 00000000..9dcaa2ad --- /dev/null +++ b/csharp/Platform.Bot/Triggers/DependencyOnlyReleaseTrigger.cs @@ -0,0 +1,176 @@ +using System; +using System.Threading.Tasks; +using Interfaces; +using Octokit; +using Platform.Threading; +using Storage.Remote.GitHub; +using System.Linq; +using System.Text.RegularExpressions; +using Storage.Local; +using System.Collections.Generic; + +namespace Platform.Bot.Triggers +{ + public class DependencyOnlyReleaseTrigger : ITrigger + { + private readonly GitHubStorage _githubStorage; + private readonly HashSet _processedCommits; + + public DependencyOnlyReleaseTrigger(GitHubStorage storage) + { + _githubStorage = storage; + _processedCommits = new HashSet(); + } + + public async Task Condition(GitHubCommit commit) + { + try + { + // Skip if we already processed this commit + if (_processedCommits.Contains(commit.Sha)) + { + return false; + } + + // Skip if this is not a dependabot commit + if (!IsDependabotCommit(commit)) + { + return false; + } + + // Get the repository + var repositoryId = commit.Repository?.Id; + if (repositoryId == null) + { + return false; + } + + // Check if this commit only changes dependency files + var commitDetails = _githubStorage.Client.Repository.Commit.Get(repositoryId.Value, commit.Sha).AwaitResult(); + + // Check if files changed are only dependency-related files + var changedFiles = commitDetails.Files; + if (changedFiles == null || !changedFiles.Any()) + { + return false; + } + + // Check if all changed files are dependency files + var isDependencyOnlyChange = changedFiles.All(file => IsDependencyFile(file.Filename)); + + if (!isDependencyOnlyChange) + { + return false; + } + + // Check if a release with this commit already exists to avoid duplicates + var releases = _githubStorage.Client.Repository.Release.GetAll(repositoryId.Value).AwaitResult(); + var existingRelease = releases.FirstOrDefault(r => r.TagName.Contains(commit.Sha.Substring(0, 7))); + + return existingRelease == null; + } + catch (Exception ex) + { + Console.WriteLine($"Error in DependencyOnlyReleaseTrigger.Condition: {ex.Message}"); + return false; + } + } + + public async Task Action(GitHubCommit commit) + { + try + { + var repositoryId = commit.Repository?.Id; + if (repositoryId == null) + { + return; + } + + var repository = _githubStorage.Client.Repository.Get(repositoryId.Value).AwaitResult(); + + // Generate a new version tag based on the current date and commit + var currentDate = DateTime.UtcNow; + var shortCommitSha = commit.Sha.Substring(0, 7); + var tagName = $"deps-{currentDate:yyyy.MM.dd}-{shortCommitSha}"; + + // Create release name and body + var releaseName = $"Dependency Updates - {currentDate:yyyy-MM-dd}"; + var releaseBody = $"Automatic release for dependency updates.\n\nCommit: {commit.HtmlUrl}\nCommit Message: {commit.Commit.Message}\n\nšŸ¤– Generated with [Claude Code](https://claude.ai/code)"; + + // Create the release + var newRelease = new NewRelease(tagName) + { + Name = releaseName, + Body = releaseBody, + Draft = false, + Prerelease = false, + TargetCommitish = commit.Sha + }; + + var createdRelease = await _githubStorage.Client.Repository.Release.Create(repositoryId.Value, newRelease); + + Console.WriteLine($"Created automatic release for dependency updates: {createdRelease.HtmlUrl}"); + Console.WriteLine($"Repository: {repository.FullName}"); + Console.WriteLine($"Tag: {tagName}"); + + // Mark this commit as processed to avoid duplicates + _processedCommits.Add(commit.Sha); + } + catch (Exception ex) + { + Console.WriteLine($"Error in DependencyOnlyReleaseTrigger.Action: {ex.Message}"); + } + } + + private bool IsDependabotCommit(GitHubCommit commit) + { + // Check if the commit is from dependabot by checking the author or commit message + if (commit.Author?.Id == GitHubStorage.DependabotId) + { + return true; + } + + if (commit.Committer?.Id == GitHubStorage.DependabotId) + { + return true; + } + + // Check commit message patterns + var commitMessage = commit.Commit?.Message?.ToLower() ?? ""; + var dependabotPatterns = new[] + { + "bump ", + "update ", + "dependabot", + "dependency" + }; + + return dependabotPatterns.Any(pattern => commitMessage.Contains(pattern)); + } + + private bool IsDependencyFile(string filename) + { + var dependencyFilePatterns = new[] + { + @"\.csproj$", // C# project files + @"packages\.config$", // NuGet packages.config + @"\.sln$", // Solution files (sometimes updated by dependabot) + @"package\.json$", // Node.js package.json + @"package-lock\.json$", // Node.js lock file + @"yarn\.lock$", // Yarn lock file + @"Cargo\.toml$", // Rust Cargo.toml + @"Cargo\.lock$", // Rust Cargo.lock + @"requirements\.txt$", // Python requirements + @"Pipfile$", // Python Pipfile + @"Pipfile\.lock$", // Python Pipfile.lock + @"pyproject\.toml$", // Python pyproject.toml + @"go\.mod$", // Go modules + @"go\.sum$", // Go sum file + @"Gemfile$", // Ruby Gemfile + @"Gemfile\.lock$" // Ruby Gemfile.lock + }; + + return dependencyFilePatterns.Any(pattern => Regex.IsMatch(filename, pattern, RegexOptions.IgnoreCase)); + } + } +} \ No newline at end of file From 7ed0af9127a8158a23d1ccc3117d6b7a6078f93f Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 10:50:01 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 4e0a1549..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Bot/issues/98 -Your prepared branch: issue-98-b1d93110 -Your prepared working directory: /tmp/gh-issue-solver-1757749036677 - -Proceed. \ No newline at end of file