@andre.buzeli/git-mcp 16.1.3 → 16.1.6
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/README.md +3 -2
- package/package.json +14 -3
- package/src/index.js +38 -42
- package/src/resources/index.js +79 -41
- package/src/tools/git-branches.js +5 -2
- package/src/tools/git-clone.js +13 -2
- package/src/tools/git-config.js +5 -2
- package/src/tools/git-diff.js +27 -6
- package/src/tools/git-files.js +5 -2
- package/src/tools/git-help.js +8 -3
- package/src/tools/git-history.js +9 -4
- package/src/tools/git-ignore.js +5 -2
- package/src/tools/git-issues.js +5 -2
- package/src/tools/git-merge.js +12 -7
- package/src/tools/git-pulls.js +5 -2
- package/src/tools/git-remote.js +10 -3
- package/src/tools/git-reset.js +5 -2
- package/src/tools/git-stash.js +5 -2
- package/src/tools/git-sync.js +5 -2
- package/src/tools/git-tags.js +5 -2
- package/src/tools/git-workflow.js +97 -42
- package/src/tools/git-worktree.js +66 -38
- package/src/utils/errors.js +21 -0
- package/src/utils/gitAdapter.js +165 -125
- package/src/utils/retry.js +1 -1
- package/src/utils/worktreeResolver.js +189 -0
package/src/tools/git-merge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
|
-
import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, createError, handleEmptyCall } from "../utils/errors.js";
|
|
3
3
|
import { validateProjectPath } from "../utils/repoHelpers.js";
|
|
4
4
|
|
|
5
5
|
const ajv = new Ajv({ allErrors: true });
|
|
@@ -68,8 +68,11 @@ SQUASH:
|
|
|
68
68
|
- Útil para manter histórico limpo`;
|
|
69
69
|
|
|
70
70
|
async function handle(args) {
|
|
71
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-merge", { projectPath: "/path/to/project", action: "status" });
|
|
72
|
+
if (emptyHelp) return emptyHelp;
|
|
73
|
+
|
|
71
74
|
const validate = ajv.compile(inputSchema);
|
|
72
|
-
if (!validate(args
|
|
75
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
73
76
|
const { projectPath, action } = args;
|
|
74
77
|
|
|
75
78
|
try {
|
|
@@ -98,11 +101,13 @@ SQUASH:
|
|
|
98
101
|
// Pre-merge check: working tree status
|
|
99
102
|
const status = await git.status(projectPath);
|
|
100
103
|
if (!status.isClean) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
return asToolError("DIRTY_WORKING_TREE",
|
|
105
|
+
"Working tree com alterações não commitadas. Faça commit ou stash antes de mergear.", {
|
|
106
|
+
modified: status.modified,
|
|
107
|
+
staged: status.staged,
|
|
108
|
+
notAdded: status.not_added,
|
|
109
|
+
suggestion: "Use git-workflow action='update' para commitar as mudanças, ou git-stash para guardá-las temporariamente"
|
|
110
|
+
});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
const currentBranch = await git.getCurrentBranch(projectPath);
|
package/src/tools/git-pulls.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import axios from "axios";
|
|
3
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
3
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
4
4
|
import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
5
5
|
import { runBoth } from "../utils/providerExec.js";
|
|
6
6
|
|
|
@@ -76,8 +76,11 @@ AÇÕES:
|
|
|
76
76
|
NOTA: O PR é criado em AMBOS os providers simultaneamente.`;
|
|
77
77
|
|
|
78
78
|
async function handle(args) {
|
|
79
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-pulls", { projectPath: "/path/to/project", action: "list" });
|
|
80
|
+
if (emptyHelp) return emptyHelp;
|
|
81
|
+
|
|
79
82
|
const validate = ajv.compile(inputSchema);
|
|
80
|
-
if (!validate(args
|
|
83
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
81
84
|
validateProjectPath(args.projectPath);
|
|
82
85
|
const repo = getRepoNameFromPath(args.projectPath);
|
|
83
86
|
try {
|
package/src/tools/git-remote.js
CHANGED
|
@@ -3,7 +3,7 @@ import axios from "axios";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import archiver from "archiver";
|
|
6
|
-
import { asToolError, asToolResult, errorToResponse, mapExternalError } from "../utils/errors.js";
|
|
6
|
+
import { asToolError, asToolResult, errorToResponse, mapExternalError, handleEmptyCall } from "../utils/errors.js";
|
|
7
7
|
import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
8
8
|
import { runBoth } from "../utils/providerExec.js";
|
|
9
9
|
import { sendLog, requestConfirmation } from "../utils/mcpNotify.js";
|
|
@@ -167,7 +167,11 @@ ARQUIVOS VIA API:
|
|
|
167
167
|
},
|
|
168
168
|
content: {
|
|
169
169
|
type: "string",
|
|
170
|
-
description: "Conteúdo do arquivo para contents-create"
|
|
170
|
+
description: "Conteúdo do arquivo para contents-create (texto simples, será convertido para base64 automaticamente)"
|
|
171
|
+
},
|
|
172
|
+
message: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "Mensagem do commit para contents-create. Default: 'Add {path}'"
|
|
171
175
|
},
|
|
172
176
|
branch: {
|
|
173
177
|
type: "string",
|
|
@@ -214,8 +218,11 @@ QUANDO USAR:
|
|
|
214
218
|
- Para configurar tópicos: use action='topics-set'`;
|
|
215
219
|
|
|
216
220
|
async function handle(args) {
|
|
221
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-remote", { projectPath: "/path/to/project", action: "list" });
|
|
222
|
+
if (emptyHelp) return emptyHelp;
|
|
223
|
+
|
|
217
224
|
const validate = ajv.compile(inputSchema);
|
|
218
|
-
if (!validate(args
|
|
225
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
219
226
|
const { projectPath, action } = args;
|
|
220
227
|
try {
|
|
221
228
|
validateProjectPath(projectPath);
|
package/src/tools/git-reset.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
3
3
|
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
4
|
import { sendLog, requestConfirmation } from "../utils/mcpNotify.js";
|
|
5
5
|
|
|
@@ -57,8 +57,11 @@ REFERÊNCIAS:
|
|
|
57
57
|
- abc1234: SHA específico do commit`;
|
|
58
58
|
|
|
59
59
|
async function handle(args) {
|
|
60
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-reset", { projectPath: "/path/to/project", action: "soft", ref: "HEAD~1" });
|
|
61
|
+
if (emptyHelp) return emptyHelp;
|
|
62
|
+
|
|
60
63
|
const validate = ajv.compile(inputSchema);
|
|
61
|
-
if (!validate(args
|
|
64
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
62
65
|
const { projectPath, action, ref } = args;
|
|
63
66
|
try {
|
|
64
67
|
validateProjectPath(projectPath);
|
package/src/tools/git-stash.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
3
3
|
import { validateProjectPath } from "../utils/repoHelpers.js";
|
|
4
4
|
import { withRetry } from "../utils/retry.js";
|
|
5
5
|
|
|
@@ -67,8 +67,11 @@ AÇÕES:
|
|
|
67
67
|
- clear: Remover todos os stashes`;
|
|
68
68
|
|
|
69
69
|
async function handle(args) {
|
|
70
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-stash", { projectPath: "/path/to/project", action: "list" });
|
|
71
|
+
if (emptyHelp) return emptyHelp;
|
|
72
|
+
|
|
70
73
|
const validate = ajv.compile(inputSchema);
|
|
71
|
-
if (!validate(args
|
|
74
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
72
75
|
const { projectPath, action } = args;
|
|
73
76
|
try {
|
|
74
77
|
validateProjectPath(projectPath);
|
package/src/tools/git-sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
3
3
|
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
4
|
|
|
5
5
|
const ajv = new Ajv({ allErrors: true });
|
|
@@ -54,8 +54,11 @@ FLUXO RECOMENDADO:
|
|
|
54
54
|
NOTA: Se pull falhar com conflito, resolva manualmente e faça commit.`;
|
|
55
55
|
|
|
56
56
|
async function handle(args) {
|
|
57
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-sync", { projectPath: "/path/to/project", action: "fetch" });
|
|
58
|
+
if (emptyHelp) return emptyHelp;
|
|
59
|
+
|
|
57
60
|
const validate = ajv.compile(inputSchema);
|
|
58
|
-
if (!validate(args
|
|
61
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
59
62
|
const { projectPath, action } = args;
|
|
60
63
|
try {
|
|
61
64
|
validateProjectPath(projectPath);
|
package/src/tools/git-tags.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
3
3
|
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
4
|
|
|
5
5
|
const ajv = new Ajv({ allErrors: true });
|
|
@@ -61,8 +61,11 @@ FLUXO TÍPICO:
|
|
|
61
61
|
2. git-tags push tag='v1.0.0'`;
|
|
62
62
|
|
|
63
63
|
async function handle(args) {
|
|
64
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-tags", { projectPath: "/path/to/project", action: "list" });
|
|
65
|
+
if (emptyHelp) return emptyHelp;
|
|
66
|
+
|
|
64
67
|
const validate = ajv.compile(inputSchema);
|
|
65
|
-
if (!validate(args
|
|
68
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
66
69
|
const { projectPath, action } = args;
|
|
67
70
|
try {
|
|
68
71
|
validateProjectPath(projectPath);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
|
|
4
|
+
import { asToolError, asToolResult, errorToResponse, createError, handleEmptyCall } from "../utils/errors.js";
|
|
5
5
|
import { getRepoNameFromPath, detectProjectType, GITIGNORE_TEMPLATES, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
6
|
+
import { resolveWorktreeContext, isProtectedPath } from "../utils/worktreeResolver.js";
|
|
6
7
|
import { sendLog } from "../utils/mcpNotify.js";
|
|
7
8
|
|
|
8
9
|
const ajv = new Ajv({ allErrors: true });
|
|
@@ -133,13 +134,41 @@ EXEMPLOS DE USO:
|
|
|
133
134
|
• Ver mudanças: { "projectPath": "/path/to/project", "action": "status" }`;
|
|
134
135
|
|
|
135
136
|
async function handle(args) {
|
|
137
|
+
// Call vazia = modelo tentando descobrir o schema. Retorna help em vez de erro.
|
|
138
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-workflow", { projectPath: "/path/to/project", action: "update", message: "feat: descrição" });
|
|
139
|
+
if (emptyHelp) return emptyHelp;
|
|
140
|
+
|
|
136
141
|
const validate = ajv.compile(inputSchema);
|
|
137
|
-
if (!validate(args
|
|
142
|
+
if (!validate(args)) {
|
|
138
143
|
return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
139
144
|
}
|
|
140
145
|
const { projectPath, action } = args;
|
|
146
|
+
let effectivePath = projectPath;
|
|
147
|
+
let worktreeCtx = null;
|
|
148
|
+
|
|
141
149
|
try {
|
|
142
150
|
validateProjectPath(projectPath);
|
|
151
|
+
|
|
152
|
+
if (action === "init") {
|
|
153
|
+
if (isProtectedPath(projectPath)) {
|
|
154
|
+
return asToolError("PROTECTED_PATH", `Não é permitido criar repositório git em ${projectPath}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
worktreeCtx = await resolveWorktreeContext(projectPath, git);
|
|
158
|
+
effectivePath = worktreeCtx.worktreePath;
|
|
159
|
+
|
|
160
|
+
// Ajustar paths relativos se estivermos rodando da raiz
|
|
161
|
+
if (effectivePath !== projectPath && Array.isArray(args.files)) {
|
|
162
|
+
const rel = path.relative(effectivePath, projectPath);
|
|
163
|
+
args.files = args.files.map(f => {
|
|
164
|
+
if (path.isAbsolute(f)) return f;
|
|
165
|
+
// Se for ".", vira o próprio diretório relativo
|
|
166
|
+
if (f === ".") return rel.replace(/\\/g, '/');
|
|
167
|
+
return path.join(rel, f).replace(/\\/g, '/');
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
143
172
|
if (action === "init") {
|
|
144
173
|
const shouldCreateGitignore = args.createGitignore !== false;
|
|
145
174
|
|
|
@@ -148,37 +177,37 @@ EXEMPLOS DE USO:
|
|
|
148
177
|
success: true,
|
|
149
178
|
dryRun: true,
|
|
150
179
|
message: "DRY RUN: Repositório seria inicializado localmente e nos providers",
|
|
151
|
-
repoName: getRepoNameFromPath(
|
|
180
|
+
repoName: getRepoNameFromPath(effectivePath),
|
|
152
181
|
gitignoreCreated: shouldCreateGitignore
|
|
153
182
|
});
|
|
154
183
|
}
|
|
155
184
|
|
|
156
|
-
const isRepo = await git.isRepo(
|
|
185
|
+
const isRepo = await git.isRepo(effectivePath);
|
|
157
186
|
if (!isRepo) {
|
|
158
|
-
await git.init(
|
|
187
|
+
await git.init(effectivePath);
|
|
159
188
|
}
|
|
160
189
|
|
|
161
190
|
// Criar .gitignore baseado no tipo de projeto (apenas se não existir ou se for novo repo)
|
|
162
191
|
let gitignoreCreated = false;
|
|
163
192
|
|
|
164
193
|
if (shouldCreateGitignore) {
|
|
165
|
-
const hasGitignore = fs.existsSync(path.join(
|
|
194
|
+
const hasGitignore = fs.existsSync(path.join(effectivePath, ".gitignore"));
|
|
166
195
|
if (!hasGitignore) {
|
|
167
|
-
const projectType = detectProjectType(
|
|
196
|
+
const projectType = detectProjectType(effectivePath);
|
|
168
197
|
const patterns = GITIGNORE_TEMPLATES[projectType] || GITIGNORE_TEMPLATES.general;
|
|
169
|
-
await git.createGitignore(
|
|
198
|
+
await git.createGitignore(effectivePath, patterns);
|
|
170
199
|
gitignoreCreated = true;
|
|
171
200
|
}
|
|
172
201
|
}
|
|
173
202
|
|
|
174
|
-
const repo = getRepoNameFromPath(
|
|
203
|
+
const repo = getRepoNameFromPath(effectivePath);
|
|
175
204
|
const isPublic = args.isPublic === true; // Default: privado
|
|
176
205
|
const organization = args.organization || undefined;
|
|
177
206
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic, organization });
|
|
178
207
|
|
|
179
208
|
// Configurar remotes com org se fornecida
|
|
180
209
|
const urls = await pm.getRemoteUrls(repo, organization);
|
|
181
|
-
await git.ensureRemotes(
|
|
210
|
+
await git.ensureRemotes(effectivePath, { githubUrl: urls.github || "", giteaUrl: urls.gitea || "" });
|
|
182
211
|
|
|
183
212
|
return asToolResult({
|
|
184
213
|
success: true,
|
|
@@ -190,7 +219,7 @@ EXEMPLOS DE USO:
|
|
|
190
219
|
});
|
|
191
220
|
}
|
|
192
221
|
if (action === "status") {
|
|
193
|
-
const st = await git.status(
|
|
222
|
+
const st = await git.status(effectivePath);
|
|
194
223
|
|
|
195
224
|
if (args.dryRun) {
|
|
196
225
|
return asToolResult({
|
|
@@ -224,7 +253,15 @@ EXEMPLOS DE USO:
|
|
|
224
253
|
_aiContext.suggestedAction = "Working tree limpa. Modifique arquivos ou use action='push' se há commits pendentes";
|
|
225
254
|
}
|
|
226
255
|
|
|
227
|
-
|
|
256
|
+
const result = { ...st, _aiContext };
|
|
257
|
+
if (worktreeCtx?.isWorktree) {
|
|
258
|
+
result._worktreeContext = {
|
|
259
|
+
detected: true,
|
|
260
|
+
repoRoot: worktreeCtx.repoRoot,
|
|
261
|
+
branch: worktreeCtx.branch
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return asToolResult(result, { tool: 'workflow', action: 'status' });
|
|
228
265
|
}
|
|
229
266
|
if (action === "add") {
|
|
230
267
|
const files = Array.isArray(args.files) && args.files.length ? args.files : ["."];
|
|
@@ -238,12 +275,12 @@ EXEMPLOS DE USO:
|
|
|
238
275
|
});
|
|
239
276
|
}
|
|
240
277
|
|
|
241
|
-
await git.add(
|
|
278
|
+
await git.add(effectivePath, files);
|
|
242
279
|
return asToolResult({ success: true, files }, { tool: 'workflow', action: 'add' });
|
|
243
280
|
}
|
|
244
281
|
if (action === "remove") {
|
|
245
282
|
const files = Array.isArray(args.files) ? args.files : [];
|
|
246
|
-
await git.remove(
|
|
283
|
+
await git.remove(effectivePath, files);
|
|
247
284
|
return asToolResult({ success: true, files });
|
|
248
285
|
}
|
|
249
286
|
if (action === "commit") {
|
|
@@ -259,12 +296,20 @@ EXEMPLOS DE USO:
|
|
|
259
296
|
});
|
|
260
297
|
}
|
|
261
298
|
|
|
262
|
-
const
|
|
299
|
+
const st = await git.status(effectivePath);
|
|
300
|
+
if (!st.staged || st.staged.length === 0) {
|
|
301
|
+
return asToolError("NOTHING_TO_COMMIT", "Nenhum arquivo staged para commitar", {
|
|
302
|
+
suggestion: "Use action='add' com files=['.'] para adicionar arquivos ao staging antes de commitar",
|
|
303
|
+
status: { modified: st.modified, notAdded: st.not_added }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const sha = await git.commit(effectivePath, args.message);
|
|
263
308
|
return asToolResult({ success: true, sha, message: args.message }, { tool: 'workflow', action: 'commit' });
|
|
264
309
|
}
|
|
265
310
|
if (action === "clean") {
|
|
266
311
|
if (args.dryRun) {
|
|
267
|
-
const result = await git.cleanUntracked(
|
|
312
|
+
const result = await git.cleanUntracked(effectivePath);
|
|
268
313
|
return asToolResult({
|
|
269
314
|
success: true,
|
|
270
315
|
dryRun: true,
|
|
@@ -273,7 +318,7 @@ EXEMPLOS DE USO:
|
|
|
273
318
|
});
|
|
274
319
|
}
|
|
275
320
|
|
|
276
|
-
const result = await git.cleanUntracked(
|
|
321
|
+
const result = await git.cleanUntracked(effectivePath);
|
|
277
322
|
return asToolResult({
|
|
278
323
|
success: true,
|
|
279
324
|
...result,
|
|
@@ -283,7 +328,7 @@ EXEMPLOS DE USO:
|
|
|
283
328
|
});
|
|
284
329
|
}
|
|
285
330
|
if (action === "ensure-remotes") {
|
|
286
|
-
const repo = getRepoNameFromPath(
|
|
331
|
+
const repo = getRepoNameFromPath(effectivePath);
|
|
287
332
|
const isPublic = args.isPublic === true; // Default: privado
|
|
288
333
|
const organization = args.organization || undefined;
|
|
289
334
|
|
|
@@ -310,12 +355,12 @@ EXEMPLOS DE USO:
|
|
|
310
355
|
const giteaBase = pm.giteaUrl?.replace(/\/$/, "");
|
|
311
356
|
const giteaUrl = ensured.gitea?.ok && ensured.gitea.repo && giteaBase
|
|
312
357
|
? `${giteaBase}/${ensured.gitea.repo}.git` : "";
|
|
313
|
-
await git.ensureRemotes(
|
|
314
|
-
const remotes = await git.listRemotes(
|
|
358
|
+
await git.ensureRemotes(effectivePath, { githubUrl, giteaUrl, organization });
|
|
359
|
+
const remotes = await git.listRemotes(effectivePath);
|
|
315
360
|
return asToolResult({ success: true, ensured, remotes, isPrivate: !isPublic, organization: organization || undefined }, { tool: 'workflow', action: 'ensure-remotes' });
|
|
316
361
|
}
|
|
317
362
|
if (action === "push") {
|
|
318
|
-
const branch = await git.getCurrentBranch(
|
|
363
|
+
const branch = await git.getCurrentBranch(effectivePath);
|
|
319
364
|
const force = !!args.force;
|
|
320
365
|
|
|
321
366
|
if (args.dryRun) {
|
|
@@ -335,7 +380,7 @@ EXEMPLOS DE USO:
|
|
|
335
380
|
|
|
336
381
|
// Retry logic for push (often fails due to network or concurrent updates)
|
|
337
382
|
const result = await withRetry(
|
|
338
|
-
() => git.pushParallel(
|
|
383
|
+
() => git.pushParallel(effectivePath, branch, force, organization),
|
|
339
384
|
3,
|
|
340
385
|
"push"
|
|
341
386
|
);
|
|
@@ -350,15 +395,15 @@ EXEMPLOS DE USO:
|
|
|
350
395
|
}
|
|
351
396
|
|
|
352
397
|
const channel = args.channel || "production";
|
|
353
|
-
const wtPath = path.join(projectPath, branch);
|
|
398
|
+
const wtPath = path.join(projectPath, branch); // Cria relativo ao path original solicitado
|
|
354
399
|
|
|
355
400
|
if (fs.existsSync(wtPath)) {
|
|
356
401
|
return asToolError("WORKTREE_PATH_EXISTS", `Diretório '${wtPath}' já existe.`, { suggestion: "Use git-worktree add se quiser configurar path customizado" });
|
|
357
402
|
}
|
|
358
403
|
|
|
359
404
|
try {
|
|
360
|
-
await git.addWorktree(
|
|
361
|
-
await git.setWorktreeConfig(
|
|
405
|
+
await git.addWorktree(effectivePath, branch, wtPath);
|
|
406
|
+
await git.setWorktreeConfig(effectivePath, branch, { path: wtPath, channel });
|
|
362
407
|
|
|
363
408
|
return asToolResult({
|
|
364
409
|
success: true,
|
|
@@ -390,13 +435,13 @@ EXEMPLOS DE USO:
|
|
|
390
435
|
if (args.dryRun) {
|
|
391
436
|
gitignored = gitignorePatterns;
|
|
392
437
|
} else {
|
|
393
|
-
await git.addToGitignore(
|
|
438
|
+
await git.addToGitignore(effectivePath, gitignorePatterns);
|
|
394
439
|
gitignored = gitignorePatterns;
|
|
395
440
|
}
|
|
396
441
|
}
|
|
397
442
|
|
|
398
443
|
// 1. Status para ver o que mudou
|
|
399
|
-
const status = await git.status(
|
|
444
|
+
const status = await git.status(effectivePath);
|
|
400
445
|
|
|
401
446
|
const hasChanges = !status.isClean ||
|
|
402
447
|
(status.not_added?.length > 0) ||
|
|
@@ -438,15 +483,15 @@ EXEMPLOS DE USO:
|
|
|
438
483
|
}
|
|
439
484
|
|
|
440
485
|
// 2. Add
|
|
441
|
-
await git.add(
|
|
486
|
+
await git.add(effectivePath, files);
|
|
442
487
|
|
|
443
488
|
// 3. Commit
|
|
444
|
-
const sha = await git.commit(
|
|
489
|
+
const sha = await git.commit(effectivePath, args.message);
|
|
445
490
|
|
|
446
491
|
// 3.5. Garantir remotes com organization (se fornecida)
|
|
447
492
|
const organization = args.organization || undefined;
|
|
448
493
|
if (organization) {
|
|
449
|
-
const repo = getRepoNameFromPath(
|
|
494
|
+
const repo = getRepoNameFromPath(effectivePath);
|
|
450
495
|
const isPublic = args.isPublic === true;
|
|
451
496
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic, organization });
|
|
452
497
|
// Build URLs from actual ensureRepos results (handles GitHub fallback to personal account)
|
|
@@ -455,11 +500,11 @@ EXEMPLOS DE USO:
|
|
|
455
500
|
const giteaBase = pm.giteaUrl?.replace(/\/$/, "");
|
|
456
501
|
const giteaUrl = ensured.gitea?.ok && ensured.gitea.repo && giteaBase
|
|
457
502
|
? `${giteaBase}/${ensured.gitea.repo}.git` : "";
|
|
458
|
-
await git.ensureRemotes(
|
|
503
|
+
await git.ensureRemotes(effectivePath, { githubUrl, giteaUrl, organization });
|
|
459
504
|
}
|
|
460
505
|
|
|
461
506
|
// 4. Push Strategy
|
|
462
|
-
const branch = await git.getCurrentBranch(
|
|
507
|
+
const branch = await git.getCurrentBranch(effectivePath);
|
|
463
508
|
let pushResult = {};
|
|
464
509
|
let synced = [];
|
|
465
510
|
let errors = [];
|
|
@@ -467,7 +512,7 @@ EXEMPLOS DE USO:
|
|
|
467
512
|
// Determine Channel
|
|
468
513
|
let channel = args.channel;
|
|
469
514
|
if (!channel) {
|
|
470
|
-
const storedChannel = await git.getConfig(
|
|
515
|
+
const storedChannel = await git.getConfig(effectivePath, `worktree-branch.${branch}.channel`);
|
|
471
516
|
if (storedChannel) channel = storedChannel;
|
|
472
517
|
}
|
|
473
518
|
|
|
@@ -477,13 +522,13 @@ EXEMPLOS DE USO:
|
|
|
477
522
|
// SYNC BRANCHES
|
|
478
523
|
if (syncBranches) {
|
|
479
524
|
// Só permitido no principal (onde .git é diretório)
|
|
480
|
-
if (fs.existsSync(path.join(
|
|
525
|
+
if (fs.existsSync(path.join(effectivePath, ".git")) && fs.statSync(path.join(effectivePath, ".git")).isFile()) {
|
|
481
526
|
return asToolError("INVALID_OPERATION", "syncBranches=true só pode ser executado a partir do repositório principal, não de um worktree.");
|
|
482
527
|
}
|
|
483
528
|
|
|
484
529
|
// 1. Push do principal (current)
|
|
485
530
|
const remoteBranch = resolveRemoteBranch(branch, channel);
|
|
486
|
-
const remotes = await git.listRemotes(
|
|
531
|
+
const remotes = await git.listRemotes(effectivePath);
|
|
487
532
|
|
|
488
533
|
await sendLog(server, "info", "update: push iniciado (sync)", { branch, remoteBranch, force });
|
|
489
534
|
|
|
@@ -491,7 +536,7 @@ EXEMPLOS DE USO:
|
|
|
491
536
|
const mainFailed = [];
|
|
492
537
|
for (const r of remotes) {
|
|
493
538
|
try {
|
|
494
|
-
await git.pushRefspec(
|
|
539
|
+
await git.pushRefspec(effectivePath, r.remote, branch, remoteBranch, force);
|
|
495
540
|
mainPushed.push(r.remote);
|
|
496
541
|
} catch(e) {
|
|
497
542
|
mainFailed.push({ remote: r.remote, error: e.message });
|
|
@@ -501,7 +546,7 @@ EXEMPLOS DE USO:
|
|
|
501
546
|
// 2. Propagar para worktrees
|
|
502
547
|
// Req 8.3: synced.length + errors.length === N (número de worktrees registrados)
|
|
503
548
|
// O principal NÃO entra no contador — só os worktrees registrados
|
|
504
|
-
const configs = await git.getWorktreeConfigs(
|
|
549
|
+
const configs = await git.getWorktreeConfigs(effectivePath);
|
|
505
550
|
|
|
506
551
|
for (const wt of configs) {
|
|
507
552
|
// Pula se for a própria branch principal (caso esteja registrada)
|
|
@@ -538,13 +583,13 @@ EXEMPLOS DE USO:
|
|
|
538
583
|
// Comportamento padrão (production)
|
|
539
584
|
await sendLog(server, "info", "update: push iniciado", { branch, force });
|
|
540
585
|
pushResult = await withRetry(
|
|
541
|
-
() => git.pushParallel(
|
|
586
|
+
() => git.pushParallel(effectivePath, branch, force, organization),
|
|
542
587
|
3,
|
|
543
588
|
"push"
|
|
544
589
|
);
|
|
545
590
|
} else {
|
|
546
591
|
// Comportamento customizado (beta/alpha) -> pushRefspec
|
|
547
|
-
const remotes = await git.listRemotes(
|
|
592
|
+
const remotes = await git.listRemotes(effectivePath);
|
|
548
593
|
|
|
549
594
|
const pushed = [];
|
|
550
595
|
const failed = [];
|
|
@@ -553,7 +598,7 @@ EXEMPLOS DE USO:
|
|
|
553
598
|
|
|
554
599
|
for (const r of remotes) {
|
|
555
600
|
try {
|
|
556
|
-
await git.pushRefspec(
|
|
601
|
+
await git.pushRefspec(effectivePath, r.remote, branch, remoteBranch, force);
|
|
557
602
|
pushed.push(r.remote);
|
|
558
603
|
} catch (e) {
|
|
559
604
|
failed.push({ remote: r.remote, error: e.message });
|
|
@@ -568,7 +613,7 @@ EXEMPLOS DE USO:
|
|
|
568
613
|
}
|
|
569
614
|
}
|
|
570
615
|
|
|
571
|
-
|
|
616
|
+
const result = {
|
|
572
617
|
success: true,
|
|
573
618
|
action: "update",
|
|
574
619
|
steps: ["status", "add", "commit", "push"],
|
|
@@ -583,7 +628,17 @@ EXEMPLOS DE USO:
|
|
|
583
628
|
completed: true,
|
|
584
629
|
message: "Ciclo completo: arquivos adicionados, commit criado e push realizado" + (organization ? ` [org: ${organization}]` : "")
|
|
585
630
|
}
|
|
586
|
-
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
if (worktreeCtx?.isWorktree) {
|
|
634
|
+
result._worktreeContext = {
|
|
635
|
+
detected: true,
|
|
636
|
+
repoRoot: worktreeCtx.repoRoot,
|
|
637
|
+
branch: worktreeCtx.branch
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return asToolResult(result, { tool: 'workflow', action: 'update' });
|
|
587
642
|
}
|
|
588
643
|
|
|
589
644
|
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|