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,495 @@
|
|
|
1
|
+
// @use(kind, contract, limit, error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: code, kind, profile -> out: feedbackLoop -> { code, history, report } @error: ValidationFailedError, EscalationFailedError)
|
|
4
|
+
// @limit(lines: 300)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validation Feedback Loop — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Sistema post-generación que:
|
|
10
|
+
* 1. Corre validación OPL sobre el código generado
|
|
11
|
+
* 2. Retroalimenta errores específicos al agente 'validate'
|
|
12
|
+
* 3. Reintenta hasta N veces según perfil
|
|
13
|
+
* 4. Si persiste, escala la complejidad hacia abajo
|
|
14
|
+
*
|
|
15
|
+
* Principio: los modelos pequeños son mejores corrigiendo errores
|
|
16
|
+
* específicos que generando código correcto la primera vez.
|
|
17
|
+
*
|
|
18
|
+
* Uso:
|
|
19
|
+
* import { feedbackLoop } from './boost/validation-loop.js'
|
|
20
|
+
* const result = await feedbackLoop(code, kind, profile)
|
|
21
|
+
* // → { code, history: [...], report: { ... } }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { execSync } from "child_process"
|
|
25
|
+
import { existsSync, writeFileSync, unlinkSync, mkdirSync } from "fs"
|
|
26
|
+
import { join } from "path"
|
|
27
|
+
|
|
28
|
+
// ──────────────────────────────────────────────
|
|
29
|
+
// Configuración
|
|
30
|
+
// ──────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const VALIDATION_CMD = "npx openprompt-lang validate"
|
|
33
|
+
const MAX_RETRIES_DEFAULT = 2
|
|
34
|
+
const TEMP_DIR = join(process.cwd(), ".opencode", "boost", "temp")
|
|
35
|
+
|
|
36
|
+
// ──────────────────────────────────────────────
|
|
37
|
+
// Errores específicos
|
|
38
|
+
// ──────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
export class ValidationFailedError extends Error {
|
|
41
|
+
constructor(message, errors) {
|
|
42
|
+
super(message)
|
|
43
|
+
this.name = "ValidationFailedError"
|
|
44
|
+
this.errors = errors
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class EscalationFailedError extends Error {
|
|
49
|
+
constructor(task, attempts) {
|
|
50
|
+
super(`Escalation failed for task "${task}" after ${attempts} attempts`)
|
|
51
|
+
this.name = "EscalationFailedError"
|
|
52
|
+
this.task = task
|
|
53
|
+
this.attempts = attempts
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ──────────────────────────────────────────────
|
|
58
|
+
// Validación OPL local
|
|
59
|
+
// ──────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Ejecuta validación OPL sobre código generado.
|
|
63
|
+
* Escribe el código en un archivo temporal, ejecuta validate, parsea errores.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} code - Código a validar
|
|
66
|
+
* @param {string} kind - Tipo de archivo (hook, component, etc.)
|
|
67
|
+
* @returns {{ valid: boolean, errors: Array<{ type: string, message: string, line: number }>, raw: string }}
|
|
68
|
+
*/
|
|
69
|
+
export function validateCode(code, kind) {
|
|
70
|
+
const tempDir = getTempDir()
|
|
71
|
+
const tempFile = join(tempDir, `_boost_validate_${kind}_${Date.now()}.ts`)
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Escribir archivo temporal
|
|
75
|
+
if (!existsSync(tempDir)) {
|
|
76
|
+
mkdirSync(tempDir, { recursive: true })
|
|
77
|
+
}
|
|
78
|
+
writeFileSync(tempFile, code, "utf-8")
|
|
79
|
+
|
|
80
|
+
// Ejecutar validación
|
|
81
|
+
let output
|
|
82
|
+
try {
|
|
83
|
+
output = execSync(`${VALIDATION_CMD} ${tempFile} 2>&1`, {
|
|
84
|
+
encoding: "utf-8",
|
|
85
|
+
timeout: 10000,
|
|
86
|
+
cwd: process.cwd(),
|
|
87
|
+
})
|
|
88
|
+
} catch (execErr) {
|
|
89
|
+
// validate devuelve código de error si hay problemas
|
|
90
|
+
output = execErr.stdout || execErr.message || ""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Parsear errores del output
|
|
94
|
+
const errors = parseValidationOutput(output)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
valid: errors.length === 0,
|
|
98
|
+
errors,
|
|
99
|
+
raw: output,
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return {
|
|
103
|
+
valid: false,
|
|
104
|
+
errors: [{ type: "system", message: `Error ejecutando validación: ${err.message}`, line: 0 }],
|
|
105
|
+
raw: err.message,
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
// Limpiar archivo temporal
|
|
109
|
+
try { unlinkSync(tempFile) } catch { /* ignore */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parseo básico del output de openprompt-lang validate.
|
|
115
|
+
* Busca patrones como: "✖ ERROR file.ts:10 - mensaje"
|
|
116
|
+
*/
|
|
117
|
+
function parseValidationOutput(output) {
|
|
118
|
+
const errors = []
|
|
119
|
+
const lines = output.split("\n")
|
|
120
|
+
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
// Patrones comunes de error
|
|
123
|
+
const patterns = [
|
|
124
|
+
/(?:ERROR|error|✖)\s+(.+?)(?::(\d+))?\s*[-–]\s*(.+)/i,
|
|
125
|
+
/@kind.*missing/i,
|
|
126
|
+
/@limit.*exceeded/i,
|
|
127
|
+
/@use.*required/i,
|
|
128
|
+
/annotation.*error/i,
|
|
129
|
+
/(@\w+)\(.*\)\s*[-–]\s*(.+)/,
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
for (const pattern of patterns) {
|
|
133
|
+
const match = line.match(pattern)
|
|
134
|
+
if (match) {
|
|
135
|
+
const [, fileOrType, lineNum, message] = match
|
|
136
|
+
errors.push({
|
|
137
|
+
type: inferErrorType(line),
|
|
138
|
+
message: message || fileOrType || line,
|
|
139
|
+
line: lineNum ? parseInt(lineNum, 10) : guessErrorLine(line, output),
|
|
140
|
+
raw: line,
|
|
141
|
+
})
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Detectar errores específicos de OPL
|
|
147
|
+
if (/\bexceeds? maximum lines?\b/i.test(line)) {
|
|
148
|
+
errors.push({ type: "limit", message: line.trim(), line: 0, raw: line })
|
|
149
|
+
}
|
|
150
|
+
if (/\bmissing @kind\b/i.test(line)) {
|
|
151
|
+
errors.push({ type: "annotation", message: line.trim(), line: 0, raw: line })
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return errors
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function inferErrorType(line) {
|
|
159
|
+
if (/@kind|@limit|@use|@contract|@props|@compose|@deps/i.test(line)) return "annotation"
|
|
160
|
+
if (/limit|lines?|exceeds/i.test(line)) return "limit"
|
|
161
|
+
if (/type|interface|TypeScript|TS\d+/i.test(line)) return "type"
|
|
162
|
+
if (/import|export|module/i.test(line)) return "module"
|
|
163
|
+
if (/syntax|parse|unexpected/i.test(line)) return "syntax"
|
|
164
|
+
return "general"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function guessErrorLine(line, fullOutput) {
|
|
168
|
+
// Intentar extraer número de línea de varios patrones
|
|
169
|
+
const lineMatch = line.match(/:(\d+):/)
|
|
170
|
+
if (lineMatch) return parseInt(lineMatch[1], 10)
|
|
171
|
+
|
|
172
|
+
// Buscar número de línea al inicio
|
|
173
|
+
const lineNumMatch = line.match(/\((\d+),\d+\)/)
|
|
174
|
+
if (lineNumMatch) return parseInt(lineNumMatch[1], 10)
|
|
175
|
+
|
|
176
|
+
return 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ──────────────────────────────────────────────
|
|
180
|
+
// Construcción de feedback para el agente
|
|
181
|
+
// ──────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Construye el mensaje de feedback para el agente 'validate'.
|
|
185
|
+
* Le dice exactamente qué está mal y cómo arreglarlo.
|
|
186
|
+
*
|
|
187
|
+
* @param {Array} errors - Errores de validación
|
|
188
|
+
* @param {object} options - Opciones
|
|
189
|
+
* @returns {string} Mensaje de feedback estructurado
|
|
190
|
+
*/
|
|
191
|
+
export function buildFeedback(errors, options = {}) {
|
|
192
|
+
if (!errors || errors.length === 0) {
|
|
193
|
+
return "El código pasó todas las validaciones. No se requieren cambios."
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const parts = [
|
|
197
|
+
`## Errores de Validación (${errors.length} encontrados)`,
|
|
198
|
+
"",
|
|
199
|
+
"Corrige SOLO los errores listados. No reescribas el código completo.",
|
|
200
|
+
"Conserva la estructura y lógica original.",
|
|
201
|
+
"",
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
for (let i = 0; i < errors.length; i++) {
|
|
205
|
+
const err = errors[i]
|
|
206
|
+
parts.push(`### Error ${i + 1}: ${err.type.toUpperCase()}`)
|
|
207
|
+
if (err.line > 0) parts.push(`Línea: ${err.line}`)
|
|
208
|
+
parts.push(`Mensaje: ${err.message}`)
|
|
209
|
+
parts.push("")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Sugerencias de fix según tipo de error
|
|
213
|
+
parts.push("## Sugerencias de Corrección")
|
|
214
|
+
parts.push("")
|
|
215
|
+
|
|
216
|
+
const seenTypes = new Set(errors.map((e) => e.type))
|
|
217
|
+
if (seenTypes.has("annotation")) {
|
|
218
|
+
parts.push("- 🔧 Anotaciones: verifica que @kind, @limit, @use estén presentes y correctos")
|
|
219
|
+
}
|
|
220
|
+
if (seenTypes.has("limit")) {
|
|
221
|
+
parts.push("- 🔧 Límite: extrae lógica a sub-funciones o acorta el archivo")
|
|
222
|
+
}
|
|
223
|
+
if (seenTypes.has("type")) {
|
|
224
|
+
parts.push("- 🔧 Tipos: verifica interfaces, tipos de retorno y parámetros")
|
|
225
|
+
}
|
|
226
|
+
if (seenTypes.has("syntax")) {
|
|
227
|
+
parts.push("- 🔧 Sintaxis: revisa paréntesis, llaves, puntos y coma")
|
|
228
|
+
}
|
|
229
|
+
if (seenTypes.has("module")) {
|
|
230
|
+
parts.push("- 🔧 Módulo: verifica imports y exports")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
parts.push("")
|
|
234
|
+
parts.push("Devuelve el código COMPLETO corregido.")
|
|
235
|
+
|
|
236
|
+
return parts.join("\n")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ──────────────────────────────────────────────
|
|
240
|
+
// Escalado de complejidad
|
|
241
|
+
// ──────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reduce la complejidad de una tarea cuando el validation loop falla.
|
|
245
|
+
*
|
|
246
|
+
* Estrategias:
|
|
247
|
+
* 1. Simplificar: reducir features del código a lo mínimo
|
|
248
|
+
* 2. Dividir: partir en sub-tareas más pequeñas
|
|
249
|
+
* 3. Skeleton: usar el skeleton básico sin lógica extra
|
|
250
|
+
*
|
|
251
|
+
* @param {string} task - Descripción de la tarea
|
|
252
|
+
* @param {number} attempt - Intento actual (1-indexed)
|
|
253
|
+
* @returns {{ description: string, simplified: boolean, strategy: string }}
|
|
254
|
+
*/
|
|
255
|
+
export function escalateDown(task, attempt) {
|
|
256
|
+
const strategies = [
|
|
257
|
+
// Intento 1: sin cambios (primer intento)
|
|
258
|
+
() => ({ description: task, simplified: false, strategy: "initial" }),
|
|
259
|
+
|
|
260
|
+
// Intento 2: simplificar descripción
|
|
261
|
+
() => ({
|
|
262
|
+
description: task
|
|
263
|
+
.replace(/con .+/i, "")
|
|
264
|
+
.replace(/usando .+/i, "")
|
|
265
|
+
.replace(/que .+/i, "")
|
|
266
|
+
.replace(/ para .+/i, ""),
|
|
267
|
+
simplified: true,
|
|
268
|
+
strategy: "simplify-description",
|
|
269
|
+
}),
|
|
270
|
+
|
|
271
|
+
// Intento 3: solo estructura básica
|
|
272
|
+
() => ({
|
|
273
|
+
description: `Genera la estructura básica de ${task.split(" ").slice(0, 3).join(" ")} sin lógica compleja`,
|
|
274
|
+
simplified: true,
|
|
275
|
+
strategy: "basic-structure",
|
|
276
|
+
}),
|
|
277
|
+
|
|
278
|
+
// Intento 4: solo skeleton vacío
|
|
279
|
+
() => ({
|
|
280
|
+
description: `Genera un esqueleto vacío de ${task.split(" ").slice(0, 3).join(" ")} solo con anotaciones OPL`,
|
|
281
|
+
simplified: true,
|
|
282
|
+
strategy: "empty-skeleton",
|
|
283
|
+
}),
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
const strategyIndex = Math.min(attempt - 1, strategies.length - 1)
|
|
287
|
+
return strategies[strategyIndex]()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ──────────────────────────────────────────────
|
|
291
|
+
// Feedback loop principal
|
|
292
|
+
// ──────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Ejecuta el validation feedback loop completo.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} code - Código generado inicial
|
|
298
|
+
* @param {string} kind - Tipo de archivo (hook, component, etc.)
|
|
299
|
+
* @param {object} profile - Perfil Boost
|
|
300
|
+
* @param {object} [options] - Opciones adicionales
|
|
301
|
+
* @param {function} [options.onValidate] - Callback por cada intento (para logging)
|
|
302
|
+
* @param {function} [options.correctWithAgent] - Función asíncrona para corregir código con agente
|
|
303
|
+
* Si se omite, se usa el agente 'validate' del pool (BOOST-005)
|
|
304
|
+
* @returns {Promise<{ code: string, valid: boolean, history: Array, report: object }>}
|
|
305
|
+
*/
|
|
306
|
+
export async function feedbackLoop(code, kind, profile, options = {}) {
|
|
307
|
+
const maxRetries = profile?.validationLoops ?? MAX_RETRIES_DEFAULT
|
|
308
|
+
const history = []
|
|
309
|
+
let currentCode = code
|
|
310
|
+
let finalValid = false
|
|
311
|
+
let totalAttempts = 0
|
|
312
|
+
|
|
313
|
+
for (let attempt = 1; attempt <= maxRetries + 2; attempt++) {
|
|
314
|
+
totalAttempts = attempt
|
|
315
|
+
|
|
316
|
+
// Validar
|
|
317
|
+
const validation = validateCode(currentCode, kind)
|
|
318
|
+
const attemptInfo = {
|
|
319
|
+
attempt,
|
|
320
|
+
valid: validation.valid,
|
|
321
|
+
errorCount: validation.errors.length,
|
|
322
|
+
errors: validation.errors,
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
history.push(attemptInfo)
|
|
327
|
+
|
|
328
|
+
if (options.onValidate) {
|
|
329
|
+
options.onValidate(attemptInfo)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Si pasa validación, terminamos
|
|
333
|
+
if (validation.valid) {
|
|
334
|
+
finalValid = true
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Si es el último intento, intentamos escalar
|
|
339
|
+
if (attempt >= maxRetries + 1) {
|
|
340
|
+
const escalated = escalateDown(kind, attempt)
|
|
341
|
+
|
|
342
|
+
if (escalated.simplified) {
|
|
343
|
+
history.push({
|
|
344
|
+
attempt: attempt + 0.5,
|
|
345
|
+
action: "escalate-down",
|
|
346
|
+
strategy: escalated.strategy,
|
|
347
|
+
description: escalated.description,
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// Si tenemos función de corrección, la usamos
|
|
351
|
+
if (options.correctWithAgent) {
|
|
352
|
+
const corrected = await options.correctWithAgent(currentCode, validation.errors, escalated.description)
|
|
353
|
+
currentCode = corrected
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Último recurso: devolver código sin validar con reporte
|
|
358
|
+
break
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Intentar corregir con el agente validate
|
|
363
|
+
if (options.correctWithAgent) {
|
|
364
|
+
const feedback = buildFeedback(validation.errors)
|
|
365
|
+
const corrected = await options.correctWithAgent(currentCode, validation.errors, feedback)
|
|
366
|
+
currentCode = corrected
|
|
367
|
+
} else {
|
|
368
|
+
// Sin agente de corrección, no podemos continuar
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Generar reporte
|
|
374
|
+
const report = generateReport(history, totalAttempts, finalValid)
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
code: currentCode,
|
|
378
|
+
valid: finalValid,
|
|
379
|
+
history,
|
|
380
|
+
report,
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ──────────────────────────────────────────────
|
|
385
|
+
// Reporte de calidad
|
|
386
|
+
// ──────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Genera un reporte estructurado del proceso de validación.
|
|
390
|
+
*
|
|
391
|
+
* @param {Array} history - Historial de intentos
|
|
392
|
+
* @param {number} totalAttempts - Total de intentos
|
|
393
|
+
* @param {boolean} finalValid - Si el código final es válido
|
|
394
|
+
* @returns {object} Reporte estructurado
|
|
395
|
+
*/
|
|
396
|
+
export function generateReport(history, totalAttempts, finalValid) {
|
|
397
|
+
const errorHistory = history.filter((h) => h.errors && h.errors.length > 0)
|
|
398
|
+
const allErrors = errorHistory.flatMap((h) => h.errors || [])
|
|
399
|
+
|
|
400
|
+
// Agrupar errores por tipo
|
|
401
|
+
const errorTypes = {}
|
|
402
|
+
for (const err of allErrors) {
|
|
403
|
+
errorTypes[err.type] = (errorTypes[err.type] || 0) + 1
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Intentos de escalado
|
|
407
|
+
const escalations = history.filter((h) => h.action === "escalate-down")
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
finalValid,
|
|
411
|
+
totalAttempts,
|
|
412
|
+
errorsFound: allErrors.length,
|
|
413
|
+
uniqueErrorTypes: Object.keys(errorTypes).length,
|
|
414
|
+
errorDistribution: errorTypes,
|
|
415
|
+
escalations: escalations.map((e) => ({ strategy: e.strategy })),
|
|
416
|
+
summary: finalValid
|
|
417
|
+
? `✅ Código válido tras ${totalAttempts} intento(s). ${allErrors.length} error(es) corregido(s).`
|
|
418
|
+
: `⚠️ Código NO validado tras ${totalAttempts} intento(s). ${allErrors.length} error(es) pendiente(s).`,
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ──────────────────────────────────────────────
|
|
423
|
+
// Utilidades
|
|
424
|
+
// ──────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
function getTempDir() {
|
|
427
|
+
return TEMP_DIR
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Wrapper simplificado para usar el feedback loop con el agent-pool.
|
|
432
|
+
*
|
|
433
|
+
* @param {string} code - Código inicial
|
|
434
|
+
* @param {string} kind - Tipo de archivo
|
|
435
|
+
* @param {object} profile - Perfil Boost
|
|
436
|
+
* @param {object} agentPool - Instancia del AgentPool (importada de agent-pool.js)
|
|
437
|
+
* @returns {Promise<{ code: string, valid: boolean, report: object }>}
|
|
438
|
+
*/
|
|
439
|
+
export async function feedbackLoopWithAgent(code, kind, profile, agentPool) {
|
|
440
|
+
return feedbackLoop(code, kind, profile, {
|
|
441
|
+
async correctWithAgent(currentCode, errors, feedback) {
|
|
442
|
+
const result = await agentPool.submit("validate", feedback, profile)
|
|
443
|
+
return result.text
|
|
444
|
+
},
|
|
445
|
+
onValidate(info) {
|
|
446
|
+
if (!info.valid) {
|
|
447
|
+
console.warn(` ↻ Intento ${info.attempt}: ${info.errorCount} error(es)`)
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Función determinística de validación rápida (sin ejecutar validate CLI).
|
|
455
|
+
* Útil para pre-validación antes del feedback loop completo.
|
|
456
|
+
*
|
|
457
|
+
* @param {string} code - Código a validar
|
|
458
|
+
* @param {string} kind - Tipo de archivo
|
|
459
|
+
* @returns {{ valid: boolean, warnings: string[] }}
|
|
460
|
+
*/
|
|
461
|
+
export function quickValidate(code, kind) {
|
|
462
|
+
const warnings = []
|
|
463
|
+
const lines = code.split("\n")
|
|
464
|
+
const lineCount = lines.length
|
|
465
|
+
|
|
466
|
+
// Límites por @kind
|
|
467
|
+
const LIMITS = { hook: 80, component: 120, page: 200, service: 150, store: 100, util: 100 }
|
|
468
|
+
const limit = LIMITS[kind] || 200
|
|
469
|
+
|
|
470
|
+
if (lineCount > limit) {
|
|
471
|
+
warnings.push(`Excede límite de ${limit} líneas para @kind(${kind}): ${lineCount} líneas`)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Verificar anotaciones mínimas
|
|
475
|
+
const hasKind = /@kind\(\w+\)/.test(code)
|
|
476
|
+
if (!hasKind) {
|
|
477
|
+
warnings.push("Falta anotación @kind()")
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const hasUse = /@use\(/.test(code)
|
|
481
|
+
if (!hasUse) {
|
|
482
|
+
warnings.push("Falta anotación @use()")
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Verificar export con nombre
|
|
486
|
+
const hasNamedExport = /export\s+(function|const|class|interface|type)\s+\w+/.test(code)
|
|
487
|
+
if (!hasNamedExport) {
|
|
488
|
+
warnings.push("No se detectó export con nombre")
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
valid: warnings.length === 0,
|
|
493
|
+
warnings,
|
|
494
|
+
}
|
|
495
|
+
}
|