@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,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
|
+
}
|