openprompt-lang 0.9.0 → 0.10.1

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/LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openprompt-lang",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "PromptLang CLI — Context Engine de anotaciones para desarrollo asistido por IA",
5
5
  "type": "module",
6
6
  "main": "./bin/cli.js",
@@ -21,6 +21,8 @@
21
21
  "src/ts-transformer/",
22
22
  "src/vite-plugin/",
23
23
  "src/mcp-server.js",
24
+ "src/mcp-session.js",
25
+ "src/mcp-workflow.js",
24
26
  "src/templates/langs/",
25
27
  "src/templates/knowledge-repo/",
26
28
  "src/templates/semantic-index.json",
@@ -88,6 +90,7 @@
88
90
  },
89
91
  "devDependencies": {
90
92
  "eslint": "^10.3.0",
93
+ "husky": "^9.1.7",
91
94
  "prettier": "^3.8.3",
92
95
  "vitest": "^4.1.6"
93
96
  },
@@ -98,5 +101,14 @@
98
101
  "bugs": {
99
102
  "url": "https://github.com/DeadJustLive/OpenPrompt-Lang/issues"
100
103
  },
101
- "homepage": "https://github.com/DeadJustLive/OpenPrompt-Lang#readme"
104
+ "homepage": "https://github.com/DeadJustLive/OpenPrompt-Lang#readme",
105
+ "lint-staged": {
106
+ "*.{ts,tsx,js,jsx,mjs,cjs}": [
107
+ "eslint --fix",
108
+ "prettier --write"
109
+ ],
110
+ "*.{ts,tsx,js,jsx}": [
111
+ "openprompt-lang lint-file"
112
+ ]
113
+ }
102
114
  }
@@ -127,6 +127,59 @@
127
127
  "description": "Archivo de salida del extractor"
128
128
  }
129
129
  }
130
+ },
131
+ "domain": {
132
+ "type": "object",
133
+ "description": "Configuración de dominio modular de contexto",
134
+ "properties": {
135
+ "active": {
136
+ "type": "array",
137
+ "items": { "type": "string" },
138
+ "description": "Dominios activos (ej: programming, shared)"
139
+ },
140
+ "default": {
141
+ "type": "string",
142
+ "description": "Dominio por defecto"
143
+ },
144
+ "patterns": {
145
+ "type": "object",
146
+ "description": "Patrones por dominio",
147
+ "additionalProperties": {
148
+ "type": "array",
149
+ "items": { "type": "string" }
150
+ }
151
+ }
152
+ }
153
+ },
154
+ "workflow": {
155
+ "type": "object",
156
+ "description": "Configuración de enforcement de workflow para IA",
157
+ "properties": {
158
+ "strictMode": {
159
+ "type": "boolean",
160
+ "default": false,
161
+ "description": "Bloquea herramientas si no se cumplen prerrequisitos"
162
+ },
163
+ "autoTicket": {
164
+ "type": "boolean",
165
+ "default": true,
166
+ "description": "Crea tickets automáticos cuando se detecta fricción en el workflow"
167
+ },
168
+ "suggestionsLevel": {
169
+ "type": "string",
170
+ "enum": ["minimal", "standard", "full"],
171
+ "default": "full",
172
+ "description": "Nivel de sugerencias de workflow"
173
+ },
174
+ "requiredToolsBefore": {
175
+ "type": "object",
176
+ "description": "Tools que requieren otros tools antes de ejecutarse",
177
+ "additionalProperties": {
178
+ "type": "array",
179
+ "items": { "type": "string" }
180
+ }
181
+ }
182
+ }
130
183
  }
131
184
  },
132
185
  "required": ["name", "stack"]
@@ -1,12 +1,14 @@
1
1
  // @use(commander, kind, contract, limit)
2
2
  // @kind(util)
3
- // @contract(in: program -> out: void, sideEffect: registra comandos context/validate/extract/analyze)
4
- // @limit(lines: 80)
3
+ // @contract(in: program -> out: void, sideEffect: registra comandos context/validate/extract/analyze y dominios)
4
+ // @limit(lines: 180)
5
5
 
6
6
  export function register(program) {
7
- program
7
+ const contextCmd = program
8
8
  .command('context')
9
9
  .description('Extraer contexto del proyecto para la IA')
10
+
11
+ contextCmd
10
12
  .option('--output <file>', 'Archivo de salida', 'contexto.md')
11
13
  .option('--scope <module>', 'Filtrar por módulo (@scope)')
12
14
  .option('--no-ai', 'No incluir anotaciones PromptLang en el output')
@@ -17,14 +19,75 @@ export function register(program) {
17
19
  .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
18
20
  .option('--unified', 'Búsqueda unificada: knowledge + learning + templates + tickets + semántico')
19
21
  .action(async (options) => {
20
- const { context, contextUnified } = await import('../commands/context.js')
21
22
  if (options.unified) {
23
+ const { contextUnified } = await import('../commands/context.js')
22
24
  await contextUnified(options)
23
25
  return
24
26
  }
27
+ const { context } = await import('../commands/context.js')
25
28
  await context(options)
26
29
  })
27
30
 
31
+ // Sub-comandos de dominios modulares
32
+ const domainCmd = contextCmd
33
+ .command('domain')
34
+ .description('Gestionar dominios modulares de contexto')
35
+
36
+ domainCmd
37
+ .command('init')
38
+ .description('Inicializar sistema de dominios modulares')
39
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
40
+ .action(async (options) => {
41
+ const { domainInit } = await import('../commands/domain.js')
42
+ await domainInit(options)
43
+ })
44
+
45
+ domainCmd
46
+ .command('use <domains>')
47
+ .description('Activar dominios de contexto (separar con +, ej: programming+shared)')
48
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
49
+ .option('--no-shared', 'No incluir el dominio shared automaticamente')
50
+ .action(async (domains, options) => {
51
+ const { domainUse } = await import('../commands/domain.js')
52
+ await domainUse(domains, options)
53
+ })
54
+
55
+ domainCmd
56
+ .command('switch <domain>')
57
+ .description('Cambiar el dominio activo')
58
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
59
+ .action(async (domain, options) => {
60
+ const { domainSwitch } = await import('../commands/domain.js')
61
+ await domainSwitch(domain, options)
62
+ })
63
+
64
+ domainCmd
65
+ .command('clear')
66
+ .description('Desactivar todos los dominios (solo shared permanece)')
67
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
68
+ .action(async (options) => {
69
+ const { domainClear } = await import('../commands/domain.js')
70
+ await domainClear(options)
71
+ })
72
+
73
+ domainCmd
74
+ .command('status')
75
+ .description('Mostrar dominios activos y su contenido')
76
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
77
+ .action(async (options) => {
78
+ const { domainStatus } = await import('../commands/domain.js')
79
+ await domainStatus(options)
80
+ })
81
+
82
+ domainCmd
83
+ .command('load <domain>')
84
+ .description('Cargar un dominio sin activarlo (solo consulta)')
85
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
86
+ .action(async (domain, options) => {
87
+ const { domainLoad } = await import('../commands/domain.js')
88
+ await domainLoad(domain, options)
89
+ })
90
+
28
91
  program
29
92
  .command('docs')
30
93
  .description('Generar FRAMEWORK.md desde el estado actual del proyecto')
@@ -83,4 +146,27 @@ export function register(program) {
83
146
  const { extractAnalyze } = await import('../commands/extract.js')
84
147
  await extractAnalyze(sourceDir || 'src', {})
85
148
  })
86
- }
149
+
150
+ program
151
+ .command('learn-pattern')
152
+ .description('Extraer patron de estructura/tono/formato de documentos ejemplo')
153
+ .option('--from <path>', 'Ruta a archivo o directorio con documentos ejemplo')
154
+ .option('--type <type>', 'Tipo de patron: report, code, technical-writing, legal, business, product', 'report')
155
+ .option('--name <name>', 'Nombre del patron')
156
+ .option('--domain <domain>', 'Dominio destino (default: tipo del patron)')
157
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
158
+ .action(async (options) => {
159
+ const { patternExtract } = await import('../commands/pattern.js')
160
+ await patternExtract(options)
161
+ })
162
+
163
+ program
164
+ .command('pattern-list')
165
+ .description('Listar patrones extraidos por dominio')
166
+ .option('--domain <domain>', 'Filtrar por dominio especifico')
167
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
168
+ .action(async (options) => {
169
+ const { patternList } = await import('../commands/pattern.js')
170
+ await patternList(options)
171
+ })
172
+ }
@@ -31,9 +31,10 @@ export function register(program) {
31
31
  knowledge
32
32
  .command('list')
33
33
  .description('Listar PDFs procesados con metadatos')
34
- .action(async () => {
34
+ .option('--domain <domain>', 'Filtrar por dominio (programming, reports, business, legal, product, technical-writing)')
35
+ .action(async (options) => {
35
36
  const { list } = await import('../commands/knowledge.js')
36
- await list()
37
+ await list(options)
37
38
  })
38
39
 
39
40
  knowledge
@@ -20,6 +20,7 @@ export function register(program) {
20
20
  .command('search <query>')
21
21
  .description('Buscar conceptos por nombre, tipo o tag')
22
22
  .option('--category <cat>', 'Filtrar por categoría (visual, ui, backend, product, ai)')
23
+ .option('--domain <domain>', 'Filtrar por dominio (programming, reports, business, etc.)')
23
24
  .action(async (query, options) => {
24
25
  const { search } = await import('../commands/learning.js')
25
26
  await search(query, options)
@@ -201,6 +201,7 @@ export function register(program) {
201
201
  .option('--description <desc>', 'Descripción del bug')
202
202
  .option('--learn-error <id>', 'ID de @learn-error relacionado')
203
203
  .option('--type <type>', 'Tipo: OPL (framework) o project (default)')
204
+ .option('--domain <domain>', 'Dominio (programming, reports, business, legal, product, technical-writing)')
204
205
  .action(async (options) => {
205
206
  const { ticketCreate } = await import('../commands/ticket.js')
206
207
  await ticketCreate(options)
@@ -212,6 +213,7 @@ export function register(program) {
212
213
  .option('--status <status>', 'Filtrar por estado: open, wip, fixed, wontfix')
213
214
  .option('--severity <severity>', 'Filtrar por severidad')
214
215
  .option('--project <name>', 'Filtrar por proyecto')
216
+ .option('--domain <domain>', 'Filtrar por dominio (programming, reports, business, etc.)')
215
217
  .action(async (options) => {
216
218
  const { ticketList } = await import('../commands/ticket.js')
217
219
  await ticketList(options)
@@ -241,6 +243,8 @@ export function register(program) {
241
243
  .description('Buscar en memoria del proyecto: aprendizajes, conceptos, errores, tickets')
242
244
  .option('--lang <lang>', 'Módulo de lenguaje (default: react)')
243
245
  .option('--dir <path>', 'Directorio del proyecto (default: cwd)')
246
+ .option('--domain <domain>', 'Filtrar por dominio (programming, reports, business, legal, product, technical-writing)')
247
+ .option('--all', 'Buscar en todos los dominios disponibles')
244
248
  .action(async (query, options) => {
245
249
  const { recall } = await import('../commands/recall.js')
246
250
  await recall(query, options)
@@ -12,6 +12,7 @@ import { searchConcepts } from '../utils/semantic-index.js'
12
12
  import { searchKnowledge } from '../utils/knowledge-search.js'
13
13
  import { searchTemplates as langSearchTemplates } from '../utils/language-loader.js'
14
14
  import { scanForLearnErrors } from '../utils/error-learner.js'
15
+ import { DOMAIN_KEYWORDS, getActiveDomains, getDomainKeywordsForFiltering, resolveCategoryForDomain } from '../utils/domains.js'
15
16
  import chalk from 'chalk'
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -61,15 +62,6 @@ const LANG_MAP = {
61
62
 
62
63
  const KIND_TYPES = new Set(['component', 'hook', 'service', 'page', 'store', 'util', 'type', 'layout', 'feature'])
63
64
 
64
- const DOMAIN_KEYWORDS = {
65
- ecommerce: ['product', 'cart', 'checkout', 'order', 'payment', 'shop', 'store', 'inventory', 'price', 'discount', 'customer', 'review', 'wishlist', 'shipping', 'invoice'],
66
- saas: ['subscription', 'tenant', 'workspace', 'organization', 'billing', 'plan', 'pricing', 'feature-flag', 'usage', 'quota', 'team', 'invite', 'role', 'permission'],
67
- mobile: ['push', 'notification', 'offline', 'sync', 'capacitor', 'camera', 'geolocation', 'biometric', 'deep-link', 'app-state', 'background'],
68
- api: ['endpoint', 'route', 'controller', 'middleware', 'schema', 'validator', 'resolver', 'api-key', 'rate-limit', 'webhook', 'swagger', 'openapi'],
69
- admin: ['dashboard', 'crud', 'datatable', 'filter', 'bulk', 'admin', 'management', 'report', 'analytics', 'chart', 'export', 'import'],
70
- blog: ['post', 'article', 'author', 'category', 'tag', 'comment', 'archive', 'search', 'rss', 'feed', 'content', 'editor']
71
- }
72
-
73
65
  function shouldIgnore (name, isDir) {
74
66
  if (isDir) return IGNORED_DIRS.has(name)
75
67
  if (IGNORED_FILES.has(name)) return true
@@ -112,7 +104,7 @@ function generateTree (dirPath, prefix = '', configIgnore = []) {
112
104
  function collectFiles (baseDir, options) {
113
105
  const files = []
114
106
  const domain = options.domain
115
- const domainKeywords = domain ? DOMAIN_KEYWORDS[domain] : null
107
+ const domainKeywords = domain ? getDomainKeywordsForFiltering(domain) : null
116
108
 
117
109
  const walkDir = (dirPath) => {
118
110
  let entries
@@ -415,7 +407,13 @@ export async function contextUnified (options) {
415
407
  const domain = options.domain
416
408
  const langId = options.lang || 'react'
417
409
 
418
- console.log(chalk.cyan(`\n🔍 Búsqueda unificada: "${query || domain || 'todo'}"\n`))
410
+ const resolvedDomain = domain || getActiveDomains(baseDir)[0] || 'shared'
411
+ const domainCategories = resolveCategoryForDomain(resolvedDomain)
412
+
413
+ console.log(chalk.cyan(`\n🔍 Búsqueda unificada: "${query || resolvedDomain || 'todo'}"`))
414
+ if (resolvedDomain && resolvedDomain !== 'shared') {
415
+ console.log(chalk.dim(` Dominio activo: ${resolvedDomain}`))
416
+ }
419
417
 
420
418
  const results = {
421
419
  knowledge: [],
@@ -423,14 +421,15 @@ export async function contextUnified (options) {
423
421
  semantic: [],
424
422
  templates: [],
425
423
  tickets: [],
426
- errors: []
424
+ errors: [],
425
+ patterns: []
427
426
  }
428
427
 
429
428
  // 1. Knowledge search
430
429
  if (query) {
431
430
  console.log(chalk.blue(' 📚 Buscando en knowledge...'))
432
431
  try {
433
- const kResults = searchKnowledge(query, { limit: 5 })
432
+ const kResults = searchKnowledge(query, { limit: 5, domain: resolvedDomain })
434
433
  results.knowledge = (kResults || []).map(r => ({
435
434
  title: r.title || r.bookTitle,
436
435
  bookTitle: r.bookTitle || r.bookId,
@@ -461,12 +460,13 @@ export async function contextUnified (options) {
461
460
  } catch {}
462
461
  }
463
462
 
464
- // 4. Learning concepts
463
+ // 4. Learning concepts (filtered by domain categories)
465
464
  const learningDir = join(baseDir, '.opencode', 'learning', 'concepts')
466
465
  if (existsSync(learningDir)) {
467
466
  try {
468
467
  for (const cat of readdirSync(learningDir)) {
469
- if (cat === 'knowledge') continue // skip auto-extracted
468
+ if (cat === 'knowledge') continue
469
+ if (domainCategories && !domainCategories.includes(cat)) continue
470
470
  const catDir = join(learningDir, cat)
471
471
  for (const entry of readdirSync(catDir)) {
472
472
  const conceptMd = join(catDir, entry, 'concept.md')
@@ -481,7 +481,7 @@ export async function contextUnified (options) {
481
481
  } catch {}
482
482
  }
483
483
 
484
- // 5. Tickets
484
+ // 5. Tickets (filtered by domain)
485
485
  const ticketDirs = [
486
486
  join(baseDir, '.opencode', 'bugs'),
487
487
  join(__dirname, '..', '..', 'BUGS-IDENTIFIED-IN-PROJECTS')
@@ -496,7 +496,10 @@ export async function contextUnified (options) {
496
496
  if (line.includes('| BUG-') && (!query || line.toLowerCase().includes(query.toLowerCase()))) {
497
497
  const cols = line.split('|').map(c => c.trim()).filter(Boolean)
498
498
  if (cols.length >= 4 && cols[3] !== 'fixed') {
499
- results.tickets.push({ id: cols[0], title: cols[1], severity: cols[2], status: cols[3] })
499
+ const ticketDomain = cols.length >= 7 ? cols[5] : null
500
+ if (!domain || !ticketDomain || ticketDomain === resolvedDomain) {
501
+ results.tickets.push({ id: cols[0], title: cols[1], severity: cols[2], status: cols[3] })
502
+ }
500
503
  }
501
504
  }
502
505
  }
@@ -515,9 +518,38 @@ export async function contextUnified (options) {
515
518
  } catch {}
516
519
  }
517
520
 
521
+ // 7. Patterns from active domains
522
+ const domainsDir = join(baseDir, '.openprompt', 'domains')
523
+ if (existsSync(domainsDir)) {
524
+ try {
525
+ const activeDomains = getActiveDomains(baseDir)
526
+ for (const d of activeDomains) {
527
+ const patternsDir = join(domainsDir, d, 'patterns')
528
+ if (!existsSync(patternsDir)) continue
529
+ for (const f of readdirSync(patternsDir)) {
530
+ if (!f.endsWith('.json')) continue
531
+ const patternName = f.replace('.json', '')
532
+ if (!query || patternName.toLowerCase().includes(query.toLowerCase())) {
533
+ try {
534
+ const patternContent = JSON.parse(readFileSync(join(patternsDir, f), 'utf-8'))
535
+ results.patterns.push({
536
+ name: patternName,
537
+ domain: d,
538
+ type: patternContent.type || 'unknown',
539
+ sections: patternContent.structure?.sections?.length || 0,
540
+ rules: patternContent.rules?.length || 0,
541
+ createdAt: patternContent.createdAt || ''
542
+ })
543
+ } catch { /* skip corrupt */ }
544
+ }
545
+ }
546
+ }
547
+ } catch {}
548
+ }
549
+
518
550
  // Output
519
551
  let totalResults = results.knowledge.length + results.learning.length + results.semantic.length +
520
- results.templates.length + results.tickets.length + results.errors.length
552
+ results.templates.length + results.tickets.length + results.errors.length + results.patterns.length
521
553
 
522
554
  if (totalResults === 0) {
523
555
  console.log(chalk.yellow(' 📭 Sin resultados para esta búsqueda.\n'))
@@ -571,6 +603,14 @@ export async function contextUnified (options) {
571
603
  }
572
604
  }
573
605
 
574
- console.log(chalk.green(`\n ✅ ${totalResults} resultados en 6 fuentes\n`))
606
+ if (results.patterns.length > 0) {
607
+ console.log(chalk.cyan(`\n📐 Patrones (${results.patterns.length}):\n`))
608
+ for (const p of results.patterns.slice(0, 5)) {
609
+ console.log(` ${chalk.bold(p.name)} [${p.domain}]`)
610
+ console.log(chalk.gray(` Tipo: ${p.type} | Secciones: ${p.sections} | Reglas: ${p.rules}`))
611
+ }
612
+ }
613
+
614
+ console.log(chalk.green(`\n ✅ ${totalResults} resultados en 7 fuentes\n`))
575
615
  return results
576
616
  }