@andre.buzeli/git-mcp 15.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ import { asToolResult } from "../utils/errors.js";
2
+
3
+ /**
4
+ * Tool de ajuda para AI Agents - fornece orientação sobre qual tool usar
5
+ */
6
+ export function createGitHelpTool() {
7
+ const inputSchema = {
8
+ type: "object",
9
+ properties: {
10
+ query: {
11
+ type: "string",
12
+ description: "O que você quer fazer? Ex: 'criar repositório', 'fazer commit', 'criar branch'"
13
+ },
14
+ listTools: {
15
+ type: "boolean",
16
+ description: "Se true, lista todas as tools disponíveis com descrição resumida"
17
+ },
18
+ showFlows: {
19
+ type: "boolean",
20
+ description: "Se true, mostra fluxos de trabalho comuns"
21
+ }
22
+ },
23
+ additionalProperties: false
24
+ };
25
+
26
+ const TOOLS_SUMMARY = {
27
+ "git-workflow": {
28
+ purpose: "Operações básicas do dia-a-dia: init, status, add, commit, push, update (⭐ RECOMENDADO)",
29
+ actions: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean", "update"],
30
+ useWhen: "Salvar e enviar mudanças. Use action='update' para fluxo automatizado completo (status → add → commit → push)"
31
+ },
32
+ "git-branches": {
33
+ purpose: "Gerenciar branches: criar, listar, trocar, deletar",
34
+ actions: ["list", "create", "checkout", "delete", "rename"],
35
+ useWhen: "Trabalhar em features isoladas, organizar desenvolvimento"
36
+ },
37
+ "git-merge": {
38
+ purpose: "Juntar branches após desenvolvimento",
39
+ actions: ["merge", "abort", "status"],
40
+ useWhen: "Integrar feature branch na main/develop"
41
+ },
42
+ "git-stash": {
43
+ purpose: "Salvar mudanças temporariamente sem commit",
44
+ actions: ["list", "save", "apply", "pop", "drop", "clear"],
45
+ useWhen: "Trocar de branch com mudanças não commitadas"
46
+ },
47
+ "git-tags": {
48
+ purpose: "Marcar versões/releases",
49
+ actions: ["list", "create", "delete", "push"],
50
+ useWhen: "Publicar versão (v1.0.0, v2.0.0)"
51
+ },
52
+ "git-history": {
53
+ purpose: "Ver histórico de commits",
54
+ actions: ["log"],
55
+ useWhen: "Encontrar commit específico, ver quem alterou o quê"
56
+ },
57
+ "git-diff": {
58
+ purpose: "Ver diferenças entre versões",
59
+ actions: ["show", "compare", "stat"],
60
+ useWhen: "Revisar mudanças antes de commit, comparar branches"
61
+ },
62
+ "git-reset": {
63
+ purpose: "Desfazer commits ou mudanças",
64
+ actions: ["soft", "mixed", "hard", "hard-clean"],
65
+ useWhen: "Desfazer último commit, limpar working directory"
66
+ },
67
+ "git-config": {
68
+ purpose: "Configurar Git (nome, email, etc)",
69
+ actions: ["get", "set", "unset", "list"],
70
+ useWhen: "Configurar autor dos commits"
71
+ },
72
+ "git-remote": {
73
+ purpose: "Operações em GitHub/Gitea",
74
+ actions: ["list", "ensure", "release-create", "topics-set", "label-create"],
75
+ useWhen: "Criar release, configurar repositório remoto"
76
+ },
77
+ "git-sync": {
78
+ purpose: "Sincronizar com remotes",
79
+ actions: ["fetch", "pull"],
80
+ useWhen: "Baixar mudanças de outros desenvolvedores"
81
+ },
82
+ "git-clone": {
83
+ purpose: "Clonar repositório existente",
84
+ actions: ["clone"],
85
+ useWhen: "Baixar projeto existente do GitHub/Gitea"
86
+ },
87
+ "git-ignore": {
88
+ purpose: "Gerenciar .gitignore",
89
+ actions: ["list", "create", "add", "remove"],
90
+ useWhen: "Ignorar arquivos (node_modules, .env, etc)"
91
+ },
92
+ "git-files": {
93
+ purpose: "Ler arquivos do histórico Git",
94
+ actions: ["list", "read"],
95
+ useWhen: "Ver arquivo em versão antiga"
96
+ },
97
+ "git-issues": {
98
+ purpose: "Gerenciar issues no GitHub/Gitea",
99
+ actions: ["create", "list", "comment"],
100
+ useWhen: "Reportar bugs, criar tarefas"
101
+ },
102
+ "git-pulls": {
103
+ purpose: "Gerenciar Pull Requests",
104
+ actions: ["create", "list", "files"],
105
+ useWhen: "Propor mudanças para review"
106
+ }
107
+ };
108
+
109
+ const COMMON_FLOWS = {
110
+ "novo-projeto": {
111
+ description: "Criar repositório do zero",
112
+ steps: [
113
+ { tool: "git-workflow", action: "init", note: "Inicializa git e cria repos no GitHub/Gitea" },
114
+ { tool: "git-workflow", action: "add", params: { files: ["."] } },
115
+ { tool: "git-workflow", action: "commit", params: { message: "Initial commit" } },
116
+ { tool: "git-workflow", action: "push" }
117
+ ]
118
+ },
119
+ "salvar-mudancas": {
120
+ description: "Salvar e enviar mudanças (fluxo automatizado)",
121
+ steps: [
122
+ { tool: "git-workflow", action: "update", params: { message: "sua mensagem" }, note: "⭐ Executa status, add, commit e push automaticamente" }
123
+ ]
124
+ },
125
+ "nova-feature": {
126
+ description: "Desenvolver feature em branch separada",
127
+ steps: [
128
+ { tool: "git-branches", action: "create", params: { branch: "feature/nome" } },
129
+ { tool: "git-branches", action: "checkout", params: { branch: "feature/nome" } },
130
+ { note: "... fazer mudanças ..." },
131
+ { tool: "git-workflow", action: "add" },
132
+ { tool: "git-workflow", action: "commit" },
133
+ { tool: "git-branches", action: "checkout", params: { branch: "main" } },
134
+ { tool: "git-merge", action: "merge", params: { branch: "feature/nome" } },
135
+ { tool: "git-workflow", action: "push" }
136
+ ]
137
+ },
138
+ "criar-release": {
139
+ description: "Publicar nova versão",
140
+ steps: [
141
+ { tool: "git-tags", action: "create", params: { tag: "v1.0.0", message: "Release v1.0.0" } },
142
+ { tool: "git-tags", action: "push", params: { tag: "v1.0.0" } },
143
+ { tool: "git-remote", action: "release-create", params: { tag: "v1.0.0", name: "v1.0.0" } }
144
+ ]
145
+ },
146
+ "desfazer-commit": {
147
+ description: "Desfazer último commit mantendo mudanças",
148
+ steps: [
149
+ { tool: "git-reset", action: "soft", params: { ref: "HEAD~1" } }
150
+ ]
151
+ },
152
+ "trocar-branch-com-mudancas": {
153
+ description: "Salvar mudanças e trocar de branch",
154
+ steps: [
155
+ { tool: "git-stash", action: "save", params: { message: "WIP" } },
156
+ { tool: "git-branches", action: "checkout", params: { branch: "outra-branch" } },
157
+ { note: "... fazer o que precisar ..." },
158
+ { tool: "git-branches", action: "checkout", params: { branch: "branch-original" } },
159
+ { tool: "git-stash", action: "pop" }
160
+ ]
161
+ }
162
+ };
163
+
164
+ // Mapeamento de intenções para tools
165
+ const INTENT_MAP = {
166
+ // Palavras-chave -> tool recomendada
167
+ "criar reposit": "git-workflow (action=init)",
168
+ "iniciar projeto": "git-workflow (action=init)",
169
+ "novo projeto": "git-workflow (action=init)",
170
+ "commit": "git-workflow (action=commit)",
171
+ "salvar": "git-workflow (action=update) - ⭐ fluxo completo automatizado",
172
+ "atualizar": "git-workflow (action=update) - ⭐ fluxo completo automatizado",
173
+ "update": "git-workflow (action=update) - ⭐ fluxo completo automatizado",
174
+ "push": "git-workflow (action=push)",
175
+ "enviar": "git-workflow (action=push)",
176
+ "branch": "git-branches",
177
+ "criar branch": "git-branches (action=create)",
178
+ "trocar branch": "git-branches (action=checkout)",
179
+ "merge": "git-merge",
180
+ "juntar": "git-merge",
181
+ "stash": "git-stash",
182
+ "guardar": "git-stash (action=save)",
183
+ "tag": "git-tags",
184
+ "versão": "git-tags",
185
+ "release": "git-remote (action=release-create)",
186
+ "histórico": "git-history",
187
+ "log": "git-history",
188
+ "diff": "git-diff",
189
+ "diferença": "git-diff",
190
+ "desfazer": "git-reset",
191
+ "reset": "git-reset",
192
+ "clone": "git-clone",
193
+ "clonar": "git-clone",
194
+ "baixar projeto": "git-clone",
195
+ "issue": "git-issues",
196
+ "bug": "git-issues (action=create)",
197
+ "pull request": "git-pulls",
198
+ "pr": "git-pulls",
199
+ "ignorar": "git-ignore",
200
+ "gitignore": "git-ignore",
201
+ "config": "git-config",
202
+ "configurar": "git-config",
203
+ "status": "git-workflow (action=status)",
204
+ "ver mudanças": "git-workflow (action=status)"
205
+ };
206
+
207
+ const description = `Ajuda para AI Agents - descobre qual tool usar para cada situação.
208
+
209
+ USE ESTA TOOL QUANDO:
210
+ - Não sabe qual git-* tool usar
211
+ - Precisa ver fluxos de trabalho comuns
212
+ - Quer lista de todas as tools disponíveis
213
+
214
+ EXEMPLOS:
215
+ • { "query": "como criar um repositório?" }
216
+ • { "listTools": true }
217
+ • { "showFlows": true }`;
218
+
219
+ async function handle(args) {
220
+ const { query, listTools, showFlows } = args || {};
221
+
222
+ // Listar todas as tools
223
+ if (listTools) {
224
+ return asToolResult({
225
+ tools: TOOLS_SUMMARY,
226
+ tip: "Use query='o que você quer fazer' para recomendação específica"
227
+ });
228
+ }
229
+
230
+ // Mostrar fluxos comuns
231
+ if (showFlows) {
232
+ return asToolResult({
233
+ flows: COMMON_FLOWS,
234
+ tip: "Siga os steps em ordem para cada fluxo"
235
+ });
236
+ }
237
+
238
+ // Buscar por query
239
+ if (query) {
240
+ const q = query.toLowerCase();
241
+ const matches = [];
242
+
243
+ for (const [intent, tool] of Object.entries(INTENT_MAP)) {
244
+ if (q.includes(intent) || intent.includes(q)) {
245
+ matches.push({ intent, recommendation: tool });
246
+ }
247
+ }
248
+
249
+ if (matches.length > 0) {
250
+ return asToolResult({
251
+ query,
252
+ recommendations: matches,
253
+ tip: "Use a tool recomendada com os parâmetros indicados"
254
+ });
255
+ }
256
+
257
+ return asToolResult({
258
+ query,
259
+ message: "Não encontrei recomendação específica. Veja todas as tools:",
260
+ tools: Object.keys(TOOLS_SUMMARY),
261
+ tip: "Use listTools=true para ver detalhes de cada tool"
262
+ });
263
+ }
264
+
265
+ // Sem parâmetros - retornar overview
266
+ return asToolResult({
267
+ message: "Git MCP - Tools para AI Agents",
268
+ availableTools: Object.keys(TOOLS_SUMMARY).length,
269
+ usage: {
270
+ "Recomendação": "{ query: 'o que você quer fazer' }",
271
+ "Listar tools": "{ listTools: true }",
272
+ "Ver fluxos": "{ showFlows: true }"
273
+ },
274
+ quickStart: "Para começar um projeto: git-workflow com action='init'"
275
+ });
276
+ }
277
+
278
+ return {
279
+ name: "git-help",
280
+ description,
281
+ inputSchema,
282
+ handle
283
+ };
284
+ }
@@ -0,0 +1,90 @@
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
+
5
+ const ajv = new Ajv({ allErrors: true });
6
+
7
+ export function createGitHistoryTool(git) {
8
+ const inputSchema = {
9
+ type: "object",
10
+ properties: {
11
+ projectPath: {
12
+ type: "string",
13
+ description: "Caminho absoluto do diretório do projeto"
14
+ },
15
+ action: {
16
+ type: "string",
17
+ enum: ["log"],
18
+ description: "Ação a executar: log - exibe histórico de commits"
19
+ },
20
+ ref: {
21
+ type: "string",
22
+ description: "Referência inicial para o log (branch, tag, SHA). Default: HEAD"
23
+ },
24
+ maxCount: {
25
+ type: "number",
26
+ description: "Número máximo de commits a retornar. Default: 50"
27
+ }
28
+ },
29
+ required: ["projectPath", "action"],
30
+ additionalProperties: false
31
+ };
32
+
33
+ const description = `Histórico de commits do repositório Git.
34
+
35
+ QUANDO USAR:
36
+ - Ver commits recentes
37
+ - Encontrar SHA de commit específico para reset/checkout
38
+ - Verificar quem fez qual alteração
39
+ - Ver histórico de uma branch
40
+
41
+ INFORMAÇÕES RETORNADAS:
42
+ - sha: Hash completo do commit
43
+ - shortSha: Hash curto (7 caracteres)
44
+ - message: Mensagem do commit
45
+ - author: Nome do autor
46
+ - email: Email do autor
47
+ - date: Data do commit
48
+
49
+ EXEMPLOS:
50
+ - Ver últimos 10 commits: action='log' maxCount=10
51
+ - Ver histórico de branch: action='log' ref='develop'
52
+ - Ver histórico de tag: action='log' ref='v1.0.0'`;
53
+
54
+ async function handle(args) {
55
+ const validate = ajv.compile(inputSchema);
56
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
57
+ const { projectPath, action } = args;
58
+ try {
59
+ validateProjectPath(projectPath);
60
+ if (action === "log") {
61
+ const items = await withRetry(() => git.log(projectPath, { ref: args.ref || "HEAD", maxCount: args.maxCount || 50 }), 3, "history-log");
62
+ if (items.length === 0) {
63
+ return asToolResult({
64
+ commits: [],
65
+ count: 0,
66
+ message: "Nenhum commit encontrado. Use git-workflow action='commit' para criar o primeiro commit."
67
+ });
68
+ }
69
+ return asToolResult({
70
+ commits: items.map(c => ({
71
+ sha: c.sha,
72
+ shortSha: c.sha.substring(0, 7),
73
+ message: c.message.split("\n")[0],
74
+ fullMessage: c.message,
75
+ author: c.author.name,
76
+ email: c.author.email,
77
+ date: c.date
78
+ })),
79
+ count: items.length,
80
+ ref: args.ref || "HEAD"
81
+ });
82
+ }
83
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["log"] });
84
+ } catch (e) {
85
+ return errorToResponse(e);
86
+ }
87
+ }
88
+
89
+ return { name: "git-history", description, inputSchema, handle };
90
+ }
@@ -0,0 +1,98 @@
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { validateProjectPath } from "../utils/repoHelpers.js";
4
+
5
+ const ajv = new Ajv({ allErrors: true });
6
+
7
+ export function createGitIgnoreTool(git) {
8
+ const inputSchema = {
9
+ type: "object",
10
+ properties: {
11
+ projectPath: {
12
+ type: "string",
13
+ description: "Caminho absoluto do diretório do projeto"
14
+ },
15
+ action: {
16
+ type: "string",
17
+ enum: ["list", "create", "add", "remove"],
18
+ description: `Ação a executar:
19
+ - list: Lista padrões atuais do .gitignore
20
+ - create: Cria novo .gitignore (sobrescreve existente)
21
+ - add: Adiciona padrões ao .gitignore existente
22
+ - remove: Remove padrões do .gitignore`
23
+ },
24
+ patterns: {
25
+ type: "array",
26
+ items: { type: "string" },
27
+ description: "Padrões para ignorar. Ex: ['node_modules/', '*.log', '.env', 'dist/', '*.tmp']"
28
+ }
29
+ },
30
+ required: ["projectPath", "action"],
31
+ additionalProperties: false
32
+ };
33
+
34
+ const description = `Gerenciamento do arquivo .gitignore.
35
+
36
+ PADRÕES COMUNS:
37
+ - node_modules/: Dependências Node.js
38
+ - *.log: Arquivos de log
39
+ - .env: Variáveis de ambiente (segredos)
40
+ - dist/, build/: Arquivos compilados
41
+ - .DS_Store: Arquivos do macOS
42
+ - Thumbs.db: Arquivos do Windows
43
+ - *.tmp, *.bak: Arquivos temporários
44
+ - .idea/, .vscode/: Configurações de IDE
45
+
46
+ SINTAXE:
47
+ - pasta/: Ignora diretório
48
+ - *.ext: Ignora por extensão
49
+ - !arquivo: Exceção (não ignorar)
50
+ - **/pasta: Ignora em qualquer nível
51
+
52
+ EXEMPLOS:
53
+ - Criar para Node.js: action='create' patterns=['node_modules/', '*.log', '.env', 'dist/']
54
+ - Adicionar padrão: action='add' patterns=['*.tmp']`;
55
+
56
+ async function handle(args) {
57
+ const validate = ajv.compile(inputSchema);
58
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
59
+ const { projectPath, action } = args;
60
+ const patterns = Array.isArray(args.patterns) ? args.patterns : [];
61
+ try {
62
+ validateProjectPath(projectPath);
63
+ if (action === "list") {
64
+ const items = await withRetry(() => git.listGitignore(projectPath), 3, "ignore-list");
65
+ return asToolResult({ patterns: items, count: items.length, hasGitignore: items.length > 0 || true });
66
+ }
67
+ if (action === "create") {
68
+ if (patterns.length === 0) {
69
+ return asToolError("MISSING_PARAMETER", "patterns é obrigatório para criar .gitignore", {
70
+ parameter: "patterns",
71
+ suggestion: "Forneça um array de padrões. Ex: ['node_modules/', '*.log', '.env']"
72
+ });
73
+ }
74
+ await withRetry(() => git.createGitignore(projectPath, patterns), 3, "ignore-create");
75
+ return asToolResult({ success: true, patterns, message: ".gitignore criado" });
76
+ }
77
+ if (action === "add") {
78
+ if (patterns.length === 0) {
79
+ return asToolError("MISSING_PARAMETER", "patterns é obrigatório", { parameter: "patterns" });
80
+ }
81
+ await withRetry(() => git.addToGitignore(projectPath, patterns), 3, "ignore-add");
82
+ return asToolResult({ success: true, added: patterns });
83
+ }
84
+ if (action === "remove") {
85
+ if (patterns.length === 0) {
86
+ return asToolError("MISSING_PARAMETER", "patterns é obrigatório", { parameter: "patterns" });
87
+ }
88
+ await withRetry(() => git.removeFromGitignore(projectPath, patterns), 3, "ignore-remove");
89
+ return asToolResult({ success: true, removed: patterns });
90
+ }
91
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "add", "remove"] });
92
+ } catch (e) {
93
+ return errorToResponse(e);
94
+ }
95
+ }
96
+
97
+ return { name: "git-ignore", description, inputSchema, handle };
98
+ }
@@ -0,0 +1,101 @@
1
+ import Ajv from "ajv";
2
+ import axios from "axios";
3
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
4
+ import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
5
+ import { runBoth } from "../utils/providerExec.js";
6
+
7
+ const ajv = new Ajv({ allErrors: true });
8
+
9
+ export function createGitIssuesTool(pm) {
10
+ const inputSchema = {
11
+ type: "object",
12
+ properties: {
13
+ projectPath: {
14
+ type: "string",
15
+ description: "Caminho absoluto do diretório do projeto"
16
+ },
17
+ action: {
18
+ type: "string",
19
+ enum: ["create", "list", "comment"],
20
+ description: `Ação a executar:
21
+ - create: Cria nova issue no GitHub E Gitea
22
+ - list: Lista issues existentes
23
+ - comment: Adiciona comentário a uma issue existente`
24
+ },
25
+ title: {
26
+ type: "string",
27
+ description: "Título da issue (obrigatório para action='create'). Ex: 'Bug: aplicação crasha ao clicar em salvar'"
28
+ },
29
+ body: {
30
+ type: "string",
31
+ description: "Descrição detalhada da issue ou texto do comentário"
32
+ },
33
+ number: {
34
+ type: "number",
35
+ description: "Número da issue para comentar (obrigatório para action='comment')"
36
+ },
37
+ organization: {
38
+ type: "string",
39
+ description: "Opcional. Nome da organização no GitHub/Gitea. Se informado, operações são feitas na org em vez da conta pessoal."
40
+ }
41
+ },
42
+ required: ["projectPath", "action"],
43
+ additionalProperties: false
44
+ };
45
+
46
+ const description = `Gerenciamento de Issues no GitHub e Gitea.
47
+
48
+ QUANDO USAR:
49
+ - Reportar bugs encontrados
50
+ - Sugerir novas funcionalidades
51
+ - Documentar tarefas a fazer
52
+ - Acompanhar progresso do projeto
53
+
54
+ AÇÕES:
55
+ - create: Criar nova issue (executa em AMBOS os providers)
56
+ - list: Ver issues existentes
57
+ - comment: Comentar em issue existente
58
+
59
+ BOAS PRÁTICAS:
60
+ - Use títulos descritivos
61
+ - Inclua passos para reproduzir bugs
62
+ - Use labels para categorizar (via git-remote label-create)`;
63
+
64
+ async function handle(args) {
65
+ const validate = ajv.compile(inputSchema);
66
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
67
+ validateProjectPath(args.projectPath);
68
+ const repo = getRepoNameFromPath(args.projectPath);
69
+ try {
70
+ if (args.action === "create") {
71
+ if (!args.title) return asToolError("MISSING_PARAMETER", "title é obrigatório para criar issue", { parameter: "title" });
72
+ const out = await withRetry(() => runBoth(pm, {
73
+ github: async (owner) => { const r = await pm.github.rest.issues.create({ owner, repo, title: args.title, body: args.body || "" }); return { ok: true, number: r.data.number, url: r.data.html_url }; },
74
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues`, { title: args.title, body: args.body || "" }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, number: r.data?.number }; }
75
+ }, { organization: args.organization }), 3, "issues-create");
76
+ return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), title: args.title, providers: out });
77
+ }
78
+ if (args.action === "list") {
79
+ const out = await withRetry(() => runBoth(pm, {
80
+ github: async (owner) => { const r = await pm.github.rest.issues.listForRepo({ owner, repo, state: "all", per_page: 30 }); return { ok: true, count: r.data.length, issues: r.data.slice(0, 10).map(i => ({ number: i.number, title: i.title, state: i.state })) }; },
81
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${base}/api/v1/repos/${owner}/${repo}/issues?state=all`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data || []).length, issues: (r.data || []).slice(0, 10).map(i => ({ number: i.number, title: i.title, state: i.state })) }; }
82
+ }, { organization: args.organization }), 3, "issues-list");
83
+ return asToolResult({ providers: out });
84
+ }
85
+ if (args.action === "comment") {
86
+ if (!args.number) return asToolError("MISSING_PARAMETER", "number é obrigatório para comentar", { parameter: "number" });
87
+ if (!args.body) return asToolError("MISSING_PARAMETER", "body é obrigatório para comentar", { parameter: "body" });
88
+ const out = await withRetry(() => runBoth(pm, {
89
+ github: async (owner) => { await pm.github.rest.issues.createComment({ owner, repo, issue_number: args.number, body: args.body }); return { ok: true }; },
90
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues/${args.number}/comments`, { body: args.body }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
91
+ }, { organization: args.organization }), 3, "issues-comment");
92
+ return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), issueNumber: args.number, providers: out });
93
+ }
94
+ return asToolError("VALIDATION_ERROR", `Ação '${args.action}' não suportada`, { availableActions: ["create", "list", "comment"] });
95
+ } catch (e) {
96
+ return errorToResponse(e);
97
+ }
98
+ }
99
+
100
+ return { name: "git-issues", description, inputSchema, handle };
101
+ }