openprompt-lang 0.3.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +663 -0
  3. package/bin/cli.js +110 -0
  4. package/bin/lint.js +50 -0
  5. package/docs/COMMANDS.md +229 -0
  6. package/docs/COMMITS/INDEX.md +11 -0
  7. package/docs/COMMITS/v0.1.0-existing.md +31 -0
  8. package/docs/COMMITS/v0.1.0-inicial.md +50 -0
  9. package/docs/COMMITS/v0.1.0-readme.md +24 -0
  10. package/docs/COMMITS/v0.2.0-strict-db-templates.md +50 -0
  11. package/docs/COMMITS/v0.3.0-parser-fixes-vscode.md +67 -0
  12. package/docs/COMMITS/v0.3.0-versioning-component.md +44 -0
  13. package/docs/DEPENDENCIES.md +45 -0
  14. package/docs/FRAMEWORK.md +1741 -0
  15. package/docs/SYNTAX.md +359 -0
  16. package/docs/VERSIONING.md +150 -0
  17. package/docs/referencia-metodologia/Anexos Finales Documentos de Respaldo y Estandarizaci/303/263n.md" +90 -0
  18. package/docs/referencia-metodologia/Cotizaciones.md +84 -0
  19. package/docs/referencia-metodologia/Example.md +1 -0
  20. package/docs/referencia-metodologia/ExtractorInformacion.py +78 -0
  21. package/docs/referencia-metodologia/Fase - 1 .- Desarrollo de la Metodolog/303/255a.md" +67 -0
  22. package/docs/referencia-metodologia/Fase - 2 .- Levantamiento de requisitos generales y traduccion a la IA.md +64 -0
  23. package/docs/referencia-metodologia/Fase - 3 .- Prototipado visual con IA (Figma Maker o equivalentes).md +64 -0
  24. package/docs/referencia-metodologia/Fase - 4 .- Especificacion de requisitos e iteracion con el cliente.md +58 -0
  25. package/docs/referencia-metodologia/Fase - 5 .- Estructuracion y maquetado de funciones (Scaffolding).md +118 -0
  26. package/docs/referencia-metodologia/Fase - 6 .- Estructuracion del backlog y division de tareas.md +48 -0
  27. package/docs/referencia-metodologia/Fase - 7 .- Desarrollo activo, pruebas y control de versiones.md +98 -0
  28. package/docs/referencia-metodologia/Fase - 8 .- Entrega, capacitaci/303/263n y mantenimiento.md" +55 -0
  29. package/docs/referencia-metodologia/Figma prompt template.md +130 -0
  30. package/docs/referencia-metodologia/Framework de Desarrollo Asistido por IA.md +1741 -0
  31. package/docs/referencia-metodologia/Indice General.md +83 -0
  32. package/docs/referencia-metodologia/Prompt refactorizar o creacion desde cero.md +50 -0
  33. package/docs/referencia-metodologia/docs/CONVENCIONES_DB.md +410 -0
  34. package/docs/referencia-metodologia/docs/CONVENCIONES_DOCUMENTACION.md +209 -0
  35. package/docs/referencia-metodologia/docs/PROMPTS/INDEX.md +73 -0
  36. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/01-hook-supabase.md +79 -0
  37. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/02-componente-ui.md +82 -0
  38. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/03-pagina-feature.md +70 -0
  39. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/04-comando-tauri.md +56 -0
  40. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/05-store-zustand.md +74 -0
  41. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/06-servicio-supabase.md +74 -0
  42. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/07-formulario-validacion.md +63 -0
  43. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/08-hook-capacitor.md +65 -0
  44. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/09-refactor-division.md +51 -0
  45. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/10-scaffolding-inicial.md +79 -0
  46. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/11-supabase-crud-service.md +114 -0
  47. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/12-supabase-hook-usetable.md +143 -0
  48. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/13-tauri-command-rust.md +84 -0
  49. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/14-tauri-wrapper-typescript.md +92 -0
  50. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/15-documentar-tabla-db.md +50 -0
  51. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/16-diagrama-arquitectura.md +60 -0
  52. package/docs/referencia-metodologia/docs/PROMPTS/PLANTILLAS/17-documentar-api-rpc.md +56 -0
  53. package/docs/referencia-metodologia/docs/PROMPTS/STACK/ionic-capacitor.md +52 -0
  54. package/docs/referencia-metodologia/docs/PROMPTS/STACK/react-web-puro.md +46 -0
  55. package/docs/referencia-metodologia/docs/PROMPTS/STACK/tauri-desktop.md +53 -0
  56. package/package.json +56 -0
  57. package/schemas/prompt-lang.json +98 -0
  58. package/src/commands/component.js +326 -0
  59. package/src/commands/context.js +206 -0
  60. package/src/commands/figma.js +63 -0
  61. package/src/commands/init.js +373 -0
  62. package/src/commands/suggest.js +31 -0
  63. package/src/commands/validate.js +183 -0
  64. package/src/generators/figma-prompt.js +56 -0
  65. package/src/utils/ai.js +143 -0
  66. package/src/utils/annotations.js +510 -0
  67. package/src/utils/config.js +60 -0
  68. package/vscode-extension/README.md +31 -0
  69. package/vscode-extension/language-configuration.json +7 -0
  70. package/vscode-extension/package.json +62 -0
  71. package/vscode-extension/snippets/promptlang.json +105 -0
  72. package/vscode-extension/syntaxes/annotations.tmGrammar.json +39 -0
  73. package/vscode-extension/syntaxes/promptlang.tmGrammar.json +14 -0
@@ -0,0 +1,326 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, cpSync } from "fs";
2
+ import { join, relative, basename } from "path";
3
+ import chalk from "chalk";
4
+
5
+ const MANIFEST_FILE = "componentes.json";
6
+
7
+ export async function componentList(options) {
8
+ const cwd = process.cwd();
9
+ const config = loadProjectConfig(cwd);
10
+ const libPath = options.library || config?.components?.library || null;
11
+
12
+ console.log(chalk.cyan("\n📦 Componentes disponibles\n"));
13
+
14
+ // Componentes del proyecto
15
+ const sourceDir = config?.components?.source || join(cwd, "src/components/ui");
16
+ if (existsSync(sourceDir)) {
17
+ console.log(chalk.blue(`📁 Proyecto (${relative(cwd, sourceDir)}/):`));
18
+ const files = listComponents(sourceDir);
19
+ if (files.length === 0) {
20
+ console.log(" (vacío)");
21
+ } else {
22
+ for (const f of files) {
23
+ const tags = extractTags(join(sourceDir, f));
24
+ console.log(` ✅ ${chalk.bold(f.replace(".tsx", ""))}${tags ? ` — ${tags}` : ""}`);
25
+ }
26
+ }
27
+ } else {
28
+ console.log(chalk.yellow(` ⚠️ No existe ${relative(cwd, sourceDir)}/`));
29
+ }
30
+
31
+ // Componentes de la biblioteca
32
+ if (libPath && existsSync(libPath)) {
33
+ const manifestPath = join(libPath, MANIFEST_FILE);
34
+ if (existsSync(manifestPath)) {
35
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
36
+ console.log(chalk.blue(`\n📚 Biblioteca (${relative(cwd, libPath)}/):`));
37
+ for (const [name, info] of Object.entries(manifest.componentes)) {
38
+ const tags = info.tags ? info.tags.join(", ") : "";
39
+ console.log(` 📦 ${chalk.bold(name)}${tags ? ` — ${tags}` : ""}`);
40
+ if (info.variants) console.log(` Variants: ${info.variants.join(", ")}`);
41
+ }
42
+ }
43
+ } else if (libPath) {
44
+ console.log(chalk.yellow(`\n ⚠️ Biblioteca no encontrada: ${libPath}`));
45
+ }
46
+
47
+ console.log("");
48
+ }
49
+
50
+ export async function componentAdd(name, options) {
51
+ const cwd = process.cwd();
52
+ const config = loadProjectConfig(cwd);
53
+ const libPath = options.library || config?.components?.library || null;
54
+
55
+ if (!libPath || !existsSync(libPath)) {
56
+ console.log(chalk.red(`\n❌ Biblioteca no encontrada: ${libPath || "no configurada"}`));
57
+ console.log(chalk.cyan(" Configura components.library en prompt-lang.json o usa --library\n"));
58
+ return;
59
+ }
60
+
61
+ const manifestPath = join(libPath, MANIFEST_FILE);
62
+ if (!existsSync(manifestPath)) {
63
+ console.log(chalk.red("\n❌ La biblioteca no tiene componentes.json\n"));
64
+ return;
65
+ }
66
+
67
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
68
+ const compInfo = manifest.componentes?.[name];
69
+ if (!compInfo) {
70
+ console.log(chalk.red(`\n❌ Componente "${name}" no encontrado en la biblioteca`));
71
+ console.log(chalk.cyan(` Componentes disponibles: ${Object.keys(manifest.componentes).join(", ")}\n`));
72
+ return;
73
+ }
74
+
75
+ const srcFile = join(libPath, compInfo.path);
76
+ if (!existsSync(srcFile)) {
77
+ console.log(chalk.red(`\n❌ Archivo no encontrado: ${compInfo.path}\n`));
78
+ return;
79
+ }
80
+
81
+ const targetDir = config?.components?.source || join(cwd, "src/components/ui");
82
+ mkdirSync(targetDir, { recursive: true });
83
+
84
+ const targetFile = join(targetDir, basename(srcFile));
85
+ if (existsSync(targetFile)) {
86
+ console.log(chalk.yellow(`\n ⚠️ ${basename(srcFile)} ya existe en el proyecto. Sobrescrito.\n`));
87
+ }
88
+
89
+ const content = readFileSync(srcFile, "utf-8");
90
+ writeFileSync(targetFile, content, "utf-8");
91
+ console.log(chalk.green(`\n ✅ ${name} agregado a src/components/ui/${basename(srcFile)}\n`));
92
+ }
93
+
94
+ export async function componentInit(options) {
95
+ const libPath = options.library || join(process.cwd(), "diseno-componentes");
96
+
97
+ if (existsSync(libPath) && readdirSync(libPath).length > 0) {
98
+ console.log(chalk.yellow(`\n ⚠️ El directorio ${libPath} ya existe y no está vacío.\n`));
99
+ return;
100
+ }
101
+
102
+ mkdirSync(join(libPath, "ui"), { recursive: true });
103
+ mkdirSync(join(libPath, "layouts"), { recursive: true });
104
+
105
+ // UI Components
106
+ const buttonContent = `import { cva, type VariantProps } from "class-variance-authority"
107
+ import { memo } from "react"
108
+
109
+ const buttonVariants = cva(
110
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
111
+ {
112
+ variants: {
113
+ variant: {
114
+ primary: "bg-blue-600 text-white hover:bg-blue-700",
115
+ secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
116
+ danger: "bg-red-600 text-white hover:bg-red-700",
117
+ ghost: "hover:bg-gray-100 hover:text-gray-900",
118
+ },
119
+ size: {
120
+ default: "h-10 px-4 py-2",
121
+ sm: "h-9 rounded-md px-3",
122
+ lg: "h-11 rounded-md px-8",
123
+ icon: "h-10 w-10",
124
+ },
125
+ },
126
+ defaultVariants: { variant: "primary", size: "default" },
127
+ }
128
+ )
129
+
130
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
131
+
132
+ const Button = memo<ButtonProps>(({ className, variant, size, ...props }) => (
133
+ <button className={buttonVariants({ variant, size, className })} {...props} />
134
+ ))
135
+ Button.displayName = "Button"
136
+
137
+ export { Button, buttonVariants }
138
+ `
139
+
140
+ const confirmModalContent = `import { useEffect, useRef, type ReactNode } from "react"
141
+ import { Button } from "./Button"
142
+
143
+ interface ConfirmModalProps {
144
+ open: boolean
145
+ onConfirm: () => void
146
+ onCancel: () => void
147
+ title: string
148
+ message: string
149
+ confirmLabel?: string
150
+ cancelLabel?: string
151
+ variant?: "danger" | "primary"
152
+ loading?: boolean
153
+ children?: ReactNode
154
+ }
155
+
156
+ export function ConfirmModal({
157
+ open,
158
+ onConfirm,
159
+ onCancel,
160
+ title,
161
+ message,
162
+ confirmLabel = "Confirmar",
163
+ cancelLabel = "Cancelar",
164
+ variant = "danger",
165
+ loading = false,
166
+ children,
167
+ }: ConfirmModalProps) {
168
+ const overlayRef = useRef<HTMLDivElement>(null)
169
+
170
+ useEffect(() => {
171
+ const handleEscape = (e: KeyboardEvent) => {
172
+ if (e.key === "Escape" && !loading) onCancel()
173
+ }
174
+ if (open) document.addEventListener("keydown", handleEscape)
175
+ return () => document.removeEventListener("keydown", handleEscape)
176
+ }, [open, loading, onCancel])
177
+
178
+ if (!open) return null
179
+
180
+ return (
181
+ <div
182
+ ref={overlayRef}
183
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
184
+ onClick={(e) => { if (e.target === overlayRef && !loading) onCancel() }}
185
+ >
186
+ <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl" role="dialog" aria-modal="true">
187
+ <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
188
+ {message && <p className="mt-2 text-sm text-gray-600">{message}</p>}
189
+ {children && <div className="mt-3">{children}</div>}
190
+ <div className="mt-6 flex justify-end gap-3">
191
+ <Button variant="ghost" onClick={onCancel} disabled={loading}>
192
+ {cancelLabel}
193
+ </Button>
194
+ <Button variant={variant} onClick={onConfirm} disabled={loading}>
195
+ {loading ? "Procesando..." : confirmLabel}
196
+ </Button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ )
201
+ }
202
+ `
203
+
204
+ const inputContent = `import { forwardRef, type InputHTMLAttributes } from "react"
205
+
206
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
207
+ label?: string
208
+ error?: string
209
+ }
210
+
211
+ const Input = forwardRef<HTMLInputElement, InputProps>(({ label, error, className, ...props }, ref) => (
212
+ <div className="w-full">
213
+ {label && <label className="mb-1 block text-sm font-medium text-gray-700">{label}</label>}
214
+ <input
215
+ ref={ref}
216
+ className={\`flex h-10 w-full rounded-md border px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 \${
217
+ error ? "border-red-500 focus:ring-red-500" : "border-gray-300"
218
+ } \${className || ""}\`}
219
+ {...props}
220
+ />
221
+ {error && <p className="mt-1 text-xs text-red-600">{error}</p>}
222
+ </div>
223
+ ))
224
+ Input.displayName = "Input"
225
+
226
+ export { Input }
227
+ `
228
+
229
+ writeFileSync(join(libPath, "ui/Button.tsx"), buttonContent, "utf-8");
230
+ writeFileSync(join(libPath, "ui/ConfirmModal.tsx"), confirmModalContent, "utf-8");
231
+ writeFileSync(join(libPath, "ui/Input.tsx"), inputContent, "utf-8");
232
+
233
+ // Barrel export
234
+ writeFileSync(join(libPath, "ui/index.ts"), `export { Button, buttonVariants } from "./Button"
235
+ export { ConfirmModal } from "./ConfirmModal"
236
+ export { Input } from "./Input"
237
+ `, "utf-8");
238
+
239
+ // Manifest
240
+ const manifest = {
241
+ name: options.name || "mi-diseno",
242
+ version: "1.0.0",
243
+ descripcion: "Biblioteca de componentes reutilizables",
244
+ componentes: {
245
+ Button: {
246
+ path: "ui/Button.tsx",
247
+ tags: ["ui", "form", "accion"],
248
+ variants: ["primary", "secondary", "danger", "ghost"],
249
+ descripcion: "Botón con variantes de color y tamaño",
250
+ },
251
+ ConfirmModal: {
252
+ path: "ui/ConfirmModal.tsx",
253
+ tags: ["ui", "modal", "confirmacion", "destructivo"],
254
+ descripcion: "Modal centrado para confirmar acciones destructivas con soporte de teclado",
255
+ },
256
+ Input: {
257
+ path: "ui/Input.tsx",
258
+ tags: ["ui", "form", "input"],
259
+ descripcion: "Input con label, error y forwardRef",
260
+ },
261
+ },
262
+ };
263
+
264
+ writeFileSync(join(libPath, MANIFEST_FILE), JSON.stringify(manifest, null, 2), "utf-8");
265
+ console.log(chalk.green(`\n ✅ Biblioteca inicializada en ${libPath}/`));
266
+ console.log(chalk.cyan(` 3 componentes creados: Button, ConfirmModal, Input`));
267
+ console.log(chalk.cyan(` Agrega a prompt-lang.json: "components": { "library": "${libPath}" }\n`));
268
+ }
269
+
270
+ export async function componentManifest(options) {
271
+ const cwd = process.cwd();
272
+ const config = loadProjectConfig(cwd);
273
+ const libPath = options.library || config?.components?.library || null;
274
+
275
+ if (!libPath || !existsSync(libPath)) {
276
+ console.log(chalk.red("\n❌ Biblioteca no encontrada\n"));
277
+ return;
278
+ }
279
+
280
+ const manifestPath = join(libPath, MANIFEST_FILE);
281
+ if (!existsSync(manifestPath)) {
282
+ console.log(chalk.yellow("\n ⚠️ No hay componentes.json en la biblioteca\n"));
283
+ return;
284
+ }
285
+
286
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
287
+ console.log(chalk.cyan(`\n📚 ${manifest.name} v${manifest.version}`));
288
+ console.log(` ${manifest.descripcion || ""}`);
289
+ console.log(`\n Componentes (${Object.keys(manifest.componentes || {}).length}):`);
290
+ for (const [name, info] of Object.entries(manifest.componentes || {})) {
291
+ console.log(` 📦 ${chalk.bold(name)} → ${info.path}`);
292
+ if (info.descripcion) console.log(` ${info.descripcion}`);
293
+ if (info.tags) console.log(` Tags: ${info.tags.join(", ")}`);
294
+ }
295
+ console.log("");
296
+ }
297
+
298
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
299
+
300
+ function loadProjectConfig(cwd) {
301
+ const configPath = join(cwd, "prompt-lang.json");
302
+ if (!existsSync(configPath)) return null;
303
+ try {
304
+ return JSON.parse(readFileSync(configPath, "utf-8"));
305
+ } catch {
306
+ return null;
307
+ }
308
+ }
309
+
310
+ function listComponents(dir) {
311
+ const entries = readdirSync(dir, { withFileTypes: true });
312
+ return entries
313
+ .filter(e => e.isFile() && (e.name.endsWith(".tsx") || e.name.endsWith(".tsx")))
314
+ .map(e => e.name)
315
+ .sort();
316
+ }
317
+
318
+ function extractTags(filePath) {
319
+ try {
320
+ const content = readFileSync(filePath, "utf-8");
321
+ const match = content.match(/\/\/\s*@kind\((\w+)\)/);
322
+ return match ? match[1] : null;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
@@ -0,0 +1,206 @@
1
+ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from "fs";
2
+ import { join, relative, extname, basename, dirname } from "path";
3
+ import { loadConfig } from "../utils/config.js";
4
+ import { lintFile } from "../utils/annotations.js";
5
+ import chalk from "chalk";
6
+
7
+ const IGNORED_DIRS = new Set([
8
+ ".git", "node_modules", "dist", "build", ".next", ".gemini",
9
+ "coverage", ".nyc_output", "__pycache__", ".cache",
10
+ ]);
11
+
12
+ const IGNORED_FILES = new Set([
13
+ "package-lock.json", "yarn.lock", ".env.local", ".env",
14
+ "contexto.md", "proyecto_completo.md",
15
+ ]);
16
+
17
+ const IGNORED_EXTENSIONS = new Set([
18
+ ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".webp",
19
+ ".pdf", ".woff", ".woff2", ".ttf", ".eot", ".otf",
20
+ ".mp4", ".mp3", ".avi", ".mov",
21
+ ".zip", ".tar", ".gz", ".rar",
22
+ ".exe", ".dll", ".so", ".dylib",
23
+ ".db", ".sqlite", ".sqlite3",
24
+ ]);
25
+
26
+ const CODE_EXTENSIONS = new Set([
27
+ ".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".html",
28
+ ".md", ".mdx", ".py", ".rs", ".toml", ".yaml", ".yml",
29
+ ".sh", ".bash", ".zsh", ".env.example",
30
+ ]);
31
+
32
+ function shouldIgnore(name, relPath, isDir) {
33
+ if (isDir) return IGNORED_DIRS.has(name);
34
+ if (IGNORED_FILES.has(name)) return true;
35
+ const ext = extname(name).toLowerCase();
36
+ if (IGNORED_EXTENSIONS.has(ext)) return true;
37
+ return false;
38
+ }
39
+
40
+ function generateTree(dirPath, prefix = "", configIgnore = []) {
41
+ let treeStr = "";
42
+ try {
43
+ const entries = readdirSync(dirPath).sort();
44
+ const filtered = entries.filter((e) => {
45
+ if (IGNORED_DIRS.has(e) || IGNORED_FILES.has(e)) return false;
46
+ if (configIgnore.some((ig) => e.includes(ig))) return false;
47
+ return true;
48
+ });
49
+
50
+ for (let i = 0; i < filtered.length; i++) {
51
+ const entry = filtered[i];
52
+ const fullPath = join(dirPath, entry);
53
+ const isLast = i === filtered.length - 1;
54
+ const connector = isLast ? "└── " : "├── ";
55
+ treeStr += `${prefix}${connector}${entry}\n`;
56
+
57
+ if (statSync(fullPath).isDirectory()) {
58
+ const extension = isLast ? " " : "│ ";
59
+ treeStr += generateTree(fullPath, prefix + extension, configIgnore);
60
+ }
61
+ }
62
+ } catch {
63
+ // PermissionError o similar
64
+ }
65
+ return treeStr;
66
+ }
67
+
68
+ export async function context(options) {
69
+ const baseDir = process.cwd();
70
+ const outputFile = options.output || "contexto.md";
71
+ const config = loadConfig(baseDir);
72
+ const configIgnores = config.extractor?.ignore || [];
73
+
74
+ console.log(chalk.cyan("🔍 Extrayendo contexto del proyecto..."));
75
+
76
+ let output = "# Contexto del Proyecto\n\n";
77
+
78
+ // Incluir AGENTS.md si existe
79
+ const agentsPath = join(baseDir, "AGENTS.md");
80
+ if (existsSync(agentsPath)) {
81
+ output += "## AGENTS.md (Contexto para IA)\n\n";
82
+ output += readFileSync(agentsPath, "utf-8") + "\n\n";
83
+ }
84
+
85
+ // Incluir prompt-lang.json si existe
86
+ const configPath = join(baseDir, "prompt-lang.json");
87
+ if (existsSync(configPath)) {
88
+ output += "## prompt-lang.json (Configuración)\n\n";
89
+ output += "```json\n";
90
+ output += readFileSync(configPath, "utf-8") + "\n";
91
+ output += "```\n\n";
92
+ }
93
+
94
+ // Recorrer archivos
95
+ let fileCount = 0;
96
+ const walkDir = (dirPath) => {
97
+ let entries;
98
+ try {
99
+ entries = readdirSync(dirPath);
100
+ } catch {
101
+ return;
102
+ }
103
+
104
+ for (const entry of entries) {
105
+ const fullPath = join(dirPath, entry);
106
+ let stat;
107
+ try {
108
+ stat = statSync(fullPath);
109
+ } catch {
110
+ continue;
111
+ }
112
+
113
+ if (stat.isDirectory()) {
114
+ if (!shouldIgnore(entry, relative(baseDir, fullPath), true)) {
115
+ walkDir(fullPath);
116
+ }
117
+ continue;
118
+ }
119
+
120
+ // Ignorar archivos
121
+ if (shouldIgnore(entry, relative(baseDir, fullPath), false)) continue;
122
+
123
+ const ext = extname(entry).toLowerCase();
124
+ if (!CODE_EXTENSIONS.has(ext)) continue;
125
+
126
+ // Ignorar el propio output
127
+ const relPath = relative(baseDir, fullPath);
128
+ if (relPath === outputFile) continue;
129
+
130
+ // Scope filtering
131
+ if (options.scope) {
132
+ try {
133
+ const content = readFileSync(fullPath, "utf-8");
134
+ if (!content.includes(`@scope`)) continue;
135
+ if (!content.includes(`module: ${options.scope}`)) continue;
136
+ } catch {
137
+ continue;
138
+ }
139
+ }
140
+
141
+ fileCount++;
142
+
143
+ let content;
144
+ try {
145
+ content = readFileSync(fullPath, "utf-8");
146
+ } catch {
147
+ continue;
148
+ }
149
+
150
+ // Detectar lenguaje para el bloque de código
151
+ const langMap = {
152
+ ".ts": "typescript", ".tsx": "tsx", ".js": "javascript",
153
+ ".jsx": "jsx", ".json": "json", ".css": "css",
154
+ ".html": "html", ".md": "markdown", ".mdx": "mdx",
155
+ ".py": "python", ".rs": "rust", ".toml": "toml",
156
+ ".yaml": "yaml", ".yml": "yaml", ".sh": "bash",
157
+ };
158
+ const lang = langMap[ext] || "";
159
+
160
+ output += `### Archivo: ${relPath}\n`;
161
+ output += `\`\`\`${lang}\n`;
162
+ output += content;
163
+ if (!content.endsWith("\n")) output += "\n";
164
+ output += "```\n\n";
165
+
166
+ // Si tiene anotaciones PromptLang, mostrarlas como metadata
167
+ if (options.ai !== false) {
168
+ try {
169
+ const { annotations, errors, warnings } = lintFile(content);
170
+ if (annotations.length > 0) {
171
+ output += `*Anotaciones PromptLang:* `;
172
+ for (const ann of annotations) {
173
+ output += `\`@${ann.name}`;
174
+ if (ann.args.length > 0) {
175
+ const argsStr = ann.args
176
+ .map((a) => (a.key ? `${a.key}: ${a.value}` : a.value))
177
+ .join(", ");
178
+ output += `(${argsStr})`;
179
+ }
180
+ output += "` ";
181
+ }
182
+ output += "\n\n";
183
+ }
184
+ } catch {
185
+ // Ignorar errores de parseo en la extracción
186
+ }
187
+ }
188
+ }
189
+ };
190
+
191
+ walkDir(baseDir);
192
+
193
+ // Agregar estructura de carpetas al final
194
+ output += "# Estructura de Carpetas\n\n";
195
+ output += "```text\n";
196
+ output += `${basename(baseDir)}/\n`;
197
+ output += generateTree(baseDir, "", configIgnores);
198
+ output += "```\n";
199
+
200
+ // Escribir archivo
201
+ const outputPath = join(baseDir, outputFile);
202
+ writeFileSync(outputPath, output, "utf-8");
203
+
204
+ console.log(chalk.green(`✅ Contexto extraído: ${fileCount} archivos`));
205
+ console.log(chalk.green(`📄 Output: ${outputFile}`));
206
+ }
@@ -0,0 +1,63 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+ import chalk from "chalk";
4
+ import inquirer from "inquirer";
5
+ import { generateFigmaPrompt } from "../generators/figma-prompt.js";
6
+
7
+ export async function figma() {
8
+ console.log(chalk.cyan("\n🎨 Generador de Prompt para Figma\n"));
9
+
10
+ const answers = await inquirer.prompt([
11
+ {
12
+ type: "list",
13
+ name: "projectType",
14
+ message: "Tipo de proyecto:",
15
+ choices: ["Dashboard SaaS", "Ecommerce", "Landing Page", "Mobile App", "Admin Panel"],
16
+ },
17
+ {
18
+ type: "input",
19
+ name: "screens",
20
+ message: "Pantallas principales (separadas por coma):",
21
+ default: "Login, Dashboard, Profile",
22
+ },
23
+ {
24
+ type: "confirm",
25
+ name: "mobileFirst",
26
+ message: "¿Mobile-first?",
27
+ default: true,
28
+ },
29
+ {
30
+ type: "list",
31
+ name: "style",
32
+ message: "Estilo visual:",
33
+ choices: [
34
+ "Minimalista corporativo",
35
+ "Moderno vibrante",
36
+ "Oscuro (dark mode)",
37
+ "Claro y limpio",
38
+ "Personalizado",
39
+ ],
40
+ },
41
+ {
42
+ type: "confirm",
43
+ name: "includeAntiPatterns",
44
+ message: "¿Incluir anti-patrones a evitar?",
45
+ default: true,
46
+ },
47
+ ]);
48
+
49
+ const promptContent = generateFigmaPrompt(answers);
50
+
51
+ // Guardar en docs/PROMPTS/
52
+ const promptsDir = join(process.cwd(), "docs", "PROMPTS");
53
+ if (!existsSync(promptsDir)) {
54
+ mkdirSync(promptsDir, { recursive: true });
55
+ }
56
+
57
+ const outputPath = join(promptsDir, "figma-prompt.md");
58
+ writeFileSync(outputPath, promptContent, "utf-8");
59
+
60
+ console.log(chalk.green(`\n✅ Prompt Figma generado:`));
61
+ console.log(chalk.cyan(` ${outputPath}\n`));
62
+ console.log(promptContent);
63
+ }