@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.
- package/README.md +40 -0
- package/package.json +29 -0
- package/src/index.js +147 -0
- package/src/prompts/index.js +870 -0
- package/src/providers/providerManager.js +317 -0
- package/src/resources/index.js +276 -0
- package/src/tools/git-branches.js +126 -0
- package/src/tools/git-clone.js +137 -0
- package/src/tools/git-config.js +94 -0
- package/src/tools/git-diff.js +137 -0
- package/src/tools/git-files.js +82 -0
- package/src/tools/git-help.js +284 -0
- package/src/tools/git-history.js +90 -0
- package/src/tools/git-ignore.js +98 -0
- package/src/tools/git-issues.js +101 -0
- package/src/tools/git-merge.js +152 -0
- package/src/tools/git-pulls.js +115 -0
- package/src/tools/git-remote.js +492 -0
- package/src/tools/git-reset.js +105 -0
- package/src/tools/git-stash.js +120 -0
- package/src/tools/git-sync.js +129 -0
- package/src/tools/git-tags.js +113 -0
- package/src/tools/git-workflow.js +443 -0
- package/src/utils/env.js +104 -0
- package/src/utils/errors.js +431 -0
- package/src/utils/gitAdapter.js +996 -0
- package/src/utils/hooks.js +255 -0
- package/src/utils/metrics.js +198 -0
- package/src/utils/providerExec.js +61 -0
- package/src/utils/repoHelpers.js +216 -0
- package/src/utils/retry.js +123 -0
|
@@ -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
|
+
}
|