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,615 @@
|
|
|
1
|
+
// @use(kind, contract, limit, error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: task, options -> out: boost -> { code, metadata, report } @error: BoostPipelineError)
|
|
4
|
+
// @limit(lines: 350)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Boost Orchestrator — Pipeline de generación multi-etapa.
|
|
8
|
+
*
|
|
9
|
+
* Orquesta todos los componentes Boost en un flujo coherente.
|
|
10
|
+
* Soporta dos modos:
|
|
11
|
+
*
|
|
12
|
+
* **Single-pass** (default): compress → few-shot → hydrate → quick-validate
|
|
13
|
+
* Usa el Agent Pool solo para la generación central.
|
|
14
|
+
*
|
|
15
|
+
* **Multi-agent** (opt-in): compress → dispatch DAG → agent pool → assemble → validate
|
|
16
|
+
* Usa agentes especializados para cada parte del código.
|
|
17
|
+
*
|
|
18
|
+
* Uso:
|
|
19
|
+
* import { boost } from './boost/orchestrator.js'
|
|
20
|
+
* const result = await boost('crea hook useAuth con Supabase', {
|
|
21
|
+
* kind: 'hook',
|
|
22
|
+
* profile: 'medium',
|
|
23
|
+
* })
|
|
24
|
+
* // → { code, metadata: { duration, stages: [...], profile }, report }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { getProfile, isBoostEnabled } from "./profile-registry.js"
|
|
28
|
+
import { compressContext, compressAgentsMd } from "./context-compressor.js"
|
|
29
|
+
import { retrieveExamples, buildFewShotPrompt } from "./fewshot-retriever.js"
|
|
30
|
+
import { hydrate, hydrateFromAgentResults } from "./hydrator.js"
|
|
31
|
+
import { quickValidate, validateCode, feedbackLoop } from "./validation-loop.js"
|
|
32
|
+
import { plan, detectKind } from "./task-dispatcher.js"
|
|
33
|
+
import { agentPool } from "./agent-pool.js"
|
|
34
|
+
|
|
35
|
+
// ──────────────────────────────────────────────
|
|
36
|
+
// Configuración
|
|
37
|
+
// ──────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
const DEFAULT_OPTIONS = {
|
|
40
|
+
profile: null, // auto-detect
|
|
41
|
+
kind: null, // auto-detect
|
|
42
|
+
dryRun: false, // solo mostrar plan
|
|
43
|
+
output: null, // archivo de salida (opcional)
|
|
44
|
+
validate: true, // ejecutar validation loop
|
|
45
|
+
multiAgent: false, // usar multi-agente (opt-in)
|
|
46
|
+
contextMode: "safe", // modo de compresión: safe | aggressive | disabled
|
|
47
|
+
maxExamples: 3, // máx ejemplos few-shot
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ──────────────────────────────────────────────
|
|
51
|
+
// Pipeline stages
|
|
52
|
+
// ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Stage 0: Detectar perfil
|
|
56
|
+
*/
|
|
57
|
+
function stageProfile(options) {
|
|
58
|
+
const startTime = Date.now()
|
|
59
|
+
const profile = options.profile || getProfile()
|
|
60
|
+
return {
|
|
61
|
+
name: "profile-detect",
|
|
62
|
+
duration: Date.now() - startTime,
|
|
63
|
+
result: profile ? { name: profile.name, mode: profile.agentPool?.mode || "queue" } : null,
|
|
64
|
+
profile,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Stage 1: Comprimir contexto
|
|
70
|
+
*/
|
|
71
|
+
function stageCompress(task, kind, profile, options) {
|
|
72
|
+
const startTime = Date.now()
|
|
73
|
+
|
|
74
|
+
// Comprimir AGENTS.md
|
|
75
|
+
const compressed = compressAgentsMd(task, profile, {
|
|
76
|
+
mode: options.contextMode || "safe",
|
|
77
|
+
kind,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name: "context-compress",
|
|
82
|
+
duration: Date.now() - startTime,
|
|
83
|
+
result: compressed
|
|
84
|
+
? {
|
|
85
|
+
originalChars: compressed.metrics.originalChars,
|
|
86
|
+
compressedChars: compressed.metrics.compressedChars,
|
|
87
|
+
reductionPercent: compressed.metrics.reductionPercent,
|
|
88
|
+
sectionsKept: compressed.metrics.sectionsKept,
|
|
89
|
+
}
|
|
90
|
+
: null,
|
|
91
|
+
compressed,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Stage 2: Buscar ejemplos few-shot
|
|
97
|
+
*/
|
|
98
|
+
function stageFewShot(task, kind, options) {
|
|
99
|
+
const startTime = Date.now()
|
|
100
|
+
|
|
101
|
+
const examples = retrieveExamples(kind, task, {
|
|
102
|
+
maxExamples: options.maxExamples || 3,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
name: "fewshot-retrieve",
|
|
107
|
+
duration: Date.now() - startTime,
|
|
108
|
+
result: {
|
|
109
|
+
examplesFound: examples.length,
|
|
110
|
+
sources: examples.map((e) => e.file),
|
|
111
|
+
},
|
|
112
|
+
examples,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Stage 3: DAG planning (solo multi-agent)
|
|
118
|
+
*/
|
|
119
|
+
function stagePlan(task, kind) {
|
|
120
|
+
const startTime = Date.now()
|
|
121
|
+
const dag = plan(task, kind)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
name: "task-plan",
|
|
125
|
+
duration: Date.now() - startTime,
|
|
126
|
+
result: {
|
|
127
|
+
kind: dag.kind,
|
|
128
|
+
nodes: dag.nodeCount,
|
|
129
|
+
parallel: dag.stages.parallel.length,
|
|
130
|
+
sequential: dag.stages.sequential.length,
|
|
131
|
+
},
|
|
132
|
+
dag,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Stage 4: Generar código con Agent Pool
|
|
138
|
+
*/
|
|
139
|
+
async function stageGenerate(task, kind, compressed, examples, dag, profile, options) {
|
|
140
|
+
const startTime = Date.now()
|
|
141
|
+
|
|
142
|
+
if (options.multiAgent) {
|
|
143
|
+
// MODO MULTI-AGENTE: ejecutar DAG con agentes especializados
|
|
144
|
+
return await stageGenerateMultiAgent(task, kind, compressed, examples, dag, profile, options)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// MODO SINGLE-PASS: generar con un solo agente (logic)
|
|
148
|
+
// El agente recibe contexto comprimido + ejemplos + instrucciones
|
|
149
|
+
const context = compressed?.context || ""
|
|
150
|
+
const fewShotPrompt = examples.length > 0
|
|
151
|
+
? buildFewShotPrompt(examples, `Genera un ${kind} para: ${task}`)
|
|
152
|
+
: `Genera un ${kind} para: ${task}`
|
|
153
|
+
|
|
154
|
+
const fullPrompt = [
|
|
155
|
+
context ? `## Contexto del Proyecto\n\n${context.substring(0, 2000)}\n\n` : "",
|
|
156
|
+
fewShotPrompt,
|
|
157
|
+
].join("")
|
|
158
|
+
|
|
159
|
+
const result = await agentPool.submit("logic", fullPrompt, profile)
|
|
160
|
+
const code = result.text
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
name: "generate",
|
|
164
|
+
duration: Date.now() - startTime,
|
|
165
|
+
result: {
|
|
166
|
+
mode: "single-pass",
|
|
167
|
+
tokensPerSecond: result.metrics?.tokensPerSecond,
|
|
168
|
+
tokenCount: result.metrics?.tokenCount,
|
|
169
|
+
codeLength: code.length,
|
|
170
|
+
},
|
|
171
|
+
code,
|
|
172
|
+
agentMetrics: result.metrics,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Stage 4b: Generación multi-agente (DAG)
|
|
178
|
+
*/
|
|
179
|
+
async function stageGenerateMultiAgent(task, kind, compressed, examples, dag, profile, options) {
|
|
180
|
+
const startTime = Date.now()
|
|
181
|
+
const context = compressed?.context || ""
|
|
182
|
+
const results = {}
|
|
183
|
+
|
|
184
|
+
// Fase paralela: nodos sin dependencias
|
|
185
|
+
const parallelNodes = dag.nodes.filter((n) => n.deps.length === 0)
|
|
186
|
+
const parallelResults = await Promise.all(
|
|
187
|
+
parallelNodes.map(async (node) => {
|
|
188
|
+
const agentInput = [
|
|
189
|
+
task,
|
|
190
|
+
context ? `\nContexto:\n${context.substring(0, 1000)}` : "",
|
|
191
|
+
examples.length > 0 ? `\nEjemplos:\n${examples.map((e) => e.file).join(", ")}` : "",
|
|
192
|
+
].join("\n")
|
|
193
|
+
|
|
194
|
+
const result = await agentPool.submit(node.agent, agentInput, profile)
|
|
195
|
+
return { nodeId: node.id, ...result }
|
|
196
|
+
})
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
for (const r of parallelResults) {
|
|
200
|
+
results[r.nodeId] = r.text
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Fase secuencial: nodos con dependencias
|
|
204
|
+
const sequentialNodes = dag.nodes.filter((n) => n.deps.length > 0)
|
|
205
|
+
for (const node of sequentialNodes) {
|
|
206
|
+
// Armar input con resultados de dependencias
|
|
207
|
+
const depsOutput = node.deps.map((depId) => {
|
|
208
|
+
const depNode = dag.nodes.find((n) => n.id === depId)
|
|
209
|
+
return `### ${depNode?.description || depId}\n${results[depId] || ""}`
|
|
210
|
+
}).join("\n\n")
|
|
211
|
+
|
|
212
|
+
const agentInput = [
|
|
213
|
+
`Task: ${task}`,
|
|
214
|
+
`Kind: ${kind}`,
|
|
215
|
+
"",
|
|
216
|
+
"Outputs from previous agents:",
|
|
217
|
+
depsOutput,
|
|
218
|
+
"",
|
|
219
|
+
`Your role: ${node.description}`,
|
|
220
|
+
].join("\n")
|
|
221
|
+
|
|
222
|
+
const result = await agentPool.submit(node.agent, agentInput, profile)
|
|
223
|
+
results[node.id] = result.text
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Ensamblar resultados en código final
|
|
227
|
+
// Extraer nombre de la tarea para el hydrator
|
|
228
|
+
const taskName = extractNameFromTask(task)
|
|
229
|
+
const assembledCode = assembleFromDAG(kind, results, taskName)
|
|
230
|
+
const hydratorStart = Date.now()
|
|
231
|
+
|
|
232
|
+
// Aplicar la misma lógica de smart detection que stageHydrate
|
|
233
|
+
const hasImports = /^import\s/m.test(assembledCode)
|
|
234
|
+
const hasExport = /^export\s/m.test(assembledCode)
|
|
235
|
+
|
|
236
|
+
let finalCode
|
|
237
|
+
let hydrationResult
|
|
238
|
+
|
|
239
|
+
if (hasImports && hasExport) {
|
|
240
|
+
// Código completo del LLM — solo inyectar anotaciones
|
|
241
|
+
const annotations = generateOPLAnnotations(kind, assembledCode)
|
|
242
|
+
finalCode = annotations + "\n\n" + assembledCode.trim()
|
|
243
|
+
hydrationResult = { hydrated: false, reason: "multi-agent-complete-injection" }
|
|
244
|
+
} else {
|
|
245
|
+
// Código parcial — hidratar con skeleton
|
|
246
|
+
const hydrated = hydrateFromAgentResults(kind, { ...results, name: taskName })
|
|
247
|
+
finalCode = hydrated.code || assembledCode
|
|
248
|
+
hydrationResult = { hydrated: true, warnings: hydrated.warnings.length }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
name: "generate",
|
|
253
|
+
duration: Date.now() - startTime,
|
|
254
|
+
result: {
|
|
255
|
+
mode: "multi-agent",
|
|
256
|
+
parallelNodes: parallelNodes.length,
|
|
257
|
+
sequentialNodes: sequentialNodes.length,
|
|
258
|
+
agentResults: Object.keys(results).length,
|
|
259
|
+
},
|
|
260
|
+
code: finalCode,
|
|
261
|
+
agentResults: results,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Ensamblado básico de resultados multi-agente.
|
|
267
|
+
*/
|
|
268
|
+
function assembleFromDAG(kind, results, taskName) {
|
|
269
|
+
const parts = []
|
|
270
|
+
if (results.types) parts.push(results.types)
|
|
271
|
+
if (results.state) parts.push(results.state)
|
|
272
|
+
if (results.logic) parts.push(results.logic)
|
|
273
|
+
return parts.join("\n\n")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Stage 5: Hidratar código final
|
|
278
|
+
*
|
|
279
|
+
* Estrategia de hidratación:
|
|
280
|
+
* 1. Si el código ya tiene @kind() → usar directo (ya anotado)
|
|
281
|
+
* 2. Si el código tiene imports + exports → código completo del LLM, solo inyectar anotaciones
|
|
282
|
+
* 3. Si es código parcial → hidratar con skeleton
|
|
283
|
+
*/
|
|
284
|
+
function stageHydrate(kind, code, profile) {
|
|
285
|
+
const startTime = Date.now()
|
|
286
|
+
|
|
287
|
+
// Caso 1: Si el código ya tiene anotaciones OPL, usarlo directamente
|
|
288
|
+
if (/@kind\(/.test(code)) {
|
|
289
|
+
return {
|
|
290
|
+
name: "hydrate",
|
|
291
|
+
duration: Date.now() - startTime,
|
|
292
|
+
result: { hydrated: false, reason: "already-annotated" },
|
|
293
|
+
code,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Caso 2: Si el código tiene imports y exports, es código completo del LLM.
|
|
298
|
+
// No envolver en skeleton — solo agregar anotaciones OPL al inicio.
|
|
299
|
+
const hasImports = /^import\s/m.test(code)
|
|
300
|
+
const hasExport = /^export\s/m.test(code)
|
|
301
|
+
|
|
302
|
+
if (hasImports && hasExport) {
|
|
303
|
+
const annotations = generateOPLAnnotations(kind, code)
|
|
304
|
+
const annotatedCode = annotations + "\n\n" + code.trim()
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
name: "hydrate",
|
|
308
|
+
duration: Date.now() - startTime,
|
|
309
|
+
result: { hydrated: false, reason: "llm-complete-injection" },
|
|
310
|
+
code: annotatedCode,
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Caso 3: Código parcial — hidratar con skeleton template
|
|
315
|
+
const hydrated = hydrate(kind, {
|
|
316
|
+
imports: "",
|
|
317
|
+
types: "",
|
|
318
|
+
logic: code,
|
|
319
|
+
name: "Generated",
|
|
320
|
+
props: "",
|
|
321
|
+
description: "Generated by OPL Boost",
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
name: "hydrate",
|
|
326
|
+
duration: Date.now() - startTime,
|
|
327
|
+
result: {
|
|
328
|
+
hydrated: true,
|
|
329
|
+
warnings: hydrated.warnings.length,
|
|
330
|
+
validationsPassed: hydrated.validations.filter((v) => v.passed).length,
|
|
331
|
+
},
|
|
332
|
+
code: hydrated.code,
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Extrae el nombre de una función/componente de la descripción de la tarea.
|
|
338
|
+
* Ejemplos:
|
|
339
|
+
* "hook useAuth para autenticación" → "useAuth"
|
|
340
|
+
* "service ProductService para ecommerce" → "ProductService"
|
|
341
|
+
* "store useCartStore con zustand" → "useCartStore"
|
|
342
|
+
* "componente ProductCard" → "ProductCard"
|
|
343
|
+
*/
|
|
344
|
+
function extractNameFromTask(task) {
|
|
345
|
+
// Patrón: kind name (hook useAuth, service ProductService, etc.)
|
|
346
|
+
const kindNamePattern = /\b(?:hook|service|store|component|page|util)\s+(use\w+|[A-Z]\w*(?:Service|Store|Component|Page|Hook|Manager|Handler))\b/i
|
|
347
|
+
const kindNameMatch = task.match(kindNamePattern)
|
|
348
|
+
if (kindNameMatch) return kindNameMatch[1]
|
|
349
|
+
|
|
350
|
+
// Patrón: useXxx (nombre de hook)
|
|
351
|
+
const hookPattern = /\b(use\w+)\b/i
|
|
352
|
+
const hookMatch = task.match(hookPattern)
|
|
353
|
+
if (hookMatch) return hookMatch[1]
|
|
354
|
+
|
|
355
|
+
// Patrón: XxxService, XxxStore, XxxComponent
|
|
356
|
+
const pascalPattern = /\b([A-Z]\w+(?:Service|Store|Component|Page|Manager|Handler))\b/
|
|
357
|
+
const pascalMatch = task.match(pascalPattern)
|
|
358
|
+
if (pascalMatch) return pascalMatch[1]
|
|
359
|
+
|
|
360
|
+
// Patrón: cualquier palabra PascalCase
|
|
361
|
+
const camelPattern = /\b([A-Z][a-zA-Z]+)\b/
|
|
362
|
+
const camelMatch = task.match(camelPattern)
|
|
363
|
+
if (camelMatch) return camelMatch[1]
|
|
364
|
+
|
|
365
|
+
return "Generated"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Genera anotaciones OPL para inyectar al inicio de un archivo completo del LLM.
|
|
370
|
+
*/
|
|
371
|
+
function generateOPLAnnotations(kind, code) {
|
|
372
|
+
const lineCount = code.split("\n").length
|
|
373
|
+
const kindLimits = { hook: 80, component: 120, page: 200, service: 150, store: 100, util: 100 }
|
|
374
|
+
const limit = kindLimits[kind] || 200
|
|
375
|
+
|
|
376
|
+
let annotation = `// @use(kind, contract, limit)\n// @kind(${kind})`
|
|
377
|
+
|
|
378
|
+
// Detectar contrato básico del código
|
|
379
|
+
const exportMatch = code.match(/export\s+(?:async\s+)?function\s+(\w+)/)
|
|
380
|
+
const funcName = exportMatch ? exportMatch[1] : ""
|
|
381
|
+
if (funcName) {
|
|
382
|
+
annotation += `\n// @contract(in: unknown -> out: ${funcName} result @error: Error)`
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
annotation += `\n// @limit(lines: ${limit})`
|
|
386
|
+
|
|
387
|
+
return annotation
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Stage 6: Validation loop
|
|
392
|
+
*/
|
|
393
|
+
async function stageValidate(kind, code, profile, options) {
|
|
394
|
+
if (!options.validate) {
|
|
395
|
+
// Solo quick-validate
|
|
396
|
+
const startTime = Date.now()
|
|
397
|
+
const check = quickValidate(code, kind)
|
|
398
|
+
return {
|
|
399
|
+
name: "quick-validate",
|
|
400
|
+
duration: Date.now() - startTime,
|
|
401
|
+
result: { valid: check.valid, warnings: check.warnings },
|
|
402
|
+
valid: check.valid,
|
|
403
|
+
code,
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Feedback loop completo con agente validate
|
|
408
|
+
const startTime = Date.now()
|
|
409
|
+
const result = await feedbackLoop(code, kind, profile, {
|
|
410
|
+
async correctWithAgent(currentCode, errors, feedback) {
|
|
411
|
+
const agentResult = await agentPool.submit("validate", feedback, profile)
|
|
412
|
+
return agentResult.text
|
|
413
|
+
},
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
name: "validate",
|
|
418
|
+
duration: Date.now() - startTime,
|
|
419
|
+
result: {
|
|
420
|
+
valid: result.valid,
|
|
421
|
+
attempts: result.history.length,
|
|
422
|
+
errorsFound: result.report.errorsFound,
|
|
423
|
+
summary: result.report.summary,
|
|
424
|
+
},
|
|
425
|
+
valid: result.valid,
|
|
426
|
+
code: result.code,
|
|
427
|
+
report: result.report,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ──────────────────────────────────────────────
|
|
432
|
+
// API principal: boost()
|
|
433
|
+
// ──────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Orquesta el pipeline completo de generación Boost.
|
|
437
|
+
*
|
|
438
|
+
* @param {string} task - Descripción de la tarea
|
|
439
|
+
* @param {object} [options] - Opciones
|
|
440
|
+
* @param {string} [options.kind] - Tipo de archivo (auto-detecta si se omite)
|
|
441
|
+
* @param {object} [options.profile] - Perfil (auto-detecta si se omite)
|
|
442
|
+
* @param {boolean} [options.dryRun=false] - Solo mostrar plan
|
|
443
|
+
* @param {string} [options.output] - Archivo de salida
|
|
444
|
+
* @param {boolean} [options.validate=true] - Ejecutar validation loop
|
|
445
|
+
* @param {boolean} [options.multiAgent=false] - Usar multi-agente
|
|
446
|
+
* @param {string} [options.contextMode="safe"] - Modo compresión
|
|
447
|
+
* @returns {Promise<{ code: string, metadata: object, report: object }>}
|
|
448
|
+
*/
|
|
449
|
+
export async function boost(task, options = {}) {
|
|
450
|
+
const opts = { ...DEFAULT_OPTIONS, ...options }
|
|
451
|
+
const kind = opts.kind || detectKind(task)
|
|
452
|
+
const stages = []
|
|
453
|
+
const startTime = Date.now()
|
|
454
|
+
|
|
455
|
+
// Verificar que Boost esté habilitado
|
|
456
|
+
if (!isBoostEnabled()) {
|
|
457
|
+
return {
|
|
458
|
+
code: "",
|
|
459
|
+
metadata: { error: "Boost deshabilitado en prompt-lang.json" },
|
|
460
|
+
report: { summary: "Boost deshabilitado. Actívalo con boost.enabled = true" },
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Stage 0: Profile
|
|
465
|
+
const profileStage = stageProfile(opts)
|
|
466
|
+
stages.push(profileStage)
|
|
467
|
+
const profile = profileStage.profile
|
|
468
|
+
|
|
469
|
+
if (opts.dryRun) {
|
|
470
|
+
return dryRun(task, kind, profile, opts)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Stage 1: Compress
|
|
474
|
+
const compressStage = stageCompress(task, kind, profile, opts)
|
|
475
|
+
stages.push(compressStage)
|
|
476
|
+
|
|
477
|
+
// Stage 2: Few-shot
|
|
478
|
+
const fewShotStage = stageFewShot(task, kind, opts)
|
|
479
|
+
stages.push(fewShotStage)
|
|
480
|
+
|
|
481
|
+
// Stage 3: Plan
|
|
482
|
+
const planStage = stagePlan(task, kind)
|
|
483
|
+
stages.push(planStage)
|
|
484
|
+
|
|
485
|
+
// Stage 4: Generate
|
|
486
|
+
const generateStage = await stageGenerate(
|
|
487
|
+
task, kind,
|
|
488
|
+
compressStage.compressed,
|
|
489
|
+
fewShotStage.examples,
|
|
490
|
+
planStage.dag,
|
|
491
|
+
profile, opts
|
|
492
|
+
)
|
|
493
|
+
stages.push(generateStage)
|
|
494
|
+
|
|
495
|
+
// Stage 5: Hydrate
|
|
496
|
+
const hydrateStage = stageHydrate(kind, generateStage.code, profile)
|
|
497
|
+
stages.push(hydrateStage)
|
|
498
|
+
|
|
499
|
+
// Stage 6: Validate
|
|
500
|
+
const validateStage = await stageValidate(kind, hydrateStage.code, profile, opts)
|
|
501
|
+
stages.push(validateStage)
|
|
502
|
+
|
|
503
|
+
// Calcular metadata
|
|
504
|
+
const totalTime = Date.now() - startTime
|
|
505
|
+
const code = validateStage.code
|
|
506
|
+
|
|
507
|
+
// Guardar a archivo si se especificó
|
|
508
|
+
if (opts.output && code) {
|
|
509
|
+
try {
|
|
510
|
+
const { writeFileSync, mkdirSync, existsSync } = await import("fs")
|
|
511
|
+
const { dirname, isAbsolute, join: joinPath } = await import("path")
|
|
512
|
+
// Si la ruta es absoluta, usarla directamente; si es relativa, resolverla desde cwd
|
|
513
|
+
const outputPath = isAbsolute(opts.output) ? opts.output : joinPath(process.cwd(), opts.output)
|
|
514
|
+
const outputDir = dirname(outputPath)
|
|
515
|
+
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true })
|
|
516
|
+
writeFileSync(outputPath, code, "utf-8")
|
|
517
|
+
} catch (err) {
|
|
518
|
+
// Error guardando archivo — no fatal
|
|
519
|
+
stages.push({
|
|
520
|
+
name: "output-save",
|
|
521
|
+
duration: 0,
|
|
522
|
+
error: err.message,
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const metadata = {
|
|
528
|
+
task,
|
|
529
|
+
kind,
|
|
530
|
+
profile: profile?.name || "unknown",
|
|
531
|
+
totalTime,
|
|
532
|
+
stages: stages.map((s) => ({
|
|
533
|
+
name: s.name,
|
|
534
|
+
duration: s.duration,
|
|
535
|
+
result: s.result || null,
|
|
536
|
+
error: s.error || null,
|
|
537
|
+
})),
|
|
538
|
+
multiAgent: opts.multiAgent,
|
|
539
|
+
contextMode: opts.contextMode,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const report = {
|
|
543
|
+
summary: validateStage.valid
|
|
544
|
+
? `✅ Código generado exitosamente en ${(totalTime / 1000).toFixed(1)}s`
|
|
545
|
+
: `⚠️ Código generado con advertencias en ${(totalTime / 1000).toFixed(1)}s`,
|
|
546
|
+
profile: profile?.name || "unknown",
|
|
547
|
+
totalTime,
|
|
548
|
+
stagesCompleted: stages.filter((s) => !s.error).length,
|
|
549
|
+
stagesFailed: stages.filter((s) => s.error).length,
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return { code, metadata, report }
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ──────────────────────────────────────────────
|
|
556
|
+
// Dry-run
|
|
557
|
+
// ──────────────────────────────────────────────
|
|
558
|
+
|
|
559
|
+
function dryRun(task, kind, profile, options) {
|
|
560
|
+
const dag = plan(task, kind)
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
code: "",
|
|
564
|
+
metadata: {
|
|
565
|
+
task,
|
|
566
|
+
kind,
|
|
567
|
+
profile: profile?.name || "unknown",
|
|
568
|
+
dryRun: true,
|
|
569
|
+
stages: [
|
|
570
|
+
{ name: "profile-detect", planned: true, result: { profile: profile?.name } },
|
|
571
|
+
{ name: "context-compress", planned: true, result: { mode: options.contextMode, level: profile?.compressionLevel } },
|
|
572
|
+
{ name: "fewshot-retrieve", planned: true, result: { maxExamples: options.maxExamples } },
|
|
573
|
+
{ name: "task-plan", planned: true, result: { kind, nodes: dag.nodeCount } },
|
|
574
|
+
{ name: "generate", planned: true, result: { mode: options.multiAgent ? "multi-agent" : "single-pass" } },
|
|
575
|
+
{ name: "hydrate", planned: true, result: {} },
|
|
576
|
+
{ name: "validate", planned: true, result: { enabled: options.validate } },
|
|
577
|
+
],
|
|
578
|
+
},
|
|
579
|
+
plan: {
|
|
580
|
+
task,
|
|
581
|
+
kind,
|
|
582
|
+
profile: profile?.name,
|
|
583
|
+
dag,
|
|
584
|
+
estimatedDuration: estimateDuration(profile, options),
|
|
585
|
+
command: `opl boost generate "${task}"${options.multiAgent ? " --multi-agent" : ""}`,
|
|
586
|
+
},
|
|
587
|
+
report: {
|
|
588
|
+
summary: `📋 Dry-run: generaría un @kind(${kind}) usando perfil ${profile?.name || "unknown"}`,
|
|
589
|
+
duration: "N/A (dry-run)",
|
|
590
|
+
},
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function estimateDuration(profile, options) {
|
|
595
|
+
const baseTime = profile?.name === "small" ? 30 : profile?.name === "large" ? 8 : 15
|
|
596
|
+
const agentMultiplier = options.multiAgent ? 2.5 : 1
|
|
597
|
+
return `${Math.round(baseTime * agentMultiplier)}s (estimado)`
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ──────────────────────────────────────────────
|
|
601
|
+
// Utilidades
|
|
602
|
+
// ──────────────────────────────────────────────
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Código de conveniencia para generar y mostrar resultado.
|
|
606
|
+
* Útil para CLI.
|
|
607
|
+
*
|
|
608
|
+
* @param {string} task
|
|
609
|
+
* @param {object} options
|
|
610
|
+
* @returns {Promise<string>} Código generado
|
|
611
|
+
*/
|
|
612
|
+
export async function generateCode(task, options = {}) {
|
|
613
|
+
const result = await boost(task, options)
|
|
614
|
+
return result.code
|
|
615
|
+
}
|