@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "15.8.6",
3
+ "version": "15.9.1",
4
4
  "private": false,
5
5
  "description": "MCP server para Git com operações locais e sincronização paralela GitHub/Gitea",
6
6
  "license": "MIT",
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
- const progressToken = req.params?._meta?.progressToken;
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
- await server.notification({ method: "notifications/progress", params: { progressToken, progress: 0 } });
100
- }
100
+ // if (progressToken) { await server.notification({ method: "notifications/progress", params: { progressToken, progress: 0 } }); }
101
+
101
102
  const result = await tool.handle(args);
102
- if (progressToken) {
103
- await server.notification({ method: "notifications/progress", params: { progressToken, progress: 100 } });
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
- (value.startsWith("'") && value.endsWith("'"))) {
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: 60000)",
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
  };
@@ -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. Se não, delete o arquivo de lock manualmente"
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
- msg.includes("missing object") || msg.includes("invalid sha") || msg.includes("fatal: packed")) {
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
- msg.includes("no address") || msg.includes("unreachable")) {
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,
@@ -21,14 +21,18 @@ const GIT_CANDIDATES = [
21
21
  "git"
22
22
  ];
23
23
 
24
- // Timeout configurável via variável de ambiente (default: 60 segundos)
25
- const GIT_TIMEOUT = parseInt(process.env.GIT_TIMEOUT_MS || "60000", 10);
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 = { ...process.env, LANG: "en_US.UTF-8" }; // Base env
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.unlinkSync(lockPath);
131
- return await this._exec(dir, args, { ...options, _lockRetried: true });
132
- } catch (unlinkError) {
133
- console.error("[GitAdapter] Failed to remove lock file:", unlinkError.message);
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
- msgLower.includes("no upstream branch") ||
420
- (msgLower.includes("remote branch") && msgLower.includes("does not exist")) ||
421
- msgLower.includes("ref_not_found") ||
422
- (msgLower.includes("fatal") && msgLower.includes("current branch") && msgLower.includes("has no upstream"));
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
- errorMsgLower.includes("repo not found") ||
457
- (errorMsgLower.includes("fatal") && errorMsgLower.includes("repository") && errorMsgLower.includes("not found")) ||
458
- (errorMsgLower.includes("not found") && (errorMsgLower.includes("remote") || errorMsgLower.includes("404")));
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
- errorMsgLower.includes("no upstream branch") ||
480
- (errorMsgLower.includes("remote branch") && errorMsgLower.includes("does not exist")) ||
481
- errorMsgLower.includes("ref_not_found");
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
  */