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,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,11 @@
1
+ // Skeleton template for @kind(type)
2
+ // Markers: {{PLACEHOLDER_IMPORTS}}, {{PLACEHOLDER_TYPES}}, {{PLACEHOLDER_EXPORTS}}
3
+
4
+ // @use(kind)
5
+ // @kind(type)
6
+
7
+ {{PLACEHOLDER_IMPORTS}}
8
+
9
+ {{PLACEHOLDER_TYPES}}
10
+
11
+ {{PLACEHOLDER_EXPORTS}}
@@ -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
+ }