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.
- package/README.md +62 -8
- package/bin/cli.js +2 -0
- package/docs/00-ARCHITECTURE/OPL-BOOST-MULTI-AGENT.md +406 -0
- package/docs/02-STANDARDS/AGENTS.template.md +89 -0
- package/docs/02-STANDARDS/ticket-driven-development.md +99 -0
- package/docs/04-TICKETS/BOOST-001-profile-registry.md +66 -0
- package/docs/04-TICKETS/BOOST-002-context-compression.md +58 -0
- package/docs/04-TICKETS/BOOST-003-template-hydration.md +69 -0
- package/docs/04-TICKETS/BOOST-004-fewshot-engine.md +58 -0
- package/docs/04-TICKETS/BOOST-005-agent-pool.md +69 -0
- package/docs/04-TICKETS/BOOST-006-specialized-agents.md +53 -0
- package/docs/04-TICKETS/BOOST-007-validation-loop.md +56 -0
- package/docs/04-TICKETS/BOOST-008-orchestrator.md +71 -0
- package/docs/04-TICKETS/BOOST-009-cache-system.md +56 -0
- package/docs/04-TICKETS/BOOST-010-cli-mcp.md +67 -0
- package/docs/04-TICKETS/BOOST-011-self-learning.md +50 -0
- package/docs/04-TICKETS/BOOST-012-prompt-preamble.md +109 -0
- package/docs/04-TICKETS/BOOST-013-hydrator-duplicate-code.md +132 -0
- package/docs/04-TICKETS/BOOST-014-multiagent-missing-parts.md +87 -0
- package/docs/04-TICKETS/BOOST-015-skeleton-type-missing.md +76 -0
- package/docs/04-TICKETS/BOOST-016-output-path-duplicate.md +68 -0
- package/docs/04-TICKETS/INDEX.md +89 -0
- package/docs/04-TICKETS/_archive/BOOST-005-micro-tasking.md +67 -0
- package/docs/04-TICKETS/_archive/BOOST-006-validation-loop.md +66 -0
- package/docs/04-TICKETS/_archive/BOOST-007-progressive-pipeline.md +69 -0
- package/docs/04-TICKETS/_archive/BOOST-008-cli-mcp-integration.md +74 -0
- package/docs/AI_CONTEXT.md +16 -0
- 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 +9 -2
- package/scripts/postinstall.js +37 -0
- package/src/boost/agent-pool.js +442 -0
- package/src/boost/agents/index.js +79 -0
- package/src/boost/cache.js +241 -0
- package/src/boost/context-compressor.js +354 -0
- package/src/boost/fewshot-retriever.js +332 -0
- package/src/boost/hardware-detector.js +486 -0
- package/src/boost/hydrator.js +398 -0
- package/src/boost/index.js +60 -0
- package/src/boost/orchestrator.js +615 -0
- package/src/boost/preamble.js +217 -0
- package/src/boost/profile-registry.js +264 -0
- package/src/boost/self-learn.js +247 -0
- package/src/boost/skeletons/component.skeleton.js +24 -0
- package/src/boost/skeletons/hook.skeleton.js +27 -0
- package/src/boost/skeletons/index.js +67 -0
- package/src/boost/skeletons/page.skeleton.js +22 -0
- package/src/boost/skeletons/service.skeleton.js +20 -0
- package/src/boost/skeletons/store.skeleton.js +18 -0
- package/src/boost/skeletons/type.skeleton.js +11 -0
- package/src/boost/task-dispatcher.js +142 -0
- package/src/boost/validation-loop.js +495 -0
- package/src/cli/commands-boost.js +394 -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
- package/src/mcp-refactor/handlers/boost.js +295 -0
- package/src/mcp-refactor/router.js +19 -0
- package/src/mcp-refactor/tools.js +113 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// @use(kind, contract, limit, error)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: executionResult -> out: metrics -> { record, recommend, autoProfile, stats } @error: SelfLearnError)
|
|
4
|
+
// @limit(lines: 200)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Self-Learning — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Registra métricas de cada ejecución Boost y las usa para mejorar
|
|
10
|
+
* automáticamente la configuración en futuras sesiones.
|
|
11
|
+
*
|
|
12
|
+
* Si un perfil consistentemente falla, recomienda bajar a uno más conservador.
|
|
13
|
+
* Si un perfil consistentemente tiene éxito rápido, recomienda subir.
|
|
14
|
+
*
|
|
15
|
+
* Uso:
|
|
16
|
+
* import { recordExecution, autoProfile, selfLearnStats } from './boost/self-learn.js'
|
|
17
|
+
* recordExecution({ kind: 'hook', profile: 'small', success: true, duration: 12000, attempts: 2 })
|
|
18
|
+
* const recommended = autoProfile()
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
|
|
22
|
+
import { join } from "path"
|
|
23
|
+
|
|
24
|
+
// ──────────────────────────────────────────────
|
|
25
|
+
// Configuración
|
|
26
|
+
// ──────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const METRICS_DIR = join(process.cwd(), ".opencode", "boost")
|
|
29
|
+
const METRICS_FILE = join(METRICS_DIR, "metrics.json")
|
|
30
|
+
const MAX_ENTRIES = 1000
|
|
31
|
+
|
|
32
|
+
// ──────────────────────────────────────────────
|
|
33
|
+
// Lectura y escritura de métricas
|
|
34
|
+
// ──────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function readMetrics() {
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(METRICS_FILE)) return { entries: [] }
|
|
39
|
+
const data = readFileSync(METRICS_FILE, "utf-8")
|
|
40
|
+
return JSON.parse(data)
|
|
41
|
+
} catch {
|
|
42
|
+
return { entries: [] }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeMetrics(metrics) {
|
|
47
|
+
if (!existsSync(METRICS_DIR)) {
|
|
48
|
+
mkdirSync(METRICS_DIR, { recursive: true })
|
|
49
|
+
}
|
|
50
|
+
// Rotación: mantener solo las últimas MAX_ENTRIES
|
|
51
|
+
if (metrics.entries.length > MAX_ENTRIES) {
|
|
52
|
+
metrics.entries = metrics.entries.slice(-MAX_ENTRIES)
|
|
53
|
+
}
|
|
54
|
+
writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2), "utf-8")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ──────────────────────────────────────────────
|
|
58
|
+
// API: recordExecution
|
|
59
|
+
// ──────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Registra métricas de una ejecución Boost.
|
|
63
|
+
*
|
|
64
|
+
* @param {{ kind: string, profile: string, success: boolean, duration: number, attempts: number, errors?: Array }} execution
|
|
65
|
+
* @returns {{ recorded: boolean, totalEntries: number }}
|
|
66
|
+
*/
|
|
67
|
+
export function recordExecution(execution) {
|
|
68
|
+
const metrics = readMetrics()
|
|
69
|
+
|
|
70
|
+
const entry = {
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
kind: execution.kind || "unknown",
|
|
73
|
+
profile: execution.profile || "unknown",
|
|
74
|
+
success: execution.success ?? false,
|
|
75
|
+
duration: execution.duration || 0,
|
|
76
|
+
attempts: execution.attempts || 1,
|
|
77
|
+
errors: execution.errors || [],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
metrics.entries.push(entry)
|
|
81
|
+
writeMetrics(metrics)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
recorded: true,
|
|
85
|
+
totalEntries: metrics.entries.length,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ──────────────────────────────────────────────
|
|
90
|
+
// API: autoProfile
|
|
91
|
+
// ──────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Analiza métricas históricas y recomienda un perfil.
|
|
95
|
+
*
|
|
96
|
+
* Reglas:
|
|
97
|
+
* - Si perfil actual falla >3 veces seguidas → recomienda bajar
|
|
98
|
+
* - Si perfil actual tiene éxito >5 veces seguidas con duración baja → recomienda subir
|
|
99
|
+
* - Si validación toma >5 intentos → recomienda activar template hydration
|
|
100
|
+
*
|
|
101
|
+
* @returns {{ recommended: string, reason: string, confidence: number }}
|
|
102
|
+
*/
|
|
103
|
+
export function autoProfile() {
|
|
104
|
+
const metrics = readMetrics()
|
|
105
|
+
const entries = metrics.entries
|
|
106
|
+
|
|
107
|
+
if (entries.length < 3) {
|
|
108
|
+
return {
|
|
109
|
+
recommended: "medium",
|
|
110
|
+
reason: "Datos insuficientes (menos de 3 ejecuciones). Perfil safe: medium.",
|
|
111
|
+
confidence: 0.3,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Analizar últimas 10 ejecuciones
|
|
116
|
+
const recent = entries.slice(-10)
|
|
117
|
+
const failures = recent.filter((e) => !e.success)
|
|
118
|
+
const successes = recent.filter((e) => e.success)
|
|
119
|
+
|
|
120
|
+
const currentProfile = recent[recent.length - 1]?.profile || "medium"
|
|
121
|
+
|
|
122
|
+
// Si hay muchas fallas consecutivas, recomendar bajar
|
|
123
|
+
const consecutiveFailures = countConsecutiveFailures(recent)
|
|
124
|
+
if (consecutiveFailures >= 3) {
|
|
125
|
+
const recommendation = downgradeProfile(currentProfile)
|
|
126
|
+
return {
|
|
127
|
+
recommended: recommendation,
|
|
128
|
+
reason: `${consecutiveFailures} fallos consecutivos con perfil ${currentProfile}. Se recomienda bajar a ${recommendation}.`,
|
|
129
|
+
confidence: 0.7,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Si hay muchos éxitos rápidos, recomendar subir
|
|
134
|
+
const consecutiveSuccesses = countConsecutiveSuccesses(recent)
|
|
135
|
+
const avgDuration = successes.length > 0
|
|
136
|
+
? successes.reduce((sum, e) => sum + e.duration, 0) / successes.length
|
|
137
|
+
: 0
|
|
138
|
+
|
|
139
|
+
if (consecutiveSuccesses >= 5 && avgDuration < 10000) {
|
|
140
|
+
const recommendation = upgradeProfile(currentProfile)
|
|
141
|
+
return {
|
|
142
|
+
recommended: recommendation,
|
|
143
|
+
reason: `${consecutiveSuccesses} éxitos consecutivos con ${currentProfile} y duración promedio ${(avgDuration / 1000).toFixed(1)}s. Se puede intentar ${recommendation}.`,
|
|
144
|
+
confidence: 0.6,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Si validación toma muchos intentos, recomendar hydration
|
|
149
|
+
const avgAttempts = recent.reduce((sum, e) => sum + e.attempts, 0) / recent.length
|
|
150
|
+
if (avgAttempts > 3) {
|
|
151
|
+
return {
|
|
152
|
+
recommended: currentProfile,
|
|
153
|
+
reason: `Validación promedio de ${avgAttempts.toFixed(1)} intentos. Se recomienda activar template hydration.`,
|
|
154
|
+
confidence: 0.5,
|
|
155
|
+
suggestion: "enable_template_hydration",
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
recommended: currentProfile,
|
|
161
|
+
reason: `Perfil ${currentProfile} funcionando correctamente. Sin cambios recomendados.`,
|
|
162
|
+
confidence: 0.8,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function countConsecutiveFailures(entries) {
|
|
167
|
+
let count = 0
|
|
168
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
169
|
+
if (!entries[i].success) count++
|
|
170
|
+
else break
|
|
171
|
+
}
|
|
172
|
+
return count
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function countConsecutiveSuccesses(entries) {
|
|
176
|
+
let count = 0
|
|
177
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
178
|
+
if (entries[i].success) count++
|
|
179
|
+
else break
|
|
180
|
+
}
|
|
181
|
+
return count
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function downgradeProfile(profile) {
|
|
185
|
+
const order = ["small", "medium", "large"]
|
|
186
|
+
const idx = order.indexOf(profile)
|
|
187
|
+
return order[Math.max(0, idx - 1)]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function upgradeProfile(profile) {
|
|
191
|
+
const order = ["small", "medium", "large"]
|
|
192
|
+
const idx = order.indexOf(profile)
|
|
193
|
+
return order[Math.min(order.length - 1, idx + 1)]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ──────────────────────────────────────────────
|
|
197
|
+
// API: selfLearnStats
|
|
198
|
+
// ──────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Estadísticas de auto-aprendizaje.
|
|
202
|
+
*
|
|
203
|
+
* @returns {{ totalExecutions: number, successRate: number, avgDuration: number, avgAttempts: number, byProfile: object, byKind: object, recommendation: object }}
|
|
204
|
+
*/
|
|
205
|
+
export function selfLearnStats() {
|
|
206
|
+
const metrics = readMetrics()
|
|
207
|
+
const entries = metrics.entries
|
|
208
|
+
|
|
209
|
+
if (entries.length === 0) {
|
|
210
|
+
return {
|
|
211
|
+
totalExecutions: 0,
|
|
212
|
+
successRate: 0,
|
|
213
|
+
avgDuration: 0,
|
|
214
|
+
avgAttempts: 0,
|
|
215
|
+
byProfile: {},
|
|
216
|
+
byKind: {},
|
|
217
|
+
recommendation: autoProfile(),
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const successRate = entries.filter((e) => e.success).length / entries.length
|
|
222
|
+
const avgDuration = entries.reduce((sum, e) => sum + e.duration, 0) / entries.length
|
|
223
|
+
const avgAttempts = entries.reduce((sum, e) => sum + e.attempts, 0) / entries.length
|
|
224
|
+
|
|
225
|
+
const byProfile = {}
|
|
226
|
+
const byKind = {}
|
|
227
|
+
|
|
228
|
+
for (const e of entries) {
|
|
229
|
+
byProfile[e.profile] = byProfile[e.profile] || { count: 0, successes: 0 }
|
|
230
|
+
byProfile[e.profile].count++
|
|
231
|
+
if (e.success) byProfile[e.profile].successes++
|
|
232
|
+
|
|
233
|
+
byKind[e.kind] = byKind[e.kind] || { count: 0, successes: 0 }
|
|
234
|
+
byKind[e.kind].count++
|
|
235
|
+
if (e.success) byKind[e.kind].successes++
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
totalExecutions: entries.length,
|
|
240
|
+
successRate: Math.round(successRate * 100),
|
|
241
|
+
avgDuration: Math.round(avgDuration),
|
|
242
|
+
avgAttempts: Math.round(avgAttempts * 10) / 10,
|
|
243
|
+
byProfile,
|
|
244
|
+
byKind,
|
|
245
|
+
recommendation: autoProfile(),
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Skeleton template for @kind(component)
|
|
2
|
+
// Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_VARIANTS}}, {{PLACEHOLDER_PROPS}}, {{PLACEHOLDER_JSX}}
|
|
3
|
+
// Max lines: 120
|
|
4
|
+
|
|
5
|
+
// @use(kind, props, limit)
|
|
6
|
+
// @kind(component)
|
|
7
|
+
// @props({ children: ReactNode, className: string })
|
|
8
|
+
// @limit(lines: 120)
|
|
9
|
+
|
|
10
|
+
{{PLACEHOLDER_IMPORTS}}
|
|
11
|
+
|
|
12
|
+
{{PLACEHOLDER_VARIANTS}}
|
|
13
|
+
|
|
14
|
+
export interface {{PLACEHOLDER_NAME}}Props {
|
|
15
|
+
{{PLACEHOLDER_PROPS}}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function {{PLACEHOLDER_NAME}}({ {{PLACEHOLDER_PROPS_DESTRUCTURE}} }: {{PLACEHOLDER_NAME}}Props) {
|
|
19
|
+
return (
|
|
20
|
+
{{PLACEHOLDER_JSX}}
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default {{PLACEHOLDER_NAME}}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Skeleton template for @kind(hook)
|
|
2
|
+
// Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_TYPES}}, {{PLACEHOLDER_STATE}}, {{PLACEHOLDER_LOGIC}}, {{PLACEHOLDER_EXPORTS}}
|
|
3
|
+
// Max lines: 80
|
|
4
|
+
|
|
5
|
+
// @use(kind, contract, limit)
|
|
6
|
+
// @kind(hook)
|
|
7
|
+
// @contract(in: {{PLACEHOLDER_CONTRACT_IN}} -> out: {{PLACEHOLDER_CONTRACT_OUT}} @error: {{PLACEHOLDER_CONTRACT_ERROR}})
|
|
8
|
+
// @limit(lines: 80)
|
|
9
|
+
|
|
10
|
+
{{PLACEHOLDER_IMPORTS}}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* {{PLACEHOLDER_DESCRIPTION}}
|
|
14
|
+
*
|
|
15
|
+
* @returns {{PLACEHOLDER_RETURN_TYPE}}
|
|
16
|
+
*/
|
|
17
|
+
{{PLACEHOLDER_TYPES}}
|
|
18
|
+
|
|
19
|
+
export function {{PLACEHOLDER_NAME}}({{PLACEHOLDER_PARAMS}}) {
|
|
20
|
+
{{PLACEHOLDER_STATE}}
|
|
21
|
+
|
|
22
|
+
{{PLACEHOLDER_LOGIC}}
|
|
23
|
+
|
|
24
|
+
return {{PLACEHOLDER_RETURN}}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default {{PLACEHOLDER_NAME}}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// @use(kind, contract, limit)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: kind -> out: skeletonContent, @error: UnknownSkeletonError)
|
|
4
|
+
// @limit(lines: 60)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registro unificado de skeletons para el Template Hydration System.
|
|
8
|
+
*
|
|
9
|
+
* Cada skeleton es un string con marcadores {{PLACEHOLDER_*}} que el
|
|
10
|
+
* hydrator reemplaza con la lógica de negocio generada por los agentes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync } from "fs"
|
|
14
|
+
import { join } from "path"
|
|
15
|
+
|
|
16
|
+
const SKELETONS_DIR = join(import.meta.dirname || __dirname)
|
|
17
|
+
|
|
18
|
+
const SKELETON_FILES = {
|
|
19
|
+
hook: "hook.skeleton.js",
|
|
20
|
+
component: "component.skeleton.js",
|
|
21
|
+
page: "page.skeleton.js",
|
|
22
|
+
service: "service.skeleton.js",
|
|
23
|
+
store: "store.skeleton.js",
|
|
24
|
+
type: "type.skeleton.js",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Obtiene el contenido de un skeleton por tipo.
|
|
29
|
+
* @param {string} kind - Tipo de skeleton (hook, component, page, service, store)
|
|
30
|
+
* @returns {string} Contenido del skeleton
|
|
31
|
+
*/
|
|
32
|
+
export function getSkeleton(kind) {
|
|
33
|
+
const filename = SKELETON_FILES[kind]
|
|
34
|
+
if (!filename) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Skeleton desconocido: "${kind}". Tipos: ${Object.keys(SKELETON_FILES).join(", ")}`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const path = join(SKELETONS_DIR, filename)
|
|
41
|
+
|
|
42
|
+
if (!existsSync(path)) {
|
|
43
|
+
throw new Error(`Archivo skeleton no encontrado: ${path}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return readFileSync(path, "utf-8")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Lista los tipos de skeletons disponibles.
|
|
51
|
+
* @returns {Array<{kind: string, file: string}>}
|
|
52
|
+
*/
|
|
53
|
+
export function listSkeletons() {
|
|
54
|
+
return Object.entries(SKELETON_FILES).map(([kind, file]) => ({
|
|
55
|
+
kind,
|
|
56
|
+
file,
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Verifica si un tipo de skeleton existe.
|
|
62
|
+
* @param {string} kind
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function hasSkeleton(kind) {
|
|
66
|
+
return kind in SKELETON_FILES
|
|
67
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Skeleton template for @kind(page)
|
|
2
|
+
// Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_TYPES}}, {{PLACEHOLDER_LAYOUT}}, {{PLACEHOLDER_DATA}}, {{PLACEHOLDER_JSX}}
|
|
3
|
+
// Max lines: 200
|
|
4
|
+
|
|
5
|
+
// @use(kind, compose, limit)
|
|
6
|
+
// @kind(page)
|
|
7
|
+
// @compose(components: [{{PLACEHOLDER_COMPONENTS}}])
|
|
8
|
+
// @limit(lines: 200)
|
|
9
|
+
|
|
10
|
+
{{PLACEHOLDER_IMPORTS}}
|
|
11
|
+
|
|
12
|
+
{{PLACEHOLDER_TYPES}}
|
|
13
|
+
|
|
14
|
+
export default function {{PLACEHOLDER_NAME}}Page() {
|
|
15
|
+
{{PLACEHOLDER_DATA}}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
{{PLACEHOLDER_LAYOUT}}
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
{{PLACEHOLDER_HELPERS}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Skeleton template for @kind(service)
|
|
2
|
+
// Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_TYPES}}, {{PLACEHOLDER_METHODS}}
|
|
3
|
+
// Max lines: 150
|
|
4
|
+
|
|
5
|
+
// @use(kind, contract, limit)
|
|
6
|
+
// @kind(service)
|
|
7
|
+
// @contract(in: {{PLACEHOLDER_CONTRACT_IN}} -> out: {{PLACEHOLDER_CONTRACT_OUT}} @error: {{PLACEHOLDER_CONTRACT_ERROR}})
|
|
8
|
+
// @limit(lines: 150)
|
|
9
|
+
|
|
10
|
+
{{PLACEHOLDER_IMPORTS}}
|
|
11
|
+
|
|
12
|
+
{{PLACEHOLDER_BASE_URL}}
|
|
13
|
+
|
|
14
|
+
{{PLACEHOLDER_TYPES}}
|
|
15
|
+
|
|
16
|
+
{{PLACEHOLDER_METHODS}}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
{{PLACEHOLDER_EXPORTS}}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Skeleton template for @kind(store)
|
|
2
|
+
// Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_TYPES}}, {{PLACEHOLDER_STORE}}
|
|
3
|
+
// Max lines: 100
|
|
4
|
+
|
|
5
|
+
// @use(kind, deps, limit)
|
|
6
|
+
// @kind(store)
|
|
7
|
+
// @deps({{PLACEHOLDER_DEPS}})
|
|
8
|
+
// @limit(lines: 100)
|
|
9
|
+
|
|
10
|
+
{{PLACEHOLDER_IMPORTS}}
|
|
11
|
+
|
|
12
|
+
{{PLACEHOLDER_TYPES}}
|
|
13
|
+
|
|
14
|
+
{{PLACEHOLDER_STORE}}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
{{PLACEHOLDER_EXPORTS}}
|
|
18
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @use(kind, contract, limit)
|
|
2
|
+
// @kind(util)
|
|
3
|
+
// @contract(in: task, kind -> out: DAG de agnodes -> { nodes: [], edges: [] })
|
|
4
|
+
// @limit(lines: 150)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Task Dispatcher — Módulo OPL Boost
|
|
8
|
+
*
|
|
9
|
+
* Convierte una descripción de tarea en un DAG (Directed Acyclic Graph)
|
|
10
|
+
* de pasos que los agentes pueden ejecutar en paralelo o secuencia.
|
|
11
|
+
*
|
|
12
|
+
* Uso:
|
|
13
|
+
* import { plan } from './boost/task-dispatcher.js'
|
|
14
|
+
* const dag = plan('crea hook useAuth con Supabase', 'hook')
|
|
15
|
+
* // → { task, kind, nodes: [...], edges: [...] }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ──────────────────────────────────────────────
|
|
19
|
+
// DAG templates por @kind
|
|
20
|
+
// ──────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const DAG_TEMPLATES = {
|
|
23
|
+
hook: {
|
|
24
|
+
nodes: [
|
|
25
|
+
{ id: "types", agent: "types", deps: [], description: "Define tipos e interfaces" },
|
|
26
|
+
{ id: "state", agent: "state", deps: [], description: "Diseña estado y efectos" },
|
|
27
|
+
{ id: "logic", agent: "logic", deps: ["types", "state"], description: "Implementa lógica de negocio" },
|
|
28
|
+
{ id: "validate", agent: "validate", deps: ["logic"], description: "Valida y corrige errores" },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
component: {
|
|
32
|
+
nodes: [
|
|
33
|
+
{ id: "types", agent: "types", deps: [], description: "Define interface de props" },
|
|
34
|
+
{ id: "state", agent: "state", deps: [], description: "Diseña estado interno" },
|
|
35
|
+
{ id: "logic", agent: "logic", deps: ["types", "state"], description: "Implementa JSX y variantes" },
|
|
36
|
+
{ id: "validate", agent: "validate", deps: ["logic"], description: "Valida y corrige errores" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
page: {
|
|
40
|
+
nodes: [
|
|
41
|
+
{ id: "types", agent: "types", deps: [], description: "Define tipos de la página" },
|
|
42
|
+
{ id: "state", agent: "state", deps: [], description: "Diseña fetching y estado" },
|
|
43
|
+
{ id: "logic", agent: "logic", deps: ["types", "state"], description: "Implementa layout y componentes" },
|
|
44
|
+
{ id: "validate", agent: "validate", deps: ["logic"], description: "Valida y corrige errores" },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
service: {
|
|
48
|
+
nodes: [
|
|
49
|
+
{ id: "types", agent: "types", deps: [], description: "Define DTOs y tipos de respuesta" },
|
|
50
|
+
{ id: "logic", agent: "logic", deps: ["types"], description: "Implementa métodos del servicio" },
|
|
51
|
+
{ id: "validate", agent: "validate", deps: ["logic"], description: "Valida y corrige errores" },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
store: {
|
|
55
|
+
nodes: [
|
|
56
|
+
{ id: "types", agent: "types", deps: [], description: "Define estado global y tipos" },
|
|
57
|
+
{ id: "logic", agent: "logic", deps: ["types"], description: "Implementa store y acciones" },
|
|
58
|
+
{ id: "validate", agent: "validate", deps: ["logic"], description: "Valida y corrige errores" },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
type: {
|
|
62
|
+
nodes: [
|
|
63
|
+
{ id: "types", agent: "types", deps: [], description: "Define TypeScript types e interfaces" },
|
|
64
|
+
{ id: "validate", agent: "validate", deps: ["types"], description: "Valida y corrige errores" },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ──────────────────────────────────────────────
|
|
70
|
+
// API principal
|
|
71
|
+
// ──────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Planifica un DAG de tareas para una descripción y tipo.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} task - Descripción de la tarea
|
|
77
|
+
* @param {string} kind - Tipo de archivo (hook, component, page, service, store)
|
|
78
|
+
* @returns {{ task: string, kind: string, nodes: Array, edges: Array, stages: object }}
|
|
79
|
+
*/
|
|
80
|
+
export function plan(task, kind) {
|
|
81
|
+
const template = DAG_TEMPLATES[kind] || DAG_TEMPLATES.component
|
|
82
|
+
|
|
83
|
+
// Generar edges del DAG
|
|
84
|
+
const edges = []
|
|
85
|
+
for (const node of template.nodes) {
|
|
86
|
+
for (const dep of node.deps) {
|
|
87
|
+
edges.push({ from: dep, to: node.id })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Clasificar nodos por stage (paralelizable vs secuencial)
|
|
92
|
+
const parallelNodes = template.nodes.filter((n) => n.deps.length === 0)
|
|
93
|
+
const sequentialNodes = template.nodes.filter((n) => n.deps.length > 0)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
task,
|
|
97
|
+
kind,
|
|
98
|
+
nodes: template.nodes,
|
|
99
|
+
edges,
|
|
100
|
+
stages: {
|
|
101
|
+
parallel: parallelNodes.map((n) => n.id),
|
|
102
|
+
sequential: sequentialNodes.map((n) => n.id),
|
|
103
|
+
},
|
|
104
|
+
nodeCount: template.nodes.length,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Detecta automáticamente el @kind basado en la descripción de la tarea.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} task - Descripción de la tarea
|
|
112
|
+
* @returns {string} kind detectado o 'component' como default
|
|
113
|
+
*/
|
|
114
|
+
export function detectKind(task) {
|
|
115
|
+
const lower = task.toLowerCase()
|
|
116
|
+
|
|
117
|
+
if (/\b(hook|use\w+)\b/.test(lower)) return "hook"
|
|
118
|
+
if (/\b(service|api|repository|client|servicio)\b/.test(lower)) return "service"
|
|
119
|
+
if (/\b(store|state|context|provider|estado)\b/.test(lower)) return "store"
|
|
120
|
+
if (/\b(page|screen|view|route|página|pagina)\b/.test(lower)) return "page"
|
|
121
|
+
if (/\b(type|interface|types|typings|dtype|tipos)\b/.test(lower)) return "type"
|
|
122
|
+
if (/\b(component|button|input|card|modal|form|table|dialog|componente|botón|formulario)\b/.test(lower)) return "component"
|
|
123
|
+
|
|
124
|
+
return "component"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Lista los tipos de DAG disponibles.
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
export function listKinds() {
|
|
132
|
+
return Object.keys(DAG_TEMPLATES)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Obtiene el DAG template para un kind específico.
|
|
137
|
+
* @param {string} kind
|
|
138
|
+
* @returns {object|null}
|
|
139
|
+
*/
|
|
140
|
+
export function getDAG(kind) {
|
|
141
|
+
return DAG_TEMPLATES[kind] || null
|
|
142
|
+
}
|