openprompt-lang 1.2.6 → 1.3.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.
Files changed (43) hide show
  1. package/README.md +62 -8
  2. package/docs/EMBEDDINGS.md +214 -0
  3. package/docs/FRAMEWORK.md +52 -0
  4. package/docs/ONBOARDING_WORKFLOW.md +151 -0
  5. package/docs/OPL-ERRORES.md +504 -0
  6. package/docs/OPL_ACADEMIC_ISSUES.md +158 -0
  7. package/docs/WEB_SCRAPER_PLAN.md +454 -0
  8. package/package.json +7 -1
  9. package/scripts/postinstall.js +37 -0
  10. package/src/cli/commands-knowledge.js +1 -0
  11. package/src/cli/commands-opl.js +79 -1
  12. package/src/cli/commands-work.js +3 -1
  13. package/src/cli/commands-workflow.js +125 -6
  14. package/src/commands/init-core.js +188 -12
  15. package/src/commands/init-existing.js +13 -6
  16. package/src/commands/init-helpers.js +20 -14
  17. package/src/commands/knowledge-ops.js +52 -0
  18. package/src/commands/opl-embeddings.js +556 -0
  19. package/src/commands/opl-help.js +26 -2
  20. package/src/commands/opl-search.js +106 -2
  21. package/src/commands/opl-webscrape.js +390 -0
  22. package/src/commands/work-context.js +17 -0
  23. package/src/commands/workflow/close/index.js +2 -1
  24. package/src/commands/workflow/delivery/index.js +4 -0
  25. package/src/commands/workflow/discovery/index.js +4 -0
  26. package/src/commands/workflow/epic-cli.js +192 -0
  27. package/src/commands/workflow/select.js +146 -0
  28. package/src/commands/workflow/specification/index.js +4 -0
  29. package/src/commands/workflow/sprint-cli.js +174 -0
  30. package/src/core/engine/sandbox.js +7 -3
  31. package/src/core/webscrape/analyzer.js +481 -0
  32. package/src/core/webscrape/deep-scraper.js +1027 -0
  33. package/src/core/workflow/epic-manager.js +845 -0
  34. package/src/core/workflow/gates.js +180 -1
  35. package/src/core/workflow/selector.js +707 -0
  36. package/src/embeddings/chunker.js +450 -0
  37. package/src/embeddings/embedder.js +431 -0
  38. package/src/embeddings/index-pipeline.js +320 -0
  39. package/src/embeddings/vector-store.js +505 -0
  40. package/src/mcp-plan-server.js +12 -5
  41. package/src/mcp-shared-state.js +25 -0
  42. package/src/mcp-refactor/mcp-server.js +0 -171
  43. package/src/mcp-server-backup.js +0 -1913
@@ -1,22 +1,30 @@
1
1
  // @use(commander, kind, contract, limit)
2
2
  // @kind(util)
3
3
  // @contract(in: program -> out: void, sideEffect: registra comandos workflow)
4
- // @limit(lines: 120)
4
+ // @limit(lines: 200)
5
5
 
6
6
  /**
7
7
  * Registra los comandos de workflow OPL.
8
8
  *
9
9
  * Comandos:
10
- * opl workflow discovery → E.1 — Wizard de descubrimiento
11
- * opl workflow specification → E.2 — Generación de documentos
12
- * opl workflow delivery → E.3 — Desarrollo por tickets
13
- * opl workflow close → E.4 — Cierre de sesión
10
+ * opl workflow discovery → E.1 — Wizard de descubrimiento
11
+ * opl workflow specification → E.2 — Generación de documentos
12
+ * opl workflow delivery → E.3 — Desarrollo por tickets
13
+ * opl workflow close → E.4 — Cierre de sesión
14
+ * opl workflow select → E.5 — Selector inteligente de workflow
15
+ * opl epic → E.6 — Gestión de épicas
16
+ * opl sprint → E.7 — Gestión de sprints
14
17
  */
15
18
 
16
19
  export function register(program) {
20
+ // ──────────────────────────────────────────────
21
+ // opl workflow — Gestión de proyectos
22
+ // ──────────────────────────────────────────────
17
23
  const workflow = program
18
24
  .command("workflow")
19
- .description("Gestionar workflows de proyecto: discovery, specification, delivery, close")
25
+ .description(
26
+ "Gestionar workflows de proyecto: discovery, specification, delivery, close, select"
27
+ )
20
28
 
21
29
  workflow
22
30
  .command("discovery")
@@ -71,4 +79,115 @@ export function register(program) {
71
79
  reportDir: options.reports,
72
80
  })
73
81
  })
82
+
83
+ // ─── E.5 — Selector inteligente de workflow ─────────────────────────────
84
+ workflow
85
+ .command("select [description]")
86
+ .description("Seleccionar workflow óptimo según descripción de la tarea")
87
+ .option("--list", "Listar workflows disponibles")
88
+ .option("--strict", "Modo estricto: muestra gates requeridos")
89
+ .option("--json", "Salida en JSON")
90
+ .option("--status", "Mostrar reporte de estado del proyecto")
91
+ .action(async (description, options) => {
92
+ const { workflowSelect } = await import("../commands/workflow/select.js")
93
+ await workflowSelect(description, options)
94
+ })
95
+
96
+ // ──────────────────────────────────────────────
97
+ // opl epic — Gestión de épicas
98
+ // ──────────────────────────────────────────────
99
+ const epic = program
100
+ .command("epic")
101
+ .description("Gestionar épicas: agrupar tickets en features grandes")
102
+
103
+ epic
104
+ .command("create")
105
+ .description("Crear una nueva épica con tickets automáticos")
106
+ .option("--title <title>", "Título de la épica")
107
+ .option("--desc <desc>", "Descripción detallada (genera tickets automáticamente)")
108
+ .option("--domain <domain>", "Dominio asociado")
109
+ .option("--no-auto-tickets", "No generar tickets automáticamente")
110
+ .action(async (options) => {
111
+ const { epicCreate } = await import("../commands/workflow/epic-cli.js")
112
+ await epicCreate(options)
113
+ })
114
+
115
+ epic
116
+ .command("list")
117
+ .description("Listar todas las épicas")
118
+ .option("--status <status>", "Filtrar por estado: planning, active, done, cancelled")
119
+ .option("--domain <domain>", "Filtrar por dominio")
120
+ .action(async (options) => {
121
+ const { epicList } = await import("../commands/workflow/epic-cli.js")
122
+ await epicList(options)
123
+ })
124
+
125
+ epic
126
+ .command("show <epicId>")
127
+ .description("Mostrar detalle de una épica")
128
+ .action(async (epicId) => {
129
+ const { epicShow } = await import("../commands/workflow/epic-cli.js")
130
+ await epicShow(epicId)
131
+ })
132
+
133
+ epic
134
+ .command("close <epicId>")
135
+ .description("Cerrar una épica")
136
+ .option("--status <status>", "Estado final (default: done)", "done")
137
+ .action(async (epicId, options) => {
138
+ const { epicClose } = await import("../commands/workflow/epic-cli.js")
139
+ await epicClose(epicId, options)
140
+ })
141
+
142
+ epic
143
+ .command("status")
144
+ .description("Mostrar reporte de estado del proyecto basado en épicas y sprints")
145
+ .action(async () => {
146
+ const { epicStatus } = await import("../commands/workflow/epic-cli.js")
147
+ await epicStatus()
148
+ })
149
+
150
+ // ──────────────────────────────────────────────
151
+ // opl sprint — Gestión de sprints
152
+ // ──────────────────────────────────────────────
153
+ const sprint = program
154
+ .command("sprint")
155
+ .description("Gestionar sprints: planificar iteraciones de desarrollo")
156
+
157
+ sprint
158
+ .command("create <name>")
159
+ .description("Crear un nuevo sprint")
160
+ .requiredOption("--goal <goal>", "Objetivo del sprint")
161
+ .option("--duration <days>", "Duración en días (default: 14)")
162
+ .option("--start-date <date>", "Fecha de inicio (default: hoy)")
163
+ .action(async (name, options) => {
164
+ const { sprintCreate } = await import("../commands/workflow/sprint-cli.js")
165
+ await sprintCreate(name, options)
166
+ })
167
+
168
+ sprint
169
+ .command("list")
170
+ .description("Listar sprints")
171
+ .option("--status <status>", "Filtrar por estado: planning, active, completed")
172
+ .action(async (options) => {
173
+ const { sprintList } = await import("../commands/workflow/sprint-cli.js")
174
+ await sprintList(options)
175
+ })
176
+
177
+ sprint
178
+ .command("plan <sprintId>")
179
+ .description("Planificar sprint automáticamente desde épicas activas")
180
+ .option("--capacity <n>", "Capacidad en tickets (default: 10)")
181
+ .action(async (sprintId, options) => {
182
+ const { sprintPlan } = await import("../commands/workflow/sprint-cli.js")
183
+ await sprintPlan(sprintId, options)
184
+ })
185
+
186
+ sprint
187
+ .command("close <sprintId>")
188
+ .description("Cerrar un sprint")
189
+ .action(async (sprintId) => {
190
+ const { sprintClose } = await import("../commands/workflow/sprint-cli.js")
191
+ await sprintClose(sprintId)
192
+ })
74
193
  }
@@ -14,8 +14,13 @@ export async function init(options) {
14
14
  const interactive = options.interactive !== false && !!process.stdin.isTTY
15
15
  const dryRun = options.dryRun || false
16
16
 
17
+ // Stack y extensiones (se actualizan si selectFullStack se ejecuta)
18
+ const finalStack = options.stack ? options.stack.split(",").map((s) => s.trim()) : []
19
+ const finalExtensions = []
20
+
17
21
  const cwd = process.cwd()
18
- const isInitialized = existsSync(join(cwd, "prompt-lang.json")) || existsSync(join(cwd, ".openprompt"))
22
+ const isInitialized =
23
+ existsSync(join(cwd, "prompt-lang.json")) || existsSync(join(cwd, ".openprompt"))
19
24
 
20
25
  if (interactive && !options.existing && !options.name && !options.preset && !options.stack) {
21
26
  console.log(chalk.cyan("\n🧙 openPrompt-Lang — Inicialización\n"))
@@ -23,7 +28,10 @@ export async function init(options) {
23
28
  const choices = []
24
29
 
25
30
  if (isInitialized) {
26
- choices.push({ name: chalk.yellow(" Actualizar/Reconstruir la integración (Rebuild)"), value: "rebuild" })
31
+ choices.push({
32
+ name: chalk.yellow(" Actualizar/Reconstruir la integración (Rebuild)"),
33
+ value: "rebuild",
34
+ })
27
35
  choices.push(new inquirer.Separator())
28
36
  }
29
37
 
@@ -41,7 +49,9 @@ export async function init(options) {
41
49
  {
42
50
  type: "list",
43
51
  name: "projectType",
44
- message: isInitialized ? "Este proyecto ya tiene OPL. ¿Qué deseas hacer?" : "¿Qué tipo de proyecto?",
52
+ message: isInitialized
53
+ ? "Este proyecto ya tiene OPL. ¿Qué deseas hacer?"
54
+ : "¿Qué tipo de proyecto?",
45
55
  choices,
46
56
  },
47
57
  ])
@@ -134,22 +144,188 @@ export async function init(options) {
134
144
  // ── Verificar OpenCode ──
135
145
  await checkAndInstallOpencode(interactive)
136
146
 
147
+ // ── Generar ONBOARDING_WORKFLOW.md ──
148
+ if (!dryRun) {
149
+ try {
150
+ const onboardingPath = join(finalBaseDir, "docs", "ONBOARDING_WORKFLOW.md")
151
+ const onboardingContent = generateOnboardingMd(finalName, finalStack, finalExtensions)
152
+ if (!existsSync(join(finalBaseDir, "docs"))) {
153
+ mkdirSync(join(finalBaseDir, "docs"), { recursive: true })
154
+ }
155
+ writeFileSync(onboardingPath, onboardingContent, "utf-8")
156
+ console.log(chalk.green(` ✅ docs/ONBOARDING_WORKFLOW.md — Guía para tu IA`))
157
+ } catch {
158
+ // No crítico si falla
159
+ }
160
+ }
161
+
137
162
  // ── Handoff a la IA ──
138
163
  console.log(chalk.cyan("\n✨ OPL Init: Estructura base lista."))
139
164
  console.log(chalk.yellow("OPL delega ahora la inicialización estratégica a tu Agente IA."))
140
- console.log(chalk.white("Copia y pega el siguiente prompt en tu chat con la IA (ej. Cline, Cursor, OpenCode, etc.):\n"))
141
-
142
- console.log(chalk.bgGray.white(`
165
+ console.log(
166
+ chalk.white(
167
+ "Copia y pega el siguiente prompt en tu chat con la IA (ej. Cline, Cursor, OpenCode, etc.):\n"
168
+ )
169
+ )
170
+
171
+ const stackStr = finalStack ? finalStack.join(", ") : "por definir"
172
+ const extStr = finalExtensions?.length ? `\nExtensiones: ${finalExtensions.join(", ")}` : ""
173
+
174
+ console.log(
175
+ chalk.bgGray.white(`
143
176
  "Asume el rol de Arquitecto Principal OPL. Acabo de ejecutar opl init para un proyecto NUEVO llamado '${finalName}'.
177
+ Stack base: ${stackStr}${extStr}
144
178
 
145
179
  Tu proceso obligatorio es:
146
180
  1. Ejecuta 'analyze_project' para inspeccionar el directorio base.
147
- 2. Hazme preguntas para entender el contexto del proyecto (dominio, stack preferido, funcionalidades clave).
148
- 3. Decide el stack óptimo con base en mis respuestas.
149
- 4. Usa 'init_project' y 'scaffold_project' para configurar el proyecto.
150
- 5. Genera los archivos canónicos: AGENTS.md, prompt-lang.json, .openprompt/FRAMEWORK.md.
151
- 6. Confirma explícitamente que el proyecto quedó estabilizado e indícame el siguiente paso operativo."
152
- `))
181
+ 2. Lee el archivo 'docs/ONBOARDING_WORKFLOW.md' (recién generado).
182
+ 3. Hazme preguntas para entender el contexto del proyecto (dominio, stack preferido, funcionalidades clave).
183
+ 4. Decide el stack óptimo con base en mis respuestas.
184
+ 5. Usa 'init_project' y 'scaffold_project' para configurar el proyecto.
185
+ 6. Genera los archivos canónicos: AGENTS.md, prompt-lang.json, .openprompt/FRAMEWORK.md.
186
+ 7. Confirma explícitamente que el proyecto quedó estabilizado e indícame el siguiente paso operativo."
187
+ `)
188
+ )
153
189
 
154
190
  return finalBaseDir
155
191
  }
192
+
193
+ /**
194
+ * Genera el contenido de ONBOARDING_WORKFLOW.md para un proyecto nuevo.
195
+ *
196
+ * @param {string} projectName
197
+ * @param {string[]} stack
198
+ * @param {string[]} extensions
199
+ * @returns {string}
200
+ */
201
+ function generateOnboardingMd(projectName, stack, extensions) {
202
+ const stackSection = stack?.length
203
+ ? stack.map((s) => ` - ${s}`).join("\n")
204
+ : " - Por definir durante la inicialización"
205
+
206
+ const extSection = extensions?.length
207
+ ? extensions.map((e) => ` - ${e}`).join("\n")
208
+ : " - (ninguna por ahora)"
209
+
210
+ return `# ONBOARDING WORKFLOW — ${projectName}
211
+
212
+ Generado automáticamente por \`opl init\`.
213
+
214
+ ## 🎯 Propósito
215
+
216
+ Este documento le indica a la IA qué pasos seguir al ser invocada en este proyecto por primera vez. Reduce la fricción de onboarding y asegura que la IA entienda el stack, la estructura y el workflow del proyecto.
217
+
218
+ ## 📋 Stack Detectado
219
+
220
+ ${stackSection}
221
+
222
+ ## 🔌 Extensiones
223
+
224
+ ${extSection}
225
+
226
+ ## 🏗️ Estructura del Proyecto
227
+
228
+ \`\`\`
229
+ .
230
+ ├── AGENTS.md → Contexto para IA (stack, convenciones, reglas)
231
+ ├── prompt-lang.json → Configuración OPL
232
+ ├── docs/
233
+ │ ├── AI_CONTEXT.md → Contexto extendido
234
+ │ ├── FRAMEWORK.md → Referencia canónica del framework
235
+ │ ├── ONBOARDING_WORKFLOW.md → Este archivo
236
+ │ └── ...
237
+ ├── .openprompt/
238
+ │ └── FRAMEWORK.md → Manual completo de OPL
239
+ ├── .opencode/
240
+ │ └── work-context/ → Sesiones, planes, logs
241
+ └── src/
242
+ └── ...
243
+ \`\`\`
244
+
245
+ ## 🔄 Workflow Obligatorio para la IA (v1.3.0)
246
+
247
+ Siempre que se te pida trabajar en este proyecto, SIGUE ESTE FLUJO ESTRICTAMENTE.
248
+ No hay excepciones. Los gates bloquean herramientas si se omiten pasos.
249
+
250
+ ### ⚠️ REGLAS INQUEBRANTABLES
251
+
252
+ 1. **workflow select primero**: Sin workflow seleccionado no hay implementación
253
+ 2. **ticket create antes de código**: Sin ticket abierto no se escribe código
254
+ 3. **docs_updated antes de cerrar**: Sin documentación no se cierra sesión
255
+
256
+ ### Flujo Completo (8 pasos, siempre en este orden)
257
+
258
+ \`\`\`
259
+ PASO 1 → workflow_select
260
+ "opl workflow select <descripción de la tarea>"
261
+ La IA describe qué necesita hacer, el sistema selecciona el workflow óptimo
262
+ (feature, bugfix, refactor, docs, research, etc.)
263
+
264
+ PASO 2 → create_ticket
265
+ "opl ticket create --title ... --severity ..."
266
+ TODO cambio requiere ticket ANTES de tocar código
267
+
268
+ PASO 3 → check_gates
269
+ Verificar: workflow_selected ✓, ticket_created ✓, plan_approved ✓
270
+
271
+ PASO 4 → work_context_plan
272
+ Planificar implementación con búsqueda de conocimiento
273
+
274
+ PASO 5 → implement
275
+ Anotaciones OPL primero (@kind → @contract → @limit → @deps)
276
+ Luego implementar lógica
277
+
278
+ PASO 6 → validate
279
+ "opl validate" — verificar anotaciones antes de cerrar
280
+
281
+ PASO 7 → docs_updated
282
+ Actualizar documentación. OBLIGATORIO antes de cerrar sesión.
283
+
284
+ PASO 8 → work_context_close
285
+ Cerrar sesión y generar reporte con aprendizajes
286
+ \`\`\`
287
+
288
+ ### Para features grandes: Épicas y Sprints
289
+
290
+ \`\`\`
291
+ # Crear épica (agrupa tickets automáticamente desde la descripción)
292
+ opl epic create --title "Sistema de Reportes" --desc "Dashboard + exportación PDF + filtros"
293
+
294
+ # Crear sprint con objetivo y duración
295
+ opl sprint create "Sprint 1" --goal "Implementar core del sistema"
296
+
297
+ # Planificar sprint automáticamente desde épicas activas
298
+ opl sprint plan SPRINT-001 --capacity 10
299
+
300
+ # Ver estado del proyecto
301
+ opl epic status
302
+ \`\`\`
303
+
304
+ ## ⚙️ Comandos Rápidos
305
+
306
+ | Comando | Descripción |
307
+ |---------|-------------|
308
+ | \`opl workflow select <desc>\` | 🥇 Seleccionar workflow óptimo (USAR SIEMPRE PRIMERO) |
309
+ | \`opl ticket create --title "..."\` | 🥇 Crear ticket (USAR SIEMPRE ANTES DE CÓDIGO) |
310
+ | \`opl epic create --title "..." --desc "..."\` | Crear épica con tickets automáticos |
311
+ | \`opl epic list\` | Listar épicas con progreso |
312
+ | \`opl sprint create <name> --goal "..."\` | Crear sprint |
313
+ | \`opl sprint plan <id> --capacity N\` | Planificar sprint automáticamente |
314
+ | \`opl index\` | Navegar conocimiento |
315
+ | \`opl search <q>\` | Buscar en conocimiento (--mode vector para semántica) |
316
+ | \`opl read <d>/<id>\` | Leer documento |
317
+ | \`opl system <name>\` | Explorar sistemas |
318
+ | \`opl assess\` | Production Readiness Assessment |
319
+ | \`opl embeddings status\` | Estado del índice vectorial |
320
+ | \`opl knowledge ingest <file>\` | Ingestar PDF |
321
+ | \`opl webscrape <url>\` | Extraer contenido web e indexar |
322
+
323
+ ## 📚 Documentación Vinculada
324
+
325
+ - \`docs/AI_CONTEXT.md\` → Stack técnico y módulos
326
+ - \`docs/FRAMEWORK.md\` → Especificación del framework
327
+ - \`docs/EMBEDDINGS.md\` → Sistema de embeddings vectoriales
328
+ - \`AGENTS.md\` → Contexto completo para IA
329
+ - \`.openprompt/FRAMEWORK.md\` → Manual canónico de OPL
330
+ `
331
+ }
@@ -138,15 +138,20 @@ export async function initExisting(options = {}) {
138
138
  const interactive = options.interactive !== false && !!process.stdin.isTTY
139
139
  if (!rebuild) {
140
140
  console.log(chalk.cyan(`\n🔧 Inicializando OPL en proyecto existente: ${projectName}\n`))
141
-
141
+
142
142
  // ── Verificar OpenCode ──
143
143
  await checkAndInstallOpencode(interactive)
144
144
 
145
145
  console.log(chalk.cyan("✨ OPL Init: Entorno detectado."))
146
146
  console.log(chalk.yellow("OPL delega ahora la inicialización estratégica a tu Agente IA."))
147
- console.log(chalk.white("Copia y pega el siguiente prompt en tu chat con la IA (ej. Cline, Cursor, OpenCode, etc.):\n"))
148
-
149
- console.log(chalk.bgGray.white(`
147
+ console.log(
148
+ chalk.white(
149
+ "Copia y pega el siguiente prompt en tu chat con la IA (ej. Cline, Cursor, OpenCode, etc.):\n"
150
+ )
151
+ )
152
+
153
+ console.log(
154
+ chalk.bgGray.white(`
150
155
  "Asume el rol de Arquitecto Principal OPL. Acabo de ejecutar opl init para un proyecto EXISTENTE llamado '${projectName}'.
151
156
 
152
157
  Tu proceso obligatorio es:
@@ -155,7 +160,8 @@ Tu proceso obligatorio es:
155
160
  3. Genera la estructura que falte (docs/, work-context, AGENTS.md, prompt-lang.json, .openprompt/FRAMEWORK.md).
156
161
  4. Organiza un backlog inicial de refactorización con la deuda detectada.
157
162
  5. Confirma explícitamente que el proyecto quedó estabilizado e indícame el siguiente paso operativo."
158
- `))
163
+ `)
164
+ )
159
165
  return cwd
160
166
  }
161
167
 
@@ -431,7 +437,8 @@ Tu proceso obligatorio es:
431
437
  {
432
438
  type: "confirm",
433
439
  name: "updateKnowledge",
434
- message: "¿Deseas descargar/actualizar la biblioteca de conocimientos (Entorno Académico)?",
440
+ message:
441
+ "¿Deseas descargar/actualizar la biblioteca de conocimientos (Entorno Académico)?",
435
442
  default: false,
436
443
  },
437
444
  ])
@@ -535,35 +535,41 @@ export async function selectFullStack(options) {
535
535
  }
536
536
 
537
537
  export async function checkAndInstallOpencode(interactive) {
538
- if (!interactive) return;
538
+ if (!interactive) return
539
539
 
540
- let isInstalled = false;
540
+ let isInstalled = false
541
541
  try {
542
- execSync("opencode --version", { stdio: "ignore" });
543
- isInstalled = true;
542
+ execSync("opencode --version", { stdio: "ignore" })
543
+ isInstalled = true
544
544
  } catch (e) {
545
- isInstalled = false;
545
+ isInstalled = false
546
546
  }
547
547
 
548
548
  if (!isInstalled) {
549
- console.log(chalk.yellow("\n⚠️ No se detectó 'opencode' instalado globalmente."));
550
- console.log(chalk.gray("OpenCode es el agente CLI recomendado para aprovechar la integración MCP de OPL."));
549
+ console.log(chalk.yellow("\n⚠️ No se detectó 'opencode' instalado globalmente."))
550
+ console.log(
551
+ chalk.gray("OpenCode es el agente CLI recomendado para aprovechar la integración MCP de OPL.")
552
+ )
551
553
  const { install } = await inquirer.prompt([
552
554
  {
553
555
  type: "confirm",
554
556
  name: "install",
555
557
  message: "¿Deseas instalar opencode globalmente ahora? (npm install -g opencode)",
556
- default: true
557
- }
558
- ]);
558
+ default: true,
559
+ },
560
+ ])
559
561
 
560
562
  if (install) {
561
- console.log(chalk.cyan("⏳ Instalando opencode..."));
563
+ console.log(chalk.cyan("⏳ Instalando opencode..."))
562
564
  try {
563
- execSync("npm install -g opencode", { stdio: "inherit" });
564
- console.log(chalk.green("✅ opencode instalado exitosamente.\n"));
565
+ execSync("npm install -g opencode", { stdio: "inherit" })
566
+ console.log(chalk.green("✅ opencode instalado exitosamente.\n"))
565
567
  } catch (e) {
566
- console.log(chalk.red("❌ Error instalando opencode. Puedes instalarlo manualmente con: npm install -g opencode\n"));
568
+ console.log(
569
+ chalk.red(
570
+ "❌ Error instalando opencode. Puedes instalarlo manualmente con: npm install -g opencode\n"
571
+ )
572
+ )
567
573
  }
568
574
  }
569
575
  }
@@ -287,9 +287,61 @@ export async function ingest(filePath, options = {}) {
287
287
  rebuildIndex()
288
288
 
289
289
  console.log(chalk.cyan(`\n✅ PDF procesado: ${shortName}\n`))
290
+
291
+ // ── Auto-embedding hook (TICKET-007) ─────────────────────
292
+ if (options.embed !== false) {
293
+ await autoEmbedPdf(pdfId, entry, chapters)
294
+ }
290
295
  }
291
296
 
292
297
  export async function rebuild(_options = {}) {
293
298
  const count = rebuildIndex()
294
299
  console.log(chalk.cyan(`\n✅ Índice reconstruido: ${count.length} PDFs\n`))
295
300
  }
301
+
302
+ /**
303
+ * Auto-embedding hook: después de ingestar un PDF, indexa sus embeddings.
304
+ *
305
+ * @param {string} pdfId - ID del PDF procesado
306
+ * @param {Object} entry - Metadatos del PDF
307
+ * @param {Array} chapters - Capítulos detectados
308
+ */
309
+ async function autoEmbedPdf(pdfId, entry, chapters) {
310
+ try {
311
+ const { indexDocument } = await import("../embeddings/index-pipeline.js")
312
+
313
+ // Construir documento en formato OPL
314
+ const doc = {
315
+ id: pdfId,
316
+ title: entry.title || pdfId,
317
+ chapters: chapters.map((ch, i) => ({
318
+ index: ch.index ?? i,
319
+ title: ch.title || "",
320
+ content: ch.content || "",
321
+ })),
322
+ }
323
+
324
+ if (doc.chapters.length === 0) {
325
+ console.log(chalk.gray(" ⏭️ Sin capítulos para indexar (embedding omitido)."))
326
+ return
327
+ }
328
+
329
+ console.log(chalk.blue(" 🧠 Indexando embeddings..."))
330
+
331
+ const result = await indexDocument(doc, { strategy: "section" })
332
+
333
+ if (result.success) {
334
+ console.log(
335
+ chalk.green(` ✅ Embeddings: ${result.indexedChunks} chunks · ${result.durationMs}ms`)
336
+ )
337
+ } else {
338
+ console.log(
339
+ chalk.yellow(
340
+ ` ⚠️ Embedding parcial: ${result.indexedChunks}/${result.totalChunks} chunks`
341
+ )
342
+ )
343
+ }
344
+ } catch (error) {
345
+ console.log(chalk.gray(` ⏭️ Embedding no disponible (${error.message})`))
346
+ }
347
+ }