From 940313287d400928cecdbf4b738e0d5c6a6c5792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Thu, 4 Dec 2025 13:50:48 -0300 Subject: [PATCH 1/6] feat: Implementa fallback para diffs grandes de PRs de GitHub --- internal/i18n/locales/active.es.toml | 9 +++ .../vcs/github/github_service.go | 64 ++++++++++++++++++- .../vcs/github/github_service_test.go | 6 +- internal/infrastructure/vcs/github/mocks.go | 12 ++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/internal/i18n/locales/active.es.toml b/internal/i18n/locales/active.es.toml index 18e3e91..c289b04 100644 --- a/internal/i18n/locales/active.es.toml +++ b/internal/i18n/locales/active.es.toml @@ -220,6 +220,8 @@ update_pr = "Error al actualizar el PR #{{.pr_number}}" get_pr = "Error al obtener el PR #{{.pr_number}}" get_commits = "Error al obtener los commits del PR #{{.pr_number}}" get_diff = "Error al obtener el diff del PR #{{.pr_number}}" +get_diff_from_commits = "Error al obtener el diff por commits del PR #{{.pr_number}}" +get_commit_diff = "Error al obtener el diff del commit" get_repo_labels = "Error al obtener las etiquetas del repo" add_labels = "Error al añadir las etiquetas al repo #{{.pr_number}}" invalid_repo_format = "Formato de repositorio inválido" @@ -228,6 +230,13 @@ no_repo_configured = "No se ha configurado ningún repositorio. Usa --repo o con vcs_provider_not_configured = "El proveedor VCS '{{.Provider}}' no está configurado" vcs_provider_auto_detected_not_configured = "Proveedor de VCS '%s' detectado automáticamente pero no configurado. Use 'matecommit config set-vcs --provider %s --token ' para configurarlo" +[warning] +pr_too_large = "⚠️ El PR #{{.pr_number}} es demasiado grande (>20,000 líneas). Obteniendo diff commit por commit..." + +[info] +fetching_commit_diffs = "📥 Obteniendo diffs de {{.total}} commits..." +processing_commit = "🔄 Procesando commit {{.sha}}" + [label] feature = "Nuevas funcionalidades" fix = "Correcciones de errores" diff --git a/internal/infrastructure/vcs/github/github_service.go b/internal/infrastructure/vcs/github/github_service.go index 70d5ed9..9c42ef3 100644 --- a/internal/infrastructure/vcs/github/github_service.go +++ b/internal/infrastructure/vcs/github/github_service.go @@ -25,9 +25,14 @@ type IssuesService interface { AddLabelsToIssue(ctx context.Context, owner, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) } +type RepositoriesService interface { + GetCommit(ctx context.Context, owner, repo, sha string) (*github.RepositoryCommit, *github.Response, error) +} + type GitHubClient struct { prService PullRequestsService issuesService IssuesService + repoService RepositoriesService owner string repo string trans *i18n.Translations @@ -56,6 +61,7 @@ func NewGitHubClient(owner, repo, token string, trans *i18n.Translations) *GitHu return &GitHubClient{ prService: client.PullRequests, issuesService: client.Issues, + repoService: client.Repositories, owner: owner, repo: repo, trans: trans, @@ -65,6 +71,7 @@ func NewGitHubClient(owner, repo, token string, trans *i18n.Translations) *GitHu func NewGitHubClientWithServices( prService PullRequestsService, issuesService IssuesService, + repoService RepositoriesService, owner string, repo string, trans *i18n.Translations, @@ -72,6 +79,7 @@ func NewGitHubClientWithServices( return &GitHubClient{ prService: prService, issuesService: issuesService, + repoService: repoService, owner: owner, repo: repo, trans: trans, @@ -120,10 +128,24 @@ func (ghc *GitHubClient) GetPR(ctx context.Context, prNumber int) (models.PRData } } - diff, _, err := ghc.prService.GetRaw(ctx, ghc.owner, ghc.repo, prNumber, github.RawOptions{Type: github.Diff}) + diff, resp, err := ghc.prService.GetRaw(ctx, ghc.owner, ghc.repo, prNumber, github.RawOptions{Type: github.Diff}) if err != nil { - return models.PRData{}, fmt.Errorf("%s: %w", ghc.trans.GetMessage("error.get_diff", 0, map[string]interface{}{"pr_number": prNumber}), err) + // Si es error 406 (diff demasiado grande), usar fallback commit por commit + if resp != nil && resp.StatusCode == http.StatusNotAcceptable { + fmt.Printf("%s\n", ghc.trans.GetMessage("warning.pr_too_large", 0, map[string]interface{}{ + "pr_number": prNumber, + })) + diff, err = ghc.getDiffFromCommits(ctx, commits) + if err != nil { + return models.PRData{}, fmt.Errorf("%s: %w", ghc.trans.GetMessage("error.get_diff_from_commits", 0, map[string]interface{}{ + "pr_number": prNumber, + }), err) + } + } else { + return models.PRData{}, fmt.Errorf("%s: %w", ghc.trans.GetMessage("error.get_diff", 0, map[string]interface{}{"pr_number": prNumber}), err) + } } + return models.PRData{ ID: prNumber, Creator: pr.GetUser().GetLogin(), @@ -132,6 +154,44 @@ func (ghc *GitHubClient) GetPR(ctx context.Context, prNumber int) (models.PRData }, nil } +// getDiffFromCommits obtiene el diff combinado de todos los commits cuando el diff completo del PR es demasiado grande +func (ghc *GitHubClient) getDiffFromCommits(ctx context.Context, commits []*github.RepositoryCommit) (string, error) { + var combinedDiff strings.Builder + + fmt.Printf("%s\n", ghc.trans.GetMessage("info.fetching_commit_diffs", 0, map[string]interface{}{ + "total": len(commits), + })) + + for i, commit := range commits { + sha := commit.GetSHA() + fmt.Printf("%s (%d/%d)\n", ghc.trans.GetMessage("info.processing_commit", 0, map[string]interface{}{ + "current": i + 1, + "total": len(commits), + "sha": sha[:8], + }), i+1, len(commits)) + + fullCommit, _, err := ghc.repoService.GetCommit(ctx, ghc.owner, ghc.repo, sha) + if err != nil { + return "", fmt.Errorf("%s %s: %w", ghc.trans.GetMessage("error.get_commit_diff", 0, nil), sha[:8], err) + } + + if fullCommit.GetStats().GetTotal() > 0 { + combinedDiff.WriteString(fmt.Sprintf("\n# Commit: %s\n", sha[:8])) + combinedDiff.WriteString(fmt.Sprintf("# Message: %s\n\n", strings.Split(commit.GetCommit().GetMessage(), "\n")[0])) + + for _, file := range fullCommit.Files { + if file.Patch != nil { + combinedDiff.WriteString(fmt.Sprintf("diff --git a/%s b/%s\n", file.GetFilename(), file.GetFilename())) + combinedDiff.WriteString(*file.Patch) + combinedDiff.WriteString("\n") + } + } + } + } + + return combinedDiff.String(), nil +} + func (ghc *GitHubClient) validateAndFilterLabels(labels []string) []string { var validLabels []string for _, label := range labels { diff --git a/internal/infrastructure/vcs/github/github_service_test.go b/internal/infrastructure/vcs/github/github_service_test.go index 9411631..050aca8 100644 --- a/internal/infrastructure/vcs/github/github_service_test.go +++ b/internal/infrastructure/vcs/github/github_service_test.go @@ -14,9 +14,11 @@ import ( func newTestClient(pr *MockPRService, issues *MockIssuesService) *GitHubClient { trans, _ := i18n.NewTranslations("es", "../../../i18n/locales/") + repo := &MockRepoService{} return NewGitHubClientWithServices( pr, issues, + repo, "test-owner", "test-repo", trans, @@ -310,11 +312,11 @@ func TestGitHubClient_GetPR_ErrorCases(t *testing.T) { client := newTestClient(mockPR, mockIssues) mockPR.On("Get", mock.Anything, "test-owner", "test-repo", 123). - Return(&github.PullRequest{}, &github.Response{}, nil) + Return(&github.PullRequest{User: &github.User{Login: github.String("test-user")}}, &github.Response{}, nil) mockPR.On("ListCommits", mock.Anything, "test-owner", "test-repo", 123, mock.Anything). Return([]*github.RepositoryCommit{}, &github.Response{}, nil) mockPR.On("GetRaw", mock.Anything, "test-owner", "test-repo", 123, mock.Anything). - Return("", &github.Response{}, assert.AnError) + Return("", nil, assert.AnError) _, err := client.GetPR(context.Background(), 123) assert.ErrorContains(t, err, client.trans.GetMessage("error.get_diff", 0, map[string]interface{}{"pr_number": 123})) diff --git a/internal/infrastructure/vcs/github/mocks.go b/internal/infrastructure/vcs/github/mocks.go index 8d71fa7..c1d127d 100644 --- a/internal/infrastructure/vcs/github/mocks.go +++ b/internal/infrastructure/vcs/github/mocks.go @@ -28,6 +28,9 @@ func (m *MockPRService) ListCommits(ctx context.Context, owner, repo string, num func (m *MockPRService) GetRaw(ctx context.Context, owner, repo string, number int, opts github.RawOptions) (string, *github.Response, error) { args := m.Called(ctx, owner, repo, number, opts) + if args.Get(1) == nil { + return args.String(0), nil, args.Error(2) + } return args.String(0), args.Get(1).(*github.Response), args.Error(2) } @@ -49,3 +52,12 @@ func (m *MockIssuesService) AddLabelsToIssue(ctx context.Context, owner, repo st args := m.Called(ctx, owner, repo, number, labels) return args.Get(0).([]*github.Label), args.Get(1).(*github.Response), args.Error(2) } + +type MockRepoService struct { + mock.Mock +} + +func (m *MockRepoService) GetCommit(ctx context.Context, owner, repo, sha string) (*github.RepositoryCommit, *github.Response, error) { + args := m.Called(ctx, owner, repo, sha) + return args.Get(0).(*github.RepositoryCommit), args.Get(1).(*github.Response), args.Error(2) +} From 4dcc6d10d2a4127bf8a9f4dd5db459647cb4172e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Thu, 4 Dec 2025 13:53:58 -0300 Subject: [PATCH 2/6] feat: Add i18n messages for large PR diff processing --- internal/i18n/i18n.go | 7 +++---- internal/i18n/locales/active.en.toml | 9 +++++++++ internal/i18n/locales/active.es.toml | 6 +++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go index 01c5ac7..3cd6ed4 100644 --- a/internal/i18n/i18n.go +++ b/internal/i18n/i18n.go @@ -3,11 +3,12 @@ package i18n import ( "embed" "fmt" + "os" + "path/filepath" + "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" - "os" - "path/filepath" ) //go:embed locales/* @@ -26,7 +27,6 @@ func NewTranslations(defaultLang string, localesPath string) (*Translations, err var files []os.DirEntry var err error - // Si localesPath está vacío, usamos el sistema embebido if localesPath == "" { files, err = readEmbeddedLocales() } else { @@ -40,7 +40,6 @@ func NewTranslations(defaultLang string, localesPath string) (*Translations, err bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) - // Cargar archivos de traducción for _, file := range files { var data []byte if localesPath == "" { diff --git a/internal/i18n/locales/active.en.toml b/internal/i18n/locales/active.en.toml index 14f8528..90cc2ac 100644 --- a/internal/i18n/locales/active.en.toml +++ b/internal/i18n/locales/active.en.toml @@ -212,6 +212,8 @@ update_pr = "Error updating PR #{{.pr_number}}" get_pr = "Error getting PR #{{.pr_number}}" get_commits = "Error getting commits for PR #{{.pr_number}}" get_diff = "Error getting diff for PR #{{.pr_number}}" +get_diff_from_commits = "Error obtaining the diff by commits of the PR #{{.pr_number}}" +get_commit_diff = "Error getting the commit diff" get_repo_labels = "Error getting repository labels" add_labels = "Error adding labels to PR #{{.pr_number}}" invalid_repo_format = "Invalid repository format" @@ -220,6 +222,13 @@ no_repo_configured = "No repository configured. Use --repo or configure an activ vcs_provider_not_configured = "VCS provider '{{.Provider}}' is not configured" vcs_provider_auto_detected_not_configured = "VCS provider '%s' auto-detected but not configured. Use 'matecommit config set-vcs --provider %s --token ' to configure it" +[warning] +pr_too_large = "The PR #{{.pr_number}} is too large (>20,000 lines). We're getting diffs commit by commit..." + +[info] +fetching_commit_diffs = "Obtaining diffs from {{.total}} commits..." +processing_commit = "Processing commit {{.sha}}" + [label] feature = "New features" fix = "Bug fixes" diff --git a/internal/i18n/locales/active.es.toml b/internal/i18n/locales/active.es.toml index c289b04..3819d04 100644 --- a/internal/i18n/locales/active.es.toml +++ b/internal/i18n/locales/active.es.toml @@ -231,11 +231,11 @@ vcs_provider_not_configured = "El proveedor VCS '{{.Provider}}' no está configu vcs_provider_auto_detected_not_configured = "Proveedor de VCS '%s' detectado automáticamente pero no configurado. Use 'matecommit config set-vcs --provider %s --token ' para configurarlo" [warning] -pr_too_large = "⚠️ El PR #{{.pr_number}} es demasiado grande (>20,000 líneas). Obteniendo diff commit por commit..." +pr_too_large = "El PR #{{.pr_number}} es demasiado grande (>20,000 líneas). Obteniendo diff commit por commit..." [info] -fetching_commit_diffs = "📥 Obteniendo diffs de {{.total}} commits..." -processing_commit = "🔄 Procesando commit {{.sha}}" +fetching_commit_diffs = "Obteniendo diffs de {{.total}} commits..." +processing_commit = "Procesando commit {{.sha}}" [label] feature = "Nuevas funcionalidades" From 490766fb98d3387585f15391069c3102318dc4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Thu, 4 Dec 2025 13:54:35 -0300 Subject: [PATCH 3/6] chore: Remove extraneous blank lines in config files --- internal/config/config.go | 2 +- internal/config/config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 1259cae..4b6cdf3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,7 +35,7 @@ type ( ActiveAI AI `json:"active_ai"` Models map[AI]Model `json:"models"` } - + VCSConfig struct { Provider string `json:"provider"` // github o gitlab lo que se te cante Token string `json:"token,omitempty"` diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 01139f2..d6d947a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -404,7 +404,7 @@ func TestValidateConfig(t *testing.T) { }, wantErr: false, }, - + { name: "DefaultLang vacío", config: &Config{ From a6ae8298a9e33ac3ad66076111c7f9c0e4806845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Thu, 4 Dec 2025 13:54:46 -0300 Subject: [PATCH 4/6] fix: Refine git add precision and execution context --- internal/infrastructure/git/git_service.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/infrastructure/git/git_service.go b/internal/infrastructure/git/git_service.go index 0a1ab1c..634cd65 100644 --- a/internal/infrastructure/git/git_service.go +++ b/internal/infrastructure/git/git_service.go @@ -101,7 +101,13 @@ func (s *GitService) CreateCommit(message string) error { } func (s *GitService) AddFileToStaging(file string) error { - cmd := exec.Command("git", "add", "--all", "--", file) + repoRoot, err := s.getRepoRoot() + if err != nil { + return fmt.Errorf("error al obtener la raíz del repositorio: %v", err) + } + + cmd := exec.Command("git", "add", "--", file) + cmd.Dir = repoRoot var stderr strings.Builder cmd.Stderr = &stderr @@ -111,6 +117,16 @@ func (s *GitService) AddFileToStaging(file string) error { return nil } +// getRepoRoot obtiene la ruta absoluta de la raíz del repositorio git +func (s *GitService) getRepoRoot() (string, error) { + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("error al obtener la raíz del repositorio: %v", err) + } + return strings.TrimSpace(string(output)), nil +} + func (s *GitService) GetCurrentBranch() (string, error) { cmd := exec.Command("git", "branch", "--show-current") output, err := cmd.Output() From f76c454ce0843fe26a90983126eff200cf588a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Tue, 9 Dec 2025 12:29:25 -0300 Subject: [PATCH 5/6] refactor: Mejorar robustez de manejo de errores y test 403 en GitHub --- .../vcs/github/github_service.go | 14 +++++++-- .../vcs/github/github_service_test.go | 31 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/internal/infrastructure/vcs/github/github_service.go b/internal/infrastructure/vcs/github/github_service.go index 9c42ef3..d492511 100644 --- a/internal/infrastructure/vcs/github/github_service.go +++ b/internal/infrastructure/vcs/github/github_service.go @@ -92,8 +92,18 @@ func (ghc *GitHubClient) UpdatePR(ctx context.Context, prNumber int, summary mod Body: github.String(summary.Body), } - _, _, err := ghc.prService.Edit(ctx, ghc.owner, ghc.repo, prNumber, pr) + _, resp, err := ghc.prService.Edit(ctx, ghc.owner, ghc.repo, prNumber, pr) if err != nil { + // Detectar error 403 de permisos insuficientes + if resp != nil && resp.Response != nil && resp.Response.StatusCode == http.StatusForbidden { + return fmt.Errorf("%s\n\n%s", + ghc.trans.GetMessage("error.insufficient_permissions", 0, map[string]interface{}{ + "pr_number": prNumber, + "owner": ghc.owner, + "repo": ghc.repo, + }), + ghc.trans.GetMessage("error.token_scopes_help", 0, nil)) + } return fmt.Errorf("%s: %w", ghc.trans.GetMessage("error.update_pr", 0, map[string]interface{}{ "pr_number": prNumber, }), err) @@ -131,7 +141,7 @@ func (ghc *GitHubClient) GetPR(ctx context.Context, prNumber int) (models.PRData diff, resp, err := ghc.prService.GetRaw(ctx, ghc.owner, ghc.repo, prNumber, github.RawOptions{Type: github.Diff}) if err != nil { // Si es error 406 (diff demasiado grande), usar fallback commit por commit - if resp != nil && resp.StatusCode == http.StatusNotAcceptable { + if resp != nil && resp.Response != nil && resp.Response.StatusCode == http.StatusNotAcceptable { fmt.Printf("%s\n", ghc.trans.GetMessage("warning.pr_too_large", 0, map[string]interface{}{ "pr_number": prNumber, })) diff --git a/internal/infrastructure/vcs/github/github_service_test.go b/internal/infrastructure/vcs/github/github_service_test.go index 050aca8..89d8094 100644 --- a/internal/infrastructure/vcs/github/github_service_test.go +++ b/internal/infrastructure/vcs/github/github_service_test.go @@ -2,6 +2,7 @@ package github import ( "context" + "net/http" "testing" "github.com/Tomas-vilte/MateCommit/internal/domain/models" @@ -230,6 +231,36 @@ func TestGitHubClient_UpdatePR_ErrorCases(t *testing.T) { assert.ErrorContains(t, err, client.trans.GetMessage("error.add_labels", 0, map[string]interface{}{"pr_number": prNumber})) mockIssues.AssertExpectations(t) }) + + t.Run("should return helpful error message for 403 insufficient permissions", func(t *testing.T) { + mockPR := &MockPRService{} + mockIssues := &MockIssuesService{} + client := newTestClient(mockPR, mockIssues) + + prNumber := 123 + summary := models.PRSummary{Title: "Title", Body: "Body"} + + // Simular un error 403 + resp403 := &github.Response{ + Response: &http.Response{ + StatusCode: http.StatusForbidden, + }, + } + + mockPR.On("Edit", mock.Anything, "test-owner", "test-repo", prNumber, mock.Anything). + Return(&github.PullRequest{}, resp403, assert.AnError) + + err := client.UpdatePR(context.Background(), prNumber, summary) + + assert.Error(t, err) + assert.ErrorContains(t, err, client.trans.GetMessage("error.insufficient_permissions", 0, map[string]interface{}{ + "pr_number": prNumber, + "owner": "test-owner", + "repo": "test-repo", + })) + assert.ErrorContains(t, err, client.trans.GetMessage("error.token_scopes_help", 0, nil)) + mockPR.AssertExpectations(t) + }) } func TestGitHubClient_AddLabelsToPR_ErrorCases(t *testing.T) { From 41a5962403cc9dc4c259466e75da4f5e9ac4aac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8thomas=C2=A8?= Date: Tue, 9 Dec 2025 12:33:10 -0300 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20A=C3=B1adir=20mensajes=20i18n=20par?= =?UTF-8?q?a=20errores=20de=20permisos=20de=20token=20de=20GitHub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/i18n/locales/active.en.toml | 14 ++++++++++++++ internal/i18n/locales/active.es.toml | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/internal/i18n/locales/active.en.toml b/internal/i18n/locales/active.en.toml index 90cc2ac..059f55d 100644 --- a/internal/i18n/locales/active.en.toml +++ b/internal/i18n/locales/active.en.toml @@ -215,6 +215,20 @@ get_diff = "Error getting diff for PR #{{.pr_number}}" get_diff_from_commits = "Error obtaining the diff by commits of the PR #{{.pr_number}}" get_commit_diff = "Error getting the commit diff" get_repo_labels = "Error getting repository labels" +insufficient_permissions = "❌ Permissions error while updating PR #{{.pr_number}} in {{.owner}}/{{.repo}}" +token_scopes_help = """💡 Solution: Your GitHub token needs additional permissions + +To update PRs in personal repositories, your token needs these scopes: + • 'repo' (full access) - for private repositories + • 'public_repo' - for public repositories + +📝 How to update your token: + 1. Go to: https://github.com/settings/tokens + 2. Generate a new token (classic) or edit the existing one + 3. Mark the scope 'repo' or 'public_repo' + 4. Update your configuration with: matecommit config set-vcs --provider github --token + +Note: Organization tokens may have different permission requirements. add_labels = "Error adding labels to PR #{{.pr_number}}" invalid_repo_format = "Invalid repository format" pr_summary_error = "Error generating PR summary" diff --git a/internal/i18n/locales/active.es.toml b/internal/i18n/locales/active.es.toml index 3819d04..14d37df 100644 --- a/internal/i18n/locales/active.es.toml +++ b/internal/i18n/locales/active.es.toml @@ -223,6 +223,20 @@ get_diff = "Error al obtener el diff del PR #{{.pr_number}}" get_diff_from_commits = "Error al obtener el diff por commits del PR #{{.pr_number}}" get_commit_diff = "Error al obtener el diff del commit" get_repo_labels = "Error al obtener las etiquetas del repo" +insufficient_permissions = "❌ Error de permisos al actualizar el PR #{{.pr_number}} en {{.owner}}/{{.repo}}" +token_scopes_help = """💡 Solución: Tu token de GitHub necesita permisos adicionales + +Para actualizar PRs en repositorios personales, tu token necesita estos scopes: + • 'repo' (acceso completo) - para repositorios privados + • 'public_repo' - para repositorios públicos + +📝 Cómo actualizar tu token: + 1. Ve a: https://github.com/settings/tokens + 2. Genera un nuevo token (classic) o edita el existente + 3. Marca el scope 'repo' o 'public_repo' + 4. Actualiza tu configuración con: matecommit config set-vcs --provider github --token + +Nota: Los tokens de organizaciones pueden tener diferentes requisitos de permisos.""" add_labels = "Error al añadir las etiquetas al repo #{{.pr_number}}" invalid_repo_format = "Formato de repositorio inválido" pr_summary_error = "Error generando el resumen del PR"