openprompt-lang 1.2.7 → 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.
@@ -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,6 +14,10 @@ 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
22
  const isInitialized =
19
23
  existsSync(join(cwd, "prompt-lang.json")) || existsSync(join(cwd, ".openprompt"))
@@ -140,6 +144,21 @@ export async function init(options) {
140
144
  // ── Verificar OpenCode ──
141
145
  await checkAndInstallOpencode(interactive)
142
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
+
143
162
  // ── Handoff a la IA ──
144
163
  console.log(chalk.cyan("\n✨ OPL Init: Estructura base lista."))
145
164
  console.log(chalk.yellow("OPL delega ahora la inicialización estratégica a tu Agente IA."))
@@ -149,19 +168,164 @@ export async function init(options) {
149
168
  )
150
169
  )
151
170
 
171
+ const stackStr = finalStack ? finalStack.join(", ") : "por definir"
172
+ const extStr = finalExtensions?.length ? `\nExtensiones: ${finalExtensions.join(", ")}` : ""
173
+
152
174
  console.log(
153
175
  chalk.bgGray.white(`
154
176
  "Asume el rol de Arquitecto Principal OPL. Acabo de ejecutar opl init para un proyecto NUEVO llamado '${finalName}'.
177
+ Stack base: ${stackStr}${extStr}
155
178
 
156
179
  Tu proceso obligatorio es:
157
180
  1. Ejecuta 'analyze_project' para inspeccionar el directorio base.
158
- 2. Hazme preguntas para entender el contexto del proyecto (dominio, stack preferido, funcionalidades clave).
159
- 3. Decide el stack óptimo con base en mis respuestas.
160
- 4. Usa 'init_project' y 'scaffold_project' para configurar el proyecto.
161
- 5. Genera los archivos canónicos: AGENTS.md, prompt-lang.json, .openprompt/FRAMEWORK.md.
162
- 6. Confirma explícitamente que el proyecto quedó estabilizado e indícame el siguiente paso operativo."
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."
163
187
  `)
164
188
  )
165
189
 
166
190
  return finalBaseDir
167
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
+ }
@@ -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
+ }