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.
- package/README.md +483 -32
- package/bin/cli.js +232 -41
- package/bin/create.js +135 -0
- package/bin/lint.js +20 -21
- package/docs/COMMANDS.md +200 -127
- package/docs/COMMITS/INDEX.md +1 -0
- package/docs/PROMPT_AI_CONTEXT.md +99 -0
- package/docs/langs/dotnet.md +36 -0
- package/docs/langs/java-spring.md +45 -0
- package/docs/langs/python-fastapi.md +35 -0
- package/docs/langs/unity.md +30 -0
- package/docs/langs/vue-nuxt.md +36 -0
- package/package.json +31 -3
- package/scaffolds/.cursorrules +6 -0
- package/scaffolds/AGENTS.md +27 -0
- package/scaffolds/Dockerfile +11 -0
- package/scaffolds/capacitor.config.ts +17 -0
- package/scaffolds/netlify.toml +8 -0
- package/scaffolds/prompt-lang.json +15 -0
- package/scaffolds/railway.json +12 -0
- package/scaffolds/tailwind.config.js +8 -0
- package/scaffolds/tauri.conf.json +26 -0
- package/schemas/language-module.json +116 -0
- package/schemas/prompt-lang.json +38 -3
- package/schemas/structures.json +145 -0
- package/src/ai/prompt-builder.js +184 -0
- package/src/ai/providers.js +247 -0
- package/src/annotations/registry.js +39 -0
- package/src/annotations/tags.json +24 -0
- package/src/commands/ai-gen.js +161 -0
- package/src/commands/component.js +242 -212
- package/src/commands/context.js +184 -109
- package/src/commands/extract.js +242 -0
- package/src/commands/figma.js +15 -15
- package/src/commands/init.js +197 -93
- package/src/commands/integrate.js +406 -0
- package/src/commands/lang.js +148 -0
- package/src/commands/qa-gen.js +139 -0
- package/src/commands/scaffold.js +127 -0
- package/src/commands/suggest.js +24 -14
- package/src/commands/teach.js +110 -0
- package/src/commands/validate.js +143 -83
- package/src/commands/wizard.js +456 -0
- package/src/generators/figma-prompt.js +20 -12
- package/src/language-service/plugin.cjs +94 -0
- package/src/language-service/plugin.d.ts +6 -0
- package/src/mcp-server.js +605 -0
- package/src/templates/langs/react/INDEX.json +262 -0
- package/src/templates/langs/react/MODULE.json +166 -0
- package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
- package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
- package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
- package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
- package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
- package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
- package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
- package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
- package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
- package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
- package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
- package/src/templates/langs/vue/INDEX.json +246 -0
- package/src/templates/langs/vue/MODULE.json +105 -0
- package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
- package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
- package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
- package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
- package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
- package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
- package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
- package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
- package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
- package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
- package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
- package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
- package/src/templates/scripts/log-actividad.sh +32 -0
- package/src/templates/scripts/log-commit.sh +35 -0
- package/src/templates/scripts/log-error.sh +45 -0
- package/src/templates/scripts/validate.sh +23 -0
- package/src/ts-transformer/index.cjs +86 -0
- package/src/utils/ai.js +35 -53
- package/src/utils/annotations.js +260 -214
- package/src/utils/config.js +61 -13
- package/src/utils/error-learner.js +203 -0
- package/src/utils/file-utils.js +119 -0
- package/src/utils/language-loader.js +167 -0
- package/src/utils/template-utils.js +45 -0
- package/src/vite-plugin/index.js +54 -0
- package/vscode-extension/package.json +23 -2
- package/vscode-extension/snippets/promptlang.json +1 -3
- 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
|
+
}
|
package/src/commands/suggest.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
11
|
-
console.log(
|
|
12
|
-
|
|
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
|
+
}
|
package/src/commands/validate.js
CHANGED
|
@@ -1,183 +1,243 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
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.
|
|
97
|
-
ann.args
|
|
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 =
|
|
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(
|
|
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.
|
|
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
|
}
|