@andrebuzeli/git-mcp 10.0.6 → 10.0.8

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.
Files changed (40) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +1 -1
  3. package/dist/scripts/test_e2e.d.ts +1 -0
  4. package/dist/scripts/test_e2e.js +199 -0
  5. package/dist/scripts/test_exhaustive.d.ts +1 -0
  6. package/dist/scripts/test_exhaustive.js +275 -0
  7. package/dist/scripts/verify_setup.d.ts +1 -0
  8. package/dist/scripts/verify_setup.js +61 -0
  9. package/dist/tools/gitAnalytics.js +3 -3
  10. package/dist/tools/gitBackup.d.ts +2 -2
  11. package/dist/tools/gitBackup.js +5 -5
  12. package/dist/tools/gitBranches.js +40 -29
  13. package/dist/tools/gitConfig.d.ts +2 -2
  14. package/dist/tools/gitConfig.js +12 -13
  15. package/dist/tools/gitFix.d.ts +2 -1
  16. package/dist/tools/gitFix.js +20 -24
  17. package/dist/tools/gitFix.tool.d.ts +2 -2
  18. package/dist/tools/gitFix.tool.js +2 -2
  19. package/dist/tools/gitHistory.js +4 -5
  20. package/dist/tools/gitIssues.js +8 -8
  21. package/dist/tools/gitMonitor.js +28 -33
  22. package/dist/tools/gitPulls.js +8 -8
  23. package/dist/tools/gitRelease.js +5 -6
  24. package/dist/tools/gitRemote.d.ts +1 -28
  25. package/dist/tools/gitRemote.js +19 -23
  26. package/dist/tools/gitReset.js +6 -7
  27. package/dist/tools/gitStash.d.ts +1 -12
  28. package/dist/tools/gitStash.js +11 -22
  29. package/dist/tools/gitSync.js +44 -36
  30. package/dist/tools/gitTags.d.ts +8 -0
  31. package/dist/tools/gitTags.js +44 -34
  32. package/dist/tools/gitUpdate.d.ts +27 -0
  33. package/dist/tools/gitUpdate.js +61 -34
  34. package/dist/tools/gitUpload.js +29 -44
  35. package/dist/tools/gitWorkflow.js +27 -46
  36. package/package.json +2 -3
  37. package/dist/server-new.d.ts +0 -2
  38. package/dist/server-new.js +0 -224
  39. package/dist/tools/gitFiles-new.d.ts +0 -89
  40. package/dist/tools/gitFiles-new.js +0 -335
@@ -1,4 +1,3 @@
1
- import simpleGit from 'simple-git';
2
1
  import { MCPError } from '../utils/errors.js';
3
2
  import axios from 'axios';
4
3
  import { getRepoInfo } from '../utils/repoHelpers.js';
@@ -30,9 +29,17 @@ export class GitTagsTool {
30
29
  type: "string",
31
30
  description: "Tag annotation message (optional for create)"
32
31
  },
32
+ annotated: {
33
+ type: "boolean",
34
+ description: "Create annotated tag (optional for create, default: false)"
35
+ },
33
36
  force: {
34
37
  type: "boolean",
35
38
  description: "Force create/delete/push tag (default: true)"
39
+ },
40
+ remote: {
41
+ type: "string",
42
+ description: "Remote name (optional for push, default: origin)"
36
43
  }
37
44
  },
38
45
  required: ["projectPath", "action"]
@@ -44,37 +51,37 @@ export class GitTagsTool {
44
51
  if (!action || !projectPath) {
45
52
  throw new MCPError('VALIDATION_ERROR', 'action and projectPath are required');
46
53
  }
47
- const git = simpleGit({ baseDir: projectPath });
54
+ if (!ctx.gitAdapter) {
55
+ throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
56
+ }
48
57
  switch (action) {
49
58
  case 'create': {
50
59
  const tagName = params.tagName;
51
60
  if (!tagName)
52
61
  throw new MCPError('VALIDATION_ERROR', 'tagName is required');
53
- if (params.annotated) {
54
- await git.addAnnotatedTag(tagName, params.message || tagName);
55
- }
56
- else {
57
- await git.addTag(tagName);
58
- }
62
+ const ref = params.ref || 'HEAD';
63
+ const message = params.annotated ? (params.message || tagName) : undefined;
64
+ await ctx.gitAdapter.createTag(projectPath, tagName, ref, message);
59
65
  return { success: true, tag: tagName, local: true };
60
66
  }
61
67
  case 'list': {
62
- const localTags = await git.tags();
68
+ const localTags = await ctx.gitAdapter.listTags(projectPath);
63
69
  const results = {
64
70
  success: true,
65
- local: { tags: localTags.all },
71
+ local: { tags: localTags },
66
72
  providers: {}
67
73
  };
68
74
  // Also query remote APIs
69
75
  const repoInfo = getRepoInfo(projectPath);
70
- const githubOwner = process.env.GITHUB_USERNAME;
71
- const giteaOwner = process.env.GITEA_USERNAME;
76
+ const repo = params.repo || repoInfo.repoName;
77
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
78
+ const giteaOwner = params.owner || process.env.GITEA_USERNAME;
72
79
  // GitHub
73
80
  if (ctx.providerManager.github && githubOwner) {
74
81
  try {
75
82
  const tags = await ctx.providerManager.github.rest.repos.listTags({
76
83
  owner: githubOwner,
77
- repo: repoInfo.repoName,
84
+ repo: repo,
78
85
  });
79
86
  results.providers.github = {
80
87
  success: true,
@@ -93,7 +100,7 @@ export class GitTagsTool {
93
100
  // Gitea
94
101
  if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
95
102
  try {
96
- const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
103
+ const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
97
104
  results.providers.gitea = {
98
105
  success: true,
99
106
  tags: tags.data.map((t) => ({
@@ -114,30 +121,24 @@ export class GitTagsTool {
114
121
  const tagName = params.tagName;
115
122
  if (!tagName)
116
123
  throw new MCPError('VALIDATION_ERROR', 'tagName is required');
117
- const localTags = await git.tags();
118
- const localTag = localTags.all.find(t => t === tagName);
124
+ const localTags = await ctx.gitAdapter.listTags(projectPath);
125
+ const localTag = localTags.find(t => t === tagName);
119
126
  const results = {
120
127
  success: true,
121
128
  local: localTag ? { found: true, tag: tagName } : { found: false },
122
129
  providers: {}
123
130
  };
124
- if (localTag) {
125
- try {
126
- const show = await git.show([tagName]);
127
- results.local.details = show;
128
- }
129
- catch { }
130
- }
131
131
  // Also query remote APIs
132
132
  const repoInfo = getRepoInfo(projectPath);
133
- const githubOwner = process.env.GITHUB_USERNAME;
134
- const giteaOwner = process.env.GITEA_USERNAME;
133
+ const repo = params.repo || repoInfo.repoName;
134
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
135
+ const giteaOwner = params.owner || process.env.GITEA_USERNAME;
135
136
  // GitHub
136
137
  if (ctx.providerManager.github && githubOwner) {
137
138
  try {
138
139
  const tag = await ctx.providerManager.github.rest.git.getRef({
139
140
  owner: githubOwner,
140
- repo: repoInfo.repoName,
141
+ repo: repo,
141
142
  ref: `tags/${tagName}`,
142
143
  });
143
144
  results.providers.github = {
@@ -157,7 +158,7 @@ export class GitTagsTool {
157
158
  // Gitea
158
159
  if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
159
160
  try {
160
- const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
161
+ const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
161
162
  const tag = tags.data.find((t) => t.name === tagName);
162
163
  if (tag) {
163
164
  results.providers.gitea = {
@@ -183,15 +184,23 @@ export class GitTagsTool {
183
184
  const tagName = params.tagName;
184
185
  if (!tagName)
185
186
  throw new MCPError('VALIDATION_ERROR', 'tagName is required');
186
- await git.tag(['-d', tagName]);
187
+ await ctx.gitAdapter.deleteTag(projectPath, tagName);
187
188
  return { success: true, deleted: tagName, local: true };
188
189
  }
190
+ case 'push': {
191
+ const tagName = params.tagName;
192
+ if (!tagName)
193
+ throw new MCPError('VALIDATION_ERROR', 'tagName is required');
194
+ const remote = params.remote || 'origin';
195
+ await ctx.gitAdapter.pushTag(projectPath, remote, tagName);
196
+ return { success: true, pushed: tagName, remote };
197
+ }
189
198
  case 'search': {
190
199
  const pattern = params.pattern;
191
200
  if (!pattern)
192
201
  throw new MCPError('VALIDATION_ERROR', 'pattern is required');
193
- const localTags = await git.tags();
194
- const filtered = localTags.all.filter(t => t.includes(pattern));
202
+ const localTags = await ctx.gitAdapter.listTags(projectPath);
203
+ const filtered = localTags.filter(t => t.includes(pattern));
195
204
  const results = {
196
205
  success: true,
197
206
  local: { tags: filtered },
@@ -199,14 +208,15 @@ export class GitTagsTool {
199
208
  };
200
209
  // Also search remote APIs
201
210
  const repoInfo = getRepoInfo(projectPath);
202
- const githubOwner = process.env.GITHUB_USERNAME;
203
- const giteaOwner = process.env.GITEA_USERNAME;
211
+ const repo = params.repo || repoInfo.repoName;
212
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
213
+ const giteaOwner = params.owner || process.env.GITEA_USERNAME;
204
214
  // GitHub
205
215
  if (ctx.providerManager.github && githubOwner) {
206
216
  try {
207
217
  const tags = await ctx.providerManager.github.rest.repos.listTags({
208
218
  owner: githubOwner,
209
- repo: repoInfo.repoName,
219
+ repo: repo,
210
220
  });
211
221
  const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
212
222
  results.providers.github = {
@@ -221,7 +231,7 @@ export class GitTagsTool {
221
231
  // Gitea
222
232
  if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
223
233
  try {
224
- const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
234
+ const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
225
235
  const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
226
236
  results.providers.gitea = {
227
237
  success: true,
@@ -22,6 +22,33 @@ export declare class GitUpdateTool implements Tool {
22
22
  type: string;
23
23
  description: string;
24
24
  };
25
+ message: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ commitMessage: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ branch: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ files: {
38
+ type: string;
39
+ items: {
40
+ type: string;
41
+ };
42
+ description: string;
43
+ };
44
+ repoName: {
45
+ type: string;
46
+ description: string;
47
+ };
48
+ description: {
49
+ type: string;
50
+ description: string;
51
+ };
25
52
  };
26
53
  required: string[];
27
54
  additionalProperties: boolean;
@@ -1,4 +1,3 @@
1
- import simpleGit from 'simple-git';
2
1
  import { MCPError } from '../utils/errors.js';
3
2
  import axios from 'axios';
4
3
  import * as fs from 'fs/promises';
@@ -27,6 +26,31 @@ export class GitUpdateTool {
27
26
  owner: {
28
27
  type: "string",
29
28
  description: "Repository owner for providers (optional, uses env vars)"
29
+ },
30
+ message: {
31
+ type: "string",
32
+ description: "Commit message"
33
+ },
34
+ commitMessage: {
35
+ type: "string",
36
+ description: "Commit message (alias)"
37
+ },
38
+ branch: {
39
+ type: "string",
40
+ description: "Branch name"
41
+ },
42
+ files: {
43
+ type: "array",
44
+ items: { type: "string" },
45
+ description: "Files to add"
46
+ },
47
+ repoName: {
48
+ type: "string",
49
+ description: "Repository name"
50
+ },
51
+ description: {
52
+ type: "string",
53
+ description: "Repository description"
30
54
  }
31
55
  },
32
56
  required: ["projectPath"],
@@ -38,10 +62,12 @@ export class GitUpdateTool {
38
62
  if (!projectPath) {
39
63
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required');
40
64
  }
65
+ if (!ctx.gitAdapter) {
66
+ throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
67
+ }
41
68
  const commitMessage = params.message || params.commitMessage || `[git-update] Update at ${new Date().toISOString()}`;
42
69
  const branch = params.branch || 'master';
43
70
  const files = params.files || ['.'];
44
- const git = simpleGit({ baseDir: projectPath });
45
71
  // Resultado com rastreabilidade completa
46
72
  const results = {
47
73
  success: true,
@@ -65,7 +91,7 @@ export class GitUpdateTool {
65
91
  action: 'check_pre_update_status',
66
92
  timestamp: new Date().toISOString(),
67
93
  });
68
- const preStatus = await git.status();
94
+ const preStatus = await ctx.gitAdapter.status(projectPath);
69
95
  results.traceability.preUpdateStatus = {
70
96
  branch: preStatus.current,
71
97
  ahead: preStatus.ahead,
@@ -76,7 +102,7 @@ export class GitUpdateTool {
76
102
  renamed: preStatus.renamed,
77
103
  staged: preStatus.staged,
78
104
  conflicted: preStatus.conflicted,
79
- isClean: preStatus.isClean(),
105
+ isClean: preStatus.isClean,
80
106
  };
81
107
  // 2. Detectar arquivos alterados com diff
82
108
  results.traceability.updateSteps.push({
@@ -84,15 +110,11 @@ export class GitUpdateTool {
84
110
  action: 'detect_changes',
85
111
  timestamp: new Date().toISOString(),
86
112
  });
87
- const diff = await git.diff(['--name-status']);
88
- const diffLines = diff.split('\n').filter(l => l.trim());
89
- results.traceability.changedFiles = diffLines.map(line => {
90
- const [status, ...fileParts] = line.split('\t');
91
- return {
92
- status: status.trim(),
93
- file: fileParts.join('\t').trim(),
94
- };
95
- });
113
+ // Isomorphic git diff is basic, so we rely on status for changed files list
114
+ results.traceability.changedFiles = preStatus.files.map(f => ({
115
+ status: f.working_dir,
116
+ file: f.path
117
+ }));
96
118
  // 3. Add files
97
119
  results.traceability.updateSteps.push({
98
120
  step: 3,
@@ -100,7 +122,7 @@ export class GitUpdateTool {
100
122
  timestamp: new Date().toISOString(),
101
123
  files,
102
124
  });
103
- await git.add(files);
125
+ await ctx.gitAdapter.add(projectPath, files);
104
126
  // 4. Commit
105
127
  results.traceability.updateSteps.push({
106
128
  step: 4,
@@ -110,7 +132,13 @@ export class GitUpdateTool {
110
132
  });
111
133
  let commitResult;
112
134
  try {
113
- commitResult = await git.commit(commitMessage);
135
+ const sha = await ctx.gitAdapter.commit(projectPath, commitMessage);
136
+ commitResult = {
137
+ commit: sha,
138
+ summary: { changes: 0, insertions: 0, deletions: 0 }, // Basic adapter doesn't return stats yet
139
+ branch: await ctx.gitAdapter.getCurrentBranch(projectPath),
140
+ author: { name: 'unknown', email: 'unknown' } // We could fetch this if needed
141
+ };
114
142
  results.traceability.commit = {
115
143
  hash: commitResult.commit,
116
144
  summary: commitResult.summary,
@@ -119,7 +147,7 @@ export class GitUpdateTool {
119
147
  };
120
148
  }
121
149
  catch (err) {
122
- if (err.message.includes('nothing to commit')) {
150
+ if (err.message.includes('nothing to commit') || (preStatus.isClean && files[0] === '.')) {
123
151
  results.traceability.commit = { message: 'No changes to commit' };
124
152
  }
125
153
  else {
@@ -127,7 +155,7 @@ export class GitUpdateTool {
127
155
  }
128
156
  }
129
157
  // 5. Get remotes and ensure repos exist
130
- const remotes = await git.getRemotes(true);
158
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
131
159
  let githubRemote = remotes.find(r => r.name === 'github' || r.name === 'origin');
132
160
  let giteaRemote = remotes.find(r => r.name === 'gitea');
133
161
  // Auto-create repos if they don't exist
@@ -169,8 +197,9 @@ export class GitUpdateTool {
169
197
  });
170
198
  }
171
199
  // Add remote
172
- await git.addRemote('github', `https://github.com/${githubOwner}/${repoName}.git`);
173
- githubRemote = { name: 'github', refs: { push: `https://github.com/${githubOwner}/${repoName}.git`, fetch: '' } };
200
+ const url = `https://github.com/${githubOwner}/${repoName}.git`;
201
+ await ctx.gitAdapter.addRemote(projectPath, 'github', url);
202
+ githubRemote = { name: 'github', remote: 'github', url, fetch: url };
174
203
  }
175
204
  catch (err) {
176
205
  results.traceability.errors.push({
@@ -211,8 +240,9 @@ export class GitUpdateTool {
211
240
  });
212
241
  }
213
242
  // Add remote
214
- await git.addRemote('gitea', `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`);
215
- giteaRemote = { name: 'gitea', refs: { push: `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`, fetch: '' } };
243
+ const url = `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`;
244
+ await ctx.gitAdapter.addRemote(projectPath, 'gitea', url);
245
+ giteaRemote = { name: 'gitea', remote: 'gitea', url, fetch: url };
216
246
  }
217
247
  catch (err) {
218
248
  results.traceability.errors.push({
@@ -233,15 +263,14 @@ export class GitUpdateTool {
233
263
  branch,
234
264
  });
235
265
  try {
236
- const pushResult = await git.push(githubRemote.name, branch);
266
+ await ctx.gitAdapter.push(projectPath, githubRemote.name, branch);
237
267
  results.providers.github = {
238
268
  success: true,
239
269
  remote: githubRemote.name,
240
- url: githubRemote.refs.push,
270
+ url: githubRemote.url,
241
271
  pushed: true,
242
272
  pushResult: {
243
- remoteMessages: pushResult.remoteMessages?.all || [],
244
- update: pushResult.update,
273
+ message: 'Push successful'
245
274
  }
246
275
  };
247
276
  // Verificar commit no GitHub via API
@@ -285,15 +314,14 @@ export class GitUpdateTool {
285
314
  branch,
286
315
  });
287
316
  try {
288
- const pushResult = await git.push(giteaRemote.name, branch);
317
+ await ctx.gitAdapter.push(projectPath, giteaRemote.name, branch);
289
318
  results.providers.gitea = {
290
319
  success: true,
291
320
  remote: giteaRemote.name,
292
- url: giteaRemote.refs.push,
321
+ url: giteaRemote.url,
293
322
  pushed: true,
294
323
  pushResult: {
295
- remoteMessages: pushResult.remoteMessages?.all || [],
296
- update: pushResult.update,
324
+ message: 'Push successful'
297
325
  }
298
326
  };
299
327
  // Verificar commit no Gitea via API
@@ -328,12 +356,12 @@ export class GitUpdateTool {
328
356
  action: 'check_post_update_status',
329
357
  timestamp: new Date().toISOString(),
330
358
  });
331
- const postStatus = await git.status();
359
+ const postStatus = await ctx.gitAdapter.status(projectPath);
332
360
  results.traceability.postUpdateStatus = {
333
361
  branch: postStatus.current,
334
362
  ahead: postStatus.ahead,
335
363
  behind: postStatus.behind,
336
- isClean: postStatus.isClean(),
364
+ isClean: postStatus.isClean,
337
365
  tracking: postStatus.tracking,
338
366
  };
339
367
  // 9. Salvar histórico local
@@ -374,12 +402,11 @@ export class GitUpdateTool {
374
402
  const githubOwner = params.owner || process.env.GITHUB_USERNAME;
375
403
  const giteaOwner = params.owner || process.env.GITEA_USERNAME;
376
404
  // Pegar nome do repo dos remotes do Git ao invés do nome da pasta
377
- const git = simpleGit(projectPath);
378
- const remotes = await git.getRemotes(true);
405
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
379
406
  let repoName = getRepoNameFromPath(projectPath); // fallback
380
407
  if (remotes.length > 0) {
381
408
  const githubRemote = remotes.find(r => r.name === 'github') || remotes.find(r => r.name === 'origin') || remotes[0];
382
- const match = githubRemote.refs.push?.match(/\/([^\/]+?)(\.git)?$/);
409
+ const match = githubRemote.fetch?.match(/\/([^\/]+?)(\.git)?$/);
383
410
  if (match)
384
411
  repoName = match[1];
385
412
  }
@@ -1,4 +1,3 @@
1
- import simpleGit from 'simple-git';
2
1
  import { MCPError } from '../utils/errors.js';
3
2
  import axios from 'axios';
4
3
  import * as fs from 'fs/promises';
@@ -42,11 +41,13 @@ export class GitUploadTool {
42
41
  if (!projectPath) {
43
42
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required');
44
43
  }
44
+ if (!ctx.gitAdapter) {
45
+ throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
46
+ }
45
47
  const repoName = params.repoName || getRepoNameFromPath(projectPath);
46
48
  const description = params.description || `Project uploaded via git-upload at ${new Date().toISOString()}`;
47
49
  const isPrivate = params.private !== undefined ? params.private : true;
48
50
  const branch = params.branch || 'master';
49
- const git = simpleGit({ baseDir: projectPath });
50
51
  // Resultado com rastreabilidade completa
51
52
  const results = {
52
53
  success: true,
@@ -64,20 +65,16 @@ export class GitUploadTool {
64
65
  try {
65
66
  // 0. Verificar se é um repositório Git, se não for, inicializar
66
67
  try {
67
- await git.status();
68
+ await ctx.gitAdapter.status(projectPath);
68
69
  }
69
70
  catch (err) {
70
- if (err.message.includes('not a git repository')) {
71
- results.traceability.uploadSteps.push({
72
- step: 0,
73
- action: 'init_git_repository',
74
- timestamp: new Date().toISOString(),
75
- });
76
- await git.init();
77
- }
78
- else {
79
- throw err;
80
- }
71
+ // If status fails, assume not a git repo or other error
72
+ results.traceability.uploadSteps.push({
73
+ step: 0,
74
+ action: 'init_git_repository',
75
+ timestamp: new Date().toISOString(),
76
+ });
77
+ await ctx.gitAdapter.init(projectPath, branch);
81
78
  }
82
79
  // 1. Verificar status local
83
80
  results.traceability.uploadSteps.push({
@@ -85,7 +82,7 @@ export class GitUploadTool {
85
82
  action: 'check_local_status',
86
83
  timestamp: new Date().toISOString(),
87
84
  });
88
- const status = await git.status();
85
+ const status = await ctx.gitAdapter.status(projectPath);
89
86
  results.traceability.localChanges = {
90
87
  modified: status.modified,
91
88
  created: status.created,
@@ -93,17 +90,17 @@ export class GitUploadTool {
93
90
  renamed: status.renamed,
94
91
  staged: status.staged,
95
92
  conflicted: status.conflicted,
96
- isClean: status.isClean(),
93
+ isClean: status.isClean,
97
94
  };
98
95
  // 2. Verificar se tem mudanças
99
- if (!status.isClean()) {
96
+ if (!status.isClean) {
100
97
  results.traceability.uploadSteps.push({
101
98
  step: 2,
102
99
  action: 'stage_all_changes',
103
100
  timestamp: new Date().toISOString(),
104
101
  files: [...status.modified, ...status.created, ...status.deleted],
105
102
  });
106
- await git.add('.');
103
+ await ctx.gitAdapter.add(projectPath, ['.']);
107
104
  }
108
105
  // 3. Commit com rastreabilidade
109
106
  const commitMessage = params.commitMessage || `[git-upload] Upload project at ${new Date().toISOString()}`;
@@ -115,7 +112,13 @@ export class GitUploadTool {
115
112
  });
116
113
  let commitResult;
117
114
  try {
118
- commitResult = await git.commit(commitMessage);
115
+ const sha = await ctx.gitAdapter.commit(projectPath, commitMessage);
116
+ commitResult = {
117
+ commit: sha,
118
+ summary: { changes: 0, insertions: 0, deletions: 0 },
119
+ branch: await ctx.gitAdapter.getCurrentBranch(projectPath),
120
+ author: { name: 'unknown', email: 'unknown' }
121
+ };
119
122
  results.traceability.commit = {
120
123
  hash: commitResult.commit,
121
124
  summary: commitResult.summary,
@@ -124,7 +127,7 @@ export class GitUploadTool {
124
127
  };
125
128
  }
126
129
  catch (err) {
127
- if (err.message.includes('nothing to commit')) {
130
+ if (err.message.includes('nothing to commit') || (status.isClean && status.staged.length === 0)) {
128
131
  results.traceability.commit = { message: 'No changes to commit' };
129
132
  }
130
133
  else {
@@ -174,12 +177,12 @@ export class GitUploadTool {
174
177
  };
175
178
  }
176
179
  // Adicionar remote se necessário (URL LIMPA sem token)
177
- const remotes = await git.getRemotes(true);
180
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
178
181
  const githubRemote = remotes.find(r => r.name === 'github');
179
182
  if (!githubRemote) {
180
183
  // Usar URL limpa sem token para segurança
181
184
  const remoteUrl = `https://github.com/${githubOwner}/${repoName}.git`;
182
- await git.addRemote('github', remoteUrl);
185
+ await ctx.gitAdapter.addRemote(projectPath, 'github', remoteUrl);
183
186
  results.traceability.uploadSteps.push({
184
187
  step: 5,
185
188
  action: 'add_github_remote',
@@ -195,19 +198,12 @@ export class GitUploadTool {
195
198
  branch,
196
199
  });
197
200
  try {
198
- // Configurar credential helper temporário para este push
199
- await git.addConfig('credential.https://github.com.helper', 'store');
200
- await git.addConfig(`credential.https://github.com.username`, githubOwner);
201
201
  // Push com --set-upstream mas SEM --force (segurança)
202
- await git.push('github', branch, ['--set-upstream']);
203
- // Limpar credential helper após push
204
- await git.addConfig('credential.https://github.com.helper', '');
202
+ await ctx.gitAdapter.push(projectPath, 'github', branch, false, true);
205
203
  results.providers.github.pushed = true;
206
204
  results.providers.github.pushStatus = 'success';
207
205
  }
208
206
  catch (pushErr) {
209
- // Limpar credential helper em caso de erro
210
- await git.addConfig('credential.https://github.com.helper', '');
211
207
  results.providers.github.pushed = false;
212
208
  results.providers.github.pushError = pushErr.message;
213
209
  results.traceability.errors.push({
@@ -271,14 +267,14 @@ export class GitUploadTool {
271
267
  };
272
268
  }
273
269
  // Adicionar remote se necessário (URL LIMPA sem token)
274
- const remotes = await git.getRemotes(true);
270
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
275
271
  const giteaRemote = remotes.find(r => r.name === 'gitea');
276
272
  if (!giteaRemote) {
277
273
  // Usar URL limpa sem token para segurança
278
274
  const baseUrl = ctx.providerManager.giteaBaseUrl;
279
275
  const cleanUrl = baseUrl.replace(/\/$/, ''); // Remover barra final se houver
280
276
  const remoteUrl = `${cleanUrl}/${giteaOwner}/${repoName}.git`;
281
- await git.addRemote('gitea', remoteUrl);
277
+ await ctx.gitAdapter.addRemote(projectPath, 'gitea', remoteUrl);
282
278
  results.traceability.uploadSteps.push({
283
279
  step: 8,
284
280
  action: 'add_gitea_remote',
@@ -294,23 +290,12 @@ export class GitUploadTool {
294
290
  branch,
295
291
  });
296
292
  try {
297
- const baseUrl = ctx.providerManager.giteaBaseUrl;
298
- const cleanUrl = baseUrl.replace(/\/$/, '');
299
- // Configurar credential helper temporário
300
- await git.addConfig(`credential.${cleanUrl}.helper`, 'store');
301
- await git.addConfig(`credential.${cleanUrl}.username`, giteaOwner);
302
293
  // Push com --set-upstream mas SEM --force (segurança)
303
- await git.push('gitea', branch, ['--set-upstream']);
304
- // Limpar credential helper após push
305
- await git.addConfig(`credential.${cleanUrl}.helper`, '');
294
+ await ctx.gitAdapter.push(projectPath, 'gitea', branch, false, true);
306
295
  results.providers.gitea.pushed = true;
307
296
  results.providers.gitea.pushStatus = 'success';
308
297
  }
309
298
  catch (pushErr) {
310
- // Limpar credential helper em caso de erro
311
- const baseUrl = ctx.providerManager.giteaBaseUrl;
312
- const cleanUrl = baseUrl.replace(/\/$/, '');
313
- await git.addConfig(`credential.${cleanUrl}.helper`, '');
314
299
  results.providers.gitea.pushed = false;
315
300
  results.providers.gitea.pushError = pushErr.message;
316
301
  results.traceability.errors.push({