jorgex-stack 1.9.36 → 1.9.37

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/dist/cli.js CHANGED
@@ -15,133 +15,80 @@ import {
15
15
 
16
16
  // src/cli.ts
17
17
  import * as p6 from "@clack/prompts";
18
- import fs25 from "fs";
18
+ import fs26 from "fs";
19
19
  import { pathToFileURL as pathToFileURL2 } from "url";
20
20
 
21
21
  // src/install.ts
22
- import fs15 from "fs";
23
- import path20 from "path";
22
+ import fs16 from "fs";
23
+ import path21 from "path";
24
24
  import * as p from "@clack/prompts";
25
25
 
26
- // src/adapters/opencode.ts
27
- import path6 from "path";
26
+ // src/lib/writing-style.ts
28
27
  import fs3 from "fs";
29
- import { isDeepStrictEqual } from "util";
30
- import { pathToFileURL } from "url";
31
-
32
- // src/lib/canonical.ts
33
- import path from "path";
34
- import fs from "fs";
35
- function parseCanonicalAgent(source, file) {
36
- const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
37
- if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
38
- const fields = {};
39
- for (const line of match[1].split(/\r?\n/)) {
40
- const sep = line.indexOf(":");
41
- if (sep === -1) continue;
42
- fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
43
- }
44
- const required = ["name", "description", "mode", "tier"];
45
- for (const key of required) {
46
- if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
47
- }
48
- return {
49
- name: fields.name,
50
- description: fields.description,
51
- mode: fields.mode === "primary" ? "primary" : "subagent",
52
- tier: fields.tier,
53
- readonly: fields.readonly === "true",
54
- bash: fields.bash ?? "full",
55
- spawn: fields.spawn !== "false",
56
- body: source.slice(match[0].length)
57
- };
58
- }
59
- function loadCanonicalAgents(agentsDir) {
60
- return fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
61
- const file = path.join(agentsDir, f);
62
- const source = fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
63
- return parseCanonicalAgent(source, file);
64
- });
65
- }
66
- var DEVTOOLS_MCP_SERVER = "chrome-devtools";
67
- function isCanonicalMcpServerEnabled(name, server, enabledServers) {
68
- return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
69
- }
70
- function loadCanonicalMcp(stackDir) {
71
- return JSON.parse(fs.readFileSync(path.join(stackDir, "mcp", "servers.json"), "utf8"));
72
- }
73
- function loadCanonicalHooks(stackDir) {
74
- return JSON.parse(fs.readFileSync(path.join(stackDir, "hooks", "hooks.json"), "utf8"));
75
- }
76
- function loadCanonicalDefaults(stackDir) {
77
- const file = path.join(stackDir, "config", "defaults.json");
78
- if (!fs.existsSync(file)) return {};
79
- const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
80
- delete parsed["$comment"];
81
- return parsed;
82
- }
83
-
84
- // src/lib/model-map.ts
85
28
  import path3 from "path";
86
- import { existsSync } from "fs";
87
29
 
88
- // src/lib/fsx.ts
30
+ // src/lib/backup.ts
89
31
  import fs2 from "fs";
90
32
  import path2 from "path";
33
+ import crypto from "crypto";
34
+
35
+ // src/lib/fsx.ts
36
+ import fs from "fs";
37
+ import path from "path";
91
38
  function readTextIfExists(file) {
92
39
  try {
93
- return fs2.readFileSync(file, "utf8");
40
+ return fs.readFileSync(file, "utf8");
94
41
  } catch {
95
42
  return null;
96
43
  }
97
44
  }
98
45
  function ensureDir(dir) {
99
- fs2.mkdirSync(dir, { recursive: true });
46
+ fs.mkdirSync(dir, { recursive: true });
100
47
  }
101
48
  var tmpCounter = 0;
102
49
  function tmpPath(target) {
103
- return path2.join(path2.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
50
+ return path.join(path.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
104
51
  }
105
52
  function renameInto(tmp, target) {
106
53
  try {
107
- fs2.renameSync(tmp, target);
54
+ fs.renameSync(tmp, target);
108
55
  } catch (err) {
109
- fs2.rmSync(tmp, { force: true });
56
+ fs.rmSync(tmp, { force: true });
110
57
  throw err;
111
58
  }
112
59
  }
113
- function writeText(file, content) {
114
- ensureDir(path2.dirname(file));
60
+ function writeText(file, content, mode) {
61
+ ensureDir(path.dirname(file));
115
62
  const tmp = tmpPath(file);
116
- fs2.writeFileSync(tmp, content, "utf8");
63
+ fs.writeFileSync(tmp, content, { encoding: "utf8", mode });
117
64
  renameInto(tmp, file);
118
65
  }
119
66
  function copyFile(source, target) {
120
- ensureDir(path2.dirname(target));
67
+ ensureDir(path.dirname(target));
121
68
  const tmp = tmpPath(target);
122
- fs2.copyFileSync(source, tmp);
69
+ fs.copyFileSync(source, tmp);
123
70
  renameInto(tmp, target);
124
71
  }
125
72
  function isContainedIn(child, root) {
126
- const rel = path2.relative(path2.resolve(root), path2.resolve(child));
127
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path2.sep}`) && !path2.isAbsolute(rel);
73
+ const rel = path.relative(path.resolve(root), path.resolve(child));
74
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
128
75
  }
129
76
  function pruneEmptyDirs(file, root) {
130
- let dir = path2.dirname(file);
77
+ let dir = path.dirname(file);
131
78
  while (dir.startsWith(root) && dir !== root) {
132
79
  try {
133
- if (fs2.readdirSync(dir).length > 0) return;
134
- fs2.rmdirSync(dir);
80
+ if (fs.readdirSync(dir).length > 0) return;
81
+ fs.rmdirSync(dir);
135
82
  } catch {
136
83
  return;
137
84
  }
138
- dir = path2.dirname(dir);
85
+ dir = path.dirname(dir);
139
86
  }
140
87
  }
141
88
  function sameFileContent(a, b) {
142
89
  try {
143
- const bufA = fs2.readFileSync(a);
144
- const bufB = fs2.readFileSync(b);
90
+ const bufA = fs.readFileSync(a);
91
+ const bufB = fs.readFileSync(b);
145
92
  return bufA.equals(bufB);
146
93
  } catch {
147
94
  return false;
@@ -149,10 +96,10 @@ function sameFileContent(a, b) {
149
96
  }
150
97
  function listFilesRecursive(dir) {
151
98
  const out = [];
152
- if (!fs2.existsSync(dir)) return out;
99
+ if (!fs.existsSync(dir)) return out;
153
100
  const walk = (d) => {
154
- for (const entry of fs2.readdirSync(d, { withFileTypes: true })) {
155
- const p7 = path2.join(d, entry.name);
101
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
102
+ const p7 = path.join(d, entry.name);
156
103
  if (entry.isDirectory()) walk(p7);
157
104
  else out.push(p7);
158
105
  }
@@ -161,143 +108,77 @@ function listFilesRecursive(dir) {
161
108
  return out;
162
109
  }
163
110
 
164
- // src/lib/model-map.ts
165
- function resolveAgentModel(models, agentName, tier) {
166
- const base = models[tier];
167
- const override = models.overrides?.[agentName];
168
- if (!override) return base;
169
- return {
170
- model: override.model || base.model,
171
- variant: "variant" in override ? override.variant || void 0 : base.variant
172
- };
173
- }
174
- var DEFAULT_MODEL_MAP = {
175
- "claude-code": {
176
- strong: { model: "fable" },
177
- standard: { model: "sonnet" },
178
- cheap: { model: "haiku" }
179
- },
180
- // El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
181
- // la skill de la app heredan el modelo elegido por el usuario. Estos defaults
182
- // son solo para subagentes; variant → model_reasoning_effort.
183
- codex: {
184
- strong: { model: "gpt-6-astra", variant: "low" },
185
- standard: { model: "gpt-5.6-luna", variant: "max" },
186
- cheap: { model: "gpt-5.6-luna", variant: "medium" }
187
- }
188
- };
189
- function modelMapFile() {
190
- return path3.join(dataDir(), "model-map.json");
111
+ // src/lib/backup.ts
112
+ var KEEP_BACKUPS = 10;
113
+ function backupsRoot() {
114
+ return path2.join(dataDir(), "backups");
191
115
  }
192
- function loadModelMap() {
193
- const file = modelMapFile();
194
- const raw = readTextIfExists(file);
195
- if (raw === null) return DEFAULT_MODEL_MAP;
196
- let fromDisk;
197
- try {
198
- fromDisk = JSON.parse(raw);
199
- } catch {
200
- throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
201
- }
202
- const merged = { ...fromDisk };
203
- for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
204
- merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
205
- if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
116
+ function compositeChecksum(files) {
117
+ const hash = crypto.createHash("sha256");
118
+ for (const file of [...files].sort()) {
119
+ const content = crypto.createHash("sha256").update(fs2.readFileSync(file)).digest("hex");
120
+ hash.update(`${file}:${content}
121
+ `);
206
122
  }
207
- return merged;
123
+ return hash.digest("hex");
208
124
  }
209
- function ensureModelMapFile() {
210
- const file = modelMapFile();
211
- if (!existsSync(file)) {
212
- writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
125
+ function createBackup(files, label, root = backupsRoot()) {
126
+ const existing = [...new Set(files)].filter((f) => fs2.existsSync(f));
127
+ if (existing.length === 0) return null;
128
+ const checksum = compositeChecksum(existing);
129
+ const latest = listBackups(root)[0];
130
+ if (latest?.checksum === checksum) return latest;
131
+ const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
132
+ let id = base;
133
+ let dir = path2.join(root, id);
134
+ for (let n = 1; fs2.existsSync(dir); n++) {
135
+ id = `${base}-${n}`;
136
+ dir = path2.join(root, id);
213
137
  }
214
- return file;
138
+ ensureDir(path2.join(dir, "files"));
139
+ const entries = existing.map((original, i) => {
140
+ const stored = path2.join(dir, "files", `${String(i).padStart(4, "0")}-${path2.basename(original)}`);
141
+ fs2.copyFileSync(original, stored);
142
+ return { original, stored };
143
+ });
144
+ const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
145
+ writeText(path2.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
146
+ pruneBackups(root);
147
+ return info;
215
148
  }
216
-
217
- // src/lib/detect.ts
218
- import path4 from "path";
219
- import { existsSync as existsSync2, statSync } from "fs";
220
- import { execFileSync } from "child_process";
221
- function lookPath(cmd) {
222
- const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
223
- for (const dir of (process.env.PATH ?? "").split(path4.delimiter)) {
224
- if (!dir) continue;
225
- for (const ext of exts) {
226
- const candidate = path4.join(dir, cmd + ext);
227
- if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
149
+ function listBackups(root = backupsRoot()) {
150
+ if (!fs2.existsSync(root)) return [];
151
+ const infos = [];
152
+ for (const entry of fs2.readdirSync(root, { withFileTypes: true })) {
153
+ if (!entry.isDirectory()) continue;
154
+ const manifest = readTextIfExists(path2.join(root, entry.name, "manifest.json"));
155
+ if (manifest === null) continue;
156
+ try {
157
+ infos.push(JSON.parse(manifest));
158
+ } catch {
159
+ infos.push({ id: entry.name, label: "(manifest corrupto)", createdAt: "", files: [] });
228
160
  }
229
161
  }
230
- return null;
231
- }
232
- function planDetectedBinCommand(bin, args) {
233
- if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
234
- if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
235
- const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
236
- return {
237
- command: process.env.ComSpec ?? "cmd.exe",
238
- args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
239
- };
162
+ return infos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
240
163
  }
241
- function runDetectedBin(bin, args, timeoutMs, envOverrides) {
242
- try {
243
- const command = planDetectedBinCommand(bin, args);
244
- if (command === null) return null;
245
- return execFileSync(command.command, command.args, {
246
- encoding: "utf8",
247
- timeout: timeoutMs,
248
- stdio: ["ignore", "pipe", "pipe"],
249
- ...envOverrides === void 0 ? {} : { env: { ...process.env, ...envOverrides } }
250
- });
251
- } catch {
252
- return null;
164
+ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
165
+ const info = listBackups(root).find((b) => b.id === id);
166
+ if (!info) throw new Error(`Backup no encontrado: ${id}`);
167
+ let restored = 0;
168
+ for (const { original, stored } of info.files) {
169
+ if (!fs2.existsSync(stored)) continue;
170
+ if (!isContainedIn(original, boundary)) continue;
171
+ ensureDir(path2.dirname(original));
172
+ fs2.copyFileSync(stored, original);
173
+ restored++;
253
174
  }
175
+ return restored;
254
176
  }
255
- function detectOpenCode() {
256
- const configDir = process.env.OPENCODE_CONFIG_DIR ?? path4.join(HOME, ".config", "opencode");
257
- const binPath = lookPath("opencode");
258
- return {
259
- id: "opencode",
260
- name: "OpenCode",
261
- installed: binPath !== null || existsSync2(configDir),
262
- binPath,
263
- configDir
264
- };
265
- }
266
- function detectClaudeCode() {
267
- const configDir = path4.join(HOME, ".claude");
268
- const binPath = lookPath("claude");
269
- return {
270
- id: "claude-code",
271
- name: "Claude Code",
272
- installed: binPath !== null || existsSync2(configDir),
273
- binPath,
274
- configDir
275
- };
276
- }
277
- function detectCodex() {
278
- const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
279
- const binPath = lookPath("codex");
280
- return {
281
- id: "codex",
282
- name: "Codex CLI",
283
- installed: binPath !== null || existsSync2(configDir),
284
- binPath,
285
- configDir
286
- };
287
- }
288
- function detectEngram() {
289
- const fromEnv = process.env.ENGRAM_BIN;
290
- if (fromEnv && existsSync2(fromEnv)) return fromEnv;
291
- const onPath = lookPath("engram");
292
- if (onPath) return onPath;
293
- const candidates = [
294
- path4.join(HOME, "go", "bin", "engram.exe"),
295
- path4.join(HOME, "go", "bin", "engram"),
296
- path4.join(HOME, ".local", "bin", "engram.exe"),
297
- path4.join(HOME, ".local", "bin", "engram")
298
- ];
299
- for (const c of candidates) if (existsSync2(c)) return c;
300
- return null;
177
+ function pruneBackups(root) {
178
+ const all = listBackups(root);
179
+ for (const old of all.slice(KEEP_BACKUPS)) {
180
+ fs2.rmSync(path2.join(root, old.id), { recursive: true, force: true });
181
+ }
301
182
  }
302
183
 
303
184
  // src/lib/filemerge.ts
@@ -331,7 +212,7 @@ ${close}`;
331
212
  if (start !== -1 && end !== -1 && end > start) {
332
213
  return existing.slice(0, start) + block + existing.slice(end + close.length);
333
214
  }
334
- const sep = existing.endsWith("\n") ? "\n" : "\n\n";
215
+ const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
335
216
  return existing + sep + block + "\n";
336
217
  }
337
218
  function removeMarkdownSection(existing, name) {
@@ -513,18 +394,313 @@ function removeTomlSection(existing, section) {
513
394
  if (result.trim() === "") return "";
514
395
  return result.endsWith(eol) ? result : result + eol;
515
396
  }
516
- function readTomlSection(existing, section) {
517
- if (existing === null) return null;
518
- const lines = existing.split(/\r?\n/);
519
- const found = findTomlSection(lines, section);
520
- if (found === null) return null;
521
- return lines.slice(found.start + 1, found.end).join("\n");
397
+ function readTomlSection(existing, section) {
398
+ if (existing === null) return null;
399
+ const lines = existing.split(/\r?\n/);
400
+ const found = findTomlSection(lines, section);
401
+ if (found === null) return null;
402
+ return lines.slice(found.start + 1, found.end).join("\n");
403
+ }
404
+
405
+ // src/lib/writing-style.ts
406
+ var DEFAULT_SECTION = "writing-style-default";
407
+ var DEFAULT_OPEN = `<!-- jorgex:${DEFAULT_SECTION} -->`;
408
+ var DEFAULT_CLOSE = `<!-- /jorgex:${DEFAULT_SECTION} -->`;
409
+ function assertContained(file, rootDir) {
410
+ const relative = path3.relative(fs3.realpathSync(rootDir), fs3.realpathSync(file));
411
+ if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
412
+ throw new Error(`La ruta de estilo ${file} sale del destino aislado.`);
413
+ }
414
+ }
415
+ function validateBackupRoot(rootDir) {
416
+ if (rootDir === void 0) return;
417
+ const backupRoot = path3.join(rootDir, "backups");
418
+ try {
419
+ fs3.lstatSync(backupRoot);
420
+ } catch (error) {
421
+ if (error.code === "ENOENT") return;
422
+ throw error;
423
+ }
424
+ assertContained(backupRoot, rootDir);
425
+ if (!fs3.statSync(backupRoot).isDirectory()) throw new Error(`El destino de backups ${backupRoot} no es un directorio.`);
426
+ }
427
+ function resolveWritingStyleFile(options = {}) {
428
+ return path3.join(options.targetDir ?? options.stateDir ?? dataDir(), "writing-style.md");
429
+ }
430
+ function readStyleText(sourcePath, options = {}) {
431
+ let stat;
432
+ try {
433
+ stat = fs3.lstatSync(sourcePath);
434
+ } catch (error) {
435
+ if (error.code === "ENOENT") return null;
436
+ throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
437
+ }
438
+ if (options.rootDir !== void 0) {
439
+ assertContained(sourcePath, options.rootDir);
440
+ }
441
+ if (!(stat.isSymbolicLink() ? fs3.statSync(sourcePath) : stat).isFile()) {
442
+ throw new Error(`La fuente de estilo ${sourcePath} debe ser un archivo.`);
443
+ }
444
+ let bytes;
445
+ try {
446
+ bytes = fs3.readFileSync(sourcePath);
447
+ } catch (error) {
448
+ throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
449
+ }
450
+ let content;
451
+ try {
452
+ content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
453
+ } catch {
454
+ throw new Error(`La fuente de estilo ${sourcePath} debe usar UTF-8 v\xE1lido.`);
455
+ }
456
+ return content;
457
+ }
458
+ function readWritingStyle(sourcePath, options = {}) {
459
+ const content = readStyleText(sourcePath, options)?.replace(/\r\n?/g, "\n").trim() ?? "";
460
+ if (content.includes("jorgex:")) throw new Error(`La fuente de estilo ${sourcePath} contiene marcadores jorgex: reservados.`);
461
+ return { sourcePath, content: content || null };
462
+ }
463
+ function prepareWritingStyle(sourcePath, options = {}) {
464
+ const canonicalPath = path3.join(options.stackDir ?? stackRoot(), "system-prompt", "writing-style.md");
465
+ const canonical = readWritingStyle(canonicalPath).content;
466
+ if (canonical === null) throw new Error(`Falta el estilo can\xF3nico o est\xE1 vac\xEDo en ${canonicalPath}; repara la instalaci\xF3n de Stack.`);
467
+ const originalContent = readStyleText(sourcePath, options);
468
+ if (originalContent?.includes("jorgex:")) {
469
+ if (!hasHealthyManagedMarkdownMarkers(originalContent, DEFAULT_SECTION) || originalContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").includes("jorgex:")) {
470
+ throw new Error(`Marcadores de estilo ambiguos o desconocidos en ${sourcePath}; revisa el bloque ${DEFAULT_SECTION}.`);
471
+ }
472
+ }
473
+ const installedContent = upsertMarkdownSection(originalContent, DEFAULT_SECTION, canonical);
474
+ if (originalContent !== null && originalContent !== installedContent) validateBackupRoot(options.rootDir);
475
+ const content = installedContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").replace(/\r\n?/g, "\n").trim();
476
+ return {
477
+ sourcePath,
478
+ content,
479
+ canonicalPath,
480
+ originalContent,
481
+ installedContent,
482
+ rootDir: options.rootDir,
483
+ backupRoot: options.rootDir === void 0 ? void 0 : path3.join(options.rootDir, "backups")
484
+ };
485
+ }
486
+ function applyWritingStyle(plan, dryRun = false) {
487
+ if (dryRun) return;
488
+ const current = readStyleText(plan.sourcePath, { rootDir: plan.rootDir });
489
+ if (current === plan.installedContent) return;
490
+ if (current !== plan.originalContent) {
491
+ throw new Error(`El estilo ${plan.sourcePath} cambi\xF3 durante la preparaci\xF3n; vuelve a ejecutar el comando.`);
492
+ }
493
+ if (current !== null) {
494
+ validateBackupRoot(plan.rootDir);
495
+ createBackup([plan.sourcePath], "writing-style", plan.backupRoot);
496
+ }
497
+ const mode = current === null ? 384 : fs3.statSync(plan.sourcePath).mode & 511;
498
+ writeText(plan.sourcePath, plan.installedContent, mode);
499
+ }
500
+
501
+ // src/adapters/opencode.ts
502
+ import path8 from "path";
503
+ import fs5 from "fs";
504
+ import { isDeepStrictEqual } from "util";
505
+ import { pathToFileURL } from "url";
506
+
507
+ // src/lib/canonical.ts
508
+ import path4 from "path";
509
+ import fs4 from "fs";
510
+ function parseCanonicalAgent(source, file) {
511
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
512
+ if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
513
+ const fields = {};
514
+ for (const line of match[1].split(/\r?\n/)) {
515
+ const sep = line.indexOf(":");
516
+ if (sep === -1) continue;
517
+ fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
518
+ }
519
+ const required = ["name", "description", "mode", "tier"];
520
+ for (const key of required) {
521
+ if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
522
+ }
523
+ return {
524
+ name: fields.name,
525
+ description: fields.description,
526
+ mode: fields.mode === "primary" ? "primary" : "subagent",
527
+ tier: fields.tier,
528
+ readonly: fields.readonly === "true",
529
+ bash: fields.bash ?? "full",
530
+ spawn: fields.spawn !== "false",
531
+ body: source.slice(match[0].length)
532
+ };
533
+ }
534
+ function loadCanonicalAgents(agentsDir) {
535
+ return fs4.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
536
+ const file = path4.join(agentsDir, f);
537
+ const source = fs4.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
538
+ return parseCanonicalAgent(source, file);
539
+ });
540
+ }
541
+ var DEVTOOLS_MCP_SERVER = "chrome-devtools";
542
+ function isCanonicalMcpServerEnabled(name, server, enabledServers) {
543
+ return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
544
+ }
545
+ function loadCanonicalMcp(stackDir) {
546
+ return JSON.parse(fs4.readFileSync(path4.join(stackDir, "mcp", "servers.json"), "utf8"));
547
+ }
548
+ function loadCanonicalHooks(stackDir) {
549
+ return JSON.parse(fs4.readFileSync(path4.join(stackDir, "hooks", "hooks.json"), "utf8"));
550
+ }
551
+ function loadCanonicalDefaults(stackDir) {
552
+ const file = path4.join(stackDir, "config", "defaults.json");
553
+ if (!fs4.existsSync(file)) return {};
554
+ const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
555
+ delete parsed["$comment"];
556
+ return parsed;
557
+ }
558
+
559
+ // src/lib/model-map.ts
560
+ import path5 from "path";
561
+ import { existsSync } from "fs";
562
+ function resolveAgentModel(models, agentName, tier) {
563
+ const base = models[tier];
564
+ const override = models.overrides?.[agentName];
565
+ if (!override) return base;
566
+ return {
567
+ model: override.model || base.model,
568
+ variant: "variant" in override ? override.variant || void 0 : base.variant
569
+ };
570
+ }
571
+ var DEFAULT_MODEL_MAP = {
572
+ "claude-code": {
573
+ strong: { model: "fable" },
574
+ standard: { model: "sonnet" },
575
+ cheap: { model: "haiku" }
576
+ },
577
+ // El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
578
+ // la skill de la app heredan el modelo elegido por el usuario. Estos defaults
579
+ // son solo para subagentes; variant → model_reasoning_effort.
580
+ codex: {
581
+ strong: { model: "gpt-6-astra", variant: "low" },
582
+ standard: { model: "gpt-5.6-luna", variant: "max" },
583
+ cheap: { model: "gpt-5.6-luna", variant: "medium" }
584
+ }
585
+ };
586
+ function modelMapFile() {
587
+ return path5.join(dataDir(), "model-map.json");
588
+ }
589
+ function loadModelMap() {
590
+ const file = modelMapFile();
591
+ const raw = readTextIfExists(file);
592
+ if (raw === null) return DEFAULT_MODEL_MAP;
593
+ let fromDisk;
594
+ try {
595
+ fromDisk = JSON.parse(raw);
596
+ } catch {
597
+ throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
598
+ }
599
+ const merged = { ...fromDisk };
600
+ for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
601
+ merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
602
+ if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
603
+ }
604
+ return merged;
605
+ }
606
+ function ensureModelMapFile() {
607
+ const file = modelMapFile();
608
+ if (!existsSync(file)) {
609
+ writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
610
+ }
611
+ return file;
612
+ }
613
+
614
+ // src/lib/detect.ts
615
+ import path6 from "path";
616
+ import { existsSync as existsSync2, statSync } from "fs";
617
+ import { execFileSync } from "child_process";
618
+ function lookPath(cmd) {
619
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
620
+ for (const dir of (process.env.PATH ?? "").split(path6.delimiter)) {
621
+ if (!dir) continue;
622
+ for (const ext of exts) {
623
+ const candidate = path6.join(dir, cmd + ext);
624
+ if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
625
+ }
626
+ }
627
+ return null;
628
+ }
629
+ function planDetectedBinCommand(bin, args) {
630
+ if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
631
+ if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
632
+ const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
633
+ return {
634
+ command: process.env.ComSpec ?? "cmd.exe",
635
+ args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
636
+ };
637
+ }
638
+ function runDetectedBin(bin, args, timeoutMs, envOverrides) {
639
+ try {
640
+ const command = planDetectedBinCommand(bin, args);
641
+ if (command === null) return null;
642
+ return execFileSync(command.command, command.args, {
643
+ encoding: "utf8",
644
+ timeout: timeoutMs,
645
+ stdio: ["ignore", "pipe", "pipe"],
646
+ ...envOverrides === void 0 ? {} : { env: { ...process.env, ...envOverrides } }
647
+ });
648
+ } catch {
649
+ return null;
650
+ }
651
+ }
652
+ function detectOpenCode() {
653
+ const configDir = process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode");
654
+ const binPath = lookPath("opencode");
655
+ return {
656
+ id: "opencode",
657
+ name: "OpenCode",
658
+ installed: binPath !== null || existsSync2(configDir),
659
+ binPath,
660
+ configDir
661
+ };
662
+ }
663
+ function detectClaudeCode() {
664
+ const configDir = path6.join(HOME, ".claude");
665
+ const binPath = lookPath("claude");
666
+ return {
667
+ id: "claude-code",
668
+ name: "Claude Code",
669
+ installed: binPath !== null || existsSync2(configDir),
670
+ binPath,
671
+ configDir
672
+ };
673
+ }
674
+ function detectCodex() {
675
+ const configDir = process.env.CODEX_HOME ?? path6.join(HOME, ".codex");
676
+ const binPath = lookPath("codex");
677
+ return {
678
+ id: "codex",
679
+ name: "Codex CLI",
680
+ installed: binPath !== null || existsSync2(configDir),
681
+ binPath,
682
+ configDir
683
+ };
684
+ }
685
+ function detectEngram() {
686
+ const fromEnv = process.env.ENGRAM_BIN;
687
+ if (fromEnv && existsSync2(fromEnv)) return fromEnv;
688
+ const onPath = lookPath("engram");
689
+ if (onPath) return onPath;
690
+ const candidates = [
691
+ path6.join(HOME, "go", "bin", "engram.exe"),
692
+ path6.join(HOME, "go", "bin", "engram"),
693
+ path6.join(HOME, ".local", "bin", "engram.exe"),
694
+ path6.join(HOME, ".local", "bin", "engram")
695
+ ];
696
+ for (const c of candidates) if (existsSync2(c)) return c;
697
+ return null;
522
698
  }
523
699
 
524
700
  // src/lib/hooks-format.ts
525
- import path5 from "path";
701
+ import path7 from "path";
526
702
  function hookScriptNames(canonical) {
527
- return Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
703
+ return Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path7.basename(s));
528
704
  }
529
705
  function plainHookCommands(canonical) {
530
706
  return new Set(
@@ -571,7 +747,7 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
571
747
  ...h.timeout !== void 0 ? { timeout: h.timeout } : {}
572
748
  }))
573
749
  };
574
- const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
750
+ const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path7.basename(s));
575
751
  const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
576
752
  const index = list.findIndex((existingEntry) => {
577
753
  const e = existingEntry;
@@ -715,7 +891,7 @@ function pruneEmpty(parent, key) {
715
891
  if (value !== null && Object.keys(value).length === 0) delete parent[key];
716
892
  }
717
893
  function hasOpenCodeManualApproval(configDir) {
718
- const content = readTextIfExists(path6.join(configDir, "opencode.json"));
894
+ const content = readTextIfExists(path8.join(configDir, "opencode.json"));
719
895
  if (content === null) return false;
720
896
  try {
721
897
  const root = JSON.parse(content);
@@ -731,7 +907,7 @@ var opencodeAdapter = {
731
907
  name: "OpenCode",
732
908
  detect: detectOpenCode,
733
909
  reportCapabilities(configDir) {
734
- const prompt = readTextIfExists(path6.join(configDir, "AGENTS.md"));
910
+ const prompt = readTextIfExists(path8.join(configDir, "AGENTS.md"));
735
911
  return createLocalCapabilityReport("opencode", [
736
912
  ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
737
913
  id: "policy-guidance",
@@ -753,16 +929,16 @@ var opencodeAdapter = {
753
929
  paths(configDir) {
754
930
  const isRealConfigDir = samePath(
755
931
  configDir,
756
- process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode")
932
+ process.env.OPENCODE_CONFIG_DIR ?? path8.join(HOME, ".config", "opencode")
757
933
  );
758
- const agentsHome = isRealConfigDir ? HOME : path6.dirname(configDir);
934
+ const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
759
935
  return {
760
- systemPromptFile: path6.join(configDir, "AGENTS.md"),
761
- agentsDir: path6.join(configDir, "agents"),
762
- skillsDir: path6.join(agentsHome, ".agents", "skills"),
763
- commandsDir: path6.join(configDir, "commands"),
764
- pluginsDir: path6.join(configDir, "plugins"),
765
- scriptsDir: path6.join(configDir, "scripts"),
936
+ systemPromptFile: path8.join(configDir, "AGENTS.md"),
937
+ agentsDir: path8.join(configDir, "agents"),
938
+ skillsDir: path8.join(agentsHome, ".agents", "skills"),
939
+ commandsDir: path8.join(configDir, "commands"),
940
+ pluginsDir: path8.join(configDir, "plugins"),
941
+ scriptsDir: path8.join(configDir, "scripts"),
766
942
  outputStylesDir: null,
767
943
  profilesDir: null
768
944
  };
@@ -820,12 +996,12 @@ ${agent.body}`,
820
996
  ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
821
997
  continue;
822
998
  }
823
- const script = `scripts/${path6.basename(match[1])}`;
999
+ const script = `scripts/${path8.basename(match[1])}`;
824
1000
  (bashEntries[includes] ??= []).push(script);
825
1001
  }
826
1002
  }
827
1003
  }
828
- const hooksFile = path6.join(ctx.configDir, "hooks.json");
1004
+ const hooksFile = path8.join(ctx.configDir, "hooks.json");
829
1005
  const content = upsertJson(readTextIfExists(hooksFile), (root) => {
830
1006
  const afterValue = root["tool.execute.after"] ??= {};
831
1007
  if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
@@ -860,16 +1036,16 @@ ${agent.body}`,
860
1036
  }
861
1037
  });
862
1038
  actions.push({ kind: "write", target: hooksFile, content });
863
- const scriptsSource = path6.join(ctx.stackDir, "scripts");
864
- if (fs3.existsSync(scriptsSource)) {
865
- for (const f of fs3.readdirSync(scriptsSource)) {
866
- actions.push({ kind: "copy", source: path6.join(scriptsSource, f), target: path6.join(scriptsDir, f) });
1039
+ const scriptsSource = path8.join(ctx.stackDir, "scripts");
1040
+ if (fs5.existsSync(scriptsSource)) {
1041
+ for (const f of fs5.readdirSync(scriptsSource)) {
1042
+ actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
867
1043
  }
868
1044
  }
869
1045
  return actions;
870
1046
  },
871
1047
  planMainConfig(canonical, ctx) {
872
- const file = path6.join(ctx.configDir, "opencode.json");
1048
+ const file = path8.join(ctx.configDir, "opencode.json");
873
1049
  const { pluginsDir } = this.paths(ctx.configDir);
874
1050
  const original = readTextIfExists(file);
875
1051
  const contentSource = original === null || original.trim() === "" ? null : original;
@@ -979,9 +1155,10 @@ ${agent.body}`,
979
1155
  let content = removeMarkdownSection(prompt, "system-prompt");
980
1156
  content = removeMarkdownSection(content, "engram-protocol");
981
1157
  content = removeMarkdownSection(content, "browser");
1158
+ content = removeMarkdownSection(content, "writing-style");
982
1159
  actions.push({ kind: "write", target: systemPromptFile, content });
983
1160
  }
984
- const configFile = path6.join(ctx.configDir, "opencode.json");
1161
+ const configFile = path8.join(ctx.configDir, "opencode.json");
985
1162
  const config = readTextIfExists(configFile);
986
1163
  if (config !== null) {
987
1164
  const mcpOwnership = [];
@@ -1049,7 +1226,7 @@ ${agent.body}`,
1049
1226
  ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
1050
1227
  });
1051
1228
  }
1052
- const hooksFile = path6.join(ctx.configDir, "hooks.json");
1229
+ const hooksFile = path8.join(ctx.configDir, "hooks.json");
1053
1230
  const hooksJson = readTextIfExists(hooksFile);
1054
1231
  if (hooksJson !== null) {
1055
1232
  const ourScripts = hookScriptNames(hooks);
@@ -1075,8 +1252,8 @@ ${agent.body}`,
1075
1252
  };
1076
1253
 
1077
1254
  // src/adapters/claude-code.ts
1078
- import path7 from "path";
1079
- import fs4 from "fs";
1255
+ import path9 from "path";
1256
+ import fs6 from "fs";
1080
1257
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1081
1258
  function yamlString2(value) {
1082
1259
  return JSON.stringify(value);
@@ -1092,7 +1269,7 @@ function isRecord(value) {
1092
1269
  return value !== null && typeof value === "object" && !Array.isArray(value);
1093
1270
  }
1094
1271
  function hasClaudeManualApproval(configDir) {
1095
- const content = readTextIfExists(path7.join(configDir, "settings.json"));
1272
+ const content = readTextIfExists(path9.join(configDir, "settings.json"));
1096
1273
  if (content === null) return false;
1097
1274
  try {
1098
1275
  const root = JSON.parse(content);
@@ -1112,8 +1289,8 @@ function toolsFor(agent) {
1112
1289
  return tools.join(", ");
1113
1290
  }
1114
1291
  function hasEngramPlugin(configDir) {
1115
- if (fs4.existsSync(path7.join(configDir, "plugins", "marketplaces", "engram"))) return true;
1116
- const registry = readTextIfExists(path7.join(configDir, "plugins", "installed_plugins.json"));
1292
+ if (fs6.existsSync(path9.join(configDir, "plugins", "marketplaces", "engram"))) return true;
1293
+ const registry = readTextIfExists(path9.join(configDir, "plugins", "installed_plugins.json"));
1117
1294
  if (registry === null) return false;
1118
1295
  try {
1119
1296
  const parsed = JSON.parse(registry);
@@ -1136,7 +1313,7 @@ var claudeCodeAdapter = {
1136
1313
  name: "Claude Code",
1137
1314
  detect: detectClaudeCode,
1138
1315
  reportCapabilities(configDir) {
1139
- const prompt = readTextIfExists(path7.join(configDir, "CLAUDE.md"));
1316
+ const prompt = readTextIfExists(path9.join(configDir, "CLAUDE.md"));
1140
1317
  return createLocalCapabilityReport("claude-code", [
1141
1318
  ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
1142
1319
  id: "policy-guidance",
@@ -1157,13 +1334,13 @@ var claudeCodeAdapter = {
1157
1334
  },
1158
1335
  paths(configDir) {
1159
1336
  return {
1160
- systemPromptFile: path7.join(configDir, "CLAUDE.md"),
1161
- agentsDir: path7.join(configDir, "agents"),
1162
- skillsDir: path7.join(configDir, "skills"),
1163
- commandsDir: path7.join(configDir, "commands"),
1337
+ systemPromptFile: path9.join(configDir, "CLAUDE.md"),
1338
+ agentsDir: path9.join(configDir, "agents"),
1339
+ skillsDir: path9.join(configDir, "skills"),
1340
+ commandsDir: path9.join(configDir, "commands"),
1164
1341
  pluginsDir: null,
1165
- scriptsDir: path7.join(configDir, "scripts"),
1166
- outputStylesDir: path7.join(configDir, "output-styles"),
1342
+ scriptsDir: path9.join(configDir, "scripts"),
1343
+ outputStylesDir: path9.join(configDir, "output-styles"),
1167
1344
  profilesDir: null
1168
1345
  };
1169
1346
  },
@@ -1209,9 +1386,9 @@ ${agent.body}`,
1209
1386
  planHooks(canonical, ctx) {
1210
1387
  const actions = [];
1211
1388
  const { scriptsDir } = this.paths(ctx.configDir);
1212
- const original = readTextIfExists(path7.join(ctx.configDir, "settings.json"));
1389
+ const original = readTextIfExists(path9.join(ctx.configDir, "settings.json"));
1213
1390
  const contentSource = original === null || original.trim() === "" ? null : original;
1214
- const settingsFile = path7.join(ctx.configDir, "settings.json");
1391
+ const settingsFile = path9.join(ctx.configDir, "settings.json");
1215
1392
  let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
1216
1393
  const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
1217
1394
  if (contentSource === null && defaults?.["permissions"] !== void 0) {
@@ -1225,16 +1402,16 @@ ${agent.body}`,
1225
1402
  });
1226
1403
  }
1227
1404
  actions.push({ kind: "write", target: settingsFile, content });
1228
- const scriptsSource = path7.join(ctx.stackDir, "scripts");
1229
- if (fs4.existsSync(scriptsSource)) {
1230
- for (const f of fs4.readdirSync(scriptsSource)) {
1231
- actions.push({ kind: "copy", source: path7.join(scriptsSource, f), target: path7.join(scriptsDir, f) });
1405
+ const scriptsSource = path9.join(ctx.stackDir, "scripts");
1406
+ if (fs6.existsSync(scriptsSource)) {
1407
+ for (const f of fs6.readdirSync(scriptsSource)) {
1408
+ actions.push({ kind: "copy", source: path9.join(scriptsSource, f), target: path9.join(scriptsDir, f) });
1232
1409
  }
1233
1410
  }
1234
1411
  return actions;
1235
1412
  },
1236
1413
  planMainConfig(canonical, ctx) {
1237
- const file = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
1414
+ const file = path9.join(path9.dirname(ctx.configDir), `${path9.basename(ctx.configDir)}.json`);
1238
1415
  const mcpOwnership = [];
1239
1416
  const content = upsertJson(readTextIfExists(file), (root) => {
1240
1417
  const servers = root["mcpServers"] ??= {};
@@ -1297,9 +1474,10 @@ ${agent.body}`,
1297
1474
  let content = removeMarkdownSection(prompt, "system-prompt");
1298
1475
  content = removeMarkdownSection(content, "engram-protocol");
1299
1476
  content = removeMarkdownSection(content, "browser");
1477
+ content = removeMarkdownSection(content, "writing-style");
1300
1478
  actions.push({ kind: "write", target: systemPromptFile, content });
1301
1479
  }
1302
- const settingsFile = path7.join(ctx.configDir, "settings.json");
1480
+ const settingsFile = path9.join(ctx.configDir, "settings.json");
1303
1481
  const settings = readTextIfExists(settingsFile);
1304
1482
  if (settings !== null) {
1305
1483
  const content = removeNativeHooks(settings, hooks);
@@ -1307,7 +1485,7 @@ ${agent.body}`,
1307
1485
  actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
1308
1486
  }
1309
1487
  }
1310
- const mainFile = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
1488
+ const mainFile = path9.join(path9.dirname(ctx.configDir), `${path9.basename(ctx.configDir)}.json`);
1311
1489
  const main2 = readTextIfExists(mainFile);
1312
1490
  if (main2 !== null) {
1313
1491
  const mcpOwnership = [];
@@ -1333,8 +1511,8 @@ ${agent.body}`,
1333
1511
  };
1334
1512
 
1335
1513
  // src/adapters/codex.ts
1336
- import path8 from "path";
1337
- import fs5 from "fs";
1514
+ import path10 from "path";
1515
+ import fs7 from "fs";
1338
1516
  function tomlString(value) {
1339
1517
  return JSON.stringify(value);
1340
1518
  }
@@ -1357,15 +1535,15 @@ function isManagedOptionalStdioServer3(server, section) {
1357
1535
  return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
1358
1536
  }
1359
1537
  function hasActiveEngramPlugin(configDir) {
1360
- const config = readTextIfExists(path8.join(configDir, "config.toml"));
1538
+ const config = readTextIfExists(path10.join(configDir, "config.toml"));
1361
1539
  if (config === null) return false;
1362
1540
  const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
1363
1541
  return match !== null && !/enabled\s*=\s*false/.test(match[1]);
1364
1542
  }
1365
1543
  function hasEngramProtocol(configDir) {
1366
1544
  if (hasActiveEngramPlugin(configDir)) return true;
1367
- if (fs5.existsSync(path8.join(configDir, "engram-instructions.md"))) return true;
1368
- const config = readTextIfExists(path8.join(configDir, "config.toml"));
1545
+ if (fs7.existsSync(path10.join(configDir, "engram-instructions.md"))) return true;
1546
+ const config = readTextIfExists(path10.join(configDir, "config.toml"));
1369
1547
  return config !== null && /engram-instructions\.md/.test(config);
1370
1548
  }
1371
1549
  var CODEX_JSON_STRING = String.raw`"(?:\\.|[^"\\\r\n])*"`;
@@ -1490,7 +1668,7 @@ function hasCanonicalCodexPermissionProfile(scan) {
1490
1668
  return true;
1491
1669
  }
1492
1670
  function hasCodexManualApproval(configDir) {
1493
- const config = readTextIfExists(path8.join(configDir, "config.toml"));
1671
+ const config = readTextIfExists(path10.join(configDir, "config.toml"));
1494
1672
  if (config === null) return false;
1495
1673
  const scan = scanCodexToml(config);
1496
1674
  if (scan === null) return false;
@@ -1508,7 +1686,7 @@ var codexAdapter = {
1508
1686
  name: "Codex CLI",
1509
1687
  detect: detectCodex,
1510
1688
  reportCapabilities(configDir) {
1511
- const prompt = readTextIfExists(path8.join(configDir, "AGENTS.md"));
1689
+ const prompt = readTextIfExists(path10.join(configDir, "AGENTS.md"));
1512
1690
  return createLocalCapabilityReport("codex", [
1513
1691
  ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
1514
1692
  id: "policy-guidance",
@@ -1533,18 +1711,18 @@ var codexAdapter = {
1533
1711
  return !hasEngramProtocol(ctx.configDir);
1534
1712
  },
1535
1713
  paths(configDir) {
1536
- const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path8.join(HOME, ".codex"));
1537
- const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
1538
- const skillsDir = path8.join(agentsHome, ".agents", "skills");
1714
+ const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path10.join(HOME, ".codex"));
1715
+ const agentsHome = isRealConfigDir ? HOME : path10.dirname(configDir);
1716
+ const skillsDir = path10.join(agentsHome, ".agents", "skills");
1539
1717
  return {
1540
- systemPromptFile: path8.join(configDir, "AGENTS.md"),
1541
- agentsDir: path8.join(configDir, "agents"),
1718
+ systemPromptFile: path10.join(configDir, "AGENTS.md"),
1719
+ agentsDir: path10.join(configDir, "agents"),
1542
1720
  skillsDir,
1543
1721
  // Los custom prompts de Codex están deprecados: los commands se
1544
1722
  // instalan como skills (renderCommand produce <nombre>/SKILL.md).
1545
1723
  commandsDir: skillsDir,
1546
1724
  pluginsDir: null,
1547
- scriptsDir: path8.join(configDir, "scripts"),
1725
+ scriptsDir: path10.join(configDir, "scripts"),
1548
1726
  outputStylesDir: null,
1549
1727
  profilesDir: configDir
1550
1728
  };
@@ -1592,7 +1770,7 @@ ${body}`
1592
1770
  planHooks(canonical, ctx) {
1593
1771
  const actions = [];
1594
1772
  const { scriptsDir } = this.paths(ctx.configDir);
1595
- const hooksFile = path8.join(ctx.configDir, "hooks.json");
1773
+ const hooksFile = path10.join(ctx.configDir, "hooks.json");
1596
1774
  const content = upsertNativeHooks(
1597
1775
  readTextIfExists(hooksFile),
1598
1776
  canonical,
@@ -1603,16 +1781,16 @@ ${body}`
1603
1781
  ctx.warnings.push(
1604
1782
  "Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
1605
1783
  );
1606
- const scriptsSource = path8.join(ctx.stackDir, "scripts");
1607
- if (fs5.existsSync(scriptsSource)) {
1608
- for (const f of fs5.readdirSync(scriptsSource)) {
1609
- actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
1784
+ const scriptsSource = path10.join(ctx.stackDir, "scripts");
1785
+ if (fs7.existsSync(scriptsSource)) {
1786
+ for (const f of fs7.readdirSync(scriptsSource)) {
1787
+ actions.push({ kind: "copy", source: path10.join(scriptsSource, f), target: path10.join(scriptsDir, f) });
1610
1788
  }
1611
1789
  }
1612
1790
  return actions;
1613
1791
  },
1614
1792
  planMainConfig(canonical, ctx) {
1615
- const file = path8.join(ctx.configDir, "config.toml");
1793
+ const file = path10.join(ctx.configDir, "config.toml");
1616
1794
  const original = readTextIfExists(file);
1617
1795
  const contentSource = original === null || original.trim() === "" ? null : original;
1618
1796
  let content = contentSource;
@@ -1729,9 +1907,10 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1729
1907
  let content = removeMarkdownSection(prompt, "system-prompt");
1730
1908
  content = removeMarkdownSection(content, "engram-protocol");
1731
1909
  content = removeMarkdownSection(content, "browser");
1910
+ content = removeMarkdownSection(content, "writing-style");
1732
1911
  actions.push({ kind: "write", target: systemPromptFile, content });
1733
1912
  }
1734
- const configFile = path8.join(ctx.configDir, "config.toml");
1913
+ const configFile = path10.join(ctx.configDir, "config.toml");
1735
1914
  const config = readTextIfExists(configFile);
1736
1915
  if (config !== null) {
1737
1916
  let content = config;
@@ -1766,7 +1945,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1766
1945
  ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
1767
1946
  });
1768
1947
  }
1769
- const hooksFile = path8.join(ctx.configDir, "hooks.json");
1948
+ const hooksFile = path10.join(ctx.configDir, "hooks.json");
1770
1949
  const hooksJson = readTextIfExists(hooksFile);
1771
1950
  if (hooksJson !== null) {
1772
1951
  const content = removeNativeHooks(hooksJson, hooks);
@@ -1779,8 +1958,8 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1779
1958
  };
1780
1959
 
1781
1960
  // src/lib/install-mode.ts
1782
- import fs6 from "fs";
1783
- import path9 from "path";
1961
+ import fs8 from "fs";
1962
+ import path11 from "path";
1784
1963
  var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
1785
1964
  var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
1786
1965
  var DEFAULT_INSTALL_MODE_PREFERENCE = {
@@ -1788,10 +1967,10 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
1788
1967
  subagentConcurrency: "serial"
1789
1968
  };
1790
1969
  function installModePreferenceFile() {
1791
- return path9.join(dataDir(), "install-mode.json");
1970
+ return path11.join(dataDir(), "install-mode.json");
1792
1971
  }
1793
1972
  function hasInstallModePreference(file = installModePreferenceFile()) {
1794
- return fs6.existsSync(file);
1973
+ return fs8.existsSync(file);
1795
1974
  }
1796
1975
  function isInstallMode(value) {
1797
1976
  return INSTALL_MODES.has(value);
@@ -1822,9 +2001,9 @@ function normalizeInstallModePreference(value) {
1822
2001
  throw new Error("Preferencia de instalaci\xF3n inv\xE1lida o corrupta.");
1823
2002
  }
1824
2003
  function loadInstallModePreference(file = installModePreferenceFile()) {
1825
- if (!fs6.existsSync(file)) return DEFAULT_INSTALL_MODE_PREFERENCE;
2004
+ if (!fs8.existsSync(file)) return DEFAULT_INSTALL_MODE_PREFERENCE;
1826
2005
  try {
1827
- const raw = fs6.readFileSync(file, "utf8");
2006
+ const raw = fs8.readFileSync(file, "utf8");
1828
2007
  return normalizeInstallModePreference(JSON.parse(raw));
1829
2008
  } catch (error) {
1830
2009
  const message = error instanceof Error ? error.message : String(error);
@@ -1861,87 +2040,11 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
1861
2040
  return {};
1862
2041
  }
1863
2042
 
1864
- // src/lib/backup.ts
1865
- import fs7 from "fs";
1866
- import path10 from "path";
1867
- import crypto from "crypto";
1868
- var KEEP_BACKUPS = 10;
1869
- function backupsRoot() {
1870
- return path10.join(dataDir(), "backups");
1871
- }
1872
- function compositeChecksum(files) {
1873
- const hash = crypto.createHash("sha256");
1874
- for (const file of [...files].sort()) {
1875
- const content = crypto.createHash("sha256").update(fs7.readFileSync(file)).digest("hex");
1876
- hash.update(`${file}:${content}
1877
- `);
1878
- }
1879
- return hash.digest("hex");
1880
- }
1881
- function createBackup(files, label, root = backupsRoot()) {
1882
- const existing = [...new Set(files)].filter((f) => fs7.existsSync(f));
1883
- if (existing.length === 0) return null;
1884
- const checksum = compositeChecksum(existing);
1885
- const latest = listBackups(root)[0];
1886
- if (latest?.checksum === checksum) return latest;
1887
- const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
1888
- let id = base;
1889
- let dir = path10.join(root, id);
1890
- for (let n = 1; fs7.existsSync(dir); n++) {
1891
- id = `${base}-${n}`;
1892
- dir = path10.join(root, id);
1893
- }
1894
- ensureDir(path10.join(dir, "files"));
1895
- const entries = existing.map((original, i) => {
1896
- const stored = path10.join(dir, "files", `${String(i).padStart(4, "0")}-${path10.basename(original)}`);
1897
- fs7.copyFileSync(original, stored);
1898
- return { original, stored };
1899
- });
1900
- const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
1901
- writeText(path10.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
1902
- pruneBackups(root);
1903
- return info;
1904
- }
1905
- function listBackups(root = backupsRoot()) {
1906
- if (!fs7.existsSync(root)) return [];
1907
- const infos = [];
1908
- for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
1909
- if (!entry.isDirectory()) continue;
1910
- const manifest = readTextIfExists(path10.join(root, entry.name, "manifest.json"));
1911
- if (manifest === null) continue;
1912
- try {
1913
- infos.push(JSON.parse(manifest));
1914
- } catch {
1915
- infos.push({ id: entry.name, label: "(manifest corrupto)", createdAt: "", files: [] });
1916
- }
1917
- }
1918
- return infos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1919
- }
1920
- function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
1921
- const info = listBackups(root).find((b) => b.id === id);
1922
- if (!info) throw new Error(`Backup no encontrado: ${id}`);
1923
- let restored = 0;
1924
- for (const { original, stored } of info.files) {
1925
- if (!fs7.existsSync(stored)) continue;
1926
- if (!isContainedIn(original, boundary)) continue;
1927
- ensureDir(path10.dirname(original));
1928
- fs7.copyFileSync(stored, original);
1929
- restored++;
1930
- }
1931
- return restored;
1932
- }
1933
- function pruneBackups(root) {
1934
- const all = listBackups(root);
1935
- for (const old of all.slice(KEEP_BACKUPS)) {
1936
- fs7.rmSync(path10.join(root, old.id), { recursive: true, force: true });
1937
- }
1938
- }
1939
-
1940
2043
  // src/lib/manifest.ts
1941
- import fs8 from "fs";
1942
- import path11 from "path";
2044
+ import fs9 from "fs";
2045
+ import path12 from "path";
1943
2046
  function manifestFile() {
1944
- return path11.join(dataDir(), "manifest.json");
2047
+ return path12.join(dataDir(), "manifest.json");
1945
2048
  }
1946
2049
  function readManifest(file = manifestFile()) {
1947
2050
  const raw = readTextIfExists(file);
@@ -1965,24 +2068,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
1965
2068
  writeText(file, JSON.stringify(manifest, null, 2) + "\n");
1966
2069
  }
1967
2070
  function findOrphans(prevOwned, currentTargets, root = HOME) {
1968
- return prevOwned.map((f) => path11.resolve(f)).filter(
1969
- (f) => !currentTargets.has(f) && path11.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs8.existsSync(f)
2071
+ return prevOwned.map((f) => path12.resolve(f)).filter(
2072
+ (f) => !currentTargets.has(f) && path12.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs9.existsSync(f)
1970
2073
  );
1971
2074
  }
1972
2075
 
1973
2076
  // src/components/system-prompt.ts
1974
- import path13 from "path";
1975
- import fs10 from "fs";
2077
+ import path14 from "path";
2078
+ import fs11 from "fs";
1976
2079
 
1977
2080
  // src/lib/mode-composition.ts
1978
- import fs9 from "fs";
1979
- import path12 from "path";
2081
+ import fs10 from "fs";
2082
+ import path13 from "path";
1980
2083
  var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
1981
2084
  var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
1982
2085
  var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
1983
2086
  var normalize = (value) => value.replace(/\r\n/g, "\n");
1984
2087
  function loadProgrammaticAddendum(stackDir, fileName) {
1985
- return normalize(fs9.readFileSync(path12.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
2088
+ return normalize(fs10.readFileSync(path13.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
1986
2089
  }
1987
2090
  function appendAddendum(base, addendum) {
1988
2091
  const normalizedBase = normalize(base);
@@ -2025,15 +2128,15 @@ function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
2025
2128
  var normalize2 = (s) => s.replace(/\r\n/g, "\n");
2026
2129
  function planSystemPrompt(adapter, ctx) {
2027
2130
  const target = adapter.paths(ctx.configDir).systemPromptFile;
2028
- const agentsMd = normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
2131
+ const agentsMd = normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
2029
2132
  const protocol = stripLeadingHtmlComments(
2030
- normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
2133
+ normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
2031
2134
  );
2032
2135
  const composedAgentsMd = composeProgrammaticSystemPrompt(ctx.stackDir, agentsMd, ctx.mode);
2033
2136
  const browser = [
2034
2137
  ctx.playwrightCliEnabled ? "browser-playwright.md" : null,
2035
2138
  ctx.enabledMcpServers?.has(DEVTOOLS_MCP_SERVER) ? "browser-chrome-devtools.md" : null
2036
- ].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
2139
+ ].filter((file) => file !== null).map((file) => normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
2037
2140
  let content = readTextIfExists(target);
2038
2141
  content = upsertMarkdownSection(content, "system-prompt", composedAgentsMd);
2039
2142
  if (adapter.injectEngramProtocol(ctx)) {
@@ -2042,11 +2145,13 @@ function planSystemPrompt(adapter, ctx) {
2042
2145
  content = removeMarkdownSection(content, "engram-protocol");
2043
2146
  }
2044
2147
  content = browser === "" ? removeMarkdownSection(content, "browser") : upsertMarkdownSection(content, "browser", browser);
2148
+ const style = ctx.mode === "programmatic" ? null : ctx.writingStyle?.content;
2149
+ content = style ? upsertMarkdownSection(content, "writing-style", style) : removeMarkdownSection(content, "writing-style");
2045
2150
  return [{ kind: "write", target, content }];
2046
2151
  }
2047
2152
 
2048
2153
  // src/components/agents.ts
2049
- import path14 from "path";
2154
+ import path15 from "path";
2050
2155
  function planAgents(adapter, ctx) {
2051
2156
  const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
2052
2157
  const dirFor = {
@@ -2056,7 +2161,7 @@ function planAgents(adapter, ctx) {
2056
2161
  profile: profilesDir
2057
2162
  };
2058
2163
  const scriptsBase = scriptsDir.replace(/\\/g, "/");
2059
- return loadCanonicalAgents(path14.join(ctx.stackDir, "agents")).flatMap((agent) => {
2164
+ return loadCanonicalAgents(path15.join(ctx.stackDir, "agents")).flatMap((agent) => {
2060
2165
  const composedAgent = {
2061
2166
  ...agent,
2062
2167
  body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
@@ -2068,42 +2173,42 @@ function planAgents(adapter, ctx) {
2068
2173
  return [];
2069
2174
  }
2070
2175
  const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
2071
- return [{ kind: "write", target: path14.join(dir, rendered.file), content }];
2176
+ return [{ kind: "write", target: path15.join(dir, rendered.file), content }];
2072
2177
  });
2073
2178
  });
2074
2179
  }
2075
2180
 
2076
2181
  // src/components/skills.ts
2077
- import path15 from "path";
2182
+ import path16 from "path";
2078
2183
  function planSkills(adapter, ctx) {
2079
2184
  const { skillsDir } = adapter.paths(ctx.configDir);
2080
- const source = path15.join(ctx.stackDir, "skills");
2185
+ const source = path16.join(ctx.stackDir, "skills");
2081
2186
  return listFilesRecursive(source).map((file) => {
2082
- const relative = path15.relative(source, file);
2083
- return { kind: "copy", source: file, target: path15.join(skillsDir, relative) };
2187
+ const relative = path16.relative(source, file);
2188
+ return { kind: "copy", source: file, target: path16.join(skillsDir, relative) };
2084
2189
  });
2085
2190
  }
2086
2191
 
2087
2192
  // src/components/commands.ts
2088
- import path16 from "path";
2089
- import fs11 from "fs";
2193
+ import path17 from "path";
2194
+ import fs12 from "fs";
2090
2195
  function planCommands(adapter, ctx) {
2091
2196
  const { commandsDir } = adapter.paths(ctx.configDir);
2092
- const source = path16.join(ctx.stackDir, "commands");
2093
- if (!fs11.existsSync(source)) return [];
2197
+ const source = path17.join(ctx.stackDir, "commands");
2198
+ if (!fs12.existsSync(source)) return [];
2094
2199
  const commandFiles = [
2095
2200
  ...listMarkdownFiles(source),
2096
- ...listMarkdownFiles(path16.join(source, adapter.id))
2201
+ ...listMarkdownFiles(path17.join(source, adapter.id))
2097
2202
  ];
2098
2203
  return commandFiles.map(({ file, fullPath }) => {
2099
- const raw = fs11.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
2204
+ const raw = fs12.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
2100
2205
  const rendered = adapter.renderCommand(file, raw);
2101
- return { kind: "write", target: path16.join(commandsDir, rendered.file), content: rendered.content };
2206
+ return { kind: "write", target: path17.join(commandsDir, rendered.file), content: rendered.content };
2102
2207
  });
2103
2208
  }
2104
2209
  function listMarkdownFiles(dir) {
2105
- if (!fs11.existsSync(dir)) return [];
2106
- return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path16.join(dir, entry.name) }));
2210
+ if (!fs12.existsSync(dir)) return [];
2211
+ return fs12.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path17.join(dir, entry.name) }));
2107
2212
  }
2108
2213
 
2109
2214
  // src/components/hooks.ts
@@ -2117,23 +2222,23 @@ function planMcp(adapter, ctx) {
2117
2222
  }
2118
2223
 
2119
2224
  // src/components/plugins.ts
2120
- import path17 from "path";
2121
- import fs12 from "fs";
2225
+ import path18 from "path";
2226
+ import fs13 from "fs";
2122
2227
  function planPlugins(adapter, ctx) {
2123
2228
  const { pluginsDir } = adapter.paths(ctx.configDir);
2124
2229
  if (pluginsDir === null) return [];
2125
- const source = path17.join(ctx.stackDir, "plugins", adapter.id);
2126
- if (!fs12.existsSync(source)) return [];
2230
+ const source = path18.join(ctx.stackDir, "plugins", adapter.id);
2231
+ if (!fs13.existsSync(source)) return [];
2127
2232
  return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
2128
- const target = path17.join(pluginsDir, path17.relative(source, sourceFile));
2129
- const raw = fs12.readFileSync(sourceFile, "utf8");
2233
+ const target = path18.join(pluginsDir, path18.relative(source, sourceFile));
2234
+ const raw = fs13.readFileSync(sourceFile, "utf8");
2130
2235
  let content = raw;
2131
2236
  if (content.includes('"{{ENGRAM_BIN}}"')) {
2132
2237
  content = content.replace(/"\{\{ENGRAM_BIN\}\}"/g, JSON.stringify(ctx.engramBin ?? "engram"));
2133
2238
  }
2134
2239
  if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
2135
2240
  const protocol = stripLeadingHtmlComments(
2136
- fs12.readFileSync(path17.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
2241
+ fs13.readFileSync(path18.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
2137
2242
  );
2138
2243
  content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
2139
2244
  }
@@ -2144,9 +2249,9 @@ function planPlugins(adapter, ctx) {
2144
2249
  }
2145
2250
 
2146
2251
  // src/lib/external-tools.ts
2147
- import fs13 from "fs";
2252
+ import fs14 from "fs";
2148
2253
  import os from "os";
2149
- import path18 from "path";
2254
+ import path19 from "path";
2150
2255
  import { execFileSync as execFileSync2 } from "child_process";
2151
2256
  var PLAYWRIGHT_CLI = {
2152
2257
  packageName: "@playwright/cli",
@@ -2191,9 +2296,9 @@ function detectPlaywrightCli() {
2191
2296
  if (binPath === null) {
2192
2297
  const pnpmHome = resolvePnpmHome();
2193
2298
  const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
2194
- const known = [path18.join(pnpmHome, name), path18.join(pnpmHome, "bin", name)].find((file) => {
2299
+ const known = [path19.join(pnpmHome, name), path19.join(pnpmHome, "bin", name)].find((file) => {
2195
2300
  try {
2196
- return fs13.statSync(file).isFile();
2301
+ return fs14.statSync(file).isFile();
2197
2302
  } catch {
2198
2303
  return false;
2199
2304
  }
@@ -2212,7 +2317,7 @@ function resolvePnpmBin() {
2212
2317
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
2213
2318
  }
2214
2319
  function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2215
- const paths = platform === "win32" ? path18.win32 : path18.posix;
2320
+ const paths = platform === "win32" ? path19.win32 : path19.posix;
2216
2321
  if (env.PNPM_HOME) return env.PNPM_HOME;
2217
2322
  if (env.XDG_DATA_HOME) return paths.join(env.XDG_DATA_HOME, "pnpm");
2218
2323
  if (platform === "darwin") return paths.join(homeDir, "Library", "pnpm");
@@ -2221,7 +2326,7 @@ function resolvePnpmHome(env = process.env, platform = process.platform, homeDir
2221
2326
  }
2222
2327
  function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform, homeDir = os.homedir()) {
2223
2328
  const pnpmHome = resolvePnpmHome(env, platform, homeDir);
2224
- const paths = platform === "win32" ? path18.win32 : path18.posix;
2329
+ const paths = platform === "win32" ? path19.win32 : path19.posix;
2225
2330
  if (!paths.isAbsolute(pnpmHome)) return { ok: false, reason: "PNPM_HOME debe ser una ruta absoluta; corrige la configuraci\xF3n antes de reintentar." };
2226
2331
  const pathEntries = [pnpmHome, paths.join(pnpmHome, "bin")];
2227
2332
  if (env.PATH !== void 0) pathEntries.push(env.PATH);
@@ -2241,10 +2346,10 @@ function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform
2241
2346
  }
2242
2347
  function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2243
2348
  const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
2244
- const cacheDir = configuredPath ?? (platform === "win32" ? path18.join(env.LOCALAPPDATA ?? path18.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path18.join(homeDir, "Library", "Caches", "ms-playwright") : path18.join(env.XDG_CACHE_HOME ?? path18.join(homeDir, ".cache"), "ms-playwright"));
2349
+ const cacheDir = configuredPath ?? (platform === "win32" ? path19.join(env.LOCALAPPDATA ?? path19.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path19.join(homeDir, "Library", "Caches", "ms-playwright") : path19.join(env.XDG_CACHE_HOME ?? path19.join(homeDir, ".cache"), "ms-playwright"));
2245
2350
  if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
2246
2351
  try {
2247
- const ready = fs13.readdirSync(cacheDir, { withFileTypes: true }).some(
2352
+ const ready = fs14.readdirSync(cacheDir, { withFileTypes: true }).some(
2248
2353
  (entry) => entry.isDirectory() && /^chromium(?:_headless_shell)?-/.test(entry.name)
2249
2354
  );
2250
2355
  return ready ? { status: "ready", path: cacheDir } : { status: "missing", path: cacheDir };
@@ -2276,9 +2381,9 @@ function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
2276
2381
  cwd,
2277
2382
  stdio: ["ignore", "pipe", "inherit"]
2278
2383
  }).trim();
2279
- if (!path18.isAbsolute(root)) return false;
2280
- const packageFile = fs13.realpathSync(path18.join(root, "@playwright", "cli", "package.json"));
2281
- const installed = JSON.parse(fs13.readFileSync(packageFile, "utf8"));
2384
+ if (!path19.isAbsolute(root)) return false;
2385
+ const packageFile = fs14.realpathSync(path19.join(root, "@playwright", "cli", "package.json"));
2386
+ const installed = JSON.parse(fs14.readFileSync(packageFile, "utf8"));
2282
2387
  if (installed.name !== PLAYWRIGHT_CLI.packageName || installed.version !== PLAYWRIGHT_CLI.version) return false;
2283
2388
  const probe = `
2284
2389
  const { createRequire } = require('node:module');
@@ -2321,7 +2426,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2321
2426
  const command = planPlaywrightCliCommand(action, pnpmBin);
2322
2427
  const invocation = planDetectedBinCommand(command.command, command.args);
2323
2428
  if (invocation === null) return { ok: false, reason: "pnpm-command" };
2324
- const cwd = action === "install-browser" ? fs13.mkdtempSync(path18.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
2429
+ const cwd = action === "install-browser" ? fs14.mkdtempSync(path19.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
2325
2430
  const childEnv = action === "install-browser" ? { ...env ?? process.env, NO_UPDATE_NOTIFIER: "1" } : env ?? process.env;
2326
2431
  try {
2327
2432
  execFileSync2(invocation.command, invocation.args, {
@@ -2336,27 +2441,27 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2336
2441
  } catch {
2337
2442
  return { ok: false, reason: "action-failed" };
2338
2443
  } finally {
2339
- if (cwd !== void 0) fs13.rmSync(cwd, { recursive: true, force: true });
2444
+ if (cwd !== void 0) fs14.rmSync(cwd, { recursive: true, force: true });
2340
2445
  }
2341
2446
  }
2342
2447
 
2343
2448
  // src/lib/tool-preferences.ts
2344
- import fs14 from "fs";
2345
- import path19 from "path";
2449
+ import fs15 from "fs";
2450
+ import path20 from "path";
2346
2451
  var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
2347
2452
  var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
2348
2453
  var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
2349
2454
  var PLAYWRIGHT_RUNTIMES = ["opencode", "claude-code", "codex", "pi"];
2350
2455
  function readPreference(file) {
2351
2456
  try {
2352
- return { raw: fs14.readFileSync(file, "utf8"), errorCode: null };
2457
+ return { raw: fs15.readFileSync(file, "utf8"), errorCode: null };
2353
2458
  } catch (error) {
2354
2459
  const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
2355
2460
  return code === "ENOENT" ? { raw: null, errorCode: null } : { raw: null, errorCode: code };
2356
2461
  }
2357
2462
  }
2358
2463
  function playwrightCliPreferenceFile(stateDir = dataDir()) {
2359
- return path19.join(stateDir, "playwright-cli.json");
2464
+ return path20.join(stateDir, "playwright-cli.json");
2360
2465
  }
2361
2466
  function parsePlaywrightCliPreference(raw) {
2362
2467
  try {
@@ -2402,10 +2507,10 @@ function savePlaywrightCliPreference(file, enabled, selection) {
2402
2507
  writeText(file, content);
2403
2508
  }
2404
2509
  function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
2405
- return path19.join(stateDir, "devtools-mcp.json");
2510
+ return path20.join(stateDir, "devtools-mcp.json");
2406
2511
  }
2407
2512
  function primaryModelOwnershipFile(stateDir = dataDir()) {
2408
- return path19.join(stateDir, "primary-model.json");
2513
+ return path20.join(stateDir, "primary-model.json");
2409
2514
  }
2410
2515
  function isRecord2(value) {
2411
2516
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -2515,7 +2620,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
2515
2620
  return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
2516
2621
  }
2517
2622
  function primaryModelConfigKey(configDir) {
2518
- const resolved = path19.resolve(configDir);
2623
+ const resolved = path20.resolve(configDir);
2519
2624
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
2520
2625
  }
2521
2626
  function loadPrimaryModelOwnership(file, runtime, configDir) {
@@ -2635,7 +2740,7 @@ function diffPlan(plan) {
2635
2740
  if (current === null) return { action, status: "create" };
2636
2741
  return { action, status: current === action.content ? "unchanged" : "update" };
2637
2742
  }
2638
- if (!fs15.existsSync(action.target)) return { action, status: "create" };
2743
+ if (!fs16.existsSync(action.target)) return { action, status: "create" };
2639
2744
  return { action, status: sameFileContent(action.source, action.target) ? "unchanged" : "update" };
2640
2745
  });
2641
2746
  }
@@ -2661,7 +2766,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2661
2766
  continue;
2662
2767
  }
2663
2768
  try {
2664
- for (const action of buildPlan(adapter, ctx)) targets.add(path20.resolve(action.target));
2769
+ for (const action of buildPlan(adapter, ctx)) targets.add(path21.resolve(action.target));
2665
2770
  } catch (error) {
2666
2771
  complete = false;
2667
2772
  warnings.push(
@@ -2674,6 +2779,15 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2674
2779
  async function runInstall(opts) {
2675
2780
  const showSummary = opts.showSummary !== false;
2676
2781
  if (showSummary) p.intro(`jorgex-stack ${opts.dryRun ? "install (dry-run)" : "install"}`);
2782
+ let writingStyle;
2783
+ let preparedStyle;
2784
+ try {
2785
+ if (opts.writingStyle !== void 0) writingStyle = opts.writingStyle;
2786
+ else writingStyle = preparedStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: opts.targetDir }), { rootDir: opts.targetDir });
2787
+ } catch (error) {
2788
+ p.log.error(error instanceof Error ? error.message : String(error));
2789
+ return 1;
2790
+ }
2677
2791
  const stackDir = stackRoot();
2678
2792
  const engramBin = opts.engramBin === void 0 ? detectEngram() : opts.engramBin;
2679
2793
  const modePreference = opts.mode === void 0 ? opts.targetDir === void 0 ? loadInstallModePreference() : DEFAULT_INSTALL_MODE_PREFERENCE : normalizeInstallModePreference(opts.mode);
@@ -2691,7 +2805,16 @@ async function runInstall(opts) {
2691
2805
  const projectPlaywrightPrompt = opts.dryRun && toolPlan?.persistEnabledOnSuccess === true;
2692
2806
  const hasFileRuntimes = opts.runtimes.length > 0;
2693
2807
  const modelMap = hasFileRuntimes ? loadModelMap() : {};
2694
- if (hasFileRuntimes && useManifest) ensureModelMapFile();
2808
+ if (preparedStyle !== void 0) {
2809
+ try {
2810
+ p.log.info(`Estilo de escritura: ${preparedStyle.sourcePath}${opts.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
2811
+ applyWritingStyle(preparedStyle, opts.dryRun);
2812
+ } catch (error) {
2813
+ p.log.error(error instanceof Error ? error.message : String(error));
2814
+ return 1;
2815
+ }
2816
+ }
2817
+ if (hasFileRuntimes && useManifest && !opts.dryRun) ensureModelMapFile();
2695
2818
  p.log.info(engramBin ? `Engram detectado: ${engramBin} (se respeta, D7)` : "Engram NO detectado.");
2696
2819
  const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
2697
2820
  const canOrphan = hasFileRuntimes && useManifest && current.complete;
@@ -2723,6 +2846,7 @@ async function runInstall(opts) {
2723
2846
  continue;
2724
2847
  }
2725
2848
  const ctx = {
2849
+ writingStyle,
2726
2850
  stackDir,
2727
2851
  configDir,
2728
2852
  mode: modePreference.mode,
@@ -2765,10 +2889,10 @@ async function runInstall(opts) {
2765
2889
  }
2766
2890
  const writeManifest = () => {
2767
2891
  if (!useManifest) return;
2768
- const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path20.resolve(a.target)));
2892
+ const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path21.resolve(a.target)));
2769
2893
  const keepTarget = (target) => !unmergeTargets.has(target);
2770
- const liveOwned = plan.map((a) => path20.resolve(a.target)).filter(keepTarget);
2771
- const previousOwned = (prevManifest?.owned ?? []).map((target) => path20.resolve(target)).filter(keepTarget);
2894
+ const liveOwned = plan.map((a) => path21.resolve(a.target)).filter(keepTarget);
2895
+ const previousOwned = (prevManifest?.owned ?? []).map((target) => path21.resolve(target)).filter(keepTarget);
2772
2896
  const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
2773
2897
  writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
2774
2898
  };
@@ -2797,9 +2921,9 @@ async function runInstall(opts) {
2797
2921
  const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
2798
2922
  if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
2799
2923
  applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
2800
- const pruneRoot = useManifest ? HOME : path20.dirname(configDir);
2924
+ const pruneRoot = useManifest ? HOME : path21.dirname(configDir);
2801
2925
  for (const orphan of orphans) {
2802
- fs15.rmSync(orphan, { force: true });
2926
+ fs16.rmSync(orphan, { force: true });
2803
2927
  pruneEmptyDirs(orphan, pruneRoot);
2804
2928
  }
2805
2929
  const verifyCtx = { ...ctx, warnings: [] };
@@ -2924,29 +3048,29 @@ async function runInstall(opts) {
2924
3048
  }
2925
3049
 
2926
3050
  // src/uninstall.ts
2927
- import fs17 from "fs";
2928
- import path24 from "path";
3051
+ import fs18 from "fs";
3052
+ import path25 from "path";
2929
3053
  import * as p2 from "@clack/prompts";
2930
3054
 
2931
3055
  // src/lib/pi-projection-lifecycle.ts
2932
- import fs16 from "fs";
2933
- import path23 from "path";
3056
+ import fs17 from "fs";
3057
+ import path24 from "path";
2934
3058
  import { createHash } from "crypto";
2935
3059
 
2936
3060
  // src/adapters/pi.ts
2937
- import path21 from "path";
3061
+ import path22 from "path";
2938
3062
  var piAdapter = {
2939
3063
  id: "pi",
2940
3064
  paths(configDir) {
2941
- const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path21.join(HOME, ".pi", "agent");
2942
- const agentsHome = samePath(configDir, piConfigDir) ? HOME : path21.join(path21.dirname(configDir), "home");
3065
+ const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path22.join(HOME, ".pi", "agent");
3066
+ const agentsHome = samePath(configDir, piConfigDir) ? HOME : path22.join(path22.dirname(configDir), "home");
2943
3067
  return {
2944
- systemPromptFile: path21.join(configDir, "AGENTS.md"),
2945
- agentsDir: path21.join(configDir, "agents"),
2946
- skillsDir: path21.join(agentsHome, ".agents", "skills"),
2947
- commandsDir: path21.join(configDir, "prompts"),
3068
+ systemPromptFile: path22.join(configDir, "AGENTS.md"),
3069
+ agentsDir: path22.join(configDir, "agents"),
3070
+ skillsDir: path22.join(agentsHome, ".agents", "skills"),
3071
+ commandsDir: path22.join(configDir, "prompts"),
2948
3072
  pluginsDir: null,
2949
- scriptsDir: path21.join(configDir, "scripts"),
3073
+ scriptsDir: path22.join(configDir, "scripts"),
2950
3074
  outputStylesDir: null,
2951
3075
  profilesDir: null
2952
3076
  };
@@ -2960,7 +3084,7 @@ var piAdapter = {
2960
3084
  };
2961
3085
 
2962
3086
  // src/lib/pi-package-lifecycle.ts
2963
- import path22 from "path";
3087
+ import path23 from "path";
2964
3088
  var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
2965
3089
  "foundation-contract-v1",
2966
3090
  "runner-json-v1",
@@ -3092,7 +3216,7 @@ function parseReceiptShape(receiptJson) {
3092
3216
  return null;
3093
3217
  }
3094
3218
  if (engram === void 0) return "upgrade-required";
3095
- if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path22.isAbsolute(Reflect.get(engram, "binary"))) {
3219
+ if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path23.isAbsolute(Reflect.get(engram, "binary"))) {
3096
3220
  return null;
3097
3221
  }
3098
3222
  return parsed;
@@ -3129,7 +3253,7 @@ function planPiPackageLifecycle(input) {
3129
3253
  if (input.receiptJson !== null) {
3130
3254
  const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
3131
3255
  kind: input.scope.kind,
3132
- codingAgentDir: path22.resolve(input.scope.codingAgentDir)
3256
+ codingAgentDir: path23.resolve(input.scope.codingAgentDir)
3133
3257
  }, input.engramBin);
3134
3258
  if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
3135
3259
  if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
@@ -3164,7 +3288,7 @@ function planPiPackageLifecycle(input) {
3164
3288
  },
3165
3289
  receipt: expectedReceipt(input.candidate, "installing", {
3166
3290
  kind: input.scope.kind,
3167
- codingAgentDir: path22.resolve(input.scope.codingAgentDir)
3291
+ codingAgentDir: path23.resolve(input.scope.codingAgentDir)
3168
3292
  }, input.engramBin),
3169
3293
  ownership: ownership(true)
3170
3294
  };
@@ -3179,7 +3303,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
3179
3303
  const parsed = JSON.parse(body);
3180
3304
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3181
3305
  const record = parsed;
3182
- if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path22.isAbsolute(record.package.root) || path22.resolve(packageRunner) !== path22.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
3306
+ if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path23.isAbsolute(record.package.root) || path23.resolve(packageRunner) !== path23.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
3183
3307
  return null;
3184
3308
  }
3185
3309
  return record;
@@ -3265,10 +3389,10 @@ function validateOwnedOperationState(input) {
3265
3389
  }))) {
3266
3390
  return { kind: "blocked", reason: "receipt-untrusted" };
3267
3391
  }
3268
- if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path22.resolve(receipt.scope.codingAgentDir) !== path22.resolve(input.paths.codingAgentDir)) {
3392
+ if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path23.resolve(receipt.scope.codingAgentDir) !== path23.resolve(input.paths.codingAgentDir)) {
3269
3393
  return { kind: "blocked", reason: "source-divergent" };
3270
3394
  }
3271
- if (input.engramBin !== null && path22.resolve(receipt.engram.binary) !== path22.resolve(input.engramBin)) {
3395
+ if (input.engramBin !== null && path23.resolve(receipt.engram.binary) !== path23.resolve(input.engramBin)) {
3272
3396
  return { kind: "blocked", reason: "receipt-corrupt" };
3273
3397
  }
3274
3398
  const source = receipt.candidate.package.source;
@@ -3375,7 +3499,7 @@ var RETIRED_PLAYWRIGHT_FILES = [
3375
3499
  "references/video-recording.md"
3376
3500
  ];
3377
3501
  function retiredPlaywrightFiles(scope) {
3378
- return RETIRED_PLAYWRIGHT_FILES.map((file) => path23.resolve(scope.home, ".agents", "skills", "playwright-cli", file));
3502
+ return RETIRED_PLAYWRIGHT_FILES.map((file) => path24.resolve(scope.home, ".agents", "skills", "playwright-cli", file));
3379
3503
  }
3380
3504
  function handoffEnabled(input, kind) {
3381
3505
  return (kind === "devtools" ? input.devtoolsMcpEnabled : input.playwrightHandoffEnabled) === true;
@@ -3384,18 +3508,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
3384
3508
  function projectionScope(scope) {
3385
3509
  return {
3386
3510
  ...scope,
3387
- settingsFile: path23.join(scope.codingAgentDir, "settings.json")
3511
+ settingsFile: path24.join(scope.codingAgentDir, "settings.json")
3388
3512
  };
3389
3513
  }
3390
3514
  function isInside(root, file) {
3391
- const relative = path23.relative(path23.resolve(root), path23.resolve(file));
3392
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path23.sep}`) && !path23.isAbsolute(relative);
3515
+ const relative = path24.relative(path24.resolve(root), path24.resolve(file));
3516
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path24.sep}`) && !path24.isAbsolute(relative);
3393
3517
  }
3394
3518
  function isManagedPath(scope, file) {
3395
3519
  return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
3396
3520
  }
3397
3521
  function isAllowedPath(scope, file) {
3398
- return isManagedPath(scope, file) || path23.resolve(file) === path23.resolve(scope.receiptFile);
3522
+ return isManagedPath(scope, file) || path24.resolve(file) === path24.resolve(scope.receiptFile);
3399
3523
  }
3400
3524
  function projectedPiAdapter(scope) {
3401
3525
  const paths = piAdapter.paths(scope.codingAgentDir);
@@ -3403,12 +3527,13 @@ function projectedPiAdapter(scope) {
3403
3527
  ...piAdapter,
3404
3528
  paths: () => ({
3405
3529
  ...paths,
3406
- skillsDir: path23.join(scope.home, ".agents", "skills")
3530
+ skillsDir: path24.join(scope.home, ".agents", "skills")
3407
3531
  })
3408
3532
  };
3409
3533
  }
3410
3534
  function projectionPlan(input, scope) {
3411
3535
  const ctx = {
3536
+ writingStyle: input.writingStyle,
3412
3537
  stackDir: input.stackDir,
3413
3538
  configDir: scope.codingAgentDir,
3414
3539
  engramBin: input.engramBin,
@@ -3446,20 +3571,20 @@ function projectionPlan(input, scope) {
3446
3571
  return actions;
3447
3572
  }
3448
3573
  function handoffPath(scope, kind) {
3449
- return path23.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
3574
+ return path24.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
3450
3575
  }
3451
3576
  function contentHash(content) {
3452
3577
  return createHash("sha256").update(content).digest("hex");
3453
3578
  }
3454
3579
  function canonicalActionContent(action) {
3455
- return action.kind === "write" ? action.content : fs16.readFileSync(action.source, "utf8");
3580
+ return action.kind === "write" ? action.content : fs17.readFileSync(action.source, "utf8");
3456
3581
  }
3457
3582
  function hasActionDrift(action, deps) {
3458
3583
  return deps.readText(action.target) !== canonicalActionContent(action);
3459
3584
  }
3460
3585
  function applyActions(actions, deps, handoffs = []) {
3461
3586
  for (const action of actions) {
3462
- const handoff = handoffs.find((entry) => path23.resolve(action.target) === entry.file);
3587
+ const handoff = handoffs.find((entry) => path24.resolve(action.target) === entry.file);
3463
3588
  if (handoff) {
3464
3589
  try {
3465
3590
  if (deps.readText(handoff.file) !== handoff.previous) return handoffConflict(handoff.kind, handoff.file);
@@ -3479,9 +3604,9 @@ function assertPlanContained(plan, scope) {
3479
3604
  }
3480
3605
  }
3481
3606
  function receiptFor(plan, scope) {
3482
- const systemPrompt = path23.join(scope.codingAgentDir, "AGENTS.md");
3607
+ const systemPrompt = path24.join(scope.codingAgentDir, "AGENTS.md");
3483
3608
  const owned = [...new Set(
3484
- plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
3609
+ plan.map((action) => path24.resolve(action.target)).filter((target) => target !== systemPrompt)
3485
3610
  )];
3486
3611
  const receipt = {
3487
3612
  schemaVersion: 1,
@@ -3494,7 +3619,7 @@ function receiptFor(plan, scope) {
3494
3619
  owned
3495
3620
  };
3496
3621
  for (const kind of HANDOFF_KINDS) {
3497
- const action = plan.find((entry) => path23.resolve(entry.target) === handoffPath(scope, kind));
3622
+ const action = plan.find((entry) => path24.resolve(entry.target) === handoffPath(scope, kind));
3498
3623
  if (action) receipt[kind] = { sha256: contentHash(canonicalActionContent(action)) };
3499
3624
  }
3500
3625
  return receipt;
@@ -3535,7 +3660,7 @@ function parseReceipt2(raw, expected) {
3535
3660
  if (matches(expectedOwned)) return { schemaVersion: 1, scope: expected.scope, owned: expectedOwned, ...digests };
3536
3661
  const retired = retiredPlaywrightFiles(expected.scope);
3537
3662
  if (retired.some((file) => expectedOwned.includes(file))) return null;
3538
- const skillsRoot = `${path23.resolve(expected.scope.home, ".agents", "skills")}${path23.sep}`;
3663
+ const skillsRoot = `${path24.resolve(expected.scope.home, ".agents", "skills")}${path24.sep}`;
3539
3664
  const insertion = expectedOwned.findIndex((file) => !file.startsWith(skillsRoot) || file > retired[0]);
3540
3665
  const legacyOwned = [...expectedOwned];
3541
3666
  legacyOwned.splice(insertion === -1 ? legacyOwned.length : insertion, 0, ...retired);
@@ -3554,13 +3679,13 @@ function realPiProjectionScope() {
3554
3679
  return {
3555
3680
  kind: "real",
3556
3681
  home: HOME,
3557
- codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path23.join(HOME, ".pi", "agent"),
3558
- receiptFile: path23.join(dataDir(), "pi-projection-receipt.json")
3682
+ codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path24.join(HOME, ".pi", "agent"),
3683
+ receiptFile: path24.join(dataDir(), "pi-projection-receipt.json")
3559
3684
  };
3560
3685
  }
3561
3686
  function readTextOnlyIfMissing(file) {
3562
3687
  try {
3563
- return fs16.readFileSync(file, "utf8");
3688
+ return fs17.readFileSync(file, "utf8");
3564
3689
  } catch (error) {
3565
3690
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT") return null;
3566
3691
  throw error;
@@ -3593,13 +3718,13 @@ function manifestOwned(manifest) {
3593
3718
  return new Set([
3594
3719
  ...manifest.runtimes.codex?.owned ?? [],
3595
3720
  ...manifest.runtimes.opencode?.owned ?? []
3596
- ].map((file) => path23.resolve(file)));
3721
+ ].map((file) => path24.resolve(file)));
3597
3722
  }
3598
3723
  function withoutManagedPromptSections(content) {
3599
- return ["system-prompt", "engram-protocol", "browser"].reduce((current, section) => removeMarkdownSection(current, section), content);
3724
+ return ["system-prompt", "engram-protocol", "browser", "writing-style"].reduce((current, section) => removeMarkdownSection(current, section), content);
3600
3725
  }
3601
3726
  function uniquePaths(paths) {
3602
- return [...new Set(paths.map((file) => path23.resolve(file)))];
3727
+ return [...new Set(paths.map((file) => path24.resolve(file)))];
3603
3728
  }
3604
3729
  function blocked2(reason, paths, remedy) {
3605
3730
  return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
@@ -3706,7 +3831,7 @@ function preparePiProjectionUninstall(input, deps) {
3706
3831
  }
3707
3832
  const scope = projectionScope(input.scope);
3708
3833
  const expectedReceipt2 = expectedProjectionReceipt(input, scope);
3709
- const prompt = path23.join(scope.codingAgentDir, "AGENTS.md");
3834
+ const prompt = path24.join(scope.codingAgentDir, "AGENTS.md");
3710
3835
  let existingPrompt;
3711
3836
  try {
3712
3837
  existingPrompt = deps.readText(prompt);
@@ -3714,7 +3839,7 @@ function preparePiProjectionUninstall(input, deps) {
3714
3839
  return cleanupFailure([prompt]);
3715
3840
  }
3716
3841
  const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
3717
- const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path23.resolve(prompt), content: promptContent };
3842
+ const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path24.resolve(prompt), content: promptContent };
3718
3843
  const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
3719
3844
  if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3720
3845
  let ownedToRemove = [];
@@ -3732,7 +3857,7 @@ function preparePiProjectionUninstall(input, deps) {
3732
3857
  return cleanupFailure([scope.receiptFile]);
3733
3858
  }
3734
3859
  ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
3735
- receiptFile = path23.resolve(scope.receiptFile);
3860
+ receiptFile = path24.resolve(scope.receiptFile);
3736
3861
  backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
3737
3862
  }
3738
3863
  const handoffs = [];
@@ -3810,10 +3935,10 @@ function runPiProjectionLifecycle(input, deps) {
3810
3935
  const prepared = preparePiProjectionUninstall(input, deps);
3811
3936
  return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
3812
3937
  }
3813
- if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path23.isAbsolute(input.pnpmBin))) {
3938
+ if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path24.isAbsolute(input.pnpmBin))) {
3814
3939
  return blocked2("projection-devtools-command", [handoffPath(scope, "devtools")], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
3815
3940
  }
3816
- if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path23.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
3941
+ if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path24.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
3817
3942
  return blocked2("projection-playwright-command", [handoffPath(scope, "playwright")], "Playwright requiere un ejecutable absoluto verificado. Abre una terminal nueva tras pnpm setup y reintenta install --playwright.");
3818
3943
  }
3819
3944
  const plan = projectionPlan(input, scope);
@@ -3826,12 +3951,12 @@ function runPiProjectionLifecycle(input, deps) {
3826
3951
  const expectedReceipt2 = receiptContent(receipt);
3827
3952
  const drifted = plan.filter((action) => hasActionDrift(action, deps));
3828
3953
  for (const { kind, file } of checked.handoffs) {
3829
- if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path23.resolve(action.target) === file)) {
3954
+ if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path24.resolve(action.target) === file)) {
3830
3955
  return input.operation === "doctor" ? { kind: "drift", paths: [file] } : handoffConflict(kind, file);
3831
3956
  }
3832
3957
  }
3833
3958
  if (input.operation === "doctor") {
3834
- const paths = drifted.map((action) => path23.resolve(action.target));
3959
+ const paths = drifted.map((action) => path24.resolve(action.target));
3835
3960
  paths.push(...removedHandoffs.map(({ file }) => file), ...retiredSkills);
3836
3961
  const currentSettings2 = deps.readText(scope.settingsFile);
3837
3962
  if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
@@ -3890,16 +4015,17 @@ function runPiProjectionLifecycle(input, deps) {
3890
4015
  return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0 };
3891
4016
  }
3892
4017
  function systemProjectionLifecycle(input) {
3893
- const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
4018
+ const targetRoot = input.targetDir === void 0 ? null : path24.resolve(input.targetDir);
3894
4019
  const scope = targetRoot === null ? realPiProjectionScope() : {
3895
4020
  kind: "target-dir",
3896
- home: path23.join(targetRoot, "home"),
3897
- codingAgentDir: path23.join(targetRoot, "pi-agent"),
3898
- receiptFile: path23.join(targetRoot, "state", "pi-projection-receipt.json")
4021
+ home: path24.join(targetRoot, "home"),
4022
+ codingAgentDir: path24.join(targetRoot, "pi-agent"),
4023
+ receiptFile: path24.join(targetRoot, "state", "pi-projection-receipt.json")
3899
4024
  };
3900
4025
  return {
3901
4026
  input: {
3902
4027
  operation: input.operation,
4028
+ writingStyle: input.writingStyle,
3903
4029
  scope,
3904
4030
  packageSource: input.packageSource,
3905
4031
  stackDir: stackRoot(),
@@ -3915,14 +4041,14 @@ function systemProjectionLifecycle(input) {
3915
4041
  backup: (paths) => createBackup(
3916
4042
  paths,
3917
4043
  `pi-projection-${input.operation}`,
3918
- targetRoot === null ? void 0 : path23.join(targetRoot, "backups")
4044
+ targetRoot === null ? void 0 : path24.join(targetRoot, "backups")
3919
4045
  ),
3920
4046
  writeText,
3921
4047
  copyFile,
3922
4048
  removeFile: (file) => {
3923
- fs16.rmSync(file, { force: true });
3924
- if (retiredPlaywrightFiles(scope).includes(path23.resolve(file))) {
3925
- pruneEmptyDirs(file, path23.join(scope.home, ".agents", "skills"));
4049
+ fs17.rmSync(file, { force: true });
4050
+ if (retiredPlaywrightFiles(scope).includes(path24.resolve(file))) {
4051
+ pruneEmptyDirs(file, path24.join(scope.home, ".agents", "skills"));
3926
4052
  }
3927
4053
  },
3928
4054
  readManifest: targetRoot === null ? readManifest : () => ({ runtimes: {} })
@@ -4001,7 +4127,7 @@ async function runUninstall(opts) {
4001
4127
  if (!detection.installed) continue;
4002
4128
  const keepCtx = makeContext(keep, detection.configDir);
4003
4129
  if (!keepCtx) continue;
4004
- for (const action of buildPlan(keep, keepCtx)) retained.add(path24.resolve(action.target));
4130
+ for (const action of buildPlan(keep, keepCtx)) retained.add(path25.resolve(action.target));
4005
4131
  }
4006
4132
  }
4007
4133
  for (const id of opts.runtimes) {
@@ -4020,15 +4146,15 @@ async function runUninstall(opts) {
4020
4146
  if (!ctx) continue;
4021
4147
  ctx.preserveEngram = !removeEngram;
4022
4148
  const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
4023
- const mergedTargets = new Set(unmerge.map((a) => path24.resolve(a.target)));
4149
+ const mergedTargets = new Set(unmerge.map((a) => path25.resolve(a.target)));
4024
4150
  const usingRealConfig = opts.targetDir === void 0;
4025
4151
  const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
4026
- const pruneRoot = usingRealConfig ? HOME : path24.dirname(configDir);
4152
+ const pruneRoot = usingRealConfig ? HOME : path25.dirname(configDir);
4027
4153
  const planTargets = [
4028
- .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path24.resolve(a.target)), ...prevOwned.map((t) => path24.resolve(t))])
4029
- ].filter((t) => !mergedTargets.has(t) && fs17.existsSync(t));
4154
+ .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path25.resolve(a.target)), ...prevOwned.map((t) => path25.resolve(t))])
4155
+ ].filter((t) => !mergedTargets.has(t) && fs18.existsSync(t));
4030
4156
  const deleteTargets = planTargets.filter(
4031
- (t) => !retained.has(t) && !(ctx.preserveEngram && path24.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
4157
+ (t) => !retained.has(t) && !(ctx.preserveEngram && path25.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
4032
4158
  );
4033
4159
  const sharedKept = planTargets.length - deleteTargets.length;
4034
4160
  p2.log.step(`${adapter.name} \u2192 ${configDir}`);
@@ -4038,7 +4164,7 @@ async function runUninstall(opts) {
4038
4164
  let backup;
4039
4165
  try {
4040
4166
  backup = createBackup(
4041
- [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs17.existsSync(t))],
4167
+ [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs18.existsSync(t))],
4042
4168
  `uninstall-${id}`
4043
4169
  );
4044
4170
  } catch (error) {
@@ -4048,13 +4174,13 @@ async function runUninstall(opts) {
4048
4174
  }
4049
4175
  if (backup) p2.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
4050
4176
  for (const target of deleteTargets) {
4051
- fs17.rmSync(target, { force: true });
4177
+ fs18.rmSync(target, { force: true });
4052
4178
  pruneEmptyDirs(target, pruneRoot);
4053
4179
  }
4054
4180
  for (const action of unmerge) {
4055
4181
  if (action.kind !== "write") continue;
4056
4182
  if (action.content.trim() === "") {
4057
- fs17.rmSync(action.target, { force: true });
4183
+ fs18.rmSync(action.target, { force: true });
4058
4184
  } else {
4059
4185
  writeText(action.target, action.content);
4060
4186
  }
@@ -4108,9 +4234,17 @@ async function runUninstall(opts) {
4108
4234
  }
4109
4235
 
4110
4236
  // src/doctor.ts
4111
- import path25 from "path";
4112
- import fs18 from "fs";
4237
+ import path26 from "path";
4238
+ import fs19 from "fs";
4113
4239
  import * as p3 from "@clack/prompts";
4240
+ function readDoctorTextIfExists(file) {
4241
+ try {
4242
+ return fs19.readFileSync(file, "utf8");
4243
+ } catch (error) {
4244
+ if (error.code === "ENOENT") return null;
4245
+ throw error;
4246
+ }
4247
+ }
4114
4248
  function engramVersion(bin) {
4115
4249
  const out = runDetectedBin(bin, ["--version"], 5e3);
4116
4250
  if (out === null) return null;
@@ -4129,16 +4263,71 @@ function resolvePlaywrightDoctorState(input) {
4129
4263
  return { status: "healthy" };
4130
4264
  }
4131
4265
  function context7KeyConfigured(id, configDir) {
4132
- const file = id === "codex" ? path25.join(configDir, "config.toml") : id === "claude-code" ? path25.join(path25.dirname(configDir), `${path25.basename(configDir)}.json`) : path25.join(configDir, "opencode.json");
4266
+ const file = id === "codex" ? path26.join(configDir, "config.toml") : id === "claude-code" ? path26.join(path26.dirname(configDir), `${path26.basename(configDir)}.json`) : path26.join(configDir, "opencode.json");
4133
4267
  const content = readTextIfExists(file);
4134
4268
  if (content === null) return null;
4135
4269
  const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
4136
4270
  if (!match) return null;
4137
4271
  return match[1] !== "";
4138
4272
  }
4139
- async function runDoctor() {
4140
- p3.intro("jorgex-stack doctor");
4273
+ function reportWritingStyle(options, style, mode) {
4141
4274
  let problems = 0;
4275
+ p3.log.info(`Estilo de escritura incluido: ${style.canonicalPath}; fuente local ${style.sourcePath}; tama\xF1o ${Buffer.byteLength(style.content, "utf8")} bytes de texto normalizado. La carga nativa no est\xE1 verificada.`);
4276
+ if (style.originalContent === style.installedContent) p3.log.success("Archivo local de estilo actualizado con el canon incluido.");
4277
+ else {
4278
+ p3.log.warn(`Archivo local de estilo ${style.originalContent === null ? "pendiente de instalar" : "desactualizado; pendiente de sincronizar"}; ejecuta install o sync (${style.sourcePath}).`);
4279
+ problems++;
4280
+ }
4281
+ const expected = mode.mode !== "programmatic" ? upsertMarkdownSection(null, "writing-style", style.content).trim() : null;
4282
+ const runtimes = options.runtimes ?? Object.values(ADAPTERS).filter((adapter) => options.targetDir !== void 0 || adapter.detect().installed).map((adapter) => adapter.id);
4283
+ for (const id of runtimes) {
4284
+ const adapter = id === "pi" ? piAdapter : ADAPTERS[id];
4285
+ if (adapter === void 0) continue;
4286
+ const configDir = id === "pi" ? options.targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path26.join(HOME, ".pi", "agent") : path26.join(options.targetDir, "pi-agent") : options.targetDir ?? ADAPTERS[id].detect().configDir;
4287
+ const file = adapter.paths(configDir).systemPromptFile;
4288
+ try {
4289
+ const content = readDoctorTextIfExists(file) ?? "";
4290
+ const open = "<!-- jorgex:writing-style -->";
4291
+ const close = "<!-- /jorgex:writing-style -->";
4292
+ const healthy = hasHealthyManagedMarkdownMarkers(content, "writing-style");
4293
+ const block = healthy ? content.slice(content.indexOf(open), content.indexOf(close) + close.length) : null;
4294
+ const matches = expected === null ? !content.includes(open) && !content.includes(close) : healthy && block === expected;
4295
+ if (matches) p3.log.success(`${id}: proyecci\xF3n de estilo coincide (${file})${mode.mode === "programmatic" ? "; omitida en modo programmatic" : ""}.`);
4296
+ else {
4297
+ p3.log.warn(`${id}: proyecci\xF3n de estilo desactualizada o ausente (${file}); ejecuta sync.`);
4298
+ problems++;
4299
+ }
4300
+ if (id === "codex") {
4301
+ const override = path26.join(configDir, "AGENTS.override.md");
4302
+ if ((readDoctorTextIfExists(override) ?? "").trim() !== "") {
4303
+ p3.log.warn(`Codex: ${override} no vac\xEDo puede ocultar el AGENTS.md gestionado; rev\xEDsalo sin modificarlo autom\xE1ticamente.`);
4304
+ problems++;
4305
+ }
4306
+ }
4307
+ } catch {
4308
+ p3.log.error(`${id}: no se puede leer la proyecci\xF3n de estilo o su override en ${configDir}; revisa archivos y permisos.`);
4309
+ problems++;
4310
+ }
4311
+ }
4312
+ return problems;
4313
+ }
4314
+ async function runDoctor(options = {}) {
4315
+ p3.intro("jorgex-stack doctor");
4316
+ let writingStyle;
4317
+ let modePreference;
4318
+ try {
4319
+ writingStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: options.targetDir }), { rootDir: options.targetDir });
4320
+ modePreference = options.mode ?? (options.targetDir === void 0 ? loadInstallModePreference() : DEFAULT_INSTALL_MODE_PREFERENCE);
4321
+ } catch (error) {
4322
+ p3.log.error(error instanceof Error ? error.message : String(error));
4323
+ p3.outro("No se puede diagnosticar el estilo; corrige la fuente o la preferencia indicada.");
4324
+ return 1;
4325
+ }
4326
+ let problems = reportWritingStyle(options, writingStyle, modePreference);
4327
+ if (options.targetDir !== void 0 || options.runtimes !== void 0 && options.runtimes.length > 0 && options.runtimes.every((id) => id === "pi")) {
4328
+ p3.outro("Diagn\xF3stico limitado al estilo; no se han ejecutado las comprobaciones globales del sistema.");
4329
+ return problems > 0 ? 1 : 0;
4330
+ }
4142
4331
  const engramBin = detectEngram();
4143
4332
  if (engramBin === null) {
4144
4333
  p3.log.warn("Engram: NO detectado. El protocolo de memoria no funcionar\xE1 \u2014 inst\xE1lalo: github.com/Gentleman-Programming/engram");
@@ -4152,13 +4341,13 @@ async function runDoctor() {
4152
4341
  p3.log.success(`Engram: ${version} (${engramBin})`);
4153
4342
  }
4154
4343
  }
4155
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path25.join(HOME, ".engram");
4156
- const engramDb = path25.join(engramDataDir, "engram.db");
4157
- if (fs18.existsSync(engramDb)) {
4158
- const sizeMb = (fs18.statSync(engramDb).size / 1024 / 1024).toFixed(1);
4344
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path26.join(HOME, ".engram");
4345
+ const engramDb = path26.join(engramDataDir, "engram.db");
4346
+ if (fs19.existsSync(engramDb)) {
4347
+ const sizeMb = (fs19.statSync(engramDb).size / 1024 / 1024).toFixed(1);
4159
4348
  p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
4160
4349
  }
4161
- if (!fs18.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
4350
+ if (!fs19.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
4162
4351
  const preferenceErrors = browserPreferenceErrors();
4163
4352
  const primaryOwnershipError = primaryModelOwnershipError();
4164
4353
  if (primaryOwnershipError !== null) {
@@ -4199,7 +4388,6 @@ async function runDoctor() {
4199
4388
  }
4200
4389
  }
4201
4390
  const manifest = readManifest();
4202
- const modePreference = loadInstallModePreference();
4203
4391
  const current = collectAllCurrentTargets(modePreference);
4204
4392
  if (!current.complete || current.warnings.length > 0) {
4205
4393
  p3.log.warn("Limpieza de hu\xE9rfanos deshabilitada: no se pudo construir el plan completo de todos los runtimes.");
@@ -4207,6 +4395,7 @@ async function runDoctor() {
4207
4395
  problems++;
4208
4396
  }
4209
4397
  for (const adapter of Object.values(ADAPTERS)) {
4398
+ if (options.runtimes !== void 0 && !options.runtimes.includes(adapter.id)) continue;
4210
4399
  const detection = adapter.detect();
4211
4400
  if (!detection.installed) {
4212
4401
  p3.log.warn(`${adapter.name}: no instalado en esta m\xE1quina.`);
@@ -4217,6 +4406,7 @@ async function runDoctor() {
4217
4406
  p3.log.info(`${adapter.name}: capabilities diagnostic (${capabilitySummary}); no certifica enforcement local.`);
4218
4407
  const ctx = makeContext(adapter, detection.configDir, modePreference);
4219
4408
  if (!ctx) continue;
4409
+ ctx.writingStyle = writingStyle;
4220
4410
  let pending;
4221
4411
  try {
4222
4412
  pending = diffPlan(buildPlan(adapter, ctx)).filter((d) => d.status !== "unchanged").length;
@@ -4239,15 +4429,9 @@ async function runDoctor() {
4239
4429
  p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
4240
4430
  problems++;
4241
4431
  }
4242
- if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "hooks.json"))) {
4432
+ if (adapter.id === "codex" && fs19.existsSync(path26.join(detection.configDir, "hooks.json"))) {
4243
4433
  p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
4244
4434
  }
4245
- if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "AGENTS.override.md"))) {
4246
- p3.log.warn(
4247
- "Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
4248
- );
4249
- problems++;
4250
- }
4251
4435
  const key = context7KeyConfigured(adapter.id, detection.configDir);
4252
4436
  if (key === false) p3.log.info(`${adapter.name}: context7 sin key (opcional \u2014 con\xE9ctala cuando quieras).`);
4253
4437
  }
@@ -4256,15 +4440,15 @@ async function runDoctor() {
4256
4440
  }
4257
4441
 
4258
4442
  // src/update.ts
4259
- import fs21 from "fs";
4260
- import path28 from "path";
4443
+ import fs22 from "fs";
4444
+ import path29 from "path";
4261
4445
  import os3 from "os";
4262
4446
  import { execFileSync as execFileSync5 } from "child_process";
4263
4447
  import * as p4 from "@clack/prompts";
4264
4448
 
4265
4449
  // src/lib/github.ts
4266
- import fs19 from "fs";
4267
- import path26 from "path";
4450
+ import fs20 from "fs";
4451
+ import path27 from "path";
4268
4452
  import { execFileSync as execFileSync3 } from "child_process";
4269
4453
  import os2 from "os";
4270
4454
  import { Readable } from "stream";
@@ -4334,19 +4518,19 @@ async function latestGithubCommit(repo) {
4334
4518
  }
4335
4519
  }
4336
4520
  function validateExtractedTree(destDir) {
4337
- const resolved = path26.resolve(destDir);
4521
+ const resolved = path27.resolve(destDir);
4338
4522
  const walk = (dir) => {
4339
4523
  let entries;
4340
4524
  try {
4341
- entries = fs19.readdirSync(dir, { withFileTypes: true });
4525
+ entries = fs20.readdirSync(dir, { withFileTypes: true });
4342
4526
  } catch {
4343
4527
  return false;
4344
4528
  }
4345
4529
  for (const entry of entries) {
4346
- const full = path26.join(dir, entry.name);
4530
+ const full = path27.join(dir, entry.name);
4347
4531
  let stat;
4348
4532
  try {
4349
- stat = fs19.lstatSync(full);
4533
+ stat = fs20.lstatSync(full);
4350
4534
  } catch {
4351
4535
  return false;
4352
4536
  }
@@ -4362,15 +4546,15 @@ function validateExtractedTree(destDir) {
4362
4546
  }
4363
4547
  function resolveTarBin() {
4364
4548
  if (process.platform !== "win32") return "tar";
4365
- const winTar = path26.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
4366
- return fs19.existsSync(winTar) ? winTar : "tar";
4549
+ const winTar = path27.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
4550
+ return fs20.existsSync(winTar) ? winTar : "tar";
4367
4551
  }
4368
4552
  async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
4369
4553
  const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
4370
- const tmp = path26.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
4554
+ const tmp = path27.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
4371
4555
  const fail = (reason) => {
4372
4556
  try {
4373
- fs19.rmSync(destDir, { recursive: true, force: true });
4557
+ fs20.rmSync(destDir, { recursive: true, force: true });
4374
4558
  } catch {
4375
4559
  }
4376
4560
  return { ok: false, reason };
@@ -4389,10 +4573,10 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
4389
4573
  if (!res.body) return fail("respuesta HTTP sin cuerpo");
4390
4574
  await pipeline(
4391
4575
  Readable.fromWeb(res.body),
4392
- fs19.createWriteStream(tmp)
4576
+ fs20.createWriteStream(tmp)
4393
4577
  );
4394
- fs19.rmSync(destDir, { recursive: true, force: true });
4395
- fs19.mkdirSync(destDir, { recursive: true });
4578
+ fs20.rmSync(destDir, { recursive: true, force: true });
4579
+ fs20.mkdirSync(destDir, { recursive: true });
4396
4580
  try {
4397
4581
  execFileSync3(resolveTarBin(), ["-xzf", tmp, "--strip-components=1", "-C", destDir], { stdio: "pipe" });
4398
4582
  } catch (err) {
@@ -4400,30 +4584,30 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
4400
4584
  const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
4401
4585
  return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
4402
4586
  }
4403
- const resolvedDest = path26.resolve(destDir);
4404
- const validateRoot = validateSubdir ? path26.resolve(resolvedDest, validateSubdir) : resolvedDest;
4587
+ const resolvedDest = path27.resolve(destDir);
4588
+ const validateRoot = validateSubdir ? path27.resolve(resolvedDest, validateSubdir) : resolvedDest;
4405
4589
  if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
4406
4590
  return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
4407
4591
  }
4408
- if (fs19.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
4592
+ if (fs20.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
4409
4593
  return fail("el \xE1rbol extra\xEDdo contiene symlinks o rutas fuera del destino");
4410
4594
  }
4411
- const validated = fs19.existsSync(validateRoot);
4595
+ const validated = fs20.existsSync(validateRoot);
4412
4596
  return { ok: true, validated };
4413
4597
  } catch (err) {
4414
4598
  const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
4415
4599
  return fail(timedOut ? "timeout de descarga (120s)" : err instanceof Error ? `fallo de red: ${err.message}` : "error desconocido");
4416
4600
  } finally {
4417
4601
  try {
4418
- fs19.rmSync(tmp, { force: true });
4602
+ fs20.rmSync(tmp, { force: true });
4419
4603
  } catch {
4420
4604
  }
4421
4605
  }
4422
4606
  }
4423
4607
 
4424
4608
  // src/lib/skill-update.ts
4425
- import fs20 from "fs";
4426
- import path27 from "path";
4609
+ import fs21 from "fs";
4610
+ import path28 from "path";
4427
4611
  import { execFileSync as execFileSync4 } from "child_process";
4428
4612
  var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
4429
4613
  "agent-delegation",
@@ -4434,8 +4618,8 @@ var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
4434
4618
  "xreview"
4435
4619
  ]);
4436
4620
  function sameTextContentNormalized(a, b) {
4437
- const ba = fs20.readFileSync(a);
4438
- const bb = fs20.readFileSync(b);
4621
+ const ba = fs21.readFileSync(a);
4622
+ const bb = fs21.readFileSync(b);
4439
4623
  if (ba.equals(bb)) return true;
4440
4624
  const sa = ba.toString("utf8").replace(/\r\n/g, "\n");
4441
4625
  const sb = bb.toString("utf8").replace(/\r\n/g, "\n");
@@ -4443,10 +4627,10 @@ function sameTextContentNormalized(a, b) {
4443
4627
  }
4444
4628
  function diffSkillDirs(upstreamDir, localDir) {
4445
4629
  const upstreamFiles = new Set(
4446
- listFilesRecursive(upstreamDir).map((f) => path27.relative(upstreamDir, f))
4630
+ listFilesRecursive(upstreamDir).map((f) => path28.relative(upstreamDir, f))
4447
4631
  );
4448
4632
  const localFiles = new Set(
4449
- listFilesRecursive(localDir).map((f) => path27.relative(localDir, f))
4633
+ listFilesRecursive(localDir).map((f) => path28.relative(localDir, f))
4450
4634
  );
4451
4635
  const added = [];
4452
4636
  const modified = [];
@@ -4454,7 +4638,7 @@ function diffSkillDirs(upstreamDir, localDir) {
4454
4638
  for (const rel of upstreamFiles) {
4455
4639
  if (!localFiles.has(rel)) {
4456
4640
  added.push(rel);
4457
- } else if (!sameTextContentNormalized(path27.join(upstreamDir, rel), path27.join(localDir, rel))) {
4641
+ } else if (!sameTextContentNormalized(path28.join(upstreamDir, rel), path28.join(localDir, rel))) {
4458
4642
  modified.push(rel);
4459
4643
  }
4460
4644
  }
@@ -4516,8 +4700,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
4516
4700
  if (PROTECTED_SKILLS.has(name)) {
4517
4701
  throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
4518
4702
  }
4519
- const upstreamsFile = upstreamsFilePath ?? path27.join(path27.dirname(stackRoot()), "upstreams.json");
4520
- const raw = fs20.readFileSync(upstreamsFile, "utf8");
4703
+ const upstreamsFile = upstreamsFilePath ?? path28.join(path28.dirname(stackRoot()), "upstreams.json");
4704
+ const raw = fs21.readFileSync(upstreamsFile, "utf8");
4521
4705
  const data = JSON.parse(raw);
4522
4706
  const skillEntry = data?.skills?.[name];
4523
4707
  if (!skillEntry) {
@@ -4526,8 +4710,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
4526
4710
  if (skillEntry.kind === "release") {
4527
4711
  throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
4528
4712
  }
4529
- const skillsRoot = localSkillsRoot ?? path27.join(stackRoot(), "skills");
4530
- const localSkillDir = path27.join(skillsRoot, name);
4713
+ const skillsRoot = localSkillsRoot ?? path28.join(stackRoot(), "skills");
4714
+ const localSkillDir = path28.join(skillsRoot, name);
4531
4715
  const localFiles = listFilesRecursive(localSkillDir);
4532
4716
  if (localFiles.length > 0) {
4533
4717
  createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
@@ -4536,24 +4720,24 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
4536
4720
  try {
4537
4721
  const upstreamFiles = listFilesRecursive(upstreamSkillDir);
4538
4722
  for (const src of upstreamFiles) {
4539
- const st = fs20.lstatSync(src);
4723
+ const st = fs21.lstatSync(src);
4540
4724
  if (st.isSymbolicLink()) {
4541
4725
  throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
4542
4726
  }
4543
- const rel = path27.relative(upstreamSkillDir, src);
4544
- const dest = path27.join(stagingDir, rel);
4545
- ensureDir(path27.dirname(dest));
4727
+ const rel = path28.relative(upstreamSkillDir, src);
4728
+ const dest = path28.join(stagingDir, rel);
4729
+ ensureDir(path28.dirname(dest));
4546
4730
  copyFile(src, dest);
4547
4731
  }
4548
4732
  const oldDir = `${localSkillDir}.old-${process.pid}`;
4549
- if (fs20.existsSync(localSkillDir)) {
4550
- fs20.renameSync(localSkillDir, oldDir);
4733
+ if (fs21.existsSync(localSkillDir)) {
4734
+ fs21.renameSync(localSkillDir, oldDir);
4551
4735
  }
4552
- fs20.renameSync(stagingDir, localSkillDir);
4553
- fs20.rmSync(oldDir, { recursive: true, force: true });
4736
+ fs21.renameSync(stagingDir, localSkillDir);
4737
+ fs21.rmSync(oldDir, { recursive: true, force: true });
4554
4738
  } catch (err) {
4555
4739
  try {
4556
- fs20.rmSync(stagingDir, { recursive: true, force: true });
4740
+ fs21.rmSync(stagingDir, { recursive: true, force: true });
4557
4741
  } catch {
4558
4742
  }
4559
4743
  throw err;
@@ -4567,8 +4751,8 @@ function rateLimitHint(prefix) {
4567
4751
  return ghPresentButTokenFailed() ? `${prefix} Tienes gh instalado pero \`gh auth token\` no devolvi\xF3 credencial (\xBFsesi\xF3n caducada?) \u2014 prueba \`gh auth login\` o define GH_TOKEN.` : `${prefix} Define GH_TOKEN o inicia sesi\xF3n en gh CLI.`;
4568
4752
  }
4569
4753
  function loadUpstreams() {
4570
- const file = path28.join(path28.dirname(stackRoot()), "upstreams.json");
4571
- return JSON.parse(fs21.readFileSync(file, "utf8"));
4754
+ const file = path29.join(path29.dirname(stackRoot()), "upstreams.json");
4755
+ return JSON.parse(fs22.readFileSync(file, "utf8"));
4572
4756
  }
4573
4757
  function skillsToScan(maintainer, upstreams) {
4574
4758
  return maintainer ? Object.keys(upstreams.skills) : [];
@@ -4717,8 +4901,8 @@ function isEngramRunning() {
4717
4901
  return null;
4718
4902
  }
4719
4903
  }
4720
- function isGitClone(projectRoot = path28.dirname(stackRoot())) {
4721
- return fs21.existsSync(path28.join(projectRoot, ".git"));
4904
+ function isGitClone(projectRoot = path29.dirname(stackRoot())) {
4905
+ return fs22.existsSync(path29.join(projectRoot, ".git"));
4722
4906
  }
4723
4907
  var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
4724
4908
  function resolvePnpm() {
@@ -4728,12 +4912,12 @@ function resolvePnpm() {
4728
4912
  }
4729
4913
  function cleanupTmp(dir) {
4730
4914
  try {
4731
- fs21.rmSync(dir, { recursive: true, force: true });
4915
+ fs22.rmSync(dir, { recursive: true, force: true });
4732
4916
  } catch {
4733
4917
  }
4734
4918
  }
4735
4919
  function updateStackGitClone() {
4736
- const projectRoot = path28.dirname(stackRoot());
4920
+ const projectRoot = path29.dirname(stackRoot());
4737
4921
  const git = lookPath("git");
4738
4922
  if (!git) throw new Error("git no encontrado en PATH.");
4739
4923
  const pnpm = resolvePnpm();
@@ -4750,7 +4934,7 @@ function updateStackGlobal() {
4750
4934
  execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
4751
4935
  }
4752
4936
  async function downloadSkillToTemp(repo, sha, skillPath) {
4753
- const root = fs21.mkdtempSync(path28.join(os3.tmpdir(), "jorgex-skill-"));
4937
+ const root = fs22.mkdtempSync(path29.join(os3.tmpdir(), "jorgex-skill-"));
4754
4938
  try {
4755
4939
  const result = await downloadRepoTarball(repo, sha, root, skillPath);
4756
4940
  if (!result.ok) {
@@ -4758,15 +4942,15 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
4758
4942
  return { error: result.reason };
4759
4943
  }
4760
4944
  if (skillPath) {
4761
- const sub = path28.resolve(path28.join(root, skillPath));
4945
+ const sub = path29.resolve(path29.join(root, skillPath));
4762
4946
  if (!isContainedIn(sub, root)) {
4763
4947
  cleanupTmp(root);
4764
4948
  return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
4765
4949
  }
4766
- if (fs21.existsSync(sub)) return { dir: sub, root };
4950
+ if (fs22.existsSync(sub)) return { dir: sub, root };
4767
4951
  const lastSeg = skillPath.split("/").pop();
4768
- const sub2 = path28.resolve(path28.join(root, lastSeg));
4769
- if (isContainedIn(sub2, root) && fs21.existsSync(sub2)) {
4952
+ const sub2 = path29.resolve(path29.join(root, lastSeg));
4953
+ if (isContainedIn(sub2, root) && fs22.existsSync(sub2)) {
4770
4954
  if (!validateExtractedTree(sub2)) {
4771
4955
  cleanupTmp(root);
4772
4956
  return { error: `el sub\xE1rbol "${lastSeg}" contiene symlinks o rutas fuera del destino` };
@@ -4785,11 +4969,11 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
4785
4969
  function pruneEngramDbBackups() {
4786
4970
  try {
4787
4971
  const dir = dataDir();
4788
- if (!fs21.existsSync(dir)) return;
4789
- const backups = fs21.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs21.statSync(path28.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
4972
+ if (!fs22.existsSync(dir)) return;
4973
+ const backups = fs22.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs22.statSync(path29.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
4790
4974
  for (const old of backups.slice(3)) {
4791
4975
  try {
4792
- fs21.rmSync(path28.join(dir, old.name));
4976
+ fs22.rmSync(path29.join(dir, old.name));
4793
4977
  } catch {
4794
4978
  }
4795
4979
  }
@@ -4797,18 +4981,18 @@ function pruneEngramDbBackups() {
4797
4981
  }
4798
4982
  }
4799
4983
  function rotateLockedBinary(binPath, sweepRoot = HOME) {
4800
- if (!fs21.existsSync(binPath)) return null;
4801
- const dir = path28.dirname(binPath);
4802
- const base = path28.basename(binPath);
4984
+ if (!fs22.existsSync(binPath)) return null;
4985
+ const dir = path29.dirname(binPath);
4986
+ const base = path29.basename(binPath);
4803
4987
  const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
4804
4988
  const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
4805
- const resolvedDir = path28.resolve(dir);
4806
- if (resolvedDir === path28.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
4989
+ const resolvedDir = path29.resolve(dir);
4990
+ if (resolvedDir === path29.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
4807
4991
  try {
4808
- for (const entry of fs21.readdirSync(dir)) {
4992
+ for (const entry of fs22.readdirSync(dir)) {
4809
4993
  if (oldPattern.test(entry)) {
4810
4994
  try {
4811
- fs21.rmSync(path28.join(dir, entry), { force: true });
4995
+ fs22.rmSync(path29.join(dir, entry), { force: true });
4812
4996
  } catch {
4813
4997
  }
4814
4998
  }
@@ -4816,8 +5000,8 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
4816
5000
  } catch {
4817
5001
  }
4818
5002
  }
4819
- const rotated = path28.join(dir, `${base}.old-${Date.now()}`);
4820
- fs21.renameSync(binPath, rotated);
5003
+ const rotated = path29.join(dir, `${base}.old-${Date.now()}`);
5004
+ fs22.renameSync(binPath, rotated);
4821
5005
  return rotated;
4822
5006
  }
4823
5007
  async function updateEngram(engramRepo, latestVersion) {
@@ -4826,19 +5010,19 @@ async function updateEngram(engramRepo, latestVersion) {
4826
5010
  "Engram est\xE1 en ejecuci\xF3n: los procesos vivos seguir\xE1n usando la versi\xF3n antigua hasta que reinicies los clientes (Claude Code/OpenCode/Codex)."
4827
5011
  );
4828
5012
  }
4829
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path28.join(HOME, ".engram");
4830
- const engramDb = path28.join(engramDataDir, "engram.db");
4831
- if (fs21.existsSync(engramDb)) {
5013
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path29.join(HOME, ".engram");
5014
+ const engramDb = path29.join(engramDataDir, "engram.db");
5015
+ if (fs22.existsSync(engramDb)) {
4832
5016
  const doBackup = await p4.confirm({
4833
5017
  message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
4834
5018
  initialValue: true
4835
5019
  });
4836
5020
  if (!p4.isCancel(doBackup) && doBackup) {
4837
5021
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4838
- const dest = path28.join(dataDir(), `engram-db-backup-${ts}.db`);
5022
+ const dest = path29.join(dataDir(), `engram-db-backup-${ts}.db`);
4839
5023
  try {
4840
- if (!fs21.existsSync(dataDir())) fs21.mkdirSync(dataDir(), { recursive: true });
4841
- fs21.copyFileSync(engramDb, dest);
5024
+ if (!fs22.existsSync(dataDir())) fs22.mkdirSync(dataDir(), { recursive: true });
5025
+ fs22.copyFileSync(engramDb, dest);
4842
5026
  p4.log.success(`DB respaldada en ${dest} (la DB original NO se modifica jam\xE1s).`);
4843
5027
  pruneEngramDbBackups();
4844
5028
  } catch (err) {
@@ -4888,10 +5072,10 @@ async function updateEngram(engramRepo, latestVersion) {
4888
5072
  ["install", `github.com/Gentleman-Programming/engram/cmd/engram@v${latestVersion}`],
4889
5073
  { stdio: "inherit" }
4890
5074
  );
4891
- const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs21.existsSync(bin ?? "") });
5075
+ const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs22.existsSync(bin ?? "") });
4892
5076
  if (rollbackOk.action === "restore") {
4893
5077
  try {
4894
- fs21.renameSync(rotated, bin);
5078
+ fs22.renameSync(rotated, bin);
4895
5079
  p4.log.warn(rollbackOk.messages.onRestore);
4896
5080
  } catch {
4897
5081
  p4.log.warn(rollbackOk.messages.onRenameFail);
@@ -4899,10 +5083,10 @@ async function updateEngram(engramRepo, latestVersion) {
4899
5083
  }
4900
5084
  return true;
4901
5085
  } catch (err) {
4902
- const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs21.existsSync(bin ?? "") });
5086
+ const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs22.existsSync(bin ?? "") });
4903
5087
  if (rollbackFail.action === "restore") {
4904
5088
  try {
4905
- fs21.renameSync(rotated, bin);
5089
+ fs22.renameSync(rotated, bin);
4906
5090
  p4.log.info(rollbackFail.messages.onRestore);
4907
5091
  } catch {
4908
5092
  p4.log.error(rollbackFail.messages.onRenameFail);
@@ -5172,7 +5356,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
5172
5356
  continue;
5173
5357
  }
5174
5358
  const { dir: tmpDir, root: tmpRoot } = tmpResult;
5175
- const localSkillDir = path28.join(stackRoot(), "skills", skillInfo.name);
5359
+ const localSkillDir = path29.join(stackRoot(), "skills", skillInfo.name);
5176
5360
  const diff = renderSkillDiff(tmpDir, localSkillDir);
5177
5361
  if (diff) {
5178
5362
  p4.log.info(`Diff de ${skillInfo.name}:
@@ -5258,7 +5442,7 @@ ${diff}`);
5258
5442
  }
5259
5443
 
5260
5444
  // src/models-picker.ts
5261
- import path29 from "path";
5445
+ import path30 from "path";
5262
5446
  import * as p5 from "@clack/prompts";
5263
5447
  var TIERS = ["strong", "standard", "cheap"];
5264
5448
  var EFFORTS = ["low", "medium", "high", "xhigh"];
@@ -5284,7 +5468,7 @@ function opencodeLiveModels(binPath) {
5284
5468
  }
5285
5469
  function agentsByTier() {
5286
5470
  const grouped = { strong: [], standard: [], cheap: [] };
5287
- for (const agent of loadCanonicalAgents(path29.join(stackRoot(), "agents"))) {
5471
+ for (const agent of loadCanonicalAgents(path30.join(stackRoot(), "agents"))) {
5288
5472
  if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
5289
5473
  }
5290
5474
  return grouped;
@@ -5469,16 +5653,16 @@ function cancelled() {
5469
5653
  }
5470
5654
 
5471
5655
  // src/lib/release.ts
5472
- import fs22 from "fs";
5473
- import path30 from "path";
5656
+ import fs23 from "fs";
5657
+ import path31 from "path";
5474
5658
  import { execFileSync as execFileSync6 } from "child_process";
5475
5659
  import { fileURLToPath } from "url";
5476
5660
  function findPackageJson() {
5477
- let dir = path30.dirname(fileURLToPath(import.meta.url));
5661
+ let dir = path31.dirname(fileURLToPath(import.meta.url));
5478
5662
  for (let i = 0; i < 6; i++) {
5479
- const candidate = path30.join(dir, "package.json");
5480
- if (fs22.existsSync(candidate)) return candidate;
5481
- dir = path30.dirname(dir);
5663
+ const candidate = path31.join(dir, "package.json");
5664
+ if (fs23.existsSync(candidate)) return candidate;
5665
+ dir = path31.dirname(dir);
5482
5666
  }
5483
5667
  throw new Error("No se encontr\xF3 package.json cerca del CLI.");
5484
5668
  }
@@ -5487,7 +5671,7 @@ function readPackageVersion() {
5487
5671
  }
5488
5672
  function readPackageMetadata() {
5489
5673
  const packageJson = findPackageJson();
5490
- const raw = fs22.readFileSync(packageJson, "utf8");
5674
+ const raw = fs23.readFileSync(packageJson, "utf8");
5491
5675
  const parsed = JSON.parse(raw);
5492
5676
  const name = typeof parsed.name === "string" ? parsed.name.trim() : "";
5493
5677
  const version = typeof parsed.version === "string" ? parsed.version.trim() : "";
@@ -5499,8 +5683,8 @@ function readPackageMetadata() {
5499
5683
 
5500
5684
  // src/lib/pi-runtime.ts
5501
5685
  import os4 from "os";
5502
- import path31 from "path";
5503
- import fs23 from "fs";
5686
+ import path32 from "path";
5687
+ import fs24 from "fs";
5504
5688
  import { spawnSync } from "child_process";
5505
5689
  import { createHash as createHash2 } from "crypto";
5506
5690
 
@@ -5667,14 +5851,14 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
5667
5851
  if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
5668
5852
  const packageValue = Reflect.get(record, "package");
5669
5853
  const result = Reflect.get(record, "result");
5670
- return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path31.resolve(packageRunner) === path31.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
5854
+ return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path32.resolve(packageRunner) === path32.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
5671
5855
  } catch {
5672
5856
  return false;
5673
5857
  }
5674
5858
  }
5675
5859
  function installPiFromVerifiedTarball(input, deps) {
5676
5860
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
5677
- const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
5861
+ const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
5678
5862
  const artifact = deps.download(destination);
5679
5863
  if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
5680
5864
  return { kind: "blocked", reason: "tarball-integrity" };
@@ -5682,7 +5866,7 @@ function installPiFromVerifiedTarball(input, deps) {
5682
5866
  deps.backupSettings();
5683
5867
  const scope = {
5684
5868
  kind: input.targetDir === void 0 ? "real" : "target-dir",
5685
- codingAgentDir: path31.resolve(paths.codingAgentDir)
5869
+ codingAgentDir: path32.resolve(paths.codingAgentDir)
5686
5870
  };
5687
5871
  const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
5688
5872
  deps.writeReceiptAtomic(`${JSON.stringify(installing)}
@@ -5711,30 +5895,30 @@ function installPiFromVerifiedTarball(input, deps) {
5711
5895
  return { kind: "installed", receipt };
5712
5896
  }
5713
5897
  function runtimePath(piExecutable) {
5714
- const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), process.env.SystemRoot ? path31.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
5715
- return [...new Set(entries.filter((entry) => entry !== null))].join(path31.delimiter);
5898
+ const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), process.env.SystemRoot ? path32.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
5899
+ return [...new Set(entries.filter((entry) => entry !== null))].join(path32.delimiter);
5716
5900
  }
5717
5901
  function targetPaths(targetDir, engramBin, piExecutable) {
5718
- const root = path31.resolve(targetDir);
5719
- const codingAgentDir = path31.join(root, "pi-agent");
5720
- const home = path31.join(root, "home");
5721
- const temporary = path31.join(root, "tmp");
5902
+ const root = path32.resolve(targetDir);
5903
+ const codingAgentDir = path32.join(root, "pi-agent");
5904
+ const home = path32.join(root, "home");
5905
+ const temporary = path32.join(root, "tmp");
5722
5906
  return {
5723
5907
  codingAgentDir,
5724
- receiptPath: path31.join(root, "state", "pi-receipt.json"),
5725
- packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5908
+ receiptPath: path32.join(root, "state", "pi-receipt.json"),
5909
+ packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5726
5910
  environment: {
5727
5911
  HOME: home,
5728
5912
  USERPROFILE: home,
5729
- APPDATA: path31.join(root, "appdata"),
5730
- LOCALAPPDATA: path31.join(root, "localappdata"),
5731
- XDG_CONFIG_HOME: path31.join(root, "xdg-config"),
5732
- XDG_DATA_HOME: path31.join(root, "xdg-data"),
5733
- XDG_CACHE_HOME: path31.join(root, "xdg-cache"),
5913
+ APPDATA: path32.join(root, "appdata"),
5914
+ LOCALAPPDATA: path32.join(root, "localappdata"),
5915
+ XDG_CONFIG_HOME: path32.join(root, "xdg-config"),
5916
+ XDG_DATA_HOME: path32.join(root, "xdg-data"),
5917
+ XDG_CACHE_HOME: path32.join(root, "xdg-cache"),
5734
5918
  TEMP: temporary,
5735
5919
  TMP: temporary,
5736
5920
  TMPDIR: temporary,
5737
- npm_config_cache: path31.join(root, "npm-cache"),
5921
+ npm_config_cache: path32.join(root, "npm-cache"),
5738
5922
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
5739
5923
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
5740
5924
  PI_CODING_AGENT_DIR: codingAgentDir,
@@ -5745,15 +5929,15 @@ function targetPaths(targetDir, engramBin, piExecutable) {
5745
5929
  }
5746
5930
  function userPaths(engramBin, piExecutable) {
5747
5931
  const home = os4.homedir();
5748
- const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent");
5932
+ const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent");
5749
5933
  return {
5750
5934
  codingAgentDir,
5751
- receiptPath: path31.join(dataDir(), "pi-receipt.json"),
5752
- packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5935
+ receiptPath: path32.join(dataDir(), "pi-receipt.json"),
5936
+ packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5753
5937
  environment: {
5754
5938
  HOME: home,
5755
- XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path31.join(home, ".config"),
5756
- XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path31.join(home, ".cache"),
5939
+ XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path32.join(home, ".config"),
5940
+ XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path32.join(home, ".cache"),
5757
5941
  TMPDIR: os4.tmpdir(),
5758
5942
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
5759
5943
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
@@ -5781,7 +5965,7 @@ function runPiRuntime(input, deps) {
5781
5965
  return { kind: "blocked", reason: "tarball-integrity" };
5782
5966
  }
5783
5967
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
5784
- const settingsJson = deps.readSettings(path31.join(paths.codingAgentDir, "settings.json"));
5968
+ const settingsJson = deps.readSettings(path32.join(paths.codingAgentDir, "settings.json"));
5785
5969
  const receiptJson = deps.readReceipt(paths.receiptPath);
5786
5970
  const lifecycleInput = {
5787
5971
  candidate: PI_RUNTIME_CANDIDATE,
@@ -5839,19 +6023,19 @@ function runPiRuntime(input, deps) {
5839
6023
  return result;
5840
6024
  }
5841
6025
  function readJsonFile(file) {
5842
- return JSON.parse(fs23.readFileSync(file, "utf8"));
6026
+ return JSON.parse(fs24.readFileSync(file, "utf8"));
5843
6027
  }
5844
6028
  function packageVersionFromExecutable(executable) {
5845
6029
  let current;
5846
6030
  try {
5847
- current = path31.dirname(fs23.realpathSync(executable));
6031
+ current = path32.dirname(fs24.realpathSync(executable));
5848
6032
  } catch {
5849
6033
  return null;
5850
6034
  }
5851
6035
  for (let depth = 0; depth < 8; depth++) {
5852
6036
  const manifests = [
5853
- path31.join(current, "package.json"),
5854
- path31.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
6037
+ path32.join(current, "package.json"),
6038
+ path32.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
5855
6039
  ];
5856
6040
  for (const manifest of manifests) {
5857
6041
  try {
@@ -5862,7 +6046,7 @@ function packageVersionFromExecutable(executable) {
5862
6046
  } catch {
5863
6047
  }
5864
6048
  }
5865
- const parent = path31.dirname(current);
6049
+ const parent = path32.dirname(current);
5866
6050
  if (parent === current) break;
5867
6051
  current = parent;
5868
6052
  }
@@ -5877,35 +6061,35 @@ function detectPiRuntime() {
5877
6061
  installed: executable !== null,
5878
6062
  executable,
5879
6063
  version: executable === null ? null : packageVersionFromExecutable(executable),
5880
- codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent")
6064
+ codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent")
5881
6065
  };
5882
6066
  }
5883
6067
  function hasManagedPiRuntime(targetDir) {
5884
- const receipt = targetDir === void 0 ? path31.join(dataDir(), "pi-receipt.json") : path31.join(path31.resolve(targetDir), "state", "pi-receipt.json");
5885
- return fs23.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
6068
+ const receipt = targetDir === void 0 ? path32.join(dataDir(), "pi-receipt.json") : path32.join(path32.resolve(targetDir), "state", "pi-receipt.json");
6069
+ return fs24.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
5886
6070
  }
5887
6071
  function resolvePiEngramBin(targetDir) {
5888
6072
  if (targetDir === void 0) return detectEngram();
5889
- const candidate = path31.join(path31.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
5890
- return fs23.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
6073
+ const candidate = path32.join(path32.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
6074
+ return fs24.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
5891
6075
  }
5892
6076
  function readOptional(file, fallback) {
5893
6077
  try {
5894
- return fs23.readFileSync(file, "utf8");
6078
+ return fs24.readFileSync(file, "utf8");
5895
6079
  } catch (error) {
5896
6080
  if (error.code === "ENOENT") return fallback;
5897
6081
  throw error;
5898
6082
  }
5899
6083
  }
5900
6084
  function hashPiTarball(file) {
5901
- const descriptor = fs23.openSync(file, "r");
6085
+ const descriptor = fs24.openSync(file, "r");
5902
6086
  const sha2562 = createHash2("sha256");
5903
6087
  const sha512 = createHash2("sha512");
5904
6088
  const buffer = Buffer.allocUnsafe(1024 * 1024);
5905
6089
  let bytes = 0;
5906
6090
  try {
5907
6091
  while (true) {
5908
- const read = fs23.readSync(descriptor, buffer, 0, buffer.length, null);
6092
+ const read = fs24.readSync(descriptor, buffer, 0, buffer.length, null);
5909
6093
  if (read === 0) break;
5910
6094
  bytes += read;
5911
6095
  const chunk = buffer.subarray(0, read);
@@ -5913,27 +6097,27 @@ function hashPiTarball(file) {
5913
6097
  sha512.update(chunk);
5914
6098
  }
5915
6099
  } finally {
5916
- fs23.closeSync(descriptor);
6100
+ fs24.closeSync(descriptor);
5917
6101
  }
5918
6102
  return { path: file, bytes, sha256: sha2562.digest("hex"), sha512: sha512.digest("hex") };
5919
6103
  }
5920
6104
  async function acquirePiTarball(destination) {
5921
- const existing = fs23.statSync(destination, { throwIfNoEntry: false });
6105
+ const existing = fs24.statSync(destination, { throwIfNoEntry: false });
5922
6106
  if (existing?.isFile()) {
5923
6107
  const observed = hashPiTarball(destination);
5924
6108
  if (observed.bytes === PI_RUNTIME_CANDIDATE.tarball.bytes && observed.sha256 === PI_RUNTIME_CANDIDATE.tarball.sha256 && observed.sha512 === PI_RUNTIME_CANDIDATE.tarball.sha512) {
5925
6109
  return observed;
5926
6110
  }
5927
- fs23.rmSync(destination, { force: true });
6111
+ fs24.rmSync(destination, { force: true });
5928
6112
  }
5929
- fs23.mkdirSync(path31.dirname(destination), { recursive: true });
6113
+ fs24.mkdirSync(path32.dirname(destination), { recursive: true });
5930
6114
  const partial = `${destination}.partial-${process.pid}`;
5931
6115
  const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
5932
6116
  redirect: "error",
5933
6117
  headers: { accept: "application/octet-stream" }
5934
6118
  });
5935
6119
  if (!response.ok || response.body === null) throw new Error(`No se pudo descargar jorgex-pi@${PI_RUNTIME_CANDIDATE.package.version} (${response.status}).`);
5936
- const descriptor = fs23.openSync(partial, "wx", 384);
6120
+ const descriptor = fs24.openSync(partial, "wx", 384);
5937
6121
  let bytes = 0;
5938
6122
  try {
5939
6123
  for await (const chunk of response.body) {
@@ -5941,15 +6125,15 @@ async function acquirePiTarball(destination) {
5941
6125
  bytes += buffer.length;
5942
6126
  if (bytes > PI_RUNTIME_CANDIDATE.tarball.bytes) throw new Error("El tarball de jorgex-pi excede el tama\xF1o fijado.");
5943
6127
  let offset = 0;
5944
- while (offset < buffer.length) offset += fs23.writeSync(descriptor, buffer, offset);
6128
+ while (offset < buffer.length) offset += fs24.writeSync(descriptor, buffer, offset);
5945
6129
  }
5946
6130
  } catch (error) {
5947
- fs23.closeSync(descriptor);
5948
- fs23.rmSync(partial, { force: true });
6131
+ fs24.closeSync(descriptor);
6132
+ fs24.rmSync(partial, { force: true });
5949
6133
  throw error;
5950
6134
  }
5951
- fs23.closeSync(descriptor);
5952
- fs23.renameSync(partial, destination);
6135
+ fs24.closeSync(descriptor);
6136
+ fs24.renameSync(partial, destination);
5953
6137
  return hashPiTarball(destination);
5954
6138
  }
5955
6139
  function runProcess(invocation) {
@@ -5999,7 +6183,7 @@ async function runPiRuntimeSystem(input) {
5999
6183
  remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
6000
6184
  };
6001
6185
  }
6002
- const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
6186
+ const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
6003
6187
  let artifact;
6004
6188
  try {
6005
6189
  artifact = await acquirePiTarball(destination);
@@ -6019,18 +6203,18 @@ async function runPiRuntimeSystem(input) {
6019
6203
  }, {
6020
6204
  download: () => artifact,
6021
6205
  backupSettings: () => createBackup(
6022
- [path31.join(paths.codingAgentDir, "settings.json")],
6206
+ [path32.join(paths.codingAgentDir, "settings.json")],
6023
6207
  "pi-package-install",
6024
- input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
6208
+ input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
6025
6209
  ),
6026
6210
  run: runProcess,
6027
- readSettings: () => readOptional(path31.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
6028
- rewriteSettings: (content) => writeText(path31.join(paths.codingAgentDir, "settings.json"), `${content}
6211
+ readSettings: () => readOptional(path32.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
6212
+ rewriteSettings: (content) => writeText(path32.join(paths.codingAgentDir, "settings.json"), `${content}
6029
6213
  `),
6030
6214
  writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
6031
6215
  });
6032
6216
  }
6033
- const packageRoot = path31.dirname(path31.dirname(paths.packageRunner));
6217
+ const packageRoot = path32.dirname(path32.dirname(paths.packageRunner));
6034
6218
  const writeReceipt = (receipt) => {
6035
6219
  writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
6036
6220
  `);
@@ -6048,13 +6232,13 @@ async function runPiRuntimeSystem(input) {
6048
6232
  value,
6049
6233
  {
6050
6234
  backupSettings: () => createBackup(
6051
- [path31.join(paths.codingAgentDir, "settings.json")],
6235
+ [path32.join(paths.codingAgentDir, "settings.json")],
6052
6236
  "pi-package-uninstall",
6053
- input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
6237
+ input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
6054
6238
  ),
6055
6239
  run: runProcess,
6056
- isPackageAbsent: () => !fs23.existsSync(packageRoot),
6057
- deleteReceipt: () => fs23.rmSync(paths.receiptPath, { force: true })
6240
+ isPackageAbsent: () => !fs24.existsSync(packageRoot),
6241
+ deleteReceipt: () => fs24.rmSync(paths.receiptPath, { force: true })
6058
6242
  }
6059
6243
  )
6060
6244
  };
@@ -6126,12 +6310,24 @@ async function runManagedPiSystem(input) {
6126
6310
  remedy: `Pi ${input.detected.version} no est\xE1 entre las versiones verificadas (${supportedVersions.join(", ")}). Actualiza Stack a una versi\xF3n compatible antes de gestionar Pi.`
6127
6311
  };
6128
6312
  }
6129
- const { devtoolsMcpEnabled: explicitDevtools, playwrightCliEnabled: explicitPlaywright, ...runtimeInput } = input;
6313
+ const {
6314
+ devtoolsMcpEnabled: explicitDevtools,
6315
+ playwrightCliEnabled: explicitPlaywright,
6316
+ writingStyle: suppliedStyle,
6317
+ writingStyleMode,
6318
+ ...runtimeInput
6319
+ } = input;
6320
+ const readsStyle = input.operation !== "uninstall" && input.operation !== "models";
6321
+ const preparedStyle = readsStyle && suppliedStyle === void 0 ? prepareWritingStyle(resolveWritingStyleFile({ targetDir: input.targetDir }), { rootDir: input.targetDir }) : void 0;
6322
+ const style = readsStyle ? suppliedStyle ?? preparedStyle : void 0;
6323
+ const mode = readsStyle ? writingStyleMode ?? (input.targetDir === void 0 ? loadInstallModePreference().mode : "human") : "human";
6324
+ const writingStyle = style && mode === "programmatic" ? { ...style, content: null } : style;
6130
6325
  const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
6131
6326
  const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
6132
6327
  const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
6133
6328
  const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
6134
6329
  const projectionInput = {
6330
+ writingStyle,
6135
6331
  targetDir: input.targetDir,
6136
6332
  packageSource: PI_RUNTIME_CANDIDATE.package.source,
6137
6333
  engramBin: input.engramBin,
@@ -6141,6 +6337,7 @@ async function runManagedPiSystem(input) {
6141
6337
  devtoolsMcpEnabled,
6142
6338
  pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
6143
6339
  };
6340
+ if (preparedStyle !== void 0 && input.operation !== "doctor") applyWritingStyle(preparedStyle);
6144
6341
  const result = await runManagedPiOperation(input.operation, {
6145
6342
  async runPackage(operation) {
6146
6343
  return managedPackageResult(await runPiRuntimeSystem({ ...runtimeInput, operation }));
@@ -6174,7 +6371,7 @@ async function runManagedPiSystem(input) {
6174
6371
  }
6175
6372
 
6176
6373
  // src/lib/quality-runner.ts
6177
- import path32 from "path";
6374
+ import path33 from "path";
6178
6375
  import { spawn, spawnSync as spawnSync2 } from "child_process";
6179
6376
  function normalizeLimit(value, name) {
6180
6377
  if (value === void 0) return void 0;
@@ -6306,7 +6503,7 @@ function killProcessTree(child) {
6306
6503
  if (pid === void 0) return;
6307
6504
  if (process.platform === "win32") {
6308
6505
  const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
6309
- const taskkill = path32.join(systemRoot, "System32", "taskkill.exe");
6506
+ const taskkill = path33.join(systemRoot, "System32", "taskkill.exe");
6310
6507
  const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
6311
6508
  shell: false,
6312
6509
  stdio: "ignore",
@@ -6552,9 +6749,9 @@ async function runQualityPlan(input) {
6552
6749
  }
6553
6750
 
6554
6751
  // src/lib/engram-install.ts
6555
- import fs24 from "fs";
6752
+ import fs25 from "fs";
6556
6753
  import os5 from "os";
6557
- import path33 from "path";
6754
+ import path34 from "path";
6558
6755
  import { execFileSync as execFileSync7 } from "child_process";
6559
6756
  import { createHash as createHash3 } from "crypto";
6560
6757
 
@@ -6599,15 +6796,15 @@ var engram_release_default = {
6599
6796
  async function installMissingEngram(options = {}) {
6600
6797
  const platform = options.platform ?? process.platform;
6601
6798
  const homeDir = options.homeDir ?? os5.homedir();
6602
- if (!path33.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
6799
+ if (!path34.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
6603
6800
  const binaryName = platform === "win32" ? "engram.exe" : "engram";
6604
- const bin = path33.join(homeDir, ".local", "bin", binaryName);
6801
+ const bin = path34.join(homeDir, ".local", "bin", binaryName);
6605
6802
  let staging;
6606
6803
  try {
6607
- const existing = fs24.lstatSync(bin, { throwIfNoEntry: false });
6804
+ const existing = fs25.lstatSync(bin, { throwIfNoEntry: false });
6608
6805
  if (existing) {
6609
- if (!fs24.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
6610
- fs24.accessSync(bin, platform === "win32" ? fs24.constants.F_OK : fs24.constants.X_OK);
6806
+ if (!fs25.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
6807
+ fs25.accessSync(bin, platform === "win32" ? fs25.constants.F_OK : fs25.constants.X_OK);
6611
6808
  return { ok: true, bin };
6612
6809
  }
6613
6810
  const key = `${platform === "win32" ? "windows" : platform}_${options.arch ?? process.arch}`;
@@ -6636,24 +6833,24 @@ async function installMissingEngram(options = {}) {
6636
6833
  if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
6637
6834
  throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
6638
6835
  }
6639
- fs24.mkdirSync(path33.dirname(bin), { recursive: true });
6640
- staging = fs24.mkdtempSync(path33.join(path33.dirname(bin), ".engram-install-"));
6641
- const archivePath = path33.join(staging, asset.name);
6642
- fs24.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
6836
+ fs25.mkdirSync(path34.dirname(bin), { recursive: true });
6837
+ staging = fs25.mkdtempSync(path34.join(path34.dirname(bin), ".engram-install-"));
6838
+ const archivePath = path34.join(staging, asset.name);
6839
+ fs25.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
6643
6840
  const binary = execFileSync7(resolveTarBin(), ["-xOf", archivePath, binaryName], {
6644
6841
  maxBuffer: 64 * 1024 * 1024,
6645
6842
  timeout: 3e4,
6646
6843
  stdio: ["ignore", "pipe", "pipe"]
6647
6844
  });
6648
6845
  if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
6649
- const stagedBin = path33.join(staging, binaryName);
6650
- fs24.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
6651
- fs24.linkSync(stagedBin, bin);
6846
+ const stagedBin = path34.join(staging, binaryName);
6847
+ fs25.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
6848
+ fs25.linkSync(stagedBin, bin);
6652
6849
  return { ok: true, bin };
6653
6850
  } catch (error) {
6654
6851
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
6655
6852
  } finally {
6656
- if (staging) fs24.rmSync(staging, { recursive: true, force: true });
6853
+ if (staging) fs25.rmSync(staging, { recursive: true, force: true });
6657
6854
  }
6658
6855
  }
6659
6856
 
@@ -6977,7 +7174,7 @@ async function resolveHostEngramForInstall(flags) {
6977
7174
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
6978
7175
  }
6979
7176
  }
6980
- async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, playwrightCliEnabled) {
7177
+ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, writingStyle, modePreference, playwrightCliEnabled) {
6981
7178
  if (targetDir === void 0 && operation !== "models") {
6982
7179
  const preferenceErrors = browserPreferenceErrors();
6983
7180
  if (preferenceErrors.length > 0) {
@@ -6985,6 +7182,13 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
6985
7182
  return 1;
6986
7183
  }
6987
7184
  }
7185
+ if (operation === "install" || operation === "sync" || operation === "update") {
7186
+ if (writingStyle === void 0) {
7187
+ const prepared = prepareWritingStyle(resolveWritingStyleFile({ targetDir }), { rootDir: targetDir });
7188
+ applyWritingStyle(prepared);
7189
+ writingStyle = prepared;
7190
+ }
7191
+ }
6988
7192
  const detected = detectPiRuntime();
6989
7193
  if (!detected.installed || detected.executable === null) {
6990
7194
  console.error("Pi no detectado. Instala el runtime Pi antes de gestionar jorgex-pi.");
@@ -7016,6 +7220,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7016
7220
  engramBin = requirement.bin;
7017
7221
  }
7018
7222
  const result = await runManagedPiSystem({
7223
+ writingStyle,
7224
+ writingStyleMode: modePreference?.mode,
7019
7225
  operation,
7020
7226
  targetDir,
7021
7227
  detected: { executable: detected.executable, version: detected.version },
@@ -7032,6 +7238,10 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7032
7238
  else p6.log.success(`Pi: ${result.kind}.`);
7033
7239
  return 0;
7034
7240
  }
7241
+ function persistSuccessfulGlobalMode(mode, targetDir, dryRun, exitCode) {
7242
+ if (mode === void 0 || targetDir !== void 0 || dryRun || exitCode !== 0) return;
7243
+ saveInstallModePreference(installModePreferenceFile(), mode);
7244
+ }
7035
7245
  function printHelp() {
7036
7246
  console.log(`jorgex-stack v${VERSION}
7037
7247
 
@@ -7125,7 +7335,7 @@ Flags disponibles: jorgex-stack --help`
7125
7335
  return;
7126
7336
  }
7127
7337
  try {
7128
- const plan = JSON.parse(fs25.readFileSync(flags.positional[0], "utf8"));
7338
+ const plan = JSON.parse(fs26.readFileSync(flags.positional[0], "utf8"));
7129
7339
  const result = await runQualityPlan(plan);
7130
7340
  const serialized = serializeQualityReceipt(result.receipt);
7131
7341
  if (flags.receipt === void 0) process.stdout.write(`${serialized}
@@ -7153,6 +7363,7 @@ Flags disponibles: jorgex-stack --help`
7153
7363
  let completed = false;
7154
7364
  p6.intro(`jorgex-stack ${command}${flags.dryRun ? " (dry-run)" : ""}`);
7155
7365
  try {
7366
+ const writingStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: flags.targetDir }), { rootDir: flags.targetDir });
7156
7367
  if (flags.targetDir === void 0) {
7157
7368
  const errors = browserPreferenceErrors();
7158
7369
  if (errors.length > 0) {
@@ -7161,7 +7372,7 @@ Flags disponibles: jorgex-stack --help`
7161
7372
  return;
7162
7373
  }
7163
7374
  }
7164
- const mode = fileRuntimes.length > 0 ? await resolveInstallMode(flags) : void 0;
7375
+ const mode = fileRuntimes.length > 0 || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 ? await resolveInstallMode(flags) : void 0;
7165
7376
  if (mode === null) return;
7166
7377
  const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, runtimes);
7167
7378
  if (devtoolsMcpSelection === null) {
@@ -7173,6 +7384,8 @@ Flags disponibles: jorgex-stack --help`
7173
7384
  exitCode = process.exitCode === 1 ? 1 : 0;
7174
7385
  return;
7175
7386
  }
7387
+ p6.log.info(`Estilo de escritura: ${writingStyle.sourcePath}${flags.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
7388
+ applyWritingStyle(writingStyle, flags.dryRun);
7176
7389
  if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
7177
7390
  exitCode = 1;
7178
7391
  return;
@@ -7190,6 +7403,7 @@ Flags disponibles: jorgex-stack --help`
7190
7403
  if (fileRuntimes.length > 0) {
7191
7404
  exitCode = await runInstall({
7192
7405
  runtimes: fileRuntimes,
7406
+ writingStyle,
7193
7407
  targetDir: flags.targetDir,
7194
7408
  dryRun: flags.dryRun,
7195
7409
  yes: flags.yes,
@@ -7206,6 +7420,7 @@ Flags disponibles: jorgex-stack --help`
7206
7420
  p6.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
7207
7421
  } else {
7208
7422
  exitCode = await runInstall({
7423
+ writingStyle,
7209
7424
  runtimes: [],
7210
7425
  targetDir: flags.targetDir,
7211
7426
  dryRun: false,
@@ -7219,15 +7434,21 @@ Flags disponibles: jorgex-stack --help`
7219
7434
  }
7220
7435
  if (runtimes.includes("pi") && piCanRun) {
7221
7436
  if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
7222
- else exitCode = Math.max(exitCode, await runSelectedPi(
7223
- command,
7224
- flags.targetDir,
7225
- flags.yes,
7226
- flags.targetDir === void 0 ? engramBin : void 0,
7227
- devtoolsMcpSelection.pi,
7228
- flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
7229
- ));
7437
+ else {
7438
+ const piExitCode = await runSelectedPi(
7439
+ command,
7440
+ flags.targetDir,
7441
+ flags.yes,
7442
+ flags.targetDir === void 0 ? engramBin : void 0,
7443
+ devtoolsMcpSelection.pi,
7444
+ writingStyle,
7445
+ mode,
7446
+ flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
7447
+ );
7448
+ exitCode = Math.max(exitCode, piExitCode);
7449
+ }
7230
7450
  }
7451
+ if (runtimes.includes("pi")) persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, exitCode);
7231
7452
  completed = true;
7232
7453
  } catch (error) {
7233
7454
  p6.log.error(error instanceof Error ? error.message : String(error));
@@ -7264,10 +7485,15 @@ Flags disponibles: jorgex-stack --help`
7264
7485
  return;
7265
7486
  }
7266
7487
  case "doctor": {
7267
- const fileDoctorSelected = flags.agents.length === 0 || flags.agents.some(isFileManagedRuntime);
7268
- let exitCode = fileDoctorSelected ? await runDoctor() : 0;
7488
+ const mode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0 ? await resolveInstallMode(flags) : void 0;
7489
+ if (mode === null) return;
7269
7490
  const piSelected = flags.agents.includes("pi") || flags.agents.length === 0 && detectPiRuntime().installed && hasManagedPiRuntime(flags.targetDir);
7270
- if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
7491
+ const doctorRuntimes = flags.agents.length > 0 ? flags.agents : flags.targetDir === void 0 ? [
7492
+ ...Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => adapter.id),
7493
+ ...piSelected ? ["pi"] : []
7494
+ ] : [...Object.keys(ADAPTERS), ...piSelected ? ["pi"] : []];
7495
+ let exitCode = await runDoctor({ targetDir: flags.targetDir, runtimes: doctorRuntimes, mode });
7496
+ if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir, false, void 0, void 0, void 0, mode));
7271
7497
  process.exitCode = exitCode;
7272
7498
  return;
7273
7499
  }
@@ -7283,20 +7509,28 @@ Flags disponibles: jorgex-stack --help`
7283
7509
  const runtimes = await resolveRuntimes(flags);
7284
7510
  if (runtimes === null) return;
7285
7511
  const fileRuntimes = runtimes.filter(isFileManagedRuntime);
7286
- if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
7287
- process.exitCode = await runSelectedPi("update", flags.targetDir);
7288
- return;
7289
- }
7290
7512
  const preferenceFile = installModePreferenceFile();
7291
7513
  const explicitMode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0;
7292
7514
  const hasSavedMode = hasInstallModePreference(preferenceFile);
7293
7515
  const canResolveMode = flags.targetDir !== void 0 || explicitMode || hasSavedMode;
7294
- const mode = fileRuntimes.length > 0 && canResolveMode ? await resolveInstallMode(flags, false) : DEFAULT_INSTALL_MODE_PREFERENCE;
7516
+ const mode = canResolveMode ? await resolveInstallMode(flags, false) : DEFAULT_INSTALL_MODE_PREFERENCE;
7295
7517
  if (mode === null) return;
7518
+ const writingStyle = prepareWritingStyle(
7519
+ resolveWritingStyleFile({ targetDir: flags.targetDir }),
7520
+ { rootDir: flags.targetDir }
7521
+ );
7522
+ applyWritingStyle(writingStyle, flags.dryRun);
7523
+ if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
7524
+ const piExitCode = await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode);
7525
+ process.exitCode = piExitCode;
7526
+ persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, piExitCode);
7527
+ return;
7528
+ }
7296
7529
  const canSync = fileRuntimes.length === 0 || canResolveMode;
7297
7530
  if (fileRuntimes.length > 0 && canSync) {
7298
7531
  const code = await runInstall({
7299
7532
  runtimes: fileRuntimes,
7533
+ writingStyle,
7300
7534
  targetDir: flags.targetDir,
7301
7535
  dryRun: flags.dryRun,
7302
7536
  yes: true,
@@ -7317,7 +7551,7 @@ Flags disponibles: jorgex-stack --help`
7317
7551
  );
7318
7552
  process.exitCode = result.exitCode;
7319
7553
  if (result.exitCode === 0 && runtimes.includes("pi")) {
7320
- process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir));
7554
+ process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode));
7321
7555
  }
7322
7556
  if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
7323
7557
  p6.log.warn("Skills/stack actualizados, pero el sync con los runtimes sigue pendiente. Ejecuta jorgex-stack sync --mode human|programmatic.");
@@ -7326,6 +7560,7 @@ Flags disponibles: jorgex-stack --help`
7326
7560
  if (!p6.isCancel(apply) && apply) {
7327
7561
  process.exitCode = await runInstall({
7328
7562
  runtimes: fileRuntimes,
7563
+ writingStyle,
7329
7564
  targetDir: flags.targetDir,
7330
7565
  dryRun: false,
7331
7566
  yes: false,
@@ -7337,6 +7572,9 @@ Flags disponibles: jorgex-stack --help`
7337
7572
  } else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && (flags.yes || !process.stdout.isTTY)) {
7338
7573
  console.log("Skills/stack actualizados. Ejecuta jorgex-stack sync para aplicarlos a los runtimes.");
7339
7574
  }
7575
+ if (runtimes.includes("pi")) {
7576
+ persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, process.exitCode ?? result.exitCode);
7577
+ }
7340
7578
  return;
7341
7579
  }
7342
7580
  case "models": {