@andre.buzeli/git-mcp 16.0.8 → 16.1.3

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,180 @@
1
+ import Ajv from "ajv";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
5
+ import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
6
+
7
+ const ajv = new Ajv({ allErrors: true });
8
+
9
+ export function createGitWorktreeTool(git) {
10
+ const inputSchema = {
11
+ type: "object",
12
+ properties: {
13
+ projectPath: {
14
+ type: "string",
15
+ description: "Caminho absoluto do diretório do projeto no IDE (ex: '/home/user/meu-projeto' ou 'C:/Users/user/meu-projeto'). IMPORTANTE: este valor não pode ser inferido automaticamente pelo servidor — o agente deve fornecer o path real do projeto sendo trabalhado, não o diretório home do usuário."
16
+ },
17
+ action: {
18
+ type: "string",
19
+ enum: ["add", "list", "remove", "prune", "setup", "set-channel"],
20
+ description: `Ação a executar:
21
+ - add: Cria novo worktree (branch em diretório separado)
22
+ - list: Lista worktrees existentes
23
+ - remove: Remove um worktree
24
+ - prune: Limpa informações de worktrees deletados manualmente
25
+ - setup: Configura estrutura de pastas recomendada (main, mac, win)
26
+ - set-channel: Define o canal de deploy (production/beta/alpha) para uma branch`
27
+ },
28
+ branch: {
29
+ type: "string",
30
+ description: "Nome da branch para o worktree. Ex: 'mac', 'win', 'feature-x'"
31
+ },
32
+ path: {
33
+ type: "string",
34
+ description: "Caminho onde criar o worktree (opcional para action='add', default: ../branch-name)"
35
+ },
36
+ force: {
37
+ type: "boolean",
38
+ description: "Forçar operação. Default: false"
39
+ },
40
+ channel: {
41
+ type: "string",
42
+ enum: ["production", "beta", "alpha"],
43
+ description: "Canal de deploy (para action='set-channel' ou 'add')"
44
+ }
45
+ },
46
+ required: ["projectPath", "action"],
47
+ additionalProperties: false
48
+ };
49
+
50
+ const description = `IMPORTANTE — projectPath:
51
+ Informe o caminho absoluto do projeto aberto no IDE. O servidor MCP não tem acesso ao
52
+ contexto do IDE e não consegue detectar automaticamente qual projeto está sendo trabalhado.
53
+
54
+ Gerenciamento de Git Worktrees (Múltiplos diretórios de trabalho).
55
+
56
+ QUANDO USAR:
57
+ - Trabalhar em múltiplas branches simultaneamente
58
+ - Testar em diferentes ambientes (ex: Windows vs Mac)
59
+ - Separar builds de produção e desenvolvimento
60
+
61
+ ESTRUTURA RECOMENDADA:
62
+ /projeto-root/
63
+ /main (repo principal)
64
+ /feature-x (worktree)
65
+ /hotfix (worktree)
66
+
67
+ AÇÕES:
68
+ - add: Criar novo worktree
69
+ - list: Ver worktrees ativos
70
+ - remove: Deletar worktree
71
+ - setup: Criar estrutura padrão (opcional)
72
+ - set-channel: Configurar canal de deploy (prod/beta/alpha)`;
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
+ const { projectPath, action } = args;
78
+
79
+ try {
80
+ validateProjectPath(projectPath);
81
+
82
+ if (action === "list") {
83
+ const worktrees = await git.listWorktrees(projectPath);
84
+
85
+ // Enrich with channel info if available
86
+ const enriched = [];
87
+ for (const wt of worktrees) {
88
+ const channel = await git.getConfig(projectPath, `worktree-branch.${wt.branch}.channel`);
89
+ enriched.push({ ...wt, channel: channel || "production" }); // Default to production
90
+ }
91
+
92
+ return asToolResult({ worktrees: enriched, count: enriched.length });
93
+ }
94
+
95
+ if (action === "add") {
96
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para add", { parameter: "branch" });
97
+
98
+ // Default path: sibling directory with branch name
99
+ // Se projectPath é .../repo/main, e branch é 'feature', cria .../repo/feature
100
+ // Mas git worktree add path branch.
101
+
102
+ // Se path não fornecido, assumir irmão do diretório atual se estivermos em um repo.
103
+ // Mas projectPath é a raiz do repo.
104
+ // Vamos usar path relativo ou absoluto fornecido, ou default: join(projectPath, "..", branch)
105
+
106
+ const targetPath = args.path
107
+ ? path.resolve(projectPath, args.path)
108
+ : path.join(projectPath, args.branch); // Cria DENTRO do projeto? Não, worktrees geralmente ficam ao lado ou fora.
109
+
110
+ // Melhor prática: criar dentro se for estrutura monorepo-style, ou fora.
111
+ // O git-mcp v16 parece usar estrutura plana ou aninhada.
112
+ // Vamos usar o comportamento padrão do git: path relativo à raiz.
113
+ // Se o usuário passar apenas o nome da branch como path, o git cria ./branch-name.
114
+
115
+ // Se path não informado, usa o nome da branch (cria pasta ./branch-name dentro do repo atual)
116
+ // Isso pode sujar o repo principal se não estiver no gitignore.
117
+ // Vamos sugerir ou usar ../branch-name se estivermos na raiz?
118
+ // Simplicidade: user define path ou usa ./branch-name.
119
+
120
+ const wtPath = args.path || args.branch;
121
+
122
+ await withRetry(() => git.addWorktree(projectPath, args.branch, wtPath, args.force), 3, "worktree-add");
123
+
124
+ // Set channel if provided
125
+ if (args.channel) {
126
+ await git.setWorktreeConfig(projectPath, args.branch, { channel: args.channel });
127
+ }
128
+
129
+ return asToolResult({
130
+ success: true,
131
+ branch: args.branch,
132
+ path: path.resolve(projectPath, wtPath),
133
+ channel: args.channel || "production"
134
+ });
135
+ }
136
+
137
+ if (action === "remove") {
138
+ if (!args.branch && !args.path) return asToolError("MISSING_PARAMETER", "branch ou path é obrigatório para remove");
139
+
140
+ // Git remove worktree by path
141
+ // Precisamos encontrar o path se só branch for fornecida
142
+ let targetPath = args.path;
143
+ if (!targetPath && args.branch) {
144
+ const wts = await git.listWorktrees(projectPath);
145
+ const found = wts.find(w => w.branch === args.branch);
146
+ if (!found) return asToolError("WORKTREE_NOT_FOUND", `Worktree para branch '${args.branch}' não encontrado`);
147
+ targetPath = found.path;
148
+ }
149
+
150
+ await withRetry(() => git.removeWorktree(projectPath, targetPath, args.force), 3, "worktree-remove");
151
+ return asToolResult({ success: true, removed: targetPath });
152
+ }
153
+
154
+ if (action === "prune") {
155
+ await git.pruneWorktrees(projectPath);
156
+ return asToolResult({ success: true, message: "Worktrees podados" });
157
+ }
158
+
159
+ if (action === "set-channel") {
160
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório");
161
+ if (!args.channel) return asToolError("MISSING_PARAMETER", "channel é obrigatório");
162
+
163
+ await git.setWorktreeConfig(projectPath, args.branch, { channel: args.channel });
164
+ return asToolResult({ success: true, branch: args.branch, channel: args.channel });
165
+ }
166
+
167
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["add", "list", "remove", "prune", "set-channel"] });
168
+ } catch (e) {
169
+ return errorToResponse(e);
170
+ }
171
+ }
172
+
173
+ return {
174
+ name: "git-worktree",
175
+ description,
176
+ inputSchema,
177
+ handle,
178
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
179
+ };
180
+ }