openprompt-lang 0.3.0 → 0.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 (90) hide show
  1. package/README.md +483 -32
  2. package/bin/cli.js +232 -41
  3. package/bin/create.js +135 -0
  4. package/bin/lint.js +20 -21
  5. package/docs/COMMANDS.md +200 -127
  6. package/docs/COMMITS/INDEX.md +1 -0
  7. package/docs/PROMPT_AI_CONTEXT.md +99 -0
  8. package/docs/langs/dotnet.md +36 -0
  9. package/docs/langs/java-spring.md +45 -0
  10. package/docs/langs/python-fastapi.md +35 -0
  11. package/docs/langs/unity.md +30 -0
  12. package/docs/langs/vue-nuxt.md +36 -0
  13. package/package.json +31 -3
  14. package/scaffolds/.cursorrules +6 -0
  15. package/scaffolds/AGENTS.md +27 -0
  16. package/scaffolds/Dockerfile +11 -0
  17. package/scaffolds/capacitor.config.ts +17 -0
  18. package/scaffolds/netlify.toml +8 -0
  19. package/scaffolds/prompt-lang.json +15 -0
  20. package/scaffolds/railway.json +12 -0
  21. package/scaffolds/tailwind.config.js +8 -0
  22. package/scaffolds/tauri.conf.json +26 -0
  23. package/schemas/language-module.json +116 -0
  24. package/schemas/prompt-lang.json +38 -3
  25. package/schemas/structures.json +145 -0
  26. package/src/ai/prompt-builder.js +184 -0
  27. package/src/ai/providers.js +247 -0
  28. package/src/annotations/registry.js +39 -0
  29. package/src/annotations/tags.json +24 -0
  30. package/src/commands/ai-gen.js +161 -0
  31. package/src/commands/component.js +242 -212
  32. package/src/commands/context.js +184 -109
  33. package/src/commands/extract.js +242 -0
  34. package/src/commands/figma.js +15 -15
  35. package/src/commands/init.js +197 -93
  36. package/src/commands/integrate.js +406 -0
  37. package/src/commands/lang.js +148 -0
  38. package/src/commands/qa-gen.js +139 -0
  39. package/src/commands/scaffold.js +127 -0
  40. package/src/commands/suggest.js +24 -14
  41. package/src/commands/teach.js +110 -0
  42. package/src/commands/validate.js +143 -83
  43. package/src/commands/wizard.js +456 -0
  44. package/src/generators/figma-prompt.js +20 -12
  45. package/src/language-service/plugin.cjs +94 -0
  46. package/src/language-service/plugin.d.ts +6 -0
  47. package/src/mcp-server.js +605 -0
  48. package/src/templates/langs/react/INDEX.json +262 -0
  49. package/src/templates/langs/react/MODULE.json +166 -0
  50. package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
  51. package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
  52. package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
  53. package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
  54. package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
  55. package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
  56. package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
  57. package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
  58. package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
  59. package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
  60. package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
  61. package/src/templates/langs/vue/INDEX.json +246 -0
  62. package/src/templates/langs/vue/MODULE.json +105 -0
  63. package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
  64. package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
  65. package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
  66. package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
  67. package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
  68. package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
  69. package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
  70. package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
  71. package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
  72. package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
  73. package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
  74. package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
  75. package/src/templates/scripts/log-actividad.sh +32 -0
  76. package/src/templates/scripts/log-commit.sh +35 -0
  77. package/src/templates/scripts/log-error.sh +45 -0
  78. package/src/templates/scripts/validate.sh +23 -0
  79. package/src/ts-transformer/index.cjs +86 -0
  80. package/src/utils/ai.js +35 -53
  81. package/src/utils/annotations.js +260 -214
  82. package/src/utils/config.js +61 -13
  83. package/src/utils/error-learner.js +203 -0
  84. package/src/utils/file-utils.js +119 -0
  85. package/src/utils/language-loader.js +167 -0
  86. package/src/utils/template-utils.js +45 -0
  87. package/src/vite-plugin/index.js +54 -0
  88. package/vscode-extension/package.json +23 -2
  89. package/vscode-extension/snippets/promptlang.json +1 -3
  90. package/vscode-extension/syntaxes/annotations-code.tmGrammar.json +15 -0
@@ -0,0 +1,127 @@
1
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, copyFileSync } from "fs"
2
+ import { join, dirname } from "path"
3
+ import { fileURLToPath } from "url"
4
+ import chalk from "chalk"
5
+ import { getStructure, getLanguages } from "../utils/language-loader.js"
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+ const STRUCTURES_PATH = join(__dirname, "..", "..", "schemas", "structures.json")
9
+
10
+ function getStructureFromCatalog(framework) {
11
+ if (!existsSync(STRUCTURES_PATH)) return null
12
+ const catalog = JSON.parse(readFileSync(STRUCTURES_PATH, "utf-8"))
13
+ return catalog[framework] || null
14
+ }
15
+
16
+ function resolveStructure(framework) {
17
+ // Priority 1: Language module
18
+ const langStructure = getStructure(framework)
19
+ if (langStructure) return { source: "module", data: langStructure }
20
+
21
+ // Priority 2: structures.json catalog
22
+ const catalog = getStructureFromCatalog(framework)
23
+ if (catalog) return { source: "catalog", data: catalog }
24
+
25
+ return null
26
+ }
27
+
28
+ export async function scaffoldFolders(framework, options) {
29
+ const cwd = process.cwd()
30
+ const resolved = resolveStructure(framework)
31
+
32
+ if (!resolved) {
33
+ console.log(chalk.red(`\n❌ Framework "${framework}" not found`))
34
+ console.log(chalk.cyan(` Run: npx openPrompt-Lang scaffold list\n`))
35
+ return
36
+ }
37
+
38
+ const { data: structure } = resolved
39
+ const projectDir = options.name ? join(cwd, options.name) : cwd
40
+ const name = structure.name || framework
41
+
42
+ console.log(chalk.cyan(`\n🏗️ Scaffolding: ${name}\n`))
43
+
44
+ if (options.name) {
45
+ mkdirSync(projectDir, { recursive: true })
46
+ }
47
+
48
+ // Create folders
49
+ const folders = structure.folders || []
50
+ for (const dir of folders) {
51
+ const fullPath = join(projectDir, dir)
52
+ mkdirSync(fullPath, { recursive: true })
53
+ console.log(` 📁 ${dir}/`)
54
+ }
55
+
56
+ // Copy scaffolds
57
+ const scaffolds = structure.scaffolds || []
58
+ const scaffoldsDir = join(__dirname, "..", "..", "scaffolds")
59
+ for (const file of scaffolds) {
60
+ const srcFile = join(scaffoldsDir, file)
61
+ if (existsSync(srcFile)) {
62
+ const destFile = join(projectDir, file)
63
+ if (!existsSync(destFile) || options.force) {
64
+ const content = readFileSync(srcFile, "utf-8")
65
+ writeFileSync(destFile, content, "utf-8")
66
+ console.log(` ✅ ${file}`)
67
+ } else {
68
+ console.log(` ⚠️ ${file} already exists. Use --force to overwrite.`)
69
+ }
70
+ } else {
71
+ console.log(` ⚠️ Scaffold ${file} not found in scaffolds/.`)
72
+ }
73
+ }
74
+
75
+ console.log(chalk.green(`\n✅ Structure "${name}" created in ${projectDir}\n`))
76
+ return projectDir
77
+ }
78
+
79
+ export async function scaffoldList() {
80
+ // Collect from both sources
81
+ const seen = new Set()
82
+ const entries = []
83
+
84
+ // From language modules
85
+ const langs = getLanguages()
86
+ for (const lang of langs) {
87
+ const struct = lang.structure
88
+ if (struct) {
89
+ seen.add(lang.id)
90
+ entries.push({
91
+ id: lang.id,
92
+ name: lang.name,
93
+ folders: (struct.folders || []).length,
94
+ scaffolds: (struct.scaffolds || []).length,
95
+ source: "module",
96
+ })
97
+ }
98
+ }
99
+
100
+ // From structures.json (fallback)
101
+ if (existsSync(STRUCTURES_PATH)) {
102
+ const catalog = JSON.parse(readFileSync(STRUCTURES_PATH, "utf-8"))
103
+ for (const [key, val] of Object.entries(catalog)) {
104
+ if (!seen.has(key)) {
105
+ entries.push({
106
+ id: key,
107
+ name: val.name || key,
108
+ folders: (val.folders || []).length,
109
+ scaffolds: (val.scaffolds || []).length,
110
+ source: "catalog",
111
+ })
112
+ }
113
+ }
114
+ }
115
+
116
+ if (entries.length === 0) {
117
+ console.log(chalk.yellow("\n ⚠️ No structures found.\n"))
118
+ return
119
+ }
120
+
121
+ console.log(chalk.cyan("\n🏗️ Available frameworks:\n"))
122
+ for (const entry of entries) {
123
+ const badge = entry.source === "module" ? chalk.green(" [module]") : chalk.gray(" [catalog]")
124
+ console.log(` ${chalk.bold(entry.id)}${badge} — ${entry.name}`)
125
+ console.log(` 📁 ${entry.folders} folders | 📄 ${entry.scaffolds} scaffolds\n`)
126
+ }
127
+ }
@@ -1,31 +1,41 @@
1
- import chalk from "chalk";
2
- import { suggestStack } from "../utils/ai.js";
1
+ import chalk from "chalk"
2
+ import { suggestStack } from "../utils/ai.js"
3
3
 
4
4
  export async function suggest(description) {
5
- console.log(chalk.cyan(`\n🤖 Analizando: "${description}"\n`));
5
+ console.log(chalk.cyan(`\n🤖 Analizando: "${description}"\n`))
6
6
 
7
- const result = await suggestStack(description);
7
+ let result
8
+ try {
9
+ result = await suggestStack(description)
10
+ } catch (err) {
11
+ console.log(chalk.red(`\n❌ Error: ${err.message}\n`))
12
+ return
13
+ }
8
14
 
9
- if (result.source === "fallback" || !result.stack) {
10
- console.log(result.plan);
11
- console.log(chalk.yellow("\n📌 No se pudo conectar con IA. Usa el plan anterior con cualquier IA externa.\n"));
12
- return;
15
+ if (!result || result.source === "fallback" || !result.stack) {
16
+ console.log(result?.plan || "No se pudo generar un plan.")
17
+ console.log(
18
+ chalk.yellow(
19
+ "\n📌 No se pudo conectar con IA. Usa el plan anterior con cualquier IA externa.\n"
20
+ )
21
+ )
22
+ return
13
23
  }
14
24
 
15
- console.log(chalk.green("✅ Stack sugerido:"));
16
- console.log(` ${result.stack.join(", ") || "React + TypeScript + Vite + Tailwind"}`);
25
+ console.log(chalk.green("✅ Stack sugerido:"))
26
+ console.log(` ${result.stack.join(", ") || "React + TypeScript + Vite + Tailwind"}`)
17
27
 
18
28
  if (result.modules) {
19
- console.log(chalk.green("\n📦 Módulos sugeridos:"));
29
+ console.log(chalk.green("\n📦 Módulos sugeridos:"))
20
30
  for (const mod of result.modules) {
21
- console.log(` • ${mod}`);
31
+ console.log(` • ${mod}`)
22
32
  }
23
33
  }
24
34
 
25
35
  if (result.priorities) {
26
- console.log(chalk.green("\n🎯 Prioridades:"));
36
+ console.log(chalk.green("\n🎯 Prioridades:"))
27
37
  for (const p of result.priorities) {
28
- console.log(` ${p}`);
38
+ console.log(` ${p}`)
29
39
  }
30
40
  }
31
41
  }
@@ -0,0 +1,110 @@
1
+ import chalk from "chalk"
2
+ import { getTemplate, getLanguage, getLanguageIndex } from "../utils/language-loader.js"
3
+ import { extractLesson, extractTags } from "../utils/template-utils.js"
4
+
5
+ const TEACH_HEADER = `
6
+ ╔══════════════════════════════════════════════════╗
7
+ ║ 📖 openPrompt-Lang — Template Lesson ║
8
+ ╚══════════════════════════════════════════════════╝
9
+ `
10
+
11
+ export async function teach(templateId, options) {
12
+ const langId = options.lang || "react"
13
+
14
+ const lang = getLanguage(langId)
15
+ if (!lang) {
16
+ console.log(chalk.red(`\n❌ Language "${langId}" not found\n`))
17
+ return
18
+ }
19
+
20
+ const template = getTemplate(templateId, langId)
21
+ if (!template) {
22
+ console.log(chalk.red(`\n❌ Template "${templateId}" not found in ${lang.name}\n`))
23
+ console.log(chalk.gray(` Run: npx openPrompt-Lang lang index ${langId}\n`))
24
+ return
25
+ }
26
+
27
+ const meta = template.metadata
28
+ const content = template.content
29
+
30
+ if (!meta.hasTeachMe && !options.force) {
31
+ console.log(chalk.yellow(`\n ⚠️ Template "${templateId}" has no @teachMe annotation.`))
32
+ console.log(chalk.gray(" Use --force to show raw template anyway.\n"))
33
+ return
34
+ }
35
+
36
+ console.log(TEACH_HEADER)
37
+ console.log(chalk.cyan.bold(`\n 📦 ${meta.name}`))
38
+ console.log(chalk.gray(` ${meta.description}\n`))
39
+
40
+ // Extract teaching content from the @teachMe section
41
+ const lesson = extractLesson(content)
42
+
43
+ if (lesson) {
44
+ console.log(chalk.yellow.bold(" ─── LESSON ───\n"))
45
+ console.log(` ${lesson.replace(/\n/g, "\n ")}`)
46
+ console.log("")
47
+ }
48
+
49
+ // Show metadata
50
+ console.log(chalk.yellow.bold(" ─── TEMPLATE INFO ───\n"))
51
+ console.log(` ${chalk.bold("Category:")} ${meta.category}`)
52
+ console.log(` ${chalk.bold("Purposes:")} ${meta.purposes?.join(", ") || "general"}`)
53
+ if (meta.variants?.length)
54
+ console.log(` ${chalk.bold("Variants:")} ${meta.variants.join(", ")}`)
55
+ if (meta.sizes?.length) console.log(` ${chalk.bold("Sizes:")} ${meta.sizes.join(", ")}`)
56
+ console.log(
57
+ ` ${chalk.bold("Dark mode:")} ${meta.darkMode ? "✅ Supported" : "❌ Not supported"}`
58
+ )
59
+ console.log(` ${chalk.bold("Responsive:")} ${meta.responsive ? "✅ Yes" : "❌ No"}`)
60
+ console.log(` ${chalk.bold("Deps:")} ${meta.dependencies?.join(", ") || "none"}`)
61
+ console.log("")
62
+
63
+ // Show fix history
64
+ if (meta.fixHistory?.length > 0) {
65
+ console.log(chalk.yellow.bold(" ─── FIX HISTORY (learned errors) ───\n"))
66
+ const index = getLanguageIndex(langId)
67
+ const errors = index?.errorsLearned?.filter((e) => meta.fixHistory.includes(e.id)) || []
68
+ for (const err of errors) {
69
+ console.log(` ${chalk.red("⚠️ " + err.id)}`)
70
+ console.log(` ${chalk.bold("Problem:")} ${err.problem}`)
71
+ console.log(` ${chalk.bold("Fix:")} ${err.solution}`)
72
+ console.log(` ${chalk.gray("Category:")} ${err.category}`)
73
+ console.log("")
74
+ }
75
+ }
76
+
77
+ // Show good/bad practices from the template
78
+ const goodPractices = extractTags(content, "goodPractice")
79
+ const badPractices = extractTags(content, "badPractice")
80
+
81
+ if (goodPractices.length > 0) {
82
+ console.log(chalk.green.bold(" ─── GOOD PRACTICES ───\n"))
83
+ for (const gp of goodPractices) {
84
+ console.log(` ✅ ${gp}`)
85
+ }
86
+ console.log("")
87
+ }
88
+
89
+ if (badPractices.length > 0) {
90
+ console.log(chalk.red.bold(" ─── BAD PRACTICES TO AVOID ───\n"))
91
+ for (const bp of badPractices) {
92
+ console.log(` ❌ ${bp}`)
93
+ }
94
+ console.log("")
95
+ }
96
+
97
+ // Show code (optionally)
98
+ if (options.code) {
99
+ console.log(chalk.yellow.bold(" ─── CODE ───\n"))
100
+ const cleanCode = content
101
+ .split("\n")
102
+ .filter((l) => !l.trim().startsWith("// @") && !l.trim().startsWith("// ##"))
103
+ .join("\n")
104
+ console.log(cleanCode)
105
+ console.log("")
106
+ }
107
+
108
+ console.log(chalk.cyan(" ─────────────────────────────────────────\n"))
109
+ console.log(chalk.gray(" Tip: Use --code to show the full implementation\n"))
110
+ }
@@ -1,183 +1,243 @@
1
- import { readFileSync, existsSync, readdirSync, statSync } from "fs";
2
- import { join, extname } from "path";
3
- import { execSync } from "child_process";
4
- import chalk from "chalk";
5
- import { loadConfig, detectStack } from "../utils/config.js";
6
- import { lintFile } from "../utils/annotations.js";
7
-
8
- const CODE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
1
+ // @use(kind, contract, limit)
2
+ // @kind(command)
3
+ // @contract(in: options -> out: void, sideEffect: process.exitCode)
4
+ // @limit(lines: 220)
5
+ // @teachMe
6
+ // ## ¿Qué hace validate.js?
7
+ // Pipeline de 5 pasos: estructura → config → anotaciones → pipeline scripts → resumen.
8
+ // Es el comando principal de verificación del framework.
9
+ // ## ¿Por qué 5 pasos y no solo lint?
10
+ // Porque un proyecto openPrompt-Lang necesita más que anotaciones válidas:
11
+ // necesita estructura de directorios, configuración coherente,
12
+ // y un pipeline de CI que se pueda ejecutar localmente.
13
+ // ## ¿Cómo funciona --fix?
14
+ // --fix solo corrige problemas estructurales (crea directorios, genera
15
+ // prompt-lang.json y AGENTS.md). NO modifica código fuente existente.
16
+ // ## Nota sobre process.exitCode
17
+ // Ya no usa process.exit(1) — establece process.exitCode = 1 para que
18
+ // el proceso termine con código de error sin matar el test runner.
19
+
20
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from "fs"
21
+ import { join, extname, relative } from "path"
22
+ import { execSync } from "child_process"
23
+ import chalk from "chalk"
24
+ import { loadConfig, getDefaultConfig } from "../utils/config.js"
25
+ import { lintFile } from "../utils/annotations.js"
26
+
27
+ const CODE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"])
9
28
 
10
29
  export async function validate(options) {
11
- const baseDir = process.cwd();
12
- const config = loadConfig(baseDir);
13
- let hasErrors = false;
30
+ const baseDir = process.cwd()
31
+ const config = loadConfig(baseDir)
32
+ const fix = options.fix || false
33
+ let hasErrors = false
34
+ let fixCount = 0
14
35
 
15
- console.log(chalk.cyan("\n🔍 openPrompt-Lang: Validación\n"));
36
+ console.log(chalk.cyan("\n🔍 openPrompt-Lang: Validación\n"))
16
37
 
17
38
  // 1. Validar estructura
18
- console.log(chalk.yellow("[1/5] Verificando estructura..."));
19
- const requiredDirs = ["src", "docs"];
39
+ console.log(chalk.yellow("[1/5] Verificando estructura..."))
40
+ const requiredDirs = ["src", "docs"]
20
41
  for (const dir of requiredDirs) {
21
42
  if (!existsSync(join(baseDir, dir))) {
22
- console.log(chalk.red(` ❌ Falta carpeta: ${dir}/`));
23
- hasErrors = true;
43
+ if (fix) {
44
+ mkdirSync(join(baseDir, dir), { recursive: true })
45
+ console.log(chalk.green(` ✅ ${dir}/ (creado por --fix)`))
46
+ fixCount++
47
+ } else {
48
+ console.log(chalk.red(` ❌ Falta carpeta: ${dir}/`))
49
+ hasErrors = true
50
+ }
24
51
  } else {
25
- console.log(chalk.green(` ✅ ${dir}/`));
52
+ console.log(chalk.green(` ✅ ${dir}/`))
26
53
  }
27
54
  }
28
55
 
29
56
  // 2. Validar config
30
- console.log(chalk.yellow("\n[2/5] Verificando configuración..."));
57
+ console.log(chalk.yellow("\n[2/5] Verificando configuración..."))
31
58
  if (existsSync(join(baseDir, "prompt-lang.json"))) {
32
- console.log(chalk.green(" ✅ prompt-lang.json"));
59
+ console.log(chalk.green(" ✅ prompt-lang.json"))
60
+ } else if (fix) {
61
+ const defaultConfig = { ...getDefaultConfig(), name: baseDir.split("/").pop() || "mi-proyecto" }
62
+ writeFileSync(
63
+ join(baseDir, "prompt-lang.json"),
64
+ JSON.stringify(defaultConfig, null, 2),
65
+ "utf-8"
66
+ )
67
+ console.log(chalk.green(" ✅ prompt-lang.json (generado por --fix)"))
68
+ fixCount++
33
69
  } else {
34
- console.log(chalk.yellow(" ⚠️ No hay prompt-lang.json (usando defaults)"));
70
+ console.log(chalk.yellow(" ⚠️ No hay prompt-lang.json (usando defaults)"))
35
71
  }
36
72
 
37
73
  if (existsSync(join(baseDir, "AGENTS.md"))) {
38
- console.log(chalk.green(" ✅ AGENTS.md"));
74
+ console.log(chalk.green(" ✅ AGENTS.md"))
75
+ } else if (fix) {
76
+ const agentsContent = `# AGENTS.md — Contexto para IA
77
+
78
+ ## Stack
79
+ - ${(config.stack?.base || ["react", "typescript", "vite"]).join("\n- ")}
80
+
81
+ ## Convenciones
82
+ - Framework: openPrompt-Lang (@kind, @contract, @limit annotations)
83
+ - NO any types
84
+ - NO exceed line limits without refactoring
85
+ - Annotations must declare @use() at file start
86
+ - Run validation before every commit
87
+ `
88
+ writeFileSync(join(baseDir, "AGENTS.md"), agentsContent, "utf-8")
89
+ console.log(chalk.green(" ✅ AGENTS.md (generado por --fix)"))
90
+ fixCount++
39
91
  } else {
40
- console.log(chalk.yellow(" ⚠️ No hay AGENTS.md (recomendado)"));
92
+ console.log(chalk.yellow(" ⚠️ No hay AGENTS.md (recomendado)"))
41
93
  }
42
94
 
43
95
  // 2b. Validar coherencia stack
44
- console.log(chalk.yellow("\n[2b/5] Validando coherencia stack..."));
45
- const stack = config.stack || {};
46
- const base = (stack.base || []).map(s => s.toLowerCase());
47
- const extensions = (stack.extensions || []).map(s => s.toLowerCase());
96
+ console.log(chalk.yellow("\n[2b/5] Validando coherencia stack..."))
97
+ const stack = config.stack || {}
98
+ const base = (stack.base || []).map((s) => s.toLowerCase())
99
+ const extensions = (stack.extensions || []).map((s) => s.toLowerCase())
48
100
 
49
101
  if (base.includes("tauri") && !extensions.includes("tauri")) {
50
- console.log(chalk.red(" ❌ stack.base incluye 'tauri' pero stack.extensions no lo declara"));
51
- hasErrors = true;
102
+ console.log(chalk.red(" ❌ stack.base incluye 'tauri' pero stack.extensions no lo declara"))
103
+ hasErrors = true
52
104
  } else {
53
- console.log(chalk.green(" ✅ stack.base / extensions coherente"));
105
+ console.log(chalk.green(" ✅ stack.base / extensions coherente"))
54
106
  }
55
107
 
56
108
  // 3. Validar anotaciones PromptLang en archivos
57
- console.log(chalk.yellow("\n[3/5] Analizando anotaciones PromptLang..."));
58
- let annotatedFiles = 0;
59
- let annotationErrors = 0;
60
- let annotationWarnings = 0;
61
- let platformUsages = new Set();
109
+ console.log(chalk.yellow("\n[3/5] Analizando anotaciones PromptLang..."))
110
+ let annotatedFiles = 0
111
+ let annotationErrors = 0
112
+ let annotationWarnings = 0
113
+ let platformUsages = new Set()
62
114
 
63
115
  const walkDir = (dirPath) => {
64
- let entries;
116
+ let entries
65
117
  try {
66
- entries = readdirSync(dirPath);
118
+ entries = readdirSync(dirPath)
67
119
  } catch {
68
- return;
120
+ return
69
121
  }
70
122
  for (const entry of entries) {
71
- const fullPath = join(dirPath, entry);
123
+ const fullPath = join(dirPath, entry)
72
124
  try {
73
- const stat = statSync(fullPath);
125
+ const stat = statSync(fullPath)
74
126
  if (stat.isDirectory()) {
75
127
  if (!entry.startsWith(".") && entry !== "node_modules" && entry !== "dist") {
76
- walkDir(fullPath);
128
+ walkDir(fullPath)
77
129
  }
78
- continue;
130
+ continue
79
131
  }
80
132
  } catch {
81
- continue;
133
+ continue
82
134
  }
83
135
 
84
- const ext = extname(entry).toLowerCase();
85
- if (!CODE_EXTENSIONS.has(ext)) continue;
136
+ const ext = extname(entry).toLowerCase()
137
+ if (!CODE_EXTENSIONS.has(ext)) continue
86
138
 
87
139
  try {
88
- const content = readFileSync(fullPath, "utf-8");
89
- if (!content.includes("@use") && !content.includes("@kind")) continue;
140
+ const content = readFileSync(fullPath, "utf-8")
141
+ if (!content.includes("@use") && !content.includes("@kind")) continue
90
142
 
91
- annotatedFiles++;
92
- const { annotations, errors, warnings } = lintFile(content, config.annotations.strict);
143
+ annotatedFiles++
144
+ const { annotations, errors, warnings } = lintFile(content, config.annotations?.strict)
93
145
 
94
146
  // Colectar @platform usages para validación de stack
95
147
  for (const ann of annotations) {
96
- if (ann.tag === "platform" && ann.args) {
97
- ann.args.split(",").forEach(p => platformUsages.add(p.trim()));
148
+ if (ann.name === "platform") {
149
+ for (const arg of ann.args) {
150
+ if (arg.value) platformUsages.add(arg.value.trim())
151
+ }
98
152
  }
99
153
  }
100
154
 
101
155
  if (errors.length > 0 || warnings.length > 0) {
102
- const relPath = entry;
156
+ const relPath = relative(baseDir, fullPath)
103
157
  for (const err of errors) {
104
- annotationErrors++;
105
- console.log(chalk.red(` ❌ ${relPath}: ${err}`));
158
+ annotationErrors++
159
+ console.log(chalk.red(` ❌ ${relPath}: ${err}`))
106
160
  }
107
161
  for (const warn of warnings) {
108
- annotationWarnings++;
109
- console.log(chalk.yellow(` ⚠️ ${relPath}: ${warn}`));
162
+ annotationWarnings++
163
+ console.log(chalk.yellow(` ⚠️ ${relPath}: ${warn}`))
110
164
  }
111
165
  }
112
166
  } catch {
113
167
  // skip
114
168
  }
115
169
  }
116
- };
170
+ }
117
171
 
118
- walkDir(join(baseDir, "src"));
172
+ walkDir(join(baseDir, "src"))
119
173
 
120
174
  if (annotatedFiles === 0) {
121
- console.log(chalk.cyan(" 📭 No se encontraron archivos con anotaciones PromptLang"));
175
+ console.log(chalk.cyan(" 📭 No se encontraron archivos con anotaciones PromptLang"))
122
176
  } else {
123
- console.log(chalk.green(` 📄 ${annotatedFiles} archivos con anotaciones`));
177
+ console.log(chalk.green(` 📄 ${annotatedFiles} archivos con anotaciones`))
124
178
  if (annotationErrors > 0) {
125
- hasErrors = true;
126
- console.log(chalk.red(` ❌ ${annotationErrors} errores`));
179
+ hasErrors = true
180
+ console.log(chalk.red(` ❌ ${annotationErrors} errores`))
127
181
  }
128
182
  if (annotationWarnings > 0) {
129
- console.log(chalk.yellow(` ⚠️ ${annotationWarnings} advertencias`));
183
+ console.log(chalk.yellow(` ⚠️ ${annotationWarnings} advertencias`))
130
184
  }
131
185
  if (annotationErrors === 0 && annotationWarnings === 0) {
132
- console.log(chalk.green(` ✅ Todas las anotaciones válidas`));
186
+ console.log(chalk.green(` ✅ Todas las anotaciones válidas`))
133
187
  }
134
188
  }
135
189
 
136
190
  // Validación cruzada: @platform vs stack
137
191
  if (platformUsages.size > 0) {
138
192
  for (const plat of platformUsages) {
139
- if (plat === "mobile" && !extensions.some(e => ["ionic", "capacitor"].includes(e))) {
140
- console.log(chalk.yellow(` ⚠️ @platform(mobile) usado pero stack no incluye ionic/capacitor`));
193
+ if (plat === "mobile" && !extensions.some((e) => ["ionic", "capacitor"].includes(e))) {
194
+ console.log(
195
+ chalk.yellow(` ⚠️ @platform(mobile) usado pero stack no incluye ionic/capacitor`)
196
+ )
141
197
  }
142
198
  if (plat === "desktop" && !extensions.includes("tauri")) {
143
- console.log(chalk.yellow(` ⚠️ @platform(desktop) usado pero stack no incluye tauri`));
199
+ console.log(chalk.yellow(` ⚠️ @platform(desktop) usado pero stack no incluye tauri`))
144
200
  }
145
201
  }
146
202
  }
147
203
 
148
204
  // 4. Ejecutar pipeline configurado
149
- console.log(chalk.yellow("\n[4/5] Ejecutando pipeline..."));
150
- const scripts = config.pipeline?.scripts || {};
151
- const pipelineSteps = config.pipeline?.["pre-commit"] || [];
205
+ console.log(chalk.yellow("\n[4/5] Ejecutando pipeline..."))
206
+ const scripts = config.pipeline?.scripts || {}
207
+ const pipelineSteps = config.pipeline?.["pre-commit"] || []
152
208
 
153
209
  if (pipelineSteps.length === 0) {
154
- console.log(chalk.cyan(" 📭 No hay pipeline configurado"));
210
+ console.log(chalk.cyan(" 📭 No hay pipeline configurado"))
155
211
  } else {
156
212
  for (const step of pipelineSteps) {
157
- const scriptCmd = scripts[step];
213
+ const scriptCmd = scripts[step]
158
214
  if (!scriptCmd) {
159
- console.log(chalk.yellow(` ⚠️ Paso "${step}" sin script configurado`));
160
- continue;
215
+ console.log(chalk.yellow(` ⚠️ Paso "${step}" sin script configurado`))
216
+ continue
161
217
  }
162
218
  try {
163
- process.stdout.write(` ▶️ ${step}... `);
164
- execSync(scriptCmd, { cwd: baseDir, stdio: "pipe" });
165
- console.log(chalk.green("✅"));
219
+ process.stdout.write(` ▶️ ${step}... `)
220
+ execSync(scriptCmd, { cwd: baseDir, stdio: "pipe" })
221
+ console.log(chalk.green("✅"))
166
222
  } catch (err) {
167
- console.log(chalk.red("❌"));
168
- hasErrors = true;
169
- console.log(chalk.red(` ${err.stderr?.toString().split("\n")[0] || "Error"}`));
223
+ console.log(chalk.red("❌"))
224
+ hasErrors = true
225
+ console.log(chalk.red(` ${err.stderr?.toString().split("\n")[0] || "Error"}`))
170
226
  }
171
227
  }
172
228
  }
173
229
 
174
230
  // 5. Resumen
175
- console.log(chalk.yellow("\n[5/5] Resumen\n"));
231
+ console.log(chalk.yellow("\n[5/5] Resumen\n"))
232
+
233
+ if (fixCount > 0) {
234
+ console.log(chalk.green(` 🔧 ${fixCount} problema(s) corregido(s) por --fix\n`))
235
+ }
176
236
 
177
237
  if (hasErrors) {
178
- console.log(chalk.red("❌ Validación falló — revisa los errores arriba\n"));
179
- process.exit(1);
238
+ console.log(chalk.red("❌ Validación falló — revisa los errores arriba\n"))
239
+ process.exitCode = 1
180
240
  } else {
181
- console.log(chalk.green("✅ Validación completa — todo correcto\n"));
241
+ console.log(chalk.green("✅ Validación completa — todo correcto\n"))
182
242
  }
183
243
  }