openprompt-lang 1.2.7 → 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/README.md +62 -8
- 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/docs/EMBEDDINGS.md +214 -0
- package/docs/ONBOARDING_WORKFLOW.md +151 -0
- package/docs/OPL_ACADEMIC_ISSUES.md +158 -0
- package/docs/WEB_SCRAPER_PLAN.md +454 -0
- package/package.json +9 -2
- package/scripts/postinstall.js +37 -0
- 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/cli/commands-knowledge.js +1 -0
- package/src/cli/commands-opl.js +79 -1
- package/src/cli/commands-workflow.js +125 -6
- package/src/commands/init-core.js +169 -5
- package/src/commands/knowledge-ops.js +52 -0
- package/src/commands/opl-embeddings.js +556 -0
- package/src/commands/opl-help.js +26 -2
- package/src/commands/opl-search.js +106 -2
- package/src/commands/opl-webscrape.js +390 -0
- package/src/commands/workflow/epic-cli.js +192 -0
- package/src/commands/workflow/select.js +146 -0
- package/src/commands/workflow/sprint-cli.js +174 -0
- package/src/core/webscrape/analyzer.js +481 -0
- package/src/core/webscrape/deep-scraper.js +1027 -0
- package/src/core/workflow/epic-manager.js +845 -0
- package/src/core/workflow/gates.js +180 -1
- package/src/core/workflow/selector.js +707 -0
- package/src/embeddings/chunker.js +450 -0
- package/src/embeddings/embedder.js +431 -0
- package/src/embeddings/index-pipeline.js +320 -0
- package/src/embeddings/vector-store.js +505 -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,217 @@
|
|
|
1
|
+
// @use(kind, contract, limit, learn-error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: options -> out: preamble text)
|
|
4
|
+
// @limit(lines: 150)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Universal Workflow Preamble — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Genera un mensaje de sistema que se inyecta al inicio de cada
|
|
10
|
+
* interacción con la IA, forzando que siga el workflow OPL.
|
|
11
|
+
*
|
|
12
|
+
* Funciona de 3 formas:
|
|
13
|
+
* 1. Archivo: genera .opencode/preamble.md que el AI client lee
|
|
14
|
+
* 2. System prompt: genera texto para inyectar como systemPrompt
|
|
15
|
+
* 3. CLI: `opl boost preamble` muestra el preamble actual
|
|
16
|
+
*
|
|
17
|
+
* El preamble es dinámico: se regenera según el estado del proyecto
|
|
18
|
+
* (tickets abiertos, perfil activo, learn-errors recientes).
|
|
19
|
+
*
|
|
20
|
+
* Uso:
|
|
21
|
+
* import { generatePreamble, writePreamble } from './boost/preamble.js'
|
|
22
|
+
* const text = generatePreamble()
|
|
23
|
+
* writePreamble()
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs"
|
|
27
|
+
import { join } from "path"
|
|
28
|
+
|
|
29
|
+
// ──────────────────────────────────────────────
|
|
30
|
+
// Configuración
|
|
31
|
+
// ──────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const PREAMBLE_PATH = join(process.cwd(), ".opencode", "boost-preamble.md")
|
|
34
|
+
const DEFAULT_MAX_WORDS = 300
|
|
35
|
+
|
|
36
|
+
// ──────────────────────────────────────────────
|
|
37
|
+
// Preamble generation
|
|
38
|
+
// ──────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Genera el preamble de workflow obligatorio.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} [options] - Opciones
|
|
44
|
+
* @param {number} [options.maxWords=300] - Máximo de palabras
|
|
45
|
+
* @param {boolean} [options.includeProfile=true] - Incluir info del perfil activo
|
|
46
|
+
* @param {boolean} [options.includeTickets=true] - Incluir tickets abiertos
|
|
47
|
+
* @param {boolean} [options.includeErrors=true] - Incluir learn-errors recientes
|
|
48
|
+
* @returns {string} Texto del preamble en markdown
|
|
49
|
+
*/
|
|
50
|
+
export async function generatePreamble(options = {}) {
|
|
51
|
+
const maxWords = options.maxWords || DEFAULT_MAX_WORDS
|
|
52
|
+
const includeProfile = options.includeProfile !== false
|
|
53
|
+
const includeTickets = options.includeTickets !== false
|
|
54
|
+
const includeErrors = options.includeErrors !== false
|
|
55
|
+
|
|
56
|
+
const sections = []
|
|
57
|
+
|
|
58
|
+
// ── Header ──
|
|
59
|
+
sections.push("## ⚠️ OPL Workflow Obligatorio")
|
|
60
|
+
sections.push("")
|
|
61
|
+
sections.push("Antes de implementar cualquier cosa, DEBES seguir este orden:")
|
|
62
|
+
sections.push("")
|
|
63
|
+
sections.push("1. `workflow select <descripción>` — Seleccionar workflow")
|
|
64
|
+
sections.push("2. `ticket create` — Crear ticket (sin ticket no hay código)")
|
|
65
|
+
sections.push("3. `check gates` — Verificar prerrequisitos")
|
|
66
|
+
sections.push("4. `work_context_plan` — Planificar (features/refactors)")
|
|
67
|
+
sections.push("5. Implementar con anotaciones OPL primero")
|
|
68
|
+
sections.push("6. `npx openprompt-lang validate` — Validar")
|
|
69
|
+
sections.push("7. Actualizar documentación")
|
|
70
|
+
sections.push("8. `work_context_close` — Cerrar sesión")
|
|
71
|
+
sections.push("")
|
|
72
|
+
|
|
73
|
+
// ── Prohibiciones ──
|
|
74
|
+
sections.push("### ⛔ Prohibido")
|
|
75
|
+
sections.push("")
|
|
76
|
+
sections.push("- Implementar sin ticket abierto")
|
|
77
|
+
sections.push("- Cerrar sesión sin documentación actualizada")
|
|
78
|
+
sections.push("- Mutar código sin `analyze_blast_radius` primero")
|
|
79
|
+
sections.push("- Modificar archivos `*.template.*`")
|
|
80
|
+
sections.push("- Usar `any` en TypeScript")
|
|
81
|
+
sections.push("- Exceder `@limit` sin refactorizar")
|
|
82
|
+
sections.push("")
|
|
83
|
+
|
|
84
|
+
// ── Perfil Boost ──
|
|
85
|
+
if (includeProfile) {
|
|
86
|
+
try {
|
|
87
|
+
const { getProfile, isBoostEnabled } = await import("./profile-registry.js")
|
|
88
|
+
if (isBoostEnabled()) {
|
|
89
|
+
const profile = getProfile()
|
|
90
|
+
if (profile) {
|
|
91
|
+
sections.push("### 🚀 Boost Activo")
|
|
92
|
+
sections.push("")
|
|
93
|
+
sections.push(`- Perfil: **${profile.name}** (${profile.label})`)
|
|
94
|
+
sections.push(`- Compresión: ${profile.compressionLevel}% | Validación: ${profile.validationLoops} loops`)
|
|
95
|
+
sections.push(`- Micro-tasking: ${profile.microTasks ? "Sí" : "No"} | Few-shot: ${profile.fewShot ? "Sí" : "No"}`)
|
|
96
|
+
sections.push("")
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Profile no disponible — omitir sección
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Tickets abiertos ──
|
|
105
|
+
if (includeTickets) {
|
|
106
|
+
const ticketCount = countOpenTickets()
|
|
107
|
+
if (ticketCount > 0) {
|
|
108
|
+
sections.push(`### 📋 Tickets Abiertos: ${ticketCount}`)
|
|
109
|
+
sections.push("")
|
|
110
|
+
sections.push("Antes de crear uno nuevo, verifica si ya existe un ticket relacionado.")
|
|
111
|
+
sections.push("")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── learn-errors recientes ──
|
|
116
|
+
if (includeErrors) {
|
|
117
|
+
const recentErrors = getRecentLearnErrors()
|
|
118
|
+
if (recentErrors.length > 0) {
|
|
119
|
+
sections.push("### 🧠 Learn-Errors Recientes")
|
|
120
|
+
sections.push("")
|
|
121
|
+
for (const err of recentErrors.slice(0, 3)) {
|
|
122
|
+
sections.push(`- **${err.id}**: ${err.fix}`)
|
|
123
|
+
}
|
|
124
|
+
sections.push("")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Límites de líneas ──
|
|
129
|
+
sections.push("### 📐 Límites @limit")
|
|
130
|
+
sections.push("")
|
|
131
|
+
sections.push("| @kind | Líneas | | @kind | Líneas |")
|
|
132
|
+
sections.push("|-------|--------|--|-------|--------|")
|
|
133
|
+
sections.push("| hook | 80 | | service | 150 |")
|
|
134
|
+
sections.push("| component | 120 | | store | 100 |")
|
|
135
|
+
sections.push("| page | 200 | | util | 100 |")
|
|
136
|
+
sections.push("")
|
|
137
|
+
|
|
138
|
+
// ── Truncar a maxWords ──
|
|
139
|
+
let result = sections.join("\n")
|
|
140
|
+
const words = result.split(/\s+/).length
|
|
141
|
+
if (words > maxWords) {
|
|
142
|
+
// Truncar por líneas completas
|
|
143
|
+
const lines = result.split("\n")
|
|
144
|
+
let count = 0
|
|
145
|
+
const truncated = []
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
count += line.split(/\s+/).length
|
|
148
|
+
if (count > maxWords) break
|
|
149
|
+
truncated.push(line)
|
|
150
|
+
}
|
|
151
|
+
result = truncated.join("\n") + "\n\n> Preamble truncado a ~300 palabras."
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Escribe el preamble a un archivo en el proyecto.
|
|
159
|
+
*
|
|
160
|
+
* @param {object} [options] - Opciones para generatePreamble
|
|
161
|
+
* @returns {string} Ruta del archivo escrito
|
|
162
|
+
*/
|
|
163
|
+
export function writePreamble(options = {}) {
|
|
164
|
+
const content = generatePreamble(options)
|
|
165
|
+
const dir = join(process.cwd(), ".opencode")
|
|
166
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
167
|
+
writeFileSync(PREAMBLE_PATH, content, "utf-8")
|
|
168
|
+
return PREAMBLE_PATH
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ──────────────────────────────────────────────
|
|
172
|
+
// Helpers (lector de estado del proyecto)
|
|
173
|
+
// ──────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
function countOpenTickets() {
|
|
176
|
+
const ticketsDir = join(process.cwd(), "docs", "04-TICKETS")
|
|
177
|
+
if (!existsSync(ticketsDir)) return 0
|
|
178
|
+
try {
|
|
179
|
+
const files = readdirSync(ticketsDir).filter((f) => f.endsWith(".md") && f.startsWith("BOOST-"))
|
|
180
|
+
let open = 0
|
|
181
|
+
for (const f of files) {
|
|
182
|
+
const content = readFileSync(join(ticketsDir, f), "utf-8")
|
|
183
|
+
if (content.includes("🔴 Pendiente") || content.includes("🟡 Parcial")) open++
|
|
184
|
+
}
|
|
185
|
+
return open
|
|
186
|
+
} catch {
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getRecentLearnErrors() {
|
|
192
|
+
const errors = []
|
|
193
|
+
const srcDir = join(process.cwd(), "src")
|
|
194
|
+
if (!existsSync(srcDir)) return errors
|
|
195
|
+
try {
|
|
196
|
+
const files = readdirSync(srcDir, { recursive: true }).filter((f) => {
|
|
197
|
+
return typeof f === "string" && (f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx"))
|
|
198
|
+
})
|
|
199
|
+
for (const f of files.slice(0, 50)) {
|
|
200
|
+
const fullPath = typeof f === "string" ? join(srcDir, f) : f
|
|
201
|
+
if (!existsSync(fullPath)) continue
|
|
202
|
+
try {
|
|
203
|
+
const content = readFileSync(fullPath, "utf-8")
|
|
204
|
+
const lines = content.split("\n")
|
|
205
|
+
for (const line of lines) {
|
|
206
|
+
const learnErrorTag = "@learn-error"
|
|
207
|
+
const pattern = new RegExp(learnErrorTag + "\\s+id=(\\S+)\\s+.*fix='([^']+)'")
|
|
208
|
+
const match = line.match(pattern)
|
|
209
|
+
if (match) {
|
|
210
|
+
errors.push({ id: match[1], fix: match[2] })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch { /* ignore */ }
|
|
214
|
+
}
|
|
215
|
+
} catch { /* ignore */ }
|
|
216
|
+
return errors
|
|
217
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// @use(kind, contract, limit)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: none -> out: PROFILES, getProfile, setProfile, detectProfile, listProfiles)
|
|
4
|
+
// @limit(lines: 150)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Profile Registry — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Sistema de perfiles para modelos de IA.
|
|
10
|
+
* Detecta automáticamente la capacidad del modelo y configura
|
|
11
|
+
* las estrategias de boost según el perfil (small/medium/large).
|
|
12
|
+
*
|
|
13
|
+
* Uso:
|
|
14
|
+
* import { getProfile } from './boost/profile-registry.js'
|
|
15
|
+
* const profile = getProfile() // { name, compressionLevel, validationLoops, ... }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs"
|
|
19
|
+
import { join, dirname } from "path"
|
|
20
|
+
|
|
21
|
+
function getStatePath() {
|
|
22
|
+
return join(process.cwd(), ".opencode", "boost-profile.json")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readState() {
|
|
26
|
+
const path = getStatePath()
|
|
27
|
+
if (!existsSync(path)) return null
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(path, "utf-8"))
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeState(state) {
|
|
36
|
+
const path = getStatePath()
|
|
37
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
38
|
+
writeFileSync(path, JSON.stringify(state, null, 2), "utf-8")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function clearState() {
|
|
42
|
+
const path = getStatePath()
|
|
43
|
+
if (existsSync(path)) rmSync(path)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ──────────────────────────────────────────────
|
|
47
|
+
// Perfiles predefinidos
|
|
48
|
+
// ──────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export const PROFILES = {
|
|
51
|
+
small: {
|
|
52
|
+
label: "Small Model",
|
|
53
|
+
description: "Modelos con <16K contexto (DeepSeek Coder, CodeGemma, Phi-3)",
|
|
54
|
+
contextWindow: 8000,
|
|
55
|
+
compressionLevel: 80, // % de reducción de contexto
|
|
56
|
+
validationLoops: 3, // reintentos de validación
|
|
57
|
+
microTasks: true, // usar micro-tasking
|
|
58
|
+
templateHydration: true, // usar skeletons
|
|
59
|
+
fewShot: true, // inyectar ejemplos
|
|
60
|
+
contextWindowMin: 0,
|
|
61
|
+
contextWindowMax: 16000,
|
|
62
|
+
},
|
|
63
|
+
medium: {
|
|
64
|
+
label: "Medium Model",
|
|
65
|
+
description: "Modelos con 16K-64K contexto (Qwen 2.5 Coder, Llama 3, Mistral)",
|
|
66
|
+
contextWindow: 32000,
|
|
67
|
+
compressionLevel: 50,
|
|
68
|
+
validationLoops: 2,
|
|
69
|
+
microTasks: true,
|
|
70
|
+
templateHydration: true,
|
|
71
|
+
fewShot: true,
|
|
72
|
+
contextWindowMin: 16000,
|
|
73
|
+
contextWindowMax: 64000,
|
|
74
|
+
},
|
|
75
|
+
large: {
|
|
76
|
+
label: "Large Model",
|
|
77
|
+
description: "Modelos con >64K contexto (GPT-4, Claude 3, Gemini Pro)",
|
|
78
|
+
contextWindow: 128000,
|
|
79
|
+
compressionLevel: 0,
|
|
80
|
+
validationLoops: 1,
|
|
81
|
+
microTasks: false,
|
|
82
|
+
templateHydration: false,
|
|
83
|
+
fewShot: false,
|
|
84
|
+
contextWindowMin: 64000,
|
|
85
|
+
contextWindowMax: Infinity,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ──────────────────────────────────────────────
|
|
90
|
+
// Estado interno (cache)
|
|
91
|
+
// ──────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
let _cachedProfile = null
|
|
94
|
+
let _forcedProfile = null
|
|
95
|
+
|
|
96
|
+
// ──────────────────────────────────────────────
|
|
97
|
+
// Detección de perfil
|
|
98
|
+
// ──────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Mapeo de nombres de modelo conocidos a perfiles.
|
|
102
|
+
*/
|
|
103
|
+
const KNOWN_MODELS = {
|
|
104
|
+
// Small
|
|
105
|
+
"deepseek-coder": "small",
|
|
106
|
+
"deepseek-coder-v2": "small",
|
|
107
|
+
"codegemma": "small",
|
|
108
|
+
"codegemma-2b": "small",
|
|
109
|
+
"phi-3": "small",
|
|
110
|
+
"phi-3-mini": "small",
|
|
111
|
+
"stable-code": "small",
|
|
112
|
+
"codestral": "small",
|
|
113
|
+
// Medium
|
|
114
|
+
"qwen2.5-coder": "medium",
|
|
115
|
+
"qwen2.5-coder-7b": "medium",
|
|
116
|
+
"llama-3": "medium",
|
|
117
|
+
"llama-3-8b": "medium",
|
|
118
|
+
"mistral": "medium",
|
|
119
|
+
"mixtral": "medium",
|
|
120
|
+
"codellama": "medium",
|
|
121
|
+
"codellama-13b": "medium",
|
|
122
|
+
"gemma-2": "medium",
|
|
123
|
+
"openchat": "medium",
|
|
124
|
+
// Large
|
|
125
|
+
"gpt-4": "large",
|
|
126
|
+
"gpt-4-turbo": "large",
|
|
127
|
+
"gpt-4o": "large",
|
|
128
|
+
"claude-3": "large",
|
|
129
|
+
"claude-3-opus": "large",
|
|
130
|
+
"claude-3-sonnet": "large",
|
|
131
|
+
"gemini-pro": "large",
|
|
132
|
+
"gemini-ultra": "large",
|
|
133
|
+
"llama-3-70b": "large",
|
|
134
|
+
"qwen2.5-72b": "large",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function detectProfile() {
|
|
138
|
+
// 1. Forzado manual en memoria (prioridad máxima en el mismo proceso)
|
|
139
|
+
if (_forcedProfile) {
|
|
140
|
+
return _forcedProfile
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 2. Estado persistido (entre procesos CLI)
|
|
144
|
+
const state = readState()
|
|
145
|
+
if (state && state.forcedProfile && PROFILES[state.forcedProfile]) {
|
|
146
|
+
return state.forcedProfile
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 3. Variable de entorno explícita
|
|
150
|
+
const envProfile = process.env.OPL_BOOST_PROFILE
|
|
151
|
+
if (envProfile && PROFILES[envProfile]) {
|
|
152
|
+
return envProfile
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 4. Detección por nombre de modelo
|
|
156
|
+
const modelName = process.env.OPL_MODEL || process.env.OPENAI_MODEL || ""
|
|
157
|
+
const normalized = modelName.toLowerCase().trim()
|
|
158
|
+
|
|
159
|
+
for (const [key, profile] of Object.entries(KNOWN_MODELS)) {
|
|
160
|
+
if (normalized.includes(key)) {
|
|
161
|
+
return profile
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 5. Config en prompt-lang.json
|
|
166
|
+
try {
|
|
167
|
+
const configPath = join(process.cwd(), "prompt-lang.json")
|
|
168
|
+
if (existsSync(configPath)) {
|
|
169
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
170
|
+
const boostConfig = config.boost
|
|
171
|
+
if (boostConfig) {
|
|
172
|
+
if (boostConfig.enabled === false) return null
|
|
173
|
+
if (boostConfig.defaultProfile && boostConfig.defaultProfile !== "auto") {
|
|
174
|
+
return boostConfig.defaultProfile
|
|
175
|
+
}
|
|
176
|
+
// Overrides de perfil específicos
|
|
177
|
+
if (boostConfig.profiles && boostConfig.profiles[modelName]) {
|
|
178
|
+
return boostConfig.profiles[modelName]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// fallback a default
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 6. Default seguro — medium
|
|
187
|
+
return "medium"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ──────────────────────────────────────────────
|
|
191
|
+
// API pública
|
|
192
|
+
// ──────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
export function getProfile() {
|
|
195
|
+
if (_cachedProfile && !_forcedProfile) {
|
|
196
|
+
return _cachedProfile
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const profileName = detectProfile()
|
|
200
|
+
if (!profileName) return null
|
|
201
|
+
|
|
202
|
+
const profileConfig = PROFILES[profileName]
|
|
203
|
+
if (!profileConfig) return null
|
|
204
|
+
|
|
205
|
+
_cachedProfile = {
|
|
206
|
+
name: profileName,
|
|
207
|
+
...profileConfig,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return _cachedProfile
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function setProfile(name) {
|
|
214
|
+
if (name === "auto" || name === null || name === undefined) {
|
|
215
|
+
_forcedProfile = null
|
|
216
|
+
_cachedProfile = null
|
|
217
|
+
clearState()
|
|
218
|
+
return getProfile()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!PROFILES[name]) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Perfil "${name}" no válido. Opciones: ${Object.keys(PROFILES).join(", ")}, "auto"`
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_forcedProfile = name
|
|
228
|
+
_cachedProfile = null
|
|
229
|
+
writeState({ forcedProfile: name, updatedAt: new Date().toISOString() })
|
|
230
|
+
return getProfile()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function listProfiles() {
|
|
234
|
+
return Object.entries(PROFILES).map(([name, config]) => ({
|
|
235
|
+
name,
|
|
236
|
+
label: config.label,
|
|
237
|
+
description: config.description,
|
|
238
|
+
contextWindow: config.contextWindow,
|
|
239
|
+
compressionLevel: config.compressionLevel,
|
|
240
|
+
validationLoops: config.validationLoops,
|
|
241
|
+
microTasks: config.microTasks,
|
|
242
|
+
templateHydration: config.templateHydration,
|
|
243
|
+
fewShot: config.fewShot,
|
|
244
|
+
}))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function clearCache() {
|
|
248
|
+
_cachedProfile = null
|
|
249
|
+
_forcedProfile = null
|
|
250
|
+
clearState()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function isBoostEnabled() {
|
|
254
|
+
try {
|
|
255
|
+
const configPath = join(process.cwd(), "prompt-lang.json")
|
|
256
|
+
if (existsSync(configPath)) {
|
|
257
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
258
|
+
if (config.boost && config.boost.enabled === false) return false
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
// fallback
|
|
262
|
+
}
|
|
263
|
+
return true
|
|
264
|
+
}
|