@dami_deleon/rikudo 1.1.0 → 2.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/README.md +89 -0
- package/dist/ai/prompts.js +1 -2
- package/dist/index.js +9 -0
- package/dist/mcp/server.js +226 -0
- package/dist/services/gitea.js +59 -0
- package/dist/utils/config.js +4 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Inspirado en el Sabio de los Seis Caminos, Rikudo utiliza seis módulos especial
|
|
|
14
14
|
4. **Camino Preta (Summarizer):** Absorción y síntesis de archivos de gran tamaño.
|
|
15
15
|
5. **Camino Naraka (Cache):** Memoria persistente basada en hashes de Git para optimizar tokens.
|
|
16
16
|
6. **Camino Asura (Rate Limiter):** Gestión de poder para respetar los límites de las APIs.
|
|
17
|
+
7. **Camino MCP (Model Context Protocol):** Servidor MCP para integrar Rikudo con agentes de IA externos.
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
## ✨ Características Principales
|
|
@@ -29,6 +30,7 @@ Inspirado en el Sabio de los Seis Caminos, Rikudo utiliza seis módulos especial
|
|
|
29
30
|
* **🚀 PR Generator:** Selecciona múltiples commits y genera una descripción de Pull Request completa siguiendo tu plantilla.
|
|
30
31
|
* **⚙️ Configuración Flexible:** Define tus preferencias globales o por proyecto.
|
|
31
32
|
* **🔍 Diagnóstico Integrado:** Herramienta `test` para verificar tus API Keys y conexión con servicios externos (Redmine, IA, etc.).
|
|
33
|
+
* **🔌 Servidor MCP:** Actúa como skill library para agentes de IA externos mediante el protocolo MCP (Model Context Protocol).
|
|
32
34
|
|
|
33
35
|
## 📦 Instalación
|
|
34
36
|
|
|
@@ -95,6 +97,87 @@ rikudo config
|
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
|
|
100
|
+
## 🔌 Servidor MCP (Model Context Protocol)
|
|
101
|
+
|
|
102
|
+
Rikudo funciona como un servidor MCP, exponiendo herramientas y prompts para que cualquier agente de IA externo compatible con MCP pueda interactuar con tu flujo de trabajo de desarrollo.
|
|
103
|
+
|
|
104
|
+
### Iniciar el Servidor
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
rikudo mcp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
El servidor se comunica mediante **stdio** (entrada/salida estándar), lo que permite integrarlo con cualquier cliente MCP.
|
|
111
|
+
|
|
112
|
+
### Herramientas Disponibles
|
|
113
|
+
|
|
114
|
+
| Herramienta | Descripción |
|
|
115
|
+
|------------|-------------|
|
|
116
|
+
| `validate_configuration` | Verifica que las variables de entorno necesarias (Redmine, Gitea) estén configuradas correctamente. |
|
|
117
|
+
| `get_ticket_context` | Obtiene el contexto completo de un ticket de Redmine (título, descripción, estado, prioridad). |
|
|
118
|
+
| `get_git_diff` | Retorna el diff de los archivos que están en staging (`git add`). |
|
|
119
|
+
| `shift_left_start` | Flujo shift-left completo: checkout a rama objetivo, crea nueva rama, commit vacío y abre Draft PR en Gitea. Soporta parámetro `isWip` para usar prefijo "WIP:" en el título. |
|
|
120
|
+
| `update_pm_redmine` | Agrega un comentario a un ticket de Redmine para notificar al Project Manager. |
|
|
121
|
+
|
|
122
|
+
### Prompts Disponibles
|
|
123
|
+
|
|
124
|
+
| Prompt | Descripción |
|
|
125
|
+
|--------|-------------|
|
|
126
|
+
| `start_feature` | Inicia el flujo de trabajo para una nueva feature. Obtiene contexto del ticket, genera nombre de rama y prepara el entorno en Gitea con PR en modo WIP. |
|
|
127
|
+
| `review_and_comment` | Analiza los cambios staged, genera un comentario técnico y lo publica en Redmine. |
|
|
128
|
+
|
|
129
|
+
### Integración con Agentes IA
|
|
130
|
+
|
|
131
|
+
Rikudo es compatible con cualquier herramienta de IA que soporte el protocolo MCP, como:
|
|
132
|
+
- **Claude Code / Claude Desktop**
|
|
133
|
+
- **Gemini CLI**
|
|
134
|
+
- **Cursor**
|
|
135
|
+
- **Windsurf**
|
|
136
|
+
- **Continue**
|
|
137
|
+
- Y cualquier otro cliente MCP
|
|
138
|
+
|
|
139
|
+
#### Configuración Genérica
|
|
140
|
+
|
|
141
|
+
Agrega esto en la configuración de tu cliente MCP:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"mcpServers": {
|
|
146
|
+
"rikudo": {
|
|
147
|
+
"command": "rikudo",
|
|
148
|
+
"args": ["mcp"]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Variables de Entorno para MCP
|
|
155
|
+
|
|
156
|
+
El servidor MCP requiere las siguientes variables de entorno:
|
|
157
|
+
|
|
158
|
+
```env
|
|
159
|
+
# Redmine
|
|
160
|
+
RIKUDO_REDMINE_URL=https://redmine.tuempresa.com
|
|
161
|
+
RIKUDO_REDMINE_API_KEY=tu_redmine_key
|
|
162
|
+
|
|
163
|
+
# Gitea
|
|
164
|
+
RIKUDO_GITEA_URL=https://gitea.tuempresa.com
|
|
165
|
+
RIKUDO_GITEA_TOKEN=tu_gitea_token
|
|
166
|
+
RIKUDO_GITEA_OWNER=tu_usuario_o_organizacion
|
|
167
|
+
RIKUDO_GITEA_REPO=nombre_del_repositorio
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Formato de Ramas
|
|
171
|
+
|
|
172
|
+
Al usar `start_feature` o `shift_left_start`, el nombre de rama sigue el formato:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
[numero-tarea]-[titulo-tarea-ingles-corto]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Ejemplo: `1234-add-user-authentication`
|
|
179
|
+
|
|
180
|
+
|
|
98
181
|
|
|
99
182
|
## ⚙️ Configuración
|
|
100
183
|
|
|
@@ -119,6 +202,12 @@ RIKUDO_OLLAMA_MODEL=llama3
|
|
|
119
202
|
RIKUDO_REDMINE_URL=https://redmine.tuempresa.com
|
|
120
203
|
RIKUDO_REDMINE_API_KEY=tu_redmine_key
|
|
121
204
|
|
|
205
|
+
# --- Gitea (para servidor MCP) ---
|
|
206
|
+
RIKUDO_GITEA_URL=https://gitea.tuempresa.com
|
|
207
|
+
RIKUDO_GITEA_TOKEN=tu_gitea_token
|
|
208
|
+
RIKUDO_GITEA_OWNER=tu_usuario_o_organizacion
|
|
209
|
+
RIKUDO_GITEA_REPO=nombre_del_repositorio
|
|
210
|
+
|
|
122
211
|
```
|
|
123
212
|
|
|
124
213
|
### Plantillas Personalizadas (Templates)
|
package/dist/ai/prompts.js
CHANGED
|
@@ -36,7 +36,6 @@ Registro del Último Commit (Log Técnico): ${commitMessage}
|
|
|
36
36
|
|
|
37
37
|
SALIDA:
|
|
38
38
|
Solo devuelve el comentario, sin títulos o subtítulos o lineas de saludos.
|
|
39
|
-
|
|
40
39
|
`;
|
|
41
40
|
}
|
|
42
41
|
export function generatePullRequestMessagePrompt(commits, ticketInfo, template) {
|
|
@@ -65,6 +64,6 @@ INSTRUCCIONES:
|
|
|
65
64
|
|
|
66
65
|
SALIDA:
|
|
67
66
|
Solo el contenido de la plantilla rellena.
|
|
68
|
-
|
|
67
|
+
`;
|
|
69
68
|
return prompt;
|
|
70
69
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { generateCommitMessagePrompt, generateProjectManagerCommentPrompt, gener
|
|
|
16
16
|
import { getConventions, getPRTemplate } from "./utils/config.js";
|
|
17
17
|
import { showIssueMenu } from "./menus/issueMenu.js";
|
|
18
18
|
import { runDiagnostics } from "./test/connection.js";
|
|
19
|
+
import { runMCPServer } from "./mcp/server.js";
|
|
19
20
|
import { COMMIT_CONVENTIONS_PATH, ENV_PATH, PULL_REQUEST_TEMPLATE_PATH } from "./const/paths.js";
|
|
20
21
|
// --- Setup ---
|
|
21
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -62,6 +63,14 @@ program
|
|
|
62
63
|
.command("config")
|
|
63
64
|
.description("Abrir menú de configuración")
|
|
64
65
|
.action(() => showConfigMenu());
|
|
66
|
+
// MCP Server
|
|
67
|
+
program
|
|
68
|
+
.command("mcp")
|
|
69
|
+
.description("Iniciar servidor MCP (Model Context Protocol) para agentes de IA")
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
loadEnv(options.env);
|
|
72
|
+
await runMCPServer();
|
|
73
|
+
});
|
|
65
74
|
// --- Modo Interactivo (Default) ---
|
|
66
75
|
program
|
|
67
76
|
.description("Modo interactivo del Sabio Rikudo")
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { simpleGit } from "simple-git";
|
|
5
|
+
import { getIssueDetails, updateIssueComment } from "../services/redmine.js";
|
|
6
|
+
import { getStagedDiff } from "../services/git.js";
|
|
7
|
+
import { createPullRequest } from "../services/gitea.js";
|
|
8
|
+
import { loadEnv } from "../utils/env.js";
|
|
9
|
+
import { getConfig } from "../utils/config.js";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const packagePath = path.resolve(__dirname, "../../package.json");
|
|
16
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
17
|
+
const git = simpleGit();
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "rikudo-mcp-server",
|
|
20
|
+
version: packageJson.version || "1.0.0",
|
|
21
|
+
});
|
|
22
|
+
server.registerTool("get_ticket_context", {
|
|
23
|
+
title: "Get Ticket Context",
|
|
24
|
+
description: "Obtiene el contexto completo de un ticket de Redmine (título, descripción, estado, prioridad).",
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
ticketId: z.string().describe("ID del ticket de Redmine (ej: '1234')"),
|
|
27
|
+
}),
|
|
28
|
+
}, async ({ ticketId }) => {
|
|
29
|
+
try {
|
|
30
|
+
const ticket = await getIssueDetails(ticketId);
|
|
31
|
+
if (!ticket) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `No se pudo obtener el ticket #${ticketId}` }],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: JSON.stringify({
|
|
42
|
+
id: ticket.id,
|
|
43
|
+
subject: ticket.subject,
|
|
44
|
+
description: ticket.description,
|
|
45
|
+
status: ticket.status,
|
|
46
|
+
priority: ticket.priority,
|
|
47
|
+
author: ticket.author,
|
|
48
|
+
url: ticket.url,
|
|
49
|
+
}, null, 2),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const errorMessage = error instanceof Error ? error.message : "Error desconocido";
|
|
56
|
+
return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
server.registerTool("get_git_diff", {
|
|
60
|
+
title: "Get Git Diff",
|
|
61
|
+
description: "Obtiene el diff de los cambios que están en staging (git add). Retorna el diff crudo.",
|
|
62
|
+
inputSchema: z.object({}),
|
|
63
|
+
}, async () => {
|
|
64
|
+
try {
|
|
65
|
+
const diff = await getStagedDiff();
|
|
66
|
+
if (!diff) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: "No hay cambios en staging. Ejecuta 'git add' primero." }],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { content: [{ type: "text", text: diff }] };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : "Error desconocido";
|
|
76
|
+
return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true };
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.registerTool("validate_configuration", {
|
|
80
|
+
title: "Validate Configuration",
|
|
81
|
+
description: "Verifica si todas las variables de entorno necesarias para Rikudo (Redmine, Gitea y Proveedor de IA) están configuradas correctamente en el sistema del usuario. Llama a esta herramienta antes de intentar interactuar con tickets o repositorios.",
|
|
82
|
+
inputSchema: z.object({}),
|
|
83
|
+
}, async () => {
|
|
84
|
+
const config = getConfig();
|
|
85
|
+
const missingVars = [];
|
|
86
|
+
if (!config.REDMINE_URL)
|
|
87
|
+
missingVars.push("RIKUDO_REDMINE_URL");
|
|
88
|
+
if (!config.REDMINE_API_KEY)
|
|
89
|
+
missingVars.push("RIKUDO_REDMINE_API_KEY");
|
|
90
|
+
if (!config.GITEA_URL)
|
|
91
|
+
missingVars.push("RIKUDO_GITEA_URL");
|
|
92
|
+
if (!config.GITEA_TOKEN)
|
|
93
|
+
missingVars.push("RIKUDO_GITEA_TOKEN");
|
|
94
|
+
if (missingVars.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: "✅ Configuración de entorno válida. Todos los sistemas listos." }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `❌ Faltan las siguientes variables de entorno: ${missingVars.join(", ")}. Por favor, pide al usuario que las configure en su archivo .env antes de continuar.`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
server.registerTool("shift_left_start", {
|
|
110
|
+
title: "Shift Left Start",
|
|
111
|
+
description: "Inicia un flujo shift-left: hace checkout a la rama objetivo, crea una nueva rama, hace commit vacío y abre un Draft PR en Gitea.",
|
|
112
|
+
inputSchema: z.object({
|
|
113
|
+
ticketId: z.string().describe("ID del ticket de Redmine (ej: '1234')"),
|
|
114
|
+
branchName: z.string().describe("Nombre de la nueva rama a crear (ej: '1234-add-user-auth')"),
|
|
115
|
+
targetBranch: z.string().default("alpha").describe("Rama base para hacer checkout (default: 'alpha')"),
|
|
116
|
+
isWip: z.boolean().default(false).describe("Si es true, el PR título lleva prefijo 'WIP:' (Work In Progress)"),
|
|
117
|
+
}),
|
|
118
|
+
}, async ({ ticketId, branchName, targetBranch, isWip }) => {
|
|
119
|
+
try {
|
|
120
|
+
await git.checkout(targetBranch);
|
|
121
|
+
await git.pull("origin", targetBranch);
|
|
122
|
+
await git.checkoutLocalBranch(branchName);
|
|
123
|
+
await git.commit("chore: init #" + ticketId, ["--allow-empty"]);
|
|
124
|
+
await git.push(["-u", "origin", branchName]);
|
|
125
|
+
const ticket = await getIssueDetails(ticketId);
|
|
126
|
+
const prTitle = isWip
|
|
127
|
+
? `WIP: ${ticket?.subject || `Ticket #${ticketId}`}`
|
|
128
|
+
: `Draft: ${ticket?.subject || `Ticket #${ticketId}`}`;
|
|
129
|
+
const prBody = `## Descripción\n\nTrabajo iniciado para el ticket #${ticketId}${ticket ? `\n\n**${ticket.subject}**` : ""}`;
|
|
130
|
+
const prResult = await createPullRequest({
|
|
131
|
+
title: prTitle,
|
|
132
|
+
head: branchName,
|
|
133
|
+
base: targetBranch,
|
|
134
|
+
body: prBody,
|
|
135
|
+
draft: true,
|
|
136
|
+
});
|
|
137
|
+
if (!prResult.success) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: `Rama '${branchName}' creada y empujada a origin. Error al crear PR: ${prResult.error}` }],
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify({
|
|
148
|
+
success: true,
|
|
149
|
+
branch: branchName,
|
|
150
|
+
targetBranch,
|
|
151
|
+
prUrl: prResult.url,
|
|
152
|
+
prNumber: prResult.number,
|
|
153
|
+
message: `Entorno preparado en rama '${branchName}'. Draft PR creado: ${prResult.url}`,
|
|
154
|
+
}, null, 2),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
const errorMessage = error instanceof Error ? error.message : "Error desconocido";
|
|
161
|
+
return { content: [{ type: "text", text: `Error en shift_left_start: ${errorMessage}` }], isError: true };
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
server.registerTool("update_pm_redmine", {
|
|
165
|
+
title: "Update PM Redmine",
|
|
166
|
+
description: "Agrega un comentario a un ticket de Redmine. Ideal para notificar al Project Manager.",
|
|
167
|
+
inputSchema: z.object({
|
|
168
|
+
ticketId: z.string().describe("ID del ticket de Redmine (ej: '1234')"),
|
|
169
|
+
commentMarkdown: z.string().describe("Comentario en formato Markdown/Textile para Redmine"),
|
|
170
|
+
}),
|
|
171
|
+
}, async ({ ticketId, commentMarkdown }) => {
|
|
172
|
+
try {
|
|
173
|
+
await updateIssueComment(ticketId, commentMarkdown);
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: `Comentario agregado exitosamente al ticket #${ticketId}` }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const errorMessage = error instanceof Error ? error.message : "Error desconocido";
|
|
180
|
+
return { content: [{ type: "text", text: `Error actualizando Redmine: ${errorMessage}` }], isError: true };
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
server.registerPrompt("start_feature", {
|
|
184
|
+
title: "Start Feature",
|
|
185
|
+
description: "Inicia el flujo de trabajo para una nueva feature. Obtiene contexto del ticket y prepara el entorno en Gitea.",
|
|
186
|
+
argsSchema: {
|
|
187
|
+
ticket_id: z.string().describe("ID del ticket de Redmine"),
|
|
188
|
+
},
|
|
189
|
+
}, async ({ ticket_id }) => {
|
|
190
|
+
return {
|
|
191
|
+
messages: [
|
|
192
|
+
{
|
|
193
|
+
role: "user",
|
|
194
|
+
content: {
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `Eres un asistente de desarrollo. Llama a la herramienta 'get_ticket_context' para el ticket ${ticket_id}. Lee su título y descripción. Genera un nombre de rama en inglés, cortas y en minúsculas, con el formato '[numero-tarea]-[titulo-tarea-ingles-corto]' (ej: '1234-add-user-authentication'). Luego, llama obligatoriamente a la herramienta 'shift_left_start' con esos datos para preparar mi entorno de trabajo en Gitea.`,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
server.registerPrompt("review_and_comment", {
|
|
203
|
+
title: "Review and Comment",
|
|
204
|
+
description: "Analiza los cambios staged, genera un comentario técnico para el Project Manager y lo publica en Redmine.",
|
|
205
|
+
argsSchema: {
|
|
206
|
+
ticket_id: z.string().describe("ID del ticket de Redmine"),
|
|
207
|
+
},
|
|
208
|
+
}, async ({ ticket_id }) => {
|
|
209
|
+
return {
|
|
210
|
+
messages: [
|
|
211
|
+
{
|
|
212
|
+
role: "user",
|
|
213
|
+
content: {
|
|
214
|
+
type: "text",
|
|
215
|
+
text: `Llama a la herramienta 'get_git_diff'. Analiza profundamente los cambios de código. Redacta un comentario técnico formal para el Project Manager explicando qué se hizo. Finalmente, llama a la herramienta 'update_pm_redmine' con el ${ticket_id} y el comentario generado para notificar al equipo.`,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
export async function runMCPServer() {
|
|
222
|
+
loadEnv();
|
|
223
|
+
const transport = new StdioServerTransport();
|
|
224
|
+
await server.connect(transport);
|
|
225
|
+
console.error("Rikudo MCP Server started");
|
|
226
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import axios, { AxiosError } from "axios";
|
|
2
|
+
const getGiteaConfig = () => {
|
|
3
|
+
return {
|
|
4
|
+
url: process.env.RIKUDO_GITEA_URL || "",
|
|
5
|
+
token: process.env.RIKUDO_GITEA_TOKEN || "",
|
|
6
|
+
owner: process.env.RIKUDO_GITEA_OWNER || "",
|
|
7
|
+
repo: process.env.RIKUDO_GITEA_REPO || "",
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
const getGiteaClient = () => {
|
|
11
|
+
const config = getGiteaConfig();
|
|
12
|
+
return axios.create({
|
|
13
|
+
baseURL: config.url,
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `token ${config.token}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
export const createPullRequest = async (payload) => {
|
|
21
|
+
const config = getGiteaConfig();
|
|
22
|
+
if (!config.url || !config.token || !config.owner || !config.repo) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: "Faltan variables de entorno de Gitea (RIKUDO_GITEA_URL, RIKUDO_GITEA_TOKEN, RIKUDO_GITEA_OWNER, RIKUDO_GITEA_REPO)",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const client = getGiteaClient();
|
|
30
|
+
const endpoint = `/api/v1/repos/${config.owner}/${config.repo}/pulls`;
|
|
31
|
+
const response = await client.post(endpoint, {
|
|
32
|
+
title: payload.title,
|
|
33
|
+
head: payload.head,
|
|
34
|
+
base: payload.base,
|
|
35
|
+
body: payload.body,
|
|
36
|
+
draft: payload.draft ?? false,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
url: response.data.html_url,
|
|
41
|
+
number: response.data.number,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof AxiosError) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: `Error de Gitea: ${error.message} - ${error.response?.data?.message || ""}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: error instanceof Error ? error.message : "Error desconocido al crear PR",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const Gitea = {
|
|
58
|
+
createPullRequest,
|
|
59
|
+
};
|
package/dist/utils/config.js
CHANGED
|
@@ -17,6 +17,10 @@ export function getConfig() {
|
|
|
17
17
|
REDMINE_URL: process.env.RIKUDO_REDMINE_URL || "",
|
|
18
18
|
REDMINE_API_KEY: process.env.RIKUDO_REDMINE_API_KEY || "",
|
|
19
19
|
PROJECT_ID: process.env.RIKUDO_REDMINE_PROJECT_ID || "",
|
|
20
|
+
GITEA_URL: process.env.RIKUDO_GITEA_URL || "",
|
|
21
|
+
GITEA_TOKEN: process.env.RIKUDO_GITEA_TOKEN || "",
|
|
22
|
+
GITEA_OWNER: process.env.RIKUDO_GITEA_OWNER || "",
|
|
23
|
+
GITEA_REPO: process.env.RIKUDO_GITEA_REPO || "",
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
export function getLimits() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dami_deleon/rikudo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "El Sabio de los Commits: AI Assistant con integración a Redmine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@anthropic-ai/sdk": "0.74.0",
|
|
36
36
|
"@google/generative-ai": "0.24.1",
|
|
37
37
|
"@inquirer/prompts": "8.2.0",
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
38
39
|
"@types/figlet": "1.7.0",
|
|
39
40
|
"axios": "1.13.2",
|
|
40
41
|
"chalk": "5.6.2",
|