@dami_deleon/rikudo 1.0.1-alpha.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 +62 -0
- package/dist/ai/drivers/claude.js +62 -0
- package/dist/ai/drivers/gemini.js +50 -0
- package/dist/ai/drivers/ollama.js +30 -0
- package/dist/ai/drivers/openai.js +24 -0
- package/dist/ai/factory.js +39 -0
- package/dist/ai/gemini.js +79 -0
- package/dist/ai/interfaces.js +1 -0
- package/dist/ai/prompts.js +70 -0
- package/dist/const/paths.js +4 -0
- package/dist/index.js +276 -0
- package/dist/menus/configMenu.js +120 -0
- package/dist/menus/issueMenu.js +45 -0
- package/dist/services/cache.js +73 -0
- package/dist/services/configManager.js +26 -0
- package/dist/services/git.js +46 -0
- package/dist/services/rateLimiter.js +77 -0
- package/dist/services/redmine.js +204 -0
- package/dist/services/summarizer.js +51 -0
- package/dist/test/connection.js +76 -0
- package/dist/utils/config.js +76 -0
- package/dist/utils/env.js +13 -0
- package/dist/utils/error.js +9 -0
- package/dist/utils/paths.js +26 -0
- package/dist/utils/try.js +11 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 🌀 Rikudo: El Sabio de los Commits
|
|
2
|
+
|
|
3
|
+
**Rikudo** es una herramienta de terminal (CLI) potenciada por Inteligencia Artificial (Google Gemini) diseñada para automatizar y estandarizar el flujo de trabajo de Git y la gestión de tareas en Redmine.
|
|
4
|
+
|
|
5
|
+
Inspirado en el Sabio de los Seis Caminos, Rikudo utiliza seis módulos especializados para observar tu código, entender el contexto de tus tareas y mantener la armonía en el repositorio siguiendo estrictamente las convenciones del equipo.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🚀 Los 6 Caminos de Rikudo
|
|
10
|
+
|
|
11
|
+
1. **Camino Deva (Git):** Control total sobre el stage y los commits.
|
|
12
|
+
2. **Camino Humano (Gemini AI):** Inteligencia profunda para redactar mensajes técnicos precisos.
|
|
13
|
+
3. **Camino Animal (Redmine):** Invocación y sincronización con el Project Manager.
|
|
14
|
+
4. **Camino Preta (Summarizer):** Absorción y síntesis de archivos de gran tamaño.
|
|
15
|
+
5. **Camino Naraka (Cache):** Memoria persistente basada en hashes de Git para optimizar tokens.
|
|
16
|
+
6. **Camino Asura (Rate Limiter):** Gestión de poder para respetar los límites de las APIs.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 🛠️ Requisitos Previos
|
|
21
|
+
|
|
22
|
+
- **Node.js** (v18 o superior)
|
|
23
|
+
- **Git** instalado y configurado.
|
|
24
|
+
- Una **API Key de Google Gemini**.
|
|
25
|
+
- Acceso a la **API de Redmine** de tu organización.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📦 Instalación
|
|
30
|
+
|
|
31
|
+
1. **Clonar y preparar el proyecto:**
|
|
32
|
+
```bash
|
|
33
|
+
git clone <url-de-tu-repo-rikudo>
|
|
34
|
+
cd rikudo
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. **Compilar e Instalar Globalmente:**
|
|
39
|
+
```bash
|
|
40
|
+
npm run build
|
|
41
|
+
npm link
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ahora el comando `rikudo` estará disponible en cualquier terminal de tu sistema.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ⚙️ Configuración
|
|
49
|
+
|
|
50
|
+
### 1. Variables de Entorno (`.env`)
|
|
51
|
+
Crea un archivo `.env` en la raíz del proyecto (no olvides agregarlo a tu `.gitignore` global):
|
|
52
|
+
|
|
53
|
+
```env
|
|
54
|
+
# Credenciales
|
|
55
|
+
GEMINI_API_KEY=tu_clave_aqui
|
|
56
|
+
REDMINE_URL=[https://redmine.tuempresa.com](https://redmine.tuempresa.com)
|
|
57
|
+
REDMINE_API_KEY=tu_clave_de_redmine
|
|
58
|
+
|
|
59
|
+
# Límites de la API (Evita el error 429)
|
|
60
|
+
AI_REQUESTS_PER_DAY=1500
|
|
61
|
+
AI_REQUESTS_PER_MINUTE=15
|
|
62
|
+
AI_TOKENS_PER_MINUTE=1000000
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getFromEnv, testEnvVar } from "../../utils/config.js";
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
function getAnthropic() {
|
|
4
|
+
const { CLAUDE_API_KEY } = getFromEnv("CLAUDE_API_KEY");
|
|
5
|
+
const client = new Anthropic({
|
|
6
|
+
apiKey: CLAUDE_API_KEY,
|
|
7
|
+
});
|
|
8
|
+
return client;
|
|
9
|
+
}
|
|
10
|
+
async function getModels() {
|
|
11
|
+
return [
|
|
12
|
+
"claude-opus-4-6",
|
|
13
|
+
"claude-opus-4-5-20251101",
|
|
14
|
+
"claude-opus-4-5",
|
|
15
|
+
"claude-3-7-sonnet-latest",
|
|
16
|
+
"claude-3-7-sonnet-20250219",
|
|
17
|
+
"claude-3-5-haiku-latest",
|
|
18
|
+
"claude-3-5-haiku-20241022",
|
|
19
|
+
"claude-haiku-4-5",
|
|
20
|
+
"claude-haiku-4-5-20251001",
|
|
21
|
+
"claude-sonnet-4-20250514",
|
|
22
|
+
"claude-sonnet-4-0",
|
|
23
|
+
"claude-4-sonnet-20250514",
|
|
24
|
+
"claude-sonnet-4-5",
|
|
25
|
+
"claude-sonnet-4-5-20250929",
|
|
26
|
+
"claude-opus-4-0",
|
|
27
|
+
"claude-opus-4-20250514",
|
|
28
|
+
"claude-4-opus-20250514",
|
|
29
|
+
"claude-opus-4-1-20250805",
|
|
30
|
+
"claude-3-opus-latest",
|
|
31
|
+
"claude-3-opus-20240229",
|
|
32
|
+
"claude-3-haiku-20240307",
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
async function validateSettings() {
|
|
36
|
+
return testEnvVar("CLAUDE_API_KEY");
|
|
37
|
+
}
|
|
38
|
+
async function generate(model, prompt) {
|
|
39
|
+
const client = getAnthropic();
|
|
40
|
+
const message = await client.messages.create({
|
|
41
|
+
model: model,
|
|
42
|
+
messages: [
|
|
43
|
+
{
|
|
44
|
+
role: "user",
|
|
45
|
+
content: prompt,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
max_tokens: 1000,
|
|
49
|
+
});
|
|
50
|
+
return message.content
|
|
51
|
+
.map((m) => (m.type === "text" ? m.text : ""))
|
|
52
|
+
.join(" ");
|
|
53
|
+
}
|
|
54
|
+
export function ClaudeDriver(modelSelected = "claude-2") {
|
|
55
|
+
return {
|
|
56
|
+
name: "claude",
|
|
57
|
+
modelSelected: modelSelected || "claude-2",
|
|
58
|
+
getModels,
|
|
59
|
+
generate: (prompt) => generate(modelSelected, prompt),
|
|
60
|
+
validateSettings,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { getConfig, testEnvVar } from "../../utils/config.js";
|
|
3
|
+
import { RateLimiter } from "../../services/rateLimiter.js";
|
|
4
|
+
const limiter = new RateLimiter();
|
|
5
|
+
const getGoogleAI = async () => {
|
|
6
|
+
const config = getConfig();
|
|
7
|
+
return new GoogleGenerativeAI(config.GEMINI_API_KEY);
|
|
8
|
+
};
|
|
9
|
+
// Estimación burda de tokens (4 caracteres ~= 1 token) para el check previo
|
|
10
|
+
const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
11
|
+
const generateContent = async (modelName, prompt) => {
|
|
12
|
+
const estimatedInputTokens = estimateTokens(prompt);
|
|
13
|
+
await limiter.checkLimit(estimatedInputTokens);
|
|
14
|
+
try {
|
|
15
|
+
const genAI = await getGoogleAI();
|
|
16
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
17
|
+
const result = await model.generateContent(prompt);
|
|
18
|
+
const response = result.response;
|
|
19
|
+
const outputText = response.text();
|
|
20
|
+
const totalTokens = estimatedInputTokens + estimateTokens(outputText);
|
|
21
|
+
limiter.logRequest(totalTokens);
|
|
22
|
+
return outputText;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const validateSettings = async () => {
|
|
29
|
+
return testEnvVar("GEMINI_API_KEY");
|
|
30
|
+
};
|
|
31
|
+
async function getModels() {
|
|
32
|
+
return [
|
|
33
|
+
"gemini-flash-lite-latest",
|
|
34
|
+
"gemini-2.5-flash-lite",
|
|
35
|
+
"gemini-2.5-flash",
|
|
36
|
+
"gemini-2.5-pro",
|
|
37
|
+
"gemini-3-pro-preview",
|
|
38
|
+
"gemini-3-flash-preview",
|
|
39
|
+
"gemma-3-27b-it",
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
export function GeminiDriver(modelSelected = "gemini-2.5-flash-lite") {
|
|
43
|
+
return {
|
|
44
|
+
name: "gemini",
|
|
45
|
+
modelSelected: modelSelected || "gemini-2.5-flash-lite",
|
|
46
|
+
getModels,
|
|
47
|
+
generate: (prompt) => generateContent(modelSelected, prompt),
|
|
48
|
+
validateSettings,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import ollama from "ollama";
|
|
2
|
+
export function OllamaDriver(model = '') {
|
|
3
|
+
return {
|
|
4
|
+
name: "Ollama (Local)",
|
|
5
|
+
modelSelected: model,
|
|
6
|
+
getModels: async () => {
|
|
7
|
+
const listResponse = await ollama.list();
|
|
8
|
+
return listResponse.models.map((modelResponse) => modelResponse.name);
|
|
9
|
+
},
|
|
10
|
+
generate: async (prompt) => {
|
|
11
|
+
if (model === '') {
|
|
12
|
+
throw new Error("Modelo de Ollama no seleccionado, seleccione uno en la configuración.");
|
|
13
|
+
}
|
|
14
|
+
const response = await ollama.chat({
|
|
15
|
+
model: model,
|
|
16
|
+
messages: [
|
|
17
|
+
{
|
|
18
|
+
role: "user",
|
|
19
|
+
content: prompt,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
return response.message.content;
|
|
24
|
+
},
|
|
25
|
+
validateSettings: async () => {
|
|
26
|
+
const listResponse = await ollama.list();
|
|
27
|
+
return listResponse.models.length > 0;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { getFromEnv, testEnvVar } from "../../utils/config.js";
|
|
3
|
+
export function OpenAIDriver(model = "gpt-5-nano") {
|
|
4
|
+
return {
|
|
5
|
+
name: "OpenAI",
|
|
6
|
+
modelSelected: model,
|
|
7
|
+
getModels: async () => {
|
|
8
|
+
return ["gpt-5.2", "gpt-5-mini", "gpt-5-nano", "gpt-4.1"];
|
|
9
|
+
},
|
|
10
|
+
generate: async (prompt) => {
|
|
11
|
+
const openai = new OpenAI({
|
|
12
|
+
apiKey: getFromEnv("OPENAI_API_KEY").OPENAI_API_KEY,
|
|
13
|
+
});
|
|
14
|
+
const response = await openai.responses.create({
|
|
15
|
+
model: model,
|
|
16
|
+
input: prompt,
|
|
17
|
+
});
|
|
18
|
+
return response.output_text;
|
|
19
|
+
},
|
|
20
|
+
validateSettings: async () => {
|
|
21
|
+
return testEnvVar("OPENAI_API_KEY");
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GeminiDriver } from "./drivers/gemini.js";
|
|
2
|
+
import { getAIPreferenceConfig } from "../services/configManager.js";
|
|
3
|
+
import { OllamaDriver } from "./drivers/ollama.js";
|
|
4
|
+
import { OpenAIDriver } from "./drivers/openai.js";
|
|
5
|
+
import { ClaudeDriver } from "./drivers/claude.js";
|
|
6
|
+
const Providers = {
|
|
7
|
+
gemini: {
|
|
8
|
+
name: "gemini",
|
|
9
|
+
label: "Google Gemini",
|
|
10
|
+
driver: GeminiDriver,
|
|
11
|
+
},
|
|
12
|
+
ollama: {
|
|
13
|
+
name: "ollama",
|
|
14
|
+
label: "Ollama (local)",
|
|
15
|
+
driver: OllamaDriver,
|
|
16
|
+
},
|
|
17
|
+
openai: {
|
|
18
|
+
name: "openai",
|
|
19
|
+
label: "OpenAI",
|
|
20
|
+
driver: OpenAIDriver,
|
|
21
|
+
},
|
|
22
|
+
claude: {
|
|
23
|
+
name: "claude",
|
|
24
|
+
label: "Claude Anthropic",
|
|
25
|
+
driver: ClaudeDriver,
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export function getAIProviders() {
|
|
29
|
+
return Providers;
|
|
30
|
+
}
|
|
31
|
+
export function getAIProvider() {
|
|
32
|
+
const { provider, model } = getAIPreferenceConfig();
|
|
33
|
+
if (provider in Providers) {
|
|
34
|
+
return Providers[provider].driver(model);
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Proveedores disponibles: ${Object.values(getAIProviders())
|
|
37
|
+
.map((p) => p.label)
|
|
38
|
+
.join(", ")}`);
|
|
39
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import { config, getConventions, getPRTemplate } from '../utils/config.js';
|
|
3
|
+
import { RateLimiter } from '../services/rateLimiter.js';
|
|
4
|
+
const genAI = new GoogleGenerativeAI(config.GEMINI_API_KEY);
|
|
5
|
+
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
6
|
+
const limiter = new RateLimiter();
|
|
7
|
+
// Estimación burda de tokens (4 caracteres ~= 1 token) para el check previo
|
|
8
|
+
const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
9
|
+
export const generateContent = async (prompt) => {
|
|
10
|
+
// 1. Verificar Límites antes de llamar
|
|
11
|
+
const estimatedInputTokens = estimateTokens(prompt);
|
|
12
|
+
await limiter.checkLimit(estimatedInputTokens);
|
|
13
|
+
try {
|
|
14
|
+
const result = await model.generateContent(prompt);
|
|
15
|
+
const response = result.response;
|
|
16
|
+
// 2. Registrar consumo real (Gemini a veces devuelve metadata de uso,
|
|
17
|
+
// si no, usamos la estimación de salida + entrada)
|
|
18
|
+
const outputText = response.text();
|
|
19
|
+
const totalTokens = estimatedInputTokens + estimateTokens(outputText);
|
|
20
|
+
limiter.logRequest(totalTokens);
|
|
21
|
+
return outputText;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Si Gemini devuelve 429, es bueno capturarlo aquí también
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const generateCommitMessage = async (diff, ticketInfo, fileContext) => {
|
|
29
|
+
const conventions = getConventions();
|
|
30
|
+
const ticketContext = ticketInfo
|
|
31
|
+
? `Contexto del Ticket #${ticketInfo.id}: ${ticketInfo.subject} - ${ticketInfo.description}`
|
|
32
|
+
: "No hay ticket asociado.";
|
|
33
|
+
const prompt = `
|
|
34
|
+
Actúa como un Senior Software Engineer. Genera un mensaje de commit siguiendo ESTRICTAMENTE estas convenciones:
|
|
35
|
+
${conventions}
|
|
36
|
+
|
|
37
|
+
${ticketInfo ? `IMPORTANTE: El commit debe incluir la referencia al ticket #${ticketInfo.id} según las convenciones.` : ''}
|
|
38
|
+
|
|
39
|
+
INFORMACIÓN DE ENTRADA:
|
|
40
|
+
1. Contexto de la Tarea (Redmine): ${ticketContext}
|
|
41
|
+
2. Resumen de archivos modificados: ${fileContext}
|
|
42
|
+
3. Git Diff (Cambios exactos):
|
|
43
|
+
${diff.slice(0, 10000)} (Truncado si es muy largo)
|
|
44
|
+
|
|
45
|
+
SALIDA:
|
|
46
|
+
Solo devuelve el mensaje del commit, sin explicaciones extra, ni bloques de código markdown.
|
|
47
|
+
`;
|
|
48
|
+
return await generateContent(prompt);
|
|
49
|
+
};
|
|
50
|
+
export const generatePRDescription = async (commits, ticketInfo, templatePath) => {
|
|
51
|
+
const template = getPRTemplate(templatePath);
|
|
52
|
+
const ticketContext = ticketInfo
|
|
53
|
+
? `Ticket #${ticketInfo.id}: ${ticketInfo.subject}\nDescripción: ${ticketInfo.description}`
|
|
54
|
+
: "No hay ticket asociado.";
|
|
55
|
+
const prompt = `
|
|
56
|
+
Actúa como un Tech Lead. Tu tarea es completar la plantilla de Pull Request basándote en los commits realizados y la tarea original.
|
|
57
|
+
|
|
58
|
+
ENTRADA:
|
|
59
|
+
1. Historial de Commits recientes:
|
|
60
|
+
${commits}
|
|
61
|
+
|
|
62
|
+
2. Información de la Tarea (Redmine):
|
|
63
|
+
${ticketContext}
|
|
64
|
+
|
|
65
|
+
3. PLANTILLA A RELLENAR (Formato Markdown):
|
|
66
|
+
${template}
|
|
67
|
+
|
|
68
|
+
INSTRUCCIONES:
|
|
69
|
+
- Rellena la plantilla con información relevante extraída de los commits y el ticket.
|
|
70
|
+
- Sé profesional pero directo.
|
|
71
|
+
- Si la plantilla tiene checkboxes [ ], marca los que apliquen con [x].
|
|
72
|
+
- NO inventes funcionalidades que no estén en los commits.
|
|
73
|
+
- Manten el formato Markdown.
|
|
74
|
+
|
|
75
|
+
SALIDA:
|
|
76
|
+
Solo el contenido de la plantilla rellena.
|
|
77
|
+
`;
|
|
78
|
+
return await generateContent(prompt);
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getConfig } from "../utils/config.js";
|
|
2
|
+
export function generateCommitMessagePrompt(diff, conventions, ticketInfo, fileContext) {
|
|
3
|
+
const { REDMINE_URL } = getConfig();
|
|
4
|
+
const ticketContext = ticketInfo
|
|
5
|
+
? `Contexto del Ticket #${ticketInfo.id}: ${ticketInfo.subject} - ${ticketInfo.description}
|
|
6
|
+
URL del ticket: ${REDMINE_URL}/issues/${ticketInfo.id}`
|
|
7
|
+
: "No hay ticket asociado.";
|
|
8
|
+
const prompt = `
|
|
9
|
+
Actúa como un Senior Software Engineer. Genera un mensaje de commit siguiendo ESTRICTAMENTE estas convenciones:
|
|
10
|
+
${conventions}
|
|
11
|
+
|
|
12
|
+
${ticketInfo ? `IMPORTANTE: El commit debe incluir la referencia al ticket #${ticketInfo.id} según las convenciones.` : ""}
|
|
13
|
+
|
|
14
|
+
INFORMACIÓN DE ENTRADA:
|
|
15
|
+
1. Contexto de la Tarea (Redmine): ${ticketContext}
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
2. Resumen de archivos modificados: ${fileContext}
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
3. Git Diff (Cambios exactos):
|
|
22
|
+
${diff.slice(0, 20000)} (Truncado si es muy largo)
|
|
23
|
+
|
|
24
|
+
SALIDA:
|
|
25
|
+
Solo devuelve el mensaje del commit, sin explicaciones extra, ni bloques de código markdown.`;
|
|
26
|
+
return prompt;
|
|
27
|
+
}
|
|
28
|
+
export function generateProjectManagerCommentPrompt(ticketInfo, commitMessage) {
|
|
29
|
+
return `Actúa como un Project Manager profesional. Debes escribir un comentario ejecutivo, directo, claro y conciso, para ser publicado en un comentario del ticket de Redmine.
|
|
30
|
+
El comentario debe servir como un resumen de estado para stakeholders, sintetizando la información técnica del último commit en un lenguaje accesible.
|
|
31
|
+
|
|
32
|
+
Contexto y Datos del Ticket:
|
|
33
|
+
ID y Asunto del Ticket: ${ticketInfo.tracker} #${ticketInfo.id} - ${ticketInfo.subject}
|
|
34
|
+
Descripción Original: ${ticketInfo.description}
|
|
35
|
+
Registro del Último Commit (Log Técnico): ${commitMessage}
|
|
36
|
+
|
|
37
|
+
SALIDA:
|
|
38
|
+
Solo devuelve el comentario, sin títulos o subtítulos o lineas de saludos.
|
|
39
|
+
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
export function generatePullRequestMessagePrompt(commits, ticketInfo, template) {
|
|
43
|
+
const ticketContext = ticketInfo
|
|
44
|
+
? `Ticket #${ticketInfo.id}: ${ticketInfo.subject}\nDescripción: ${ticketInfo.description}`
|
|
45
|
+
: "No hay ticket asociado.";
|
|
46
|
+
const prompt = `
|
|
47
|
+
Actúa como un Tech Lead. Tu tarea es completar la plantilla de Pull Request basándote en los commits realizados y la tarea original.
|
|
48
|
+
|
|
49
|
+
ENTRADA:
|
|
50
|
+
1. Historial de Commits recientes:
|
|
51
|
+
${commits}
|
|
52
|
+
|
|
53
|
+
2. Información de la Tarea (Redmine):
|
|
54
|
+
${ticketContext}
|
|
55
|
+
|
|
56
|
+
3. PLANTILLA A RELLENAR (Formato Markdown):
|
|
57
|
+
${template}
|
|
58
|
+
|
|
59
|
+
INSTRUCCIONES:
|
|
60
|
+
- Rellena la plantilla con información relevante extraída de los commits y el ticket.
|
|
61
|
+
- Sé profesional pero directo.
|
|
62
|
+
- Si la plantilla tiene checkboxes [ ], marca los que apliquen con [x].
|
|
63
|
+
- NO inventes funcionalidades que no estén en los commits.
|
|
64
|
+
- Mantener el formato Markdown.
|
|
65
|
+
|
|
66
|
+
SALIDA:
|
|
67
|
+
Solo el contenido de la plantilla rellena.
|
|
68
|
+
`;
|
|
69
|
+
return prompt;
|
|
70
|
+
}
|