Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.21', '1.22', '1.23']
go-version: [ '1.25', '1.26']

steps:
- name: Checkout code
Expand All @@ -33,11 +33,6 @@ jobs:
go mod tidy
git diff --exit-code go.mod go.sum || (echo "go.mod or go.sum changed unexpectedly" && exit 1)

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest

- name: Run tests
env:
JIRA_API_KEY: ${{ secrets.JIRA_API_KEY }}
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v9
with:
version: latest
# golangci-lint stopped cutting v1.x releases; "latest" here still
# resolves to the last v1.x tag (v1.64.8, built with go1.24), which
# can't lint a module whose go.mod requires go1.25+. Pin an actual
# v2 release explicitly instead of trusting "latest" — and the
# action itself has to be v7+, since v6 hard-rejects v2 versions.
version: v2.12.2
6 changes: 3 additions & 3 deletions internal/ai/gemini/issue_content_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func (s *GeminiIssueContentGenerator) buildIssuePrompt(request models.IssueGener
var sb strings.Builder

if request.Description != "" {
sb.WriteString(fmt.Sprintf("Global Description: %s\n\n", request.Description))
fmt.Fprintf(&sb, "Global Description: %s\n\n", request.Description)
}

if request.Diff != "" {
Expand All @@ -243,14 +243,14 @@ func (s *GeminiIssueContentGenerator) buildIssuePrompt(request models.IssueGener
if len(request.ChangedFiles) > 0 {
sb.WriteString("Changed files:\n")
for _, file := range request.ChangedFiles {
sb.WriteString(fmt.Sprintf("- %s\n", file))
fmt.Fprintf(&sb, "- %s\n", file)
}
sb.WriteString("\n")
}
}

if request.Hint != "" {
sb.WriteString(fmt.Sprintf("User Hint: %s\n\n", request.Hint))
fmt.Fprintf(&sb, "User Hint: %s\n\n", request.Hint)
}

if request.Template != nil {
Expand Down
50 changes: 25 additions & 25 deletions internal/ai/gemini/release_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,94 +283,94 @@ func (g *ReleaseNotesGenerator) formatChangesForPrompt(release *models.Release)
headers := ai.GetReleaseNotesSectionHeaders(g.lang)

if len(release.Breaking) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["breaking"]))
fmt.Fprintf(&sb, "%s\n", headers["breaking"])
for _, item := range release.Breaking {
sb.WriteString(fmt.Sprintf("- %s: %s\n", item.Type, item.Description))
fmt.Fprintf(&sb, "- %s: %s\n", item.Type, item.Description)
}
sb.WriteString("\n")
}

if len(release.Features) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["features"]))
fmt.Fprintf(&sb, "%s\n", headers["features"])
for _, item := range release.Features {
sb.WriteString(fmt.Sprintf("- %s: %s\n", item.Type, item.Description))
fmt.Fprintf(&sb, "- %s: %s\n", item.Type, item.Description)
}
sb.WriteString("\n")
}

if len(release.BugFixes) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["fixes"]))
fmt.Fprintf(&sb, "%s\n", headers["fixes"])
for _, item := range release.BugFixes {
sb.WriteString(fmt.Sprintf("- %s: %s\n", item.Type, item.Description))
fmt.Fprintf(&sb, "- %s: %s\n", item.Type, item.Description)
}
sb.WriteString("\n")
}

if len(release.Improvements) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["improvements"]))
fmt.Fprintf(&sb, "%s\n", headers["improvements"])
for _, item := range release.Improvements {
sb.WriteString(fmt.Sprintf("- %s: %s\n", item.Type, item.Description))
fmt.Fprintf(&sb, "- %s: %s\n", item.Type, item.Description)
}
sb.WriteString("\n")
}

if len(release.ClosedIssues) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["closed_issues"]))
fmt.Fprintf(&sb, "%s\n", headers["closed_issues"])
for _, issue := range release.ClosedIssues {
sb.WriteString(fmt.Sprintf("- #%d: %s (by @%s)\n", issue.Number, issue.Title, issue.Author))
fmt.Fprintf(&sb, "- #%d: %s (by @%s)\n", issue.Number, issue.Title, issue.Author)
}
sb.WriteString("\n")
}

if len(release.MergedPRs) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["merged_prs"]))
fmt.Fprintf(&sb, "%s\n", headers["merged_prs"])
for _, pr := range release.MergedPRs {
sb.WriteString(fmt.Sprintf("- #%d: %s (by @%s)\n", pr.Number, pr.Title, pr.Author))
fmt.Fprintf(&sb, "- #%d: %s (by @%s)\n", pr.Number, pr.Title, pr.Author)
if pr.Description != "" {
lines := strings.Split(pr.Description, "\n")
if len(lines) > 0 && lines[0] != "" {
sb.WriteString(fmt.Sprintf(" Description: %s\n", lines[0]))
fmt.Fprintf(&sb, " Description: %s\n", lines[0])
}
}
}
sb.WriteString("\n")
}

if len(release.Contributors) > 0 {
sb.WriteString(fmt.Sprintf("%s (%d total):\n", headers["contributors"], len(release.Contributors)))
fmt.Fprintf(&sb, "%s (%d total):\n", headers["contributors"], len(release.Contributors))
for _, contributor := range release.Contributors {
sb.WriteString(fmt.Sprintf("- @%s\n", contributor))
fmt.Fprintf(&sb, "- @%s\n", contributor)
}
if len(release.NewContributors) > 0 {
sb.WriteString(fmt.Sprintf("New contributors: %s\n", strings.Join(release.NewContributors, ", ")))
fmt.Fprintf(&sb, "New contributors: %s\n", strings.Join(release.NewContributors, ", "))
}
sb.WriteString("\n")
}

if release.FileStats.FilesChanged > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["file_stats"]))
sb.WriteString(fmt.Sprintf("- Files changed: %d\n", release.FileStats.FilesChanged))
sb.WriteString(fmt.Sprintf("- Insertions: +%d\n", release.FileStats.Insertions))
sb.WriteString(fmt.Sprintf("- Deletions: -%d\n", release.FileStats.Deletions))
fmt.Fprintf(&sb, "%s\n", headers["file_stats"])
fmt.Fprintf(&sb, "- Files changed: %d\n", release.FileStats.FilesChanged)
fmt.Fprintf(&sb, "- Insertions: +%d\n", release.FileStats.Insertions)
fmt.Fprintf(&sb, "- Deletions: -%d\n", release.FileStats.Deletions)
if len(release.FileStats.TopFiles) > 0 {
sb.WriteString("Top modified files:\n")
for _, file := range release.FileStats.TopFiles {
sb.WriteString(fmt.Sprintf(" - %s (+%d/-%d)\n", file.Path, file.Additions, file.Deletions))
fmt.Fprintf(&sb, " - %s (+%d/-%d)\n", file.Path, file.Additions, file.Deletions)
}
}
sb.WriteString("\n")
}

if len(release.Dependencies) > 0 {
sb.WriteString(fmt.Sprintf("%s\n", headers["deps"]))
fmt.Fprintf(&sb, "%s\n", headers["deps"])
for _, dep := range release.Dependencies {
switch dep.Type {
case "updated":
sb.WriteString(fmt.Sprintf("- %s: %s → %s\n", dep.Name, dep.OldVersion, dep.NewVersion))
fmt.Fprintf(&sb, "- %s: %s → %s\n", dep.Name, dep.OldVersion, dep.NewVersion)
case "added":
sb.WriteString(fmt.Sprintf("- Added: %s %s\n", dep.Name, dep.NewVersion))
fmt.Fprintf(&sb, "- Added: %s %s\n", dep.Name, dep.NewVersion)
case "removed":
sb.WriteString(fmt.Sprintf("- Removed: %s %s\n", dep.Name, dep.OldVersion))
fmt.Fprintf(&sb, "- Removed: %s %s\n", dep.Name, dep.OldVersion)
}
}
sb.WriteString("\n")
Expand Down
22 changes: 11 additions & 11 deletions internal/ai/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,17 +446,17 @@ func FormatTemplateForPrompt(template *models.IssueTemplate, lang string, templa

if template.Name != "" {
if lang == "es" {
sb.WriteString(fmt.Sprintf("Nombre del Template: %s\n", template.Name))
fmt.Fprintf(&sb, "Nombre del Template: %s\n", template.Name)
} else {
sb.WriteString(fmt.Sprintf("Template Name: %s\n", template.Name))
fmt.Fprintf(&sb, "Template Name: %s\n", template.Name)
}
}

if template.GetAbout() != "" {
if lang == "es" {
sb.WriteString(fmt.Sprintf("Descripción del Template: %s\n", template.GetAbout()))
fmt.Fprintf(&sb, "Descripción del Template: %s\n", template.GetAbout())
} else {
sb.WriteString(fmt.Sprintf("Template Description: %s\n", template.GetAbout()))
fmt.Fprintf(&sb, "Template Description: %s\n", template.GetAbout())
}
}

Expand Down Expand Up @@ -497,12 +497,12 @@ func FormatTemplateForPrompt(template *models.IssueTemplate, lang string, templa
}

if item.Attributes.Label != "" {
sb.WriteString(fmt.Sprintf("### %s\n", item.Attributes.Label))
fmt.Fprintf(&sb, "### %s\n", item.Attributes.Label)
if item.Attributes.Description != "" {
sb.WriteString(fmt.Sprintf("Context: %s\n", item.Attributes.Description))
fmt.Fprintf(&sb, "Context: %s\n", item.Attributes.Description)
}
if item.Attributes.Placeholder != "" {
sb.WriteString(fmt.Sprintf("Example: %s\n", item.Attributes.Placeholder))
fmt.Fprintf(&sb, "Example: %s\n", item.Attributes.Placeholder)
}
sb.WriteString("\n")
}
Expand Down Expand Up @@ -581,22 +581,22 @@ func FormatIssuesForPrompt(issues []models.Issue, locale string) string {
var result strings.Builder
for _, issue := range issues {
if locale == "es" {
result.WriteString(fmt.Sprintf("- Issue #%d: %s\n", issue.Number, issue.Title))
fmt.Fprintf(&result, "- Issue #%d: %s\n", issue.Number, issue.Title)
if issue.Description != "" {
desc := issue.Description
if len(desc) > 200 {
desc = desc[:200] + "..."
}
result.WriteString(fmt.Sprintf(" Descripción: %s\n", desc))
fmt.Fprintf(&result, " Descripción: %s\n", desc)
}
} else {
result.WriteString(fmt.Sprintf("- Issue #%d: %s\n", issue.Number, issue.Title))
fmt.Fprintf(&result, "- Issue #%d: %s\n", issue.Number, issue.Title)
if issue.Description != "" {
desc := issue.Description
if len(desc) > 200 {
desc = desc[:200] + "..."
}
result.WriteString(fmt.Sprintf(" Description: %s\n", desc))
fmt.Fprintf(&result, " Description: %s\n", desc)
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions internal/commands/release/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes,
md.WriteString("\n\n")
for _, pr := range release.MergedPRs {
if pr.URL != "" {
md.WriteString(fmt.Sprintf("- [#%d](%s) %s (by @%s)\n", pr.Number, pr.URL, pr.Title, pr.Author))
fmt.Fprintf(&md, "- [#%d](%s) %s (by @%s)\n", pr.Number, pr.URL, pr.Title, pr.Author)
} else {
md.WriteString(fmt.Sprintf("- #%d %s (by @%s)\n", pr.Number, pr.Title, pr.Author))
fmt.Fprintf(&md, "- #%d %s (by @%s)\n", pr.Number, pr.Title, pr.Author)
}
}
md.WriteString("\n")
Expand All @@ -97,7 +97,7 @@ func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes,
md.WriteString(trans.GetMessage("release.new_contributors", 0, struct{ Count int }{len(release.NewContributors)}))
md.WriteString(" ")
for i, contributor := range release.NewContributors {
md.WriteString(fmt.Sprintf("@%s", contributor))
fmt.Fprintf(&md, "@%s", contributor)
if i < len(release.NewContributors)-1 {
md.WriteString(", ")
}
Expand All @@ -108,7 +108,7 @@ func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes,
md.WriteString(trans.GetMessage("release.all_contributors", 0, nil))
md.WriteString("\n")
for _, contributor := range release.Contributors {
md.WriteString(fmt.Sprintf("- @%s\n", contributor))
fmt.Fprintf(&md, "- @%s\n", contributor)
}
md.WriteString("\n")
}
Expand All @@ -117,15 +117,15 @@ func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes,
md.WriteString("## ")
md.WriteString(trans.GetMessage("release.md_stats", 0, nil))
md.WriteString("\n\n")
md.WriteString(fmt.Sprintf("- %s: **%d**\n",
fmt.Fprintf(&md, "- %s: **%d**\n",
trans.GetMessage("release.files_changed", 0, nil),
release.FileStats.FilesChanged))
md.WriteString(fmt.Sprintf("- %s: **+%d**\n",
release.FileStats.FilesChanged)
fmt.Fprintf(&md, "- %s: **+%d**\n",
trans.GetMessage("release.insertions", 0, nil),
release.FileStats.Insertions))
md.WriteString(fmt.Sprintf("- %s: **-%d**\n",
release.FileStats.Insertions)
fmt.Fprintf(&md, "- %s: **-%d**\n",
trans.GetMessage("release.deletions", 0, nil),
release.FileStats.Deletions))
release.FileStats.Deletions)
md.WriteString("\n")
}
return content + md.String()
Expand Down
14 changes: 7 additions & 7 deletions internal/services/issue_generator_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func (s *IssueGeneratorService) GenerateFromPR(ctx context.Context, prNumber int
"diff_size", len(prData.Diff))

var contextBuilder strings.Builder
contextBuilder.WriteString(fmt.Sprintf("Pull Request #%d: %s\n\n", prNumber, prData.Title))
fmt.Fprintf(&contextBuilder, "Pull Request #%d: %s\n\n", prNumber, prData.Title)

if prData.Description != "" {
contextBuilder.WriteString("PR Description:\n")
Expand All @@ -299,7 +299,7 @@ func (s *IssueGeneratorService) GenerateFromPR(ctx context.Context, prNumber int
if len(prData.Commits) > 0 {
contextBuilder.WriteString("Commits:\n")
for _, commit := range prData.Commits {
contextBuilder.WriteString(fmt.Sprintf("- %s\n", commit))
fmt.Fprintf(&contextBuilder, "- %s\n", commit)
}
contextBuilder.WriteString("\n")
}
Expand Down Expand Up @@ -423,21 +423,21 @@ func (s *IssueGeneratorService) SelectTemplateWithAI(ctx context.Context, title,

var templateListBuilder strings.Builder
for _, t := range templates {
templateListBuilder.WriteString(fmt.Sprintf("- %s: %s\n", t.Name, t.About))
fmt.Fprintf(&templateListBuilder, "- %s: %s\n", t.Name, t.About)
}

var contextBuilder strings.Builder
if title != "" {
contextBuilder.WriteString(fmt.Sprintf("Title: %s\n", title))
fmt.Fprintf(&contextBuilder, "Title: %s\n", title)
}
if description != "" {
contextBuilder.WriteString(fmt.Sprintf("Description: %s\n", description))
fmt.Fprintf(&contextBuilder, "Description: %s\n", description)
}
if len(changedFiles) > 0 {
contextBuilder.WriteString(fmt.Sprintf("Changed files: %s\n", strings.Join(changedFiles, ", ")))
fmt.Fprintf(&contextBuilder, "Changed files: %s\n", strings.Join(changedFiles, ", "))
}
if len(labels) > 0 {
contextBuilder.WriteString(fmt.Sprintf("Labels: %s\n", strings.Join(labels, ", ")))
fmt.Fprintf(&contextBuilder, "Labels: %s\n", strings.Join(labels, ", "))
}

prompt := fmt.Sprintf(`You are an intelligent assistant helping to select the correct issue template for a software project.
Expand Down
4 changes: 2 additions & 2 deletions internal/services/pull_request_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (s *PRService) generateTestPlan(prData models.PRData) string {
if len(prData.RelatedIssues) > 0 {
testPlan.WriteString("### Associated Issues\n")
for _, issue := range prData.RelatedIssues {
testPlan.WriteString(fmt.Sprintf("- [ ] Verify #%d is fully resolved\n", issue.Number))
fmt.Fprintf(&testPlan, "- [ ] Verify #%d is fully resolved\n", issue.Number)
}
testPlan.WriteString("\n")
}
Expand Down Expand Up @@ -438,7 +438,7 @@ func (s *PRService) addBreakingChangesToSummary(summary models.PRSummary, breaki
breakingSection.WriteString("\n\n## Breaking Changes\n\n")

for _, change := range breakingChanges {
breakingSection.WriteString(fmt.Sprintf("- %s\n", change))
fmt.Fprintf(&breakingSection, "- %s\n", change)
}

summary.Body += breakingSection.String()
Expand Down
Loading
Loading