@andrebuzeli/git-mcp 13.6.0 → 14.0.0
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/package.json +1 -1
- package/src/index.js +11 -0
- package/src/providers/providerManager.js +24 -0
- package/src/tools/git-branches.js +64 -35
- package/src/tools/git-config.js +51 -12
- package/src/tools/git-files.js +39 -10
- package/src/tools/git-history.js +44 -9
- package/src/tools/git-ignore.js +48 -11
- package/src/tools/git-issues.js +56 -18
- package/src/tools/git-pulls.js +70 -21
- package/src/tools/git-remote.js +129 -60
- package/src/tools/git-reset.js +51 -11
- package/src/tools/git-stash.js +60 -20
- package/src/tools/git-sync.js +41 -9
- package/src/tools/git-tags.js +62 -30
- package/src/tools/git-workflow.js +61 -22
- package/src/utils/gitAdapter.js +11 -0
package/src/tools/git-stash.js
CHANGED
|
@@ -7,16 +7,58 @@ export function createGitStashTool(git) {
|
|
|
7
7
|
const inputSchema = {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
10
|
-
projectPath: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
projectPath: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
13
|
+
},
|
|
14
|
+
action: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["list", "save", "apply", "pop", "drop", "clear"],
|
|
17
|
+
description: `Ação a executar:
|
|
18
|
+
- list: Lista todos os stashes salvos
|
|
19
|
+
- save: Salva mudanças atuais no stash (limpa working directory)
|
|
20
|
+
- apply: Aplica stash sem removê-lo da lista
|
|
21
|
+
- pop: Aplica stash E remove da lista (mais comum)
|
|
22
|
+
- drop: Remove stash específico da lista
|
|
23
|
+
- clear: Remove TODOS os stashes`
|
|
24
|
+
},
|
|
25
|
+
message: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Mensagem descritiva para o stash (action='save'). Ex: 'WIP: implementando feature X'"
|
|
28
|
+
},
|
|
29
|
+
ref: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Referência do stash para apply/pop/drop. Ex: 'stash@{0}' (mais recente), 'stash@{1}' (segundo)"
|
|
32
|
+
},
|
|
33
|
+
includeUntracked: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Incluir arquivos não rastreados no stash (action='save'). Default: false"
|
|
36
|
+
}
|
|
15
37
|
},
|
|
16
38
|
required: ["projectPath", "action"],
|
|
17
|
-
additionalProperties:
|
|
39
|
+
additionalProperties: false
|
|
18
40
|
};
|
|
19
41
|
|
|
42
|
+
const description = `Gerenciamento de stash Git - salva mudanças temporariamente.
|
|
43
|
+
|
|
44
|
+
QUANDO USAR STASH:
|
|
45
|
+
- Precisa trocar de branch mas tem mudanças não commitadas
|
|
46
|
+
- Quer salvar trabalho em progresso sem commitar
|
|
47
|
+
- Precisa aplicar hotfix urgente em outra branch
|
|
48
|
+
|
|
49
|
+
FLUXO TÍPICO:
|
|
50
|
+
1. git-stash save message='WIP: minha feature' → salva mudanças
|
|
51
|
+
2. (trabalha em outra coisa)
|
|
52
|
+
3. git-stash pop → restaura mudanças
|
|
53
|
+
|
|
54
|
+
AÇÕES:
|
|
55
|
+
- save: Salva e limpa working directory
|
|
56
|
+
- pop: Restaura e remove do stash (use este na maioria dos casos)
|
|
57
|
+
- apply: Restaura mas mantém no stash (para aplicar em múltiplas branches)
|
|
58
|
+
- list: Ver stashes salvos
|
|
59
|
+
- drop: Remover stash específico
|
|
60
|
+
- clear: Remover todos os stashes`;
|
|
61
|
+
|
|
20
62
|
async function handle(args) {
|
|
21
63
|
const validate = ajv.compile(inputSchema);
|
|
22
64
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
@@ -25,54 +67,52 @@ export function createGitStashTool(git) {
|
|
|
25
67
|
if (action === "list") {
|
|
26
68
|
const items = await git.listStash(projectPath);
|
|
27
69
|
return asToolResult({
|
|
28
|
-
|
|
29
|
-
count: items.length
|
|
70
|
+
stashes: items.map((s, i) => ({ index: i, ref: `stash@{${i}}`, message: s.message, timestamp: s.timestamp })),
|
|
71
|
+
count: items.length,
|
|
72
|
+
message: items.length === 0 ? "Nenhum stash salvo" : undefined
|
|
30
73
|
});
|
|
31
74
|
}
|
|
32
75
|
if (action === "save") {
|
|
33
|
-
// Auto-verificação: checar se há mudanças
|
|
34
76
|
const status = await git.status(projectPath);
|
|
35
77
|
if (status.isClean && !args.includeUntracked) {
|
|
36
78
|
return asToolError("NOTHING_TO_STASH", "Working tree limpa, nada para stash", { status });
|
|
37
79
|
}
|
|
38
80
|
await git.saveStash(projectPath, args.message || "WIP", !!args.includeUntracked);
|
|
39
|
-
return asToolResult({ success: true, message: args.message || "WIP" });
|
|
81
|
+
return asToolResult({ success: true, message: args.message || "WIP", savedFiles: status.modified.length + status.created.length + status.not_added.length });
|
|
40
82
|
}
|
|
41
83
|
if (action === "apply") {
|
|
42
|
-
// Auto-verificação: checar se stash existe
|
|
43
84
|
const items = await git.listStash(projectPath);
|
|
44
85
|
if (items.length === 0) {
|
|
45
86
|
return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
|
|
46
87
|
}
|
|
47
|
-
await git.applyStash(projectPath, args.ref);
|
|
48
|
-
return asToolResult({ success: true, ref: args.ref || "stash@{0}" });
|
|
88
|
+
await git.applyStash(projectPath, args.ref || "stash@{0}");
|
|
89
|
+
return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash aplicado (ainda está na lista)" });
|
|
49
90
|
}
|
|
50
91
|
if (action === "pop") {
|
|
51
|
-
// Auto-verificação: checar se stash existe
|
|
52
92
|
const items = await git.listStash(projectPath);
|
|
53
93
|
if (items.length === 0) {
|
|
54
94
|
return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
|
|
55
95
|
}
|
|
56
|
-
await git.popStash(projectPath, args.ref);
|
|
57
|
-
return asToolResult({ success: true, ref: args.ref || "stash@{0}" });
|
|
96
|
+
await git.popStash(projectPath, args.ref || "stash@{0}");
|
|
97
|
+
return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash aplicado e removido da lista" });
|
|
58
98
|
}
|
|
59
99
|
if (action === "drop") {
|
|
60
100
|
const items = await git.listStash(projectPath);
|
|
61
101
|
if (items.length === 0) {
|
|
62
102
|
return asToolError("STASH_NOT_FOUND", "Nenhum stash disponível", { availableStashes: [] });
|
|
63
103
|
}
|
|
64
|
-
await git.dropStash(projectPath, args.ref);
|
|
65
|
-
return asToolResult({ success: true, ref: args.ref || "stash@{0}" });
|
|
104
|
+
await git.dropStash(projectPath, args.ref || "stash@{0}");
|
|
105
|
+
return asToolResult({ success: true, ref: args.ref || "stash@{0}", message: "Stash removido" });
|
|
66
106
|
}
|
|
67
107
|
if (action === "clear") {
|
|
68
108
|
await git.clearStash(projectPath);
|
|
69
109
|
return asToolResult({ success: true, message: "Todos os stashes removidos" });
|
|
70
110
|
}
|
|
71
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
111
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "save", "apply", "pop", "drop", "clear"] });
|
|
72
112
|
} catch (e) {
|
|
73
113
|
return errorToResponse(e);
|
|
74
114
|
}
|
|
75
115
|
}
|
|
76
116
|
|
|
77
|
-
return { name: "git-stash", description
|
|
117
|
+
return { name: "git-stash", description, inputSchema, handle };
|
|
78
118
|
}
|
package/src/tools/git-sync.js
CHANGED
|
@@ -7,15 +7,47 @@ export function createGitSyncTool(git) {
|
|
|
7
7
|
const inputSchema = {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
10
|
-
projectPath: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
projectPath: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
13
|
+
},
|
|
14
|
+
action: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["pull", "fetch"],
|
|
17
|
+
description: `Ação a executar:
|
|
18
|
+
- fetch: Baixa commits do remote sem aplicar (seguro, apenas atualiza refs remotas)
|
|
19
|
+
- pull: Baixa E aplica commits do remote (pode gerar conflitos)`
|
|
20
|
+
},
|
|
21
|
+
remote: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Nome do remote específico (github, gitea, origin). Se não especificado, tenta todos"
|
|
24
|
+
},
|
|
25
|
+
branch: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Branch para sincronizar. Default: branch atual"
|
|
28
|
+
}
|
|
14
29
|
},
|
|
15
30
|
required: ["projectPath", "action"],
|
|
16
|
-
additionalProperties:
|
|
31
|
+
additionalProperties: false
|
|
17
32
|
};
|
|
18
33
|
|
|
34
|
+
const description = `Sincronização com repositórios remotos (GitHub/Gitea).
|
|
35
|
+
|
|
36
|
+
DIFERENÇA FETCH vs PULL:
|
|
37
|
+
- fetch: Seguro - apenas baixa, não modifica working directory
|
|
38
|
+
- pull: Baixa E aplica - pode gerar conflitos se houver mudanças locais
|
|
39
|
+
|
|
40
|
+
QUANDO USAR:
|
|
41
|
+
- fetch: Para ver se há atualizações sem aplicar
|
|
42
|
+
- pull: Para atualizar seu código local com as mudanças do remote
|
|
43
|
+
|
|
44
|
+
FLUXO RECOMENDADO:
|
|
45
|
+
1. git-workflow status → verificar se tem mudanças locais
|
|
46
|
+
2. git-sync fetch → baixar atualizações
|
|
47
|
+
3. git-sync pull → aplicar atualizações (se não tiver conflitos)
|
|
48
|
+
|
|
49
|
+
NOTA: Se pull falhar com conflito, resolva manualmente e faça commit.`;
|
|
50
|
+
|
|
19
51
|
async function handle(args) {
|
|
20
52
|
const validate = ajv.compile(inputSchema);
|
|
21
53
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
@@ -33,7 +65,7 @@ export function createGitSyncTool(git) {
|
|
|
33
65
|
branch,
|
|
34
66
|
fetched: successful.map(x => x.remote),
|
|
35
67
|
failed: failed.map(x => ({ remote: x.remote, error: x.error })),
|
|
36
|
-
message: successful.length === 0 ? "Nenhum remote alcançado.
|
|
68
|
+
message: successful.length === 0 ? "Nenhum remote alcançado. Use git-remote ensure para configurar." : `Fetch de ${successful.length} remote(s) concluído`
|
|
37
69
|
});
|
|
38
70
|
}
|
|
39
71
|
if (action === "pull") {
|
|
@@ -47,14 +79,14 @@ export function createGitSyncTool(git) {
|
|
|
47
79
|
branch,
|
|
48
80
|
pulled: successful.map(x => x.remote),
|
|
49
81
|
failed: failed.map(x => ({ remote: x.remote, error: x.error })),
|
|
50
|
-
message: successful.length === 0 ? "Nenhum remote alcançado.
|
|
82
|
+
message: successful.length === 0 ? "Nenhum remote alcançado. Use git-remote ensure para configurar." : `Pull de ${successful.length} remote(s) concluído`
|
|
51
83
|
});
|
|
52
84
|
}
|
|
53
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
85
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["pull", "fetch"] });
|
|
54
86
|
} catch (e) {
|
|
55
87
|
return errorToResponse(e);
|
|
56
88
|
}
|
|
57
89
|
}
|
|
58
90
|
|
|
59
|
-
return { name: "git-sync", description
|
|
91
|
+
return { name: "git-sync", description, inputSchema, handle };
|
|
60
92
|
}
|
package/src/tools/git-tags.js
CHANGED
|
@@ -7,16 +7,54 @@ export function createGitTagsTool(git) {
|
|
|
7
7
|
const inputSchema = {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
10
|
-
projectPath: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
projectPath: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
13
|
+
},
|
|
14
|
+
action: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["list", "create", "delete", "push"],
|
|
17
|
+
description: `Ação a executar:
|
|
18
|
+
- list: Lista todas as tags existentes
|
|
19
|
+
- create: Cria nova tag no commit atual ou especificado
|
|
20
|
+
- delete: Remove uma tag local
|
|
21
|
+
- push: Envia tag para GitHub e Gitea`
|
|
22
|
+
},
|
|
23
|
+
tag: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Nome da tag. Use versionamento semântico: v1.0.0, v1.1.0, v2.0.0"
|
|
26
|
+
},
|
|
27
|
+
ref: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Commit/branch para criar tag (opcional, default: HEAD)"
|
|
30
|
+
},
|
|
31
|
+
message: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Mensagem da tag anotada (opcional). Se fornecido, cria tag anotada"
|
|
34
|
+
}
|
|
15
35
|
},
|
|
16
36
|
required: ["projectPath", "action"],
|
|
17
|
-
additionalProperties:
|
|
37
|
+
additionalProperties: false
|
|
18
38
|
};
|
|
19
39
|
|
|
40
|
+
const description = `Gerenciamento de tags Git para versionamento.
|
|
41
|
+
|
|
42
|
+
AÇÕES DISPONÍVEIS:
|
|
43
|
+
- list: Ver todas as tags existentes
|
|
44
|
+
- create: Criar nova tag de versão
|
|
45
|
+
- delete: Remover tag local
|
|
46
|
+
- push: Enviar tag para GitHub e Gitea
|
|
47
|
+
|
|
48
|
+
VERSIONAMENTO SEMÂNTICO:
|
|
49
|
+
- v1.0.0: Versão inicial
|
|
50
|
+
- v1.1.0: Nova funcionalidade (minor)
|
|
51
|
+
- v1.0.1: Correção de bug (patch)
|
|
52
|
+
- v2.0.0: Breaking change (major)
|
|
53
|
+
|
|
54
|
+
FLUXO TÍPICO:
|
|
55
|
+
1. git-tags create tag='v1.0.0' message='Release inicial'
|
|
56
|
+
2. git-tags push tag='v1.0.0'`;
|
|
57
|
+
|
|
20
58
|
async function handle(args) {
|
|
21
59
|
const validate = ajv.compile(inputSchema);
|
|
22
60
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
@@ -27,38 +65,32 @@ export function createGitTagsTool(git) {
|
|
|
27
65
|
return asToolResult({ tags, count: tags.length });
|
|
28
66
|
}
|
|
29
67
|
if (action === "create") {
|
|
30
|
-
|
|
31
|
-
if (!tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
32
|
-
// Auto-verificação: checar se já existe
|
|
68
|
+
if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
33
69
|
const existing = await git.listTags(projectPath);
|
|
34
|
-
if (existing.includes(tag)) {
|
|
35
|
-
return asToolError("TAG_ALREADY_EXISTS", `Tag '${tag}' já existe`, { tag, existingTags: existing });
|
|
70
|
+
if (existing.includes(args.tag)) {
|
|
71
|
+
return asToolError("TAG_ALREADY_EXISTS", `Tag '${args.tag}' já existe`, { tag: args.tag, existingTags: existing });
|
|
36
72
|
}
|
|
37
|
-
await git.createTag(projectPath, tag, args.ref || "HEAD", args.message);
|
|
38
|
-
return asToolResult({ success: true, tag, ref: args.ref || "HEAD" });
|
|
73
|
+
await git.createTag(projectPath, args.tag, args.ref || "HEAD", args.message);
|
|
74
|
+
return asToolResult({ success: true, tag: args.tag, ref: args.ref || "HEAD", message: `Tag '${args.tag}' criada. Use action='push' para enviar ao GitHub/Gitea.` });
|
|
39
75
|
}
|
|
40
76
|
if (action === "delete") {
|
|
41
|
-
|
|
42
|
-
if (!tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
43
|
-
// Auto-verificação: checar se existe
|
|
77
|
+
if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
44
78
|
const existing = await git.listTags(projectPath);
|
|
45
|
-
if (!existing.includes(tag)) {
|
|
46
|
-
return asToolError("TAG_NOT_FOUND", `Tag '${tag}' não encontrada`, { tag, existingTags: existing });
|
|
79
|
+
if (!existing.includes(args.tag)) {
|
|
80
|
+
return asToolError("TAG_NOT_FOUND", `Tag '${args.tag}' não encontrada`, { tag: args.tag, existingTags: existing });
|
|
47
81
|
}
|
|
48
|
-
await git.deleteTag(projectPath, tag);
|
|
49
|
-
return asToolResult({ success: true, tag });
|
|
82
|
+
await git.deleteTag(projectPath, args.tag);
|
|
83
|
+
return asToolResult({ success: true, tag: args.tag, deleted: true });
|
|
50
84
|
}
|
|
51
85
|
if (action === "push") {
|
|
52
|
-
|
|
53
|
-
if (!tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
54
|
-
// Auto-verificação: checar se existe
|
|
86
|
+
if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório", { parameter: "tag" });
|
|
55
87
|
const existing = await git.listTags(projectPath);
|
|
56
|
-
if (!existing.includes(tag)) {
|
|
57
|
-
return asToolError("TAG_NOT_FOUND", `Tag '${tag}' não existe localmente. Crie primeiro com action='create'`, { tag, existingTags: existing });
|
|
88
|
+
if (!existing.includes(args.tag)) {
|
|
89
|
+
return asToolError("TAG_NOT_FOUND", `Tag '${args.tag}' não existe localmente. Crie primeiro com action='create'`, { tag: args.tag, existingTags: existing });
|
|
58
90
|
}
|
|
59
91
|
const results = await Promise.allSettled([
|
|
60
|
-
git.pushTag(projectPath, "github", tag),
|
|
61
|
-
git.pushTag(projectPath, "gitea", tag)
|
|
92
|
+
git.pushTag(projectPath, "github", args.tag),
|
|
93
|
+
git.pushTag(projectPath, "gitea", args.tag)
|
|
62
94
|
]);
|
|
63
95
|
const pushed = [];
|
|
64
96
|
const failed = [];
|
|
@@ -67,13 +99,13 @@ export function createGitTagsTool(git) {
|
|
|
67
99
|
if (results[1].status === "fulfilled") pushed.push("gitea");
|
|
68
100
|
else failed.push({ remote: "gitea", error: results[1].reason?.message });
|
|
69
101
|
|
|
70
|
-
return asToolResult({ success: pushed.length > 0, tag, pushed, failed });
|
|
102
|
+
return asToolResult({ success: pushed.length > 0, tag: args.tag, pushed, failed });
|
|
71
103
|
}
|
|
72
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
104
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "delete", "push"] });
|
|
73
105
|
} catch (e) {
|
|
74
106
|
return errorToResponse(e);
|
|
75
107
|
}
|
|
76
108
|
}
|
|
77
109
|
|
|
78
|
-
return { name: "git-tags", description
|
|
110
|
+
return { name: "git-tags", description, inputSchema, handle };
|
|
79
111
|
}
|
|
@@ -8,16 +8,56 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
8
8
|
const inputSchema = {
|
|
9
9
|
type: "object",
|
|
10
10
|
properties: {
|
|
11
|
-
projectPath: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto do diretório do projeto (ex: 'C:/Users/user/projeto' ou '/home/user/projeto')"
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes"],
|
|
18
|
+
description: `Ação a executar:
|
|
19
|
+
- init: Inicializa repositório git local E cria repos no GitHub/Gitea automaticamente
|
|
20
|
+
- status: Retorna arquivos modificados, staged, untracked (use ANTES de commit para ver o que mudou)
|
|
21
|
+
- add: Adiciona arquivos ao staging (use ANTES de commit)
|
|
22
|
+
- remove: Remove arquivos do staging
|
|
23
|
+
- commit: Cria commit com os arquivos staged (use DEPOIS de add)
|
|
24
|
+
- push: Envia commits para GitHub E Gitea em paralelo (use DEPOIS de commit)
|
|
25
|
+
- ensure-remotes: Configura remotes GitHub e Gitea (use se push falhar por falta de remote)`
|
|
26
|
+
},
|
|
27
|
+
files: {
|
|
28
|
+
type: "array",
|
|
29
|
+
items: { type: "string" },
|
|
30
|
+
description: "Lista de arquivos para add/remove. Use ['.'] para todos os arquivos. Ex: ['src/index.js', 'package.json']"
|
|
31
|
+
},
|
|
32
|
+
message: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Mensagem do commit. Obrigatório para action='commit'. Ex: 'feat: adiciona nova funcionalidade'"
|
|
35
|
+
},
|
|
36
|
+
force: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Force push (use apenas se push normal falhar com erro de histórico divergente). Default: false"
|
|
39
|
+
}
|
|
16
40
|
},
|
|
17
41
|
required: ["projectPath", "action"],
|
|
18
|
-
additionalProperties:
|
|
42
|
+
additionalProperties: false
|
|
19
43
|
};
|
|
20
44
|
|
|
45
|
+
const description = `Operações Git essenciais com sincronização automática GitHub + Gitea.
|
|
46
|
+
|
|
47
|
+
FLUXO TÍPICO DE TRABALHO:
|
|
48
|
+
1. git-workflow status → ver arquivos modificados
|
|
49
|
+
2. git-workflow add → adicionar arquivos ao staging
|
|
50
|
+
3. git-workflow commit → criar commit
|
|
51
|
+
4. git-workflow push → enviar para GitHub e Gitea
|
|
52
|
+
|
|
53
|
+
QUANDO USAR CADA ACTION:
|
|
54
|
+
- status: Para verificar estado atual do repositório
|
|
55
|
+
- add: Quando há arquivos modificados para commitar
|
|
56
|
+
- commit: Após add, para salvar as mudanças
|
|
57
|
+
- push: Após commit, para sincronizar com remotes
|
|
58
|
+
- init: Apenas uma vez, para novos projetos
|
|
59
|
+
- ensure-remotes: Se push falhar por falta de configuração`;
|
|
60
|
+
|
|
21
61
|
async function handle(args) {
|
|
22
62
|
const validate = ajv.compile(inputSchema);
|
|
23
63
|
if (!validate(args || {})) {
|
|
@@ -29,7 +69,7 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
29
69
|
await git.init(projectPath);
|
|
30
70
|
const repo = getRepoNameFromPath(projectPath);
|
|
31
71
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true });
|
|
32
|
-
return asToolResult({ success: true, ensured, message: "Repositório inicializado" });
|
|
72
|
+
return asToolResult({ success: true, ensured, message: "Repositório inicializado localmente e nos providers" });
|
|
33
73
|
}
|
|
34
74
|
if (action === "status") {
|
|
35
75
|
const st = await git.status(projectPath);
|
|
@@ -38,7 +78,7 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
38
78
|
if (action === "add") {
|
|
39
79
|
const files = Array.isArray(args.files) && args.files.length ? args.files : ["."];
|
|
40
80
|
await git.add(projectPath, files);
|
|
41
|
-
return asToolResult({ success: true, files });
|
|
81
|
+
return asToolResult({ success: true, files, nextStep: "Use action='commit' com message para criar o commit" });
|
|
42
82
|
}
|
|
43
83
|
if (action === "remove") {
|
|
44
84
|
const files = Array.isArray(args.files) ? args.files : [];
|
|
@@ -46,9 +86,11 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
46
86
|
return asToolResult({ success: true, files });
|
|
47
87
|
}
|
|
48
88
|
if (action === "commit") {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
if (!args.message) {
|
|
90
|
+
return asToolError("MISSING_PARAMETER", "message é obrigatório para commit", { parameter: "message" });
|
|
91
|
+
}
|
|
92
|
+
const sha = await git.commit(projectPath, args.message);
|
|
93
|
+
return asToolResult({ success: true, sha, message: args.message, nextStep: "Use action='push' para enviar ao GitHub/Gitea" });
|
|
52
94
|
}
|
|
53
95
|
if (action === "ensure-remotes") {
|
|
54
96
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -59,20 +101,18 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
59
101
|
const base = pm.giteaUrl?.replace(/\/$/, "") || "";
|
|
60
102
|
const giteaUrl = geOwner && base ? `${base}/${geOwner}/${repo}.git` : "";
|
|
61
103
|
await git.ensureRemotes(projectPath, { githubUrl, giteaUrl });
|
|
62
|
-
|
|
104
|
+
const remotes = await git.listRemotes(projectPath);
|
|
105
|
+
return asToolResult({ success: true, ensured, remotes, nextStep: "Agora pode usar action='push'" });
|
|
63
106
|
}
|
|
64
107
|
if (action === "push") {
|
|
65
108
|
const branch = await git.getCurrentBranch(projectPath);
|
|
66
109
|
const force = !!args.force;
|
|
67
|
-
await git.pushParallel(projectPath, branch, force);
|
|
68
|
-
return asToolResult({ success: true, branch,
|
|
69
|
-
}
|
|
70
|
-
if (action === "sync" || action === "pull") {
|
|
71
|
-
// Basic implementation: fetch + status; full merge/pull can be added
|
|
72
|
-
return asToolResult({ success: true, message: "Operação de pull/sync não-op realizada" });
|
|
110
|
+
const result = await git.pushParallel(projectPath, branch, force);
|
|
111
|
+
return asToolResult({ success: true, branch, ...result });
|
|
73
112
|
}
|
|
74
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
75
|
-
availableActions: ["init", "status", "add", "remove", "commit", "push", "
|
|
113
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
114
|
+
availableActions: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes"],
|
|
115
|
+
suggestion: "Use uma das actions disponíveis"
|
|
76
116
|
});
|
|
77
117
|
} catch (e) {
|
|
78
118
|
return errorToResponse(e);
|
|
@@ -81,9 +121,8 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
81
121
|
|
|
82
122
|
return {
|
|
83
123
|
name: "git-workflow",
|
|
84
|
-
description
|
|
124
|
+
description,
|
|
85
125
|
inputSchema,
|
|
86
126
|
handle
|
|
87
127
|
};
|
|
88
128
|
}
|
|
89
|
-
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -6,6 +6,10 @@ import { MCPError, createError, mapExternalError } from "./errors.js";
|
|
|
6
6
|
import { getProvidersEnv } from "./repoHelpers.js";
|
|
7
7
|
import { withRetry } from "./retry.js";
|
|
8
8
|
|
|
9
|
+
// #region agent log
|
|
10
|
+
const debugLog = (loc, msg, data) => { fetch('http://127.0.0.1:8242/ingest/e5799a4a-1a0d-4201-a6ce-42835e6f6fc7',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:loc,message:msg,data,timestamp:Date.now(),sessionId:'debug-session'})}).catch(()=>{}); };
|
|
11
|
+
// #endregion
|
|
12
|
+
|
|
9
13
|
export class GitAdapter {
|
|
10
14
|
constructor(providerManager) {
|
|
11
15
|
this.pm = providerManager;
|
|
@@ -16,7 +20,14 @@ export class GitAdapter {
|
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
async status(dir) {
|
|
23
|
+
// #region agent log
|
|
24
|
+
const t0 = Date.now();
|
|
25
|
+
debugLog('gitAdapter.js:status', 'START statusMatrix', { dir, hypothesisId: 'A' });
|
|
26
|
+
// #endregion
|
|
19
27
|
const matrix = await git.statusMatrix({ fs, dir });
|
|
28
|
+
// #region agent log
|
|
29
|
+
debugLog('gitAdapter.js:status', 'END statusMatrix', { duration: Date.now() - t0, fileCount: matrix.length, hypothesisId: 'A' });
|
|
30
|
+
// #endregion
|
|
20
31
|
const FILE = 0, HEAD = 1, WORKDIR = 2, STAGE = 3;
|
|
21
32
|
const modified = [], created = [], deleted = [], not_added = [], files = [];
|
|
22
33
|
for (const row of matrix) {
|