@andre.buzeli/git-mcp 15.12.5
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 +40 -0
- package/package.json +29 -0
- package/src/index.js +147 -0
- package/src/prompts/index.js +870 -0
- package/src/providers/providerManager.js +317 -0
- package/src/resources/index.js +276 -0
- package/src/tools/git-branches.js +126 -0
- package/src/tools/git-clone.js +137 -0
- package/src/tools/git-config.js +94 -0
- package/src/tools/git-diff.js +137 -0
- package/src/tools/git-files.js +82 -0
- package/src/tools/git-help.js +284 -0
- package/src/tools/git-history.js +90 -0
- package/src/tools/git-ignore.js +98 -0
- package/src/tools/git-issues.js +101 -0
- package/src/tools/git-merge.js +152 -0
- package/src/tools/git-pulls.js +115 -0
- package/src/tools/git-remote.js +492 -0
- package/src/tools/git-reset.js +105 -0
- package/src/tools/git-stash.js +120 -0
- package/src/tools/git-sync.js +129 -0
- package/src/tools/git-tags.js +113 -0
- package/src/tools/git-workflow.js +443 -0
- package/src/utils/env.js +104 -0
- package/src/utils/errors.js +431 -0
- package/src/utils/gitAdapter.js +996 -0
- package/src/utils/hooks.js +255 -0
- package/src/utils/metrics.js +198 -0
- package/src/utils/providerExec.js +61 -0
- package/src/utils/repoHelpers.js +216 -0
- package/src/utils/retry.js +123 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
3
|
+
import { validateProjectPath } from "../utils/repoHelpers.js";
|
|
4
|
+
|
|
5
|
+
const ajv = new Ajv({ allErrors: true });
|
|
6
|
+
|
|
7
|
+
export function createGitBranchesTool(git) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["list", "create", "delete", "rename", "checkout"],
|
|
18
|
+
description: `Ação a executar:
|
|
19
|
+
- list: Lista todas as branches locais e remotas
|
|
20
|
+
- create: Cria nova branch a partir do HEAD atual
|
|
21
|
+
- delete: Remove uma branch (não pode ser a atual)
|
|
22
|
+
- rename: Renomeia uma branch existente
|
|
23
|
+
- checkout: Muda para outra branch`
|
|
24
|
+
},
|
|
25
|
+
branch: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Nome da branch para create/delete/checkout. Ex: 'feature/nova-funcionalidade', 'bugfix/correcao'"
|
|
28
|
+
},
|
|
29
|
+
newBranch: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Novo nome da branch (apenas para action='rename')"
|
|
32
|
+
},
|
|
33
|
+
force: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Forçar delete mesmo se branch não está merged. Default: false"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ["projectPath", "action"],
|
|
39
|
+
additionalProperties: false
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const description = `Gerenciamento de branches Git.
|
|
43
|
+
|
|
44
|
+
AÇÕES DISPONÍVEIS:
|
|
45
|
+
- list: Ver branches existentes (locais e remotas)
|
|
46
|
+
- create: Criar nova branch para desenvolvimento
|
|
47
|
+
- checkout: Trocar de branch
|
|
48
|
+
- delete: Remover branch após merge
|
|
49
|
+
- rename: Renomear branch
|
|
50
|
+
|
|
51
|
+
CONVENÇÕES DE NOMES:
|
|
52
|
+
- feature/nome: Para novas funcionalidades
|
|
53
|
+
- bugfix/nome: Para correções de bugs
|
|
54
|
+
- hotfix/nome: Para correções urgentes
|
|
55
|
+
- release/versao: Para releases`;
|
|
56
|
+
|
|
57
|
+
async function handle(args) {
|
|
58
|
+
const validate = ajv.compile(inputSchema);
|
|
59
|
+
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
60
|
+
const { projectPath, action } = args;
|
|
61
|
+
try {
|
|
62
|
+
validateProjectPath(projectPath);
|
|
63
|
+
if (action === "list") {
|
|
64
|
+
const local = await git.listBranches(projectPath, false);
|
|
65
|
+
const remote = await git.listBranches(projectPath, true).catch(() => []);
|
|
66
|
+
const current = await git.getCurrentBranch(projectPath);
|
|
67
|
+
return asToolResult({ current, local, remote });
|
|
68
|
+
}
|
|
69
|
+
if (action === "create") {
|
|
70
|
+
if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para criar", { parameter: "branch" });
|
|
71
|
+
const existing = await git.listBranches(projectPath, false);
|
|
72
|
+
if (existing.includes(args.branch)) {
|
|
73
|
+
return asToolError("BRANCH_ALREADY_EXISTS", `Branch '${args.branch}' já existe`, { branch: args.branch, existingBranches: existing });
|
|
74
|
+
}
|
|
75
|
+
await git.createBranch(projectPath, args.branch);
|
|
76
|
+
return asToolResult({ success: true, branch: args.branch, message: `Branch '${args.branch}' criada. Use action='checkout' para mudar para ela.` });
|
|
77
|
+
}
|
|
78
|
+
if (action === "delete") {
|
|
79
|
+
if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para deletar", { parameter: "branch" });
|
|
80
|
+
const existing = await git.listBranches(projectPath, false);
|
|
81
|
+
if (!existing.includes(args.branch)) {
|
|
82
|
+
return asToolError("BRANCH_NOT_FOUND", `Branch '${args.branch}' não encontrada`, { branch: args.branch, availableBranches: existing });
|
|
83
|
+
}
|
|
84
|
+
const current = await git.getCurrentBranch(projectPath);
|
|
85
|
+
if (args.branch === current) {
|
|
86
|
+
return asToolError("CANNOT_DELETE_CURRENT", `Não pode deletar branch atual '${args.branch}'`, { branch: args.branch, suggestion: "Faça checkout para outra branch primeiro" });
|
|
87
|
+
}
|
|
88
|
+
await git.deleteBranch(projectPath, args.branch, !!args.force);
|
|
89
|
+
return asToolResult({ success: true, branch: args.branch, deleted: true });
|
|
90
|
+
}
|
|
91
|
+
if (action === "rename") {
|
|
92
|
+
if (!args.branch || !args.newBranch) return asToolError("MISSING_PARAMETER", "branch e newBranch são obrigatórios", { parameters: ["branch", "newBranch"] });
|
|
93
|
+
await git.renameBranch(projectPath, args.branch, args.newBranch);
|
|
94
|
+
return asToolResult({ success: true, from: args.branch, to: args.newBranch });
|
|
95
|
+
}
|
|
96
|
+
if (action === "checkout") {
|
|
97
|
+
if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para checkout", { parameter: "branch" });
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await git.checkout(projectPath, args.branch);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
const msg = e.message || "";
|
|
103
|
+
// Auto-fix: Create if not exists (common intent)
|
|
104
|
+
if (msg.includes("did not match any file") || msg.includes("pathspec") || msg.includes("not found")) {
|
|
105
|
+
// We can suggest or auto-create if configured. For now, let's suggest clearly or auto-create if it looks like a feature branch?
|
|
106
|
+
// Safer: Suggest. Or better: check if we should auto-create.
|
|
107
|
+
// Let's stick to safe behavior but enhanced error.
|
|
108
|
+
|
|
109
|
+
// Check if user meant create
|
|
110
|
+
return asToolError("BRANCH_NOT_FOUND", `Branch '${args.branch}' não existe`, {
|
|
111
|
+
suggestion: "Use action='create' para criar esta branch, ou verifique o nome."
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return asToolResult({ success: true, branch: args.branch, message: `Mudou para branch '${args.branch}'` });
|
|
118
|
+
}
|
|
119
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "delete", "rename", "checkout"] });
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return errorToResponse(e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { name: "git-branches", description, inputSchema, handle };
|
|
126
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
|
|
3
|
+
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
|
+
|
|
5
|
+
const ajv = new Ajv({ allErrors: true });
|
|
6
|
+
|
|
7
|
+
export function createGitCloneTool(git) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto onde o repositório será clonado"
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["clone"],
|
|
18
|
+
description: "Ação a executar: clone - clona um repositório remoto"
|
|
19
|
+
},
|
|
20
|
+
url: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "URL do repositório para clonar. Ex: 'https://github.com/user/repo.git'"
|
|
23
|
+
},
|
|
24
|
+
branch: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Branch específica para clonar. Default: branch padrão do repo"
|
|
27
|
+
},
|
|
28
|
+
depth: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: "Profundidade do clone (shallow clone). Ex: 1 para último commit apenas"
|
|
31
|
+
},
|
|
32
|
+
singleBranch: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Se true, clona apenas a branch especificada. Default: false"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
required: ["projectPath", "action", "url"],
|
|
38
|
+
additionalProperties: false
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const description = `Clonar repositórios remotos.
|
|
42
|
+
|
|
43
|
+
QUANDO USAR:
|
|
44
|
+
- Baixar um repositório existente do GitHub/Gitea
|
|
45
|
+
- Iniciar trabalho em um projeto existente
|
|
46
|
+
- Criar cópia local de um repositório
|
|
47
|
+
|
|
48
|
+
EXEMPLOS:
|
|
49
|
+
- Clone básico: action='clone' url='https://github.com/user/repo.git'
|
|
50
|
+
- Clone shallow: action='clone' url='...' depth=1
|
|
51
|
+
- Clone de branch: action='clone' url='...' branch='develop'
|
|
52
|
+
|
|
53
|
+
AUTENTICAÇÃO:
|
|
54
|
+
- Repos públicos: Não precisa de token
|
|
55
|
+
- Repos privados: Configure GITHUB_TOKEN ou GITEA_TOKEN
|
|
56
|
+
|
|
57
|
+
NOTAS:
|
|
58
|
+
- O diretório de destino será criado automaticamente
|
|
59
|
+
- Se já existir arquivos, o clone falhará`;
|
|
60
|
+
|
|
61
|
+
async function handle(args) {
|
|
62
|
+
const validate = ajv.compile(inputSchema);
|
|
63
|
+
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
64
|
+
const { projectPath, action, url } = args;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Validação de path
|
|
68
|
+
validateProjectPath(projectPath);
|
|
69
|
+
|
|
70
|
+
if (action === "clone") {
|
|
71
|
+
if (!url) {
|
|
72
|
+
return asToolError("MISSING_PARAMETER", "url é obrigatório para clone", {
|
|
73
|
+
parameter: "url",
|
|
74
|
+
example: "https://github.com/user/repo.git"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Idempotency check
|
|
79
|
+
if (fs.existsSync(path.join(projectPath, ".git"))) {
|
|
80
|
+
try {
|
|
81
|
+
// Check if it's the same repo
|
|
82
|
+
// git.getRemoteUrl is not standard in adapter, use exec or listRemotes logic
|
|
83
|
+
// Assuming git.listRemotes or similar exists, or we catch the error
|
|
84
|
+
// Let's try to infer from config or just warn
|
|
85
|
+
|
|
86
|
+
// We'll rely on git status to check if it's healthy
|
|
87
|
+
await git.status(projectPath);
|
|
88
|
+
|
|
89
|
+
return asToolResult({
|
|
90
|
+
success: true,
|
|
91
|
+
url,
|
|
92
|
+
path: projectPath,
|
|
93
|
+
branch: args.branch || "current",
|
|
94
|
+
message: `Repositório já existe em '${projectPath}'. Clone ignorado (idempotente).`,
|
|
95
|
+
nextStep: "Use git-workflow status para ver o estado atual"
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// If status fails, maybe it's broken
|
|
99
|
+
console.warn("Existing repo check failed:", e);
|
|
100
|
+
}
|
|
101
|
+
} else if (fs.existsSync(projectPath) && fs.readdirSync(projectPath).length > 0) {
|
|
102
|
+
return asToolError("DIR_NOT_EMPTY", `Diretório '${projectPath}' existe e não está vazio`, {
|
|
103
|
+
suggestion: "Use um diretório novo ou limpe o atual"
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = await withRetry(
|
|
108
|
+
() => git.clone(url, projectPath, {
|
|
109
|
+
branch: args.branch,
|
|
110
|
+
depth: args.depth,
|
|
111
|
+
singleBranch: args.singleBranch
|
|
112
|
+
}),
|
|
113
|
+
3,
|
|
114
|
+
"clone"
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return asToolResult({
|
|
118
|
+
success: true,
|
|
119
|
+
url,
|
|
120
|
+
path: projectPath,
|
|
121
|
+
branch: result.branch || args.branch || "default",
|
|
122
|
+
...result,
|
|
123
|
+
message: `Repositório clonado com sucesso em '${projectPath}'`,
|
|
124
|
+
nextStep: "Use git-workflow status para ver o estado do repositório"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
129
|
+
availableActions: ["clone"]
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return errorToResponse(e);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { name: "git-clone", description, inputSchema, handle };
|
|
137
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
3
|
+
import { validateProjectPath } from "../utils/repoHelpers.js";
|
|
4
|
+
import { withRetry } from "../utils/retry.js";
|
|
5
|
+
|
|
6
|
+
const ajv = new Ajv({ allErrors: true });
|
|
7
|
+
|
|
8
|
+
export function createGitConfigTool(git) {
|
|
9
|
+
const inputSchema = {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
projectPath: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
15
|
+
},
|
|
16
|
+
action: {
|
|
17
|
+
type: "string",
|
|
18
|
+
enum: ["get", "set", "unset", "list"],
|
|
19
|
+
description: `Ação a executar:
|
|
20
|
+
- get: Obter valor de uma configuração específica
|
|
21
|
+
- set: Definir valor de uma configuração
|
|
22
|
+
- unset: Remover uma configuração
|
|
23
|
+
- list: Listar todas as configurações do escopo`
|
|
24
|
+
},
|
|
25
|
+
key: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Chave da configuração. Ex: 'user.name', 'user.email', 'core.autocrlf'"
|
|
28
|
+
},
|
|
29
|
+
value: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Valor a definir (apenas para action='set')"
|
|
32
|
+
},
|
|
33
|
+
scope: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: ["local", "global", "system"],
|
|
36
|
+
description: "Escopo da configuração. local: apenas este repo, global: todos os repos do usuário, system: todo o sistema"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
required: ["projectPath", "action"],
|
|
40
|
+
additionalProperties: false
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const description = `Gerenciamento de configurações Git.
|
|
44
|
+
|
|
45
|
+
CONFIGURAÇÕES COMUNS:
|
|
46
|
+
- user.name: Nome do autor dos commits
|
|
47
|
+
- user.email: Email do autor dos commits
|
|
48
|
+
- core.autocrlf: Conversão de fim de linha (true/false/input)
|
|
49
|
+
- core.editor: Editor padrão para commits
|
|
50
|
+
|
|
51
|
+
ESCOPOS:
|
|
52
|
+
- local: Apenas neste repositório (.git/config)
|
|
53
|
+
- global: Todos os repos do usuário (~/.gitconfig)
|
|
54
|
+
- system: Todo o sistema (/etc/gitconfig)
|
|
55
|
+
|
|
56
|
+
EXEMPLOS:
|
|
57
|
+
- Definir nome: action='set' key='user.name' value='Meu Nome'
|
|
58
|
+
- Definir email: action='set' key='user.email' value='email@example.com'
|
|
59
|
+
- Ver nome: action='get' key='user.name'`;
|
|
60
|
+
|
|
61
|
+
async function handle(args) {
|
|
62
|
+
const validate = ajv.compile(inputSchema);
|
|
63
|
+
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
64
|
+
const { projectPath, action } = args;
|
|
65
|
+
const scope = args.scope || "local";
|
|
66
|
+
try {
|
|
67
|
+
validateProjectPath(projectPath);
|
|
68
|
+
if (action === "get") {
|
|
69
|
+
if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
|
|
70
|
+
const val = await withRetry(() => git.getConfig(projectPath, args.key, scope), 3, "get-config");
|
|
71
|
+
return asToolResult({ key: args.key, value: val, found: val !== undefined, scope });
|
|
72
|
+
}
|
|
73
|
+
if (action === "set") {
|
|
74
|
+
if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
|
|
75
|
+
await withRetry(() => git.setConfig(projectPath, args.key, args.value ?? "", scope), 3, "set-config");
|
|
76
|
+
return asToolResult({ success: true, key: args.key, value: args.value ?? "", scope });
|
|
77
|
+
}
|
|
78
|
+
if (action === "unset") {
|
|
79
|
+
if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
|
|
80
|
+
await withRetry(() => git.unsetConfig(projectPath, args.key, scope), 3, "unset-config");
|
|
81
|
+
return asToolResult({ success: true, key: args.key, removed: true, scope });
|
|
82
|
+
}
|
|
83
|
+
if (action === "list") {
|
|
84
|
+
const items = await withRetry(() => git.listConfig(projectPath, scope), 3, "list-config");
|
|
85
|
+
return asToolResult({ scope, configs: items, count: Object.keys(items).length });
|
|
86
|
+
}
|
|
87
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["get", "set", "unset", "list"] });
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return errorToResponse(e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { name: "git-config", description, inputSchema, handle };
|
|
94
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
3
|
+
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
|
+
|
|
5
|
+
const ajv = new Ajv({ allErrors: true });
|
|
6
|
+
|
|
7
|
+
export function createGitDiffTool(git) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["show", "compare", "stat"],
|
|
18
|
+
description: `Ação a executar:
|
|
19
|
+
- show: Mostra diff do working directory (arquivos não commitados)
|
|
20
|
+
- compare: Compara dois commits/branches/tags
|
|
21
|
+
- stat: Mostra estatísticas (arquivos alterados, linhas +/-)`
|
|
22
|
+
},
|
|
23
|
+
from: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Ref inicial para comparação (commit SHA, branch, tag). Default: HEAD"
|
|
26
|
+
},
|
|
27
|
+
fromRef: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Alias para 'from'. Ref inicial para comparação"
|
|
30
|
+
},
|
|
31
|
+
to: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Ref final para comparação. Se não fornecido, compara com working directory"
|
|
34
|
+
},
|
|
35
|
+
toRef: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Alias para 'to'. Ref final para comparação"
|
|
38
|
+
},
|
|
39
|
+
file: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Arquivo específico para ver diff. Se não fornecido, mostra todos"
|
|
42
|
+
},
|
|
43
|
+
filepath: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Alias para 'file'. Arquivo específico para ver diff"
|
|
46
|
+
},
|
|
47
|
+
context: {
|
|
48
|
+
type: "number",
|
|
49
|
+
description: "Número de linhas de contexto ao redor das mudanças. Default: 3"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
required: ["projectPath", "action"],
|
|
53
|
+
additionalProperties: false
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const description = `Ver diferenças entre commits, branches ou arquivos.
|
|
57
|
+
|
|
58
|
+
QUANDO USAR:
|
|
59
|
+
- Ver o que mudou antes de commitar (action='show')
|
|
60
|
+
- Comparar branches antes de merge (action='compare')
|
|
61
|
+
- Ver estatísticas de mudanças (action='stat')
|
|
62
|
+
|
|
63
|
+
EXEMPLOS:
|
|
64
|
+
- Ver mudanças não commitadas: action='show'
|
|
65
|
+
- Ver mudanças de um arquivo: action='show' file='src/index.js'
|
|
66
|
+
- Comparar com último commit: action='compare' from='HEAD~1'
|
|
67
|
+
- Comparar branches: action='compare' from='main' to='feature/x'
|
|
68
|
+
- Ver stats: action='stat' from='main' to='develop'`;
|
|
69
|
+
|
|
70
|
+
async function handle(args) {
|
|
71
|
+
const validate = ajv.compile(inputSchema);
|
|
72
|
+
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
73
|
+
const { projectPath, action } = args;
|
|
74
|
+
|
|
75
|
+
// Support aliases: fromRef -> from, toRef -> to, filepath -> file
|
|
76
|
+
const fromParam = args.from || args.fromRef || "HEAD";
|
|
77
|
+
const toParam = args.to || args.toRef;
|
|
78
|
+
const fileParam = args.file || args.filepath;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
validateProjectPath(projectPath);
|
|
82
|
+
if (action === "show") {
|
|
83
|
+
const diff = await withRetry(() => git.diff(projectPath, {
|
|
84
|
+
file: fileParam,
|
|
85
|
+
context: args.context || 3
|
|
86
|
+
}), 3, "diff-show");
|
|
87
|
+
|
|
88
|
+
if (!diff || diff.length === 0) {
|
|
89
|
+
return asToolResult({
|
|
90
|
+
changes: [],
|
|
91
|
+
message: "Nenhuma mudança detectada no working directory"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return asToolResult({
|
|
96
|
+
changes: diff,
|
|
97
|
+
totalFiles: diff.length,
|
|
98
|
+
message: `${diff.length} arquivo(s) com mudanças`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (action === "compare") {
|
|
103
|
+
const diff = await withRetry(() => git.diffCommits(projectPath, fromParam, toParam, {
|
|
104
|
+
file: fileParam,
|
|
105
|
+
context: args.context || 3
|
|
106
|
+
}), 3, "diff-compare");
|
|
107
|
+
|
|
108
|
+
return asToolResult({
|
|
109
|
+
from: fromParam,
|
|
110
|
+
to: toParam || "working directory",
|
|
111
|
+
changes: diff,
|
|
112
|
+
totalFiles: diff.length,
|
|
113
|
+
message: `Comparando ${fromParam} com ${toParam || 'working directory'}: ${diff.length} arquivo(s)`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (action === "stat") {
|
|
118
|
+
const stats = await withRetry(() => git.diffStats(projectPath, fromParam, toParam), 3, "diff-stat");
|
|
119
|
+
|
|
120
|
+
return asToolResult({
|
|
121
|
+
from: fromParam,
|
|
122
|
+
to: toParam || "working directory",
|
|
123
|
+
...stats,
|
|
124
|
+
message: `${stats.filesChanged} arquivo(s), +${stats.insertions} -${stats.deletions}`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
129
|
+
availableActions: ["show", "compare", "stat"]
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return errorToResponse(e);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { name: "git-diff", description, inputSchema, handle };
|
|
137
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
3
|
+
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
4
|
+
|
|
5
|
+
const ajv = new Ajv({ allErrors: true });
|
|
6
|
+
|
|
7
|
+
export function createGitFilesTool(git) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["list", "read"],
|
|
18
|
+
description: `Ação a executar:
|
|
19
|
+
- list: Lista todos os arquivos rastreados pelo Git em um commit/branch
|
|
20
|
+
- read: Lê o conteúdo de um arquivo em um commit/branch específico`
|
|
21
|
+
},
|
|
22
|
+
filepath: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Caminho do arquivo relativo à raiz do repo (action='read'). Ex: 'src/index.js', 'package.json'"
|
|
25
|
+
},
|
|
26
|
+
ref: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Referência Git para ler (commit SHA, branch, tag). Default: HEAD (commit atual)"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ["projectPath", "action"],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const description = `Leitura de arquivos do repositório Git.
|
|
36
|
+
|
|
37
|
+
QUANDO USAR:
|
|
38
|
+
- Ver arquivos de um commit antigo
|
|
39
|
+
- Comparar versões de arquivo entre commits
|
|
40
|
+
- Listar arquivos rastreados pelo Git
|
|
41
|
+
|
|
42
|
+
EXEMPLOS:
|
|
43
|
+
- Listar arquivos: action='list'
|
|
44
|
+
- Listar arquivos de tag: action='list' ref='v1.0.0'
|
|
45
|
+
- Ler arquivo atual: action='read' filepath='package.json'
|
|
46
|
+
- Ler arquivo de commit antigo: action='read' filepath='src/index.js' ref='abc1234'
|
|
47
|
+
|
|
48
|
+
NOTA: Esta tool lê arquivos do histórico Git, não do sistema de arquivos.`;
|
|
49
|
+
|
|
50
|
+
async function handle(args) {
|
|
51
|
+
const validate = ajv.compile(inputSchema);
|
|
52
|
+
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
53
|
+
const { projectPath, action } = args;
|
|
54
|
+
try {
|
|
55
|
+
validateProjectPath(projectPath);
|
|
56
|
+
if (action === "list") {
|
|
57
|
+
const files = await git.listFiles(projectPath, args.ref || "HEAD");
|
|
58
|
+
return asToolResult({ files, count: files.length, ref: args.ref || "HEAD" });
|
|
59
|
+
}
|
|
60
|
+
if (action === "read") {
|
|
61
|
+
if (!args.filepath) return asToolError("MISSING_PARAMETER", "filepath é obrigatório", { parameter: "filepath" });
|
|
62
|
+
try {
|
|
63
|
+
const text = await git.readFile(projectPath, args.filepath, args.ref || "HEAD");
|
|
64
|
+
return { content: [{ type: "text", text }], isError: false };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
const files = await git.listFiles(projectPath, args.ref || "HEAD").catch(() => []);
|
|
67
|
+
return asToolError("FILE_NOT_FOUND", `Arquivo '${args.filepath}' não encontrado`, {
|
|
68
|
+
filepath: args.filepath,
|
|
69
|
+
ref: args.ref || "HEAD",
|
|
70
|
+
similarFiles: files.filter(f => f.includes(args.filepath.split("/").pop())).slice(0, 10),
|
|
71
|
+
totalFiles: files.length
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "read"] });
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return errorToResponse(e);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { name: "git-files", description, inputSchema, handle };
|
|
82
|
+
}
|