@andre.buzeli/git-mcp 16.0.0 → 16.0.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": "@andre.buzeli/git-mcp",
3
- "version": "16.0.0",
3
+ "version": "16.0.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",
@@ -167,7 +167,7 @@ export const ERROR_CODES = {
167
167
  // Git Corruption
168
168
  GIT_CORRUPTED: {
169
169
  code: "GIT_CORRUPTED",
170
- suggestion: "Repositório Git corrompido. Tente: 1) git fsck --full, 2) git gc --prune=now, ou 3) re-clone do remote"
170
+ suggestion: "Repositório Git corrompido. O sistema tentou auto-corrigir (remove index, gc --prune=now, re-add). Se persistir, re-clone do remote"
171
171
  },
172
172
  GIT_LOCK_FILE: {
173
173
  code: "GIT_LOCK_FILE",
@@ -312,7 +312,9 @@ export function mapExternalError(error, context = {}) {
312
312
 
313
313
  // Git corruption
314
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")) {
315
+ msg.includes("missing object") || msg.includes("invalid sha") || msg.includes("fatal: packed") ||
316
+ msg.includes("invalid object") || msg.includes("error building trees") ||
317
+ msg.includes("unable to read tree")) {
316
318
  return createError("GIT_CORRUPTED", { originalError: msg });
317
319
  }
318
320
 
@@ -181,6 +181,124 @@ export class GitAdapter {
181
181
  }
182
182
  }
183
183
 
184
+ // Auto-fix 4: Git object corruption (invalid object, error building trees, bad object, unable to read tree)
185
+ if ((msg.includes("invalid object") || msg.includes("error building trees") ||
186
+ msg.includes("bad object") || msg.includes("corrupt") ||
187
+ msg.includes("missing object") || msg.includes("broken link") ||
188
+ msg.includes("unable to read tree")) &&
189
+ !options._corruptionRetried) {
190
+ console.error("[GitAdapter] Auto-fix: git object corruption detected, attempting multi-step recovery...");
191
+ try {
192
+ // Step 1: Remove corrupt index file
193
+ const indexPath = path.join(dir, ".git", "index");
194
+ if (fs.existsSync(indexPath)) {
195
+ try { fs.unlinkSync(indexPath); } catch { }
196
+ console.error("[GitAdapter] Removed corrupt index file");
197
+ }
198
+
199
+ // Step 2: Try to clean corrupt refs (branches pointing to bad objects)
200
+ try {
201
+ const packedRefsPath = path.join(dir, ".git", "packed-refs");
202
+ if (fs.existsSync(packedRefsPath)) {
203
+ // Read packed refs and remove lines referencing corrupt objects
204
+ const content = fs.readFileSync(packedRefsPath, "utf8");
205
+ const lines = content.split("\n");
206
+ const cleanLines = lines.filter(line => {
207
+ // Keep header and non-ref lines
208
+ if (line.startsWith("#") || !line.trim()) return true;
209
+ // Keep lines that DON'T reference tmp/ or test branches
210
+ return !line.includes("tmp/") && !line.includes("test-");
211
+ });
212
+ if (cleanLines.length !== lines.length) {
213
+ fs.writeFileSync(packedRefsPath, cleanLines.join("\n"), "utf8");
214
+ console.error(`[GitAdapter] Cleaned ${lines.length - cleanLines.length} potentially corrupt packed refs`);
215
+ }
216
+ }
217
+ } catch { }
218
+
219
+ // Step 3: Remove loose corrupt objects (they'll be re-created)
220
+ try {
221
+ const objectsDir = path.join(dir, ".git", "objects");
222
+ // Run fsck to identify broken objects (ignore errors)
223
+ const fsckOutput = await this._runSpawn(this.gitPath, ["fsck", "--no-dangling"], dir).catch(e => e.message || "");
224
+ const brokenShas = [];
225
+ const fsckLines = (typeof fsckOutput === "string" ? fsckOutput : "").split("\n");
226
+ for (const line of fsckLines) {
227
+ // Match patterns like "error in tree SHA: invalid object"
228
+ const shaMatch = line.match(/([0-9a-f]{40})/);
229
+ if (shaMatch && (line.includes("invalid") || line.includes("corrupt") || line.includes("broken") || line.includes("missing"))) {
230
+ brokenShas.push(shaMatch[1]);
231
+ }
232
+ }
233
+ // Remove identified corrupt loose objects
234
+ for (const sha of brokenShas) {
235
+ const objPath = path.join(objectsDir, sha.substring(0, 2), sha.substring(2));
236
+ if (fs.existsSync(objPath)) {
237
+ try { fs.unlinkSync(objPath); console.error(`[GitAdapter] Removed corrupt object: ${sha.substring(0, 8)}`); } catch { }
238
+ }
239
+ }
240
+ } catch { }
241
+
242
+ // Step 4: Try gc (may fail with deep corruption, that's OK)
243
+ await this._runSpawn(this.gitPath, ["reflog", "expire", "--expire=now", "--all"], dir).catch(() => { });
244
+ await this._runSpawn(this.gitPath, ["gc", "--prune=now", "--aggressive"], dir).catch(() => { });
245
+
246
+ // Step 5: Try simple reset first
247
+ let resetOk = false;
248
+ try {
249
+ await this._runSpawn(this.gitPath, ["reset"], dir);
250
+ resetOk = true;
251
+ } catch {
252
+ // Reset failed (HEAD tree is corrupt) - try nuclear recovery
253
+ console.error("[GitAdapter] Simple reset failed, attempting nuclear recovery...");
254
+ try {
255
+ // Re-add all working directory files to create fresh objects
256
+ await this._runSpawn(this.gitPath, ["add", "."], dir);
257
+ // Create new tree from working directory
258
+ const newTree = await this._runSpawn(this.gitPath, ["write-tree"], dir);
259
+ if (newTree && newTree.match(/^[0-9a-f]{40}$/)) {
260
+ // Find a valid parent commit (walk back until we find one)
261
+ let parentArg = [];
262
+ for (let i = 1; i <= 10; i++) {
263
+ try {
264
+ const parentSha = await this._runSpawn(this.gitPath, ["rev-parse", `HEAD~${i}`], dir);
265
+ if (parentSha && parentSha.match(/^[0-9a-f]{40}$/)) {
266
+ parentArg = ["-p", parentSha];
267
+ console.error(`[GitAdapter] Found valid parent at HEAD~${i}: ${parentSha.substring(0, 8)}`);
268
+ break;
269
+ }
270
+ } catch { continue; }
271
+ }
272
+ // Create recovery commit using low-level git commands
273
+ const newCommit = await this._runSpawn(this.gitPath,
274
+ ["commit-tree", newTree, ...parentArg, "-m", "chore: auto-recovery from git object corruption"], dir);
275
+ if (newCommit && newCommit.match(/^[0-9a-f]{40}$/)) {
276
+ // Point HEAD to the new clean commit
277
+ await this._runSpawn(this.gitPath, ["update-ref", "HEAD", newCommit], dir);
278
+ console.error(`[GitAdapter] Nuclear recovery complete: created clean commit ${newCommit.substring(0, 8)}`);
279
+ resetOk = true;
280
+ }
281
+ }
282
+ } catch (nuclearError) {
283
+ console.error("[GitAdapter] Nuclear recovery failed:", nuclearError.message);
284
+ }
285
+ }
286
+
287
+ // Step 6: Final re-add (rebuild index from working directory)
288
+ await this._runSpawn(this.gitPath, ["add", "."], dir).catch(() => { });
289
+
290
+ if (resetOk) {
291
+ console.error("[GitAdapter] Recovery complete, retrying operation...");
292
+ return await this._exec(dir, args, { ...options, _corruptionRetried: true });
293
+ } else {
294
+ console.error("[GitAdapter] Recovery partially failed. Retrying anyway...");
295
+ return await this._exec(dir, args, { ...options, _corruptionRetried: true });
296
+ }
297
+ } catch (recoveryError) {
298
+ console.error("[GitAdapter] Recovery failed:", recoveryError.message);
299
+ }
300
+ }
301
+
184
302
  if (options.ignoreErrors) return "";
185
303
 
186
304
  // Enhance error message with context