@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,120 @@
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 createGitStashTool(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", "save", "apply", "pop", "drop", "clear"],
18
+ description: `Ação a executar:
19
+ - list: Lista todos os stashes salvos
20
+ - save: Salva mudanças atuais no stash (limpa working directory)
21
+ - apply: Aplica stash sem removê-lo da lista
22
+ - pop: Aplica stash E remove da lista (mais comum)
23
+ - drop: Remove stash específico da lista
24
+ - clear: Remove TODOS os stashes`
25
+ },
26
+ message: {
27
+ type: "string",
28
+ description: "Mensagem descritiva para o stash (action='save'). Ex: 'WIP: implementando feature X'"
29
+ },
30
+ ref: {
31
+ type: "string",
32
+ description: "Referência do stash para apply/pop/drop. Ex: 'stash@{0}' (mais recente), 'stash@{1}' (segundo)"
33
+ },
34
+ includeUntracked: {
35
+ type: "boolean",
36
+ description: "Incluir arquivos não rastreados no stash (action='save'). Default: false"
37
+ }
38
+ },
39
+ required: ["projectPath", "action"],
40
+ additionalProperties: false
41
+ };
42
+
43
+ const description = `Gerenciamento de stash Git - salva mudanças temporariamente.
44
+
45
+ QUANDO USAR STASH:
46
+ - Precisa trocar de branch mas tem mudanças não commitadas
47
+ - Quer salvar trabalho em progresso sem commitar
48
+ - Precisa aplicar hotfix urgente em outra branch
49
+
50
+ FLUXO TÍPICO:
51
+ 1. git-stash save message='WIP: minha feature' → salva mudanças
52
+ 2. (trabalha em outra coisa)
53
+ 3. git-stash pop → restaura mudanças
54
+
55
+ AÇÕES:
56
+ - save: Salva e limpa working directory
57
+ - pop: Restaura e remove do stash (use este na maioria dos casos)
58
+ - apply: Restaura mas mantém no stash (para aplicar em múltiplas branches)
59
+ - list: Ver stashes salvos
60
+ - drop: Remover stash específico
61
+ - clear: Remover todos os stashes`;
62
+
63
+ async function handle(args) {
64
+ const validate = ajv.compile(inputSchema);
65
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
66
+ const { projectPath, action } = args;
67
+ try {
68
+ validateProjectPath(projectPath);
69
+ if (action === "list") {
70
+ const items = await withRetry(() => git.listStash(projectPath), 3, "stash-list");
71
+ return asToolResult({
72
+ stashes: items.map((s, i) => ({ index: i, ref: `stash@{${i}}`, message: s.message, timestamp: s.timestamp })),
73
+ count: items.length,
74
+ message: items.length === 0 ? "Nenhum stash salvo" : undefined
75
+ });
76
+ }
77
+ if (action === "save") {
78
+ const status = await git.status(projectPath);
79
+ if (status.isClean && !args.includeUntracked) {
80
+ return asToolError("NOTHING_TO_STASH", "Working tree limpa, nada para stash", { status });
81
+ }
82
+ await withRetry(() => git.saveStash(projectPath, args.message || "WIP", !!args.includeUntracked), 3, "stash-save");
83
+ return asToolResult({ success: true, message: args.message || "WIP", savedFiles: status.modified.length + status.created.length + status.not_added.length });
84
+ }
85
+ if (action === "apply") {
86
+ const items = await git.listStash(projectPath);
87
+ if (items.length === 0) {
88
+ return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
89
+ }
90
+ await withRetry(() => git.applyStash(projectPath, args.ref || "stash@{0}"), 3, "stash-apply");
91
+ return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash aplicado (ainda está na lista)" });
92
+ }
93
+ if (action === "pop") {
94
+ const items = await git.listStash(projectPath);
95
+ if (items.length === 0) {
96
+ return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
97
+ }
98
+ await withRetry(() => git.popStash(projectPath, args.ref || "stash@{0}"), 3, "stash-pop");
99
+ return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash aplicado e removido da lista" });
100
+ }
101
+ if (action === "drop") {
102
+ const items = await git.listStash(projectPath);
103
+ if (items.length === 0) {
104
+ return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
105
+ }
106
+ await git.dropStash(projectPath, args.ref || "stash@{0}");
107
+ return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash removido" });
108
+ }
109
+ if (action === "clear") {
110
+ await git.clearStash(projectPath);
111
+ return asToolResult({ success: true, message: "Todos os stashes removidos" });
112
+ }
113
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "save", "apply", "pop", "drop", "clear"] });
114
+ } catch (e) {
115
+ return errorToResponse(e);
116
+ }
117
+ }
118
+
119
+ return { name: "git-stash", description, inputSchema, handle };
120
+ }
@@ -0,0 +1,129 @@
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 createGitSyncTool(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: ["pull", "fetch"],
18
+ description: `Ação a executar:
19
+ - fetch: Baixa commits do remote sem aplicar (seguro, apenas atualiza refs remotas)
20
+ - pull: Baixa E aplica commits do remote (pode gerar conflitos)`
21
+ },
22
+ remote: {
23
+ type: "string",
24
+ description: "Nome do remote específico (github, gitea, origin). Se não especificado, tenta todos"
25
+ },
26
+ branch: {
27
+ type: "string",
28
+ description: "Branch para sincronizar. Default: branch atual"
29
+ }
30
+ },
31
+ required: ["projectPath", "action"],
32
+ additionalProperties: false
33
+ };
34
+
35
+ const description = `Sincronização com repositórios remotos (GitHub/Gitea).
36
+
37
+ DIFERENÇA FETCH vs PULL:
38
+ - fetch: Seguro - apenas baixa, não modifica working directory
39
+ - pull: Baixa E aplica - pode gerar conflitos se houver mudanças locais
40
+
41
+ QUANDO USAR:
42
+ - fetch: Para ver se há atualizações sem aplicar
43
+ - pull: Para atualizar seu código local com as mudanças do remote
44
+
45
+ FLUXO RECOMENDADO:
46
+ 1. git-workflow status → verificar se tem mudanças locais
47
+ 2. git-sync fetch → baixar atualizações
48
+ 3. git-sync pull → aplicar atualizações (se não tiver conflitos)
49
+
50
+ NOTA: Se pull falhar com conflito, resolva manualmente e faça commit.`;
51
+
52
+ async function handle(args) {
53
+ const validate = ajv.compile(inputSchema);
54
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
55
+ const { projectPath, action } = args;
56
+ try {
57
+ validateProjectPath(projectPath);
58
+ const branch = args.branch || await git.getCurrentBranch(projectPath);
59
+ if (action === "fetch") {
60
+ const remotes = args.remote ? [args.remote] : ["origin", "github", "gitea"];
61
+ const promises = remotes.map(r => withRetry(
62
+ () => git.fetch(projectPath, r, branch),
63
+ 3,
64
+ `fetch-${r}`
65
+ ).then(() => ({ remote: r, ok: true })).catch(e => ({ remote: r, ok: false, error: String(e?.message || e) })));
66
+ const results = await Promise.all(promises);
67
+ const successful = results.filter(x => x.ok);
68
+ const failed = results.filter(x => !x.ok);
69
+ return asToolResult({
70
+ success: successful.length > 0,
71
+ branch,
72
+ fetched: successful.map(x => x.remote),
73
+ failed: failed.map(x => ({ remote: x.remote, error: x.error })),
74
+ message: successful.length === 0 ? "Nenhum remote alcançado. Use git-remote ensure para configurar." : `Fetch de ${successful.length} remote(s) concluído`
75
+ });
76
+ }
77
+ if (action === "pull") {
78
+ // Auto-stash logic
79
+ const status = await git.status(projectPath);
80
+ let stashed = false;
81
+ if (!status.isClean) {
82
+ console.log("[GitSync] Working directory dirty, creating auto-stash...");
83
+ try {
84
+ await git.saveStash(projectPath, `Auto-stash before pull ${new Date().toISOString()}`, true);
85
+ stashed = true;
86
+ } catch (stashError) {
87
+ return asToolError("STASH_FAILED", "Falha ao criar stash automático antes do pull", { error: stashError.message });
88
+ }
89
+ }
90
+
91
+ const remotes = args.remote ? [args.remote] : ["origin", "github", "gitea"];
92
+ const promises = remotes.map(r => withRetry(
93
+ () => git.pull(projectPath, r, branch),
94
+ 3,
95
+ `pull-${r}`
96
+ ).then(() => ({ remote: r, ok: true })).catch(e => ({ remote: r, ok: false, error: String(e?.message || e) })));
97
+
98
+ const results = await Promise.all(promises);
99
+ const successful = results.filter(x => x.ok);
100
+ const failed = results.filter(x => !x.ok);
101
+
102
+ let stashMessage = "";
103
+ if (stashed) {
104
+ try {
105
+ console.log("[GitSync] Restoring auto-stash...");
106
+ await git.popStash(projectPath);
107
+ stashMessage = " (mudanças locais restauradas)";
108
+ } catch (popError) {
109
+ stashMessage = " (ATENÇÃO: mudanças salvas no stash, mas houve conflito ao restaurar. Use 'git-stash pop' manualmente)";
110
+ }
111
+ }
112
+
113
+ return asToolResult({
114
+ success: successful.length > 0,
115
+ branch,
116
+ pulled: successful.map(x => x.remote),
117
+ failed: failed.map(x => ({ remote: x.remote, error: x.error })),
118
+ message: (successful.length === 0 ? "Nenhum remote alcançado." : `Pull de ${successful.length} remote(s) concluído`) + stashMessage,
119
+ stashed
120
+ });
121
+ }
122
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["pull", "fetch"] });
123
+ } catch (e) {
124
+ return errorToResponse(e);
125
+ }
126
+ }
127
+
128
+ return { name: "git-sync", description, inputSchema, handle };
129
+ }
@@ -0,0 +1,113 @@
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 createGitTagsTool(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", "delete", "push"],
18
+ description: `Ação a executar:
19
+ - list: Lista todas as tags existentes
20
+ - create: Cria nova tag no commit atual ou especificado
21
+ - delete: Remove uma tag local
22
+ - push: Envia tag para GitHub e Gitea`
23
+ },
24
+ tag: {
25
+ type: "string",
26
+ description: "Nome da tag. Use versionamento semântico: v1.0.0, v1.1.0, v2.0.0"
27
+ },
28
+ ref: {
29
+ type: "string",
30
+ description: "Commit/branch para criar tag (opcional, default: HEAD)"
31
+ },
32
+ message: {
33
+ type: "string",
34
+ description: "Mensagem da tag anotada (opcional). Se fornecido, cria tag anotada"
35
+ }
36
+ },
37
+ required: ["projectPath", "action"],
38
+ additionalProperties: false
39
+ };
40
+
41
+ const description = `Gerenciamento de tags Git para versionamento.
42
+
43
+ AÇÕES DISPONÍVEIS:
44
+ - list: Ver todas as tags existentes
45
+ - create: Criar nova tag de versão
46
+ - delete: Remover tag local
47
+ - push: Enviar tag para GitHub e Gitea
48
+
49
+ VERSIONAMENTO SEMÂNTICO:
50
+ - v1.0.0: Versão inicial
51
+ - v1.1.0: Nova funcionalidade (minor)
52
+ - v1.0.1: Correção de bug (patch)
53
+ - v2.0.0: Breaking change (major)
54
+
55
+ FLUXO TÍPICO:
56
+ 1. git-tags create tag='v1.0.0' message='Release inicial'
57
+ 2. git-tags push tag='v1.0.0'`;
58
+
59
+ async function handle(args) {
60
+ const validate = ajv.compile(inputSchema);
61
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
62
+ const { projectPath, action } = args;
63
+ try {
64
+ validateProjectPath(projectPath);
65
+ if (action === "list") {
66
+ const tags = await git.listTags(projectPath);
67
+ return asToolResult({ tags, count: tags.length });
68
+ }
69
+ if (action === "create") {
70
+ if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
71
+ const existing = await git.listTags(projectPath);
72
+ if (existing.includes(args.tag)) {
73
+ return asToolError("TAG_ALREADY_EXISTS", `Tag '${args.tag}' já existe`, { tag: args.tag, existingTags: existing });
74
+ }
75
+ await withRetry(() => git.createTag(projectPath, args.tag, args.ref || "HEAD", args.message), 3, "create-tag");
76
+ return asToolResult({ success: true, tag: args.tag, ref: args.ref || "HEAD", message: `Tag '${args.tag}' criada. Use action='push' para enviar ao GitHub/Gitea.` });
77
+ }
78
+ if (action === "delete") {
79
+ if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
80
+ const existing = await git.listTags(projectPath);
81
+ if (!existing.includes(args.tag)) {
82
+ return asToolError("TAG_NOT_FOUND", `Tag '${args.tag}' não encontrada`, { tag: args.tag, existingTags: existing });
83
+ }
84
+ await withRetry(() => git.deleteTag(projectPath, args.tag), 3, "delete-tag");
85
+ return asToolResult({ success: true, tag: args.tag, deleted: true });
86
+ }
87
+ if (action === "push") {
88
+ if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
89
+ const existing = await git.listTags(projectPath);
90
+ if (!existing.includes(args.tag)) {
91
+ return asToolError("TAG_NOT_FOUND", `Tag '${args.tag}' não existe localmente. Crie primeiro com action='create'`, { tag: args.tag, existingTags: existing });
92
+ }
93
+ const results = await Promise.allSettled([
94
+ withRetry(() => git.pushTag(projectPath, "github", args.tag), 3, "push-tag-github"),
95
+ withRetry(() => git.pushTag(projectPath, "gitea", args.tag), 3, "push-tag-gitea")
96
+ ]);
97
+ const pushed = [];
98
+ const failed = [];
99
+ if (results[0].status === "fulfilled") pushed.push("github");
100
+ else failed.push({ remote: "github", error: results[0].reason?.message });
101
+ if (results[1].status === "fulfilled") pushed.push("gitea");
102
+ else failed.push({ remote: "gitea", error: results[1].reason?.message });
103
+
104
+ return asToolResult({ success: pushed.length > 0, tag: args.tag, pushed, failed });
105
+ }
106
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "delete", "push"] });
107
+ } catch (e) {
108
+ return errorToResponse(e);
109
+ }
110
+ }
111
+
112
+ return { name: "git-tags", description, inputSchema, handle };
113
+ }