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,332 @@
|
|
|
1
|
+
// @use(kind, contract, limit, error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: kind, task, profile -> out: retrieveExamples -> example[] @error: NoExamplesFound)
|
|
4
|
+
// @limit(lines: 250)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Few-Shot Example Engine — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Busca ejemplos relevantes en el proyecto y knowledge-repo para
|
|
10
|
+
* inyectarlos como few-shot examples en los prompts de los agentes.
|
|
11
|
+
*
|
|
12
|
+
* Los modelos pequeños mejoran dramáticamente cuando tienen ejemplos
|
|
13
|
+
* concretos del mismo dominio y @kind antes de generar código.
|
|
14
|
+
*
|
|
15
|
+
* Uso:
|
|
16
|
+
* import { retrieveExamples, buildFewShotPrompt } from './boost/fewshot-retriever.js'
|
|
17
|
+
*
|
|
18
|
+
* const examples = retrieveExamples('hook', 'crea hook useAuth con Supabase')
|
|
19
|
+
* const prompt = buildFewShotPrompt(examples, 'Genera un hook useAuth...')
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "fs"
|
|
23
|
+
import { join, extname } from "path"
|
|
24
|
+
|
|
25
|
+
// ──────────────────────────────────────────────
|
|
26
|
+
// Configuración
|
|
27
|
+
// ──────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const MAX_EXAMPLES = 3
|
|
30
|
+
const MAX_EXAMPLE_LINES = 60
|
|
31
|
+
const EXAMPLE_MATCH_THRESHOLD = 0.3
|
|
32
|
+
|
|
33
|
+
// ──────────────────────────────────────────────
|
|
34
|
+
// Búsqueda en directorios del proyecto
|
|
35
|
+
// ──────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Busca ejemplos en los directorios del proyecto según el @kind.
|
|
39
|
+
* Busca archivos con patrones de naming y contenido relevante.
|
|
40
|
+
*/
|
|
41
|
+
const SEARCH_PATTERNS = {
|
|
42
|
+
hook: [
|
|
43
|
+
{ dir: "src", pattern: ["**/hooks/**/*.ts", "**/hooks/**/*.tsx"], kindAnnotation: "hook" },
|
|
44
|
+
{ dir: "src", pattern: ["**/lib/hooks/**/*.ts"], kindAnnotation: "hook" },
|
|
45
|
+
{ dir: "src", pattern: ["**/features/*/hooks/*.ts"], kindAnnotation: "hook" },
|
|
46
|
+
],
|
|
47
|
+
component: [
|
|
48
|
+
{ dir: "src", pattern: ["**/components/**/*.tsx"], kindAnnotation: "component" },
|
|
49
|
+
{ dir: "src", pattern: ["**/ui/**/*.tsx"], kindAnnotation: "component" },
|
|
50
|
+
{ dir: "src", pattern: ["**/features/*/components/*.tsx"], kindAnnotation: "component" },
|
|
51
|
+
],
|
|
52
|
+
page: [
|
|
53
|
+
{ dir: "src", pattern: ["**/pages/**/*.tsx", "**/app/**/page.tsx"], kindAnnotation: "page" },
|
|
54
|
+
{ dir: "src", pattern: ["**/features/*/pages/*.tsx"], kindAnnotation: "page" },
|
|
55
|
+
],
|
|
56
|
+
service: [
|
|
57
|
+
{ dir: "src", pattern: ["**/lib/api/**/*.ts", "**/services/**/*.ts"], kindAnnotation: "service" },
|
|
58
|
+
{ dir: "src", pattern: ["**/lib/*.ts"], kindAnnotation: "service" },
|
|
59
|
+
],
|
|
60
|
+
store: [
|
|
61
|
+
{ dir: "src", pattern: ["**/stores/**/*.ts", "**/store/**/*.ts"], kindAnnotation: "store" },
|
|
62
|
+
{ dir: "src", pattern: ["**/lib/stores/**/*.ts"], kindAnnotation: "store" },
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Escanea un directorio buscando archivos que coincidan con un patrón glob simplificado.
|
|
68
|
+
* Esta es una versión básica sin glob — solo busca por extensión y nombre de directorio.
|
|
69
|
+
*/
|
|
70
|
+
function scanDir(baseDir, searchPath, keyword) {
|
|
71
|
+
const results = []
|
|
72
|
+
const fullDir = join(process.cwd(), searchPath.dir)
|
|
73
|
+
|
|
74
|
+
if (!existsSync(fullDir)) return results
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const files = walkDir(fullDir, [])
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
const ext = extname(file)
|
|
80
|
+
if (!ext.match(/\.(ts|tsx|js|jsx)$/)) continue
|
|
81
|
+
|
|
82
|
+
// Verificar si el path coincide con el patrón
|
|
83
|
+
const relativePath = file.replace(process.cwd(), "")
|
|
84
|
+
const hasKeywordInPath = !keyword || relativePath.toLowerCase().includes(keyword.toLowerCase())
|
|
85
|
+
|
|
86
|
+
// Leer primeras líneas para verificar @kind
|
|
87
|
+
try {
|
|
88
|
+
const content = readFileSync(file, "utf-8")
|
|
89
|
+
const firstLines = content.split("\n").slice(0, 10).join("\n")
|
|
90
|
+
const hasKindAnnotation = firstLines.includes(`@kind(${searchPath.kindAnnotation})`)
|
|
91
|
+
|
|
92
|
+
if (hasKindAnnotation && hasKeywordInPath) {
|
|
93
|
+
results.push({
|
|
94
|
+
file: relativePath,
|
|
95
|
+
content: content.split("\n").slice(0, MAX_EXAMPLE_LINES).join("\n"),
|
|
96
|
+
kind: searchPath.kindAnnotation,
|
|
97
|
+
lines: content.split("\n").length,
|
|
98
|
+
annotation: readAnnotations(firstLines),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Archivo no legible
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Directorio no accesible
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return results
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function walkDir(dir, files) {
|
|
113
|
+
try {
|
|
114
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const fullPath = join(dir, entry.name)
|
|
117
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !entry.name.startsWith("node_modules")) {
|
|
118
|
+
walkDir(fullPath, files)
|
|
119
|
+
} else if (entry.isFile()) {
|
|
120
|
+
files.push(fullPath)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Directorio no accesible
|
|
125
|
+
}
|
|
126
|
+
return files
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readAnnotations(firstLines) {
|
|
130
|
+
const annotations = []
|
|
131
|
+
const lines = firstLines.split("\n")
|
|
132
|
+
for (const line of lines.slice(0, 5)) {
|
|
133
|
+
const match = line.match(/\/\/\s*@(\w+)\((.*)\)/)
|
|
134
|
+
if (match) {
|
|
135
|
+
annotations.push({ name: match[1], value: match[2] })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return annotations
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ──────────────────────────────────────────────
|
|
142
|
+
// Ranking de relevancia
|
|
143
|
+
// ──────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Calcula un score de relevancia entre un ejemplo y la tarea.
|
|
147
|
+
*
|
|
148
|
+
* Factores:
|
|
149
|
+
* 1. Matching de @kind (peso 0.4)
|
|
150
|
+
* 2. Palabras clave compartidas (peso 0.3)
|
|
151
|
+
* 3. Tamaño del archivo (peso 0.2) — preferir archivos pequeños
|
|
152
|
+
* 4. Cercanía en el filesystem (peso 0.1)
|
|
153
|
+
*/
|
|
154
|
+
function scoreRelevance(example, kind, taskKeywords) {
|
|
155
|
+
let score = 0
|
|
156
|
+
|
|
157
|
+
// Factor 1: @kind matching (0-0.4)
|
|
158
|
+
if (example.kind === kind) {
|
|
159
|
+
score += 0.4
|
|
160
|
+
} else {
|
|
161
|
+
score += 0.1 // algo de relevancia aunque sea otro tipo
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Factor 2: Palabras clave compartidas (0-0.3)
|
|
165
|
+
if (taskKeywords.length > 0) {
|
|
166
|
+
const content = example.content.toLowerCase()
|
|
167
|
+
const matchedKeywords = taskKeywords.filter((kw) => content.includes(kw.toLowerCase()))
|
|
168
|
+
score += (matchedKeywords.length / taskKeywords.length) * 0.3
|
|
169
|
+
} else {
|
|
170
|
+
score += 0.15
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Factor 3: Tamaño del archivo (0-0.2) — preferir archivos más pequeños
|
|
174
|
+
const lineScore = Math.max(0, 1 - example.lines / 100)
|
|
175
|
+
score += lineScore * 0.2
|
|
176
|
+
|
|
177
|
+
// Factor 4: Cercanía de path (0-0.1) — ejemplos en directorios similares tienen más peso
|
|
178
|
+
const pathDepth = example.file.split("/").length
|
|
179
|
+
score += (1 / pathDepth) * 0.1
|
|
180
|
+
|
|
181
|
+
return Math.min(score, 1.0)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function extractKeywordsFromTask(task) {
|
|
185
|
+
const stopWords = new Set([
|
|
186
|
+
"el", "la", "los", "las", "un", "una", "de", "del", "en", "con",
|
|
187
|
+
"para", "por", "que", "es", "se", "no", "su", "lo", "como", "más",
|
|
188
|
+
"pero", "sus", "le", "ya", "este", "entre", "porque", "the", "a",
|
|
189
|
+
"an", "of", "in", "to", "for", "and", "or", "is", "it", "on", "at",
|
|
190
|
+
"crea", "crear", "hacer", "haz", "genera", "generar", "nuevo", "nueva",
|
|
191
|
+
])
|
|
192
|
+
return task.toLowerCase().split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ──────────────────────────────────────────────
|
|
196
|
+
// API: retrieveExamples
|
|
197
|
+
// ──────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Busca ejemplos relevantes para una tarea de generación.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} kind - Tipo de archivo a generar (hook, component, page, service, store)
|
|
203
|
+
* @param {string} task - Descripción de la tarea
|
|
204
|
+
* @param {object} [options] - Opciones adicionales
|
|
205
|
+
* @param {number} [options.maxExamples=3] - Máximo de ejemplos a retornar
|
|
206
|
+
* @param {number} [options.threshold=0.3] - Umbral mínimo de relevancia
|
|
207
|
+
* @param {boolean} [options.includeTemplates=true] - Incluir templates de OPL como fallback
|
|
208
|
+
* @returns {Array<{ file: string, content: string, kind: string, score: number, annotations: Array }>}
|
|
209
|
+
*/
|
|
210
|
+
export function retrieveExamples(kind, task, options = {}) {
|
|
211
|
+
const maxExamples = options.maxExamples || MAX_EXAMPLES
|
|
212
|
+
const threshold = options.threshold || EXAMPLE_MATCH_THRESHOLD
|
|
213
|
+
const includeTemplates = options.includeTemplates !== false
|
|
214
|
+
const taskKeywords = extractKeywordsFromTask(task)
|
|
215
|
+
|
|
216
|
+
// 1. Buscar en el proyecto
|
|
217
|
+
const patterns = SEARCH_PATTERNS[kind] || SEARCH_PATTERNS.component
|
|
218
|
+
let candidates = []
|
|
219
|
+
|
|
220
|
+
for (const pattern of patterns) {
|
|
221
|
+
const results = scanDir("", pattern)
|
|
222
|
+
candidates.push(...results)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 2. Calcular relevancia
|
|
226
|
+
const scored = candidates.map((c) => ({
|
|
227
|
+
...c,
|
|
228
|
+
score: scoreRelevance(c, kind, taskKeywords),
|
|
229
|
+
}))
|
|
230
|
+
|
|
231
|
+
// 3. Ordenar por score descendente
|
|
232
|
+
scored.sort((a, b) => b.score - a.score)
|
|
233
|
+
|
|
234
|
+
// 4. Filtrar por umbral
|
|
235
|
+
let filtered = scored.filter((e) => e.score >= threshold)
|
|
236
|
+
|
|
237
|
+
// 5. Si no hay suficientes ejemplos, buscar en otras carpetas
|
|
238
|
+
if (filtered.length < maxExamples && includeTemplates) {
|
|
239
|
+
// Buscar en todos los patrones (cross-kind) como fallback
|
|
240
|
+
for (const [otherKind, otherPatterns] of Object.entries(SEARCH_PATTERNS)) {
|
|
241
|
+
if (otherKind === kind) continue
|
|
242
|
+
for (const pattern of otherPatterns) {
|
|
243
|
+
const results = scanDir("", pattern)
|
|
244
|
+
for (const r of results) {
|
|
245
|
+
const existing = filtered.find((f) => f.file === r.file)
|
|
246
|
+
if (!existing) {
|
|
247
|
+
const sc = scoreRelevance(r, kind, taskKeywords)
|
|
248
|
+
if (sc >= threshold * 0.5) {
|
|
249
|
+
filtered.push({ ...r, score: sc })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Re-ordenar
|
|
257
|
+
filtered.sort((a, b) => b.score - a.score)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return filtered.slice(0, maxExamples)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ──────────────────────────────────────────────
|
|
264
|
+
// API: buildFewShotPrompt
|
|
265
|
+
// ──────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Construye un prompt con ejemplos few-shot inyectados.
|
|
269
|
+
*
|
|
270
|
+
* @param {Array} examples - Resultados de retrieveExamples()
|
|
271
|
+
* @param {string} userPrompt - Prompt del usuario para la tarea
|
|
272
|
+
* @returns {string} Prompt completo con ejemplos
|
|
273
|
+
*/
|
|
274
|
+
export function buildFewShotPrompt(examples, userPrompt) {
|
|
275
|
+
if (!examples || examples.length === 0) {
|
|
276
|
+
return userPrompt
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const parts = [
|
|
280
|
+
"## Instrucción",
|
|
281
|
+
"",
|
|
282
|
+
"Genera código siguiendo el mismo patrón que los ejemplos siguientes.",
|
|
283
|
+
"Respeta las anotaciones OPL (@kind, @limit, @contract/@props/@compose).",
|
|
284
|
+
"",
|
|
285
|
+
"## Ejemplos",
|
|
286
|
+
"",
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
for (let i = 0; i < examples.length; i++) {
|
|
290
|
+
const ex = examples[i]
|
|
291
|
+
parts.push(`### Ejemplo ${i + 1}: ${ex.file}`)
|
|
292
|
+
parts.push(`Tipo: ${ex.kind} | Relevancia: ${(ex.score * 100).toFixed(0)}%`)
|
|
293
|
+
parts.push("")
|
|
294
|
+
parts.push("```typescript")
|
|
295
|
+
parts.push(ex.content || "// (contenido no disponible)")
|
|
296
|
+
parts.push("```")
|
|
297
|
+
parts.push("")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
parts.push("## Tarea")
|
|
301
|
+
parts.push("")
|
|
302
|
+
parts.push(userPrompt)
|
|
303
|
+
|
|
304
|
+
return parts.join("\n")
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ──────────────────────────────────────────────
|
|
308
|
+
// API: exampleStats
|
|
309
|
+
// ──────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Estadísticas de ejemplos encontrados en el proyecto.
|
|
313
|
+
* Útil para diagnóstico (opl boost check).
|
|
314
|
+
*
|
|
315
|
+
* @returns {object} { total: number, byKind: object, lastUpdated: string }
|
|
316
|
+
*/
|
|
317
|
+
export function exampleStats() {
|
|
318
|
+
const stats = { total: 0, byKind: {} }
|
|
319
|
+
|
|
320
|
+
for (const [kind, patterns] of Object.entries(SEARCH_PATTERNS)) {
|
|
321
|
+
for (const pattern of patterns) {
|
|
322
|
+
const results = scanDir("", pattern)
|
|
323
|
+
stats.total += results.length
|
|
324
|
+
stats.byKind[kind] = (stats.byKind[kind] || 0) + results.length
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
...stats,
|
|
330
|
+
lastUpdated: new Date().toISOString(),
|
|
331
|
+
}
|
|
332
|
+
}
|