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,456 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync, readFileSync } from "fs"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
import chalk from "chalk"
|
|
4
|
+
import inquirer from "inquirer"
|
|
5
|
+
import {
|
|
6
|
+
getLanguages,
|
|
7
|
+
getProjectTypes,
|
|
8
|
+
getUiStyles,
|
|
9
|
+
getExtensions,
|
|
10
|
+
getStructure,
|
|
11
|
+
getLanguageIndex,
|
|
12
|
+
getLanguagePath,
|
|
13
|
+
getTemplateErrors,
|
|
14
|
+
} from "../utils/language-loader.js"
|
|
15
|
+
|
|
16
|
+
const PROFILES = [
|
|
17
|
+
{ name: "Senior — max control, minimal boilerplate", value: "senior" },
|
|
18
|
+
{ name: "Mid — balance between speed and structure", value: "mid" },
|
|
19
|
+
{ name: "Junior — more guidance and restrictions", value: "junior" },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const PROJECT_REQUIREMENTS = {
|
|
23
|
+
api: {
|
|
24
|
+
questions: [
|
|
25
|
+
{
|
|
26
|
+
name: "auth",
|
|
27
|
+
message: "Authentication?",
|
|
28
|
+
choices: ["none", "JWT", "Supabase", "Auth.js", "Clerk"],
|
|
29
|
+
default: "none",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "database",
|
|
33
|
+
message: "Database?",
|
|
34
|
+
choices: ["none", "SQLite", "PostgreSQL", "MongoDB", "Supabase"],
|
|
35
|
+
default: "none",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "orm",
|
|
39
|
+
message: "ORM / Query builder?",
|
|
40
|
+
choices: ["none", "Drizzle", "Prisma", "TypeORM", "Knex"],
|
|
41
|
+
default: "none",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "validation",
|
|
45
|
+
message: "Validation library?",
|
|
46
|
+
choices: ["none", "Zod", "Yup", "Joi"],
|
|
47
|
+
default: "none",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "testing",
|
|
51
|
+
message: "Testing framework?",
|
|
52
|
+
choices: ["Vitest", "Jest", "none"],
|
|
53
|
+
default: "Vitest",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
web: {
|
|
58
|
+
questions: [
|
|
59
|
+
{
|
|
60
|
+
name: "routing",
|
|
61
|
+
message: "Routing strategy?",
|
|
62
|
+
choices: ["React Router", "TanStack Router", "File-based (Next.js)", "none"],
|
|
63
|
+
default: "React Router",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "data-fetching",
|
|
67
|
+
message: "Data fetching?",
|
|
68
|
+
choices: ["TanStack Query", "SWR", "RTK Query", "none"],
|
|
69
|
+
default: "TanStack Query",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "state-management",
|
|
73
|
+
message: "State management?",
|
|
74
|
+
choices: ["Zustand", "Redux Toolkit", "Jotai", "Context only", "none"],
|
|
75
|
+
default: "Zustand",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "styling",
|
|
79
|
+
message: "Styling approach?",
|
|
80
|
+
choices: ["Tailwind", "CSS Modules", "Styled Components", "Vanilla CSS"],
|
|
81
|
+
default: "Tailwind",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
mobile: {
|
|
86
|
+
questions: [
|
|
87
|
+
{
|
|
88
|
+
name: "framework",
|
|
89
|
+
message: "Mobile framework?",
|
|
90
|
+
choices: ["React Native", "Capacitor", "Tauri"],
|
|
91
|
+
default: "Capacitor",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "navigation",
|
|
95
|
+
message: "Navigation?",
|
|
96
|
+
choices: ["React Navigation", "File-based (Expo)", "none"],
|
|
97
|
+
default: "React Navigation",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
desktop: {
|
|
102
|
+
questions: [
|
|
103
|
+
{
|
|
104
|
+
name: "framework",
|
|
105
|
+
message: "Desktop framework?",
|
|
106
|
+
choices: ["Tauri", "Electron"],
|
|
107
|
+
default: "Tauri",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
library: {
|
|
112
|
+
questions: [
|
|
113
|
+
{
|
|
114
|
+
name: "bundler",
|
|
115
|
+
message: "Bundler?",
|
|
116
|
+
choices: ["tsup", "Rollup", "Vite library mode"],
|
|
117
|
+
default: "tsup",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "publish",
|
|
121
|
+
message: "Publish target?",
|
|
122
|
+
choices: ["npm", "GitHub Packages", "none"],
|
|
123
|
+
default: "none",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function wizard() {
|
|
130
|
+
console.log(chalk.cyan("\n🧙 openPrompt-Lang Project Wizard\n"))
|
|
131
|
+
console.log(chalk.gray("Answer the questions to generate the optimal configuration.\n"))
|
|
132
|
+
|
|
133
|
+
// Load languages dynamically from modules
|
|
134
|
+
const languages = getLanguages(true)
|
|
135
|
+
if (languages.length === 0) {
|
|
136
|
+
console.log(chalk.red("\n❌ No language modules found. Run: npx openPrompt-Lang lang list\n"))
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const langChoices = languages.map((l) => ({
|
|
141
|
+
name: `${l.name} v${l.version}`,
|
|
142
|
+
value: l.id,
|
|
143
|
+
}))
|
|
144
|
+
|
|
145
|
+
// 1. Language
|
|
146
|
+
const { langId } = await inquirer.prompt([
|
|
147
|
+
{
|
|
148
|
+
type: "list",
|
|
149
|
+
name: "langId",
|
|
150
|
+
message: "Which language/framework will you use?",
|
|
151
|
+
choices: langChoices,
|
|
152
|
+
},
|
|
153
|
+
])
|
|
154
|
+
|
|
155
|
+
// 2. Project type
|
|
156
|
+
const projectTypes = getProjectTypes(langId)
|
|
157
|
+
const typeChoices = projectTypes
|
|
158
|
+
? Object.entries(projectTypes).map(([key, val]) => ({
|
|
159
|
+
name: `${val.name} — ${val.description || ""}`,
|
|
160
|
+
value: key,
|
|
161
|
+
}))
|
|
162
|
+
: [{ name: "Default", value: "default" }]
|
|
163
|
+
|
|
164
|
+
const { projectType } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: "list",
|
|
167
|
+
name: "projectType",
|
|
168
|
+
message: "What type of project is this?",
|
|
169
|
+
choices: typeChoices,
|
|
170
|
+
},
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
// 2b. Requirements refinement
|
|
174
|
+
const refinement = await askRefinement(projectType)
|
|
175
|
+
|
|
176
|
+
// 3. UI Style (only if project type needs UI)
|
|
177
|
+
let uiStyle = "none"
|
|
178
|
+
const needsUI = projectTypes?.[projectType]?.ui !== false
|
|
179
|
+
if (needsUI) {
|
|
180
|
+
const styles = getUiStyles(langId)
|
|
181
|
+
const styleChoices = styles.map((s) => ({
|
|
182
|
+
name: s === "none" ? "None (no UI library)" : s,
|
|
183
|
+
value: s,
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
const resp = await inquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
type: "list",
|
|
189
|
+
name: "uiStyle",
|
|
190
|
+
message: "Which UI library?",
|
|
191
|
+
choices: styleChoices,
|
|
192
|
+
},
|
|
193
|
+
])
|
|
194
|
+
uiStyle = resp.uiStyle
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 4. Extensions available for this language
|
|
198
|
+
const availableExts = getExtensions(langId)
|
|
199
|
+
let extensions = []
|
|
200
|
+
if (availableExts.length > 0) {
|
|
201
|
+
const resp = await inquirer.prompt([
|
|
202
|
+
{
|
|
203
|
+
type: "checkbox",
|
|
204
|
+
name: "extensions",
|
|
205
|
+
message: "Which extensions do you need?",
|
|
206
|
+
choices: availableExts.map((e) => ({ name: e.name, value: e.value })),
|
|
207
|
+
},
|
|
208
|
+
])
|
|
209
|
+
extensions = resp.extensions
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 5. Profile
|
|
213
|
+
const { profile } = await inquirer.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: "list",
|
|
216
|
+
name: "profile",
|
|
217
|
+
message: "Developer profile?",
|
|
218
|
+
choices: PROFILES,
|
|
219
|
+
},
|
|
220
|
+
])
|
|
221
|
+
|
|
222
|
+
// 6. Project name
|
|
223
|
+
const { projectName } = await inquirer.prompt([
|
|
224
|
+
{
|
|
225
|
+
type: "input",
|
|
226
|
+
name: "projectName",
|
|
227
|
+
message: "Project name:",
|
|
228
|
+
default: "mi-proyecto",
|
|
229
|
+
validate: (input) => input.trim().length > 0 || "Name cannot be empty",
|
|
230
|
+
},
|
|
231
|
+
])
|
|
232
|
+
|
|
233
|
+
// Get structure info for the scaffold suggestion
|
|
234
|
+
const structure = getStructure(langId)
|
|
235
|
+
const index = getLanguageIndex(langId)
|
|
236
|
+
const templates = index?.templates || []
|
|
237
|
+
|
|
238
|
+
// Build config
|
|
239
|
+
const config = {
|
|
240
|
+
name: projectName,
|
|
241
|
+
version: "0.1.0",
|
|
242
|
+
lenguaje: langId,
|
|
243
|
+
projectType,
|
|
244
|
+
profile,
|
|
245
|
+
stack: {
|
|
246
|
+
base: structure?.folders
|
|
247
|
+
?.filter((f) => f.startsWith("src/") || f.startsWith("app/"))
|
|
248
|
+
.map((f) => f.split("/")[1])
|
|
249
|
+
.filter(Boolean) || ["react"],
|
|
250
|
+
extensions,
|
|
251
|
+
},
|
|
252
|
+
annotations: { enabled: true, strict: profile === "senior" },
|
|
253
|
+
ui: uiStyle !== "none" ? { style: uiStyle } : undefined,
|
|
254
|
+
docs: { commits: true, logs: true, backlog: true },
|
|
255
|
+
extractor: {
|
|
256
|
+
ignore: [".env", "*.local", "dist", "node_modules"],
|
|
257
|
+
output: "contexto.md",
|
|
258
|
+
},
|
|
259
|
+
...(Object.keys(refinement).length > 0 && { requirements: refinement }),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Quality gating
|
|
263
|
+
const gateWarnings = qualityGate(config, structure, templates)
|
|
264
|
+
|
|
265
|
+
// Summary
|
|
266
|
+
console.log(chalk.cyan("\n📋 Configuration summary:\n"))
|
|
267
|
+
console.log(` ${chalk.bold("Name:")} ${projectName}`)
|
|
268
|
+
console.log(` ${chalk.bold("Language:")} ${langId}`)
|
|
269
|
+
console.log(` ${chalk.bold("Type:")} ${projectType}`)
|
|
270
|
+
console.log(` ${chalk.bold("UI:")} ${uiStyle}`)
|
|
271
|
+
console.log(
|
|
272
|
+
` ${chalk.bold("Extensions:")} ${extensions.length > 0 ? extensions.join(", ") : "none"}`
|
|
273
|
+
)
|
|
274
|
+
console.log(` ${chalk.bold("Profile:")} ${profile}\n`)
|
|
275
|
+
|
|
276
|
+
for (const w of gateWarnings) {
|
|
277
|
+
console.log(chalk.yellow(` ⚠️ ${w}`))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (gateWarnings.length > 0) console.log("")
|
|
281
|
+
|
|
282
|
+
const { confirm } = await inquirer.prompt([
|
|
283
|
+
{
|
|
284
|
+
type: "confirm",
|
|
285
|
+
name: "confirm",
|
|
286
|
+
message: "Generate configuration?",
|
|
287
|
+
default: true,
|
|
288
|
+
},
|
|
289
|
+
])
|
|
290
|
+
|
|
291
|
+
if (!confirm) {
|
|
292
|
+
console.log(chalk.yellow("\n⏹️ Wizard cancelled.\n"))
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Write files
|
|
297
|
+
const targetDir = join(process.cwd(), projectName)
|
|
298
|
+
mkdirSync(targetDir, { recursive: true })
|
|
299
|
+
|
|
300
|
+
writeFileSync(join(targetDir, "prompt-lang.json"), JSON.stringify(config, null, 2), "utf-8")
|
|
301
|
+
console.log(chalk.green(` ✅ prompt-lang.json`))
|
|
302
|
+
|
|
303
|
+
const agentsContent = generateAgentsMd(config, structure)
|
|
304
|
+
writeFileSync(join(targetDir, "AGENTS.md"), agentsContent, "utf-8")
|
|
305
|
+
console.log(chalk.green(" ✅ AGENTS.md"))
|
|
306
|
+
|
|
307
|
+
// Post-wizard analyzer
|
|
308
|
+
postWizardReport(projectName, langId, config, templates)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function askRefinement(projectType) {
|
|
312
|
+
const specs = PROJECT_REQUIREMENTS[projectType]
|
|
313
|
+
if (!specs) return {}
|
|
314
|
+
|
|
315
|
+
return inquirer.prompt(
|
|
316
|
+
specs.questions.map((q) => ({
|
|
317
|
+
type: "list",
|
|
318
|
+
name: q.name,
|
|
319
|
+
message: q.message,
|
|
320
|
+
choices: q.choices,
|
|
321
|
+
default: q.default,
|
|
322
|
+
}))
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function qualityGate(config, structure, templates) {
|
|
327
|
+
const warnings = []
|
|
328
|
+
|
|
329
|
+
if (config.projectType === "api" && config.ui?.style && config.ui.style !== "none") {
|
|
330
|
+
warnings.push("UI library selected for an API project — you may not need it.")
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (config.profile === "senior" && config.projectType === "default") {
|
|
334
|
+
warnings.push(
|
|
335
|
+
"Senior profile with a default project type — consider specifying a type for better scaffolding."
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (config.projectType === "web" && !config.ui?.style) {
|
|
340
|
+
warnings.push(
|
|
341
|
+
"Web project without a UI library — consider adding one via component add --template."
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (config.projectType === "mobile" && config.ui?.style === "shadcn") {
|
|
346
|
+
warnings.push("shadcn/ui is not available for mobile projects.")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (config.requirements?.testing === "none" && config.profile !== "junior") {
|
|
350
|
+
warnings.push("No testing framework selected — consider adding one.")
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (config.requirements?.database !== "none" && config.requirements?.orm === "none") {
|
|
354
|
+
warnings.push(
|
|
355
|
+
`Database (${config.requirements.database}) selected without an ORM — consider Drizzle or Prisma.`
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (templates && templates.length === 0) {
|
|
360
|
+
warnings.push(
|
|
361
|
+
"No templates available for this language module — teach command will have limited content."
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return warnings
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function postWizardReport(projectName, langId, config, templates) {
|
|
369
|
+
console.log(chalk.cyan("\n📊 Post-wizard analysis:\n"))
|
|
370
|
+
|
|
371
|
+
// Suggested templates to explore
|
|
372
|
+
const suggestedTemplates = templates
|
|
373
|
+
? templates
|
|
374
|
+
.filter((t) => t.hasTeachMe)
|
|
375
|
+
.slice(0, 4)
|
|
376
|
+
.map((t) => ` • npx openPrompt-Lang teach ${t.id} --lang ${langId}`)
|
|
377
|
+
: []
|
|
378
|
+
|
|
379
|
+
console.log(chalk.bold(" 🎯 Commands to run next:\n"))
|
|
380
|
+
console.log(` cd ${projectName}`)
|
|
381
|
+
console.log(` npx openPrompt-Lang scaffold folders ${langId}`)
|
|
382
|
+
if (config.requirements?.testing && config.requirements.testing !== "none") {
|
|
383
|
+
console.log(` npx openPrompt-Lang qa-gen --lang ${langId} --output src/__tests__`)
|
|
384
|
+
}
|
|
385
|
+
console.log("")
|
|
386
|
+
|
|
387
|
+
if (suggestedTemplates.length > 0) {
|
|
388
|
+
console.log(chalk.bold(" 📚 Templates available with @teachMe:\n"))
|
|
389
|
+
for (const cmd of suggestedTemplates) console.log(cmd)
|
|
390
|
+
console.log("")
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Template coverage
|
|
394
|
+
if (templates) {
|
|
395
|
+
const practiceCounts = countPractices(langId, templates)
|
|
396
|
+
console.log(chalk.bold(" 📈 Module stats:\n"))
|
|
397
|
+
console.log(` Templates: ${templates.length}`)
|
|
398
|
+
console.log(` @teachMe: ${templates.filter((t) => t.hasTeachMe).length}`)
|
|
399
|
+
console.log(` @goodPractice: ${practiceCounts.good}`)
|
|
400
|
+
console.log(` @badPractice: ${practiceCounts.bad}`)
|
|
401
|
+
const errorCount = getTemplateErrors(langId).length
|
|
402
|
+
console.log(` Learned errors: ${errorCount}\n`)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.log(chalk.cyan(` ✅ Done. Happy coding!\n`))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function generateAgentsMd(config, structure) {
|
|
409
|
+
const uiNote =
|
|
410
|
+
config.ui?.style && config.ui.style !== "none" ? `- UI Library: ${config.ui.style}\n` : ""
|
|
411
|
+
|
|
412
|
+
const foldersNote = structure?.folders?.length
|
|
413
|
+
? `\n## Structure\n${structure.folders.map((f) => `- ${f}/`).join("\n")}\n`
|
|
414
|
+
: ""
|
|
415
|
+
|
|
416
|
+
return `# AGENTS.md — Context for AI
|
|
417
|
+
|
|
418
|
+
## Project: ${config.name}
|
|
419
|
+
## Stack
|
|
420
|
+
- Language: ${config.lenguaje}
|
|
421
|
+
- Profile: ${config.profile}
|
|
422
|
+
${uiNote}${config.stack.extensions.length > 0 ? config.stack.extensions.map((e) => `- ${e}`).join("\n") : ""}
|
|
423
|
+
|
|
424
|
+
## Conventions
|
|
425
|
+
- Framework: openPrompt-Lang (@kind, @contract, @limit annotations)
|
|
426
|
+
- NO any types
|
|
427
|
+
- NO exceed line limits without refactoring
|
|
428
|
+
- Annotations must declare @use() at file start
|
|
429
|
+
- Run validation before every commit
|
|
430
|
+
- Search templates: npx openPrompt-Lang search <query>
|
|
431
|
+
- Learn from templates: npx openPrompt-Lang teach <template-id>
|
|
432
|
+
|
|
433
|
+
## References
|
|
434
|
+
- PromptLang syntax: docs/SYNTAX.md
|
|
435
|
+
- Methodology: docs/referencia-metodologia/
|
|
436
|
+
- Templates: src/templates/langs/${config.lenguaje}/INDEX.json
|
|
437
|
+
${foldersNote}`
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function countPractices(langId, templates) {
|
|
441
|
+
const langPath = getLanguagePath(langId)
|
|
442
|
+
let good = 0
|
|
443
|
+
let bad = 0
|
|
444
|
+
|
|
445
|
+
for (const t of templates) {
|
|
446
|
+
if (!t.file || !langPath) continue
|
|
447
|
+
try {
|
|
448
|
+
const filePath = join(langPath, t.file)
|
|
449
|
+
const content = readFileSync(filePath, "utf-8")
|
|
450
|
+
if (content.includes("@goodPractice")) good++
|
|
451
|
+
if (content.includes("@badPractice")) bad++
|
|
452
|
+
} catch {}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return { good, bad }
|
|
456
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
export function generateFigmaPrompt(answers) {
|
|
2
|
-
const { projectType, screens, mobileFirst, style, includeAntiPatterns } = answers
|
|
3
|
-
const screenList = screens.split(",").map((s) => s.trim())
|
|
2
|
+
const { projectType, screens, mobileFirst, style, includeAntiPatterns } = answers
|
|
3
|
+
const screenList = screens.split(",").map((s) => s.trim())
|
|
4
4
|
|
|
5
5
|
const styleMap = {
|
|
6
|
-
"Minimalista corporativo":
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
6
|
+
"Minimalista corporativo":
|
|
7
|
+
"minimalista, profesional, colores neutros con acento azul oscuro, tipografía sans-serif limpia, espaciado generoso",
|
|
8
|
+
"Moderno vibrante":
|
|
9
|
+
"moderno, atrevido, gradientes sutiles, colores vibrantes (púrpura/cian), micro-interacciones, glassmorphism ligero",
|
|
10
|
+
"Oscuro (dark mode)":
|
|
11
|
+
"dark mode first, fondo gris oscuro (#0f0f0f), acentos en verde/azul neón, contrastes altos, sombras sutiles",
|
|
12
|
+
"Claro y limpio":
|
|
13
|
+
"claro, fondos blancos/gris claro, mucho espacio negativo, bordes suaves, iconos lineales, tipografía elegante",
|
|
14
|
+
Personalizado: style,
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
const prompt = `> [!example] Prompt generado por openPrompt-Lang
|
|
14
18
|
> \`\`\`markdown
|
|
@@ -34,12 +38,16 @@ export function generateFigmaPrompt(answers) {
|
|
|
34
38
|
> - Zonas táctiles: mínimo \`h-10 w-10\` en mobile
|
|
35
39
|
> - Estados: loading, empty, error, success para cada vista
|
|
36
40
|
>
|
|
37
|
-
> ${
|
|
41
|
+
> ${
|
|
42
|
+
includeAntiPatterns
|
|
43
|
+
? `## Anti-patrones (Prohibido)
|
|
38
44
|
> - ❌ Tamaños absolutos (h-[500px], w-[300px])
|
|
39
45
|
> - ❌ Posicionamiento absoluto para layout (usar flex/grid)
|
|
40
46
|
> - ❌ CSS personalizado o modules
|
|
41
47
|
> - ❌ Componentes de más de 120 líneas
|
|
42
|
-
> - ❌ Mezclar lógica de negocio en componentes de presentación`
|
|
48
|
+
> - ❌ Mezclar lógica de negocio en componentes de presentación`
|
|
49
|
+
: ""
|
|
50
|
+
}
|
|
43
51
|
>
|
|
44
52
|
> ## Lista de Pantallas
|
|
45
53
|
> ${screenList.map((s, i) => `${i + 1}. **${s}:** Describir componentes principales y flujo`).join("\n")}
|
|
@@ -50,7 +58,7 @@ export function generateFigmaPrompt(answers) {
|
|
|
50
58
|
> - Estados visuales (loading, empty, error) para cada componente crítico
|
|
51
59
|
> - Sugerencia de estructura de carpetas
|
|
52
60
|
> \`\`\`
|
|
53
|
-
|
|
61
|
+
`
|
|
54
62
|
|
|
55
|
-
return prompt
|
|
63
|
+
return prompt
|
|
56
64
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var builtinTags = [
|
|
2
|
+
"use",
|
|
3
|
+
"kind",
|
|
4
|
+
"contract",
|
|
5
|
+
"props",
|
|
6
|
+
"state",
|
|
7
|
+
"limit",
|
|
8
|
+
"forbidden",
|
|
9
|
+
"compose",
|
|
10
|
+
"deps",
|
|
11
|
+
"platform",
|
|
12
|
+
"scope",
|
|
13
|
+
"test",
|
|
14
|
+
"meta",
|
|
15
|
+
"pattern",
|
|
16
|
+
"learn-error",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
function makeTagPattern(tags) {
|
|
20
|
+
var escaped = tags.map(function (t) {
|
|
21
|
+
return t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
22
|
+
})
|
|
23
|
+
return new RegExp("^\\s*@(" + escaped.join("|") + ")\\(")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function create(typescript) {
|
|
27
|
+
var tagPattern = makeTagPattern(builtinTags)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
create: function (info) {
|
|
31
|
+
var proxy = Object.create(null)
|
|
32
|
+
var oldLS = info.languageService
|
|
33
|
+
|
|
34
|
+
function isAnnotationLine(line) {
|
|
35
|
+
return tagPattern.test(line.trim())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
proxy.getSemanticDiagnostics = function (fileName) {
|
|
39
|
+
var original = oldLS.getSemanticDiagnostics(fileName)
|
|
40
|
+
if (!original || !original.length) return original
|
|
41
|
+
|
|
42
|
+
var sourceFile = info.project.getSourceFile(fileName)
|
|
43
|
+
if (!sourceFile) return original
|
|
44
|
+
|
|
45
|
+
var text = sourceFile.text || ""
|
|
46
|
+
var lines = text.split("\n")
|
|
47
|
+
|
|
48
|
+
return original.filter(function (diag) {
|
|
49
|
+
if (diag.start === undefined) return true
|
|
50
|
+
var lineIndex = 0
|
|
51
|
+
var pos = 0
|
|
52
|
+
while (pos < diag.start && lineIndex < lines.length) {
|
|
53
|
+
pos += lines[lineIndex].length + 1
|
|
54
|
+
lineIndex++
|
|
55
|
+
}
|
|
56
|
+
return !isAnnotationLine(lines[lineIndex] || "")
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
proxy.getQuickInfoAtPosition = function (fileName, position) {
|
|
61
|
+
var sourceFile = info.project.getSourceFile(fileName)
|
|
62
|
+
if (!sourceFile) return undefined
|
|
63
|
+
|
|
64
|
+
var text = sourceFile.text || ""
|
|
65
|
+
var lines = text.split("\n")
|
|
66
|
+
var pos = 0
|
|
67
|
+
for (var i = 0; i < lines.length; i++) {
|
|
68
|
+
if (position >= pos && position < pos + lines[i].length + 1) {
|
|
69
|
+
var line = lines[i].trim()
|
|
70
|
+
var match = line.match(tagPattern)
|
|
71
|
+
if (match) {
|
|
72
|
+
return {
|
|
73
|
+
displayParts: [{ text: "promptlang annotation", kind: "keyword" }],
|
|
74
|
+
documentation: [
|
|
75
|
+
{
|
|
76
|
+
text: "@" + match[1] + " — annotation for AI-assisted development",
|
|
77
|
+
kind: "documentation",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
pos += lines[i].length + 1
|
|
85
|
+
}
|
|
86
|
+
return oldLS.getQuickInfoAtPosition(fileName, position)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return proxy
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { create }
|