@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,152 @@
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
3
+ import { validateProjectPath } from "../utils/repoHelpers.js";
4
+
5
+ const ajv = new Ajv({ allErrors: true });
6
+
7
+ export function createGitMergeTool(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: ["merge", "abort", "status"],
18
+ description: `Ação a executar:
19
+ - merge: Faz merge de uma branch na branch atual
20
+ - abort: Aborta um merge em andamento (se houver conflitos)
21
+ - status: Verifica se há merge em andamento`
22
+ },
23
+ branch: {
24
+ type: "string",
25
+ description: "Branch para fazer merge na branch atual. Ex: 'feature/nova-funcionalidade'"
26
+ },
27
+ message: {
28
+ type: "string",
29
+ description: "Mensagem do commit de merge. Se não fornecida, usa mensagem padrão"
30
+ },
31
+ noCommit: {
32
+ type: "boolean",
33
+ description: "Se true, faz merge mas não cria commit automaticamente. Default: false"
34
+ },
35
+ squash: {
36
+ type: "boolean",
37
+ description: "Se true, squash all commits into one. Default: false"
38
+ }
39
+ },
40
+ required: ["projectPath", "action"],
41
+ additionalProperties: false
42
+ };
43
+
44
+ const description = `Merge de branches Git.
45
+
46
+ QUANDO USAR:
47
+ - Integrar mudanças de uma feature branch na main
48
+ - Combinar trabalho de diferentes branches
49
+ - Finalizar uma feature/bugfix
50
+
51
+ FLUXO TÍPICO:
52
+ 1. git-branches checkout branch='main' → ir para branch destino
53
+ 2. git-merge merge branch='feature/x' → fazer merge
54
+ 3. git-workflow push → enviar resultado
55
+
56
+ CONFLITOS:
57
+ - Se houver conflitos, o merge será interrompido
58
+ - Resolva os conflitos manualmente
59
+ - Use git-workflow add e commit para finalizar
60
+ - Ou use git-merge abort para cancelar
61
+
62
+ SQUASH:
63
+ - Use squash=true para combinar todos os commits em um só
64
+ - Útil para manter histórico limpo`;
65
+
66
+ async function handle(args) {
67
+ const validate = ajv.compile(inputSchema);
68
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
69
+ const { projectPath, action } = args;
70
+
71
+ try {
72
+ validateProjectPath(projectPath);
73
+ if (action === "status") {
74
+ const mergeStatus = await git.getMergeStatus(projectPath);
75
+ return asToolResult(mergeStatus);
76
+ }
77
+
78
+ if (action === "abort") {
79
+ await git.abortMerge(projectPath);
80
+ return asToolResult({
81
+ success: true,
82
+ message: "Merge abortado com sucesso"
83
+ });
84
+ }
85
+
86
+ if (action === "merge") {
87
+ if (!args.branch) {
88
+ return asToolError("MISSING_PARAMETER", "branch é obrigatório para merge", {
89
+ parameter: "branch",
90
+ example: "feature/nova-funcionalidade"
91
+ });
92
+ }
93
+
94
+ // Pre-merge check: working tree status
95
+ const status = await git.status(projectPath);
96
+ if (!status.isClean) {
97
+ // We could warn or fail. Standard git refuses merge if changes would be overwritten.
98
+ // Let's just warn in logs or return error if critical?
99
+ // Git will fail anyway if conflicts with local changes.
100
+ // Let's inform the user.
101
+ console.warn("[GitMerge] Working tree not clean. Merge might fail.");
102
+ }
103
+
104
+ const currentBranch = await git.getCurrentBranch(projectPath);
105
+ if (currentBranch === args.branch) {
106
+ return asToolError("VALIDATION_ERROR", "Não pode fazer merge de uma branch nela mesma", {
107
+ branch: args.branch,
108
+ suggestion: "Faça checkout para a branch destino primeiro"
109
+ });
110
+ }
111
+
112
+ try {
113
+ const result = await git.merge(projectPath, args.branch, {
114
+ message: args.message,
115
+ noCommit: args.noCommit,
116
+ squash: args.squash
117
+ });
118
+
119
+ return asToolResult({
120
+ success: true,
121
+ from: args.branch,
122
+ into: currentBranch,
123
+ ...result,
124
+ message: result.conflicts?.length > 0
125
+ ? `Merge com conflitos. Resolva os conflitos e faça commit.`
126
+ : `Merge de '${args.branch}' em '${currentBranch}' concluído`
127
+ });
128
+ } catch (e) {
129
+ const msg = e.message || "";
130
+ if (msg.includes("conflict") || msg.includes("CONFLICT")) {
131
+ return asToolResult({
132
+ success: false,
133
+ conflict: true,
134
+ message: "Merge resultou em conflitos. Resolva manualmente e faça commit.",
135
+ files: [], // If we could parse files from error, great.
136
+ nextStep: "Edite os arquivos conflitantes, use git add e git commit."
137
+ });
138
+ }
139
+ throw e;
140
+ }
141
+ }
142
+
143
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
144
+ availableActions: ["merge", "abort", "status"]
145
+ });
146
+ } catch (e) {
147
+ return errorToResponse(e);
148
+ }
149
+ }
150
+
151
+ return { name: "git-merge", description, inputSchema, handle };
152
+ }
@@ -0,0 +1,115 @@
1
+ import Ajv from "ajv";
2
+ import axios from "axios";
3
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
4
+ import { getRepoNameFromPath, validateProjectPath } from "../utils/repoHelpers.js";
5
+ import { runBoth } from "../utils/providerExec.js";
6
+
7
+ const ajv = new Ajv({ allErrors: true });
8
+
9
+ export function createGitPullsTool(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", "files"],
20
+ description: `Ação a executar:
21
+ - create: Cria Pull Request no GitHub E Gitea
22
+ - list: Lista Pull Requests existentes
23
+ - files: Lista arquivos modificados em um PR`
24
+ },
25
+ title: {
26
+ type: "string",
27
+ description: "Título do PR. Ex: 'feat: adiciona autenticação OAuth'"
28
+ },
29
+ head: {
30
+ type: "string",
31
+ description: "Branch de origem (com as mudanças). Ex: 'feature/oauth'"
32
+ },
33
+ base: {
34
+ type: "string",
35
+ description: "Branch de destino (onde será mergeado). Ex: 'main', 'develop'"
36
+ },
37
+ number: {
38
+ type: "number",
39
+ description: "Número do PR para action='files'"
40
+ },
41
+ body: {
42
+ type: "string",
43
+ description: "Descrição do PR com detalhes das mudanças"
44
+ },
45
+ organization: {
46
+ type: "string",
47
+ description: "Opcional. Nome da organização no GitHub/Gitea. Se informado, operações são feitas na org em vez da conta pessoal."
48
+ }
49
+ },
50
+ required: ["projectPath", "action"],
51
+ additionalProperties: false
52
+ };
53
+
54
+ const description = `Gerenciamento de Pull Requests no GitHub e Gitea.
55
+
56
+ QUANDO USAR:
57
+ - Propor mudanças de uma branch para outra
58
+ - Code review antes de merge
59
+ - Documentar mudanças significativas
60
+
61
+ FLUXO TÍPICO:
62
+ 1. Cria branch: git-branches create branch='feature/nova'
63
+ 2. Faz mudanças e commits
64
+ 3. Push: git-workflow push
65
+ 4. Cria PR: git-pulls create head='feature/nova' base='main' title='Nova feature'
66
+
67
+ AÇÕES:
68
+ - create: Criar novo PR
69
+ - list: Ver PRs existentes
70
+ - files: Ver arquivos modificados em um PR
71
+
72
+ NOTA: O PR é criado em AMBOS os providers simultaneamente.`;
73
+
74
+ async function handle(args) {
75
+ const validate = ajv.compile(inputSchema);
76
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
77
+ validateProjectPath(args.projectPath);
78
+ const repo = getRepoNameFromPath(args.projectPath);
79
+ try {
80
+ if (args.action === "create") {
81
+ if (!args.head) return asToolError("MISSING_PARAMETER", "head (branch de origem) é obrigatório", { parameter: "head", example: "feature/nova-funcionalidade" });
82
+ if (!args.base) return asToolError("MISSING_PARAMETER", "base (branch de destino) é obrigatório", { parameter: "base", example: "main" });
83
+ const head = args.head;
84
+ const base = args.base;
85
+ const title = args.title || `${head} -> ${base}`;
86
+ const body = args.body || "";
87
+ const out = await withRetry(() => runBoth(pm, {
88
+ github: async (owner) => { const r = await pm.github.rest.pulls.create({ owner, repo, title, head, base, body }); return { ok: true, number: r.data.number, url: r.data.html_url }; },
89
+ gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls`, { title, head, base, body }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, number: r.data?.number }; }
90
+ }, { organization: args.organization }), 3, "pulls-create");
91
+ return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), title, head, base, providers: out });
92
+ }
93
+ if (args.action === "list") {
94
+ const out = await withRetry(() => runBoth(pm, {
95
+ github: async (owner) => { const r = await pm.github.rest.pulls.list({ owner, repo, state: "all", per_page: 30 }); return { ok: true, count: r.data.length, pulls: r.data.slice(0, 10).map(p => ({ number: p.number, title: p.title, state: p.state, head: p.head.ref, base: p.base.ref })) }; },
96
+ gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls?state=all`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data || []).length, pulls: (r.data || []).slice(0, 10).map(p => ({ number: p.number, title: p.title, state: p.state, head: p.head?.ref, base: p.base?.ref })) }; }
97
+ }, { organization: args.organization }), 3, "pulls-list");
98
+ return asToolResult({ providers: out });
99
+ }
100
+ if (args.action === "files") {
101
+ if (!args.number) return asToolError("MISSING_PARAMETER", "number é obrigatório para ver arquivos do PR", { parameter: "number" });
102
+ const out = await withRetry(() => runBoth(pm, {
103
+ github: async (owner) => { const r = await pm.github.rest.pulls.listFiles({ owner, repo, pull_number: args.number }); return { ok: true, count: r.data.length, files: r.data.map(f => ({ filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions })) }; },
104
+ gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls/${args.number}/files`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data || []).length, files: (r.data || []).map(f => ({ filename: f.filename, status: f.status })) }; }
105
+ }, { organization: args.organization }), 3, "pulls-files");
106
+ return asToolResult({ success: true, providers: out });
107
+ }
108
+ return asToolError("VALIDATION_ERROR", `Ação '${args.action}' não suportada`, { availableActions: ["create", "list", "files"] });
109
+ } catch (e) {
110
+ return errorToResponse(e);
111
+ }
112
+ }
113
+
114
+ return { name: "git-pulls", description, inputSchema, handle };
115
+ }