@andrebuzeli/git-mcp 13.7.0 → 14.0.0

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": "13.7.0",
3
+ "version": "14.0.0",
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",
@@ -7,16 +7,52 @@ export function createGitBranchesTool(git) {
7
7
  const inputSchema = {
8
8
  type: "object",
9
9
  properties: {
10
- projectPath: { type: "string" },
11
- action: { type: "string", enum: ["list", "create", "delete", "rename", "checkout"] },
12
- branch: { type: "string" },
13
- newBranch: { type: "string" },
14
- force: { type: "boolean" }
10
+ projectPath: {
11
+ type: "string",
12
+ description: "Caminho absoluto do diretório do projeto"
13
+ },
14
+ action: {
15
+ type: "string",
16
+ enum: ["list", "create", "delete", "rename", "checkout"],
17
+ description: `Ação a executar:
18
+ - list: Lista todas as branches locais e remotas
19
+ - create: Cria nova branch a partir do HEAD atual
20
+ - delete: Remove uma branch (não pode ser a atual)
21
+ - rename: Renomeia uma branch existente
22
+ - checkout: Muda para outra branch`
23
+ },
24
+ branch: {
25
+ type: "string",
26
+ description: "Nome da branch para create/delete/checkout. Ex: 'feature/nova-funcionalidade', 'bugfix/correcao'"
27
+ },
28
+ newBranch: {
29
+ type: "string",
30
+ description: "Novo nome da branch (apenas para action='rename')"
31
+ },
32
+ force: {
33
+ type: "boolean",
34
+ description: "Forçar delete mesmo se branch não está merged. Default: false"
35
+ }
15
36
  },
16
37
  required: ["projectPath", "action"],
17
- additionalProperties: true
38
+ additionalProperties: false
18
39
  };
19
40
 
41
+ const description = `Gerenciamento de branches Git.
42
+
43
+ AÇÕES DISPONÍVEIS:
44
+ - list: Ver branches existentes (locais e remotas)
45
+ - create: Criar nova branch para desenvolvimento
46
+ - checkout: Trocar de branch
47
+ - delete: Remover branch após merge
48
+ - rename: Renomear branch
49
+
50
+ CONVENÇÕES DE NOMES:
51
+ - feature/nome: Para novas funcionalidades
52
+ - bugfix/nome: Para correções de bugs
53
+ - hotfix/nome: Para correções urgentes
54
+ - release/versao: Para releases`;
55
+
20
56
  async function handle(args) {
21
57
  const validate = ajv.compile(inputSchema);
22
58
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
@@ -25,53 +61,46 @@ export function createGitBranchesTool(git) {
25
61
  if (action === "list") {
26
62
  const local = await git.listBranches(projectPath, false);
27
63
  const remote = await git.listBranches(projectPath, true).catch(() => []);
28
- return asToolResult({ local, remote });
64
+ const current = await git.getCurrentBranch(projectPath);
65
+ return asToolResult({ current, local, remote });
29
66
  }
30
67
  if (action === "create") {
31
- const ref = args.branch;
32
- if (!ref) return asToolError("MISSING_PARAMETER", "branch é obrigatório", { parameter: "branch" });
33
- // Auto-correção: verificar se já existe
68
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para criar", { parameter: "branch" });
34
69
  const existing = await git.listBranches(projectPath, false);
35
- if (existing.includes(ref)) {
36
- return asToolError("BRANCH_ALREADY_EXISTS", `Branch '${ref}' já existe`, { branch: ref, existingBranches: existing });
70
+ if (existing.includes(args.branch)) {
71
+ return asToolError("BRANCH_ALREADY_EXISTS", `Branch '${args.branch}' já existe`, { branch: args.branch, existingBranches: existing });
37
72
  }
38
- await git.createBranch(projectPath, ref);
39
- return asToolResult({ success: true, branch: ref });
73
+ await git.createBranch(projectPath, args.branch);
74
+ return asToolResult({ success: true, branch: args.branch, message: `Branch '${args.branch}' criada. Use action='checkout' para mudar para ela.` });
40
75
  }
41
76
  if (action === "delete") {
42
- const ref = args.branch;
43
- if (!ref) return asToolError("MISSING_PARAMETER", "branch é obrigatório", { parameter: "branch" });
44
- // Auto-correção: verificar se existe e não é a atual
77
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para deletar", { parameter: "branch" });
45
78
  const existing = await git.listBranches(projectPath, false);
46
- if (!existing.includes(ref)) {
47
- return asToolError("BRANCH_NOT_FOUND", `Branch '${ref}' não encontrada`, { branch: ref, availableBranches: existing });
79
+ if (!existing.includes(args.branch)) {
80
+ return asToolError("BRANCH_NOT_FOUND", `Branch '${args.branch}' não encontrada`, { branch: args.branch, availableBranches: existing });
48
81
  }
49
82
  const current = await git.getCurrentBranch(projectPath);
50
- if (ref === current) {
51
- return asToolError("CANNOT_DELETE_CURRENT", `Não pode deletar branch atual '${ref}'`, { branch: ref, suggestion: "Faça checkout para outra branch primeiro" });
83
+ if (args.branch === current) {
84
+ return asToolError("CANNOT_DELETE_CURRENT", `Não pode deletar branch atual '${args.branch}'`, { branch: args.branch, suggestion: "Faça checkout para outra branch primeiro" });
52
85
  }
53
- await git.deleteBranch(projectPath, ref, !!args.force);
54
- return asToolResult({ success: true, branch: ref });
86
+ await git.deleteBranch(projectPath, args.branch, !!args.force);
87
+ return asToolResult({ success: true, branch: args.branch, deleted: true });
55
88
  }
56
89
  if (action === "rename") {
57
- const oldName = args.branch;
58
- const newName = args.newBranch;
59
- if (!oldName || !newName) return asToolError("MISSING_PARAMETER", "branch e newBranch são obrigatórios", { parameters: ["branch", "newBranch"] });
60
- await git.renameBranch(projectPath, oldName, newName);
61
- return asToolResult({ success: true, from: oldName, to: newName });
90
+ if (!args.branch || !args.newBranch) return asToolError("MISSING_PARAMETER", "branch e newBranch são obrigatórios", { parameters: ["branch", "newBranch"] });
91
+ await git.renameBranch(projectPath, args.branch, args.newBranch);
92
+ return asToolResult({ success: true, from: args.branch, to: args.newBranch });
62
93
  }
63
94
  if (action === "checkout") {
64
- const ref = args.branch;
65
- if (!ref) return asToolError("MISSING_PARAMETER", "branch é obrigatório", { parameter: "branch" });
66
- await git.checkout(projectPath, ref);
67
- return asToolResult({ success: true, branch: ref });
95
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para checkout", { parameter: "branch" });
96
+ await git.checkout(projectPath, args.branch);
97
+ return asToolResult({ success: true, branch: args.branch, message: `Mudou para branch '${args.branch}'` });
68
98
  }
69
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${action}`, { availableActions: ["list", "create", "delete", "rename", "checkout"] });
99
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "delete", "rename", "checkout"] });
70
100
  } catch (e) {
71
101
  return errorToResponse(e);
72
102
  }
73
103
  }
74
104
 
75
- return { name: "git-branches", description: "Gerencia branches", inputSchema, handle };
105
+ return { name: "git-branches", description, inputSchema, handle };
76
106
  }
77
-
@@ -7,16 +7,55 @@ export function createGitConfigTool(git) {
7
7
  const inputSchema = {
8
8
  type: "object",
9
9
  properties: {
10
- projectPath: { type: "string" },
11
- action: { type: "string", enum: ["get", "set", "unset", "list"] },
12
- key: { type: "string" },
13
- value: { type: "string" },
14
- scope: { type: "string", enum: ["local", "global", "system"], default: "local" }
10
+ projectPath: {
11
+ type: "string",
12
+ description: "Caminho absoluto do diretório do projeto"
13
+ },
14
+ action: {
15
+ type: "string",
16
+ enum: ["get", "set", "unset", "list"],
17
+ description: `Ação a executar:
18
+ - get: Obter valor de uma configuração específica
19
+ - set: Definir valor de uma configuração
20
+ - unset: Remover uma configuração
21
+ - list: Listar todas as configurações do escopo`
22
+ },
23
+ key: {
24
+ type: "string",
25
+ description: "Chave da configuração. Ex: 'user.name', 'user.email', 'core.autocrlf'"
26
+ },
27
+ value: {
28
+ type: "string",
29
+ description: "Valor a definir (apenas para action='set')"
30
+ },
31
+ scope: {
32
+ type: "string",
33
+ enum: ["local", "global", "system"],
34
+ description: "Escopo da configuração. local: apenas este repo, global: todos os repos do usuário, system: todo o sistema"
35
+ }
15
36
  },
16
37
  required: ["projectPath", "action"],
17
- additionalProperties: true
38
+ additionalProperties: false
18
39
  };
19
40
 
41
+ const description = `Gerenciamento de configurações Git.
42
+
43
+ CONFIGURAÇÕES COMUNS:
44
+ - user.name: Nome do autor dos commits
45
+ - user.email: Email do autor dos commits
46
+ - core.autocrlf: Conversão de fim de linha (true/false/input)
47
+ - core.editor: Editor padrão para commits
48
+
49
+ ESCOPOS:
50
+ - local: Apenas neste repositório (.git/config)
51
+ - global: Todos os repos do usuário (~/.gitconfig)
52
+ - system: Todo o sistema (/etc/gitconfig)
53
+
54
+ EXEMPLOS:
55
+ - Definir nome: action='set' key='user.name' value='Meu Nome'
56
+ - Definir email: action='set' key='user.email' value='email@example.com'
57
+ - Ver nome: action='get' key='user.name'`;
58
+
20
59
  async function handle(args) {
21
60
  const validate = ajv.compile(inputSchema);
22
61
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
@@ -26,27 +65,27 @@ export function createGitConfigTool(git) {
26
65
  if (action === "get") {
27
66
  if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
28
67
  const val = await git.getConfig(projectPath, args.key, scope);
29
- return asToolResult({ key: args.key, value: val, found: val !== undefined });
68
+ return asToolResult({ key: args.key, value: val, found: val !== undefined, scope });
30
69
  }
31
70
  if (action === "set") {
32
71
  if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
33
72
  await git.setConfig(projectPath, args.key, args.value ?? "", scope);
34
- return asToolResult({ success: true, key: args.key, value: args.value ?? "" });
73
+ return asToolResult({ success: true, key: args.key, value: args.value ?? "", scope });
35
74
  }
36
75
  if (action === "unset") {
37
76
  if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
38
77
  await git.unsetConfig(projectPath, args.key, scope);
39
- return asToolResult({ success: true, key: args.key });
78
+ return asToolResult({ success: true, key: args.key, removed: true, scope });
40
79
  }
41
80
  if (action === "list") {
42
81
  const items = await git.listConfig(projectPath, scope);
43
- return asToolResult({ scope, items, count: Object.keys(items).length });
82
+ return asToolResult({ scope, configs: items, count: Object.keys(items).length });
44
83
  }
45
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${action}`, { availableActions: ["get", "set", "unset", "list"] });
84
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["get", "set", "unset", "list"] });
46
85
  } catch (e) {
47
86
  return errorToResponse(e);
48
87
  }
49
88
  }
50
89
 
51
- return { name: "git-config", description: "Gerencia configurações Git (local/global/system)", inputSchema, handle };
90
+ return { name: "git-config", description, inputSchema, handle };
52
91
  }
@@ -7,15 +7,45 @@ export function createGitFilesTool(git) {
7
7
  const inputSchema = {
8
8
  type: "object",
9
9
  properties: {
10
- projectPath: { type: "string" },
11
- action: { type: "string", enum: ["list", "read"] },
12
- filepath: { type: "string" },
13
- ref: { type: "string" }
10
+ projectPath: {
11
+ type: "string",
12
+ description: "Caminho absoluto do diretório do projeto"
13
+ },
14
+ action: {
15
+ type: "string",
16
+ enum: ["list", "read"],
17
+ description: `Ação a executar:
18
+ - list: Lista todos os arquivos rastreados pelo Git em um commit/branch
19
+ - read: Lê o conteúdo de um arquivo em um commit/branch específico`
20
+ },
21
+ filepath: {
22
+ type: "string",
23
+ description: "Caminho do arquivo relativo à raiz do repo (action='read'). Ex: 'src/index.js', 'package.json'"
24
+ },
25
+ ref: {
26
+ type: "string",
27
+ description: "Referência Git para ler (commit SHA, branch, tag). Default: HEAD (commit atual)"
28
+ }
14
29
  },
15
30
  required: ["projectPath", "action"],
16
- additionalProperties: true
31
+ additionalProperties: false
17
32
  };
18
33
 
34
+ const description = `Leitura de arquivos do repositório Git.
35
+
36
+ QUANDO USAR:
37
+ - Ver arquivos de um commit antigo
38
+ - Comparar versões de arquivo entre commits
39
+ - Listar arquivos rastreados pelo Git
40
+
41
+ EXEMPLOS:
42
+ - Listar arquivos: action='list'
43
+ - Listar arquivos de tag: action='list' ref='v1.0.0'
44
+ - Ler arquivo atual: action='read' filepath='package.json'
45
+ - Ler arquivo de commit antigo: action='read' filepath='src/index.js' ref='abc1234'
46
+
47
+ NOTA: Esta tool lê arquivos do histórico Git, não do sistema de arquivos.`;
48
+
19
49
  async function handle(args) {
20
50
  const validate = ajv.compile(inputSchema);
21
51
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
@@ -23,7 +53,7 @@ export function createGitFilesTool(git) {
23
53
  try {
24
54
  if (action === "list") {
25
55
  const files = await git.listFiles(projectPath, args.ref || "HEAD");
26
- return asToolResult({ files, count: files.length });
56
+ return asToolResult({ files, count: files.length, ref: args.ref || "HEAD" });
27
57
  }
28
58
  if (action === "read") {
29
59
  if (!args.filepath) return asToolError("MISSING_PARAMETER", "filepath é obrigatório", { parameter: "filepath" });
@@ -31,21 +61,20 @@ export function createGitFilesTool(git) {
31
61
  const text = await git.readFile(projectPath, args.filepath, args.ref || "HEAD");
32
62
  return { content: [{ type: "text", text }], isError: false };
33
63
  } catch (e) {
34
- // Auto-diagnóstico: listar arquivos disponíveis se não encontrar
35
64
  const files = await git.listFiles(projectPath, args.ref || "HEAD").catch(() => []);
36
65
  return asToolError("FILE_NOT_FOUND", `Arquivo '${args.filepath}' não encontrado`, {
37
66
  filepath: args.filepath,
38
67
  ref: args.ref || "HEAD",
39
- availableFiles: files.slice(0, 20),
68
+ similarFiles: files.filter(f => f.includes(args.filepath.split("/").pop())).slice(0, 10),
40
69
  totalFiles: files.length
41
70
  });
42
71
  }
43
72
  }
44
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${action}`, { availableActions: ["list", "read"] });
73
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "read"] });
45
74
  } catch (e) {
46
75
  return errorToResponse(e);
47
76
  }
48
77
  }
49
78
 
50
- return { name: "git-files", description: "Lista e lê arquivos do repo", inputSchema, handle };
79
+ return { name: "git-files", description, inputSchema, handle };
51
80
  }
@@ -7,15 +7,49 @@ export function createGitHistoryTool(git) {
7
7
  const inputSchema = {
8
8
  type: "object",
9
9
  properties: {
10
- projectPath: { type: "string" },
11
- action: { type: "string", enum: ["log"] },
12
- ref: { type: "string" },
13
- maxCount: { type: "number" }
10
+ projectPath: {
11
+ type: "string",
12
+ description: "Caminho absoluto do diretório do projeto"
13
+ },
14
+ action: {
15
+ type: "string",
16
+ enum: ["log"],
17
+ description: "Ação a executar: log - exibe histórico de commits"
18
+ },
19
+ ref: {
20
+ type: "string",
21
+ description: "Referência inicial para o log (branch, tag, SHA). Default: HEAD"
22
+ },
23
+ maxCount: {
24
+ type: "number",
25
+ description: "Número máximo de commits a retornar. Default: 50"
26
+ }
14
27
  },
15
28
  required: ["projectPath", "action"],
16
- additionalProperties: true
29
+ additionalProperties: false
17
30
  };
18
31
 
32
+ const description = `Histórico de commits do repositório Git.
33
+
34
+ QUANDO USAR:
35
+ - Ver commits recentes
36
+ - Encontrar SHA de commit específico para reset/checkout
37
+ - Verificar quem fez qual alteração
38
+ - Ver histórico de uma branch
39
+
40
+ INFORMAÇÕES RETORNADAS:
41
+ - sha: Hash completo do commit
42
+ - shortSha: Hash curto (7 caracteres)
43
+ - message: Mensagem do commit
44
+ - author: Nome do autor
45
+ - email: Email do autor
46
+ - date: Data do commit
47
+
48
+ EXEMPLOS:
49
+ - Ver últimos 10 commits: action='log' maxCount=10
50
+ - Ver histórico de branch: action='log' ref='develop'
51
+ - Ver histórico de tag: action='log' ref='v1.0.0'`;
52
+
19
53
  async function handle(args) {
20
54
  const validate = ajv.compile(inputSchema);
21
55
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
@@ -27,7 +61,7 @@ export function createGitHistoryTool(git) {
27
61
  return asToolResult({
28
62
  commits: [],
29
63
  count: 0,
30
- message: "Nenhum commit encontrado. Use action='commit' para criar o primeiro commit."
64
+ message: "Nenhum commit encontrado. Use git-workflow action='commit' para criar o primeiro commit."
31
65
  });
32
66
  }
33
67
  return asToolResult({
@@ -40,14 +74,15 @@ export function createGitHistoryTool(git) {
40
74
  email: c.author.email,
41
75
  date: c.date
42
76
  })),
43
- count: items.length
77
+ count: items.length,
78
+ ref: args.ref || "HEAD"
44
79
  });
45
80
  }
46
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${action}`, { availableActions: ["log"] });
81
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["log"] });
47
82
  } catch (e) {
48
83
  return errorToResponse(e);
49
84
  }
50
85
  }
51
86
 
52
- return { name: "git-history", description: "Histórico de commits", inputSchema, handle };
87
+ return { name: "git-history", description, inputSchema, handle };
53
88
  }
@@ -7,14 +7,51 @@ export function createGitIgnoreTool(git) {
7
7
  const inputSchema = {
8
8
  type: "object",
9
9
  properties: {
10
- projectPath: { type: "string" },
11
- action: { type: "string", enum: ["list", "create", "add", "remove"] },
12
- patterns: { type: "array", items: { type: "string" } }
10
+ projectPath: {
11
+ type: "string",
12
+ description: "Caminho absoluto do diretório do projeto"
13
+ },
14
+ action: {
15
+ type: "string",
16
+ enum: ["list", "create", "add", "remove"],
17
+ description: `Ação a executar:
18
+ - list: Lista padrões atuais do .gitignore
19
+ - create: Cria novo .gitignore (sobrescreve existente)
20
+ - add: Adiciona padrões ao .gitignore existente
21
+ - remove: Remove padrões do .gitignore`
22
+ },
23
+ patterns: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ description: "Padrões para ignorar. Ex: ['node_modules/', '*.log', '.env', 'dist/', '*.tmp']"
27
+ }
13
28
  },
14
29
  required: ["projectPath", "action"],
15
- additionalProperties: true
30
+ additionalProperties: false
16
31
  };
17
32
 
33
+ const description = `Gerenciamento do arquivo .gitignore.
34
+
35
+ PADRÕES COMUNS:
36
+ - node_modules/: Dependências Node.js
37
+ - *.log: Arquivos de log
38
+ - .env: Variáveis de ambiente (segredos)
39
+ - dist/, build/: Arquivos compilados
40
+ - .DS_Store: Arquivos do macOS
41
+ - Thumbs.db: Arquivos do Windows
42
+ - *.tmp, *.bak: Arquivos temporários
43
+ - .idea/, .vscode/: Configurações de IDE
44
+
45
+ SINTAXE:
46
+ - pasta/: Ignora diretório
47
+ - *.ext: Ignora por extensão
48
+ - !arquivo: Exceção (não ignorar)
49
+ - **/pasta: Ignora em qualquer nível
50
+
51
+ EXEMPLOS:
52
+ - Criar para Node.js: action='create' patterns=['node_modules/', '*.log', '.env', 'dist/']
53
+ - Adicionar padrão: action='add' patterns=['*.tmp']`;
54
+
18
55
  async function handle(args) {
19
56
  const validate = ajv.compile(inputSchema);
20
57
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
@@ -23,37 +60,37 @@ export function createGitIgnoreTool(git) {
23
60
  try {
24
61
  if (action === "list") {
25
62
  const items = await git.listGitignore(projectPath);
26
- return asToolResult({ items, count: items.length });
63
+ return asToolResult({ patterns: items, count: items.length, hasGitignore: items.length > 0 || true });
27
64
  }
28
65
  if (action === "create") {
29
66
  if (patterns.length === 0) {
30
67
  return asToolError("MISSING_PARAMETER", "patterns é obrigatório para criar .gitignore", {
31
68
  parameter: "patterns",
32
- suggestion: "Forneça um array de padrões, ex: ['node_modules/', '*.log', '.env']"
69
+ suggestion: "Forneça um array de padrões. Ex: ['node_modules/', '*.log', '.env']"
33
70
  });
34
71
  }
35
72
  await git.createGitignore(projectPath, patterns);
36
- return asToolResult({ success: true, patterns });
73
+ return asToolResult({ success: true, patterns, message: ".gitignore criado" });
37
74
  }
38
75
  if (action === "add") {
39
76
  if (patterns.length === 0) {
40
- return asToolError("MISSING_PARAMETER", "patterns é obrigatório para adicionar ao .gitignore", { parameter: "patterns" });
77
+ return asToolError("MISSING_PARAMETER", "patterns é obrigatório", { parameter: "patterns" });
41
78
  }
42
79
  await git.addToGitignore(projectPath, patterns);
43
80
  return asToolResult({ success: true, added: patterns });
44
81
  }
45
82
  if (action === "remove") {
46
83
  if (patterns.length === 0) {
47
- return asToolError("MISSING_PARAMETER", "patterns é obrigatório para remover do .gitignore", { parameter: "patterns" });
84
+ return asToolError("MISSING_PARAMETER", "patterns é obrigatório", { parameter: "patterns" });
48
85
  }
49
86
  await git.removeFromGitignore(projectPath, patterns);
50
87
  return asToolResult({ success: true, removed: patterns });
51
88
  }
52
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${action}`, { availableActions: ["list", "create", "add", "remove"] });
89
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "add", "remove"] });
53
90
  } catch (e) {
54
91
  return errorToResponse(e);
55
92
  }
56
93
  }
57
94
 
58
- return { name: "git-ignore", description: "Gerencia .gitignore", inputSchema, handle };
95
+ return { name: "git-ignore", description, inputSchema, handle };
59
96
  }
@@ -10,49 +10,87 @@ export function createGitIssuesTool(pm) {
10
10
  const inputSchema = {
11
11
  type: "object",
12
12
  properties: {
13
- projectPath: { type: "string" },
14
- action: { type: "string", enum: ["create", "list", "comment"] },
15
- title: { type: "string" },
16
- body: { type: "string" },
17
- number: { type: "number" }
13
+ projectPath: {
14
+ type: "string",
15
+ description: "Caminho absoluto do diretório do projeto"
16
+ },
17
+ action: {
18
+ type: "string",
19
+ enum: ["create", "list", "comment"],
20
+ description: `Ação a executar:
21
+ - create: Cria nova issue no GitHub E Gitea
22
+ - list: Lista issues existentes
23
+ - comment: Adiciona comentário a uma issue existente`
24
+ },
25
+ title: {
26
+ type: "string",
27
+ description: "Título da issue (obrigatório para action='create'). Ex: 'Bug: aplicação crasha ao clicar em salvar'"
28
+ },
29
+ body: {
30
+ type: "string",
31
+ description: "Descrição detalhada da issue ou texto do comentário"
32
+ },
33
+ number: {
34
+ type: "number",
35
+ description: "Número da issue para comentar (obrigatório para action='comment')"
36
+ }
18
37
  },
19
38
  required: ["projectPath", "action"],
20
- additionalProperties: true
39
+ additionalProperties: false
21
40
  };
22
41
 
42
+ const description = `Gerenciamento de Issues no GitHub e Gitea.
43
+
44
+ QUANDO USAR:
45
+ - Reportar bugs encontrados
46
+ - Sugerir novas funcionalidades
47
+ - Documentar tarefas a fazer
48
+ - Acompanhar progresso do projeto
49
+
50
+ AÇÕES:
51
+ - create: Criar nova issue (executa em AMBOS os providers)
52
+ - list: Ver issues existentes
53
+ - comment: Comentar em issue existente
54
+
55
+ BOAS PRÁTICAS:
56
+ - Use títulos descritivos
57
+ - Inclua passos para reproduzir bugs
58
+ - Use labels para categorizar (via git-remote label-create)`;
59
+
23
60
  async function handle(args) {
24
61
  const validate = ajv.compile(inputSchema);
25
62
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
26
63
  const repo = getRepoNameFromPath(args.projectPath);
27
64
  try {
28
65
  if (args.action === "create") {
66
+ if (!args.title) return asToolError("MISSING_PARAMETER", "title é obrigatório para criar issue", { parameter: "title" });
29
67
  const out = await runBoth(pm, {
30
- github: async (owner) => { const r = await pm.github.rest.issues.create({ owner, repo, title: args.title || "", body: args.body || "" }); return { ok: true, number: r.data.number }; },
31
- gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues`, { title: args.title || "", body: args.body || "" }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, number: r.data?.number }; }
68
+ github: async (owner) => { const r = await pm.github.rest.issues.create({ owner, repo, title: args.title, body: args.body || "" }); return { ok: true, number: r.data.number, url: r.data.html_url }; },
69
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues`, { title: args.title, body: args.body || "" }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, number: r.data?.number }; }
32
70
  });
33
- return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
71
+ return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), title: args.title, providers: out });
34
72
  }
35
73
  if (args.action === "list") {
36
74
  const out = await runBoth(pm, {
37
- github: async (owner) => { const r = await pm.github.rest.issues.listForRepo({ owner, repo, state: "all" }); return { ok: true, count: r.data.length }; },
38
- gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${base}/api/v1/repos/${owner}/${repo}/issues`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data||[]).length }; }
75
+ github: async (owner) => { const r = await pm.github.rest.issues.listForRepo({ owner, repo, state: "all", per_page: 30 }); return { ok: true, count: r.data.length, issues: r.data.slice(0, 10).map(i => ({ number: i.number, title: i.title, state: i.state })) }; },
76
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${base}/api/v1/repos/${owner}/${repo}/issues?state=all`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data||[]).length, issues: (r.data||[]).slice(0, 10).map(i => ({ number: i.number, title: i.title, state: i.state })) }; }
39
77
  });
40
78
  return asToolResult({ providers: out });
41
79
  }
42
80
  if (args.action === "comment") {
43
- const num = args.number;
44
- if (!num) return asToolError("VALIDATION_ERROR", "number é obrigatório");
81
+ if (!args.number) return asToolError("MISSING_PARAMETER", "number é obrigatório para comentar", { parameter: "number" });
82
+ if (!args.body) return asToolError("MISSING_PARAMETER", "body é obrigatório para comentar", { parameter: "body" });
45
83
  const out = await runBoth(pm, {
46
- github: async (owner) => { await pm.github.rest.issues.createComment({ owner, repo, issue_number: num, body: args.body || "" }); return { ok: true }; },
47
- gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues/${num}/comments`, { body: args.body || "" }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
84
+ github: async (owner) => { await pm.github.rest.issues.createComment({ owner, repo, issue_number: args.number, body: args.body }); return { ok: true }; },
85
+ gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues/${args.number}/comments`, { body: args.body }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
48
86
  });
49
- return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
87
+ return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), issueNumber: args.number, providers: out });
50
88
  }
51
- return asToolError("VALIDATION_ERROR", `Ação não suportada: ${args.action}`, { availableActions: ["create", "list", "comment"] });
89
+ return asToolError("VALIDATION_ERROR", `Ação '${args.action}' não suportada`, { availableActions: ["create", "list", "comment"] });
52
90
  } catch (e) {
53
91
  return errorToResponse(e);
54
92
  }
55
93
  }
56
94
 
57
- return { name: "git-issues", description: "Issues em paralelo (GitHub + Gitea)", inputSchema, handle };
95
+ return { name: "git-issues", description, inputSchema, handle };
58
96
  }