@andrebuzeli/git-mcp 15.8.6 → 15.9.1
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 +8 -7
- package/src/utils/env.js +9 -9
- package/src/utils/errors.js +60 -60
- package/src/utils/gitAdapter.js +98 -63
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -91,17 +91,18 @@ server.setRequestHandler(
|
|
|
91
91
|
async (req) => {
|
|
92
92
|
const name = req.params?.name || "";
|
|
93
93
|
const args = req.params?.arguments || {};
|
|
94
|
-
|
|
94
|
+
// Progress reporting removed to prevent timeouts/unknown token errors on slow networks
|
|
95
|
+
// const progressToken = req.params?._meta?.progressToken;
|
|
96
|
+
|
|
95
97
|
const tool = tools.find(t => t.name === name);
|
|
96
98
|
if (!tool) return { content: [{ type: "text", text: `Tool não encontrada: ${name}` }], isError: true };
|
|
97
99
|
try {
|
|
98
|
-
if (progressToken) {
|
|
99
|
-
|
|
100
|
-
}
|
|
100
|
+
// if (progressToken) { await server.notification({ method: "notifications/progress", params: { progressToken, progress: 0 } }); }
|
|
101
|
+
|
|
101
102
|
const result = await tool.handle(args);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
|
|
104
|
+
// if (progressToken) { await server.notification({ method: "notifications/progress", params: { progressToken, progress: 100 } }); }
|
|
105
|
+
|
|
105
106
|
return result;
|
|
106
107
|
} catch (e) {
|
|
107
108
|
return asToolError(e.code || "ERROR", e.message || String(e));
|
package/src/utils/env.js
CHANGED
|
@@ -29,7 +29,7 @@ export function loadEnv(envPath = null) {
|
|
|
29
29
|
// Ignora erros de leitura
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -39,24 +39,24 @@ export function loadEnv(envPath = null) {
|
|
|
39
39
|
*/
|
|
40
40
|
function parseEnvFile(content) {
|
|
41
41
|
const lines = content.split("\n");
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
for (const line of lines) {
|
|
44
44
|
// Ignora linhas vazias e comentários
|
|
45
45
|
const trimmed = line.trim();
|
|
46
46
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
// Parse KEY=VALUE
|
|
49
49
|
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
50
50
|
if (match) {
|
|
51
51
|
const key = match[1].trim();
|
|
52
52
|
let value = match[2].trim();
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
// Remove aspas ao redor do valor
|
|
55
55
|
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
56
|
-
|
|
56
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
57
57
|
value = value.slice(1, -1);
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
// Só define se não existir (variáveis de ambiente têm precedência)
|
|
61
61
|
if (!(key in process.env)) {
|
|
62
62
|
process.env[key] = value;
|
|
@@ -95,10 +95,10 @@ export const ENV_CONFIG = {
|
|
|
95
95
|
GITHUB_TOKEN: "Token de autenticação do GitHub",
|
|
96
96
|
GITEA_URL: "URL do servidor Gitea",
|
|
97
97
|
GITEA_TOKEN: "Token de autenticação do Gitea",
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Performance
|
|
100
|
-
GIT_TIMEOUT_MS: "Timeout para operações Git em ms (default:
|
|
101
|
-
|
|
100
|
+
GIT_TIMEOUT_MS: "Timeout para operações Git em ms (default: 120000)",
|
|
101
|
+
|
|
102
102
|
// Debug
|
|
103
103
|
DEBUG_AGENT_LOG: "Habilita logging de debug"
|
|
104
104
|
};
|
package/src/utils/errors.js
CHANGED
|
@@ -16,14 +16,14 @@ export const ERROR_CODES = {
|
|
|
16
16
|
suggestion: "Verifique se GITHUB_TOKEN está configurado e é válido. Gere um novo token em github.com/settings/tokens"
|
|
17
17
|
},
|
|
18
18
|
AUTH_GITEA_INVALID: {
|
|
19
|
-
code: "AUTH_GITEA_INVALID",
|
|
19
|
+
code: "AUTH_GITEA_INVALID",
|
|
20
20
|
suggestion: "Verifique se GITEA_TOKEN e GITEA_URL estão configurados corretamente"
|
|
21
21
|
},
|
|
22
22
|
AUTH_NO_PERMISSION: {
|
|
23
23
|
code: "AUTH_NO_PERMISSION",
|
|
24
24
|
suggestion: "Token não tem permissão para esta operação. Verifique os scopes do token"
|
|
25
25
|
},
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
// Repo
|
|
28
28
|
REPO_NOT_FOUND: {
|
|
29
29
|
code: "REPO_NOT_FOUND",
|
|
@@ -37,7 +37,7 @@ export const ERROR_CODES = {
|
|
|
37
37
|
code: "REPO_ALREADY_EXISTS",
|
|
38
38
|
suggestion: "Repositório já existe. Use action='ensure' para verificar ou escolha outro nome"
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
// Git Local
|
|
42
42
|
NOT_A_GIT_REPO: {
|
|
43
43
|
code: "NOT_A_GIT_REPO",
|
|
@@ -55,7 +55,7 @@ export const ERROR_CODES = {
|
|
|
55
55
|
code: "NOTHING_TO_PUSH",
|
|
56
56
|
suggestion: "Nenhuma mudança para push. Faça commits primeiro"
|
|
57
57
|
},
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
// Branches
|
|
60
60
|
BRANCH_NOT_FOUND: {
|
|
61
61
|
code: "BRANCH_NOT_FOUND",
|
|
@@ -69,7 +69,7 @@ export const ERROR_CODES = {
|
|
|
69
69
|
code: "CANNOT_DELETE_CURRENT",
|
|
70
70
|
suggestion: "Não pode deletar branch atual. Faça checkout para outra branch primeiro"
|
|
71
71
|
},
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
// Tags
|
|
74
74
|
TAG_NOT_FOUND: {
|
|
75
75
|
code: "TAG_NOT_FOUND",
|
|
@@ -79,7 +79,7 @@ export const ERROR_CODES = {
|
|
|
79
79
|
code: "TAG_ALREADY_EXISTS",
|
|
80
80
|
suggestion: "Tag já existe. Use outro nome ou delete a existente primeiro"
|
|
81
81
|
},
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// Refs
|
|
84
84
|
REF_NOT_FOUND: {
|
|
85
85
|
code: "REF_NOT_FOUND",
|
|
@@ -89,7 +89,7 @@ export const ERROR_CODES = {
|
|
|
89
89
|
code: "INSUFFICIENT_HISTORY",
|
|
90
90
|
suggestion: "Histórico insuficiente para HEAD~N. Use action='log' para verificar quantos commits existem"
|
|
91
91
|
},
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
// Stash
|
|
94
94
|
NOTHING_TO_STASH: {
|
|
95
95
|
code: "NOTHING_TO_STASH",
|
|
@@ -99,7 +99,7 @@ export const ERROR_CODES = {
|
|
|
99
99
|
code: "STASH_NOT_FOUND",
|
|
100
100
|
suggestion: "Stash não encontrado. Use action='list' para ver stashes disponíveis"
|
|
101
101
|
},
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
// Remote
|
|
104
104
|
REMOTE_NOT_FOUND: {
|
|
105
105
|
code: "REMOTE_NOT_FOUND",
|
|
@@ -113,7 +113,7 @@ export const ERROR_CODES = {
|
|
|
113
113
|
code: "MERGE_CONFLICT",
|
|
114
114
|
suggestion: "Conflito de merge. Resolva conflitos manualmente e faça novo commit"
|
|
115
115
|
},
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
// Network
|
|
118
118
|
NETWORK_TIMEOUT: {
|
|
119
119
|
code: "NETWORK_TIMEOUT",
|
|
@@ -123,7 +123,7 @@ export const ERROR_CODES = {
|
|
|
123
123
|
code: "RATE_LIMIT",
|
|
124
124
|
suggestion: "Rate limit excedido. Aguarde alguns minutos antes de tentar novamente"
|
|
125
125
|
},
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
// Validation
|
|
128
128
|
VALIDATION_ERROR: {
|
|
129
129
|
code: "VALIDATION_ERROR",
|
|
@@ -133,13 +133,13 @@ export const ERROR_CODES = {
|
|
|
133
133
|
code: "MISSING_PARAMETER",
|
|
134
134
|
suggestion: "Parâmetro obrigatório faltando"
|
|
135
135
|
},
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
// Files
|
|
138
138
|
FILE_NOT_FOUND: {
|
|
139
139
|
code: "FILE_NOT_FOUND",
|
|
140
140
|
suggestion: "Arquivo não encontrado no repositório. Use action='list' para ver arquivos disponíveis"
|
|
141
141
|
},
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
// Issues/PRs
|
|
144
144
|
ISSUE_NOT_FOUND: {
|
|
145
145
|
code: "ISSUE_NOT_FOUND",
|
|
@@ -149,7 +149,7 @@ export const ERROR_CODES = {
|
|
|
149
149
|
code: "PR_NOT_FOUND",
|
|
150
150
|
suggestion: "Pull Request não encontrado. Use action='list' para ver PRs disponíveis"
|
|
151
151
|
},
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
// Filesystem
|
|
154
154
|
DISK_FULL: {
|
|
155
155
|
code: "DISK_FULL",
|
|
@@ -163,7 +163,7 @@ export const ERROR_CODES = {
|
|
|
163
163
|
code: "PERMISSION_DENIED_FS",
|
|
164
164
|
suggestion: "Sem permissão para acessar arquivo/diretório. Verifique permissões do sistema de arquivos"
|
|
165
165
|
},
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
// Git Corruption
|
|
168
168
|
GIT_CORRUPTED: {
|
|
169
169
|
code: "GIT_CORRUPTED",
|
|
@@ -171,9 +171,9 @@ export const ERROR_CODES = {
|
|
|
171
171
|
},
|
|
172
172
|
GIT_LOCK_FILE: {
|
|
173
173
|
code: "GIT_LOCK_FILE",
|
|
174
|
-
suggestion: "Arquivo de lock presente (.git/index.lock). Outro processo git pode estar rodando.
|
|
174
|
+
suggestion: "Arquivo de lock presente (.git/index.lock). Outro processo git pode estar rodando (comum em drives de rede). O sistema tentou auto-corrigir, mas se persistir, delete o arquivo de lock manualmente."
|
|
175
175
|
},
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
// Remote/URL
|
|
178
178
|
INVALID_REMOTE_URL: {
|
|
179
179
|
code: "INVALID_REMOTE_URL",
|
|
@@ -187,7 +187,7 @@ export const ERROR_CODES = {
|
|
|
187
187
|
code: "SSL_ERROR",
|
|
188
188
|
suggestion: "Erro de SSL/TLS. Verifique certificados ou use http.sslVerify=false (não recomendado)"
|
|
189
189
|
},
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
// Generic
|
|
192
192
|
UNKNOWN_ERROR: {
|
|
193
193
|
code: "UNKNOWN_ERROR",
|
|
@@ -207,14 +207,14 @@ export function createError(errorCode, details = {}) {
|
|
|
207
207
|
// Função para mapear erros externos para códigos internos
|
|
208
208
|
export function mapExternalError(error, context = {}) {
|
|
209
209
|
const msg = error?.message?.toLowerCase() || String(error).toLowerCase();
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
// GitHub/Gitea API errors
|
|
212
212
|
if (msg.includes("bad credentials") || msg.includes("401")) {
|
|
213
|
-
return context.provider === "gitea"
|
|
213
|
+
return context.provider === "gitea"
|
|
214
214
|
? createError("AUTH_GITEA_INVALID", { originalError: msg })
|
|
215
215
|
: createError("AUTH_GITHUB_INVALID", { originalError: msg });
|
|
216
216
|
}
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
if (msg.includes("not found") || msg.includes("404")) {
|
|
219
219
|
if (context.type === "repo") return createError("REPO_NOT_FOUND", { repo: context.repo, originalError: msg });
|
|
220
220
|
if (context.type === "branch") return createError("BRANCH_NOT_FOUND", { branch: context.branch, originalError: msg });
|
|
@@ -224,116 +224,116 @@ export function mapExternalError(error, context = {}) {
|
|
|
224
224
|
if (context.type === "pr") return createError("PR_NOT_FOUND", { number: context.number, originalError: msg });
|
|
225
225
|
return createError("REF_NOT_FOUND", { ref: context.ref, originalError: msg });
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
if (msg.includes("permission") || msg.includes("403") || msg.includes("forbidden")) {
|
|
229
229
|
return createError("AUTH_NO_PERMISSION", { originalError: msg });
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
if (msg.includes("rate limit") || msg.includes("429")) {
|
|
233
233
|
return createError("RATE_LIMIT", { originalError: msg });
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
if (msg.includes("timeout") || msg.includes("etimedout") || msg.includes("econnrefused")) {
|
|
237
237
|
return createError("NETWORK_TIMEOUT", { originalError: msg });
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
if (msg.includes("push rejected") || msg.includes("non-fast-forward")) {
|
|
241
241
|
return createError("PUSH_REJECTED", { originalError: msg });
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
if (msg.includes("conflict")) {
|
|
245
245
|
return createError("MERGE_CONFLICT", { originalError: msg });
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
if (msg.includes("already exists")) {
|
|
249
249
|
if (context.type === "repo") return createError("REPO_ALREADY_EXISTS", { repo: context.repo, originalError: msg });
|
|
250
250
|
if (context.type === "branch") return createError("BRANCH_ALREADY_EXISTS", { branch: context.branch, originalError: msg });
|
|
251
251
|
if (context.type === "tag") return createError("TAG_ALREADY_EXISTS", { tag: context.tag, originalError: msg });
|
|
252
252
|
return createError("BRANCH_ALREADY_EXISTS", { originalError: msg });
|
|
253
253
|
}
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
// isomorphic-git specific errors
|
|
256
256
|
if (msg.includes("could not find") && msg.includes("head~")) {
|
|
257
257
|
return createError("INSUFFICIENT_HISTORY", { originalError: msg });
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
if (msg.includes("is not a git repository") || msg.includes("could not find .git")) {
|
|
261
261
|
return createError("NOT_A_GIT_REPO", { path: context.path, originalError: msg });
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
if (msg.includes("nothing to commit") || msg.includes("working tree clean")) {
|
|
265
265
|
return createError("NOTHING_TO_COMMIT", { originalError: msg });
|
|
266
266
|
}
|
|
267
|
-
|
|
267
|
+
|
|
268
268
|
if (msg.includes("could not resolve ref") || msg.includes("invalid reference")) {
|
|
269
269
|
return createError("REF_NOT_FOUND", { originalError: msg });
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
if (msg.includes("branch") && (msg.includes("does not exist") || msg.includes("not found"))) {
|
|
273
273
|
return createError("BRANCH_NOT_FOUND", { originalError: msg });
|
|
274
274
|
}
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if (msg.includes("tag") && (msg.includes("does not exist") || msg.includes("not found"))) {
|
|
277
277
|
return createError("TAG_NOT_FOUND", { originalError: msg });
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
|
|
280
280
|
if (msg.includes("no commits") || msg.includes("unknown revision")) {
|
|
281
281
|
return createError("NO_COMMITS", { originalError: msg });
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
if (msg.includes("nada para stash") || msg.includes("nothing to stash") || msg.includes("no local changes")) {
|
|
285
285
|
return createError("NOTHING_TO_STASH", { originalError: msg });
|
|
286
286
|
}
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
if (msg.includes("stash") && (msg.includes("not found") || msg.includes("não encontrado"))) {
|
|
289
289
|
return createError("STASH_NOT_FOUND", { originalError: msg });
|
|
290
290
|
}
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
if (msg.includes("author") || msg.includes("committer")) {
|
|
293
|
-
return createError("VALIDATION_ERROR", {
|
|
294
|
-
message: "Configure user.name e user.email",
|
|
293
|
+
return createError("VALIDATION_ERROR", {
|
|
294
|
+
message: "Configure user.name e user.email",
|
|
295
295
|
suggestion: "Use git-config para configurar ou forneça tokens de provider",
|
|
296
|
-
originalError: msg
|
|
296
|
+
originalError: msg
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
// Filesystem errors
|
|
301
301
|
if (msg.includes("enospc") || msg.includes("no space left") || msg.includes("disk full")) {
|
|
302
302
|
return createError("DISK_FULL", { originalError: msg });
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
if (msg.includes("file too large") || msg.includes("exceeds") || msg.includes("size limit")) {
|
|
306
306
|
return createError("FILE_TOO_LARGE", { originalError: msg });
|
|
307
307
|
}
|
|
308
|
-
|
|
308
|
+
|
|
309
309
|
if (msg.includes("eacces") || msg.includes("eperm") || (msg.includes("permission") && msg.includes("denied"))) {
|
|
310
310
|
return createError("PERMISSION_DENIED_FS", { originalError: msg });
|
|
311
311
|
}
|
|
312
|
-
|
|
312
|
+
|
|
313
313
|
// Git corruption
|
|
314
|
-
if (msg.includes("corrupt") || msg.includes("bad object") || msg.includes("broken") ||
|
|
315
|
-
|
|
314
|
+
if (msg.includes("corrupt") || msg.includes("bad object") || msg.includes("broken") ||
|
|
315
|
+
msg.includes("missing object") || msg.includes("invalid sha") || msg.includes("fatal: packed")) {
|
|
316
316
|
return createError("GIT_CORRUPTED", { originalError: msg });
|
|
317
317
|
}
|
|
318
|
-
|
|
318
|
+
|
|
319
319
|
if (msg.includes(".git/index.lock") || msg.includes("unable to create") && msg.includes("lock")) {
|
|
320
320
|
return createError("GIT_LOCK_FILE", { originalError: msg });
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
// Remote/URL errors
|
|
324
324
|
if (msg.includes("invalid url") || msg.includes("malformed") || msg.includes("not a valid")) {
|
|
325
325
|
return createError("INVALID_REMOTE_URL", { originalError: msg });
|
|
326
326
|
}
|
|
327
|
-
|
|
328
|
-
if (msg.includes("could not resolve host") || msg.includes("name resolution") ||
|
|
329
|
-
|
|
327
|
+
|
|
328
|
+
if (msg.includes("could not resolve host") || msg.includes("name resolution") ||
|
|
329
|
+
msg.includes("no address") || msg.includes("unreachable")) {
|
|
330
330
|
return createError("REMOTE_UNREACHABLE", { originalError: msg });
|
|
331
331
|
}
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
if (msg.includes("ssl") || msg.includes("certificate") || msg.includes("tls")) {
|
|
334
334
|
return createError("SSL_ERROR", { originalError: msg });
|
|
335
335
|
}
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
// Generic fallback
|
|
338
338
|
return createError("UNKNOWN_ERROR", { originalError: msg, context });
|
|
339
339
|
}
|
|
@@ -347,27 +347,27 @@ export const NEXT_ACTIONS = {
|
|
|
347
347
|
"workflow:add": { tool: "git-workflow", action: "commit", hint: "Crie um commit com uma mensagem descritiva" },
|
|
348
348
|
"workflow:commit": { tool: "git-workflow", action: "push", hint: "Envie as mudanças para GitHub/Gitea" },
|
|
349
349
|
"workflow:push": null, // Fluxo completo
|
|
350
|
-
|
|
350
|
+
|
|
351
351
|
// git-branches
|
|
352
352
|
"branches:create": { tool: "git-branches", action: "checkout", hint: "Mude para a nova branch criada" },
|
|
353
353
|
"branches:checkout": { tool: "git-workflow", action: "status", hint: "Verifique o estado da branch" },
|
|
354
354
|
"branches:delete": null,
|
|
355
|
-
|
|
355
|
+
|
|
356
356
|
// git-stash
|
|
357
357
|
"stash:save": { tool: "git-branches", action: "checkout", hint: "Agora pode trocar de branch com segurança" },
|
|
358
358
|
"stash:pop": { tool: "git-workflow", action: "status", hint: "Verifique as mudanças restauradas" },
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// git-merge
|
|
361
361
|
"merge:merge": { tool: "git-workflow", action: "push", hint: "Envie o merge para os remotes" },
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
// git-tags
|
|
364
364
|
"tags:create": { tool: "git-tags", action: "push", hint: "Envie a tag para os remotes" },
|
|
365
365
|
"tags:push": null,
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
// git-remote
|
|
368
368
|
"remote:ensure": { tool: "git-workflow", action: "push", hint: "Agora pode fazer push" },
|
|
369
369
|
"remote:release-create": null,
|
|
370
|
-
|
|
370
|
+
|
|
371
371
|
// git-sync
|
|
372
372
|
"sync:pull": { tool: "git-workflow", action: "status", hint: "Verifique mudanças após pull" },
|
|
373
373
|
"sync:fetch": { tool: "git-sync", action: "pull", hint: "Aplique as mudanças baixadas" },
|
|
@@ -388,7 +388,7 @@ export function getNextAction(tool, action) {
|
|
|
388
388
|
export function asToolResult(result, options = {}) {
|
|
389
389
|
const { tool, action } = options;
|
|
390
390
|
const response = { ...result };
|
|
391
|
-
|
|
391
|
+
|
|
392
392
|
// Adicionar próxima ação sugerida se disponível
|
|
393
393
|
if (tool && action) {
|
|
394
394
|
const next = getNextAction(tool, action);
|
|
@@ -398,7 +398,7 @@ export function asToolResult(result, options = {}) {
|
|
|
398
398
|
response._flowComplete = true;
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
return {
|
|
403
403
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
404
404
|
isError: false,
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -21,14 +21,18 @@ const GIT_CANDIDATES = [
|
|
|
21
21
|
"git"
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
// Timeout configurável via variável de ambiente (default:
|
|
25
|
-
const GIT_TIMEOUT = parseInt(process.env.GIT_TIMEOUT_MS || "
|
|
24
|
+
// Timeout configurável via variável de ambiente (default: 120 segundos para suportar drives de rede)
|
|
25
|
+
const GIT_TIMEOUT = parseInt(process.env.GIT_TIMEOUT_MS || "120000", 10);
|
|
26
26
|
|
|
27
27
|
export class GitAdapter {
|
|
28
28
|
constructor(providerManager) {
|
|
29
29
|
this.pm = providerManager;
|
|
30
30
|
this.gitPath = "git"; // Initial default
|
|
31
|
-
this.gitEnv = {
|
|
31
|
+
this.gitEnv = {
|
|
32
|
+
...process.env,
|
|
33
|
+
LANG: "en_US.UTF-8",
|
|
34
|
+
GIT_OPTIONAL_LOCKS: "0", // Evita operações de background que seguram lock (importante para drives de rede)
|
|
35
|
+
};
|
|
32
36
|
this.timeout = GIT_TIMEOUT;
|
|
33
37
|
this.resolvePromise = this._resolveGit();
|
|
34
38
|
}
|
|
@@ -68,7 +72,7 @@ export class GitAdapter {
|
|
|
68
72
|
let stdout = [];
|
|
69
73
|
let stderr = [];
|
|
70
74
|
let killed = false;
|
|
71
|
-
|
|
75
|
+
|
|
72
76
|
// Timeout handler
|
|
73
77
|
const timeoutId = setTimeout(() => {
|
|
74
78
|
killed = true;
|
|
@@ -87,7 +91,7 @@ export class GitAdapter {
|
|
|
87
91
|
cp.on("close", code => {
|
|
88
92
|
clearTimeout(timeoutId);
|
|
89
93
|
if (killed) return; // Already rejected by timeout
|
|
90
|
-
|
|
94
|
+
|
|
91
95
|
const outStr = Buffer.concat(stdout).toString("utf8").trim();
|
|
92
96
|
const errStr = Buffer.concat(stderr).toString("utf8").trim();
|
|
93
97
|
|
|
@@ -109,7 +113,7 @@ export class GitAdapter {
|
|
|
109
113
|
return output;
|
|
110
114
|
} catch (e) {
|
|
111
115
|
const msg = e.message || "";
|
|
112
|
-
|
|
116
|
+
|
|
113
117
|
// Auto-fix 1: dubious ownership error (common on network shares)
|
|
114
118
|
if (msg.includes("dubious ownership") && !options._retried) {
|
|
115
119
|
console.error("[GitAdapter] Auto-fix: dubious ownership, adding safe.directory...");
|
|
@@ -120,21 +124,52 @@ export class GitAdapter {
|
|
|
120
124
|
console.error("[GitAdapter] Failed to configure safe.directory:", configError.message);
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
// Auto-fix 2: Lock file presente (outro processo git pode ter crashado)
|
|
125
|
-
if ((msg.includes(".git/index.lock") || msg.includes("Unable to create") && msg.includes("lock")) && !options._lockRetried) {
|
|
129
|
+
if ((msg.includes(".git/index.lock") || (msg.includes("Unable to create") && msg.includes("lock"))) && !options._lockRetried) {
|
|
126
130
|
const lockPath = path.join(dir, ".git", "index.lock");
|
|
131
|
+
let shouldDelete = false;
|
|
132
|
+
|
|
127
133
|
if (fs.existsSync(lockPath)) {
|
|
128
|
-
console.error("[GitAdapter] Auto-fix: removing stale .git/index.lock...");
|
|
129
134
|
try {
|
|
130
|
-
fs.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const stats = fs.statSync(lockPath);
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
const ageMs = now - stats.mtimeMs;
|
|
138
|
+
// Se arquivo tem mais de 5 minutos ou se o erro já persiste (implicado pelo fato de estarmos aqui) -> Deletar
|
|
139
|
+
// Para redes lentas, 5 min é seguro.
|
|
140
|
+
if (ageMs > 5 * 60 * 1000) {
|
|
141
|
+
console.error(`[GitAdapter] Lock file is old (${Math.round(ageMs / 1000)}s), deleting...`);
|
|
142
|
+
shouldDelete = true;
|
|
143
|
+
} else {
|
|
144
|
+
// Se é recente, pode ser um processo ativo. Mas como falhou o comando, assumimos que bloqueou.
|
|
145
|
+
// Em execuções agenticas sequenciais, dificilmente tem outro processo legítimo rodando ao mesmo tempo.
|
|
146
|
+
console.error(`[GitAdapter] Lock file found. Agentic force cleanup activated.`);
|
|
147
|
+
shouldDelete = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (shouldDelete) {
|
|
151
|
+
console.error("[GitAdapter] Auto-fix: removing stale .git/index.lock...");
|
|
152
|
+
try {
|
|
153
|
+
fs.unlinkSync(lockPath);
|
|
154
|
+
} catch (unlinkErr) {
|
|
155
|
+
console.error("[GitAdapter] Failed to delete lock file:", unlinkErr.message);
|
|
156
|
+
// Set shouldDelete to false so we don't try to retry if deletion failed
|
|
157
|
+
shouldDelete = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (shouldDelete) {
|
|
161
|
+
// Espera um pouco para o sistema de arquivos (especialmente SMB) registrar
|
|
162
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
163
|
+
return await this._exec(dir, args, { ...options, _lockRetried: true });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (fsError) {
|
|
167
|
+
console.error("[GitAdapter] Error handling lock file:", fsError.message);
|
|
168
|
+
// Continua para lançar o erro original
|
|
134
169
|
}
|
|
135
170
|
}
|
|
136
171
|
}
|
|
137
|
-
|
|
172
|
+
|
|
138
173
|
// Auto-fix 3: CRLF warnings (just retry with autocrlf config)
|
|
139
174
|
if (msg.includes("CRLF will be replaced") && !options._crlfRetried) {
|
|
140
175
|
console.error("[GitAdapter] Auto-fix: configuring core.autocrlf...");
|
|
@@ -147,7 +182,7 @@ export class GitAdapter {
|
|
|
147
182
|
}
|
|
148
183
|
|
|
149
184
|
if (options.ignoreErrors) return "";
|
|
150
|
-
|
|
185
|
+
|
|
151
186
|
// Enhance error message with context
|
|
152
187
|
const cmdStr = `git ${args.join(" ")}`;
|
|
153
188
|
const enhancedError = new Error(`Command failed: ${cmdStr}\n${msg}`);
|
|
@@ -182,12 +217,12 @@ export class GitAdapter {
|
|
|
182
217
|
*/
|
|
183
218
|
async checkIntegrity(dir) {
|
|
184
219
|
await this._ensureGitRepo(dir);
|
|
185
|
-
|
|
220
|
+
|
|
186
221
|
try {
|
|
187
222
|
// Verificação rápida de objetos
|
|
188
223
|
const output = await this._exec(dir, ["fsck", "--connectivity-only"], { ignoreErrors: true });
|
|
189
224
|
const hasErrors = output.includes("error") || output.includes("missing") || output.includes("broken");
|
|
190
|
-
|
|
225
|
+
|
|
191
226
|
return {
|
|
192
227
|
ok: !hasErrors,
|
|
193
228
|
output: output || "Repository integrity OK",
|
|
@@ -211,7 +246,7 @@ export class GitAdapter {
|
|
|
211
246
|
if (!url || typeof url !== "string") {
|
|
212
247
|
return { valid: false, error: "URL vazia ou inválida" };
|
|
213
248
|
}
|
|
214
|
-
|
|
249
|
+
|
|
215
250
|
// Padrões válidos de URL git
|
|
216
251
|
const patterns = [
|
|
217
252
|
/^https?:\/\/[^\s]+\.git$/i, // https://github.com/user/repo.git
|
|
@@ -220,9 +255,9 @@ export class GitAdapter {
|
|
|
220
255
|
/^git:\/\/[^\s]+\.git$/i, // git://github.com/user/repo.git
|
|
221
256
|
/^ssh:\/\/[^\s]+\.git$/i, // ssh://git@github.com/user/repo.git
|
|
222
257
|
];
|
|
223
|
-
|
|
258
|
+
|
|
224
259
|
const isValid = patterns.some(p => p.test(url));
|
|
225
|
-
|
|
260
|
+
|
|
226
261
|
if (!isValid) {
|
|
227
262
|
return {
|
|
228
263
|
valid: false,
|
|
@@ -230,7 +265,7 @@ export class GitAdapter {
|
|
|
230
265
|
suggestion: "Use: https://github.com/user/repo.git ou git@github.com:user/repo.git"
|
|
231
266
|
};
|
|
232
267
|
}
|
|
233
|
-
|
|
268
|
+
|
|
234
269
|
return { valid: true, url };
|
|
235
270
|
}
|
|
236
271
|
|
|
@@ -291,18 +326,18 @@ export class GitAdapter {
|
|
|
291
326
|
|
|
292
327
|
const currentBranch = await this.getCurrentBranch(dir);
|
|
293
328
|
return {
|
|
294
|
-
modified,
|
|
295
|
-
created,
|
|
296
|
-
deleted,
|
|
297
|
-
renamed: [],
|
|
329
|
+
modified,
|
|
330
|
+
created,
|
|
331
|
+
deleted,
|
|
332
|
+
renamed: [],
|
|
298
333
|
notAdded,
|
|
299
334
|
// Aliases para compatibilidade
|
|
300
335
|
not_added: notAdded,
|
|
301
|
-
staged,
|
|
336
|
+
staged,
|
|
302
337
|
conflicted: [],
|
|
303
338
|
currentBranch,
|
|
304
339
|
current: currentBranch, // Alias para compatibilidade
|
|
305
|
-
isClean: lines.length === 0,
|
|
340
|
+
isClean: lines.length === 0,
|
|
306
341
|
files
|
|
307
342
|
};
|
|
308
343
|
}
|
|
@@ -413,19 +448,19 @@ export class GitAdapter {
|
|
|
413
448
|
} catch (e) {
|
|
414
449
|
const msg = e.message || "";
|
|
415
450
|
const msgLower = msg.toLowerCase();
|
|
416
|
-
|
|
451
|
+
|
|
417
452
|
// Auto-correção: Se branch não existe no remote, tenta com -u (set-upstream)
|
|
418
|
-
const branchNotExists = msgLower.includes("has no upstream branch") ||
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
453
|
+
const branchNotExists = msgLower.includes("has no upstream branch") ||
|
|
454
|
+
msgLower.includes("no upstream branch") ||
|
|
455
|
+
(msgLower.includes("remote branch") && msgLower.includes("does not exist")) ||
|
|
456
|
+
msgLower.includes("ref_not_found") ||
|
|
457
|
+
(msgLower.includes("fatal") && msgLower.includes("current branch") && msgLower.includes("has no upstream"));
|
|
458
|
+
|
|
424
459
|
if (branchNotExists && !setUpstream && !force) {
|
|
425
460
|
console.error(`[GitAdapter] Auto-fix: branch '${branch}' não existe no remote '${remote}', tentando com --set-upstream (-u)...`);
|
|
426
461
|
return await this.pushOne(dir, remote, branch, force, true);
|
|
427
462
|
}
|
|
428
|
-
|
|
463
|
+
|
|
429
464
|
if (msg.includes("rejected") || msgLower.includes("non-fast-forward")) {
|
|
430
465
|
throw createError("PUSH_REJECTED", { message: msg, remote, branch });
|
|
431
466
|
}
|
|
@@ -450,22 +485,22 @@ export class GitAdapter {
|
|
|
450
485
|
} catch (error) {
|
|
451
486
|
const errorMsg = error.message || String(error);
|
|
452
487
|
const errorMsgLower = errorMsg.toLowerCase();
|
|
453
|
-
|
|
488
|
+
|
|
454
489
|
// Auto-correção 1: Se repositório não existe, cria automaticamente
|
|
455
|
-
const repoNotFound = errorMsgLower.includes("repository not found") ||
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
490
|
+
const repoNotFound = errorMsgLower.includes("repository not found") ||
|
|
491
|
+
errorMsgLower.includes("repo not found") ||
|
|
492
|
+
(errorMsgLower.includes("fatal") && errorMsgLower.includes("repository") && errorMsgLower.includes("not found")) ||
|
|
493
|
+
(errorMsgLower.includes("not found") && (errorMsgLower.includes("remote") || errorMsgLower.includes("404")));
|
|
494
|
+
|
|
460
495
|
if (repoNotFound && !force) {
|
|
461
496
|
try {
|
|
462
497
|
console.error(`[GitAdapter] Auto-fix: repositório não existe no remote '${remote}', criando automaticamente...`);
|
|
463
498
|
const repoName = getRepoNameFromPath(dir);
|
|
464
499
|
const ensured = await this.pm.ensureRepos({ repoName, createIfMissing: true, isPublic: false });
|
|
465
|
-
|
|
500
|
+
|
|
466
501
|
// Atualiza remotes após criar repo
|
|
467
502
|
await this.ensureRemotes(dir, {});
|
|
468
|
-
|
|
503
|
+
|
|
469
504
|
// Tenta push novamente
|
|
470
505
|
await this.pushOne(dir, remote, branch, force, true); // Usa -u para criar branch também
|
|
471
506
|
return { remote, success: true, repoCreated: true, setUpstream: true };
|
|
@@ -473,13 +508,13 @@ export class GitAdapter {
|
|
|
473
508
|
return { remote, success: false, error: `Falhou ao criar repo: ${repoCreateError.message}`, triedCreateRepo: true };
|
|
474
509
|
}
|
|
475
510
|
}
|
|
476
|
-
|
|
511
|
+
|
|
477
512
|
// Auto-correção 2: Se branch não existe no remote, tenta com --set-upstream
|
|
478
|
-
const branchNotExists = errorMsgLower.includes("has no upstream branch") ||
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
513
|
+
const branchNotExists = errorMsgLower.includes("has no upstream branch") ||
|
|
514
|
+
errorMsgLower.includes("no upstream branch") ||
|
|
515
|
+
(errorMsgLower.includes("remote branch") && errorMsgLower.includes("does not exist")) ||
|
|
516
|
+
errorMsgLower.includes("ref_not_found");
|
|
517
|
+
|
|
483
518
|
if (branchNotExists && !force) {
|
|
484
519
|
try {
|
|
485
520
|
console.error(`[GitAdapter] Auto-fix: branch '${branch}' não existe no remote '${remote}', tentando com --set-upstream...`);
|
|
@@ -489,7 +524,7 @@ export class GitAdapter {
|
|
|
489
524
|
return { remote, success: false, error: retryError.message, triedSetUpstream: true };
|
|
490
525
|
}
|
|
491
526
|
}
|
|
492
|
-
|
|
527
|
+
|
|
493
528
|
return { remote, success: false, error: errorMsg };
|
|
494
529
|
}
|
|
495
530
|
});
|
|
@@ -499,17 +534,17 @@ export class GitAdapter {
|
|
|
499
534
|
const failed = results.filter(r => r.status === "rejected" || !r.value.success);
|
|
500
535
|
|
|
501
536
|
if (successful.length === 0) {
|
|
502
|
-
throw createError("PUSH_REJECTED", {
|
|
503
|
-
message: "Push falhou para todos os remotes",
|
|
504
|
-
errors: failed.map(f => f.value?.error || f.reason?.message || "Erro desconhecido")
|
|
537
|
+
throw createError("PUSH_REJECTED", {
|
|
538
|
+
message: "Push falhou para todos os remotes",
|
|
539
|
+
errors: failed.map(f => f.value?.error || f.reason?.message || "Erro desconhecido")
|
|
505
540
|
});
|
|
506
541
|
}
|
|
507
542
|
|
|
508
543
|
return {
|
|
509
544
|
pushed: successful.map(s => s.value.remote),
|
|
510
|
-
failed: failed.map(f => ({
|
|
511
|
-
remote: f.value?.remote || "unknown",
|
|
512
|
-
error: f.value?.error || f.reason?.message || "Erro desconhecido"
|
|
545
|
+
failed: failed.map(f => ({
|
|
546
|
+
remote: f.value?.remote || "unknown",
|
|
547
|
+
error: f.value?.error || f.reason?.message || "Erro desconhecido"
|
|
513
548
|
}))
|
|
514
549
|
};
|
|
515
550
|
}
|
|
@@ -709,7 +744,7 @@ export class GitAdapter {
|
|
|
709
744
|
async resetSoft(dir, ref) { await this._exec(dir, ["reset", "--soft", ref]); }
|
|
710
745
|
async resetMixed(dir, ref) { await this._exec(dir, ["reset", "--mixed", ref]); }
|
|
711
746
|
async resetHard(dir, ref) { await this._exec(dir, ["reset", "--hard", ref]); }
|
|
712
|
-
|
|
747
|
+
|
|
713
748
|
async resetHardClean(dir, ref) {
|
|
714
749
|
await this._exec(dir, ["reset", "--hard", ref]);
|
|
715
750
|
const cleanResult = await this.cleanUntracked(dir);
|
|
@@ -719,19 +754,19 @@ export class GitAdapter {
|
|
|
719
754
|
async getMergeStatus(dir) {
|
|
720
755
|
const mergeHeadPath = path.join(dir, ".git", "MERGE_HEAD");
|
|
721
756
|
const isMerging = fs.existsSync(mergeHeadPath);
|
|
722
|
-
|
|
757
|
+
|
|
723
758
|
if (!isMerging) {
|
|
724
759
|
return { merging: false, message: "Nenhum merge em andamento" };
|
|
725
760
|
}
|
|
726
|
-
|
|
761
|
+
|
|
727
762
|
// Get conflicted files
|
|
728
763
|
try {
|
|
729
764
|
const diff = await this._exec(dir, ["diff", "--name-only", "--diff-filter=U"]);
|
|
730
765
|
const conflicts = diff.split("\n").filter(Boolean);
|
|
731
|
-
return {
|
|
732
|
-
merging: true,
|
|
766
|
+
return {
|
|
767
|
+
merging: true,
|
|
733
768
|
conflicts,
|
|
734
|
-
message: conflicts.length > 0
|
|
769
|
+
message: conflicts.length > 0
|
|
735
770
|
? `Merge em andamento com ${conflicts.length} conflito(s)`
|
|
736
771
|
: "Merge em andamento sem conflitos"
|
|
737
772
|
};
|
|
@@ -841,7 +876,7 @@ export class GitAdapter {
|
|
|
841
876
|
}
|
|
842
877
|
|
|
843
878
|
// ============ GIT LFS SUPPORT ============
|
|
844
|
-
|
|
879
|
+
|
|
845
880
|
/**
|
|
846
881
|
* Verifica se Git LFS está instalado
|
|
847
882
|
*/
|