openprompt-lang 0.8.0 → 0.9.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/docs/FRAMEWORK.md +292 -1707
- package/package.json +1 -1
- package/src/ai/prompt-builder.js +76 -2
- package/src/cli/commands-context.js +29 -1
- package/src/cli/commands-knowledge.js +1 -0
- package/src/cli/commands-learning.js +1 -0
- package/src/cli/commands-misc.js +70 -0
- package/src/commands/context.js +177 -5
- package/src/commands/doctor.js +241 -0
- package/src/commands/knowledge-query.js +123 -2
- package/src/commands/learning.js +37 -14
- package/src/commands/qa-gen.js +114 -2
- package/src/commands/recall.js +152 -0
- package/src/commands/suggest.js +33 -6
- package/src/commands/ticket.js +330 -0
- package/src/commands/work-context.js +45 -0
- package/src/mcp-server.js +2 -2
- package/src/templates/langs/react/INDEX.json +202 -330
- package/src/templates/langs/react/templates/hooks/use-comparison.ts +69 -0
- package/src/templates/langs/react/templates/hooks/use-reducer-form.ts +176 -0
- package/src/templates/langs/react/templates/patterns/dual-mode-api.ts +313 -0
- package/src/templates/langs/react/templates/patterns/lazy-routes.tsx +101 -0
- package/src/templates/langs/react/templates/patterns/shared-constants.ts +49 -0
- package/src/templates/langs/react/templates/ui/modal-accessible.tsx +155 -0
- package/src/utils/error-learner.js +169 -22
package/package.json
CHANGED
package/src/ai/prompt-builder.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { readFileSync } from 'fs'
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from 'fs'
|
|
2
2
|
import { join } from 'path'
|
|
3
3
|
import { getLanguageIndex, getLanguagePath } from '../utils/language-loader.js'
|
|
4
|
+
import { searchKnowledge } from '../utils/knowledge-search.js'
|
|
4
5
|
|
|
5
6
|
export function buildPrompt (description, options = {}) {
|
|
6
7
|
const langId = options.lang || 'react'
|
|
7
8
|
const index = getLanguageIndex(langId)
|
|
8
9
|
const profile = options.profile || 'mid'
|
|
10
|
+
const baseDir = options.dir || process.cwd()
|
|
9
11
|
|
|
10
12
|
const relevantTemplates = findRelevantTemplates(description, index)
|
|
11
13
|
|
|
@@ -20,6 +22,10 @@ export function buildPrompt (description, options = {}) {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// Inject project context: session, learning, learn-errors, config
|
|
26
|
+
const projectContext = buildProjectContext(baseDir, langId, description)
|
|
27
|
+
const knowledgeContext = buildKnowledgeContext(description)
|
|
28
|
+
|
|
23
29
|
const kind = detectKind(description)
|
|
24
30
|
const componentName = options.name || suggestName(description, kind)
|
|
25
31
|
|
|
@@ -37,12 +43,14 @@ export function buildPrompt (description, options = {}) {
|
|
|
37
43
|
'- Include @use() annotation at the top with the tags you use',
|
|
38
44
|
'- Include @kind, @props (if component), @contract (if hook/service), @limit, @test annotations',
|
|
39
45
|
templateExamples,
|
|
46
|
+
projectContext,
|
|
47
|
+
knowledgeContext,
|
|
40
48
|
`## Language: ${langId}`,
|
|
41
49
|
'## Profile rules:',
|
|
42
50
|
...getProfileRules(profile),
|
|
43
51
|
'',
|
|
44
52
|
'Return ONLY the code, no markdown wrapping or explanation.'
|
|
45
|
-
].join('\n')
|
|
53
|
+
].filter(Boolean).join('\n')
|
|
46
54
|
|
|
47
55
|
return { componentName, kind, systemPrompt, userPrompt, prompt: userPrompt }
|
|
48
56
|
}
|
|
@@ -180,3 +188,69 @@ function getProfileRules (profile) {
|
|
|
180
188
|
|
|
181
189
|
return rules[profile] || rules.mid
|
|
182
190
|
}
|
|
191
|
+
|
|
192
|
+
function buildProjectContext (baseDir, langId, description) {
|
|
193
|
+
const parts = []
|
|
194
|
+
|
|
195
|
+
// Session
|
|
196
|
+
const sessionPath = join(baseDir, '.opencode', 'work-context', 'SESSION.json')
|
|
197
|
+
if (existsSync(sessionPath)) {
|
|
198
|
+
try {
|
|
199
|
+
const session = JSON.parse(readFileSync(sessionPath, 'utf-8'))
|
|
200
|
+
if (session?.session?.task?.description) {
|
|
201
|
+
parts.push(`- Current session task: ${session.session.task.description}`)
|
|
202
|
+
}
|
|
203
|
+
} catch {}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Project config
|
|
207
|
+
const configPath = join(baseDir, 'prompt-lang.json')
|
|
208
|
+
if (existsSync(configPath)) {
|
|
209
|
+
try {
|
|
210
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
211
|
+
parts.push(`- Project: ${config.name || 'unnamed'} (${config.lenguaje || langId})`)
|
|
212
|
+
if (config.stack?.base) parts.push(`- Stack: ${config.stack.base.join(', ')}`)
|
|
213
|
+
if (config.stack?.extensions?.length) parts.push(`- Extensions: ${config.stack.extensions.join(', ')}`)
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Learning concepts relevant to description
|
|
218
|
+
const learningDir = join(baseDir, '.opencode', 'learning', 'concepts')
|
|
219
|
+
if (existsSync(learningDir)) {
|
|
220
|
+
try {
|
|
221
|
+
const desc = description.toLowerCase()
|
|
222
|
+
const found = []
|
|
223
|
+
for (const cat of readdirSync(learningDir)) {
|
|
224
|
+
const catDir = join(learningDir, cat)
|
|
225
|
+
for (const entry of readdirSync(catDir)) {
|
|
226
|
+
const conceptMd = join(catDir, entry, 'concept.md')
|
|
227
|
+
if (existsSync(conceptMd)) {
|
|
228
|
+
const content = readFileSync(conceptMd, 'utf-8').toLowerCase()
|
|
229
|
+
if (content.includes(desc) || entry.includes(desc)) {
|
|
230
|
+
found.push(`${entry} (${cat})`)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (found.length > 0) parts.push(`- Relevant learning concepts: ${found.slice(0, 3).join(', ')}`)
|
|
236
|
+
} catch {}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (parts.length === 0) return ''
|
|
240
|
+
return '\n## Project context:\n' + parts.map(p => `${p}`).join('\n') + '\n'
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function buildKnowledgeContext (description) {
|
|
244
|
+
try {
|
|
245
|
+
const kResults = searchKnowledge(description, { limit: 3 })
|
|
246
|
+
if (!kResults || kResults.length === 0) return ''
|
|
247
|
+
|
|
248
|
+
const parts = ['\n## Relevant knowledge:\n']
|
|
249
|
+
for (const r of kResults) {
|
|
250
|
+
parts.push(`- ${r.title || r.bookTitle}: ${(r.snippet || '').slice(0, 150)}`)
|
|
251
|
+
}
|
|
252
|
+
return parts.join('\n') + '\n'
|
|
253
|
+
} catch {
|
|
254
|
+
return ''
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -15,11 +15,39 @@ export function register(program) {
|
|
|
15
15
|
.option('--domain <domain>', 'Filtrar por dominio de negocio (ecommerce, saas, mobile, api, admin, blog)')
|
|
16
16
|
.option('--projects <list>', 'Proyectos vecinos a referenciar (separados por coma)')
|
|
17
17
|
.option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
|
|
18
|
+
.option('--unified', 'Búsqueda unificada: knowledge + learning + templates + tickets + semántico')
|
|
18
19
|
.action(async (options) => {
|
|
19
|
-
const { context } = await import('../commands/context.js')
|
|
20
|
+
const { context, contextUnified } = await import('../commands/context.js')
|
|
21
|
+
if (options.unified) {
|
|
22
|
+
await contextUnified(options)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
20
25
|
await context(options)
|
|
21
26
|
})
|
|
22
27
|
|
|
28
|
+
program
|
|
29
|
+
.command('docs')
|
|
30
|
+
.description('Generar FRAMEWORK.md desde el estado actual del proyecto')
|
|
31
|
+
.option('--output <file>', 'Archivo de salida', '.openprompt/FRAMEWORK.md')
|
|
32
|
+
.option('--dir <path>', 'Directorio del proyecto (default: cwd)')
|
|
33
|
+
.option('--dry-run', 'Mostrar sin escribir archivo')
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
const { generateFrameworkMd } = await import('../utils/generate-framework-md.js')
|
|
36
|
+
const output = generateFrameworkMd({ dir: options.dir || process.cwd() })
|
|
37
|
+
if (options.dryRun) {
|
|
38
|
+
console.log(output.slice(0, 2000))
|
|
39
|
+
console.log('\n... (truncado en dry-run)')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
const { writeFileSync, existsSync, mkdirSync } = await import('fs')
|
|
43
|
+
const { join, dirname } = await import('path')
|
|
44
|
+
const outputPath = join(options.dir || process.cwd(), options.output)
|
|
45
|
+
const outDir = dirname(outputPath)
|
|
46
|
+
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true })
|
|
47
|
+
writeFileSync(outputPath, output, 'utf-8')
|
|
48
|
+
console.log(`✅ FRAMEWORK.md generado: ${outputPath}`)
|
|
49
|
+
})
|
|
50
|
+
|
|
23
51
|
program
|
|
24
52
|
.command('validate')
|
|
25
53
|
.description('Ejecutar pipeline de validación y verificar anotaciones')
|
|
@@ -47,6 +47,7 @@ export function register(program) {
|
|
|
47
47
|
knowledge
|
|
48
48
|
.command('to-learning')
|
|
49
49
|
.description('Integrar PDFs procesados a la memoria semántica (learning)')
|
|
50
|
+
.option('--all', 'Procesar todos los PDFs disponibles')
|
|
50
51
|
.option('--pdf <id>', 'Integrar solo un PDF específico por ID')
|
|
51
52
|
.option('--force', 'Re-integran aunque ya estén integrados')
|
|
52
53
|
.action(async (options) => {
|
|
@@ -29,6 +29,7 @@ export function register(program) {
|
|
|
29
29
|
.command('add <name>')
|
|
30
30
|
.description('Crear un nuevo concepto con template')
|
|
31
31
|
.option('--category <cat>', 'Categoría del concepto (default: visual)')
|
|
32
|
+
.option('--tags <tags>', 'Tags separados por coma (ej: modal,accesibilidad)')
|
|
32
33
|
.action(async (name, options) => {
|
|
33
34
|
const { add } = await import('../commands/learning.js')
|
|
34
35
|
await add(name, options)
|
package/src/cli/commands-misc.js
CHANGED
|
@@ -125,11 +125,22 @@ export function register(program) {
|
|
|
125
125
|
.option('--template <id>', 'Generate tests only for a specific template')
|
|
126
126
|
.option('--dry-run', 'Preview without writing files')
|
|
127
127
|
.option('--no-scan', 'Skip scanning source for new @learn-error annotations')
|
|
128
|
+
.option('--from-tickets', 'Read tickets from .opencode/bugs/ and generate tests from them')
|
|
128
129
|
.action(async (options) => {
|
|
129
130
|
const { qaGen } = await import('../commands/qa-gen.js')
|
|
130
131
|
await qaGen(options)
|
|
131
132
|
})
|
|
132
133
|
|
|
134
|
+
program
|
|
135
|
+
.command('lint-file <file>')
|
|
136
|
+
.description('Lint a single file for OPL annotation errors')
|
|
137
|
+
.option('--strict', 'Enable strict mode (warnings become errors)')
|
|
138
|
+
.option('--lang <lang>', 'Language module for tag resolution (default: react)')
|
|
139
|
+
.action(async (file, options) => {
|
|
140
|
+
const { lintFileCli } = await import('../commands/qa-gen.js')
|
|
141
|
+
await lintFileCli(file, options)
|
|
142
|
+
})
|
|
143
|
+
|
|
133
144
|
program
|
|
134
145
|
.command('qa-learn')
|
|
135
146
|
.description('Parse @learn-error from a file and persist to language module')
|
|
@@ -175,4 +186,63 @@ export function register(program) {
|
|
|
175
186
|
const { startServer } = await import('../mcp-server.js')
|
|
176
187
|
await startServer()
|
|
177
188
|
})
|
|
189
|
+
|
|
190
|
+
const ticket = program
|
|
191
|
+
.command('ticket')
|
|
192
|
+
.description('Gestión de tickets de bugs (proyecto y OPL)')
|
|
193
|
+
|
|
194
|
+
ticket
|
|
195
|
+
.command('create')
|
|
196
|
+
.description('Crear un nuevo ticket de bug')
|
|
197
|
+
.option('--title <title>', 'Título del ticket')
|
|
198
|
+
.option('--severity <severity>', 'Severidad: critical, high, medium, low')
|
|
199
|
+
.option('--project <name>', 'Nombre del proyecto')
|
|
200
|
+
.option('--file <path>', 'Archivo fuente del bug')
|
|
201
|
+
.option('--description <desc>', 'Descripción del bug')
|
|
202
|
+
.option('--learn-error <id>', 'ID de @learn-error relacionado')
|
|
203
|
+
.option('--type <type>', 'Tipo: OPL (framework) o project (default)')
|
|
204
|
+
.action(async (options) => {
|
|
205
|
+
const { ticketCreate } = await import('../commands/ticket.js')
|
|
206
|
+
await ticketCreate(options)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
ticket
|
|
210
|
+
.command('list')
|
|
211
|
+
.description('Listar tickets del proyecto y OPL')
|
|
212
|
+
.option('--status <status>', 'Filtrar por estado: open, wip, fixed, wontfix')
|
|
213
|
+
.option('--severity <severity>', 'Filtrar por severidad')
|
|
214
|
+
.option('--project <name>', 'Filtrar por proyecto')
|
|
215
|
+
.action(async (options) => {
|
|
216
|
+
const { ticketList } = await import('../commands/ticket.js')
|
|
217
|
+
await ticketList(options)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
ticket
|
|
221
|
+
.command('close <id>')
|
|
222
|
+
.description('Cerrar un ticket (marcar como fixed)')
|
|
223
|
+
.option('--fix <desc>', 'Descripción del fix aplicado')
|
|
224
|
+
.action(async (id, options) => {
|
|
225
|
+
const { ticketClose } = await import('../commands/ticket.js')
|
|
226
|
+
await ticketClose(id, options)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
program
|
|
230
|
+
.command('doctor')
|
|
231
|
+
.description('Health-check: verificar que OPL y el proyecto están sanos')
|
|
232
|
+
.option('--dir <path>', 'Directorio del proyecto (default: cwd)')
|
|
233
|
+
.option('--verbose', 'Mostrar detalles adicionales')
|
|
234
|
+
.action(async (options) => {
|
|
235
|
+
const { doctor } = await import('../commands/doctor.js')
|
|
236
|
+
await doctor(options)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
program
|
|
240
|
+
.command('recall <query>')
|
|
241
|
+
.description('Buscar en memoria del proyecto: aprendizajes, conceptos, errores, tickets')
|
|
242
|
+
.option('--lang <lang>', 'Módulo de lenguaje (default: react)')
|
|
243
|
+
.option('--dir <path>', 'Directorio del proyecto (default: cwd)')
|
|
244
|
+
.action(async (query, options) => {
|
|
245
|
+
const { recall } = await import('../commands/recall.js')
|
|
246
|
+
await recall(query, options)
|
|
247
|
+
})
|
|
178
248
|
}
|
package/src/commands/context.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
// @use(kind, contract, limit)
|
|
2
|
-
// @kind(
|
|
3
|
-
// @contract(in: options -> out: void, sideEffect: contexto.md)
|
|
4
|
-
// @limit(lines:
|
|
1
|
+
// @use(kind, contract, limit, learn-error)
|
|
2
|
+
// @kind(feature)
|
|
3
|
+
// @contract(in: options -> out: void, sideEffect: contexto.md + unified search)
|
|
4
|
+
// @limit(lines: 600)
|
|
5
5
|
|
|
6
6
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs'
|
|
7
|
-
import { join, relative, extname, basename } from 'path'
|
|
7
|
+
import { join, relative, extname, basename, dirname } from 'path'
|
|
8
|
+
import { fileURLToPath } from 'url'
|
|
8
9
|
import { loadConfig } from '../utils/config.js'
|
|
9
10
|
import { lintFile, parseAnnotations } from '../utils/annotations.js'
|
|
10
11
|
import { searchConcepts } from '../utils/semantic-index.js'
|
|
12
|
+
import { searchKnowledge } from '../utils/knowledge-search.js'
|
|
13
|
+
import { searchTemplates as langSearchTemplates } from '../utils/language-loader.js'
|
|
14
|
+
import { scanForLearnErrors } from '../utils/error-learner.js'
|
|
11
15
|
import chalk from 'chalk'
|
|
12
16
|
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
18
|
+
|
|
13
19
|
const IGNORED_DIRS = new Set([
|
|
14
20
|
'.git', 'node_modules', 'dist', 'build', '.next', '.gemini',
|
|
15
21
|
'coverage', '.nyc_output', '__pycache__', '.cache'
|
|
@@ -402,3 +408,169 @@ export async function context (options) {
|
|
|
402
408
|
if (options.domain) console.log(chalk.green(` Dominio: ${options.domain}`))
|
|
403
409
|
console.log(chalk.green(`📄 Output: ${outputFile}`))
|
|
404
410
|
}
|
|
411
|
+
|
|
412
|
+
export async function contextUnified (options) {
|
|
413
|
+
const baseDir = options.dir || process.cwd()
|
|
414
|
+
const query = options.query || ''
|
|
415
|
+
const domain = options.domain
|
|
416
|
+
const langId = options.lang || 'react'
|
|
417
|
+
|
|
418
|
+
console.log(chalk.cyan(`\n🔍 Búsqueda unificada: "${query || domain || 'todo'}"\n`))
|
|
419
|
+
|
|
420
|
+
const results = {
|
|
421
|
+
knowledge: [],
|
|
422
|
+
learning: [],
|
|
423
|
+
semantic: [],
|
|
424
|
+
templates: [],
|
|
425
|
+
tickets: [],
|
|
426
|
+
errors: []
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 1. Knowledge search
|
|
430
|
+
if (query) {
|
|
431
|
+
console.log(chalk.blue(' 📚 Buscando en knowledge...'))
|
|
432
|
+
try {
|
|
433
|
+
const kResults = searchKnowledge(query, { limit: 5 })
|
|
434
|
+
results.knowledge = (kResults || []).map(r => ({
|
|
435
|
+
title: r.title || r.bookTitle,
|
|
436
|
+
bookTitle: r.bookTitle || r.bookId,
|
|
437
|
+
snippet: r.snippet,
|
|
438
|
+
relevance: r.relevance || 1
|
|
439
|
+
}))
|
|
440
|
+
} catch { /* knowledge unavailable */ }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 2. Semantic index
|
|
444
|
+
if (query) {
|
|
445
|
+
try {
|
|
446
|
+
const semantic = searchConcepts(query, langId)
|
|
447
|
+
if (Array.isArray(semantic)) results.semantic = semantic
|
|
448
|
+
} catch {}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 3. Template search
|
|
452
|
+
if (query) {
|
|
453
|
+
try {
|
|
454
|
+
const langTemplates = langSearchTemplates(query, { lang: langId, limit: 5 })
|
|
455
|
+
results.templates = (langTemplates || []).map(t => ({
|
|
456
|
+
id: t.id,
|
|
457
|
+
name: t.name || t.id,
|
|
458
|
+
category: t.category || 'general',
|
|
459
|
+
description: t.description || ''
|
|
460
|
+
}))
|
|
461
|
+
} catch {}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 4. Learning concepts
|
|
465
|
+
const learningDir = join(baseDir, '.opencode', 'learning', 'concepts')
|
|
466
|
+
if (existsSync(learningDir)) {
|
|
467
|
+
try {
|
|
468
|
+
for (const cat of readdirSync(learningDir)) {
|
|
469
|
+
if (cat === 'knowledge') continue // skip auto-extracted
|
|
470
|
+
const catDir = join(learningDir, cat)
|
|
471
|
+
for (const entry of readdirSync(catDir)) {
|
|
472
|
+
const conceptMd = join(catDir, entry, 'concept.md')
|
|
473
|
+
if (existsSync(conceptMd)) {
|
|
474
|
+
const c = readFileSync(conceptMd, 'utf-8')
|
|
475
|
+
if (!query || c.toLowerCase().includes(query.toLowerCase()) || entry.toLowerCase().includes(query.toLowerCase())) {
|
|
476
|
+
results.learning.push({ name: entry, category: cat, path: join(catDir, entry) })
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} catch {}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// 5. Tickets
|
|
485
|
+
const ticketDirs = [
|
|
486
|
+
join(baseDir, '.opencode', 'bugs'),
|
|
487
|
+
join(__dirname, '..', '..', 'BUGS-IDENTIFIED-IN-PROJECTS')
|
|
488
|
+
]
|
|
489
|
+
for (const td of ticketDirs) {
|
|
490
|
+
if (!existsSync(td)) continue
|
|
491
|
+
try {
|
|
492
|
+
const idxPath = join(td, 'INDEX.md')
|
|
493
|
+
if (!existsSync(idxPath)) continue
|
|
494
|
+
const idx = readFileSync(idxPath, 'utf-8')
|
|
495
|
+
for (const line of idx.split('\n')) {
|
|
496
|
+
if (line.includes('| BUG-') && (!query || line.toLowerCase().includes(query.toLowerCase()))) {
|
|
497
|
+
const cols = line.split('|').map(c => c.trim()).filter(Boolean)
|
|
498
|
+
if (cols.length >= 4 && cols[3] !== 'fixed') {
|
|
499
|
+
results.tickets.push({ id: cols[0], title: cols[1], severity: cols[2], status: cols[3] })
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} catch {}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 6. Learn-errors from source
|
|
507
|
+
if (query) {
|
|
508
|
+
try {
|
|
509
|
+
const foundErrors = scanForLearnErrors(baseDir, langId)
|
|
510
|
+
results.errors = foundErrors.filter(e =>
|
|
511
|
+
e.id.toLowerCase().includes(query.toLowerCase()) ||
|
|
512
|
+
(e.problem && e.problem.toLowerCase().includes(query.toLowerCase())) ||
|
|
513
|
+
(e.category && e.category.toLowerCase().includes(query.toLowerCase()))
|
|
514
|
+
).slice(0, 10)
|
|
515
|
+
} catch {}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Output
|
|
519
|
+
let totalResults = results.knowledge.length + results.learning.length + results.semantic.length +
|
|
520
|
+
results.templates.length + results.tickets.length + results.errors.length
|
|
521
|
+
|
|
522
|
+
if (totalResults === 0) {
|
|
523
|
+
console.log(chalk.yellow(' 📭 Sin resultados para esta búsqueda.\n'))
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (results.knowledge.length > 0) {
|
|
528
|
+
console.log(chalk.cyan(`\n📚 Knowledge (${results.knowledge.length}):\n`))
|
|
529
|
+
for (const r of results.knowledge.slice(0, 5)) {
|
|
530
|
+
console.log(` ${chalk.bold(r.title)} (relevance: ${r.relevance})`)
|
|
531
|
+
if (r.snippet) console.log(chalk.gray(` ${r.snippet.slice(0, 100)}...`))
|
|
532
|
+
if (r.bookTitle) console.log(chalk.gray(` Fuente: ${r.bookTitle}`))
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (results.semantic.length > 0) {
|
|
537
|
+
console.log(chalk.cyan(`\n🧩 Semántico (${results.semantic.length}):\n`))
|
|
538
|
+
for (const r of results.semantic.slice(0, 5)) {
|
|
539
|
+
console.log(` ${chalk.bold(r.concept)} → ${(r.components || []).join(', ')}`)
|
|
540
|
+
if (r.patterns?.length) console.log(chalk.gray(` Patrones: ${r.patterns.join(', ')}`))
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (results.templates.length > 0) {
|
|
545
|
+
console.log(chalk.cyan(`\n📄 Templates (${results.templates.length}):\n`))
|
|
546
|
+
for (const t of results.templates.slice(0, 5)) {
|
|
547
|
+
console.log(` ${chalk.bold(t.id)} [${t.category}] — ${t.description || t.name}`)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (results.learning.length > 0) {
|
|
552
|
+
console.log(chalk.cyan(`\n🧠 Learning (${results.learning.length}):\n`))
|
|
553
|
+
for (const l of results.learning.slice(0, 5)) {
|
|
554
|
+
console.log(` ${chalk.bold(l.name)} (${l.category})`)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (results.tickets.length > 0) {
|
|
559
|
+
console.log(chalk.cyan(`\n📋 Tickets (${results.tickets.length}):\n`))
|
|
560
|
+
for (const t of results.tickets.slice(0, 5)) {
|
|
561
|
+
const sevColor = { critical: chalk.red, high: chalk.yellow, medium: chalk.cyan, low: chalk.gray }[t.severity] || chalk.white
|
|
562
|
+
console.log(` ${t.id} ${sevColor(`[${t.severity}]`)} — ${t.title}`)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (results.errors.length > 0) {
|
|
567
|
+
console.log(chalk.cyan(`\n🐛 Errores aprendidos (${results.errors.length}):\n`))
|
|
568
|
+
for (const e of results.errors.slice(0, 5)) {
|
|
569
|
+
console.log(` ${chalk.bold(e.id)}: ${e.problem}`)
|
|
570
|
+
if (e.solution) console.log(chalk.gray(` Fix: ${e.solution}`))
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
console.log(chalk.green(`\n ✅ ${totalResults} resultados en 6 fuentes\n`))
|
|
575
|
+
return results
|
|
576
|
+
}
|