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.
- package/README.md +62 -8
- 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 +7 -1
- package/scripts/postinstall.js +37 -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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @kind(module)
|
|
2
2
|
// @contract(in: gateName:string, context:object -> out: {passed:boolean, reason:string} @error: WorkflowError)
|
|
3
|
-
// @limit(lines:
|
|
3
|
+
// @limit(lines: 340)
|
|
4
4
|
// @pattern(provider)
|
|
5
5
|
|
|
6
|
+
import { join } from "path"
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs"
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* Módulo de gates de workflow.
|
|
8
11
|
*
|
|
@@ -75,6 +78,182 @@ const GATES = Object.freeze({
|
|
|
75
78
|
message: "El proyecto debe existir en la base de datos (opl project init o wizard)",
|
|
76
79
|
description: "El proyecto debe estar registrado en SQLite",
|
|
77
80
|
},
|
|
81
|
+
|
|
82
|
+
// ─── Nuevos gates v2 (workflow inteligente) ─────────────────────────────────
|
|
83
|
+
|
|
84
|
+
docs_updated: {
|
|
85
|
+
requires: "docs_updated",
|
|
86
|
+
message: "Debes actualizar la documentación antes de cerrar. Ejecuta: opl doc flow/framework",
|
|
87
|
+
description: "La documentación debe estar actualizada antes de cerrar la sesión",
|
|
88
|
+
customCheck: (ctx) => {
|
|
89
|
+
// Verificar que existe documentación reciente
|
|
90
|
+
const docsDir = join(process.cwd(), "docs")
|
|
91
|
+
let hasRecentDocs = false
|
|
92
|
+
try {
|
|
93
|
+
if (existsSync(docsDir)) {
|
|
94
|
+
const files = readdirSync(docsDir).filter((f) => f.endsWith(".md") || f.endsWith(".mdx"))
|
|
95
|
+
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
|
96
|
+
for (const f of files) {
|
|
97
|
+
const stat = statSync(join(docsDir, f))
|
|
98
|
+
if (stat.mtimeMs > fourHoursAgo) {
|
|
99
|
+
hasRecentDocs = true
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Si no se puede verificar, pasar con advertencia
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (hasRecentDocs || ctx.flags?.docs_updated) {
|
|
109
|
+
return { passed: true, reason: "Documentación actualizada verificada" }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const sessionPath = join(process.cwd(), ".opencode", "work-context", "SESSION.json")
|
|
113
|
+
let hasSessionDocs = false
|
|
114
|
+
try {
|
|
115
|
+
if (existsSync(sessionPath)) {
|
|
116
|
+
const session = JSON.parse(readFileSync(sessionPath, "utf-8"))
|
|
117
|
+
hasSessionDocs = !!session.docGenerated
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Ignorar
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (hasSessionDocs) {
|
|
124
|
+
return { passed: true, reason: "Documentación de sesión generada" }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
passed: false,
|
|
129
|
+
reason:
|
|
130
|
+
"No se detectaron documentos actualizados recientemente. Debes generar la documentación antes de cerrar.",
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
ticket_created: {
|
|
136
|
+
requires: "ticket_created",
|
|
137
|
+
message:
|
|
138
|
+
'Debes crear un ticket antes de implementar. Ejecuta: npx openPrompt-Lang ticket create --title "..."',
|
|
139
|
+
description: "Todo cambio debe tener un ticket asociado antes de implementar",
|
|
140
|
+
customCheck: (ctx) => {
|
|
141
|
+
const bugsDir = join(process.cwd(), ".opencode", "bugs")
|
|
142
|
+
let hasOpenTicket = false
|
|
143
|
+
let ticketCount = 0
|
|
144
|
+
try {
|
|
145
|
+
if (existsSync(bugsDir)) {
|
|
146
|
+
const files = readdirSync(bugsDir).filter((f) => f.endsWith(".md"))
|
|
147
|
+
ticketCount = files.length
|
|
148
|
+
for (const f of files) {
|
|
149
|
+
const content = readFileSync(join(bugsDir, f), "utf-8")
|
|
150
|
+
const statusMatch = content.match(/\*\*Estado\*\*\s+\|\s+(\w+)/)
|
|
151
|
+
if (statusMatch && ["open", "wip"].includes(statusMatch[1])) {
|
|
152
|
+
hasOpenTicket = true
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// Si no se puede leer, asumir que no hay tickets
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (hasOpenTicket || ctx.flags?.ticket_created) {
|
|
162
|
+
return {
|
|
163
|
+
passed: true,
|
|
164
|
+
reason: `Ticket existente verificado (${ticketCount} ticket(s) en total)`,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
passed: false,
|
|
170
|
+
reason: "No hay tickets abiertos. Todo cambio debe comenzar con un ticket.",
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
plan_approved: {
|
|
176
|
+
requires: "plan_approved",
|
|
177
|
+
message: "Debes tener un plan aprobado antes de implementar. Ejecuta: work_context_plan",
|
|
178
|
+
description: "Implementación requiere plan aprobado",
|
|
179
|
+
customCheck: (ctx) => {
|
|
180
|
+
const plansDir = join(process.cwd(), ".opencode", "work-context", "PLANS")
|
|
181
|
+
let hasPlan = false
|
|
182
|
+
try {
|
|
183
|
+
if (existsSync(plansDir)) {
|
|
184
|
+
const files = readdirSync(plansDir).filter((f) => f.endsWith(".md"))
|
|
185
|
+
// Buscar plan aprobado (últimas 24h)
|
|
186
|
+
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000
|
|
187
|
+
for (const f of files) {
|
|
188
|
+
const stat = statSync(join(plansDir, f))
|
|
189
|
+
if (stat.mtimeMs > oneDayAgo) {
|
|
190
|
+
const content = readFileSync(join(plansDir, f), "utf-8")
|
|
191
|
+
if (
|
|
192
|
+
content.includes("## Plan") ||
|
|
193
|
+
content.toLowerCase().includes("work_context_plan")
|
|
194
|
+
) {
|
|
195
|
+
hasPlan = true
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Si no hay plan reciente, aceptar cualquier plan
|
|
201
|
+
if (!hasPlan && files.length > 0) {
|
|
202
|
+
hasPlan = true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// Si no se puede leer, pasar con advertencia
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (hasPlan || ctx.flags?.plan_approved) {
|
|
210
|
+
return { passed: true, reason: "Plan de trabajo verificado" }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
passed: false,
|
|
215
|
+
reason:
|
|
216
|
+
"No se encontró un plan de trabajo. Usa work_context_plan para crear uno antes de implementar.",
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
workflow_selected: {
|
|
222
|
+
requires: "workflow_selected",
|
|
223
|
+
message:
|
|
224
|
+
"Debes seleccionar un workflow primero. Describe la tarea con: npx openPrompt-Lang workflow select <descripción>",
|
|
225
|
+
description: "Requiere que se haya seleccionado un workflow automáticamente",
|
|
226
|
+
customCheck: (ctx) => {
|
|
227
|
+
if (ctx.flags?.workflow_selected) {
|
|
228
|
+
return {
|
|
229
|
+
passed: true,
|
|
230
|
+
reason: `Workflow seleccionado: ${ctx.flags?.selected_workflow || "desconocido"}`,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Verificar si hay workflow seleccionado en sesión
|
|
235
|
+
const sessionPath = join(process.cwd(), ".opencode", "work-context", "SESSION.json")
|
|
236
|
+
try {
|
|
237
|
+
if (existsSync(sessionPath)) {
|
|
238
|
+
const session = JSON.parse(readFileSync(sessionPath, "utf-8"))
|
|
239
|
+
if (session.selectedWorkflow) {
|
|
240
|
+
return {
|
|
241
|
+
passed: true,
|
|
242
|
+
reason: `Workflow seleccionado en sesión: ${session.selectedWorkflow}`,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Ignorar
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
passed: false,
|
|
252
|
+
reason:
|
|
253
|
+
"No hay workflow seleccionado. Usa el selector automático para elegir el workflow adecuado.",
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
},
|
|
78
257
|
})
|
|
79
258
|
|
|
80
259
|
/**
|