openprompt-lang 1.3.0 → 1.4.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/bin/cli.js +2 -0
- package/docs/00-ARCHITECTURE/OPL-BOOST-MULTI-AGENT.md +406 -0
- package/docs/02-STANDARDS/AGENTS.template.md +89 -0
- package/docs/02-STANDARDS/ticket-driven-development.md +99 -0
- package/docs/04-TICKETS/BOOST-001-profile-registry.md +66 -0
- package/docs/04-TICKETS/BOOST-002-context-compression.md +58 -0
- package/docs/04-TICKETS/BOOST-003-template-hydration.md +69 -0
- package/docs/04-TICKETS/BOOST-004-fewshot-engine.md +58 -0
- package/docs/04-TICKETS/BOOST-005-agent-pool.md +69 -0
- package/docs/04-TICKETS/BOOST-006-specialized-agents.md +53 -0
- package/docs/04-TICKETS/BOOST-007-validation-loop.md +56 -0
- package/docs/04-TICKETS/BOOST-008-orchestrator.md +71 -0
- package/docs/04-TICKETS/BOOST-009-cache-system.md +56 -0
- package/docs/04-TICKETS/BOOST-010-cli-mcp.md +67 -0
- package/docs/04-TICKETS/BOOST-011-self-learning.md +50 -0
- package/docs/04-TICKETS/BOOST-012-prompt-preamble.md +109 -0
- package/docs/04-TICKETS/BOOST-013-hydrator-duplicate-code.md +132 -0
- package/docs/04-TICKETS/BOOST-014-multiagent-missing-parts.md +87 -0
- package/docs/04-TICKETS/BOOST-015-skeleton-type-missing.md +76 -0
- package/docs/04-TICKETS/BOOST-016-output-path-duplicate.md +68 -0
- package/docs/04-TICKETS/INDEX.md +89 -0
- package/docs/04-TICKETS/_archive/BOOST-005-micro-tasking.md +67 -0
- package/docs/04-TICKETS/_archive/BOOST-006-validation-loop.md +66 -0
- package/docs/04-TICKETS/_archive/BOOST-007-progressive-pipeline.md +69 -0
- package/docs/04-TICKETS/_archive/BOOST-008-cli-mcp-integration.md +74 -0
- package/docs/AI_CONTEXT.md +16 -0
- package/package.json +3 -2
- package/src/boost/agent-pool.js +442 -0
- package/src/boost/agents/index.js +79 -0
- package/src/boost/cache.js +241 -0
- package/src/boost/context-compressor.js +354 -0
- package/src/boost/fewshot-retriever.js +332 -0
- package/src/boost/hardware-detector.js +486 -0
- package/src/boost/hydrator.js +398 -0
- package/src/boost/index.js +60 -0
- package/src/boost/orchestrator.js +615 -0
- package/src/boost/preamble.js +217 -0
- package/src/boost/profile-registry.js +264 -0
- package/src/boost/self-learn.js +247 -0
- package/src/boost/skeletons/component.skeleton.js +24 -0
- package/src/boost/skeletons/hook.skeleton.js +27 -0
- package/src/boost/skeletons/index.js +67 -0
- package/src/boost/skeletons/page.skeleton.js +22 -0
- package/src/boost/skeletons/service.skeleton.js +20 -0
- package/src/boost/skeletons/store.skeleton.js +18 -0
- package/src/boost/skeletons/type.skeleton.js +11 -0
- package/src/boost/task-dispatcher.js +142 -0
- package/src/boost/validation-loop.js +495 -0
- package/src/cli/commands-boost.js +394 -0
- package/src/mcp-refactor/handlers/boost.js +295 -0
- package/src/mcp-refactor/router.js +19 -0
- package/src/mcp-refactor/tools.js +113 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# BOOST-006 — Validation Feedback Loop
|
|
2
|
+
|
|
3
|
+
## Metadatos
|
|
4
|
+
|
|
5
|
+
| Campo | Valor |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| **ID** | BOOST-006 |
|
|
8
|
+
| **Título** | Validation Feedback Loop |
|
|
9
|
+
| **Épica** | Módulo OPL Boost |
|
|
10
|
+
| **Prioridad** | Alta |
|
|
11
|
+
| **Estado** | 🔴 Pendiente |
|
|
12
|
+
| **Depende de** | BOOST-005 |
|
|
13
|
+
| **Archivos** | `src/boost/validation-loop.js` |
|
|
14
|
+
|
|
15
|
+
## Descripción
|
|
16
|
+
|
|
17
|
+
Sistema post-generación que corre validación OPL y TypeScript sobre el código generado, y retroalimenta los errores específicos al modelo para que los corrija. Los modelos pequeños son significativamente mejores corrigiendo errores específicos que generando código correcto la primera vez.
|
|
18
|
+
|
|
19
|
+
Si tras N reintentos el código sigue fallando, escala hacia abajo la complejidad de la tarea.
|
|
20
|
+
|
|
21
|
+
## Criterios de Aceptación
|
|
22
|
+
|
|
23
|
+
### CA-1: Validación post-generación
|
|
24
|
+
- [ ] `validate(code, kind)` → ejecuta validación OPL sobre el código generado
|
|
25
|
+
- [ ] Detecta: errores de anotaciones, errores de tipo, errores de sintaxis, violaciones de @limit
|
|
26
|
+
- [ ] Retorna array de errores con: tipo, mensaje, línea, sugerencia de fix
|
|
27
|
+
|
|
28
|
+
### CA-2: Feedback loop
|
|
29
|
+
- [ ] `feedbackLoop(code, errors, profile)` → intenta corregir errores iterativamente
|
|
30
|
+
- [ ] Por cada error, genera un mensaje de feedback claro para el modelo
|
|
31
|
+
- [ ] El feedback dice exactamente qué está mal y da una sugerencia de cómo arreglarlo
|
|
32
|
+
- [ ] El modelo corrige y se re-valida
|
|
33
|
+
- [ ] Número de reintentos según perfil: small=3, medium=2, large=1
|
|
34
|
+
|
|
35
|
+
### CA-3: Escalado de complejidad
|
|
36
|
+
- [ ] Si tras 3 reintentos (small) el código sigue fallando, reduce complejidad
|
|
37
|
+
- [ ] Estrategias de escalado: simplificar lógica, usar skeleton más básico, dividir en más micro-tareas
|
|
38
|
+
- [ ] `escalateDown(task, attempt)` → devuelve versión simplificada de la tarea
|
|
39
|
+
- [ ] El escalado se registra como @learn-error para futuras sesiones
|
|
40
|
+
|
|
41
|
+
### CA-4: Integración con micro-tasker
|
|
42
|
+
- [ ] Cuando una micro-tarea individual falla la validación, se reintenta antes de pasar a la siguiente
|
|
43
|
+
- [ ] Si la micro-tarea falla consistentemente, el orquestador (BOOST-005) decide escalar
|
|
44
|
+
|
|
45
|
+
### CA-5: Reporte de calidad
|
|
46
|
+
- [ ] `generateReport(codeHistory, errors)` → genera reporte de cuántos intentos tomó, qué errores se corrigieron
|
|
47
|
+
- [ ] El reporte se puede incluir en la documentación de la sesión
|
|
48
|
+
|
|
49
|
+
### CA-6: Tests
|
|
50
|
+
- [ ] Test de detección de errores de anotaciones
|
|
51
|
+
- [ ] Test de feedback loop con mock de correcciones
|
|
52
|
+
- [ ] Test de escalado tras N reintentos fallidos
|
|
53
|
+
- [ ] Test de reporte de calidad
|
|
54
|
+
|
|
55
|
+
## Archivos a crear/modificar
|
|
56
|
+
|
|
57
|
+
| Archivo | Acción |
|
|
58
|
+
|---------|--------|
|
|
59
|
+
| `src/boost/validation-loop.js` | ➕ Crear |
|
|
60
|
+
|
|
61
|
+
## Notas técnicas
|
|
62
|
+
|
|
63
|
+
- La validación OPL se hace invocando `npx openprompt-lang validate` o usando el módulo de validación directamente
|
|
64
|
+
- El feedback loop necesita acceso al modelo para re-generar — pero en esta fase inicial, solo prepara los mensajes de feedback (la re-generación la hace quien llame al boost)
|
|
65
|
+
- El escalado de complejidad es determinista (no necesita IA): simplifica basado en reglas
|
|
66
|
+
- Los errores más comunes en modelos pequeños: @kind faltante, @limit excedido, tipos incorrectos
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# BOOST-007 — Progressive Disclosure Pipeline
|
|
2
|
+
|
|
3
|
+
## Metadatos
|
|
4
|
+
|
|
5
|
+
| Campo | Valor |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| **ID** | BOOST-007 |
|
|
8
|
+
| **Título** | Progressive Disclosure Pipeline |
|
|
9
|
+
| **Épica** | Módulo OPL Boost |
|
|
10
|
+
| **Prioridad** | Alta |
|
|
11
|
+
| **Estado** | 🔴 Pendiente |
|
|
12
|
+
| **Depende de** | BOOST-002, BOOST-006 |
|
|
13
|
+
| **Archivos** | `src/boost/progressive-pipeline.js`, `src/boost/index.js` |
|
|
14
|
+
|
|
15
|
+
## Descripción
|
|
16
|
+
|
|
17
|
+
Pipeline multi-etapa que orquesta todos los componentes Boost en un flujo coherente. Cada etapa expone gradualmente más complejidad al modelo, de modo que nunca ve el problema completo de golpe.
|
|
18
|
+
|
|
19
|
+
El `index.js` es el punto de entrada unificado del módulo Boost.
|
|
20
|
+
|
|
21
|
+
## Criterios de Aceptación
|
|
22
|
+
|
|
23
|
+
### CA-1: Pipeline multi-etapa
|
|
24
|
+
- [ ] **Stage 1 — Diseño**: el modelo define props, tipos, interface, contract sin implementar lógica
|
|
25
|
+
- [ ] **Stage 2 — Implementación**: el modelo rellena el skeleton con lógica de negocio
|
|
26
|
+
- [ ] **Stage 3 — Polish**: el modelo recibe feedback de validación y corrige errores
|
|
27
|
+
- [ ] Cada etapa es más específica que la anterior
|
|
28
|
+
|
|
29
|
+
### CA-2: Orchestrador unificado (index.js)
|
|
30
|
+
- [ ] `boost(task, options)` → orquesta todo el pipeline
|
|
31
|
+
- [ ] `options.profile` → perfil a usar
|
|
32
|
+
- [ ] `options.kind` → tipo de archivo a generar
|
|
33
|
+
- [ ] `options.dryRun` → mostrar plan sin ejecutar
|
|
34
|
+
- [ ] `options.output` → archivo de salida (opcional)
|
|
35
|
+
- [ ] Retorna: `{ code, metadata, report }`
|
|
36
|
+
|
|
37
|
+
### CA-3: Metadata de ejecución
|
|
38
|
+
- [ ] `{ profile, stages: [{name, duration, result}], totalTime, compressionRatio, validationAttempts }`
|
|
39
|
+
- [ ] Permite comparar rendimiento entre perfiles
|
|
40
|
+
- [ ] Se puede exportar como JSON
|
|
41
|
+
|
|
42
|
+
### CA-4: Modo dry-run
|
|
43
|
+
- [ ] `boost(task, { dryRun: true })` → muestra qué haría en cada etapa sin ejecutar
|
|
44
|
+
- [ ] Muestra: plan de micro-tareas, skeletons a usar, ejemplos a inyectar
|
|
45
|
+
|
|
46
|
+
### CA-5: Integración con compress
|
|
47
|
+
- [ ] Antes del Stage 1, comprime el contexto según perfil (usa BOOST-002)
|
|
48
|
+
- [ ] El Stage 1 recibe contexto mínimo
|
|
49
|
+
- [ ] Los Stages 2 y 3 pueden recibir contexto adicional si es necesario
|
|
50
|
+
|
|
51
|
+
### CA-6: Tests
|
|
52
|
+
- [ ] Test de pipeline completo con mock de modelo
|
|
53
|
+
- [ ] Test de metadata de ejecución
|
|
54
|
+
- [ ] Test de dry-run
|
|
55
|
+
- [ ] Test de integración: pipeline completo produce código válido
|
|
56
|
+
|
|
57
|
+
## Archivos a crear/modificar
|
|
58
|
+
|
|
59
|
+
| Archivo | Acción |
|
|
60
|
+
|---------|--------|
|
|
61
|
+
| `src/boost/index.js` | ➕ Crear (orquestador unificado) |
|
|
62
|
+
| `src/boost/progressive-pipeline.js` | ➕ Crear (pipeline multi-etapa) |
|
|
63
|
+
|
|
64
|
+
## Notas técnicas
|
|
65
|
+
|
|
66
|
+
- El index.js es el API público del módulo Boost
|
|
67
|
+
- Cada etapa del pipeline es un plugin: `pipeline.use(stage)` para futura extensibilidad
|
|
68
|
+
- El pipeline registra tiempo de cada etapa para diagnóstico
|
|
69
|
+
- Si una etapa falla, el pipeline detiene la ejecución y devuelve error con estado parcial
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# BOOST-008 — CLI + MCP Integration
|
|
2
|
+
|
|
3
|
+
## Metadatos
|
|
4
|
+
|
|
5
|
+
| Campo | Valor |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| **ID** | BOOST-008 |
|
|
8
|
+
| **Título** | CLI + MCP Integration |
|
|
9
|
+
| **Épica** | Módulo OPL Boost |
|
|
10
|
+
| **Prioridad** | Media |
|
|
11
|
+
| **Estado** | 🔴 Pendiente |
|
|
12
|
+
| **Depende de** | BOOST-007 |
|
|
13
|
+
| **Archivos** | `src/cli/commands-boost.js`, `src/mcp-server.js`, `src/mcp-refactor/router.js`, `AGENTS.md` |
|
|
14
|
+
|
|
15
|
+
## Descripción
|
|
16
|
+
|
|
17
|
+
Integrar el módulo Boost con la CLI de OPL (comandos `opl boost *`) y con el servidor MCP (tools `OPL_Boost_*`). También actualizar AGENTS.md con la sección Boost Workflow para que las IAs futuras sepan cómo usar el módulo.
|
|
18
|
+
|
|
19
|
+
## Criterios de Aceptación
|
|
20
|
+
|
|
21
|
+
### CA-1: CLI completa
|
|
22
|
+
- [ ] `opl boost check` → diagnóstico del perfil activo
|
|
23
|
+
- [ ] `opl boost profile <name>` → forzar perfil (small/medium/large/auto)
|
|
24
|
+
- [ ] `opl boost generate <desc>` → generar código con pipeline completo
|
|
25
|
+
- [ ] `opl boost microtask <task>` → descomponer tarea en micro-tareas
|
|
26
|
+
- [ ] Todos los comandos tienen `--help` descriptivo
|
|
27
|
+
- [ ] Todos los comandos tienen `--dry-run`
|
|
28
|
+
|
|
29
|
+
### CA-2: MCP tools
|
|
30
|
+
- [ ] `OPL_Boost_profile` → mostrar perfil actual del modelo
|
|
31
|
+
- [ ] `OPL_Boost_compress` → comprimir contexto según perfil
|
|
32
|
+
- [ ] `OPL_Boost_microtask` → ejecutar pipeline de micro-tasking
|
|
33
|
+
- [ ] `OPL_Boost_hydrate` → hidratar un skeleton con lógica
|
|
34
|
+
- [ ] `OPL_Boost_validate` → loop de validación con retroalimentación
|
|
35
|
+
- [ ] Las tools aparecen en el servidor MCP y son invocables
|
|
36
|
+
|
|
37
|
+
### CA-3: AGENTS.md actualizado
|
|
38
|
+
- [ ] Nueva sección "## 🚀 OPL Boost — Potenciar modelos pequeños" en AGENTS.md
|
|
39
|
+
- [ ] Describe: qué es, cuándo usarlo, cómo configurarlo
|
|
40
|
+
- [ ] Tabla de perfiles (small/medium/large)
|
|
41
|
+
- [ ] Workflow Boost para IAs que usan el módulo
|
|
42
|
+
- [ ] Referencia rápida de comandos `opl boost *`
|
|
43
|
+
|
|
44
|
+
### CA-4: Registrar en bin/cli.js
|
|
45
|
+
- [ ] `registerBoost(program)` llamado desde `bin/cli.js`
|
|
46
|
+
- [ ] Import y registro consistente con los otros comandos
|
|
47
|
+
|
|
48
|
+
### CA-5: Registrar en MCP server
|
|
49
|
+
- [ ] Tools de boost agregadas a TOOLS en `src/mcp-refactor/tools.js`
|
|
50
|
+
- [ ] Router en `src/mcp-refactor/router.js` maneja boost tools
|
|
51
|
+
- [ ] Workflow generator en `src/mcp-workflow.js` incluye boost
|
|
52
|
+
|
|
53
|
+
### CA-6: Tests de integración
|
|
54
|
+
- [ ] Test de CLI: `opl boost check` funciona sin errores
|
|
55
|
+
- [ ] Test de MCP: tools registradas correctamente
|
|
56
|
+
- [ ] Test que todos los comandos tienen `--help`
|
|
57
|
+
|
|
58
|
+
## Archivos a crear/modificar
|
|
59
|
+
|
|
60
|
+
| Archivo | Acción |
|
|
61
|
+
|---------|--------|
|
|
62
|
+
| `src/cli/commands-boost.js` | ➕ Crear (registro completo de comandos) |
|
|
63
|
+
| `bin/cli.js` | ✏️ Modificar (import + register) |
|
|
64
|
+
| `src/mcp-server.js` | ✏️ Modificar (boost tools en TOOLS) |
|
|
65
|
+
| `src/mcp-refactor/router.js` | ✏️ Modificar (boost routes) |
|
|
66
|
+
| `src/mcp-workflow.js` | ✏️ Modificar (boost instructions) |
|
|
67
|
+
| `AGENTS.md` | ✏️ Modificar (sección Boost) |
|
|
68
|
+
|
|
69
|
+
## Notas técnicas
|
|
70
|
+
|
|
71
|
+
- Los MCP tools deben seguir el patrón existente en `src/mcp-refactor/tools.js`
|
|
72
|
+
- Los comandos CLI deben seguir el patrón de commander (`.command().description().action()`)
|
|
73
|
+
- La sección de AGENTS.md debe seguir el tono y formato del documento existente
|
|
74
|
+
- No debe romper comandos existentes ni tools MCP existentes
|
package/docs/AI_CONTEXT.md
CHANGED
|
@@ -60,8 +60,24 @@ npx openPrompt-Lang validate # Validar anotaciones
|
|
|
60
60
|
npx openPrompt-Lang lang list # Listar módulos
|
|
61
61
|
npx openPrompt-Lang teach <id> # Aprender de un template
|
|
62
62
|
npx openPrompt-Lang qa-gen # Generar tests de regresión
|
|
63
|
+
|
|
64
|
+
# OPL Boost (multi-agente para modelos pequeños)
|
|
65
|
+
opl boost check # Diagnóstico perfil + estado del sistema
|
|
66
|
+
opl boost profile [name] # Ver/forzar perfil (small/medium/large/auto)
|
|
67
|
+
opl boost setup # Detectar hardware + configurar
|
|
68
|
+
opl boost generate <desc> # Generar código con pipeline Boost
|
|
69
|
+
opl boost microtask <task> # Descomponer tarea en DAG
|
|
70
|
+
opl boost cache [action] # Gestionar caché (stats, clear, clean)
|
|
63
71
|
```
|
|
64
72
|
|
|
73
|
+
## 5.5. Módulo OPL Boost
|
|
74
|
+
- **12 tickets implementados** (BOOST-001 a BOOST-012, todos completados)
|
|
75
|
+
- **Pipeline**: profile detection → context compression → few-shot → agent pool → skeleton hydration → validation loop
|
|
76
|
+
- **Dos modos**: single-pass (default) y multi-agent (`--multi-agent`)
|
|
77
|
+
- **Código**: `src/boost/` (17 archivos, ~3,700 líneas)
|
|
78
|
+
- **MCP tools**: `boost_generate`, `boost_compress`, `boost_profile`, `boost_plan`, `boost_validate`
|
|
79
|
+
- **Filosofía**: cada componente funciona independientemente; el multi-agente es opt-in
|
|
80
|
+
|
|
65
81
|
## 6. Referencia canónica
|
|
66
82
|
- `.openprompt/FRAMEWORK.md` — Manual completo: anotaciones, comandos CLI, MCP, dominios, módulos, reglas estrictas.
|
|
67
83
|
- `AGENTS.md` — Stack, convenciones, UI, calidad.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openprompt-lang",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "PromptLang CLI — Context Engine de anotaciones para desarrollo asistido por IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/cli.js",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"opencode:wizard": "openprompt-lang wizard",
|
|
56
56
|
"opencode:plan": "openprompt-lang plan",
|
|
57
57
|
"opencode:execute": "openprompt-lang execute",
|
|
58
|
-
"opencode:mode": "openprompt-lang mode"
|
|
58
|
+
"opencode:mode": "openprompt-lang mode",
|
|
59
|
+
"opl:enforce": "node scripts/opl-enforce-setup.mjs"
|
|
59
60
|
},
|
|
60
61
|
"keywords": [
|
|
61
62
|
"prompt-lang",
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// @use(kind, contract, limit, error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: prompt, role, profile -> out: AgentPool instance + callOllama, buildPrompt, checkOllama @error: OllamaConnectionError, AgentTimeoutError)
|
|
4
|
+
// @limit(lines: 200)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Agent Pool — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Conector a Ollama + pool de agentes para orquestación interna.
|
|
10
|
+
* Permite que OPL llame a modelos locales sin depender de OpenCode.
|
|
11
|
+
*
|
|
12
|
+
* Modo de uso:
|
|
13
|
+
* import { agentPool } from './agent-pool.js'
|
|
14
|
+
* const result = await agentPool.submit('types', 'hook useAuth')
|
|
15
|
+
*
|
|
16
|
+
* Modo cola (FIFO): default, una solicitud a la vez
|
|
17
|
+
* Modo paralelo: configurable via profile.agentPool.maxConcurrency
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { getProfile } from "./profile-registry.js"
|
|
21
|
+
|
|
22
|
+
// ──────────────────────────────────────────────
|
|
23
|
+
// Configuración
|
|
24
|
+
// ──────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434"
|
|
27
|
+
const DEFAULT_TIMEOUT = 30_000 // 30s
|
|
28
|
+
|
|
29
|
+
// ──────────────────────────────────────────────
|
|
30
|
+
// System prompts por rol de agente
|
|
31
|
+
// ──────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const AGENT_SYSTEM_PROMPTS = {
|
|
34
|
+
types:
|
|
35
|
+
"Eres un experto en TypeScript. Tu única tarea es definir TYPES e INTERFACES. " +
|
|
36
|
+
"No implementes lógica. No generes JSX. Solo tipos, interfaces, y anotaciones de tipo. " +
|
|
37
|
+
"Output exacto: bloques de código TypeScript con export.",
|
|
38
|
+
|
|
39
|
+
state:
|
|
40
|
+
"Eres un experto en React state management. Recibes types ya definidos. " +
|
|
41
|
+
"Tu tarea es diseñar el ESTADO del componente/hook: useState, useReducer, useEffect. " +
|
|
42
|
+
"No implementes lógica de negocio. Solo estructura de estado y efectos. " +
|
|
43
|
+
"Output exacto: bloques de código con hooks de React.",
|
|
44
|
+
|
|
45
|
+
logic:
|
|
46
|
+
"Eres un experto en implementar lógica de negocio. Recibes types + estado ya definidos. " +
|
|
47
|
+
"Tu tarea es IMPLEMENTAR las funciones con lógica real. " +
|
|
48
|
+
"Usa los types y estado existentes. No los redefinas. " +
|
|
49
|
+
"Output exacto: la función/componente completo con implementación.",
|
|
50
|
+
|
|
51
|
+
cleaner:
|
|
52
|
+
"Eres un compresor de contexto para desarrollo asistido por IA. " +
|
|
53
|
+
"Recibes reglas extensas y debes devolver SOLO las reglas relevantes para la tarea específica. " +
|
|
54
|
+
"Prioriza: límites de líneas (@kind + @limit), reglas de enforcement, prohibiciones (NUNCA). " +
|
|
55
|
+
"Omite: stack, UI, colores, documentación de comandos no relevantes. " +
|
|
56
|
+
"Output exacto: markdown compacto con las reglas esenciales.",
|
|
57
|
+
|
|
58
|
+
validate:
|
|
59
|
+
"Eres un corrector de código. Recibes código que falló validación y los errores específicos. " +
|
|
60
|
+
"Tu tarea es CORREGIR solo los errores reportados, no reescribir todo. " +
|
|
61
|
+
"Conserva la estructura y lógica original. Arregla solo lo que falló. " +
|
|
62
|
+
"Output exacto: el código completo corregido.",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ──────────────────────────────────────────────
|
|
66
|
+
// Builders de prompts
|
|
67
|
+
// ──────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
const PROMPT_TEMPLATES = {
|
|
70
|
+
types: (input) =>
|
|
71
|
+
`Task: Define types for "${input}"
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
- Export all interfaces
|
|
75
|
+
- Use TypeScript strict mode
|
|
76
|
+
- Add JSDoc comments
|
|
77
|
+
- Maximum 60 lines
|
|
78
|
+
|
|
79
|
+
Output ONLY TypeScript types.`,
|
|
80
|
+
|
|
81
|
+
state: (input) =>
|
|
82
|
+
`Task: Design state management
|
|
83
|
+
|
|
84
|
+
Context: ${input}
|
|
85
|
+
|
|
86
|
+
Rules:
|
|
87
|
+
- Use React hooks (useState, useEffect, useCallback)
|
|
88
|
+
- Reference existing types
|
|
89
|
+
- No business logic
|
|
90
|
+
- Maximum 60 lines
|
|
91
|
+
|
|
92
|
+
Output ONLY React hooks and state.`,
|
|
93
|
+
|
|
94
|
+
logic: (input) =>
|
|
95
|
+
`Task: Implement business logic
|
|
96
|
+
|
|
97
|
+
Context: ${input}
|
|
98
|
+
|
|
99
|
+
Rules:
|
|
100
|
+
- Use existing types and state
|
|
101
|
+
- Implement full logic
|
|
102
|
+
- Handle errors and edge cases
|
|
103
|
+
- Maximum 80 lines
|
|
104
|
+
|
|
105
|
+
Output ONLY the implementation.`,
|
|
106
|
+
|
|
107
|
+
cleaner: (input) =>
|
|
108
|
+
`Task: Compress context for code generation
|
|
109
|
+
|
|
110
|
+
Target task: ${input.substring(0, 200)}
|
|
111
|
+
|
|
112
|
+
Rules to preserve (if present in context):
|
|
113
|
+
- @kind limits (hook:80, component:120, page:200, service:150, store:100, util:100)
|
|
114
|
+
- @limit blocks commits, NOT a suggestion
|
|
115
|
+
- @use() required at file top
|
|
116
|
+
- Named exports over default
|
|
117
|
+
- NUNCA rules (any, template files, etc.)
|
|
118
|
+
- Annotations first, implementation second
|
|
119
|
+
|
|
120
|
+
Compress to <200 lines. Preserve critical rules only.`,
|
|
121
|
+
|
|
122
|
+
validate: (input) =>
|
|
123
|
+
`Task: Fix validation errors
|
|
124
|
+
|
|
125
|
+
Code with errors:
|
|
126
|
+
\`\`\`
|
|
127
|
+
${input.substring(0, 1500)}
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
Fix ONLY the reported errors. Do not rewrite.
|
|
131
|
+
Output the ENTIRE corrected file.`,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function buildPrompt(role, input) {
|
|
135
|
+
const systemPrompt = AGENT_SYSTEM_PROMPTS[role]
|
|
136
|
+
const template = PROMPT_TEMPLATES[role]
|
|
137
|
+
|
|
138
|
+
if (!systemPrompt || !template) {
|
|
139
|
+
throw new Error(`Rol de agente desconocido: "${role}". Roles: ${Object.keys(AGENT_SYSTEM_PROMPTS).join(", ")}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
system: systemPrompt,
|
|
144
|
+
prompt: template(input),
|
|
145
|
+
role,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ──────────────────────────────────────────────
|
|
150
|
+
// Ollama connector
|
|
151
|
+
// ──────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export class OllamaConnectionError extends Error {
|
|
154
|
+
constructor(message, cause) {
|
|
155
|
+
super(message)
|
|
156
|
+
this.name = "OllamaConnectionError"
|
|
157
|
+
this.cause = cause
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class AgentTimeoutError extends Error {
|
|
162
|
+
constructor(role, timeout) {
|
|
163
|
+
super(`Agent "${role}" timed out after ${timeout}ms`)
|
|
164
|
+
this.name = "AgentTimeoutError"
|
|
165
|
+
this.role = role
|
|
166
|
+
this.timeout = timeout
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Cache de modelo seleccionado para no re-detectar en cada llamada
|
|
171
|
+
let _bestModelCache = null
|
|
172
|
+
|
|
173
|
+
export async function callOllama(promptText, options = {}) {
|
|
174
|
+
const {
|
|
175
|
+
model = _bestModelCache || "llama3.2:latest", // default seguro siempre disponible
|
|
176
|
+
system = "",
|
|
177
|
+
temperature = 0.2,
|
|
178
|
+
maxTokens = 4096,
|
|
179
|
+
timeout = DEFAULT_TIMEOUT,
|
|
180
|
+
} = options
|
|
181
|
+
|
|
182
|
+
const controller = new AbortController()
|
|
183
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const body = {
|
|
187
|
+
model,
|
|
188
|
+
prompt: promptText,
|
|
189
|
+
system,
|
|
190
|
+
stream: false,
|
|
191
|
+
options: {
|
|
192
|
+
temperature,
|
|
193
|
+
num_predict: maxTokens,
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const response = await fetch(`${OLLAMA_HOST}/api/generate`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { "Content-Type": "application/json" },
|
|
200
|
+
body: JSON.stringify(body),
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new OllamaConnectionError(
|
|
206
|
+
`Ollama responded with ${response.status}: ${response.statusText}`
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const data = await response.json()
|
|
211
|
+
|
|
212
|
+
if (data.error) {
|
|
213
|
+
throw new OllamaConnectionError(`Ollama error: ${data.error}`)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
text: data.response || "",
|
|
218
|
+
totalDuration: data.total_duration || 0,
|
|
219
|
+
tokensPerSecond: data.tokens_per_second || 0,
|
|
220
|
+
tokenCount: (data.tokens_evaluated || 0) + (data.prompt_eval_count || 0),
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
if (err.name === "AbortError") {
|
|
224
|
+
throw new AgentTimeoutError(options.role || "unknown", timeout)
|
|
225
|
+
}
|
|
226
|
+
if (err instanceof OllamaConnectionError || err instanceof AgentTimeoutError) {
|
|
227
|
+
throw err
|
|
228
|
+
}
|
|
229
|
+
throw new OllamaConnectionError(
|
|
230
|
+
`Failed to connect to Ollama at ${OLLAMA_HOST}: ${err.message}`,
|
|
231
|
+
err
|
|
232
|
+
)
|
|
233
|
+
} finally {
|
|
234
|
+
clearTimeout(timeoutId)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ──────────────────────────────────────────────
|
|
239
|
+
// Pool de agentes (cola FIFO)
|
|
240
|
+
// ──────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
class AgentPool {
|
|
243
|
+
constructor() {
|
|
244
|
+
this._queue = []
|
|
245
|
+
this._running = false
|
|
246
|
+
this._currentTask = null
|
|
247
|
+
this._stats = { completed: 0, failed: 0, totalTime: 0 }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Encola una solicitud de agente y retorna el resultado.
|
|
252
|
+
* Antes de encolar, verifica el estado del sistema (RAM, CPU).
|
|
253
|
+
*
|
|
254
|
+
* @param {string} role - Rol del agente (types, state, logic, cleaner, validate)
|
|
255
|
+
* @param {string} input - Input para el agente
|
|
256
|
+
* @param {object} [profile] - Perfil Boost (opcional, auto-detecta si se omite)
|
|
257
|
+
* @returns {Promise<{text: string, role: string, duration: number, metrics: object}>}
|
|
258
|
+
*/
|
|
259
|
+
async submit(role, input, profile) {
|
|
260
|
+
const prof = profile || getProfile()
|
|
261
|
+
const agentConfig = prof?.agentPool || {}
|
|
262
|
+
|
|
263
|
+
// Verificar estado del sistema antes de encolar
|
|
264
|
+
const { getSafeParallelism, getRAMWarning } = await import("./hardware-detector.js")
|
|
265
|
+
const safety = getSafeParallelism(prof)
|
|
266
|
+
|
|
267
|
+
if (!safety.safe) {
|
|
268
|
+
const warning = getRAMWarning()
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Boost bloqueado por recursos insuficientes.\n${warning}\n` +
|
|
271
|
+
`→ Libera memoria y vuelve a intentarlo.`
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
this._queue.push({
|
|
277
|
+
role,
|
|
278
|
+
input,
|
|
279
|
+
profile: prof,
|
|
280
|
+
agentConfig,
|
|
281
|
+
safety, // guardamos la recomendación de seguridad
|
|
282
|
+
resolve,
|
|
283
|
+
reject,
|
|
284
|
+
})
|
|
285
|
+
this._processQueue()
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async _processQueue() {
|
|
290
|
+
if (this._running || this._queue.length === 0) return
|
|
291
|
+
this._running = true
|
|
292
|
+
|
|
293
|
+
while (this._queue.length > 0) {
|
|
294
|
+
const task = this._queue.shift()
|
|
295
|
+
this._currentTask = task
|
|
296
|
+
const startTime = Date.now()
|
|
297
|
+
|
|
298
|
+
// Re-verificar sistema antes de CADA agente (por si cambió)
|
|
299
|
+
try {
|
|
300
|
+
const { getSafeParallelism, getRAMWarning } = await import("./hardware-detector.js")
|
|
301
|
+
const currentSafety = getSafeParallelism(task.profile)
|
|
302
|
+
|
|
303
|
+
if (!currentSafety.safe) {
|
|
304
|
+
// Si el sistema se saturó mientras esperaba, re-encolar
|
|
305
|
+
const warning = getRAMWarning()
|
|
306
|
+
console.warn(`\n ⏸ Agente "${task.role}" pausado — ${warning}`)
|
|
307
|
+
// Esperar 5 segundos y re-intentar
|
|
308
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
309
|
+
this._queue.unshift(task) // re-encolar al principio
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Si hay demasiados agentes en paralelo y estamos en modo cola, esperar
|
|
314
|
+
if (currentSafety.mode === "queue" && task.safety?.mode === "parallel") {
|
|
315
|
+
// La situación cambió: degradar a cola automáticamente
|
|
316
|
+
console.warn(`\n ⏸ Cambiando a modo cola por carga del sistema.`)
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
// Si falla la verificación, continuar de todas formas
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const prompt = buildPrompt(task.role, task.input)
|
|
324
|
+
const modelName =
|
|
325
|
+
task.agentConfig.modelName || task.profile?.agentPool?.modelName || _bestModelCache || await selectBestModel(task.profile) || "llama3.2:latest"
|
|
326
|
+
|
|
327
|
+
// Estimar RAM que usará el modelo
|
|
328
|
+
const { estimateModelRAM } = await import("./hardware-detector.js")
|
|
329
|
+
const estimatedRAM = estimateModelRAM(modelName)
|
|
330
|
+
|
|
331
|
+
const result = await callOllama(prompt.prompt, {
|
|
332
|
+
model: modelName,
|
|
333
|
+
system: prompt.system,
|
|
334
|
+
temperature: task.agentConfig.temperature || 0.2,
|
|
335
|
+
timeout: task.agentConfig.timeout || DEFAULT_TIMEOUT,
|
|
336
|
+
role: task.role,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const duration = Date.now() - startTime
|
|
340
|
+
this._stats.completed++
|
|
341
|
+
this._stats.totalTime += duration
|
|
342
|
+
|
|
343
|
+
task.resolve({
|
|
344
|
+
text: result.text.trim(),
|
|
345
|
+
role: task.role,
|
|
346
|
+
duration,
|
|
347
|
+
metrics: {
|
|
348
|
+
tokensPerSecond: result.tokensPerSecond,
|
|
349
|
+
tokenCount: result.tokenCount,
|
|
350
|
+
model: modelName,
|
|
351
|
+
estimatedRAMGB: estimatedRAM,
|
|
352
|
+
},
|
|
353
|
+
})
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this._stats.failed++
|
|
356
|
+
task.reject(err)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this._running = false
|
|
361
|
+
this._currentTask = null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
get stats() {
|
|
365
|
+
return { ...this._stats, queueLength: this._queue.length, isRunning: this._running }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async clear() {
|
|
369
|
+
this._queue = []
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Singleton
|
|
374
|
+
export const agentPool = new AgentPool()
|
|
375
|
+
|
|
376
|
+
// ──────────────────────────────────────────────
|
|
377
|
+
// Utilidades de detección
|
|
378
|
+
// ──────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
export async function checkOllama() {
|
|
381
|
+
try {
|
|
382
|
+
const response = await fetch(`${OLLAMA_HOST}/api/tags`, {
|
|
383
|
+
signal: AbortSignal.timeout(5000),
|
|
384
|
+
})
|
|
385
|
+
return response.ok
|
|
386
|
+
} catch {
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export async function listOllamaModels() {
|
|
392
|
+
try {
|
|
393
|
+
const response = await fetch(`${OLLAMA_HOST}/api/tags`, {
|
|
394
|
+
signal: AbortSignal.timeout(5000),
|
|
395
|
+
})
|
|
396
|
+
if (!response.ok) return []
|
|
397
|
+
const data = await response.json()
|
|
398
|
+
return (data.models || []).map((m) => ({
|
|
399
|
+
name: m.name,
|
|
400
|
+
size: m.size,
|
|
401
|
+
modifiedAt: m.modified_at,
|
|
402
|
+
}))
|
|
403
|
+
} catch {
|
|
404
|
+
return []
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export async function selectBestModel(profile) {
|
|
409
|
+
const models = await listOllamaModels()
|
|
410
|
+
if (models.length === 0) return null
|
|
411
|
+
|
|
412
|
+
const profileName = profile?.name || "medium"
|
|
413
|
+
|
|
414
|
+
// Preferencias según perfil — mapeadas a modelos que existen en esta instancia
|
|
415
|
+
const preferences = {
|
|
416
|
+
small: ["qwen2.5-coder:7b", "qwen2.5-coder", "llama3.2", "llama3.2:latest", "phi", "deepseek-coder"],
|
|
417
|
+
medium: ["qwen2.5-coder:7b", "qwen2.5-coder", "codellama", "mistral", "llama3"],
|
|
418
|
+
large: ["qwen2.5-72b", "llama-3-70b", "llama3:latest", "qwen2.5-coder:7b"],
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const preferred = preferences[profileName] || preferences.medium
|
|
422
|
+
|
|
423
|
+
// Buscar el mejor modelo disponible según preferencias
|
|
424
|
+
for (const pref of preferred) {
|
|
425
|
+
const match = models.find((m) => m.name === pref || m.name.toLowerCase().includes(pref))
|
|
426
|
+
if (match) {
|
|
427
|
+
_bestModelCache = match.name
|
|
428
|
+
return match.name
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Fallback: primer modelo disponible (excluir embeddings)
|
|
433
|
+
const nonEmbedding = models.find((m) => !m.name.includes("nomic") && !m.name.includes("embed"))
|
|
434
|
+
if (nonEmbedding) {
|
|
435
|
+
_bestModelCache = nonEmbedding.name
|
|
436
|
+
return nonEmbedding.name
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Último recurso
|
|
440
|
+
_bestModelCache = models[0].name
|
|
441
|
+
return models[0].name
|
|
442
|
+
}
|