pigmalion-setup 0.1.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 +30 -0
- package/bin/cli.js +430 -0
- package/package.json +17 -0
- package/templates/AGENTS.md +299 -0
- package/templates/CODEX.md +29 -0
- package/templates/SETUP.md +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# pigmalion-setup
|
|
2
|
+
|
|
3
|
+
Utilidad NPX para inicializar un proyecto copiando `AGENTS.md`, `CODEX.md` y `SETUP.md`, y arrancar Codex usando `SETUP.md` como base del flujo.
|
|
4
|
+
|
|
5
|
+
## Uso
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx pigmalion-setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requiere que el comando `codex` este disponible en `PATH`.
|
|
12
|
+
|
|
13
|
+
## Comportamiento
|
|
14
|
+
|
|
15
|
+
- Copia `AGENTS.md`, `CODEX.md`, `SETUP.md` en el directorio actual si no existen.
|
|
16
|
+
- Ejecuta `codex` y le pasa el contenido de `SETUP.md` para iniciar la conversación.
|
|
17
|
+
- Codex define documentación base y `ROADMAP.md` según el flujo de `SETUP.md`.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Desarrollo local
|
|
21
|
+
|
|
22
|
+
Para probar la herramienta localmente sin publicarla, puedes usar `npm link`:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm link
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Esto hará que el comando `pigmalion-setup` esté disponible globalmente en tu sistema, apuntando a tu versión local del proyecto. Luego puedes ejecutar `npx pigmalion-setup` para probarlo.
|
|
29
|
+
|
|
30
|
+
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import fsPromises from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const TEMPLATE_DIR = path.join(__dirname, "..", "templates");
|
|
13
|
+
const TARGET_DIR = process.cwd();
|
|
14
|
+
|
|
15
|
+
const INITIAL_FILES = ["SETUP.md"];
|
|
16
|
+
const POST_FILES = ["AGENTS.md", "CODEX.md"];
|
|
17
|
+
|
|
18
|
+
function log(msg) {
|
|
19
|
+
process.stdout.write(`${msg}\n`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizePath(p) {
|
|
23
|
+
if (process.platform !== "win32") return p;
|
|
24
|
+
if (!p) return p;
|
|
25
|
+
const msysMatch = p.match(/^\/([a-zA-Z])\/(.*)/);
|
|
26
|
+
if (msysMatch) {
|
|
27
|
+
const drive = msysMatch[1].toUpperCase();
|
|
28
|
+
const rest = msysMatch[2].replace(/\//g, "\\");
|
|
29
|
+
return `${drive}:\\${rest}`;
|
|
30
|
+
}
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function detectShell() {
|
|
35
|
+
const env = process.env;
|
|
36
|
+
const shell = (env.SHELL || "").toLowerCase();
|
|
37
|
+
if (shell.includes("bash")) return "bash";
|
|
38
|
+
const psModulePath = (env.PSModulePath || "").toLowerCase();
|
|
39
|
+
if (psModulePath.includes("powershell") || env.PSExecutionPolicyPreference) {
|
|
40
|
+
return "powershell";
|
|
41
|
+
}
|
|
42
|
+
const comspec = (env.COMSPEC || "").toLowerCase();
|
|
43
|
+
if (comspec.includes("cmd.exe")) return "cmd";
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureWindowsExecutable(codexCmd) {
|
|
48
|
+
if (process.platform !== "win32") return codexCmd;
|
|
49
|
+
if (!codexCmd) return codexCmd;
|
|
50
|
+
const ext = path.extname(codexCmd);
|
|
51
|
+
if (ext) return codexCmd;
|
|
52
|
+
const candidates = [
|
|
53
|
+
`${codexCmd}.cmd`,
|
|
54
|
+
`${codexCmd}.ps1`,
|
|
55
|
+
`${codexCmd}.exe`,
|
|
56
|
+
codexCmd
|
|
57
|
+
];
|
|
58
|
+
for (const candidate of candidates) {
|
|
59
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
60
|
+
}
|
|
61
|
+
return codexCmd;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function canRunCodex(codexCmd) {
|
|
65
|
+
if (!codexCmd) return false;
|
|
66
|
+
const ext = path.extname(codexCmd).toLowerCase();
|
|
67
|
+
const useShell =
|
|
68
|
+
process.platform === "win32" && (!ext || ext === ".cmd" || ext === ".ps1");
|
|
69
|
+
const result = spawnSync(codexCmd, ["--version"], {
|
|
70
|
+
stdio: "ignore",
|
|
71
|
+
env: process.env,
|
|
72
|
+
shell: useShell
|
|
73
|
+
});
|
|
74
|
+
if (result.error && result.error.code === "ENOENT") return false;
|
|
75
|
+
if (result.error) return false;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function fileExists(p) {
|
|
80
|
+
try {
|
|
81
|
+
await fsPromises.access(p);
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function confirmOverwrite(name) {
|
|
89
|
+
if (!process.stdin.isTTY) {
|
|
90
|
+
return true; // Asumir sí en modo no interactivo
|
|
91
|
+
}
|
|
92
|
+
const rl = readline.createInterface({
|
|
93
|
+
input: process.stdin,
|
|
94
|
+
output: process.stdout
|
|
95
|
+
});
|
|
96
|
+
try {
|
|
97
|
+
const answer = await rl.question(
|
|
98
|
+
`- ${name} ya existe. ¿Sobrescribir? (s/N): `
|
|
99
|
+
);
|
|
100
|
+
const normalized = answer.trim().toLowerCase();
|
|
101
|
+
return normalized === "s" || normalized === "si" || normalized === "y";
|
|
102
|
+
} finally {
|
|
103
|
+
rl.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function waitForEnter() {
|
|
108
|
+
if (!process.stdin.isTTY) {
|
|
109
|
+
log("Modo no interactivo detectado. Continuando...");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const rl = readline.createInterface({
|
|
113
|
+
input: process.stdin,
|
|
114
|
+
output: process.stdout
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
await rl.question("Presiona Enter para continuar...");
|
|
118
|
+
} finally {
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function copyTemplates(files, { allowOverwrite } = { allowOverwrite: false }) {
|
|
124
|
+
for (const name of files) {
|
|
125
|
+
const src = path.join(TEMPLATE_DIR, name);
|
|
126
|
+
const dest = path.join(TARGET_DIR, name);
|
|
127
|
+
if (await fileExists(dest)) {
|
|
128
|
+
if (!allowOverwrite) {
|
|
129
|
+
log(`- ${name} ya existe, se omite`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const shouldOverwrite = await confirmOverwrite(name);
|
|
133
|
+
if (!shouldOverwrite) {
|
|
134
|
+
log(`- ${name} ya existe, se omite`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await fsPromises.copyFile(src, dest);
|
|
139
|
+
log(`- ${name} instalado`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveCodexJsFromCmd(codexCmd) {
|
|
144
|
+
if (!codexCmd.toLowerCase().endsWith(".cmd")) return null;
|
|
145
|
+
const binDir = path.dirname(codexCmd);
|
|
146
|
+
const candidate = path.join(
|
|
147
|
+
binDir,
|
|
148
|
+
"node_modules",
|
|
149
|
+
"@openai",
|
|
150
|
+
"codex",
|
|
151
|
+
"bin",
|
|
152
|
+
"codex.js",
|
|
153
|
+
);
|
|
154
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildCodexProcess(codexCmd, prompt) {
|
|
159
|
+
const codexJs = resolveCodexJsFromCmd(codexCmd);
|
|
160
|
+
if (codexJs) {
|
|
161
|
+
return {
|
|
162
|
+
cmd: process.execPath,
|
|
163
|
+
args: [codexJs],
|
|
164
|
+
options: { shell: false },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const ext = path.extname(codexCmd).toLowerCase();
|
|
168
|
+
const useShell =
|
|
169
|
+
process.platform === "win32" && (ext === ".cmd" || ext === ".ps1");
|
|
170
|
+
return { cmd: codexCmd, args: [], options: { shell: useShell } };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runCodex(codexCmd) {
|
|
174
|
+
const setupPath = path.join(TARGET_DIR, "SETUP.md");
|
|
175
|
+
|
|
176
|
+
log("");
|
|
177
|
+
log("Setup detectado. Para continuar con Codex:");
|
|
178
|
+
log(`Ejecuta: ${codexCmd}`);
|
|
179
|
+
log("");
|
|
180
|
+
log("Y pega el siguiente prompt:");
|
|
181
|
+
log("");
|
|
182
|
+
const prompt = [
|
|
183
|
+
`Lee y sigue estrictamente SETUP.md en: ${setupPath}`,
|
|
184
|
+
"Inicia el flujo base con el usuario para definir vision general, documentacion y ROADMAP.",
|
|
185
|
+
"No toques codigo todavia. Solo documentacion, roadmap y arquitectura.",
|
|
186
|
+
"Comienza ahora."
|
|
187
|
+
].join("\n");
|
|
188
|
+
log(prompt);
|
|
189
|
+
log("");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function findCodexInBinDir(binDir) {
|
|
193
|
+
if (!binDir) return null;
|
|
194
|
+
const normalized = normalizePath(binDir);
|
|
195
|
+
const candidates = [
|
|
196
|
+
path.join(normalized, "codex"),
|
|
197
|
+
path.join(normalized, "codex.cmd"),
|
|
198
|
+
path.join(normalized, "codex.ps1"),
|
|
199
|
+
path.join(normalized, "codex.exe")
|
|
200
|
+
];
|
|
201
|
+
for (const candidate of candidates) {
|
|
202
|
+
try {
|
|
203
|
+
fs.accessSync(candidate);
|
|
204
|
+
return candidate;
|
|
205
|
+
} catch {
|
|
206
|
+
// continue
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function findCodexFromNpmGlobal() {
|
|
213
|
+
const npmPrefix = spawnSync("npm", ["prefix", "-g"], {
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
216
|
+
env: process.env
|
|
217
|
+
});
|
|
218
|
+
if (npmPrefix.status !== 0 || !npmPrefix.stdout) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const prefix = npmPrefix.stdout.trim();
|
|
222
|
+
const direct = findCodexInBinDir(prefix);
|
|
223
|
+
if (direct) return direct;
|
|
224
|
+
return findCodexInBinDir(path.join(prefix, "bin"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function findCodexFromNpmPrefix() {
|
|
228
|
+
const npmPrefix = spawnSync("npm", ["prefix", "-g"], {
|
|
229
|
+
encoding: "utf8",
|
|
230
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
231
|
+
env: process.env
|
|
232
|
+
});
|
|
233
|
+
if (npmPrefix.status === 0 && npmPrefix.stdout) {
|
|
234
|
+
const prefix = npmPrefix.stdout.trim();
|
|
235
|
+
const found = findCodexInBinDir(prefix);
|
|
236
|
+
if (found) return found;
|
|
237
|
+
const foundBin = findCodexInBinDir(path.join(prefix, "bin"));
|
|
238
|
+
if (foundBin) return foundBin;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const npmConfigPrefix = spawnSync("npm", ["config", "get", "prefix"], {
|
|
242
|
+
encoding: "utf8",
|
|
243
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
244
|
+
env: process.env
|
|
245
|
+
});
|
|
246
|
+
if (npmConfigPrefix.status === 0 && npmConfigPrefix.stdout) {
|
|
247
|
+
const prefix = npmConfigPrefix.stdout.trim();
|
|
248
|
+
const found = findCodexInBinDir(prefix);
|
|
249
|
+
if (found) return found;
|
|
250
|
+
const foundBin = findCodexInBinDir(path.join(prefix, "bin"));
|
|
251
|
+
if (foundBin) return foundBin;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function findCodexFromLocalNodeModules() {
|
|
258
|
+
const localBin = path.join(TARGET_DIR, "node_modules", ".bin");
|
|
259
|
+
return findCodexInBinDir(localBin);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function findCodexFromWhere() {
|
|
263
|
+
const whereResult = spawnSync("where", ["codex"], {
|
|
264
|
+
encoding: "utf8",
|
|
265
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
266
|
+
env: process.env
|
|
267
|
+
});
|
|
268
|
+
if (whereResult.status !== 0 || !whereResult.stdout) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const lines = whereResult.stdout
|
|
272
|
+
.split(/\r?\n/)
|
|
273
|
+
.map((l) => l.trim())
|
|
274
|
+
.filter(Boolean);
|
|
275
|
+
if (lines.length === 0) return null;
|
|
276
|
+
return lines[0];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function findCodexFromWhich() {
|
|
280
|
+
const whichResult = spawnSync("which", ["codex"], {
|
|
281
|
+
encoding: "utf8",
|
|
282
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
283
|
+
env: process.env
|
|
284
|
+
});
|
|
285
|
+
if (whichResult.status !== 0 || !whichResult.stdout) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
const lines = whichResult.stdout
|
|
289
|
+
.split(/\r?\n/)
|
|
290
|
+
.map((l) => l.trim())
|
|
291
|
+
.filter(Boolean);
|
|
292
|
+
if (lines.length === 0) return null;
|
|
293
|
+
const first = normalizePath(lines[0]);
|
|
294
|
+
const hasExt = path.extname(first).length > 0;
|
|
295
|
+
if (hasExt && fs.existsSync(first)) return first;
|
|
296
|
+
const candidates = [
|
|
297
|
+
`${first}.cmd`,
|
|
298
|
+
`${first}.ps1`,
|
|
299
|
+
`${first}.exe`,
|
|
300
|
+
first
|
|
301
|
+
];
|
|
302
|
+
for (const candidate of candidates) {
|
|
303
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function findCodexFromShell() {
|
|
309
|
+
const shell = detectShell();
|
|
310
|
+
if (shell === "bash") return findCodexFromWhich();
|
|
311
|
+
if (shell === "powershell" || shell === "cmd") return findCodexFromWhere();
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function findCodexFromPnpmGlobal() {
|
|
316
|
+
const pnpmBin = spawnSync("pnpm", ["bin", "-g"], {
|
|
317
|
+
encoding: "utf8",
|
|
318
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
319
|
+
env: process.env
|
|
320
|
+
});
|
|
321
|
+
if (pnpmBin.status !== 0 || !pnpmBin.stdout) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
return findCodexInBinDir(pnpmBin.stdout.trim());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function findCodexFromYarnGlobal() {
|
|
328
|
+
const yarnBin = spawnSync("yarn", ["global", "bin"], {
|
|
329
|
+
encoding: "utf8",
|
|
330
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
331
|
+
env: process.env
|
|
332
|
+
});
|
|
333
|
+
if (yarnBin.status !== 0 || !yarnBin.stdout) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
return findCodexInBinDir(yarnBin.stdout.trim());
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function findCodexFromKnownPaths() {
|
|
340
|
+
const bins = [];
|
|
341
|
+
if (process.env.npm_config_prefix) {
|
|
342
|
+
bins.push(process.env.npm_config_prefix);
|
|
343
|
+
bins.push(path.join(process.env.npm_config_prefix, "bin"));
|
|
344
|
+
}
|
|
345
|
+
if (process.env.npm_prefix) {
|
|
346
|
+
bins.push(process.env.npm_prefix);
|
|
347
|
+
bins.push(path.join(process.env.npm_prefix, "bin"));
|
|
348
|
+
}
|
|
349
|
+
if (process.env.npm_config_global_prefix) {
|
|
350
|
+
bins.push(process.env.npm_config_global_prefix);
|
|
351
|
+
bins.push(path.join(process.env.npm_config_global_prefix, "bin"));
|
|
352
|
+
}
|
|
353
|
+
if (process.env.APPDATA) {
|
|
354
|
+
bins.push(path.join(process.env.APPDATA, "npm"));
|
|
355
|
+
bins.push(path.join(process.env.APPDATA, "Yarn", "bin"));
|
|
356
|
+
}
|
|
357
|
+
if (process.env.LOCALAPPDATA) {
|
|
358
|
+
bins.push(path.join(process.env.LOCALAPPDATA, "Yarn", "bin"));
|
|
359
|
+
bins.push(path.join(process.env.LOCALAPPDATA, "npm"));
|
|
360
|
+
}
|
|
361
|
+
if (process.env.PNPM_HOME) {
|
|
362
|
+
bins.push(process.env.PNPM_HOME);
|
|
363
|
+
}
|
|
364
|
+
if (process.env.YARN_GLOBAL_FOLDER) {
|
|
365
|
+
bins.push(process.env.YARN_GLOBAL_FOLDER);
|
|
366
|
+
bins.push(path.join(process.env.YARN_GLOBAL_FOLDER, "bin"));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const binDir of bins) {
|
|
370
|
+
const found = findCodexInBinDir(binDir);
|
|
371
|
+
if (found) return found;
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function checkCodexAvailable() {
|
|
377
|
+
const candidates = [
|
|
378
|
+
findCodexFromShell(),
|
|
379
|
+
findCodexFromLocalNodeModules(),
|
|
380
|
+
findCodexFromNpmGlobal(),
|
|
381
|
+
findCodexFromNpmPrefix(),
|
|
382
|
+
findCodexFromPnpmGlobal(),
|
|
383
|
+
findCodexFromYarnGlobal(),
|
|
384
|
+
findCodexFromWhere(),
|
|
385
|
+
findCodexFromWhich(),
|
|
386
|
+
findCodexFromKnownPaths()
|
|
387
|
+
];
|
|
388
|
+
for (const candidate of candidates) {
|
|
389
|
+
const resolved = ensureWindowsExecutable(candidate);
|
|
390
|
+
if (canRunCodex(resolved)) return resolved;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const result = spawnSync("codex", ["--version"], {
|
|
394
|
+
stdio: "ignore",
|
|
395
|
+
env: process.env,
|
|
396
|
+
shell: process.platform === "win32"
|
|
397
|
+
});
|
|
398
|
+
if (!result.error && result.status === 0) {
|
|
399
|
+
return "codex";
|
|
400
|
+
}
|
|
401
|
+
if (result.error && result.error.code !== "ENOENT") {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
log("");
|
|
406
|
+
log("No se encontro el comando 'codex' en PATH ni en npm/pnpm/yarn global.");
|
|
407
|
+
log("Instalacion recomendada:");
|
|
408
|
+
log("- Instala el CLI de Codex con el metodo oficial segun tu entorno.");
|
|
409
|
+
log("- Verifica que el comando `codex` quede disponible en PATH.");
|
|
410
|
+
log("- Luego vuelve a ejecutar `npx pigmalion-setup`.");
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function main() {
|
|
415
|
+
log("Pigmalion Setup- Init");
|
|
416
|
+
await copyTemplates(INITIAL_FILES);
|
|
417
|
+
const codexCmd = checkCodexAvailable();
|
|
418
|
+
if (!codexCmd) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
await runCodex(codexCmd);
|
|
422
|
+
await copyTemplates(POST_FILES, { allowOverwrite: true });
|
|
423
|
+
log("");
|
|
424
|
+
log("Listo. Puedes revisar SETUP.md y los docs generados por Codex.");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
main().catch((err) => {
|
|
428
|
+
console.error(err);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pigmalion-setup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "NPX utility to initialize projects with Pigmalion Manager setup flow.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pigmalion-setup": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"templates"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# 🤖 AGENTS.md - Workflow Automatizado IA
|
|
2
|
+
|
|
3
|
+
## 🎯 PRINCIPIO FUNDAMENTAL
|
|
4
|
+
**Actúa primero, pregunta después solo si es crítico**
|
|
5
|
+
Objetivo: Maximizar velocidad sin sacrificar calidad
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🚀 AUTONOMÍA MÁXIMA
|
|
10
|
+
|
|
11
|
+
### Reglas de Decisión
|
|
12
|
+
```
|
|
13
|
+
SI duda <5s → Implementa mejor práctica estándar
|
|
14
|
+
SI impacto >1h trabajo → 1 pregunta concreta máximo
|
|
15
|
+
SI breaking change → Avisa + propón migración
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Comunicación Prohibida
|
|
19
|
+
❌ "¿Quieres que...?"
|
|
20
|
+
❌ "¿Prefieres A o B?"
|
|
21
|
+
❌ "¿Dónde pongo...?"
|
|
22
|
+
|
|
23
|
+
### Comunicación Correcta
|
|
24
|
+
✅ "Implementado X. Tests 92%. Review: /diff"
|
|
25
|
+
✅ "Breaking: API v2. Migración: /docs/MIGRATION.md"
|
|
26
|
+
✅ "Hotfix deployed. Rollback: `git revert abc123`"
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ESTRUCTURA
|
|
31
|
+
(Según proyecto)
|
|
32
|
+
backend/ (3001) | frontend/ (3002) | .github/
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# 🧠 STACK INTELIGENTE (AUTO-SELECCIÓN)
|
|
36
|
+
|
|
37
|
+
## HEURÍSTICAS DECISIÓN STACK (Evalúa + elige mejor)
|
|
38
|
+
|
|
39
|
+
PROYECTO GRANDE (>10 modelos/usuarios/cruzado): Laravel 11 + Inertia + Vue 3 + MySQL (puerto 3000)
|
|
40
|
+
SIMPLE (<1 semana/MVP): Node Express + React Vite (3001/3002)
|
|
41
|
+
REALTIME (chat/socket): Node Socket.io + React (3001/3002)
|
|
42
|
+
ML/DATA (análisis): Python + FastAPI + Streamlit (8000/3002)
|
|
43
|
+
MÓVIL/PWA: Laravel API + React Native
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📁 EJEMPLO DE ESTRUCTURA PROYECTO
|
|
48
|
+
|
|
49
|
+
Por defecto, se sigue una estructura modular por capas o pipelines, evitando mezclar responsabilidades.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
proyecto/
|
|
53
|
+
├── backend/ # API + lógica negocio
|
|
54
|
+
├── frontend/ # UI + cliente
|
|
55
|
+
├── shared/ # types, utils, schemas compartidos
|
|
56
|
+
├── tests/ # tests unitarios e integración
|
|
57
|
+
├── docs/ # Documentación técnica
|
|
58
|
+
│ ├── API.md
|
|
59
|
+
│ ├── ARCHITECTURE.md
|
|
60
|
+
│ └── DECISIONS.md # ADRs importantes
|
|
61
|
+
├── .github/
|
|
62
|
+
│ ├── workflows/ # CI/CD
|
|
63
|
+
├── docker-compose.yml
|
|
64
|
+
├── AGENTS.md # ← ESTE archivo
|
|
65
|
+
├── ROADMAP.md # Plan de trabajo
|
|
66
|
+
├── README.md # Documentación general
|
|
67
|
+
└── package.json # Scripts raíz
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Scripts NPM Raíz (Requeridos)
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "concurrently 'npm:dev:*'",
|
|
75
|
+
"dev:backend": "cd backend && npm run dev",
|
|
76
|
+
"dev:frontend": "cd frontend && npm run dev",
|
|
77
|
+
"install:all": "npm i && cd backend && npm i && cd ../frontend && npm i",
|
|
78
|
+
"lint": "prettier --check . && eslint .",
|
|
79
|
+
"format": "prettier --write .",
|
|
80
|
+
"test": "npm run test --workspaces",
|
|
81
|
+
"test:ci": "npm run test:ci --workspaces",
|
|
82
|
+
"build": "npm run build --workspaces"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🔄 WORKFLOW GIT
|
|
90
|
+
|
|
91
|
+
### Branching Strategy
|
|
92
|
+
```bash
|
|
93
|
+
# Nomenclatura obligatoria
|
|
94
|
+
codex-{tipo}-{descripción-corta}-{timestamp}
|
|
95
|
+
|
|
96
|
+
# Ejemplos
|
|
97
|
+
codex-feat-auth-api-20260215-1430
|
|
98
|
+
codex-fix-login-bug-20260215-1445
|
|
99
|
+
codex-docs-api-spec-20260215-1500
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Tipos Válidos
|
|
103
|
+
- `feat`: Nueva funcionalidad
|
|
104
|
+
- `fix`: Corrección bug
|
|
105
|
+
- `refactor`: Refactorización sin cambio comportamiento
|
|
106
|
+
- `docs`: Solo documentación
|
|
107
|
+
- `test`: Añadir/mejorar tests
|
|
108
|
+
- `chore`: Tareas mantenimiento (deps, config)
|
|
109
|
+
|
|
110
|
+
### Flujo Automático
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
1. /diff # Muestra cambios + impacto
|
|
114
|
+
2. /test # Ejecuta tests + coverage
|
|
115
|
+
3. /merge # Merge si tests >85% + CI pass
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Commits
|
|
119
|
+
```bash
|
|
120
|
+
# Formato: tipo(scope): descripción
|
|
121
|
+
feat(auth): add JWT refresh token endpoint
|
|
122
|
+
fix(ui): resolve mobile menu overflow
|
|
123
|
+
docs(api): update rate limit documentation
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
# 🔄 ISSUE WORKFLOW (GitHub Issues nativo - OBLIGATORIO)
|
|
129
|
+
|
|
130
|
+
## PROCESO AUTOMÁTICO
|
|
131
|
+
|
|
132
|
+
GitHub Issues → Label: 🔴 bug 🤖 codex | 🟢 feature 🤖 codex
|
|
133
|
+
codex /issues → Lista disponibles kanban
|
|
134
|
+
codex /next-bug → codex-bug-#12 (auto-branch)
|
|
135
|
+
Implementa → /test → coverage >85% → /pr → /close #12
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## COMANDOS AGENTES
|
|
139
|
+
codex /issues # Lista + kanban status
|
|
140
|
+
codex /next-bug # Toma siguiente 🔴 bug
|
|
141
|
+
codex /next-feature # Toma siguiente 🟢 feature
|
|
142
|
+
codex /assign #15 # Asigna agent a issue
|
|
143
|
+
codex /close #12 # Merge + close auto
|
|
144
|
+
codex /kanban # Status visual ToDo/InProgress/Done
|
|
145
|
+
|
|
146
|
+
## BRANCH CONVENCIÓN
|
|
147
|
+
🔴 bug → codex-bug-#12-login-crash
|
|
148
|
+
🟢 feature → codex-feature-#15-google-oauth
|
|
149
|
+
🔵 docs → codex-docs-#18-api-readme
|
|
150
|
+
|
|
151
|
+
## PRIORIDADES
|
|
152
|
+
bug > feature > refactor > docs
|
|
153
|
+
GitHub Project: "Codex Kanban" (4 columnas)
|
|
154
|
+
Labels auto: 'crash→bug', 'añade→feature', 'docs→docs'
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 📚 DOCUMENTACIÓN OBLIGATORIA
|
|
159
|
+
|
|
160
|
+
Mantener ROADMAP.md siempre activo y actualizado: referencia principal para agentes y consulta humana.
|
|
161
|
+
|
|
162
|
+
### README.md (Raíz)
|
|
163
|
+
```markdown
|
|
164
|
+
# Proyecto XYZ
|
|
165
|
+
|
|
166
|
+
## Quick Start
|
|
167
|
+
npm run install:all
|
|
168
|
+
npm run dev # → backend:3001 + frontend:3002
|
|
169
|
+
|
|
170
|
+
## Architecture
|
|
171
|
+
Ver [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
172
|
+
|
|
173
|
+
## Roadmap (Fases y entregables)
|
|
174
|
+
Ver [ROADMAP.md](ROADMAP.md)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### backend/README.md
|
|
178
|
+
```markdown
|
|
179
|
+
# Backend API
|
|
180
|
+
|
|
181
|
+
## Endpoints
|
|
182
|
+
GET /api/users # Lista usuarios
|
|
183
|
+
POST /api/users # Crear usuario
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
## Examples
|
|
187
|
+
curl -X POST http://localhost:3001/api/users \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-d '{"name":"John"}'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### frontend/README.md
|
|
193
|
+
```markdown
|
|
194
|
+
# Frontend
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
npm run dev # → http://localhost:3002
|
|
198
|
+
|
|
199
|
+
## Build
|
|
200
|
+
npm run build # → dist/
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Inline Code
|
|
204
|
+
- JSDoc en funciones públicas
|
|
205
|
+
- Comentarios en bloques >15 líneas o lógica compleja
|
|
206
|
+
- TODOs con issue: `// TODO(#42): Refactor to use async/await`
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## ✅ DECISIONES AUTOMÁTICAS
|
|
211
|
+
|
|
212
|
+
- Al añadir nuevas funciones o testar diferentes parámetros de configuración, siempre crear test particulares para cada escenario.
|
|
213
|
+
- Siempre mantener los tests actualizados de cualquier capa, módulo o servicio del proyecto.
|
|
214
|
+
|
|
215
|
+
### Coverage & Quality
|
|
216
|
+
```yaml
|
|
217
|
+
Tests pass: ✅ Auto-approve
|
|
218
|
+
Coverage ≥85%: ✅ Auto-approve
|
|
219
|
+
Lint errors: ❌ Block merge
|
|
220
|
+
Breaking changes: ⚠️ Require explicit approval
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Comandos
|
|
224
|
+
```bash
|
|
225
|
+
/apply # Si tests pass → commit
|
|
226
|
+
/merge # Si CI pass + coverage OK → merge to main
|
|
227
|
+
/docs # Genera/actualiza docs antes de merge
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 🔧 HERRAMIENTAS OPCIONALES
|
|
233
|
+
|
|
234
|
+
### Browser Automation (si aplica)
|
|
235
|
+
- Puppeteer para E2E tests
|
|
236
|
+
- Playwright para cross-browser
|
|
237
|
+
|
|
238
|
+
### MCP Servers (si aplica)
|
|
239
|
+
- GitHub MCP: Gestión issues/PRs
|
|
240
|
+
- Filesystem MCP: Operaciones archivo
|
|
241
|
+
- Database MCP: Queries directo
|
|
242
|
+
|
|
243
|
+
### Monitoring (producción)
|
|
244
|
+
- Sentry (errores)
|
|
245
|
+
- Plausible/Umami (analytics ligero)
|
|
246
|
+
- Uptime robot (disponibilidad)
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 🎓 DECISIONES ARQUITECTÓNICAS
|
|
251
|
+
|
|
252
|
+
### Cuándo Cambiar Stack
|
|
253
|
+
```
|
|
254
|
+
JSON DB → PostgreSQL: >1000 registros o queries complejas
|
|
255
|
+
Context API → Zustand: >5 contexts o prop drilling
|
|
256
|
+
REST → GraphQL: Frontend necesita datos muy específicos
|
|
257
|
+
Monolito → Microservicios: >3 dominios independientes
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Logging
|
|
261
|
+
```javascript
|
|
262
|
+
// Desarrollo: console.log OK
|
|
263
|
+
// Producción: winston/pino con niveles
|
|
264
|
+
logger.info('User created', { userId: 123 });
|
|
265
|
+
logger.error('DB connection failed', { error });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 🚨 EXCEPCIONES AL WORKFLOW
|
|
271
|
+
|
|
272
|
+
### Requiere Aprobación Humana
|
|
273
|
+
- Cambios en AGENTS.md
|
|
274
|
+
- Eliminación datos producción
|
|
275
|
+
- Cambios permisos/seguridad
|
|
276
|
+
- Refactorización >500 líneas
|
|
277
|
+
|
|
278
|
+
### Hotfix Critical
|
|
279
|
+
```bash
|
|
280
|
+
# Branch directo desde main
|
|
281
|
+
hotfix-{descripción}-{timestamp}
|
|
282
|
+
|
|
283
|
+
# Deploy inmediato después de tests
|
|
284
|
+
# Notificar en #incidents canal
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 📖 RECURSOS
|
|
290
|
+
|
|
291
|
+
- [Conventional Commits](https://www.conventionalcommits.org/)
|
|
292
|
+
- [Semantic Versioning](https://semver.org/)
|
|
293
|
+
- [GitHub Actions Docs](https://docs.github.com/en/actions)
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
**Última actualización:** 2026-02-15
|
|
298
|
+
**Versión:** 1.0
|
|
299
|
+
**Mantenedor:** Equipo + AI Agents
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# CODING NORMS
|
|
2
|
+
|
|
3
|
+
## Stack base
|
|
4
|
+
|
|
5
|
+
- Backend: Express puerto 3001, db.json
|
|
6
|
+
- Frontend: React + Vite puerto 3002
|
|
7
|
+
- Tests: Jest y Vitest, cobertura objetivo >= 85%
|
|
8
|
+
- Git: ramas prefijo codex-*
|
|
9
|
+
|
|
10
|
+
## Normas de desarrollo
|
|
11
|
+
|
|
12
|
+
- Crear carpeta `test/` en backend y frontend.
|
|
13
|
+
- Toda funcion nueva debe tener al menos un test asociado.
|
|
14
|
+
- Todo endpoint debe tener tests de integracion.
|
|
15
|
+
- No hacer cambios sin actualizar tests y documentacion si aplica.
|
|
16
|
+
- Validar entradas y manejar errores de forma consistente.
|
|
17
|
+
- Mantener estilos y dependencias simples.
|
|
18
|
+
|
|
19
|
+
## CI
|
|
20
|
+
|
|
21
|
+
- CI debe correr en cada push y PR.
|
|
22
|
+
- Pasos minimos: install, test backend, test frontend, coverage.
|
|
23
|
+
- El pipeline debe ejecutar `npm run test:ci` en la raiz.
|
|
24
|
+
- Rechazar merges si coverage < 85% o tests fallan.
|
|
25
|
+
|
|
26
|
+
## Documentacion
|
|
27
|
+
|
|
28
|
+
- README.md describe setup, arquitectura y endpoints.
|
|
29
|
+
- ROADMAP.md mantiene el plan de trabajo.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Setup Project
|
|
2
|
+
|
|
3
|
+
Este documento define el flujo guiado para alinear el objetivo del proyecto, el alcance y las restricciones antes de tocar documentación o arquitectura.
|
|
4
|
+
|
|
5
|
+
## Resultado acordado
|
|
6
|
+
|
|
7
|
+
Aplicación sencilla de lista de tareas con:
|
|
8
|
+
|
|
9
|
+
- CRUD básico (crear, listar, completar, eliminar)
|
|
10
|
+
- Web app con backend local
|
|
11
|
+
- Persistencia en SQLite
|
|
12
|
+
- UI cuidada aunque sea simple
|
|
13
|
+
- Sin usuarios ni autenticación
|
|
14
|
+
- Reaprovechar el código existente
|
|
15
|
+
|
|
16
|
+
## Flujo Base (Iterativo)
|
|
17
|
+
|
|
18
|
+
1. El usuario manifiesta objetivos o plan inicial.
|
|
19
|
+
2. Si hubiera documentación previa y archivos de código, el agente la analiza todo y extrae puntos clave para la visión general.
|
|
20
|
+
3. Si *NO* hubiera archivos previos (ignorar SETUP.md), el agente hace una primera pregunta abierta para entender el objetivo general del proyecto.
|
|
21
|
+
4. A partir de ahí, el agente propone **tres escenarios posibles** basados en esa información.
|
|
22
|
+
5. El usuario elige uno de los tres.
|
|
23
|
+
6. El agente plantea otro cuestionario de **tres opciones** sobre un tema específico que necesite ser clarificado o ampliado.
|
|
24
|
+
7. Repetir por **3 o 4 rondas** hasta lograr una visión general clara del proyecto y objetivos del usuario.
|
|
25
|
+
8. El agente propone un **resumen breve** del proyecto.
|
|
26
|
+
9. Si el usuario corrige, se abre una nueva ronda para aclarar y luego se actualiza el resumen.
|
|
27
|
+
10. Si el usuario acepta, el agente redacta o corrige:
|
|
28
|
+
- `README.md`
|
|
29
|
+
- `ROADMAP.md`
|
|
30
|
+
- `docs/ARCHITECTURE.md`
|
|
31
|
+
- otros documentos relevantes
|
|
32
|
+
11. El agente NO MODIFICA NI CREA CÓDIGO AUN, solo se enfoca en documentación, roadmap y arquitectura. El código se tocará en rondas posteriores.
|
|
33
|
+
|
|
34
|
+
## Reglas del agente
|
|
35
|
+
|
|
36
|
+
- Preguntas cortas y enfocadas.
|
|
37
|
+
- Siempre ofrecer **tres opciones**, siendo la primera la recomendada.
|
|
38
|
+
- Si hubiera código o documentación previa, las opciones deben basarse en esa información, siendo la primera opción la que mejor se alinea con lo ya existente.
|
|
39
|
+
- No avanzar a documentación hasta tener una visión general aceptada.
|
|
40
|
+
- Reaprovechar el código existente siempre que sea posible.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
Estas instrucciones se usan al iniciar o retomar el proyecto, o para corregir dirección.
|
|
45
|
+
|
|
46
|
+
*NUNCA* se modifica el código previo en esta etapa, solo se propone la visión general, el roadmap y la arquitectura.
|
|
47
|
+
|
|
48
|
+
|