@andre.buzeli/git-mcp 16.1.3 → 16.1.4

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.
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import { asToolError, asToolResult, errorToResponse, createError } 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 });
@@ -138,8 +139,32 @@ EXEMPLOS DE USO:
138
139
  return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
139
140
  }
140
141
  const { projectPath, action } = args;
142
+ let effectivePath = projectPath;
143
+ let worktreeCtx = null;
144
+
141
145
  try {
142
146
  validateProjectPath(projectPath);
147
+
148
+ if (action === "init") {
149
+ if (isProtectedPath(projectPath)) {
150
+ return asToolError("PROTECTED_PATH", `Não é permitido criar repositório git em ${projectPath}`);
151
+ }
152
+ } else {
153
+ worktreeCtx = await resolveWorktreeContext(projectPath, git);
154
+ effectivePath = worktreeCtx.worktreePath;
155
+
156
+ // Ajustar paths relativos se estivermos rodando da raiz
157
+ if (effectivePath !== projectPath && Array.isArray(args.files)) {
158
+ const rel = path.relative(effectivePath, projectPath);
159
+ args.files = args.files.map(f => {
160
+ if (path.isAbsolute(f)) return f;
161
+ // Se for ".", vira o próprio diretório relativo
162
+ if (f === ".") return rel.replace(/\\/g, '/');
163
+ return path.join(rel, f).replace(/\\/g, '/');
164
+ });
165
+ }
166
+ }
167
+
143
168
  if (action === "init") {
144
169
  const shouldCreateGitignore = args.createGitignore !== false;
145
170
 
@@ -148,37 +173,37 @@ EXEMPLOS DE USO:
148
173
  success: true,
149
174
  dryRun: true,
150
175
  message: "DRY RUN: Repositório seria inicializado localmente e nos providers",
151
- repoName: getRepoNameFromPath(projectPath),
176
+ repoName: getRepoNameFromPath(effectivePath),
152
177
  gitignoreCreated: shouldCreateGitignore
153
178
  });
154
179
  }
155
180
 
156
- const isRepo = await git.isRepo(projectPath);
181
+ const isRepo = await git.isRepo(effectivePath);
157
182
  if (!isRepo) {
158
- await git.init(projectPath);
183
+ await git.init(effectivePath);
159
184
  }
160
185
 
161
186
  // Criar .gitignore baseado no tipo de projeto (apenas se não existir ou se for novo repo)
162
187
  let gitignoreCreated = false;
163
188
 
164
189
  if (shouldCreateGitignore) {
165
- const hasGitignore = fs.existsSync(path.join(projectPath, ".gitignore"));
190
+ const hasGitignore = fs.existsSync(path.join(effectivePath, ".gitignore"));
166
191
  if (!hasGitignore) {
167
- const projectType = detectProjectType(projectPath);
192
+ const projectType = detectProjectType(effectivePath);
168
193
  const patterns = GITIGNORE_TEMPLATES[projectType] || GITIGNORE_TEMPLATES.general;
169
- await git.createGitignore(projectPath, patterns);
194
+ await git.createGitignore(effectivePath, patterns);
170
195
  gitignoreCreated = true;
171
196
  }
172
197
  }
173
198
 
174
- const repo = getRepoNameFromPath(projectPath);
199
+ const repo = getRepoNameFromPath(effectivePath);
175
200
  const isPublic = args.isPublic === true; // Default: privado
176
201
  const organization = args.organization || undefined;
177
202
  const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic, organization });
178
203
 
179
204
  // Configurar remotes com org se fornecida
180
205
  const urls = await pm.getRemoteUrls(repo, organization);
181
- await git.ensureRemotes(projectPath, { githubUrl: urls.github || "", giteaUrl: urls.gitea || "" });
206
+ await git.ensureRemotes(effectivePath, { githubUrl: urls.github || "", giteaUrl: urls.gitea || "" });
182
207
 
183
208
  return asToolResult({
184
209
  success: true,
@@ -190,7 +215,7 @@ EXEMPLOS DE USO:
190
215
  });
191
216
  }
192
217
  if (action === "status") {
193
- const st = await git.status(projectPath);
218
+ const st = await git.status(effectivePath);
194
219
 
195
220
  if (args.dryRun) {
196
221
  return asToolResult({
@@ -224,7 +249,15 @@ EXEMPLOS DE USO:
224
249
  _aiContext.suggestedAction = "Working tree limpa. Modifique arquivos ou use action='push' se há commits pendentes";
225
250
  }
226
251
 
227
- return asToolResult({ ...st, _aiContext }, { tool: 'workflow', action: 'status' });
252
+ const result = { ...st, _aiContext };
253
+ if (worktreeCtx?.isWorktree) {
254
+ result._worktreeContext = {
255
+ detected: true,
256
+ repoRoot: worktreeCtx.repoRoot,
257
+ branch: worktreeCtx.branch
258
+ };
259
+ }
260
+ return asToolResult(result, { tool: 'workflow', action: 'status' });
228
261
  }
229
262
  if (action === "add") {
230
263
  const files = Array.isArray(args.files) && args.files.length ? args.files : ["."];
@@ -238,12 +271,12 @@ EXEMPLOS DE USO:
238
271
  });
239
272
  }
240
273
 
241
- await git.add(projectPath, files);
274
+ await git.add(effectivePath, files);
242
275
  return asToolResult({ success: true, files }, { tool: 'workflow', action: 'add' });
243
276
  }
244
277
  if (action === "remove") {
245
278
  const files = Array.isArray(args.files) ? args.files : [];
246
- await git.remove(projectPath, files);
279
+ await git.remove(effectivePath, files);
247
280
  return asToolResult({ success: true, files });
248
281
  }
249
282
  if (action === "commit") {
@@ -259,12 +292,20 @@ EXEMPLOS DE USO:
259
292
  });
260
293
  }
261
294
 
262
- const sha = await git.commit(projectPath, args.message);
295
+ const st = await git.status(effectivePath);
296
+ if (!st.staged || st.staged.length === 0) {
297
+ return asToolError("NOTHING_TO_COMMIT", "Nenhum arquivo staged para commitar", {
298
+ suggestion: "Use action='add' com files=['.'] para adicionar arquivos ao staging antes de commitar",
299
+ status: { modified: st.modified, notAdded: st.not_added }
300
+ });
301
+ }
302
+
303
+ const sha = await git.commit(effectivePath, args.message);
263
304
  return asToolResult({ success: true, sha, message: args.message }, { tool: 'workflow', action: 'commit' });
264
305
  }
265
306
  if (action === "clean") {
266
307
  if (args.dryRun) {
267
- const result = await git.cleanUntracked(projectPath);
308
+ const result = await git.cleanUntracked(effectivePath);
268
309
  return asToolResult({
269
310
  success: true,
270
311
  dryRun: true,
@@ -273,7 +314,7 @@ EXEMPLOS DE USO:
273
314
  });
274
315
  }
275
316
 
276
- const result = await git.cleanUntracked(projectPath);
317
+ const result = await git.cleanUntracked(effectivePath);
277
318
  return asToolResult({
278
319
  success: true,
279
320
  ...result,
@@ -283,7 +324,7 @@ EXEMPLOS DE USO:
283
324
  });
284
325
  }
285
326
  if (action === "ensure-remotes") {
286
- const repo = getRepoNameFromPath(projectPath);
327
+ const repo = getRepoNameFromPath(effectivePath);
287
328
  const isPublic = args.isPublic === true; // Default: privado
288
329
  const organization = args.organization || undefined;
289
330
 
@@ -310,12 +351,12 @@ EXEMPLOS DE USO:
310
351
  const giteaBase = pm.giteaUrl?.replace(/\/$/, "");
311
352
  const giteaUrl = ensured.gitea?.ok && ensured.gitea.repo && giteaBase
312
353
  ? `${giteaBase}/${ensured.gitea.repo}.git` : "";
313
- await git.ensureRemotes(projectPath, { githubUrl, giteaUrl, organization });
314
- const remotes = await git.listRemotes(projectPath);
354
+ await git.ensureRemotes(effectivePath, { githubUrl, giteaUrl, organization });
355
+ const remotes = await git.listRemotes(effectivePath);
315
356
  return asToolResult({ success: true, ensured, remotes, isPrivate: !isPublic, organization: organization || undefined }, { tool: 'workflow', action: 'ensure-remotes' });
316
357
  }
317
358
  if (action === "push") {
318
- const branch = await git.getCurrentBranch(projectPath);
359
+ const branch = await git.getCurrentBranch(effectivePath);
319
360
  const force = !!args.force;
320
361
 
321
362
  if (args.dryRun) {
@@ -335,7 +376,7 @@ EXEMPLOS DE USO:
335
376
 
336
377
  // Retry logic for push (often fails due to network or concurrent updates)
337
378
  const result = await withRetry(
338
- () => git.pushParallel(projectPath, branch, force, organization),
379
+ () => git.pushParallel(effectivePath, branch, force, organization),
339
380
  3,
340
381
  "push"
341
382
  );
@@ -350,15 +391,15 @@ EXEMPLOS DE USO:
350
391
  }
351
392
 
352
393
  const channel = args.channel || "production";
353
- const wtPath = path.join(projectPath, branch);
394
+ const wtPath = path.join(projectPath, branch); // Cria relativo ao path original solicitado
354
395
 
355
396
  if (fs.existsSync(wtPath)) {
356
397
  return asToolError("WORKTREE_PATH_EXISTS", `Diretório '${wtPath}' já existe.`, { suggestion: "Use git-worktree add se quiser configurar path customizado" });
357
398
  }
358
399
 
359
400
  try {
360
- await git.addWorktree(projectPath, branch, wtPath);
361
- await git.setWorktreeConfig(projectPath, branch, { path: wtPath, channel });
401
+ await git.addWorktree(effectivePath, branch, wtPath);
402
+ await git.setWorktreeConfig(effectivePath, branch, { path: wtPath, channel });
362
403
 
363
404
  return asToolResult({
364
405
  success: true,
@@ -390,13 +431,13 @@ EXEMPLOS DE USO:
390
431
  if (args.dryRun) {
391
432
  gitignored = gitignorePatterns;
392
433
  } else {
393
- await git.addToGitignore(projectPath, gitignorePatterns);
434
+ await git.addToGitignore(effectivePath, gitignorePatterns);
394
435
  gitignored = gitignorePatterns;
395
436
  }
396
437
  }
397
438
 
398
439
  // 1. Status para ver o que mudou
399
- const status = await git.status(projectPath);
440
+ const status = await git.status(effectivePath);
400
441
 
401
442
  const hasChanges = !status.isClean ||
402
443
  (status.not_added?.length > 0) ||
@@ -438,15 +479,15 @@ EXEMPLOS DE USO:
438
479
  }
439
480
 
440
481
  // 2. Add
441
- await git.add(projectPath, files);
482
+ await git.add(effectivePath, files);
442
483
 
443
484
  // 3. Commit
444
- const sha = await git.commit(projectPath, args.message);
485
+ const sha = await git.commit(effectivePath, args.message);
445
486
 
446
487
  // 3.5. Garantir remotes com organization (se fornecida)
447
488
  const organization = args.organization || undefined;
448
489
  if (organization) {
449
- const repo = getRepoNameFromPath(projectPath);
490
+ const repo = getRepoNameFromPath(effectivePath);
450
491
  const isPublic = args.isPublic === true;
451
492
  const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic, organization });
452
493
  // Build URLs from actual ensureRepos results (handles GitHub fallback to personal account)
@@ -455,11 +496,11 @@ EXEMPLOS DE USO:
455
496
  const giteaBase = pm.giteaUrl?.replace(/\/$/, "");
456
497
  const giteaUrl = ensured.gitea?.ok && ensured.gitea.repo && giteaBase
457
498
  ? `${giteaBase}/${ensured.gitea.repo}.git` : "";
458
- await git.ensureRemotes(projectPath, { githubUrl, giteaUrl, organization });
499
+ await git.ensureRemotes(effectivePath, { githubUrl, giteaUrl, organization });
459
500
  }
460
501
 
461
502
  // 4. Push Strategy
462
- const branch = await git.getCurrentBranch(projectPath);
503
+ const branch = await git.getCurrentBranch(effectivePath);
463
504
  let pushResult = {};
464
505
  let synced = [];
465
506
  let errors = [];
@@ -467,7 +508,7 @@ EXEMPLOS DE USO:
467
508
  // Determine Channel
468
509
  let channel = args.channel;
469
510
  if (!channel) {
470
- const storedChannel = await git.getConfig(projectPath, `worktree-branch.${branch}.channel`);
511
+ const storedChannel = await git.getConfig(effectivePath, `worktree-branch.${branch}.channel`);
471
512
  if (storedChannel) channel = storedChannel;
472
513
  }
473
514
 
@@ -477,13 +518,13 @@ EXEMPLOS DE USO:
477
518
  // SYNC BRANCHES
478
519
  if (syncBranches) {
479
520
  // Só permitido no principal (onde .git é diretório)
480
- if (fs.existsSync(path.join(projectPath, ".git")) && fs.statSync(path.join(projectPath, ".git")).isFile()) {
521
+ if (fs.existsSync(path.join(effectivePath, ".git")) && fs.statSync(path.join(effectivePath, ".git")).isFile()) {
481
522
  return asToolError("INVALID_OPERATION", "syncBranches=true só pode ser executado a partir do repositório principal, não de um worktree.");
482
523
  }
483
524
 
484
525
  // 1. Push do principal (current)
485
526
  const remoteBranch = resolveRemoteBranch(branch, channel);
486
- const remotes = await git.listRemotes(projectPath);
527
+ const remotes = await git.listRemotes(effectivePath);
487
528
 
488
529
  await sendLog(server, "info", "update: push iniciado (sync)", { branch, remoteBranch, force });
489
530
 
@@ -491,7 +532,7 @@ EXEMPLOS DE USO:
491
532
  const mainFailed = [];
492
533
  for (const r of remotes) {
493
534
  try {
494
- await git.pushRefspec(projectPath, r.remote, branch, remoteBranch, force);
535
+ await git.pushRefspec(effectivePath, r.remote, branch, remoteBranch, force);
495
536
  mainPushed.push(r.remote);
496
537
  } catch(e) {
497
538
  mainFailed.push({ remote: r.remote, error: e.message });
@@ -501,7 +542,7 @@ EXEMPLOS DE USO:
501
542
  // 2. Propagar para worktrees
502
543
  // Req 8.3: synced.length + errors.length === N (número de worktrees registrados)
503
544
  // O principal NÃO entra no contador — só os worktrees registrados
504
- const configs = await git.getWorktreeConfigs(projectPath);
545
+ const configs = await git.getWorktreeConfigs(effectivePath);
505
546
 
506
547
  for (const wt of configs) {
507
548
  // Pula se for a própria branch principal (caso esteja registrada)
@@ -538,13 +579,13 @@ EXEMPLOS DE USO:
538
579
  // Comportamento padrão (production)
539
580
  await sendLog(server, "info", "update: push iniciado", { branch, force });
540
581
  pushResult = await withRetry(
541
- () => git.pushParallel(projectPath, branch, force, organization),
582
+ () => git.pushParallel(effectivePath, branch, force, organization),
542
583
  3,
543
584
  "push"
544
585
  );
545
586
  } else {
546
587
  // Comportamento customizado (beta/alpha) -> pushRefspec
547
- const remotes = await git.listRemotes(projectPath);
588
+ const remotes = await git.listRemotes(effectivePath);
548
589
 
549
590
  const pushed = [];
550
591
  const failed = [];
@@ -553,7 +594,7 @@ EXEMPLOS DE USO:
553
594
 
554
595
  for (const r of remotes) {
555
596
  try {
556
- await git.pushRefspec(projectPath, r.remote, branch, remoteBranch, force);
597
+ await git.pushRefspec(effectivePath, r.remote, branch, remoteBranch, force);
557
598
  pushed.push(r.remote);
558
599
  } catch (e) {
559
600
  failed.push({ remote: r.remote, error: e.message });
@@ -568,7 +609,7 @@ EXEMPLOS DE USO:
568
609
  }
569
610
  }
570
611
 
571
- return asToolResult({
612
+ const result = {
572
613
  success: true,
573
614
  action: "update",
574
615
  steps: ["status", "add", "commit", "push"],
@@ -583,7 +624,17 @@ EXEMPLOS DE USO:
583
624
  completed: true,
584
625
  message: "Ciclo completo: arquivos adicionados, commit criado e push realizado" + (organization ? ` [org: ${organization}]` : "")
585
626
  }
586
- }, { tool: 'workflow', action: 'update' });
627
+ };
628
+
629
+ if (worktreeCtx?.isWorktree) {
630
+ result._worktreeContext = {
631
+ detected: true,
632
+ repoRoot: worktreeCtx.repoRoot,
633
+ branch: worktreeCtx.branch
634
+ };
635
+ }
636
+
637
+ return asToolResult(result, { tool: 'workflow', action: 'update' });
587
638
  }
588
639
 
589
640
  return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
5
5
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
6
+ import { resolveWorktreeContext, isProtectedPath } from "../utils/worktreeResolver.js";
6
7
 
7
8
  const ajv = new Ajv({ allErrors: true });
8
9
 
@@ -75,62 +76,59 @@ AÇÕES:
75
76
  const validate = ajv.compile(inputSchema);
76
77
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
77
78
  const { projectPath, action } = args;
79
+ let effectivePath = projectPath;
80
+ let worktreeCtx = null;
78
81
 
79
82
  try {
80
83
  validateProjectPath(projectPath);
81
84
 
85
+ worktreeCtx = await resolveWorktreeContext(projectPath, git);
86
+ effectivePath = worktreeCtx.worktreePath;
87
+
88
+ const _worktreeContext = worktreeCtx.isWorktree ? {
89
+ detected: true,
90
+ repoRoot: worktreeCtx.repoRoot,
91
+ branch: worktreeCtx.branch
92
+ } : undefined;
93
+
82
94
  if (action === "list") {
83
- const worktrees = await git.listWorktrees(projectPath);
95
+ const worktrees = await git.listWorktrees(effectivePath);
84
96
 
85
97
  // Enrich with channel info if available
86
98
  const enriched = [];
87
99
  for (const wt of worktrees) {
88
- const channel = await git.getConfig(projectPath, `worktree-branch.${wt.branch}.channel`);
100
+ const channel = await git.getConfig(effectivePath, `worktree-branch.${wt.branch}.channel`);
89
101
  enriched.push({ ...wt, channel: channel || "production" }); // Default to production
90
102
  }
91
103
 
92
- return asToolResult({ worktrees: enriched, count: enriched.length });
104
+ return asToolResult({ worktrees: enriched, count: enriched.length, _worktreeContext });
93
105
  }
94
106
 
95
107
  if (action === "add") {
96
108
  if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para add", { parameter: "branch" });
97
109
 
98
- // Default path: sibling directory with branch name
99
- // Se projectPath é .../repo/main, e branch é 'feature', cria .../repo/feature
100
- // Mas git worktree add path branch.
101
-
102
- // Se path não fornecido, assumir irmão do diretório atual se estivermos em um repo.
103
- // Mas projectPath é a raiz do repo.
104
- // Vamos usar path relativo ou absoluto fornecido, ou default: join(projectPath, "..", branch)
105
-
106
- const targetPath = args.path
110
+ // Cria relativo ao projectPath original se path não for absoluto
111
+ const wtPath = args.path
107
112
  ? path.resolve(projectPath, args.path)
108
- : path.join(projectPath, args.branch); // Cria DENTRO do projeto? Não, worktrees geralmente ficam ao lado ou fora.
109
-
110
- // Melhor prática: criar dentro se for estrutura monorepo-style, ou fora.
111
- // O git-mcp v16 parece usar estrutura plana ou aninhada.
112
- // Vamos usar o comportamento padrão do git: path relativo à raiz.
113
- // Se o usuário passar apenas o nome da branch como path, o git cria ./branch-name.
114
-
115
- // Se path não informado, usa o nome da branch (cria pasta ./branch-name dentro do repo atual)
116
- // Isso pode sujar o repo principal se não estiver no gitignore.
117
- // Vamos sugerir ou usar ../branch-name se estivermos na raiz?
118
- // Simplicidade: user define path ou usa ./branch-name.
119
-
120
- const wtPath = args.path || args.branch;
113
+ : path.join(projectPath, args.branch);
121
114
 
122
- await withRetry(() => git.addWorktree(projectPath, args.branch, wtPath, args.force), 3, "worktree-add");
115
+ if (isProtectedPath(wtPath)) {
116
+ return asToolError("PROTECTED_PATH", `Não é permitido criar worktree em ${wtPath}`);
117
+ }
118
+
119
+ await withRetry(() => git.addWorktree(effectivePath, args.branch, wtPath, args.force), 3, "worktree-add");
123
120
 
124
121
  // Set channel if provided
125
122
  if (args.channel) {
126
- await git.setWorktreeConfig(projectPath, args.branch, { channel: args.channel });
123
+ await git.setWorktreeConfig(effectivePath, args.branch, { channel: args.channel });
127
124
  }
128
125
 
129
126
  return asToolResult({
130
127
  success: true,
131
128
  branch: args.branch,
132
- path: path.resolve(projectPath, wtPath),
133
- channel: args.channel || "production"
129
+ path: wtPath,
130
+ channel: args.channel || "production",
131
+ _worktreeContext
134
132
  });
135
133
  }
136
134
 
@@ -141,27 +139,54 @@ AÇÕES:
141
139
  // Precisamos encontrar o path se só branch for fornecida
142
140
  let targetPath = args.path;
143
141
  if (!targetPath && args.branch) {
144
- const wts = await git.listWorktrees(projectPath);
142
+ const wts = await git.listWorktrees(effectivePath);
145
143
  const found = wts.find(w => w.branch === args.branch);
146
144
  if (!found) return asToolError("WORKTREE_NOT_FOUND", `Worktree para branch '${args.branch}' não encontrado`);
147
145
  targetPath = found.path;
148
146
  }
149
147
 
150
- await withRetry(() => git.removeWorktree(projectPath, targetPath, args.force), 3, "worktree-remove");
151
- return asToolResult({ success: true, removed: targetPath });
148
+ await withRetry(() => git.removeWorktree(effectivePath, targetPath, args.force), 3, "worktree-remove");
149
+ return asToolResult({ success: true, removed: targetPath, _worktreeContext });
152
150
  }
153
151
 
154
152
  if (action === "prune") {
155
- await git.pruneWorktrees(projectPath);
156
- return asToolResult({ success: true, message: "Worktrees podados" });
153
+ await git.pruneWorktrees(effectivePath);
154
+ return asToolResult({ success: true, message: "Worktrees podados", _worktreeContext });
157
155
  }
158
156
 
159
157
  if (action === "set-channel") {
160
158
  if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório");
161
159
  if (!args.channel) return asToolError("MISSING_PARAMETER", "channel é obrigatório");
162
160
 
163
- await git.setWorktreeConfig(projectPath, args.branch, { channel: args.channel });
164
- return asToolResult({ success: true, branch: args.branch, channel: args.channel });
161
+ await git.setWorktreeConfig(effectivePath, args.branch, { channel: args.channel });
162
+ return asToolResult({ success: true, branch: args.branch, channel: args.channel, _worktreeContext });
163
+ }
164
+
165
+ if (action === "setup") {
166
+ // Setup action logic - Estrutura recomendada
167
+ // 1. Verifica se está num repo
168
+ // 2. Se não estiver, init
169
+ // 3. Move master para subpasta 'main' se estiver na raiz?
170
+ // Simplificação: Apenas cria as pastas se não existirem
171
+
172
+ const mainPath = path.join(effectivePath, 'main');
173
+ const worktreesPath = path.join(effectivePath, 'worktrees');
174
+
175
+ if (!fs.existsSync(mainPath)) {
176
+ // Se estamos na raiz de um repo normal, setup é meio redundante se não formos mover arquivos.
177
+ // Mas se o usuário quer estruturar do zero:
178
+ // git init bare? Não, o spec não diz bare.
179
+
180
+ // Vamos apenas retornar uma mensagem informativa por enquanto, pois a migração automática é arriscada.
181
+ return asToolResult({
182
+ success: true,
183
+ message: "Para configurar worktrees, recomenda-se mover o branch principal para uma pasta 'main' e criar novos worktrees em pastas irmãs.",
184
+ structure: {
185
+ root: projectPath,
186
+ recommended: ["main/", "feature-branch/", "hotfix/"]
187
+ }
188
+ });
189
+ }
165
190
  }
166
191
 
167
192
  return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["add", "list", "remove", "prune", "set-channel"] });