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.
Files changed (79) hide show
  1. package/README.md +62 -8
  2. package/bin/cli.js +2 -0
  3. package/docs/00-ARCHITECTURE/OPL-BOOST-MULTI-AGENT.md +406 -0
  4. package/docs/02-STANDARDS/AGENTS.template.md +89 -0
  5. package/docs/02-STANDARDS/ticket-driven-development.md +99 -0
  6. package/docs/04-TICKETS/BOOST-001-profile-registry.md +66 -0
  7. package/docs/04-TICKETS/BOOST-002-context-compression.md +58 -0
  8. package/docs/04-TICKETS/BOOST-003-template-hydration.md +69 -0
  9. package/docs/04-TICKETS/BOOST-004-fewshot-engine.md +58 -0
  10. package/docs/04-TICKETS/BOOST-005-agent-pool.md +69 -0
  11. package/docs/04-TICKETS/BOOST-006-specialized-agents.md +53 -0
  12. package/docs/04-TICKETS/BOOST-007-validation-loop.md +56 -0
  13. package/docs/04-TICKETS/BOOST-008-orchestrator.md +71 -0
  14. package/docs/04-TICKETS/BOOST-009-cache-system.md +56 -0
  15. package/docs/04-TICKETS/BOOST-010-cli-mcp.md +67 -0
  16. package/docs/04-TICKETS/BOOST-011-self-learning.md +50 -0
  17. package/docs/04-TICKETS/BOOST-012-prompt-preamble.md +109 -0
  18. package/docs/04-TICKETS/BOOST-013-hydrator-duplicate-code.md +132 -0
  19. package/docs/04-TICKETS/BOOST-014-multiagent-missing-parts.md +87 -0
  20. package/docs/04-TICKETS/BOOST-015-skeleton-type-missing.md +76 -0
  21. package/docs/04-TICKETS/BOOST-016-output-path-duplicate.md +68 -0
  22. package/docs/04-TICKETS/INDEX.md +89 -0
  23. package/docs/04-TICKETS/_archive/BOOST-005-micro-tasking.md +67 -0
  24. package/docs/04-TICKETS/_archive/BOOST-006-validation-loop.md +66 -0
  25. package/docs/04-TICKETS/_archive/BOOST-007-progressive-pipeline.md +69 -0
  26. package/docs/04-TICKETS/_archive/BOOST-008-cli-mcp-integration.md +74 -0
  27. package/docs/AI_CONTEXT.md +16 -0
  28. package/docs/EMBEDDINGS.md +214 -0
  29. package/docs/ONBOARDING_WORKFLOW.md +151 -0
  30. package/docs/OPL_ACADEMIC_ISSUES.md +158 -0
  31. package/docs/WEB_SCRAPER_PLAN.md +454 -0
  32. package/package.json +9 -2
  33. package/scripts/postinstall.js +37 -0
  34. package/src/boost/agent-pool.js +442 -0
  35. package/src/boost/agents/index.js +79 -0
  36. package/src/boost/cache.js +241 -0
  37. package/src/boost/context-compressor.js +354 -0
  38. package/src/boost/fewshot-retriever.js +332 -0
  39. package/src/boost/hardware-detector.js +486 -0
  40. package/src/boost/hydrator.js +398 -0
  41. package/src/boost/index.js +60 -0
  42. package/src/boost/orchestrator.js +615 -0
  43. package/src/boost/preamble.js +217 -0
  44. package/src/boost/profile-registry.js +264 -0
  45. package/src/boost/self-learn.js +247 -0
  46. package/src/boost/skeletons/component.skeleton.js +24 -0
  47. package/src/boost/skeletons/hook.skeleton.js +27 -0
  48. package/src/boost/skeletons/index.js +67 -0
  49. package/src/boost/skeletons/page.skeleton.js +22 -0
  50. package/src/boost/skeletons/service.skeleton.js +20 -0
  51. package/src/boost/skeletons/store.skeleton.js +18 -0
  52. package/src/boost/skeletons/type.skeleton.js +11 -0
  53. package/src/boost/task-dispatcher.js +142 -0
  54. package/src/boost/validation-loop.js +495 -0
  55. package/src/cli/commands-boost.js +394 -0
  56. package/src/cli/commands-knowledge.js +1 -0
  57. package/src/cli/commands-opl.js +79 -1
  58. package/src/cli/commands-workflow.js +125 -6
  59. package/src/commands/init-core.js +169 -5
  60. package/src/commands/knowledge-ops.js +52 -0
  61. package/src/commands/opl-embeddings.js +556 -0
  62. package/src/commands/opl-help.js +26 -2
  63. package/src/commands/opl-search.js +106 -2
  64. package/src/commands/opl-webscrape.js +390 -0
  65. package/src/commands/workflow/epic-cli.js +192 -0
  66. package/src/commands/workflow/select.js +146 -0
  67. package/src/commands/workflow/sprint-cli.js +174 -0
  68. package/src/core/webscrape/analyzer.js +481 -0
  69. package/src/core/webscrape/deep-scraper.js +1027 -0
  70. package/src/core/workflow/epic-manager.js +845 -0
  71. package/src/core/workflow/gates.js +180 -1
  72. package/src/core/workflow/selector.js +707 -0
  73. package/src/embeddings/chunker.js +450 -0
  74. package/src/embeddings/embedder.js +431 -0
  75. package/src/embeddings/index-pipeline.js +320 -0
  76. package/src/embeddings/vector-store.js +505 -0
  77. package/src/mcp-refactor/handlers/boost.js +295 -0
  78. package/src/mcp-refactor/router.js +19 -0
  79. package/src/mcp-refactor/tools.js +113 -0
@@ -0,0 +1,217 @@
1
+ // @use(kind, contract, limit, learn-error)
2
+ // @kind(util)
3
+ // @contract(in: options -> out: preamble text)
4
+ // @limit(lines: 150)
5
+
6
+ /**
7
+ * Universal Workflow Preamble — Módulo OPL Boost
8
+ *
9
+ * Genera un mensaje de sistema que se inyecta al inicio de cada
10
+ * interacción con la IA, forzando que siga el workflow OPL.
11
+ *
12
+ * Funciona de 3 formas:
13
+ * 1. Archivo: genera .opencode/preamble.md que el AI client lee
14
+ * 2. System prompt: genera texto para inyectar como systemPrompt
15
+ * 3. CLI: `opl boost preamble` muestra el preamble actual
16
+ *
17
+ * El preamble es dinámico: se regenera según el estado del proyecto
18
+ * (tickets abiertos, perfil activo, learn-errors recientes).
19
+ *
20
+ * Uso:
21
+ * import { generatePreamble, writePreamble } from './boost/preamble.js'
22
+ * const text = generatePreamble()
23
+ * writePreamble()
24
+ */
25
+
26
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs"
27
+ import { join } from "path"
28
+
29
+ // ──────────────────────────────────────────────
30
+ // Configuración
31
+ // ──────────────────────────────────────────────
32
+
33
+ const PREAMBLE_PATH = join(process.cwd(), ".opencode", "boost-preamble.md")
34
+ const DEFAULT_MAX_WORDS = 300
35
+
36
+ // ──────────────────────────────────────────────
37
+ // Preamble generation
38
+ // ──────────────────────────────────────────────
39
+
40
+ /**
41
+ * Genera el preamble de workflow obligatorio.
42
+ *
43
+ * @param {object} [options] - Opciones
44
+ * @param {number} [options.maxWords=300] - Máximo de palabras
45
+ * @param {boolean} [options.includeProfile=true] - Incluir info del perfil activo
46
+ * @param {boolean} [options.includeTickets=true] - Incluir tickets abiertos
47
+ * @param {boolean} [options.includeErrors=true] - Incluir learn-errors recientes
48
+ * @returns {string} Texto del preamble en markdown
49
+ */
50
+ export async function generatePreamble(options = {}) {
51
+ const maxWords = options.maxWords || DEFAULT_MAX_WORDS
52
+ const includeProfile = options.includeProfile !== false
53
+ const includeTickets = options.includeTickets !== false
54
+ const includeErrors = options.includeErrors !== false
55
+
56
+ const sections = []
57
+
58
+ // ── Header ──
59
+ sections.push("## ⚠️ OPL Workflow Obligatorio")
60
+ sections.push("")
61
+ sections.push("Antes de implementar cualquier cosa, DEBES seguir este orden:")
62
+ sections.push("")
63
+ sections.push("1. `workflow select <descripción>` — Seleccionar workflow")
64
+ sections.push("2. `ticket create` — Crear ticket (sin ticket no hay código)")
65
+ sections.push("3. `check gates` — Verificar prerrequisitos")
66
+ sections.push("4. `work_context_plan` — Planificar (features/refactors)")
67
+ sections.push("5. Implementar con anotaciones OPL primero")
68
+ sections.push("6. `npx openprompt-lang validate` — Validar")
69
+ sections.push("7. Actualizar documentación")
70
+ sections.push("8. `work_context_close` — Cerrar sesión")
71
+ sections.push("")
72
+
73
+ // ── Prohibiciones ──
74
+ sections.push("### ⛔ Prohibido")
75
+ sections.push("")
76
+ sections.push("- Implementar sin ticket abierto")
77
+ sections.push("- Cerrar sesión sin documentación actualizada")
78
+ sections.push("- Mutar código sin `analyze_blast_radius` primero")
79
+ sections.push("- Modificar archivos `*.template.*`")
80
+ sections.push("- Usar `any` en TypeScript")
81
+ sections.push("- Exceder `@limit` sin refactorizar")
82
+ sections.push("")
83
+
84
+ // ── Perfil Boost ──
85
+ if (includeProfile) {
86
+ try {
87
+ const { getProfile, isBoostEnabled } = await import("./profile-registry.js")
88
+ if (isBoostEnabled()) {
89
+ const profile = getProfile()
90
+ if (profile) {
91
+ sections.push("### 🚀 Boost Activo")
92
+ sections.push("")
93
+ sections.push(`- Perfil: **${profile.name}** (${profile.label})`)
94
+ sections.push(`- Compresión: ${profile.compressionLevel}% | Validación: ${profile.validationLoops} loops`)
95
+ sections.push(`- Micro-tasking: ${profile.microTasks ? "Sí" : "No"} | Few-shot: ${profile.fewShot ? "Sí" : "No"}`)
96
+ sections.push("")
97
+ }
98
+ }
99
+ } catch {
100
+ // Profile no disponible — omitir sección
101
+ }
102
+ }
103
+
104
+ // ── Tickets abiertos ──
105
+ if (includeTickets) {
106
+ const ticketCount = countOpenTickets()
107
+ if (ticketCount > 0) {
108
+ sections.push(`### 📋 Tickets Abiertos: ${ticketCount}`)
109
+ sections.push("")
110
+ sections.push("Antes de crear uno nuevo, verifica si ya existe un ticket relacionado.")
111
+ sections.push("")
112
+ }
113
+ }
114
+
115
+ // ── learn-errors recientes ──
116
+ if (includeErrors) {
117
+ const recentErrors = getRecentLearnErrors()
118
+ if (recentErrors.length > 0) {
119
+ sections.push("### 🧠 Learn-Errors Recientes")
120
+ sections.push("")
121
+ for (const err of recentErrors.slice(0, 3)) {
122
+ sections.push(`- **${err.id}**: ${err.fix}`)
123
+ }
124
+ sections.push("")
125
+ }
126
+ }
127
+
128
+ // ── Límites de líneas ──
129
+ sections.push("### 📐 Límites @limit")
130
+ sections.push("")
131
+ sections.push("| @kind | Líneas | | @kind | Líneas |")
132
+ sections.push("|-------|--------|--|-------|--------|")
133
+ sections.push("| hook | 80 | | service | 150 |")
134
+ sections.push("| component | 120 | | store | 100 |")
135
+ sections.push("| page | 200 | | util | 100 |")
136
+ sections.push("")
137
+
138
+ // ── Truncar a maxWords ──
139
+ let result = sections.join("\n")
140
+ const words = result.split(/\s+/).length
141
+ if (words > maxWords) {
142
+ // Truncar por líneas completas
143
+ const lines = result.split("\n")
144
+ let count = 0
145
+ const truncated = []
146
+ for (const line of lines) {
147
+ count += line.split(/\s+/).length
148
+ if (count > maxWords) break
149
+ truncated.push(line)
150
+ }
151
+ result = truncated.join("\n") + "\n\n> Preamble truncado a ~300 palabras."
152
+ }
153
+
154
+ return result
155
+ }
156
+
157
+ /**
158
+ * Escribe el preamble a un archivo en el proyecto.
159
+ *
160
+ * @param {object} [options] - Opciones para generatePreamble
161
+ * @returns {string} Ruta del archivo escrito
162
+ */
163
+ export function writePreamble(options = {}) {
164
+ const content = generatePreamble(options)
165
+ const dir = join(process.cwd(), ".opencode")
166
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
167
+ writeFileSync(PREAMBLE_PATH, content, "utf-8")
168
+ return PREAMBLE_PATH
169
+ }
170
+
171
+ // ──────────────────────────────────────────────
172
+ // Helpers (lector de estado del proyecto)
173
+ // ──────────────────────────────────────────────
174
+
175
+ function countOpenTickets() {
176
+ const ticketsDir = join(process.cwd(), "docs", "04-TICKETS")
177
+ if (!existsSync(ticketsDir)) return 0
178
+ try {
179
+ const files = readdirSync(ticketsDir).filter((f) => f.endsWith(".md") && f.startsWith("BOOST-"))
180
+ let open = 0
181
+ for (const f of files) {
182
+ const content = readFileSync(join(ticketsDir, f), "utf-8")
183
+ if (content.includes("🔴 Pendiente") || content.includes("🟡 Parcial")) open++
184
+ }
185
+ return open
186
+ } catch {
187
+ return 0
188
+ }
189
+ }
190
+
191
+ function getRecentLearnErrors() {
192
+ const errors = []
193
+ const srcDir = join(process.cwd(), "src")
194
+ if (!existsSync(srcDir)) return errors
195
+ try {
196
+ const files = readdirSync(srcDir, { recursive: true }).filter((f) => {
197
+ return typeof f === "string" && (f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx"))
198
+ })
199
+ for (const f of files.slice(0, 50)) {
200
+ const fullPath = typeof f === "string" ? join(srcDir, f) : f
201
+ if (!existsSync(fullPath)) continue
202
+ try {
203
+ const content = readFileSync(fullPath, "utf-8")
204
+ const lines = content.split("\n")
205
+ for (const line of lines) {
206
+ const learnErrorTag = "@learn-error"
207
+ const pattern = new RegExp(learnErrorTag + "\\s+id=(\\S+)\\s+.*fix='([^']+)'")
208
+ const match = line.match(pattern)
209
+ if (match) {
210
+ errors.push({ id: match[1], fix: match[2] })
211
+ }
212
+ }
213
+ } catch { /* ignore */ }
214
+ }
215
+ } catch { /* ignore */ }
216
+ return errors
217
+ }
@@ -0,0 +1,264 @@
1
+ // @use(kind, contract, limit)
2
+ // @kind(util)
3
+ // @contract(in: none -> out: PROFILES, getProfile, setProfile, detectProfile, listProfiles)
4
+ // @limit(lines: 150)
5
+
6
+ /**
7
+ * Profile Registry — Módulo OPL Boost
8
+ *
9
+ * Sistema de perfiles para modelos de IA.
10
+ * Detecta automáticamente la capacidad del modelo y configura
11
+ * las estrategias de boost según el perfil (small/medium/large).
12
+ *
13
+ * Uso:
14
+ * import { getProfile } from './boost/profile-registry.js'
15
+ * const profile = getProfile() // { name, compressionLevel, validationLoops, ... }
16
+ */
17
+
18
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs"
19
+ import { join, dirname } from "path"
20
+
21
+ function getStatePath() {
22
+ return join(process.cwd(), ".opencode", "boost-profile.json")
23
+ }
24
+
25
+ function readState() {
26
+ const path = getStatePath()
27
+ if (!existsSync(path)) return null
28
+ try {
29
+ return JSON.parse(readFileSync(path, "utf-8"))
30
+ } catch {
31
+ return null
32
+ }
33
+ }
34
+
35
+ function writeState(state) {
36
+ const path = getStatePath()
37
+ mkdirSync(dirname(path), { recursive: true })
38
+ writeFileSync(path, JSON.stringify(state, null, 2), "utf-8")
39
+ }
40
+
41
+ function clearState() {
42
+ const path = getStatePath()
43
+ if (existsSync(path)) rmSync(path)
44
+ }
45
+
46
+ // ──────────────────────────────────────────────
47
+ // Perfiles predefinidos
48
+ // ──────────────────────────────────────────────
49
+
50
+ export const PROFILES = {
51
+ small: {
52
+ label: "Small Model",
53
+ description: "Modelos con <16K contexto (DeepSeek Coder, CodeGemma, Phi-3)",
54
+ contextWindow: 8000,
55
+ compressionLevel: 80, // % de reducción de contexto
56
+ validationLoops: 3, // reintentos de validación
57
+ microTasks: true, // usar micro-tasking
58
+ templateHydration: true, // usar skeletons
59
+ fewShot: true, // inyectar ejemplos
60
+ contextWindowMin: 0,
61
+ contextWindowMax: 16000,
62
+ },
63
+ medium: {
64
+ label: "Medium Model",
65
+ description: "Modelos con 16K-64K contexto (Qwen 2.5 Coder, Llama 3, Mistral)",
66
+ contextWindow: 32000,
67
+ compressionLevel: 50,
68
+ validationLoops: 2,
69
+ microTasks: true,
70
+ templateHydration: true,
71
+ fewShot: true,
72
+ contextWindowMin: 16000,
73
+ contextWindowMax: 64000,
74
+ },
75
+ large: {
76
+ label: "Large Model",
77
+ description: "Modelos con >64K contexto (GPT-4, Claude 3, Gemini Pro)",
78
+ contextWindow: 128000,
79
+ compressionLevel: 0,
80
+ validationLoops: 1,
81
+ microTasks: false,
82
+ templateHydration: false,
83
+ fewShot: false,
84
+ contextWindowMin: 64000,
85
+ contextWindowMax: Infinity,
86
+ },
87
+ }
88
+
89
+ // ──────────────────────────────────────────────
90
+ // Estado interno (cache)
91
+ // ──────────────────────────────────────────────
92
+
93
+ let _cachedProfile = null
94
+ let _forcedProfile = null
95
+
96
+ // ──────────────────────────────────────────────
97
+ // Detección de perfil
98
+ // ──────────────────────────────────────────────
99
+
100
+ /**
101
+ * Mapeo de nombres de modelo conocidos a perfiles.
102
+ */
103
+ const KNOWN_MODELS = {
104
+ // Small
105
+ "deepseek-coder": "small",
106
+ "deepseek-coder-v2": "small",
107
+ "codegemma": "small",
108
+ "codegemma-2b": "small",
109
+ "phi-3": "small",
110
+ "phi-3-mini": "small",
111
+ "stable-code": "small",
112
+ "codestral": "small",
113
+ // Medium
114
+ "qwen2.5-coder": "medium",
115
+ "qwen2.5-coder-7b": "medium",
116
+ "llama-3": "medium",
117
+ "llama-3-8b": "medium",
118
+ "mistral": "medium",
119
+ "mixtral": "medium",
120
+ "codellama": "medium",
121
+ "codellama-13b": "medium",
122
+ "gemma-2": "medium",
123
+ "openchat": "medium",
124
+ // Large
125
+ "gpt-4": "large",
126
+ "gpt-4-turbo": "large",
127
+ "gpt-4o": "large",
128
+ "claude-3": "large",
129
+ "claude-3-opus": "large",
130
+ "claude-3-sonnet": "large",
131
+ "gemini-pro": "large",
132
+ "gemini-ultra": "large",
133
+ "llama-3-70b": "large",
134
+ "qwen2.5-72b": "large",
135
+ }
136
+
137
+ export function detectProfile() {
138
+ // 1. Forzado manual en memoria (prioridad máxima en el mismo proceso)
139
+ if (_forcedProfile) {
140
+ return _forcedProfile
141
+ }
142
+
143
+ // 2. Estado persistido (entre procesos CLI)
144
+ const state = readState()
145
+ if (state && state.forcedProfile && PROFILES[state.forcedProfile]) {
146
+ return state.forcedProfile
147
+ }
148
+
149
+ // 3. Variable de entorno explícita
150
+ const envProfile = process.env.OPL_BOOST_PROFILE
151
+ if (envProfile && PROFILES[envProfile]) {
152
+ return envProfile
153
+ }
154
+
155
+ // 4. Detección por nombre de modelo
156
+ const modelName = process.env.OPL_MODEL || process.env.OPENAI_MODEL || ""
157
+ const normalized = modelName.toLowerCase().trim()
158
+
159
+ for (const [key, profile] of Object.entries(KNOWN_MODELS)) {
160
+ if (normalized.includes(key)) {
161
+ return profile
162
+ }
163
+ }
164
+
165
+ // 5. Config en prompt-lang.json
166
+ try {
167
+ const configPath = join(process.cwd(), "prompt-lang.json")
168
+ if (existsSync(configPath)) {
169
+ const config = JSON.parse(readFileSync(configPath, "utf-8"))
170
+ const boostConfig = config.boost
171
+ if (boostConfig) {
172
+ if (boostConfig.enabled === false) return null
173
+ if (boostConfig.defaultProfile && boostConfig.defaultProfile !== "auto") {
174
+ return boostConfig.defaultProfile
175
+ }
176
+ // Overrides de perfil específicos
177
+ if (boostConfig.profiles && boostConfig.profiles[modelName]) {
178
+ return boostConfig.profiles[modelName]
179
+ }
180
+ }
181
+ }
182
+ } catch {
183
+ // fallback a default
184
+ }
185
+
186
+ // 6. Default seguro — medium
187
+ return "medium"
188
+ }
189
+
190
+ // ──────────────────────────────────────────────
191
+ // API pública
192
+ // ──────────────────────────────────────────────
193
+
194
+ export function getProfile() {
195
+ if (_cachedProfile && !_forcedProfile) {
196
+ return _cachedProfile
197
+ }
198
+
199
+ const profileName = detectProfile()
200
+ if (!profileName) return null
201
+
202
+ const profileConfig = PROFILES[profileName]
203
+ if (!profileConfig) return null
204
+
205
+ _cachedProfile = {
206
+ name: profileName,
207
+ ...profileConfig,
208
+ }
209
+
210
+ return _cachedProfile
211
+ }
212
+
213
+ export function setProfile(name) {
214
+ if (name === "auto" || name === null || name === undefined) {
215
+ _forcedProfile = null
216
+ _cachedProfile = null
217
+ clearState()
218
+ return getProfile()
219
+ }
220
+
221
+ if (!PROFILES[name]) {
222
+ throw new Error(
223
+ `Perfil "${name}" no válido. Opciones: ${Object.keys(PROFILES).join(", ")}, "auto"`
224
+ )
225
+ }
226
+
227
+ _forcedProfile = name
228
+ _cachedProfile = null
229
+ writeState({ forcedProfile: name, updatedAt: new Date().toISOString() })
230
+ return getProfile()
231
+ }
232
+
233
+ export function listProfiles() {
234
+ return Object.entries(PROFILES).map(([name, config]) => ({
235
+ name,
236
+ label: config.label,
237
+ description: config.description,
238
+ contextWindow: config.contextWindow,
239
+ compressionLevel: config.compressionLevel,
240
+ validationLoops: config.validationLoops,
241
+ microTasks: config.microTasks,
242
+ templateHydration: config.templateHydration,
243
+ fewShot: config.fewShot,
244
+ }))
245
+ }
246
+
247
+ export function clearCache() {
248
+ _cachedProfile = null
249
+ _forcedProfile = null
250
+ clearState()
251
+ }
252
+
253
+ export function isBoostEnabled() {
254
+ try {
255
+ const configPath = join(process.cwd(), "prompt-lang.json")
256
+ if (existsSync(configPath)) {
257
+ const config = JSON.parse(readFileSync(configPath, "utf-8"))
258
+ if (config.boost && config.boost.enabled === false) return false
259
+ }
260
+ } catch {
261
+ // fallback
262
+ }
263
+ return true
264
+ }