@andrebuzeli/git-mcp 13.6.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 +1 -1
- package/src/index.js +11 -0
- package/src/providers/providerManager.js +24 -0
- package/src/tools/git-branches.js +64 -35
- package/src/tools/git-config.js +51 -12
- package/src/tools/git-files.js +39 -10
- package/src/tools/git-history.js +44 -9
- package/src/tools/git-ignore.js +48 -11
- package/src/tools/git-issues.js +56 -18
- package/src/tools/git-pulls.js +70 -21
- package/src/tools/git-remote.js +129 -60
- package/src/tools/git-reset.js +51 -11
- package/src/tools/git-stash.js +60 -20
- package/src/tools/git-sync.js +41 -9
- package/src/tools/git-tags.js +62 -30
- package/src/tools/git-workflow.js +61 -22
- package/src/utils/gitAdapter.js +11 -0
package/src/tools/git-issues.js
CHANGED
|
@@ -10,49 +10,87 @@ export function createGitIssuesTool(pm) {
|
|
|
10
10
|
const inputSchema = {
|
|
11
11
|
type: "object",
|
|
12
12
|
properties: {
|
|
13
|
-
projectPath: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
|
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
|
|
31
|
-
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues`, { title: args.title
|
|
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
|
-
|
|
44
|
-
if (!
|
|
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:
|
|
47
|
-
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/issues/${
|
|
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
|
|
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
|
|
95
|
+
return { name: "git-issues", description, inputSchema, handle };
|
|
58
96
|
}
|
package/src/tools/git-pulls.js
CHANGED
|
@@ -10,52 +10,101 @@ export function createGitPullsTool(pm) {
|
|
|
10
10
|
const inputSchema = {
|
|
11
11
|
type: "object",
|
|
12
12
|
properties: {
|
|
13
|
-
projectPath: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
projectPath: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
16
|
+
},
|
|
17
|
+
action: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: ["create", "list", "files"],
|
|
20
|
+
description: `Ação a executar:
|
|
21
|
+
- create: Cria Pull Request no GitHub E Gitea
|
|
22
|
+
- list: Lista Pull Requests existentes
|
|
23
|
+
- files: Lista arquivos modificados em um PR`
|
|
24
|
+
},
|
|
25
|
+
title: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Título do PR. Ex: 'feat: adiciona autenticação OAuth'"
|
|
28
|
+
},
|
|
29
|
+
head: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Branch de origem (com as mudanças). Ex: 'feature/oauth'"
|
|
32
|
+
},
|
|
33
|
+
base: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Branch de destino (onde será mergeado). Ex: 'main', 'develop'"
|
|
36
|
+
},
|
|
37
|
+
number: {
|
|
38
|
+
type: "number",
|
|
39
|
+
description: "Número do PR para action='files'"
|
|
40
|
+
},
|
|
41
|
+
body: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Descrição do PR com detalhes das mudanças"
|
|
44
|
+
}
|
|
19
45
|
},
|
|
20
46
|
required: ["projectPath", "action"],
|
|
21
|
-
additionalProperties:
|
|
47
|
+
additionalProperties: false
|
|
22
48
|
};
|
|
23
49
|
|
|
50
|
+
const description = `Gerenciamento de Pull Requests no GitHub e Gitea.
|
|
51
|
+
|
|
52
|
+
QUANDO USAR:
|
|
53
|
+
- Propor mudanças de uma branch para outra
|
|
54
|
+
- Code review antes de merge
|
|
55
|
+
- Documentar mudanças significativas
|
|
56
|
+
|
|
57
|
+
FLUXO TÍPICO:
|
|
58
|
+
1. Cria branch: git-branches create branch='feature/nova'
|
|
59
|
+
2. Faz mudanças e commits
|
|
60
|
+
3. Push: git-workflow push
|
|
61
|
+
4. Cria PR: git-pulls create head='feature/nova' base='main' title='Nova feature'
|
|
62
|
+
|
|
63
|
+
AÇÕES:
|
|
64
|
+
- create: Criar novo PR
|
|
65
|
+
- list: Ver PRs existentes
|
|
66
|
+
- files: Ver arquivos modificados em um PR
|
|
67
|
+
|
|
68
|
+
NOTA: O PR é criado em AMBOS os providers simultaneamente.`;
|
|
69
|
+
|
|
24
70
|
async function handle(args) {
|
|
25
71
|
const validate = ajv.compile(inputSchema);
|
|
26
72
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
27
73
|
const repo = getRepoNameFromPath(args.projectPath);
|
|
28
74
|
try {
|
|
29
75
|
if (args.action === "create") {
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
if (!args.head) return asToolError("MISSING_PARAMETER", "head (branch de origem) é obrigatório", { parameter: "head", example: "feature/nova-funcionalidade" });
|
|
77
|
+
if (!args.base) return asToolError("MISSING_PARAMETER", "base (branch de destino) é obrigatório", { parameter: "base", example: "main" });
|
|
78
|
+
const head = args.head;
|
|
79
|
+
const base = args.base;
|
|
80
|
+
const title = args.title || `${head} -> ${base}`;
|
|
81
|
+
const body = args.body || "";
|
|
32
82
|
const out = await runBoth(pm, {
|
|
33
|
-
github: async (owner) => { const r = await pm.github.rest.pulls.create({ owner, repo, title
|
|
34
|
-
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls`, { title
|
|
83
|
+
github: async (owner) => { const r = await pm.github.rest.pulls.create({ owner, repo, title, head, base, body }); return { ok: true, number: r.data.number, url: r.data.html_url }; },
|
|
84
|
+
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls`, { title, head, base, body }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, number: r.data?.number }; }
|
|
35
85
|
});
|
|
36
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
86
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), title, head, base, providers: out });
|
|
37
87
|
}
|
|
38
88
|
if (args.action === "list") {
|
|
39
89
|
const out = await runBoth(pm, {
|
|
40
|
-
github: async (owner) => { const r = await pm.github.rest.pulls.list({ owner, repo, state: "all" }); return { ok: true, count: r.data.length }; },
|
|
41
|
-
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data||[]).length }; }
|
|
90
|
+
github: async (owner) => { const r = await pm.github.rest.pulls.list({ owner, repo, state: "all", per_page: 30 }); return { ok: true, count: r.data.length, pulls: r.data.slice(0, 10).map(p => ({ number: p.number, title: p.title, state: p.state, head: p.head.ref, base: p.base.ref })) }; },
|
|
91
|
+
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls?state=all`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data||[]).length, pulls: (r.data||[]).slice(0, 10).map(p => ({ number: p.number, title: p.title, state: p.state, head: p.head?.ref, base: p.base?.ref })) }; }
|
|
42
92
|
});
|
|
43
93
|
return asToolResult({ providers: out });
|
|
44
94
|
}
|
|
45
95
|
if (args.action === "files") {
|
|
46
|
-
|
|
47
|
-
if (!num) return asToolError("VALIDATION_ERROR", "number é obrigatório");
|
|
96
|
+
if (!args.number) return asToolError("MISSING_PARAMETER", "number é obrigatório para ver arquivos do PR", { parameter: "number" });
|
|
48
97
|
const out = await runBoth(pm, {
|
|
49
|
-
github: async (owner) => { const r = await pm.github.rest.pulls.listFiles({ owner, repo, pull_number:
|
|
50
|
-
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls/${
|
|
98
|
+
github: async (owner) => { const r = await pm.github.rest.pulls.listFiles({ owner, repo, pull_number: args.number }); return { ok: true, count: r.data.length, files: r.data.map(f => ({ filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions })) }; },
|
|
99
|
+
gitea: async (owner) => { const baseUrl = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.get(`${baseUrl}/api/v1/repos/${owner}/${repo}/pulls/${args.number}/files`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, count: (r.data||[]).length, files: (r.data||[]).map(f => ({ filename: f.filename, status: f.status })) }; }
|
|
51
100
|
});
|
|
52
|
-
return asToolResult({ providers: out });
|
|
101
|
+
return asToolResult({ prNumber: args.number, providers: out });
|
|
53
102
|
}
|
|
54
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
103
|
+
return asToolError("VALIDATION_ERROR", `Ação '${args.action}' não suportada`, { availableActions: ["create", "list", "files"] });
|
|
55
104
|
} catch (e) {
|
|
56
105
|
return errorToResponse(e);
|
|
57
106
|
}
|
|
58
107
|
}
|
|
59
108
|
|
|
60
|
-
return { name: "git-pulls", description
|
|
109
|
+
return { name: "git-pulls", description, inputSchema, handle };
|
|
61
110
|
}
|
package/src/tools/git-remote.js
CHANGED
|
@@ -10,37 +10,101 @@ export function createGitRemoteTool(pm, git) {
|
|
|
10
10
|
const inputSchema = {
|
|
11
11
|
type: "object",
|
|
12
12
|
properties: {
|
|
13
|
-
projectPath: {
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
13
|
+
projectPath: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
16
|
+
},
|
|
17
|
+
action: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: [
|
|
20
|
+
"list", "ensure", "repo-delete",
|
|
21
|
+
"release-create", "topics-set", "milestone-create", "label-create",
|
|
22
|
+
"fork-create", "fork-list", "star-set", "star-unset",
|
|
23
|
+
"subscription-set", "subscription-unset", "contents-create"
|
|
24
|
+
],
|
|
25
|
+
description: `Ação a executar:
|
|
26
|
+
|
|
27
|
+
CONFIGURAÇÃO:
|
|
28
|
+
- list: Lista remotes configurados (github, gitea, origin)
|
|
29
|
+
- ensure: Cria repos no GitHub/Gitea e configura remotes (USE ESTE se push falhar)
|
|
30
|
+
|
|
31
|
+
REPOSITÓRIO:
|
|
32
|
+
- repo-delete: Deleta repositório no GitHub E Gitea (⚠️ PERIGOSO)
|
|
33
|
+
- release-create: Cria release/versão no GitHub e Gitea
|
|
34
|
+
- topics-set: Define tópicos/tags do repositório
|
|
35
|
+
- milestone-create: Cria milestone para organização
|
|
36
|
+
- label-create: Cria label para issues
|
|
37
|
+
|
|
38
|
+
SOCIAL:
|
|
39
|
+
- star-set: Adiciona estrela ao repo
|
|
40
|
+
- star-unset: Remove estrela
|
|
41
|
+
- fork-create: Cria fork do repositório
|
|
42
|
+
- fork-list: Lista forks existentes
|
|
43
|
+
|
|
44
|
+
NOTIFICAÇÕES:
|
|
45
|
+
- subscription-set: Ativa notificações do repo
|
|
46
|
+
- subscription-unset: Desativa notificações
|
|
47
|
+
|
|
48
|
+
ARQUIVOS VIA API:
|
|
49
|
+
- contents-create: Cria arquivo diretamente via API (sem git local)`
|
|
50
|
+
},
|
|
51
|
+
tag: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Tag para release-create. Ex: 'v1.0.0'"
|
|
54
|
+
},
|
|
55
|
+
name: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Nome da release, label, etc."
|
|
58
|
+
},
|
|
59
|
+
body: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Descrição/corpo da release"
|
|
62
|
+
},
|
|
63
|
+
topics: {
|
|
64
|
+
type: "array",
|
|
65
|
+
items: { type: "string" },
|
|
66
|
+
description: "Lista de tópicos para topics-set. Ex: ['javascript', 'nodejs', 'mcp']"
|
|
67
|
+
},
|
|
68
|
+
path: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Caminho do arquivo para contents-create"
|
|
71
|
+
},
|
|
72
|
+
content: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Conteúdo do arquivo para contents-create"
|
|
75
|
+
},
|
|
76
|
+
branch: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Branch alvo para contents-create. Default: main"
|
|
79
|
+
},
|
|
80
|
+
color: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Cor do label em hex (sem #). Ex: 'ff0000' para vermelho"
|
|
83
|
+
},
|
|
84
|
+
title: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Título do milestone"
|
|
87
|
+
}
|
|
39
88
|
},
|
|
40
89
|
required: ["projectPath", "action"],
|
|
41
|
-
additionalProperties:
|
|
90
|
+
additionalProperties: false
|
|
42
91
|
};
|
|
43
92
|
|
|
93
|
+
const description = `Operações em repositórios remotos GitHub e Gitea.
|
|
94
|
+
|
|
95
|
+
AÇÕES MAIS USADAS:
|
|
96
|
+
- ensure: Configurar remotes e criar repos (ESSENCIAL antes de push)
|
|
97
|
+
- list: Ver remotes configurados
|
|
98
|
+
- release-create: Publicar nova versão
|
|
99
|
+
|
|
100
|
+
EXECUÇÃO PARALELA:
|
|
101
|
+
Todas as operações são executadas em AMBOS os providers (GitHub + Gitea) simultaneamente.
|
|
102
|
+
|
|
103
|
+
QUANDO USAR:
|
|
104
|
+
- Se git-workflow push falhar: use action='ensure'
|
|
105
|
+
- Para publicar release: use action='release-create'
|
|
106
|
+
- Para configurar tópicos: use action='topics-set'`;
|
|
107
|
+
|
|
44
108
|
async function handle(args) {
|
|
45
109
|
const validate = ajv.compile(inputSchema);
|
|
46
110
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
@@ -48,7 +112,13 @@ export function createGitRemoteTool(pm, git) {
|
|
|
48
112
|
try {
|
|
49
113
|
if (action === "list") {
|
|
50
114
|
const remotes = await git.listRemotes(projectPath);
|
|
51
|
-
return asToolResult({
|
|
115
|
+
return asToolResult({
|
|
116
|
+
remotes,
|
|
117
|
+
configured: remotes.length > 0,
|
|
118
|
+
hasGithub: remotes.some(r => r.remote === "github"),
|
|
119
|
+
hasGitea: remotes.some(r => r.remote === "gitea"),
|
|
120
|
+
message: remotes.length === 0 ? "Nenhum remote configurado. Use action='ensure' para configurar." : undefined
|
|
121
|
+
});
|
|
52
122
|
}
|
|
53
123
|
if (action === "ensure") {
|
|
54
124
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -60,11 +130,13 @@ export function createGitRemoteTool(pm, git) {
|
|
|
60
130
|
const giteaUrl = geOwner && base ? `${base}/${geOwner}/${repo}.git` : "";
|
|
61
131
|
await git.ensureRemotes(projectPath, { githubUrl, giteaUrl });
|
|
62
132
|
const remotes = await git.listRemotes(projectPath);
|
|
63
|
-
return asToolResult({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
133
|
+
return asToolResult({
|
|
134
|
+
success: true,
|
|
135
|
+
ensured,
|
|
136
|
+
remotes,
|
|
137
|
+
urls: { github: githubUrl, gitea: giteaUrl },
|
|
138
|
+
message: "Remotes configurados. Agora pode usar git-workflow push."
|
|
139
|
+
});
|
|
68
140
|
}
|
|
69
141
|
if (action === "repo-delete") {
|
|
70
142
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -72,36 +144,38 @@ export function createGitRemoteTool(pm, git) {
|
|
|
72
144
|
github: async (owner) => { await pm.github.rest.repos.delete({ owner, repo }); return { ok: true }; },
|
|
73
145
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.delete(`${base}/api/v1/repos/${owner}/${repo}`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
|
|
74
146
|
});
|
|
75
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
147
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), deleted: repo, providers: out, warning: "Repositório deletado permanentemente!" });
|
|
76
148
|
}
|
|
77
149
|
if (action === "release-create") {
|
|
78
150
|
const repo = getRepoNameFromPath(projectPath);
|
|
79
|
-
|
|
151
|
+
if (!args.tag) return asToolError("MISSING_PARAMETER", "tag é obrigatório para criar release", { parameter: "tag", example: "v1.0.0" });
|
|
152
|
+
const tag = args.tag;
|
|
80
153
|
const name = args.name || tag;
|
|
81
154
|
const body = args.body || "";
|
|
82
155
|
const out = await runBoth(pm, {
|
|
83
|
-
github: async (owner) => { const r = await pm.github.rest.repos.createRelease({ owner, repo, tag_name: tag, name, body, draft: false, prerelease: false }); return { ok: true, id: r.data.id }; },
|
|
156
|
+
github: async (owner) => { const r = await pm.github.rest.repos.createRelease({ owner, repo, tag_name: tag, name, body, draft: false, prerelease: false }); return { ok: true, id: r.data.id, url: r.data.html_url }; },
|
|
84
157
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/releases`, { tag_name: tag, name, body }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, id: r.data?.id }; }
|
|
85
158
|
});
|
|
86
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
159
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), tag, name, providers: out });
|
|
87
160
|
}
|
|
88
161
|
if (action === "topics-set") {
|
|
89
162
|
const repo = getRepoNameFromPath(projectPath);
|
|
90
163
|
const topics = Array.isArray(args.topics) ? args.topics : [];
|
|
164
|
+
if (topics.length === 0) return asToolError("MISSING_PARAMETER", "topics é obrigatório", { parameter: "topics", example: ["javascript", "nodejs"] });
|
|
91
165
|
const out = await runBoth(pm, {
|
|
92
166
|
github: async (owner) => { await pm.github.request("PUT /repos/{owner}/{repo}/topics", { owner, repo, names: topics, headers: { accept: "application/vnd.github.mercy-preview+json" } }); return { ok: true }; },
|
|
93
167
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.put(`${base}/api/v1/repos/${owner}/${repo}/topics`, { topics }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
|
|
94
168
|
});
|
|
95
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
169
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), topics, providers: out });
|
|
96
170
|
}
|
|
97
171
|
if (action === "milestone-create") {
|
|
98
172
|
const repo = getRepoNameFromPath(projectPath);
|
|
99
|
-
const title = args.title || "v1.0";
|
|
173
|
+
const title = args.title || args.name || "v1.0";
|
|
100
174
|
const out = await runBoth(pm, {
|
|
101
175
|
github: async (owner) => { const r = await pm.github.request("POST /repos/{owner}/{repo}/milestones", { owner, repo, title }); return { ok: true, id: r.data?.id }; },
|
|
102
176
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); const r = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/milestones`, { title }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true, id: r.data?.id }; }
|
|
103
177
|
});
|
|
104
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
178
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), title, providers: out });
|
|
105
179
|
}
|
|
106
180
|
if (action === "label-create") {
|
|
107
181
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -111,7 +185,7 @@ export function createGitRemoteTool(pm, git) {
|
|
|
111
185
|
github: async (owner) => { await pm.github.request("POST /repos/{owner}/{repo}/labels", { owner, repo, name, color }); return { ok: true }; },
|
|
112
186
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/labels`, { name, color }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
|
|
113
187
|
});
|
|
114
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
188
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), label: { name, color }, providers: out });
|
|
115
189
|
}
|
|
116
190
|
if (action === "fork-create") {
|
|
117
191
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -129,22 +203,20 @@ export function createGitRemoteTool(pm, git) {
|
|
|
129
203
|
});
|
|
130
204
|
return asToolResult({ providers: out });
|
|
131
205
|
}
|
|
132
|
-
if (action === "star-set" || action === "star-unset"
|
|
206
|
+
if (action === "star-set" || action === "star-unset") {
|
|
133
207
|
const repo = getRepoNameFromPath(projectPath);
|
|
134
208
|
const out = await runBoth(pm, {
|
|
135
209
|
github: async (owner) => {
|
|
136
210
|
if (action === "star-set") { await pm.github.request("PUT /user/starred/{owner}/{repo}", { owner, repo }); return { ok: true }; }
|
|
137
|
-
|
|
138
|
-
const r = await pm.github.request("GET /user/starred/{owner}/{repo}", { owner, repo }); return { ok: r.status === 204 || r.status === 200 };
|
|
211
|
+
await pm.github.request("DELETE /user/starred/{owner}/{repo}", { owner, repo }); return { ok: true };
|
|
139
212
|
},
|
|
140
213
|
gitea: async (owner) => {
|
|
141
214
|
const base = pm.giteaUrl.replace(/\/$/, "");
|
|
142
215
|
if (action === "star-set") { await axios.put(`${base}/api/v1/user/starred/${owner}/${repo}`, {}, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
|
|
143
|
-
|
|
144
|
-
const r = await axios.get(`${base}/api/v1/user/starred/${owner}/${repo}`, { headers: { Authorization: `token ${pm.giteaToken}` }, validateStatus: () => true }); return { ok: r.status === 204 || r.status === 200 };
|
|
216
|
+
await axios.delete(`${base}/api/v1/user/starred/${owner}/${repo}`, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true };
|
|
145
217
|
}
|
|
146
218
|
});
|
|
147
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
219
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), action, providers: out });
|
|
148
220
|
}
|
|
149
221
|
if (action === "subscription-set" || action === "subscription-unset") {
|
|
150
222
|
const repo = getRepoNameFromPath(projectPath);
|
|
@@ -152,33 +224,30 @@ export function createGitRemoteTool(pm, git) {
|
|
|
152
224
|
github: async (owner) => { if (action === "subscription-set") { await pm.github.request("PUT /repos/{owner}/{repo}/subscription", { owner, repo, subscribed: true }); } else { await pm.github.request("DELETE /repos/{owner}/{repo}/subscription", { owner, repo }); } return { ok: true }; },
|
|
153
225
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); if (action === "subscription-set") { await axios.put(`${base}/api/v1/repos/${owner}/${repo}/subscription`, { subscribed: true }, { headers: { Authorization: `token ${pm.giteaToken}` } }); } else { await axios.delete(`${base}/api/v1/repos/${owner}/${repo}/subscription`, { headers: { Authorization: `token ${pm.giteaToken}` } }); } return { ok: true }; }
|
|
154
226
|
});
|
|
155
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), providers: out });
|
|
227
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), action, providers: out });
|
|
156
228
|
}
|
|
157
229
|
if (action === "contents-create") {
|
|
158
230
|
const repo = getRepoNameFromPath(projectPath);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
231
|
+
if (!args.path) return asToolError("MISSING_PARAMETER", "path é obrigatório", { parameter: "path" });
|
|
232
|
+
if (!args.content) return asToolError("MISSING_PARAMETER", "content é obrigatório", { parameter: "content" });
|
|
233
|
+
const filePath = args.path;
|
|
234
|
+
const message = args.message || `Add ${filePath}`;
|
|
235
|
+
const data = args.content;
|
|
162
236
|
const branch = args.branch || "main";
|
|
163
237
|
const b64 = Buffer.from(data, "utf8").toString("base64");
|
|
164
238
|
const out = await runBoth(pm, {
|
|
165
239
|
github: async (owner) => { await pm.github.request("PUT /repos/{owner}/{repo}/contents/{path}", { owner, repo, path: filePath, message, content: b64, branch }); return { ok: true }; },
|
|
166
240
|
gitea: async (owner) => { const base = pm.giteaUrl.replace(/\/$/, ""); await axios.post(`${base}/api/v1/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`, { content: b64, message, branch }, { headers: { Authorization: `token ${pm.giteaToken}` } }); return { ok: true }; }
|
|
167
241
|
});
|
|
168
|
-
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), path: filePath, providers: out });
|
|
242
|
+
return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), path: filePath, branch, providers: out });
|
|
169
243
|
}
|
|
170
|
-
return asToolError("VALIDATION_ERROR", `Ação
|
|
171
|
-
availableActions: ["list", "ensure", "
|
|
244
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
245
|
+
availableActions: ["list", "ensure", "repo-delete", "release-create", "topics-set", "milestone-create", "label-create", "fork-create", "fork-list", "star-set", "star-unset", "subscription-set", "subscription-unset", "contents-create"]
|
|
172
246
|
});
|
|
173
247
|
} catch (e) {
|
|
174
248
|
return errorToResponse(e);
|
|
175
249
|
}
|
|
176
250
|
}
|
|
177
251
|
|
|
178
|
-
return {
|
|
179
|
-
name: "git-remote",
|
|
180
|
-
description: "Gerencia remotes e garante GitHub + Gitea",
|
|
181
|
-
inputSchema,
|
|
182
|
-
handle
|
|
183
|
-
};
|
|
252
|
+
return { name: "git-remote", description, inputSchema, handle };
|
|
184
253
|
}
|
package/src/tools/git-reset.js
CHANGED
|
@@ -7,20 +7,52 @@ export function createGitResetTool(git) {
|
|
|
7
7
|
const inputSchema = {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
10
|
-
projectPath: {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
projectPath: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Caminho absoluto do diretório do projeto"
|
|
13
|
+
},
|
|
14
|
+
action: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["soft", "mixed", "hard"],
|
|
17
|
+
description: `Tipo de reset a executar:
|
|
18
|
+
- soft: Desfaz commits mas MANTÉM mudanças staged (pronto para re-commit)
|
|
19
|
+
- mixed: Desfaz commits e unstage, mas MANTÉM mudanças no working directory
|
|
20
|
+
- hard: Desfaz commits e DESCARTA todas as mudanças (PERIGOSO - dados perdidos)`
|
|
21
|
+
},
|
|
22
|
+
ref: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Referência para reset. Ex: 'HEAD~1' (volta 1 commit), 'HEAD~2' (volta 2), ou SHA específico"
|
|
25
|
+
}
|
|
13
26
|
},
|
|
14
27
|
required: ["projectPath", "action", "ref"],
|
|
15
|
-
additionalProperties:
|
|
28
|
+
additionalProperties: false
|
|
16
29
|
};
|
|
17
30
|
|
|
31
|
+
const description = `Reset Git - desfaz commits e/ou mudanças.
|
|
32
|
+
|
|
33
|
+
⚠️ CUIDADO: Reset pode causar perda de dados!
|
|
34
|
+
|
|
35
|
+
TIPOS DE RESET:
|
|
36
|
+
- soft: Seguro - mantém tudo, apenas move HEAD
|
|
37
|
+
- mixed: Moderado - remove do staging mas mantém arquivos
|
|
38
|
+
- hard: PERIGOSO - descarta todas as mudanças
|
|
39
|
+
|
|
40
|
+
EXEMPLOS COMUNS:
|
|
41
|
+
- Desfazer último commit (manter mudanças): action='soft' ref='HEAD~1'
|
|
42
|
+
- Desfazer último commit (unstage): action='mixed' ref='HEAD~1'
|
|
43
|
+
- Descartar tudo e voltar ao commit anterior: action='hard' ref='HEAD~1'
|
|
44
|
+
|
|
45
|
+
REFERÊNCIAS:
|
|
46
|
+
- HEAD~1: Um commit atrás
|
|
47
|
+
- HEAD~2: Dois commits atrás
|
|
48
|
+
- abc1234: SHA específico do commit`;
|
|
49
|
+
|
|
18
50
|
async function handle(args) {
|
|
19
51
|
const validate = ajv.compile(inputSchema);
|
|
20
52
|
if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
21
53
|
const { projectPath, action, ref } = args;
|
|
22
54
|
try {
|
|
23
|
-
//
|
|
55
|
+
// Verificar se há commits suficientes para HEAD~N
|
|
24
56
|
if (ref.match(/HEAD~(\d+)/)) {
|
|
25
57
|
const match = ref.match(/HEAD~(\d+)/);
|
|
26
58
|
const steps = parseInt(match[1], 10);
|
|
@@ -35,16 +67,24 @@ export function createGitResetTool(git) {
|
|
|
35
67
|
}
|
|
36
68
|
}
|
|
37
69
|
|
|
38
|
-
if (action === "soft")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
if (action === "soft") {
|
|
71
|
+
await git.resetSoft(projectPath, ref);
|
|
72
|
+
return asToolResult({ success: true, action: "soft", ref, message: "Commits desfeitos, mudanças mantidas staged" });
|
|
73
|
+
}
|
|
74
|
+
if (action === "mixed") {
|
|
75
|
+
await git.resetMixed(projectPath, ref);
|
|
76
|
+
return asToolResult({ success: true, action: "mixed", ref, message: "Commits desfeitos, mudanças mantidas no working directory" });
|
|
77
|
+
}
|
|
78
|
+
if (action === "hard") {
|
|
79
|
+
await git.resetHard(projectPath, ref);
|
|
80
|
+
return asToolResult({ success: true, action: "hard", ref, message: "⚠️ Reset hard executado - mudanças descartadas" });
|
|
81
|
+
}
|
|
42
82
|
|
|
43
|
-
return
|
|
83
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["soft", "mixed", "hard"] });
|
|
44
84
|
} catch (e) {
|
|
45
85
|
return errorToResponse(e);
|
|
46
86
|
}
|
|
47
87
|
}
|
|
48
88
|
|
|
49
|
-
return { name: "git-reset", description
|
|
89
|
+
return { name: "git-reset", description, inputSchema, handle };
|
|
50
90
|
}
|