@dami_deleon/rikudo 1.1.0 → 2.0.1
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 +115 -53
- 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
|
@@ -1,107 +1,182 @@
|
|
|
1
1
|
# 🌀 Rikudo: El Sabio de los Commits
|
|
2
2
|
|
|
3
|
-
**Rikudo** es una herramienta
|
|
3
|
+
**Rikudo** es una herramienta CLI potenciada por Inteligencia Artificial, agnóstica del proveedor de IA, diseñada para automatizar y estandarizar el flujo de trabajo de Git y la gestión de tareas en Redmine.
|
|
4
4
|
|
|
5
|
-
Inspirado en el Sabio de los Seis Caminos, Rikudo utiliza
|
|
5
|
+
Inspirado en el Sabio de los Seis Caminos, Rikudo utiliza módulos especializados para observar tu código, entender el contexto de tus tareas y mantener la armonía en el repositorio siguiendo las convenciones del equipo.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## 🚀 Los 6 Caminos de Rikudo
|
|
10
10
|
|
|
11
11
|
1. **Camino Deva (Git):** Control total sobre el stage y los commits.
|
|
12
|
-
2. **Camino Humano (
|
|
12
|
+
2. **Camino Humano (AI):** Inteligencia profunda para redactar mensajes técnicos precisos.
|
|
13
13
|
3. **Camino Animal (Redmine):** Invocación y sincronización con el Project Manager.
|
|
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
17
|
|
|
18
|
+
---
|
|
18
19
|
|
|
19
|
-
## ✨
|
|
20
|
+
## ✨ Dos Modos de Uso
|
|
21
|
+
|
|
22
|
+
Rikudo puede usarse de dos maneras:
|
|
23
|
+
|
|
24
|
+
### Modo CLI
|
|
25
|
+
Interactúa directamente desde la terminal con un menú interactivo o comandos directos.
|
|
26
|
+
|
|
27
|
+
### Modo MCP (Model Context Protocol)
|
|
28
|
+
Actúa como servidor MCP, exponiendo herramientas y prompts para que agentes de IA externos (Claude Code, Gemini CLI, Cursor, etc.) puedan invocar sus funcionalidades.
|
|
29
|
+
|
|
30
|
+
---
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
* **Google Gemini** (Rápido y eficiente)
|
|
23
|
-
* **OpenAI GPT-4o / GPT-3.5** (El estándar)
|
|
24
|
-
* **Anthropic Claude** (Gran capacidad de contexto)
|
|
25
|
-
* **Ollama** (Privacidad total con modelos locales como Llama 3)
|
|
32
|
+
## ✨ Características Principales
|
|
26
33
|
|
|
34
|
+
* **🧠 Agnóstico de IA:** Tú eliges el cerebro. Soporte nativo para:
|
|
35
|
+
* **Google Gemini** (Rápido y eficiente)
|
|
36
|
+
* **OpenAI GPT-4o / GPT-3.5** (El estándar)
|
|
37
|
+
* **Anthropic Claude** (Gran capacidad de contexto)
|
|
38
|
+
* **Ollama** (Privacidad total con modelos locales como Llama 3)
|
|
27
39
|
|
|
28
40
|
* **📝 Smart Commits:** Analiza tus `staged files` y genera mensajes siguiendo **Conventional Commits** basados en tus propias reglas.
|
|
29
41
|
* **🚀 PR Generator:** Selecciona múltiples commits y genera una descripción de Pull Request completa siguiendo tu plantilla.
|
|
30
42
|
* **⚙️ Configuración Flexible:** Define tus preferencias globales o por proyecto.
|
|
31
|
-
* **🔍 Diagnóstico Integrado:** Herramienta `test` para verificar tus API Keys y conexión con servicios externos
|
|
43
|
+
* **🔍 Diagnóstico Integrado:** Herramienta `test` para verificar tus API Keys y conexión con servicios externos.
|
|
44
|
+
* **🔌 Servidor MCP:** Actúa como skill library para agentes de IA externos mediante el protocolo MCP.
|
|
32
45
|
|
|
33
46
|
## 📦 Instalación
|
|
34
47
|
|
|
35
|
-
Rikudo está disponible en npm (actualmente en Alpha).
|
|
36
|
-
|
|
37
|
-
### Uso global (Recomendado)
|
|
38
|
-
|
|
39
48
|
```bash
|
|
40
|
-
npm install -g @dami_deleon/rikudo
|
|
41
|
-
|
|
49
|
+
npm install -g @dami_deleon/rikudo
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
### Ejecución única (sin instalar)
|
|
45
53
|
|
|
46
54
|
```bash
|
|
47
|
-
npx @dami_deleon/rikudo
|
|
48
|
-
|
|
55
|
+
npx @dami_deleon/rikudo
|
|
49
56
|
```
|
|
50
57
|
|
|
58
|
+
---
|
|
59
|
+
|
|
51
60
|
## 🚀 Uso Rápido
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
### Modo CLI
|
|
63
|
+
|
|
64
|
+
Ejecuta el comando en tu terminal dentro de cualquier repositorio git:
|
|
54
65
|
|
|
55
66
|
```bash
|
|
56
67
|
rikudo
|
|
57
|
-
|
|
58
68
|
```
|
|
59
69
|
|
|
60
70
|
Esto abrirá el **Menú Interactivo** donde podrás elegir entre crear commits, PRs o configurar la herramienta.
|
|
61
71
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Si eres un power-user, puedes usar atajos:
|
|
72
|
+
#### Comandos Directos
|
|
65
73
|
|
|
66
74
|
* **Crear Commit:**
|
|
67
75
|
```bash
|
|
68
76
|
rikudo commit
|
|
69
77
|
# O con ticket asociado:
|
|
70
78
|
rikudo commit -t 1234
|
|
71
|
-
|
|
72
79
|
```
|
|
73
80
|
|
|
74
|
-
|
|
75
81
|
* **Crear Pull Request:**
|
|
76
82
|
```bash
|
|
77
83
|
rikudo pr
|
|
78
84
|
# O con ticket asociado:
|
|
79
85
|
rikudo pr -t 1234
|
|
80
|
-
|
|
81
86
|
```
|
|
82
87
|
|
|
83
|
-
|
|
84
88
|
* **Diagnóstico de Conexión:**
|
|
85
89
|
```bash
|
|
86
90
|
rikudo test
|
|
87
|
-
|
|
88
91
|
```
|
|
89
92
|
|
|
90
|
-
|
|
91
93
|
* **Configuración:**
|
|
92
94
|
```bash
|
|
93
95
|
rikudo config
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Modo MCP
|
|
101
|
+
|
|
102
|
+
Para usar Rikudo como servidor MCP con cualquier agente de IA compatible:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
rikudo mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
El servidor se comunica mediante **stdio**, permitiendo integrarse con cualquier cliente MCP.
|
|
109
|
+
|
|
110
|
+
#### Herramientas Disponibles
|
|
111
|
+
|
|
112
|
+
| Herramienta | Descripción |
|
|
113
|
+
|------------|-------------|
|
|
114
|
+
| `validate_configuration` | Verifica que las variables de entorno necesarias (Redmine, Gitea) estén configuradas correctamente. |
|
|
115
|
+
| `get_ticket_context` | Obtiene el contexto completo de un ticket de Redmine (título, descripción, estado, prioridad). |
|
|
116
|
+
| `get_git_diff` | Retorna el diff de los archivos que están en staging (`git add`). |
|
|
117
|
+
| `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. |
|
|
118
|
+
| `update_pm_redmine` | Agrega un comentario a un ticket de Redmine para notificar al Project Manager. |
|
|
119
|
+
|
|
120
|
+
#### Prompts Disponibles
|
|
121
|
+
|
|
122
|
+
| Prompt | Descripción |
|
|
123
|
+
|--------|-------------|
|
|
124
|
+
| `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. |
|
|
125
|
+
| `review_and_comment` | Analiza los cambios staged, genera un comentario técnico y lo publica en Redmine. |
|
|
126
|
+
|
|
127
|
+
#### Integración con Agentes IA
|
|
128
|
+
|
|
129
|
+
Rikudo es compatible con cualquier herramienta de IA que soporte el protocolo MCP, como:
|
|
130
|
+
- **Claude Code / Claude Desktop**
|
|
131
|
+
- **Gemini CLI**
|
|
132
|
+
- **Cursor**
|
|
133
|
+
- **Windsurf**
|
|
134
|
+
- **Continue**
|
|
135
|
+
- Y cualquier otro cliente MCP
|
|
136
|
+
|
|
137
|
+
Agrega esto en la configuración de tu cliente MCP:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"rikudo": {
|
|
143
|
+
"command": "rikudo",
|
|
144
|
+
"args": ["mcp"]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Variables de Entorno para MCP
|
|
151
|
+
|
|
152
|
+
```env
|
|
153
|
+
# Redmine
|
|
154
|
+
RIKUDO_REDMINE_URL=https://redmine.tuempresa.com
|
|
155
|
+
RIKUDO_REDMINE_API_KEY=tu_redmine_key
|
|
156
|
+
|
|
157
|
+
# Gitea
|
|
158
|
+
RIKUDO_GITEA_URL=https://gitea.tuempresa.com
|
|
159
|
+
RIKUDO_GITEA_TOKEN=tu_gitea_token
|
|
160
|
+
RIKUDO_GITEA_OWNER=tu_usuario_o_organizacion
|
|
161
|
+
RIKUDO_GITEA_REPO=nombre_del_repositorio
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Formato de Ramas
|
|
165
|
+
|
|
166
|
+
Al usar `start_feature` o `shift_left_start`, el nombre de rama sigue el formato:
|
|
94
167
|
|
|
168
|
+
```
|
|
169
|
+
[numero-tarea]-[titulo-tarea-ingles-corto]
|
|
95
170
|
```
|
|
96
171
|
|
|
172
|
+
Ejemplo: `1234-add-user-authentication`
|
|
97
173
|
|
|
174
|
+
---
|
|
98
175
|
|
|
99
176
|
## ⚙️ Configuración
|
|
100
177
|
|
|
101
178
|
Rikudo busca un archivo `.env` en la raíz de tu proyecto para las credenciales sensibles.
|
|
102
179
|
|
|
103
|
-
Crea un archivo `.env` y añade las variables según el proveedor que quieras usar:
|
|
104
|
-
|
|
105
180
|
```env
|
|
106
181
|
# --- Configuración General ---
|
|
107
182
|
RIKUDO_AI_PROVIDER=gemini # Opciones: gemini, openai, claude, ollama
|
|
@@ -115,41 +190,28 @@ RIKUDO_CLAUDE_API_KEY=tu_api_key_aqui
|
|
|
115
190
|
RIKUDO_OLLAMA_URL=http://localhost:11434/api/generate
|
|
116
191
|
RIKUDO_OLLAMA_MODEL=llama3
|
|
117
192
|
|
|
118
|
-
# --- Integraciones
|
|
193
|
+
# --- Integraciones ---
|
|
119
194
|
RIKUDO_REDMINE_URL=https://redmine.tuempresa.com
|
|
120
195
|
RIKUDO_REDMINE_API_KEY=tu_redmine_key
|
|
121
196
|
|
|
197
|
+
# --- Gitea (para servidor MCP) ---
|
|
198
|
+
RIKUDO_GITEA_URL=https://gitea.tuempresa.com
|
|
199
|
+
RIKUDO_GITEA_TOKEN=tu_gitea_token
|
|
200
|
+
RIKUDO_GITEA_OWNER=tu_usuario_o_organizacion
|
|
201
|
+
RIKUDO_GITEA_REPO=nombre_del_repositorio
|
|
122
202
|
```
|
|
123
203
|
|
|
124
|
-
### Plantillas Personalizadas
|
|
125
|
-
|
|
126
|
-
Puedes personalizar cómo Rikudo escribe. Coloca estos archivos en la raíz de tu proyecto:
|
|
204
|
+
### Plantillas Personalizadas
|
|
127
205
|
|
|
128
206
|
1. **`conventions.txt`**: Reglas para tus commits.
|
|
129
|
-
* *Ejemplo:* "Usa emojis al inicio. Formato: <emoji> <tipo>: <mensaje>."
|
|
130
|
-
|
|
131
|
-
|
|
132
207
|
2. **`pull-request-template.txt`**: Estructura para tus PRs.
|
|
133
|
-
* *Ejemplo:*
|
|
134
|
-
```markdown
|
|
135
|
-
# Cambios
|
|
136
|
-
- ...
|
|
137
|
-
# Tickets Relacionados
|
|
138
|
-
- ...
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
208
|
|
|
209
|
+
---
|
|
145
210
|
|
|
146
211
|
## 🛠️ Solución de Problemas
|
|
147
212
|
|
|
148
|
-
Si tienes problemas de conexión o configuración, ejecuta el diagnóstico:
|
|
149
|
-
|
|
150
213
|
```bash
|
|
151
214
|
rikudo test
|
|
152
|
-
|
|
153
215
|
```
|
|
154
216
|
|
|
155
217
|
Esto validará:
|
|
@@ -160,4 +222,4 @@ Esto validará:
|
|
|
160
222
|
|
|
161
223
|
---
|
|
162
224
|
|
|
163
|
-
Hecho con ❤️ y TypeScript.
|
|
225
|
+
Hecho con ❤️ y TypeScript.
|
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.1",
|
|
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",
|