jorgex-stack 1.9.57 → 1.9.58

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
@@ -1,443 +1,115 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- HOME,
4
3
  QUALITY_PROFILES,
5
4
  canonicalJson,
6
5
  createQualityReceipt,
7
- dataDir,
8
6
  evaluateQualityPolicy,
9
- samePath,
10
7
  serializeQualityReceipt,
11
8
  sha256,
12
- stackRoot,
13
9
  validateQualityReceipt
14
- } from "./chunk-IO2FFZOH.js";
10
+ } from "./chunk-N5IC2JBJ.js";
11
+ import {
12
+ inspectOpencodePluginFile,
13
+ opencodeAdapter,
14
+ shouldRetireLegacyEngram
15
+ } from "./chunk-M6QX2F3A.js";
16
+ import {
17
+ claudeCodeAdapter
18
+ } from "./chunk-NNPFC6QV.js";
19
+ import {
20
+ codexAdapter
21
+ } from "./chunk-GOLJ5PA3.js";
22
+ import {
23
+ DEFAULT_MODEL_MAP,
24
+ DEVTOOLS_MCP_SERVER,
25
+ SYSTEM_PROMPT_SECTIONS,
26
+ assertSystemPromptFile,
27
+ assertSystemPromptMarkers,
28
+ copyFile,
29
+ createBackup,
30
+ detectClaudeCode,
31
+ detectCodex,
32
+ detectEngram,
33
+ detectOpenCode,
34
+ engramVersion,
35
+ ensureDir,
36
+ ensureModelMapFile,
37
+ hasHealthyManagedMarkdownMarkers,
38
+ isContainedIn,
39
+ listBackups,
40
+ listFilesRecursive,
41
+ loadCanonicalAgents,
42
+ loadCanonicalHooks,
43
+ loadCanonicalMcp,
44
+ loadModelMap,
45
+ localQualityStatus,
46
+ lookPath,
47
+ modelMapFile,
48
+ planDetectedBinCommand,
49
+ pruneEmptyDirs,
50
+ readSystemPromptFile,
51
+ readTextIfExists,
52
+ removeMarkdownSection,
53
+ removeSystemPromptSections,
54
+ resolveAgentModel,
55
+ restoreBackup,
56
+ runDetectedBin,
57
+ runOfficialSetupIfNeeded,
58
+ sameFileContent,
59
+ shouldRunOfficialSetup,
60
+ stripLeadingHtmlComments,
61
+ upsertMarkdownSection,
62
+ validateOfficialSetupDestination,
63
+ writeText
64
+ } from "./chunk-KB5IQEKL.js";
65
+ import {
66
+ HOME,
67
+ dataDir,
68
+ samePath,
69
+ stackRoot
70
+ } from "./chunk-NQZVJFV2.js";
15
71
 
16
72
  // src/cli.ts
17
73
  import * as p6 from "@clack/prompts";
18
- import fs27 from "fs";
19
- import { pathToFileURL as pathToFileURL2 } from "url";
74
+ import fs20 from "fs";
75
+ import { pathToFileURL } from "url";
20
76
 
21
77
  // src/install.ts
22
- import fs17 from "fs";
23
- import path22 from "path";
78
+ import fs10 from "fs";
79
+ import path12 from "path";
24
80
  import * as p from "@clack/prompts";
25
81
 
26
82
  // src/lib/writing-style.ts
27
- import fs3 from "fs";
28
- import path3 from "path";
29
-
30
- // src/lib/backup.ts
31
- import fs2 from "fs";
32
- import path2 from "path";
33
- import crypto from "crypto";
34
-
35
- // src/lib/fsx.ts
36
83
  import fs from "fs";
37
84
  import path from "path";
38
- function readTextIfExists(file) {
39
- try {
40
- return fs.readFileSync(file, "utf8");
41
- } catch {
42
- return null;
43
- }
44
- }
45
- function ensureDir(dir) {
46
- fs.mkdirSync(dir, { recursive: true });
47
- }
48
- var tmpCounter = 0;
49
- function tmpPath(target) {
50
- return path.join(path.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
51
- }
52
- function renameInto(tmp, target) {
53
- try {
54
- fs.renameSync(tmp, target);
55
- } catch (err) {
56
- fs.rmSync(tmp, { force: true });
57
- throw err;
58
- }
59
- }
60
- function writeText(file, content, mode) {
61
- ensureDir(path.dirname(file));
62
- const tmp = tmpPath(file);
63
- fs.writeFileSync(tmp, content, { encoding: "utf8", mode });
64
- renameInto(tmp, file);
65
- }
66
- function copyFile(source, target) {
67
- ensureDir(path.dirname(target));
68
- const tmp = tmpPath(target);
69
- fs.copyFileSync(source, tmp);
70
- renameInto(tmp, target);
71
- }
72
- function isContainedIn(child, root) {
73
- const rel = path.relative(path.resolve(root), path.resolve(child));
74
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
75
- }
76
- function pruneEmptyDirs(file, root) {
77
- let dir = path.dirname(file);
78
- while (dir.startsWith(root) && dir !== root) {
79
- try {
80
- if (fs.readdirSync(dir).length > 0) return;
81
- fs.rmdirSync(dir);
82
- } catch {
83
- return;
84
- }
85
- dir = path.dirname(dir);
86
- }
87
- }
88
- function sameFileContent(a, b) {
89
- try {
90
- const bufA = fs.readFileSync(a);
91
- const bufB = fs.readFileSync(b);
92
- return bufA.equals(bufB);
93
- } catch {
94
- return false;
95
- }
96
- }
97
- function listFilesRecursive(dir) {
98
- const out = [];
99
- if (!fs.existsSync(dir)) return out;
100
- const walk = (d) => {
101
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
102
- const p7 = path.join(d, entry.name);
103
- if (entry.isDirectory()) walk(p7);
104
- else out.push(p7);
105
- }
106
- };
107
- walk(dir);
108
- return out;
109
- }
110
-
111
- // src/lib/backup.ts
112
- var KEEP_BACKUPS = 10;
113
- function backupsRoot() {
114
- return path2.join(dataDir(), "backups");
115
- }
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
- `);
122
- }
123
- return hash.digest("hex");
124
- }
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);
137
- }
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;
148
- }
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: [] });
160
- }
161
- }
162
- return infos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
163
- }
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++;
174
- }
175
- return restored;
176
- }
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
- }
182
- }
183
-
184
- // src/lib/filemerge.ts
185
- function markers(name) {
186
- return { open: `<!-- jorgex:${name} -->`, close: `<!-- /jorgex:${name} -->` };
187
- }
188
- function hasHealthyManagedMarkdownMarkers(existing, name) {
189
- const { open, close } = markers(name);
190
- const count = (marker) => existing.split(marker).length - 1;
191
- const onOwnLine = (marker) => existing.split("\n").some((line) => line.trim() === marker);
192
- return count(open) === 1 && count(close) === 1 && existing.indexOf(close) > existing.indexOf(open) && onOwnLine(open) && onOwnLine(close);
193
- }
194
- function repairOrphanMarkers(existing, name) {
195
- const { open, close } = markers(name);
196
- const count = (marker) => existing.split(marker).length - 1;
197
- const opens = count(open);
198
- const closes = count(close);
199
- if (opens === 0 && closes === 0) return existing;
200
- if (hasHealthyManagedMarkdownMarkers(existing, name)) return existing;
201
- return existing.split("\n").map((line) => line.trim() === open || line.trim() === close ? null : line.split(open).join("").split(close).join("")).filter((line) => line !== null).join("\n");
202
- }
203
- function upsertMarkdownSection(existing, name, content) {
204
- const { open, close } = markers(name);
205
- const block = `${open}
206
- ${content.trim()}
207
- ${close}`;
208
- if (existing === null || existing.trim() === "") return block + "\n";
209
- existing = repairOrphanMarkers(existing, name);
210
- const start = existing.indexOf(open);
211
- const end = existing.indexOf(close);
212
- if (start !== -1 && end !== -1 && end > start) {
213
- return existing.slice(0, start) + block + existing.slice(end + close.length);
214
- }
215
- const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
216
- return existing + sep + block + "\n";
217
- }
218
- function removeMarkdownSection(existing, name) {
219
- existing = repairOrphanMarkers(existing, name);
220
- const { open, close } = markers(name);
221
- const start = existing.indexOf(open);
222
- const end = existing.indexOf(close);
223
- if (start === -1 || end === -1 || end < start) return existing;
224
- const before = existing.slice(0, start).replace(/\n+$/, "\n");
225
- const after = existing.slice(end + close.length).replace(/^\n+/, "\n");
226
- const joined = before + after;
227
- return joined.trim() === "" ? "" : joined.replace(/^\n+/, "");
228
- }
229
- function stripLeadingHtmlComments(md) {
230
- let out = md.trimStart();
231
- while (out.startsWith("<!--")) {
232
- const end = out.indexOf("-->");
233
- if (end === -1) break;
234
- out = out.slice(end + 3).trimStart();
235
- }
236
- return out;
237
- }
238
- function upsertJson(existing, mutate) {
239
- let root = {};
240
- if (existing !== null && existing.trim() !== "") {
241
- root = JSON.parse(existing);
242
- }
243
- mutate(root);
244
- return JSON.stringify(root, null, 2) + "\n";
245
- }
246
- function tomlRootEnd(lines) {
247
- const mask = multilineStringMask(lines);
248
- const index = lines.findIndex((line, lineIndex) => !mask[lineIndex] && headerName(line) !== null);
249
- return index === -1 ? lines.length : index;
250
- }
251
- function rootKeyLineIndex(lines, key) {
252
- const end = tomlRootEnd(lines);
253
- const mask = multilineStringMask(lines);
254
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
255
- const pattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
256
- return lines.slice(0, end).findIndex((line, index) => !mask[index] && pattern.test(line));
257
- }
258
- function hasTomlRootKey(existing, key) {
259
- if (existing === null || existing === "") return false;
260
- return rootKeyLineIndex(existing.replace(/\r\n/g, "\n").split("\n"), key) !== -1;
261
- }
262
- function upsertTomlRootKeyIfMissing(existing, key, value) {
263
- if (hasTomlRootKey(existing, key)) return existing;
264
- const eol = existing?.includes("\r\n") ? "\r\n" : "\n";
265
- const normalized = (existing ?? "").replace(/\r\n/g, "\n");
266
- const lines = normalized === "" ? [] : normalized.split("\n");
267
- const index = tomlRootEnd(lines);
268
- lines.splice(index, 0, `${key} = ${value}`);
269
- return lines.join(eol);
270
- }
271
- function removeTomlRootKeyIfExact(existing, key, value) {
272
- const eol = existing.includes("\r\n") ? "\r\n" : "\n";
273
- const normalized = existing.replace(/\r\n/g, "\n");
274
- const lines = normalized.split("\n");
275
- const index = rootKeyLineIndex(lines, key);
276
- if (index === -1) return existing;
277
- const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
278
- const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
279
- const exact = new RegExp(`^\\s*(?:${escapedKey}|"${escapedKey}"|'${escapedKey}')\\s*=\\s*${escapedValue}\\s*(?:#.*)?$`);
280
- if (!exact.test(lines[index])) return existing;
281
- lines.splice(index, 1);
282
- return lines.join(eol);
283
- }
284
- function headerName(line) {
285
- const match = /^\s*\[\s*([^\]]+?)\s*\]\s*(#.*)?$/.exec(line);
286
- if (!match) return null;
287
- return match[1].split(".").map((part) => part.trim().replace(/^["']|["']$/g, "")).join(".");
288
- }
289
- function quoteRunLength(line, start, quote) {
290
- let end = start + 1;
291
- while (end < line.length && line[end] === quote) end++;
292
- return end - start;
293
- }
294
- function skipShortString(line, start, quote) {
295
- let index = start + 1;
296
- while (index < line.length) {
297
- if (line[index] === quote) return index + 1;
298
- if (quote === '"' && line[index] === "\\") {
299
- index += 2;
300
- continue;
301
- }
302
- index++;
303
- }
304
- return line.length;
305
- }
306
- function scanTomlLine(line, initial) {
307
- let inside = initial;
308
- let index = 0;
309
- while (index < line.length) {
310
- if (inside !== null) {
311
- const quote = inside === '"""' ? '"' : "'";
312
- if (line[index] === quote) {
313
- const run2 = quoteRunLength(line, index, quote);
314
- if (run2 >= 3) {
315
- inside = null;
316
- }
317
- index += run2;
318
- continue;
319
- }
320
- if (inside === '"""' && line[index] === "\\") {
321
- index += 2;
322
- continue;
323
- }
324
- index++;
325
- continue;
326
- }
327
- const char = line[index];
328
- if (char === "#") break;
329
- if (char !== '"' && char !== "'") {
330
- index++;
331
- continue;
332
- }
333
- const run = quoteRunLength(line, index, char);
334
- if (run >= 3) {
335
- inside = char === '"' ? '"""' : "'''";
336
- index += 3;
337
- } else {
338
- index = skipShortString(line, index, char);
339
- }
340
- }
341
- return inside;
342
- }
343
- function multilineStringMask(lines) {
344
- const mask = [];
345
- let inside = null;
346
- for (const line of lines) {
347
- mask.push(inside !== null);
348
- inside = scanTomlLine(line, inside);
349
- }
350
- return mask;
351
- }
352
- function findTomlSection(lines, section) {
353
- const mask = multilineStringMask(lines);
354
- const start = lines.findIndex((line, i) => !mask[i] && headerName(line) === section);
355
- if (start === -1) return null;
356
- let end = start + 1;
357
- while (end < lines.length && (mask[end] || headerName(lines[end]) === null)) end++;
358
- return { start, end };
359
- }
360
- function upsertTomlSection(existing, section, body) {
361
- const header = `[${section}]`;
362
- const target = headerName(header) ?? section;
363
- const block = `${header}
364
- ${body.trim()}
365
- `;
366
- if (existing === null || existing.trim() === "") return block;
367
- const rawLines = existing.split("\n");
368
- const lines = rawLines.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
369
- const lineStarts = [];
370
- let offset = 0;
371
- for (let index = 0; index < rawLines.length; index++) {
372
- lineStarts.push(offset);
373
- offset += rawLines[index].length;
374
- if (index < rawLines.length - 1) offset++;
375
- }
376
- const found = findTomlSection(lines, target);
377
- if (found === null) {
378
- const sep = existing.endsWith("\n") ? "\n" : "\n\n";
379
- return existing + sep + block;
380
- }
381
- let sectionEnd = found.end;
382
- while (sectionEnd > found.start + 1 && lines[sectionEnd - 1].trim() === "") sectionEnd--;
383
- const startOffset = lineStarts[found.start];
384
- const endOffset = lineStarts[sectionEnd] ?? existing.length;
385
- return existing.slice(0, startOffset) + block.trimEnd() + "\n" + existing.slice(endOffset);
386
- }
387
- function removeTomlSection(existing, section) {
388
- const eol = existing.includes("\r\n") ? "\r\n" : "\n";
389
- const lines = existing.split(/\r?\n/);
390
- const found = findTomlSection(lines, section);
391
- if (found === null) return existing;
392
- let realStart = found.start;
393
- while (realStart > 0 && lines[realStart - 1].trim() === "") realStart--;
394
- const result = [...lines.slice(0, realStart), ...lines.slice(found.end)].join(eol);
395
- if (result.trim() === "") return "";
396
- return result.endsWith(eol) ? result : result + eol;
397
- }
398
- function readTomlSection(existing, section) {
399
- if (existing === null) return null;
400
- const lines = existing.split(/\r?\n/);
401
- const found = findTomlSection(lines, section);
402
- if (found === null) return null;
403
- return lines.slice(found.start + 1, found.end).join("\n");
404
- }
405
- function hasTomlChildSection(existing, section) {
406
- if (existing === null) return false;
407
- const lines = existing.split(/\r?\n/);
408
- const mask = multilineStringMask(lines);
409
- return lines.some((line, index) => !mask[index] && headerName(line)?.startsWith(`${section}.`) === true);
410
- }
411
-
412
- // src/lib/writing-style.ts
413
85
  var DEFAULT_SECTION = "writing-style-default";
414
86
  var DEFAULT_OPEN = `<!-- jorgex:${DEFAULT_SECTION} -->`;
415
87
  var DEFAULT_CLOSE = `<!-- /jorgex:${DEFAULT_SECTION} -->`;
416
88
  function assertContained(file, rootDir) {
417
- const relative = path3.relative(fs3.realpathSync(rootDir), fs3.realpathSync(file));
418
- if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
89
+ const relative = path.relative(fs.realpathSync(rootDir), fs.realpathSync(file));
90
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
419
91
  throw new Error(`La ruta de estilo ${file} sale del destino aislado.`);
420
92
  }
421
93
  }
422
94
  function validateBackupRoot(rootDir) {
423
95
  if (rootDir === void 0) return;
424
- const backupRoot = path3.join(rootDir, "backups");
96
+ const backupRoot = path.join(rootDir, "backups");
425
97
  try {
426
- fs3.lstatSync(backupRoot);
98
+ fs.lstatSync(backupRoot);
427
99
  } catch (error) {
428
100
  if (error.code === "ENOENT") return;
429
101
  throw error;
430
102
  }
431
103
  assertContained(backupRoot, rootDir);
432
- if (!fs3.statSync(backupRoot).isDirectory()) throw new Error(`El destino de backups ${backupRoot} no es un directorio.`);
104
+ if (!fs.statSync(backupRoot).isDirectory()) throw new Error(`El destino de backups ${backupRoot} no es un directorio.`);
433
105
  }
434
106
  function resolveWritingStyleFile(options = {}) {
435
- return path3.join(options.targetDir ?? options.stateDir ?? dataDir(), "writing-style.md");
107
+ return path.join(options.targetDir ?? options.stateDir ?? dataDir(), "writing-style.md");
436
108
  }
437
109
  function readStyleText(sourcePath, options = {}) {
438
110
  let stat;
439
111
  try {
440
- stat = fs3.lstatSync(sourcePath);
112
+ stat = fs.lstatSync(sourcePath);
441
113
  } catch (error) {
442
114
  if (error.code === "ENOENT") return null;
443
115
  throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
@@ -445,12 +117,12 @@ function readStyleText(sourcePath, options = {}) {
445
117
  if (options.rootDir !== void 0) {
446
118
  assertContained(sourcePath, options.rootDir);
447
119
  }
448
- if (!(stat.isSymbolicLink() ? fs3.statSync(sourcePath) : stat).isFile()) {
120
+ if (!(stat.isSymbolicLink() ? fs.statSync(sourcePath) : stat).isFile()) {
449
121
  throw new Error(`La fuente de estilo ${sourcePath} debe ser un archivo.`);
450
122
  }
451
123
  let bytes;
452
124
  try {
453
- bytes = fs3.readFileSync(sourcePath);
125
+ bytes = fs.readFileSync(sourcePath);
454
126
  } catch (error) {
455
127
  throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
456
128
  }
@@ -468,1884 +140,46 @@ function readWritingStyle(sourcePath, options = {}) {
468
140
  return { sourcePath, content: content || null };
469
141
  }
470
142
  function prepareWritingStyle(sourcePath, options = {}) {
471
- const canonicalPath = path3.join(options.stackDir ?? stackRoot(), "system-prompt", "writing-style.md");
143
+ const canonicalPath = path.join(options.stackDir ?? stackRoot(), "system-prompt", "writing-style.md");
472
144
  const canonical = readWritingStyle(canonicalPath).content;
473
145
  if (canonical === null) throw new Error(`Falta el estilo can\xF3nico o est\xE1 vac\xEDo en ${canonicalPath}; repara la instalaci\xF3n de Stack.`);
474
146
  const originalContent = readStyleText(sourcePath, options);
475
147
  if (originalContent?.includes("jorgex:")) {
476
148
  if (!hasHealthyManagedMarkdownMarkers(originalContent, DEFAULT_SECTION) || originalContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").includes("jorgex:")) {
477
- throw new Error(`Marcadores de estilo ambiguos o desconocidos en ${sourcePath}; revisa el bloque ${DEFAULT_SECTION}.`);
478
- }
479
- }
480
- const installedContent = upsertMarkdownSection(originalContent, DEFAULT_SECTION, canonical);
481
- if (originalContent !== null && originalContent !== installedContent) validateBackupRoot(options.rootDir);
482
- const content = installedContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").replace(/\r\n?/g, "\n").trim();
483
- return {
484
- sourcePath,
485
- content,
486
- canonicalPath,
487
- originalContent,
488
- installedContent,
489
- rootDir: options.rootDir,
490
- backupRoot: options.rootDir === void 0 ? void 0 : path3.join(options.rootDir, "backups")
491
- };
492
- }
493
- function applyWritingStyle(plan, dryRun = false) {
494
- if (dryRun) return;
495
- const current = readStyleText(plan.sourcePath, { rootDir: plan.rootDir });
496
- if (current === plan.installedContent) return;
497
- if (current !== plan.originalContent) {
498
- throw new Error(`El estilo ${plan.sourcePath} cambi\xF3 durante la preparaci\xF3n; vuelve a ejecutar el comando.`);
499
- }
500
- if (current !== null) {
501
- validateBackupRoot(plan.rootDir);
502
- createBackup([plan.sourcePath], "writing-style", plan.backupRoot);
503
- }
504
- const mode = current === null ? 384 : fs3.statSync(plan.sourcePath).mode & 511;
505
- writeText(plan.sourcePath, plan.installedContent, mode);
506
- }
507
-
508
- // src/lib/system-prompt-sections.ts
509
- import fs4 from "fs";
510
- import path4 from "path";
511
- var SYSTEM_PROMPT_SECTIONS = [
512
- "system-prompt",
513
- "engram-protocol",
514
- "context7",
515
- "playwright",
516
- "chrome-devtools",
517
- "browser",
518
- "writing-style"
519
- ];
520
- function assertSystemPromptMarkers(content, target) {
521
- if (content === null) return;
522
- const ranges = [];
523
- for (const section of SYSTEM_PROMPT_SECTIONS) {
524
- const markers2 = [...content.matchAll(new RegExp(`<!--\\s*\\/?jorgex:${section}(?=[\\s>]|-->|$)`, "g"))];
525
- if (markers2.length === 0) continue;
526
- if (markers2.length !== 2 || !hasHealthyManagedMarkdownMarkers(content, section)) {
527
- throw new Error(`Marcadores jorgex:${section} ambiguos en ${target}. Repara el bloque antes de reintentar; su contenido se conserva.`);
528
- }
529
- ranges.push({
530
- section,
531
- start: content.indexOf(`<!-- jorgex:${section} -->`),
532
- end: content.indexOf(`<!-- /jorgex:${section} -->`) + `<!-- /jorgex:${section} -->`.length
533
- });
534
- }
535
- ranges.sort((a, b) => a.start - b.start);
536
- for (let index = 1; index < ranges.length; index++) {
537
- if (ranges[index].start < ranges[index - 1].end) {
538
- throw new Error(`Marcadores jorgex:${ranges[index].section} anidados en ${target}. Separa los bloques antes de reintentar; su contenido se conserva.`);
539
- }
540
- }
541
- }
542
- function readSystemPromptFile(target, rootDir) {
543
- if (rootDir !== void 0) {
544
- const probe = fs4.existsSync(target) ? target : path4.dirname(target);
545
- if (fs4.existsSync(probe) && fs4.existsSync(rootDir)) {
546
- const resolved = fs4.realpathSync(probe);
547
- const root = fs4.realpathSync(rootDir);
548
- if (resolved !== root && !isContainedIn(resolved, root)) {
549
- throw new Error(`El prompt ${target} sale del destino aislado.`);
550
- }
551
- }
552
- }
553
- let bytes;
554
- try {
555
- bytes = fs4.readFileSync(target);
556
- } catch (error) {
557
- if (error.code === "ENOENT") return null;
558
- throw new Error(`No se puede leer el prompt ${target}. Revisa los permisos y el tipo de archivo.`, { cause: error });
559
- }
560
- try {
561
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
562
- } catch (error) {
563
- throw new Error(`El prompt ${target} debe usar UTF-8 v\xE1lido.`, { cause: error });
564
- }
565
- }
566
- function assertSystemPromptFile(target, rootDir) {
567
- assertSystemPromptMarkers(readSystemPromptFile(target, rootDir), target);
568
- }
569
- function removeSystemPromptSections(content) {
570
- assertSystemPromptMarkers(content, "system prompt");
571
- return SYSTEM_PROMPT_SECTIONS.reduce((current, section) => removeMarkdownSection(current, section), content);
572
- }
573
-
574
- // src/adapters/opencode.ts
575
- import path9 from "path";
576
- import fs6 from "fs";
577
- import { isDeepStrictEqual } from "util";
578
- import { pathToFileURL } from "url";
579
-
580
- // src/lib/canonical.ts
581
- import path5 from "path";
582
- import fs5 from "fs";
583
- function parseCanonicalAgent(source, file) {
584
- const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
585
- if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
586
- const fields = {};
587
- for (const line of match[1].split(/\r?\n/)) {
588
- const sep = line.indexOf(":");
589
- if (sep === -1) continue;
590
- fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
591
- }
592
- const required = ["name", "description", "mode", "tier"];
593
- for (const key of required) {
594
- if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
595
- }
596
- return {
597
- name: fields.name,
598
- description: fields.description,
599
- mode: fields.mode === "primary" ? "primary" : "subagent",
600
- tier: fields.tier,
601
- readonly: fields.readonly === "true",
602
- bash: fields.bash ?? "full",
603
- spawn: fields.spawn !== "false",
604
- body: source.slice(match[0].length)
605
- };
606
- }
607
- function loadCanonicalAgents(agentsDir) {
608
- return fs5.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
609
- const file = path5.join(agentsDir, f);
610
- const source = fs5.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
611
- return parseCanonicalAgent(source, file);
612
- });
613
- }
614
- var DEVTOOLS_MCP_SERVER = "chrome-devtools";
615
- function isCanonicalMcpServerEnabled(name, server, enabledServers) {
616
- return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
617
- }
618
- function loadCanonicalMcp(stackDir) {
619
- return JSON.parse(fs5.readFileSync(path5.join(stackDir, "mcp", "servers.json"), "utf8"));
620
- }
621
- function loadCanonicalHooks(stackDir) {
622
- return JSON.parse(fs5.readFileSync(path5.join(stackDir, "hooks", "hooks.json"), "utf8"));
623
- }
624
- function loadCanonicalDefaults(stackDir) {
625
- const file = path5.join(stackDir, "config", "defaults.json");
626
- if (!fs5.existsSync(file)) return {};
627
- const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
628
- delete parsed["$comment"];
629
- return parsed;
630
- }
631
-
632
- // src/lib/model-map.ts
633
- import path6 from "path";
634
- import { existsSync } from "fs";
635
- function resolveAgentModel(models, agentName, tier) {
636
- const base = models[tier];
637
- const override = models.overrides?.[agentName];
638
- if (!override) return base;
639
- return {
640
- model: override.model || base.model,
641
- variant: "variant" in override ? override.variant || void 0 : base.variant
642
- };
643
- }
644
- var DEFAULT_MODEL_MAP = {
645
- "claude-code": {
646
- strong: { model: "fable" },
647
- standard: { model: "sonnet" },
648
- cheap: { model: "haiku" }
649
- },
650
- // El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
651
- // la skill de la app heredan el modelo elegido por el usuario. Estos defaults
652
- // son solo para subagentes; variant → model_reasoning_effort.
653
- codex: {
654
- strong: { model: "gpt-6-astra", variant: "low" },
655
- standard: { model: "gpt-5.6-luna", variant: "max" },
656
- cheap: { model: "gpt-5.6-luna", variant: "medium" }
657
- }
658
- };
659
- function modelMapFile() {
660
- return path6.join(dataDir(), "model-map.json");
661
- }
662
- function loadModelMap() {
663
- const file = modelMapFile();
664
- const raw = readTextIfExists(file);
665
- if (raw === null) return DEFAULT_MODEL_MAP;
666
- let fromDisk;
667
- try {
668
- fromDisk = JSON.parse(raw);
669
- } catch {
670
- throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
671
- }
672
- const merged = { ...fromDisk };
673
- for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
674
- merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
675
- if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
676
- }
677
- return merged;
678
- }
679
- function ensureModelMapFile() {
680
- const file = modelMapFile();
681
- if (!existsSync(file)) {
682
- writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
683
- }
684
- return file;
685
- }
686
-
687
- // src/lib/detect.ts
688
- import path7 from "path";
689
- import { existsSync as existsSync2, statSync } from "fs";
690
- import { execFileSync } from "child_process";
691
- function lookPath(cmd, env = process.env) {
692
- const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
693
- for (const dir of (env.PATH ?? "").split(path7.delimiter)) {
694
- if (!dir) continue;
695
- for (const ext of exts) {
696
- const candidate = path7.join(dir, cmd + ext);
697
- if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
698
- }
699
- }
700
- return null;
701
- }
702
- function planDetectedBinCommand(bin, args) {
703
- if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
704
- if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
705
- const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
706
- return {
707
- command: process.env.ComSpec ?? "cmd.exe",
708
- args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
709
- };
710
- }
711
- function runDetectedBin(bin, args, timeoutMs, envOverrides) {
712
- try {
713
- const command = planDetectedBinCommand(bin, args);
714
- if (command === null) return null;
715
- return execFileSync(command.command, command.args, {
716
- encoding: "utf8",
717
- timeout: timeoutMs,
718
- stdio: ["ignore", "pipe", "pipe"],
719
- ...envOverrides === void 0 ? {} : { env: { ...process.env, ...envOverrides } }
720
- });
721
- } catch {
722
- return null;
723
- }
724
- }
725
- function detectOpenCode() {
726
- const configDir = process.env.OPENCODE_CONFIG_DIR ?? path7.join(HOME, ".config", "opencode");
727
- const binPath = lookPath("opencode");
728
- return {
729
- id: "opencode",
730
- name: "OpenCode",
731
- installed: binPath !== null || existsSync2(configDir),
732
- binPath,
733
- configDir
734
- };
735
- }
736
- function detectClaudeCode() {
737
- const configDir = path7.join(HOME, ".claude");
738
- const binPath = lookPath("claude");
739
- return {
740
- id: "claude-code",
741
- name: "Claude Code",
742
- installed: binPath !== null || existsSync2(configDir),
743
- binPath,
744
- configDir
745
- };
746
- }
747
- function detectCodex() {
748
- const configDir = process.env.CODEX_HOME ?? path7.join(HOME, ".codex");
749
- const binPath = lookPath("codex");
750
- return {
751
- id: "codex",
752
- name: "Codex CLI",
753
- installed: binPath !== null || existsSync2(configDir),
754
- binPath,
755
- configDir
756
- };
757
- }
758
- function detectEngram() {
759
- const fromEnv = process.env.ENGRAM_BIN;
760
- if (fromEnv && existsSync2(fromEnv)) return fromEnv;
761
- const onPath = lookPath("engram");
762
- if (onPath) return onPath;
763
- const candidates = [
764
- path7.join(HOME, "go", "bin", "engram.exe"),
765
- path7.join(HOME, "go", "bin", "engram"),
766
- path7.join(HOME, ".local", "bin", "engram.exe"),
767
- path7.join(HOME, ".local", "bin", "engram")
768
- ];
769
- for (const c of candidates) if (existsSync2(c)) return c;
770
- return null;
771
- }
772
-
773
- // src/lib/hooks-format.ts
774
- import path8 from "path";
775
- function hookScriptNames(canonical) {
776
- 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) => path8.basename(s));
777
- }
778
- function plainHookCommands(canonical) {
779
- return new Set(
780
- Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"))
781
- );
782
- }
783
- function removeNativeHooks(existing, canonical) {
784
- if (existing === null || existing.trim() === "") return existing;
785
- const scriptNames = hookScriptNames(canonical);
786
- const plainCommands = plainHookCommands(canonical);
787
- return upsertJson(existing, (root) => {
788
- const hooks = root["hooks"];
789
- if (!hooks) return;
790
- for (const event of Object.keys(hooks)) {
791
- const list = hooks[event];
792
- if (!Array.isArray(list)) continue;
793
- hooks[event] = list.filter((entry) => {
794
- const e = entry;
795
- if (!Array.isArray(e?.hooks)) return true;
796
- const ours = e.hooks.some((hh) => {
797
- const cmd = String(hh?.command ?? "");
798
- return scriptNames.some((s) => cmd.includes(s)) || plainCommands.has(cmd);
799
- });
800
- return !ours;
801
- });
802
- if (hooks[event].length === 0) delete hooks[event];
803
- }
804
- if (Object.keys(hooks).length === 0) delete root["hooks"];
805
- });
806
- }
807
- function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) => m) {
808
- const scriptsDirForCommand = scriptsDir.replace(/\\/g, "/");
809
- return upsertJson(existing, (root) => {
810
- const hooks = root["hooks"] ??= {};
811
- for (const [event, entries] of Object.entries(canonical.hooks)) {
812
- const list = hooks[event] ??= [];
813
- for (const entry of entries) {
814
- const matcher = entry.matcher !== void 0 ? mapMatcher(entry.matcher) : void 0;
815
- const rendered = {
816
- ...matcher !== void 0 ? { matcher } : {},
817
- hooks: entry.hooks.map((h) => ({
818
- type: h.type,
819
- command: h.command.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsDirForCommand),
820
- ...h.timeout !== void 0 ? { timeout: h.timeout } : {}
821
- }))
822
- };
823
- const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path8.basename(s));
824
- const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
825
- const index = list.findIndex((existingEntry) => {
826
- const e = existingEntry;
827
- if (!Array.isArray(e?.hooks)) return false;
828
- return e.hooks.some((hh) => {
829
- const cmd = String(hh?.command ?? "");
830
- return scriptNames.some((s) => cmd.includes(s)) || plainCommands.includes(cmd);
831
- });
832
- });
833
- if (index >= 0) list[index] = rendered;
834
- else list.push(rendered);
835
- }
836
- }
837
- });
838
- }
839
-
840
- // src/lib/quality-capabilities.ts
841
- var QUALITY_CAPABILITY_NAMESPACE = "jorgex.quality.capabilities";
842
- var QUALITY_CAPABILITY_VERSION = 1;
843
- var QUALITY_CAPABILITY_IDS = [
844
- "policy-guidance",
845
- "tool-approval",
846
- "external-verification"
847
- ];
848
- var UNAVAILABLE_REASON = "No reviewed local declaration is available";
849
- var EXTERNAL_VERIFICATION_REASON = "External verification is available only through the external verifier";
850
- function hasText(value) {
851
- return typeof value === "string" && value.trim() !== "";
852
- }
853
- function isRuntime(value) {
854
- return value === "claude-code" || value === "codex" || value === "opencode" || value === "pi";
855
- }
856
- function hasValidEvidence(value) {
857
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
858
- const evidence = value;
859
- return hasText(evidence.source) && hasText(evidence.version);
860
- }
861
- function unavailableCapability(id, reason = UNAVAILABLE_REASON) {
862
- return { id, state: "unavailable", reason };
863
- }
864
- function declarationFor(id, declarations, runtime) {
865
- if (runtime === "unknown") {
866
- return unavailableCapability(id, "Runtime is unknown; local capability cannot be established");
867
- }
868
- if (id === "external-verification") {
869
- return unavailableCapability(id, EXTERNAL_VERIFICATION_REASON);
870
- }
871
- const matches = declarations.filter((declaration2) => declaration2?.id === id);
872
- if (matches.length === 0) return unavailableCapability(id);
873
- if (matches.length !== 1) return unavailableCapability(id, "Duplicate capability declarations are ambiguous");
874
- const declaration = matches[0];
875
- const state = declaration.state;
876
- const reason = hasText(declaration.reason) ? declaration.reason.trim() : void 0;
877
- if (!reason) return unavailableCapability(id, "Capability declaration has no reason");
878
- if (state !== "prompt-only" && state !== "manual" && state !== "unavailable") {
879
- return unavailableCapability(id, "Capability state is not a supported local state");
880
- }
881
- if (state === "unavailable") return { id, state, reason };
882
- if (!hasValidEvidence(declaration.evidence)) {
883
- return unavailableCapability(id, "Capability declaration lacks reviewed source/version evidence");
884
- }
885
- const evidence = declaration.evidence;
886
- return {
887
- id,
888
- state,
889
- reason,
890
- evidence: {
891
- source: evidence.source.trim(),
892
- version: evidence.version.trim()
893
- }
894
- };
895
- }
896
- function hasManagedMarkdownSection(content, name) {
897
- return content !== null && hasHealthyManagedMarkdownMarkers(content, name);
898
- }
899
- function createLocalCapabilityReport(runtime, declarations) {
900
- const normalizedRuntime = isRuntime(runtime) ? runtime : "unknown";
901
- return {
902
- namespace: QUALITY_CAPABILITY_NAMESPACE,
903
- version: QUALITY_CAPABILITY_VERSION,
904
- runtime: normalizedRuntime,
905
- capabilities: QUALITY_CAPABILITY_IDS.map((id) => declarationFor(id, declarations, normalizedRuntime))
906
- };
907
- }
908
- function localQualityStatus(profile, status) {
909
- if ((profile === "high" || profile === "release") && status === "pass") return "incomplete";
910
- return status;
911
- }
912
-
913
- // src/adapters/opencode.ts
914
- var gitReadPrefix = "git --no-pager -c core.fsmonitor=false -c log.showSignature=false";
915
- var gitReadCommands = [
916
- "diff",
917
- "diff --stat",
918
- "diff --name-only",
919
- "diff --cached",
920
- "log",
921
- "log --oneline -10"
922
- ].map((action) => `${gitReadPrefix} ${action} --no-ext-diff --no-textconv --end-of-options`);
923
- function yamlString(value) {
924
- return JSON.stringify(value);
925
- }
926
- function readMcpConfig(file) {
927
- let content;
928
- try {
929
- content = fs6.readFileSync(file, "utf8");
930
- } catch (error) {
931
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
932
- if (code === "ENOENT") return null;
933
- throw new Error(`OpenCode: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
934
- }
935
- if (content.trim() !== "") {
936
- try {
937
- if (objectValue(JSON.parse(content)) === null) throw new Error();
938
- } catch {
939
- throw new Error(`OpenCode: la configuraci\xF3n MCP en ${file} debe contener un objeto JSON v\xE1lido.`);
940
- }
941
- }
942
- return content;
943
- }
944
- function isManagedOptionalStdioServer(server, value) {
945
- if (!server.optional || server.transport !== "stdio" || value === null || typeof value !== "object" || Array.isArray(value)) {
946
- return false;
947
- }
948
- const current = value;
949
- const expectedCommand = [server.command, ...server.args ?? []];
950
- return Object.keys(current).length === 2 && current.type === "local" && Array.isArray(current.command) && current.command.length === expectedCommand.length && current.command.every((arg, index) => arg === expectedCommand[index]);
951
- }
952
- var PRIMARY_MODEL = "openai/gpt-5.6-sol";
953
- var PRIMARY_MODEL_ID = "gpt-5.6-sol";
954
- var PRIMARY_LIMITS = { context: 872e3, input: 744e3, output: 128e3 };
955
- var PRIMARY_MODEL_FIELD = "model";
956
- var PRIMARY_PROVIDER_FIELD = "provider";
957
- var PRIMARY_OPENAI_FIELD = "provider.openai";
958
- var PRIMARY_MODELS_FIELD = "provider.openai.models";
959
- var PRIMARY_SOL_FIELD = `provider.openai.models.${PRIMARY_MODEL_ID}`;
960
- var PRIMARY_LIMIT_PREFIX = `provider.openai.models.${PRIMARY_MODEL_ID}.limit`;
961
- function objectValue(value) {
962
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
963
- }
964
- function isCompatibleContext7Server(server, value) {
965
- const current = objectValue(value);
966
- if (server.transport !== "http" || typeof server.url !== "string" || current === null) return false;
967
- return current.type === "remote" && current.url === server.url && (current.enabled === void 0 || current.enabled === true);
968
- }
969
- function canonicalContext7Server(server) {
970
- const headers = {};
971
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
972
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
973
- headers[key] = envRef ? `{env:${envRef[1]}}` : raw;
974
- }
975
- return {
976
- type: "remote",
977
- url: server.url,
978
- ...Object.keys(headers).length > 0 ? { headers } : {}
979
- };
980
- }
981
- function isCanonicalContext7Server(server, value) {
982
- return isCompatibleContext7Server(server, value) && isDeepStrictEqual(value, canonicalContext7Server(server));
983
- }
984
- function assertCompatibleContext7(server, value) {
985
- if (value !== void 0 && !isCompatibleContext7Server(server, value)) {
986
- throw new Error("OpenCode: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint, tipo o estado nativo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
987
- }
988
- }
989
- function ensureObject(parent, key, fieldPath) {
990
- if (parent[key] === void 0) parent[key] = {};
991
- const value = objectValue(parent[key]);
992
- if (value === null) throw new Error(`OpenCode: '${fieldPath}' debe ser un objeto; corr\xEDgelo antes de reintentar sync.`);
993
- return value;
994
- }
995
- function ensureOwnedPrimaryObject(parent, key, field, owned, changes) {
996
- const created = parent[key] === void 0;
997
- const value = ensureObject(parent, key, field);
998
- if (created && owned?.has(field) !== true) changes.push({ field, owned: true });
999
- return value;
1000
- }
1001
- function pruneEmpty(parent, key) {
1002
- const value = objectValue(parent[key]);
1003
- if (value !== null && Object.keys(value).length === 0) delete parent[key];
1004
- }
1005
- function hasOpenCodeManualApproval(configDir) {
1006
- const content = readTextIfExists(path9.join(configDir, "opencode.json"));
1007
- if (content === null) return false;
1008
- try {
1009
- const root = JSON.parse(content);
1010
- const permission = objectValue(objectValue(root)?.permission);
1011
- const expected = loadCanonicalDefaults(stackRoot())["opencode"]?.["permission"];
1012
- return permission !== null && expected !== void 0 && isDeepStrictEqual(permission, expected);
1013
- } catch {
1014
- return false;
1015
- }
1016
- }
1017
- var opencodeAdapter = {
1018
- id: "opencode",
1019
- name: "OpenCode",
1020
- detect: detectOpenCode,
1021
- reportCapabilities(configDir) {
1022
- const prompt = readTextIfExists(path9.join(configDir, "AGENTS.md"));
1023
- return createLocalCapabilityReport("opencode", [
1024
- ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
1025
- id: "policy-guidance",
1026
- state: "prompt-only",
1027
- reason: "The managed policy prompt is advisory and cannot enforce the policy",
1028
- evidence: { source: "jorgex-stack-system-prompt", version: "1" }
1029
- }] : [],
1030
- ...hasOpenCodeManualApproval(configDir) ? [{
1031
- id: "tool-approval",
1032
- state: "manual",
1033
- reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
1034
- evidence: { source: "jorgex-stack-opencode-approval-policy", version: "1" }
1035
- }] : []
1036
- ]);
1037
- },
1038
- injectEngramProtocol() {
1039
- return false;
1040
- },
1041
- paths(configDir) {
1042
- const isRealConfigDir = samePath(
1043
- configDir,
1044
- process.env.OPENCODE_CONFIG_DIR ?? path9.join(HOME, ".config", "opencode")
1045
- );
1046
- const agentsHome = isRealConfigDir ? HOME : path9.dirname(configDir);
1047
- return {
1048
- systemPromptFile: path9.join(configDir, "AGENTS.md"),
1049
- agentsDir: path9.join(configDir, "agents"),
1050
- skillsDir: path9.join(agentsHome, ".agents", "skills"),
1051
- commandsDir: path9.join(configDir, "commands"),
1052
- pluginsDir: path9.join(configDir, "plugins"),
1053
- scriptsDir: path9.join(configDir, "scripts"),
1054
- outputStylesDir: null,
1055
- profilesDir: null
1056
- };
1057
- },
1058
- renderAgent(agent, models) {
1059
- const lines = [`description: ${yamlString(agent.description)}`, `mode: ${agent.mode}`];
1060
- if (agent.mode === "subagent") {
1061
- const tierModel = resolveAgentModel(models, agent.name, agent.tier);
1062
- lines.push(`model: ${tierModel.model}`);
1063
- if (tierModel.variant) lines.push(`variant: ${tierModel.variant}`);
1064
- if (agent.readonly || agent.bash !== "full" || !agent.spawn) lines.push("permission:");
1065
- if (agent.readonly) lines.push(" edit: deny");
1066
- if (agent.bash === "none") lines.push(" bash: deny");
1067
- else if (agent.bash === "git-read") {
1068
- lines.push(' bash:\n "*": deny');
1069
- for (const command of gitReadCommands) {
1070
- lines.push(` ${yamlString(command)}: allow`, ` ${yamlString(`${command} *`)}: allow`);
1071
- }
1072
- const permission = objectValue(loadCanonicalDefaults(stackRoot())["opencode"]?.permission);
1073
- const bash = objectValue(permission?.bash);
1074
- if (bash === null) throw new Error("OpenCode: canonical Bash policy is required for git-read agents.");
1075
- for (const [pattern, decision] of Object.entries(bash)) {
1076
- if (decision === "deny") lines.push(` ${yamlString(pattern)}: deny`);
1077
- }
1078
- }
1079
- if (!agent.spawn) lines.push(" task: deny");
1080
- }
1081
- return [
1082
- {
1083
- file: `${agent.name}.md`,
1084
- content: `---
1085
- ${lines.join("\n")}
1086
- ---
1087
- ${agent.body}${agent.bash === "git-read" ? `
1088
-
1089
- Use only these read-only Git command prefixes; put refs and paths after --end-of-options:
1090
- ${gitReadCommands.map((command) => `- \`${command}\``).join("\n")}
1091
- ` : ""}`,
1092
- kind: "agent"
1093
- }
1094
- ];
1095
- },
1096
- renderCommand(file, content) {
1097
- return { file, content: content.replace(/\{\{input\}\}/g, "$ARGUMENTS") };
1098
- },
1099
- planHooks(canonical, ctx) {
1100
- const actions = [];
1101
- const { scriptsDir } = this.paths(ctx.configDir);
1102
- const bashEntries = {};
1103
- for (const [event, entries] of Object.entries(canonical.hooks)) {
1104
- if (event !== "PostToolUse") {
1105
- ctx.warnings.push(`opencode: evento de hook '${event}' a\xFAn no soportado por el puente \u2014 omitido.`);
1106
- continue;
1107
- }
1108
- for (const entry of entries) {
1109
- if (!(entry.matcher ?? "").split("|").some((p7) => p7.trim().toLowerCase() === "bash")) {
1110
- ctx.warnings.push(`opencode: matcher de hook '${entry.matcher}' no soportado \u2014 omitido.`);
1111
- continue;
1112
- }
1113
- const includes = entry["x-command-includes"] ?? "*";
1114
- for (const hook of entry.hooks) {
1115
- const match = /\{\{SCRIPTS_DIR\}\}[/\\]([\w./\\-]+)/.exec(hook.command);
1116
- if (!match) {
1117
- ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
1118
- continue;
1119
- }
1120
- const script = `scripts/${path9.basename(match[1])}`;
1121
- (bashEntries[includes] ??= []).push(script);
1122
- }
1123
- }
1124
- }
1125
- const hooksFile = path9.join(ctx.configDir, "hooks.json");
1126
- const content = upsertJson(readTextIfExists(hooksFile), (root) => {
1127
- const afterValue = root["tool.execute.after"] ??= {};
1128
- if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
1129
- ctx.warnings.push("opencode: tool.execute.after no es un objeto; hooks gestionados omitidos.");
1130
- return;
1131
- }
1132
- const after = afterValue;
1133
- const bashValue = after["bash"];
1134
- if (bashValue !== void 0 && !Array.isArray(bashValue) && (bashValue === null || typeof bashValue !== "object")) {
1135
- ctx.warnings.push("opencode: tool.execute.after.bash no es un array ni un mapa; hooks gestionados omitidos.");
1136
- return;
1137
- }
1138
- const bash = Array.isArray(bashValue) ? { "*": bashValue } : bashValue ?? {};
1139
- after["bash"] = bash;
1140
- const managedScripts = new Set(Object.values(bashEntries).flat());
1141
- for (const [includes, scripts] of Object.entries(bash)) {
1142
- if (!Array.isArray(scripts)) continue;
1143
- const preserved = scripts.filter(
1144
- (script) => typeof script !== "string" || !managedScripts.has(script)
1145
- );
1146
- if (preserved.length > 0) bash[includes] = preserved;
1147
- else delete bash[includes];
1148
- }
1149
- for (const [includes, scripts] of Object.entries(bashEntries)) {
1150
- const current = bash[includes];
1151
- if (current !== void 0 && !Array.isArray(current)) {
1152
- ctx.warnings.push(`opencode: trigger bash '${includes}' no es un array; hook gestionado omitido.`);
1153
- continue;
1154
- }
1155
- const list = bash[includes] ??= [];
1156
- for (const s of scripts) if (!list.includes(s)) list.push(s);
1157
- }
1158
- });
1159
- actions.push({ kind: "write", target: hooksFile, content });
1160
- const scriptsSource = path9.join(ctx.stackDir, "scripts");
1161
- if (fs6.existsSync(scriptsSource)) {
1162
- for (const f of fs6.readdirSync(scriptsSource)) {
1163
- actions.push({ kind: "copy", source: path9.join(scriptsSource, f), target: path9.join(scriptsDir, f) });
1164
- }
1165
- }
1166
- return actions;
1167
- },
1168
- planMainConfig(canonical, ctx) {
1169
- const file = path9.join(ctx.configDir, "opencode.json");
1170
- const { pluginsDir } = this.paths(ctx.configDir);
1171
- const original = readMcpConfig(file);
1172
- const contentSource = original === null || original.trim() === "" ? null : original;
1173
- const isFreshConfig = contentSource === null;
1174
- const mcpOwnership = [];
1175
- const primaryModelOwnership = [];
1176
- const content = upsertJson(contentSource, (root) => {
1177
- const rawMcp = root["mcp"];
1178
- if (rawMcp !== void 0 && objectValue(rawMcp) === null) {
1179
- throw new Error("OpenCode: la clave 'mcp' debe ser un objeto; corr\xEDgela antes de reintentar sync.");
1180
- }
1181
- const existingMcp = rawMcp;
1182
- const context7 = canonical.servers.context7;
1183
- if (context7 !== void 0) assertCompatibleContext7(context7, existingMcp?.["context7"]);
1184
- root["$schema"] ??= "https://opencode.ai/config.json";
1185
- if (root[PRIMARY_MODEL_FIELD] === void 0) {
1186
- root[PRIMARY_MODEL_FIELD] = PRIMARY_MODEL;
1187
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD) !== true) {
1188
- primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD, owned: true });
1189
- }
1190
- } else if (typeof root[PRIMARY_MODEL_FIELD] !== "string" || root[PRIMARY_MODEL_FIELD].trim() === "") {
1191
- throw new Error("OpenCode: 'model' debe ser un identificador provider/model no vac\xEDo; corr\xEDgelo antes de reintentar sync.");
1192
- }
1193
- const provider = ensureOwnedPrimaryObject(root, "provider", PRIMARY_PROVIDER_FIELD, ctx.ownedPrimaryModelFields, primaryModelOwnership);
1194
- const openai = ensureOwnedPrimaryObject(provider, "openai", PRIMARY_OPENAI_FIELD, ctx.ownedPrimaryModelFields, primaryModelOwnership);
1195
- const models = ensureOwnedPrimaryObject(openai, "models", PRIMARY_MODELS_FIELD, ctx.ownedPrimaryModelFields, primaryModelOwnership);
1196
- const sol = ensureOwnedPrimaryObject(models, PRIMARY_MODEL_ID, PRIMARY_SOL_FIELD, ctx.ownedPrimaryModelFields, primaryModelOwnership);
1197
- const limit = ensureOwnedPrimaryObject(sol, "limit", PRIMARY_LIMIT_PREFIX, ctx.ownedPrimaryModelFields, primaryModelOwnership);
1198
- for (const [key, value] of Object.entries(PRIMARY_LIMITS)) {
1199
- if (limit[key] !== void 0) continue;
1200
- limit[key] = value;
1201
- const field = `${PRIMARY_LIMIT_PREFIX}.${key}`;
1202
- if (ctx.ownedPrimaryModelFields?.has(field) !== true) {
1203
- primaryModelOwnership.push({ field, owned: true });
1204
- }
1205
- }
1206
- const defaults = loadCanonicalDefaults(ctx.stackDir)["opencode"];
1207
- const canonicalPermission = defaults?.["permission"];
1208
- if (isFreshConfig) {
1209
- if (canonicalPermission !== void 0) {
1210
- root["permission"] = canonicalPermission;
1211
- ctx.warnings.push(
1212
- "OpenCode: fresh config allows ordinary reads, edits, web access and Bash; sensitive operations ask, while protected paths and obvious destruction are denied. Native matching is not a universal filesystem sandbox."
1213
- );
1214
- }
1215
- } else if (canonicalPermission !== void 0 && !isDeepStrictEqual(root["permission"], canonicalPermission)) {
1216
- if (ctx.upgradePermissions === true) {
1217
- root["permission"] = canonicalPermission;
1218
- } else {
1219
- ctx.warnings.push(
1220
- "OpenCode: permission block differs from the stack default and was left untouched; re-run with --upgrade-permissions to replace it (a backup is created first), or edit it by hand. Overwriting discards your own permission changes, including any extra hardenings."
1221
- );
1222
- }
1223
- }
1224
- const mcp = root["mcp"] ??= {};
1225
- for (const [name, server] of Object.entries(canonical.servers)) {
1226
- const existing = mcp[name];
1227
- const owned = ctx.ownedMcpServers?.has(name) === true;
1228
- if (name === "context7" && existing !== void 0) {
1229
- if (owned && !isCanonicalContext7Server(server, existing)) {
1230
- mcpOwnership.push({ server: name, owned: false });
1231
- }
1232
- continue;
1233
- }
1234
- if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
1235
- if (owned) {
1236
- if (isManagedOptionalStdioServer(server, existing)) delete mcp[name];
1237
- mcpOwnership.push({ server: name, owned: false });
1238
- }
1239
- continue;
1240
- }
1241
- if (server.optional && existing !== void 0) {
1242
- if (!owned || !isManagedOptionalStdioServer(server, existing)) {
1243
- if (owned) mcpOwnership.push({ server: name, owned: false });
1244
- ctx.warnings.push(`OpenCode: MCP opcional '${name}' ya pertenece a la configuraci\xF3n del usuario; se conserva.`);
1245
- continue;
1246
- }
1247
- }
1248
- if (server.transport === "stdio") {
1249
- if (server.command === "{{ENGRAM_BIN}}" && ctx.engramBin === null) {
1250
- ctx.warnings.push(
1251
- "Engram no detectado: el MCP 'engram' no se registra. Inst\xE1lalo (github.com/Gentleman-Programming/engram) y re-ejecuta sync."
1252
- );
1253
- continue;
1254
- }
1255
- const command = server.command === "{{ENGRAM_BIN}}" ? ctx.engramBin : server.command;
1256
- mcp[name] = { type: "local", command: [command, ...server.args ?? []] };
1257
- if (server.optional && existing === void 0 && !owned) mcpOwnership.push({ server: name, owned: true });
1258
- } else {
1259
- const previous = mcp[name];
1260
- const headers = {};
1261
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
1262
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
1263
- headers[key] = previous?.headers?.[key] || (envRef ? `{env:${envRef[1]}}` : raw);
1264
- }
1265
- mcp[name] = {
1266
- type: "remote",
1267
- url: server.url,
1268
- ...Object.keys(headers).length > 0 ? { headers } : {}
1269
- };
1270
- if (name === "context7" && existing === void 0 && !owned) {
1271
- mcpOwnership.push({ server: name, owned: true });
1272
- }
1273
- }
1274
- }
1275
- if (pluginsDir !== null) {
1276
- const plugin = root["plugin"];
1277
- if (Array.isArray(plugin)) {
1278
- const pluginsDirPrefix = pathToFileURL(pluginsDir).href + "/";
1279
- const kept = plugin.filter((url) => !url.startsWith(pluginsDirPrefix));
1280
- if (kept.length === 0) delete root["plugin"];
1281
- else root["plugin"] = kept;
1282
- if (kept.some((u) => /engram/i.test(u))) {
1283
- ctx.warnings.push(
1284
- "OpenCode: hay un plugin de Engram registrado como paquete \u2014 revisa que no conviva con el engram.ts del stack (duplicar\xEDa la integraci\xF3n)."
1285
- );
1286
- }
1287
- }
1288
- }
1289
- });
1290
- return [{
1291
- kind: "write",
1292
- target: file,
1293
- content,
1294
- ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
1295
- ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
1296
- }];
1297
- },
1298
- planUnmerge(mcp, hooks, ctx) {
1299
- const actions = [];
1300
- const { systemPromptFile, pluginsDir } = this.paths(ctx.configDir);
1301
- const prompt = readTextIfExists(systemPromptFile);
1302
- if (prompt !== null) {
1303
- const content = removeSystemPromptSections(prompt);
1304
- actions.push({ kind: "write", target: systemPromptFile, content });
1305
- }
1306
- const configFile = path9.join(ctx.configDir, "opencode.json");
1307
- const config = readMcpConfig(configFile);
1308
- if (config !== null) {
1309
- const mcpOwnership = [];
1310
- const primaryModelOwnership = [];
1311
- const content = upsertJson(config, (root) => {
1312
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD) === true) {
1313
- if (root[PRIMARY_MODEL_FIELD] === PRIMARY_MODEL) delete root[PRIMARY_MODEL_FIELD];
1314
- primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD, owned: false });
1315
- }
1316
- const provider = objectValue(root["provider"]);
1317
- const openai = provider === null ? null : objectValue(provider["openai"]);
1318
- const models = openai === null ? null : objectValue(openai["models"]);
1319
- const sol = models === null ? null : objectValue(models[PRIMARY_MODEL_ID]);
1320
- const limit = sol === null ? null : objectValue(sol["limit"]);
1321
- if (limit !== null) {
1322
- for (const [key, value] of Object.entries(PRIMARY_LIMITS)) {
1323
- const field = `${PRIMARY_LIMIT_PREFIX}.${key}`;
1324
- if (ctx.ownedPrimaryModelFields?.has(field) !== true) continue;
1325
- if (limit[key] === value) delete limit[key];
1326
- }
1327
- }
1328
- if (sol !== null && ctx.ownedPrimaryModelFields?.has(PRIMARY_LIMIT_PREFIX) === true) pruneEmpty(sol, "limit");
1329
- if (models !== null && ctx.ownedPrimaryModelFields?.has(PRIMARY_SOL_FIELD) === true) pruneEmpty(models, PRIMARY_MODEL_ID);
1330
- if (openai !== null && ctx.ownedPrimaryModelFields?.has(PRIMARY_MODELS_FIELD) === true) pruneEmpty(openai, "models");
1331
- if (provider !== null && ctx.ownedPrimaryModelFields?.has(PRIMARY_OPENAI_FIELD) === true) pruneEmpty(provider, "openai");
1332
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_PROVIDER_FIELD) === true) pruneEmpty(root, "provider");
1333
- const managedFields = [
1334
- PRIMARY_PROVIDER_FIELD,
1335
- PRIMARY_OPENAI_FIELD,
1336
- PRIMARY_MODELS_FIELD,
1337
- PRIMARY_SOL_FIELD,
1338
- PRIMARY_LIMIT_PREFIX,
1339
- ...Object.keys(PRIMARY_LIMITS).map((key) => `${PRIMARY_LIMIT_PREFIX}.${key}`)
1340
- ];
1341
- for (const field of managedFields) {
1342
- if (ctx.ownedPrimaryModelFields?.has(field) === true) primaryModelOwnership.push({ field, owned: false });
1343
- }
1344
- const rawMcpBlock = root["mcp"];
1345
- if (rawMcpBlock !== void 0 && objectValue(rawMcpBlock) === null) {
1346
- throw new Error("OpenCode: la clave 'mcp' debe ser un objeto; corr\xEDgela antes de reintentar uninstall.");
1347
- }
1348
- const mcpBlock = rawMcpBlock;
1349
- if (mcpBlock !== void 0) {
1350
- for (const [name, server] of Object.entries(mcp.servers)) {
1351
- if (name === "context7") {
1352
- const canonical = isCanonicalContext7Server(server, mcpBlock[name]);
1353
- const owned = ctx.ownedMcpServers?.has(name) === true;
1354
- if (owned) {
1355
- if (canonical) delete mcpBlock[name];
1356
- mcpOwnership.push({ server: name, owned: false });
1357
- }
1358
- continue;
1359
- }
1360
- if (!server.optional) {
1361
- delete mcpBlock[name];
1362
- continue;
1363
- }
1364
- if (ctx.ownedMcpServers?.has(name) === true) {
1365
- if (isManagedOptionalStdioServer(server, mcpBlock[name])) delete mcpBlock[name];
1366
- mcpOwnership.push({ server: name, owned: false });
1367
- }
1368
- }
1369
- if (Object.keys(mcpBlock).length === 0) delete root["mcp"];
1370
- }
1371
- const plugin = root["plugin"];
1372
- if (Array.isArray(plugin) && pluginsDir !== null) {
1373
- const pluginsDirPrefix = pathToFileURL(pluginsDir).href + "/";
1374
- const kept = plugin.filter((url) => !url.startsWith(pluginsDirPrefix));
1375
- if (kept.length === 0) delete root["plugin"];
1376
- else root["plugin"] = kept;
1377
- }
1378
- });
1379
- actions.push({
1380
- kind: "write",
1381
- target: configFile,
1382
- content,
1383
- ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
1384
- ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
1385
- });
1386
- }
1387
- const hooksFile = path9.join(ctx.configDir, "hooks.json");
1388
- const hooksJson = readTextIfExists(hooksFile);
1389
- if (hooksJson !== null) {
1390
- const ourScripts = hookScriptNames(hooks);
1391
- const content = upsertJson(hooksJson, (root) => {
1392
- const after = root["tool.execute.after"];
1393
- if (!after) return;
1394
- for (const tool of Object.keys(after)) {
1395
- const byCommand = after[tool];
1396
- for (const includes of Object.keys(byCommand)) {
1397
- byCommand[includes] = byCommand[includes].filter(
1398
- (script) => !ourScripts.some((s) => script.includes(s))
1399
- );
1400
- if (byCommand[includes].length === 0) delete byCommand[includes];
1401
- }
1402
- if (Object.keys(byCommand).length === 0) delete after[tool];
1403
- }
1404
- if (Object.keys(after).length === 0) delete root["tool.execute.after"];
1405
- });
1406
- actions.push({ kind: "write", target: hooksFile, content: content.trim() === "{}" ? "" : content });
1407
- }
1408
- return actions;
1409
- }
1410
- };
1411
-
1412
- // src/adapters/claude-code.ts
1413
- import path10 from "path";
1414
- import fs7 from "fs";
1415
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1416
- function yamlString2(value) {
1417
- return JSON.stringify(value);
1418
- }
1419
- var memoryTools = (names) => names.flatMap((n) => [`mcp__engram__${n}`, `mcp__plugin_engram_engram__${n}`]);
1420
- var MEMORY_TOOLS = memoryTools(["mem_save", "mem_search", "mem_context"]);
1421
- var ENGRAM_AGENT_TOOLS = [
1422
- "Read",
1423
- "Skill",
1424
- ...memoryTools(["mem_context", "mem_search", "mem_get_observation", "mem_timeline", "mem_current_project"])
1425
- ];
1426
- function isRecord(value) {
1427
- return value !== null && typeof value === "object" && !Array.isArray(value);
1428
- }
1429
- function readMcpConfig2(file) {
1430
- let content;
1431
- try {
1432
- content = fs7.readFileSync(file, "utf8");
1433
- } catch (error) {
1434
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
1435
- if (code === "ENOENT") return null;
1436
- throw new Error(`Claude Code: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
1437
- }
1438
- if (content.trim() !== "") {
1439
- try {
1440
- if (!isRecord(JSON.parse(content))) throw new Error();
1441
- } catch {
1442
- throw new Error(`Claude Code: la configuraci\xF3n MCP en ${file} debe contener un objeto JSON v\xE1lido.`);
1443
- }
1444
- }
1445
- return content;
1446
- }
1447
- function isCompatibleContext7Server2(server, value) {
1448
- if (server.transport !== "http" || typeof server.url !== "string" || !isRecord(value)) return false;
1449
- return value.type === "http" && value.url === server.url;
1450
- }
1451
- function canonicalContext7Server2(server) {
1452
- const headers = {};
1453
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
1454
- headers[key] = /^\$\{(\w+)\}$/.test(raw) ? "" : raw;
1455
- }
1456
- return {
1457
- type: "http",
1458
- url: server.url,
1459
- ...Object.keys(headers).length > 0 ? { headers } : {}
1460
- };
1461
- }
1462
- function isCanonicalContext7Server2(server, value) {
1463
- return isCompatibleContext7Server2(server, value) && isDeepStrictEqual2(value, canonicalContext7Server2(server));
1464
- }
1465
- function assertCompatibleContext72(server, value) {
1466
- if (value !== void 0 && !isCompatibleContext7Server2(server, value)) {
1467
- throw new Error("Claude Code: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint o tipo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
1468
- }
1469
- }
1470
- function hasClaudeManualApproval(configDir) {
1471
- const content = readTextIfExists(path10.join(configDir, "settings.json"));
1472
- if (content === null) return false;
1473
- try {
1474
- const root = JSON.parse(content);
1475
- const permissions = isRecord(root) ? root.permissions : void 0;
1476
- const expected = loadCanonicalDefaults(stackRoot())["claude-code"]?.["permissions"];
1477
- return isRecord(permissions) && expected !== void 0 && isDeepStrictEqual2(permissions, expected);
1478
- } catch {
1479
- return false;
1480
- }
1481
- }
1482
- function toolsFor(agent) {
1483
- if (agent.name === "engram") return ENGRAM_AGENT_TOOLS.join(", ");
1484
- if (!agent.readonly) return null;
1485
- const tools = ["Read", "Grep", "Glob", "Skill"];
1486
- if (agent.bash !== "none") tools.push("Bash");
1487
- tools.push(...MEMORY_TOOLS);
1488
- return tools.join(", ");
1489
- }
1490
- function hasEngramPlugin(configDir) {
1491
- if (fs7.existsSync(path10.join(configDir, "plugins", "marketplaces", "engram"))) return true;
1492
- const registry = readTextIfExists(path10.join(configDir, "plugins", "installed_plugins.json"));
1493
- if (registry === null) return false;
1494
- try {
1495
- const parsed = JSON.parse(registry);
1496
- const keys = [...Object.keys(parsed), ...Object.keys(parsed["plugins"] ?? {})];
1497
- return keys.some((k) => k === "engram" || k.startsWith("engram@"));
1498
- } catch {
1499
- return false;
1500
- }
1501
- }
1502
- function isManagedOptionalStdioServer2(server, value) {
1503
- if (!server.optional || server.transport !== "stdio" || value === null || typeof value !== "object" || Array.isArray(value)) {
1504
- return false;
1505
- }
1506
- const current = value;
1507
- const expectedArgs = server.args ?? [];
1508
- return Object.keys(current).length === 3 && current.type === "stdio" && current.command === server.command && Array.isArray(current.args) && current.args.length === expectedArgs.length && current.args.every((arg, index) => arg === expectedArgs[index]);
1509
- }
1510
- var claudeCodeAdapter = {
1511
- id: "claude-code",
1512
- name: "Claude Code",
1513
- detect: detectClaudeCode,
1514
- reportCapabilities(configDir) {
1515
- const prompt = readTextIfExists(path10.join(configDir, "CLAUDE.md"));
1516
- return createLocalCapabilityReport("claude-code", [
1517
- ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
1518
- id: "policy-guidance",
1519
- state: "prompt-only",
1520
- reason: "The managed policy prompt is advisory and cannot enforce the policy",
1521
- evidence: { source: "jorgex-stack-system-prompt", version: "1" }
1522
- }] : [],
1523
- ...hasClaudeManualApproval(configDir) ? [{
1524
- id: "tool-approval",
1525
- state: "manual",
1526
- reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
1527
- evidence: { source: "jorgex-stack-claude-approval-policy", version: "1" }
1528
- }] : []
1529
- ]);
1530
- },
1531
- injectEngramProtocol(ctx) {
1532
- return !hasEngramPlugin(ctx.configDir);
1533
- },
1534
- paths(configDir) {
1535
- return {
1536
- systemPromptFile: path10.join(configDir, "CLAUDE.md"),
1537
- agentsDir: path10.join(configDir, "agents"),
1538
- skillsDir: path10.join(configDir, "skills"),
1539
- commandsDir: path10.join(configDir, "commands"),
1540
- pluginsDir: null,
1541
- scriptsDir: path10.join(configDir, "scripts"),
1542
- outputStylesDir: path10.join(configDir, "output-styles"),
1543
- profilesDir: null
1544
- };
1545
- },
1546
- renderAgent(agent, models) {
1547
- if (agent.mode === "primary") {
1548
- const title = agent.name.charAt(0).toUpperCase() + agent.name.slice(1);
1549
- const style = `---
1550
- name: ${title}
1551
- description: ${yamlString2(agent.description)}
1552
- keep-coding-instructions: true
1553
- ---
1554
- ${agent.body}`;
1555
- return [{ file: `${agent.name}.md`, content: style, kind: "output-style" }];
1556
- }
1557
- const lines = [`name: ${agent.name}`, `description: ${yamlString2(agent.description)}`];
1558
- const tools = toolsFor(agent);
1559
- if (tools !== null) lines.push(`tools: ${tools}`);
1560
- lines.push(`model: ${resolveAgentModel(models, agent.name, agent.tier).model}`);
1561
- return [
1562
- {
1563
- file: `${agent.name}.md`,
1564
- content: `---
1565
- ${lines.join("\n")}
1566
- ---
1567
- ${agent.body}`,
1568
- kind: "agent"
1569
- }
1570
- ];
1571
- },
1572
- renderCommand(file, content) {
1573
- return { file, content: content.replace(/\{\{input\}\}/g, "$ARGUMENTS") };
1574
- },
1575
- planHooks(canonical, ctx) {
1576
- const actions = [];
1577
- const { scriptsDir } = this.paths(ctx.configDir);
1578
- const original = readTextIfExists(path10.join(ctx.configDir, "settings.json"));
1579
- const contentSource = original === null || original.trim() === "" ? null : original;
1580
- const settingsFile = path10.join(ctx.configDir, "settings.json");
1581
- let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
1582
- const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
1583
- const canonicalPermissions = defaults?.["permissions"];
1584
- if (contentSource === null) {
1585
- if (canonicalPermissions !== void 0) {
1586
- content = upsertJson(content, (root) => {
1587
- if (root["permissions"] === void 0) {
1588
- root["permissions"] = canonicalPermissions;
1589
- ctx.warnings.push(
1590
- "Claude Code: fresh config enables read-anywhere via Read/Grep/Glob allow rules; shell, writes and web egress remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
1591
- );
1592
- }
1593
- });
1594
- }
1595
- } else if (canonicalPermissions !== void 0) {
1596
- content = upsertJson(content, (root) => {
1597
- if (isDeepStrictEqual2(root["permissions"], canonicalPermissions)) return;
1598
- if (ctx.upgradePermissions === true) {
1599
- root["permissions"] = canonicalPermissions;
1600
- } else {
1601
- ctx.warnings.push(
1602
- "Claude Code: permissions block differs from the stack default and was left untouched; re-run with --upgrade-permissions to replace it (a backup is created first), or edit it by hand. Overwriting discards your own permission changes, including any extra hardenings."
1603
- );
1604
- }
1605
- });
1606
- }
1607
- actions.push({ kind: "write", target: settingsFile, content });
1608
- const scriptsSource = path10.join(ctx.stackDir, "scripts");
1609
- if (fs7.existsSync(scriptsSource)) {
1610
- for (const f of fs7.readdirSync(scriptsSource)) {
1611
- actions.push({ kind: "copy", source: path10.join(scriptsSource, f), target: path10.join(scriptsDir, f) });
1612
- }
1613
- }
1614
- return actions;
1615
- },
1616
- planMainConfig(canonical, ctx) {
1617
- const file = path10.join(path10.dirname(ctx.configDir), `${path10.basename(ctx.configDir)}.json`);
1618
- const mcpOwnership = [];
1619
- const content = upsertJson(readMcpConfig2(file), (root) => {
1620
- const rawServers = root["mcpServers"];
1621
- if (rawServers !== void 0 && !isRecord(rawServers)) {
1622
- throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar sync.");
1623
- }
1624
- const existingServers = rawServers;
1625
- const context7 = canonical.servers.context7;
1626
- if (context7 !== void 0) assertCompatibleContext72(context7, existingServers?.["context7"]);
1627
- const servers = root["mcpServers"] ??= {};
1628
- for (const [name, server] of Object.entries(canonical.servers)) {
1629
- const existing = servers[name];
1630
- const owned = ctx.ownedMcpServers?.has(name) === true;
1631
- if (name === "context7" && existing !== void 0) {
1632
- if (owned && !isCanonicalContext7Server2(server, existing)) {
1633
- mcpOwnership.push({ server: name, owned: false });
1634
- }
1635
- continue;
1636
- }
1637
- if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
1638
- if (owned) {
1639
- if (isManagedOptionalStdioServer2(server, existing)) delete servers[name];
1640
- mcpOwnership.push({ server: name, owned: false });
1641
- }
1642
- continue;
1643
- }
1644
- if (server.optional && existing !== void 0) {
1645
- if (!owned || !isManagedOptionalStdioServer2(server, existing)) {
1646
- if (owned) mcpOwnership.push({ server: name, owned: false });
1647
- ctx.warnings.push(`Claude Code: MCP opcional '${name}' ya pertenece a la configuraci\xF3n del usuario; se conserva.`);
1648
- continue;
1649
- }
1650
- }
1651
- if (server.transport === "stdio") {
1652
- if (server.command === "{{ENGRAM_BIN}}" && hasEngramPlugin(ctx.configDir)) {
1653
- if (name in servers) delete servers[name];
1654
- ctx.warnings.push(
1655
- "Claude Code: Engram ya est\xE1 integrado v\xEDa plugin \u2014 no se registra el MCP para no duplicar las tools de memoria."
1656
- );
1657
- continue;
1658
- }
1659
- if (server.command === "{{ENGRAM_BIN}}" && ctx.engramBin === null) {
1660
- ctx.warnings.push(
1661
- "Engram no detectado: el MCP 'engram' no se registra. Inst\xE1lalo (github.com/Gentleman-Programming/engram) y re-ejecuta sync."
1662
- );
1663
- continue;
1664
- }
1665
- const command = server.command === "{{ENGRAM_BIN}}" ? ctx.engramBin : server.command;
1666
- servers[name] = { type: "stdio", command, args: server.args ?? [] };
1667
- if (server.optional && existing === void 0 && !owned) mcpOwnership.push({ server: name, owned: true });
1668
- } else {
1669
- const previous = servers[name];
1670
- const headers = {};
1671
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
1672
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
1673
- headers[key] = previous?.headers?.[key] || (envRef ? "" : raw);
1674
- }
1675
- servers[name] = {
1676
- type: "http",
1677
- url: server.url,
1678
- ...Object.keys(headers).length > 0 ? { headers } : {}
1679
- };
1680
- if (name === "context7" && existing === void 0 && !owned) {
1681
- mcpOwnership.push({ server: name, owned: true });
1682
- }
1683
- }
1684
- }
1685
- });
1686
- return [{ kind: "write", target: file, content, ...mcpOwnership.length > 0 ? { mcpOwnership } : {} }];
1687
- },
1688
- planUnmerge(mcp, hooks, ctx) {
1689
- const actions = [];
1690
- const { systemPromptFile } = this.paths(ctx.configDir);
1691
- const prompt = readTextIfExists(systemPromptFile);
1692
- if (prompt !== null) {
1693
- const content = removeSystemPromptSections(prompt);
1694
- actions.push({ kind: "write", target: systemPromptFile, content });
1695
- }
1696
- const settingsFile = path10.join(ctx.configDir, "settings.json");
1697
- const settings = readTextIfExists(settingsFile);
1698
- if (settings !== null) {
1699
- const content = removeNativeHooks(settings, hooks);
1700
- if (content !== null) {
1701
- actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
1702
- }
1703
- }
1704
- const mainFile = path10.join(path10.dirname(ctx.configDir), `${path10.basename(ctx.configDir)}.json`);
1705
- const main2 = readMcpConfig2(mainFile);
1706
- if (main2 !== null) {
1707
- const mcpOwnership = [];
1708
- const content = upsertJson(main2, (root) => {
1709
- const rawServers = root["mcpServers"];
1710
- if (rawServers === void 0) return;
1711
- if (!isRecord(rawServers)) {
1712
- throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar uninstall.");
1713
- }
1714
- const servers = rawServers;
1715
- for (const [name, server] of Object.entries(mcp.servers)) {
1716
- if (name === "context7") {
1717
- const canonical = isCanonicalContext7Server2(server, servers[name]);
1718
- const owned = ctx.ownedMcpServers?.has(name) === true;
1719
- if (owned) {
1720
- if (canonical) delete servers[name];
1721
- mcpOwnership.push({ server: name, owned: false });
1722
- }
1723
- continue;
1724
- }
1725
- if (!server.optional) {
1726
- delete servers[name];
1727
- continue;
1728
- }
1729
- if (ctx.ownedMcpServers?.has(name) === true) {
1730
- if (isManagedOptionalStdioServer2(server, servers[name])) delete servers[name];
1731
- mcpOwnership.push({ server: name, owned: false });
1732
- }
1733
- }
1734
- if (Object.keys(servers).length === 0) delete root["mcpServers"];
1735
- });
1736
- actions.push({ kind: "write", target: mainFile, content, ...mcpOwnership.length > 0 ? { mcpOwnership } : {} });
1737
- }
1738
- return actions;
1739
- }
1740
- };
1741
-
1742
- // src/adapters/codex.ts
1743
- import path11 from "path";
1744
- import fs8 from "fs";
1745
- function tomlString(value) {
1746
- return JSON.stringify(value);
1747
- }
1748
- function readMcpConfig3(file) {
1749
- try {
1750
- return fs8.readFileSync(file, "utf8");
1751
- } catch (error) {
1752
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
1753
- if (code === "ENOENT") return null;
1754
- throw new Error(`Codex: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
1755
- }
1756
- }
1757
- var PRIMARY_MODEL2 = '"gpt-5.6-sol"';
1758
- var PRIMARY_CONTEXT_WINDOW = "872000";
1759
- var PRIMARY_MODEL_FIELD2 = "model";
1760
- var PRIMARY_CONTEXT_FIELD = "model_context_window";
1761
- function tomlMultiline(value) {
1762
- if (value.includes("'''")) return JSON.stringify(value);
1763
- return `'''
1764
- ${value.replace(/\r\n/g, "\n").trim()}
1765
- '''`;
1766
- }
1767
- function stdioMcpSection(server) {
1768
- const args = (server.args ?? []).map(tomlString).join(", ");
1769
- return `command = ${tomlString(server.command)}
1770
- args = [${args}]`;
1771
- }
1772
- function isManagedOptionalStdioServer3(server, section) {
1773
- return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
1774
- }
1775
- function hasActiveEngramPlugin(configDir) {
1776
- const config = readTextIfExists(path11.join(configDir, "config.toml"));
1777
- if (config === null) return false;
1778
- const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
1779
- return match !== null && !/enabled\s*=\s*false/.test(match[1]);
1780
- }
1781
- function hasEngramProtocol(configDir) {
1782
- if (hasActiveEngramPlugin(configDir)) return true;
1783
- if (fs8.existsSync(path11.join(configDir, "engram-instructions.md"))) return true;
1784
- const config = readTextIfExists(path11.join(configDir, "config.toml"));
1785
- return config !== null && /engram-instructions\.md/.test(config);
1786
- }
1787
- var CODEX_JSON_STRING = String.raw`"(?:\\.|[^"\\\r\n])*"`;
1788
- var CODEX_JSON_VALUE = String.raw`(?:${CODEX_JSON_STRING}|-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|true|false)`;
1789
- var CODEX_JSON_STRING_ARRAY = String.raw`\[(?:\s*${CODEX_JSON_STRING}(?:\s*,\s*${CODEX_JSON_STRING})*\s*)?\]`;
1790
- var CODEX_KEY = String.raw`(?:[A-Za-z0-9_-]+|${CODEX_JSON_STRING})`;
1791
- var CODEX_ASSIGNMENT = new RegExp(String.raw`^\s*(${CODEX_KEY})\s*=\s*(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})\s*(?:#.*)?$`);
1792
- var CODEX_HEADER = new RegExp(String.raw`^\[\s*(${CODEX_KEY}(?:\s*\.\s*${CODEX_KEY})*)\s*\]\s*(?:#.*)?$`);
1793
- function tomlAssignment(section, key) {
1794
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1795
- const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
1796
- const lines = section.split(/\r?\n/);
1797
- const mask = multilineStringMask(lines);
1798
- for (const [index, line] of lines.entries()) {
1799
- if (mask[index] || !keyPattern.test(line)) continue;
1800
- const match = CODEX_ASSIGNMENT.exec(line);
1801
- if (match) return { present: true, raw: match[2] };
1802
- const equals = line.indexOf("=");
1803
- const raw = equals === -1 ? void 0 : line.slice(equals + 1).trim().replace(/\s+#.*$/, "");
1804
- return { present: true, ...raw?.startsWith("'") && raw.endsWith("'") ? { raw } : {} };
1805
- }
1806
- return { present: false };
1807
- }
1808
- function parseTomlString(raw) {
1809
- if (raw === void 0) return void 0;
1810
- const value = raw.trim();
1811
- if (value.startsWith('"')) {
1812
- try {
1813
- const parsed = JSON.parse(value);
1814
- return typeof parsed === "string" ? parsed : void 0;
1815
- } catch {
1816
- return void 0;
1817
- }
1818
- }
1819
- if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
1820
- return value.slice(1, -1);
1821
- }
1822
- return void 0;
1823
- }
1824
- function context7HttpSection(server) {
1825
- const body = [`url = ${tomlString(server.url)}`];
1826
- const envHeaders = [];
1827
- const literalHeaders = [];
1828
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
1829
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
1830
- if (envRef) envHeaders.push(`${tomlString(key)} = ${tomlString(envRef[1])}`);
1831
- else literalHeaders.push(`${tomlString(key)} = ${tomlString(raw)}`);
1832
- }
1833
- if (literalHeaders.length > 0) body.push(`http_headers = { ${literalHeaders.join(", ")} }`);
1834
- if (envHeaders.length > 0) body.push(`env_http_headers = { ${envHeaders.join(", ")} }`);
1835
- return body.join("\n");
1836
- }
1837
- function isCompatibleContext7Server3(server, section) {
1838
- if (server.transport !== "http" || typeof server.url !== "string" || section === null) return false;
1839
- const url = tomlAssignment(section, "url");
1840
- const command = tomlAssignment(section, "command");
1841
- const type = tomlAssignment(section, "type");
1842
- const enabled = tomlAssignment(section, "enabled");
1843
- const declaredType = type.present ? parseTomlString(type.raw) : "http";
1844
- return url.present && parseTomlString(url.raw) === server.url && !command.present && declaredType === "http" && (!enabled.present || enabled.raw?.trim() === "true");
1845
- }
1846
- function isCanonicalContext7Server3(server, section, config) {
1847
- return isCompatibleContext7Server3(server, section) && section.trim() === context7HttpSection(server) && !hasTomlChildSection(config, "mcp_servers.context7");
1848
- }
1849
- function assertCompatibleContext73(server, section, config) {
1850
- if (section === null && hasTomlChildSection(config, "mcp_servers.context7") || section !== null && !isCompatibleContext7Server3(server, section)) {
1851
- throw new Error("Codex: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint, tipo o estado nativo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
1852
- }
1853
- }
1854
- var CODEX_PERMISSION_HEADERS = {
1855
- base: "permissions.jorgex-read-anywhere",
1856
- filesystem: "permissions.jorgex-read-anywhere.filesystem",
1857
- workspaceRoots: 'permissions.jorgex-read-anywhere.filesystem.":workspace_roots"'
1858
- };
1859
- var CODEX_PERMISSION_PROFILE = {
1860
- base: [["extends", ":workspace"]],
1861
- filesystem: [
1862
- [":root", "read"],
1863
- ["*.env", "deny"],
1864
- ["*.env.*", "deny"],
1865
- ["~/.ssh/**", "deny"],
1866
- ["~/.aws/credentials", "deny"],
1867
- ["~/.npmrc", "deny"],
1868
- ["~/.git-credentials", "deny"],
1869
- ["**/id_rsa", "deny"],
1870
- ["**/id_ed25519", "deny"],
1871
- ["**/*.pem", "deny"],
1872
- ["**/*.key", "deny"]
1873
- ],
1874
- workspaceRoots: [
1875
- [".", "write"],
1876
- ["*.env", "deny"],
1877
- ["*.env.*", "deny"],
1878
- [".ssh/**", "deny"],
1879
- [".aws/credentials", "deny"],
1880
- [".npmrc", "deny"],
1881
- [".git-credentials", "deny"],
1882
- ["**/id_rsa", "deny"],
1883
- ["**/id_ed25519", "deny"],
1884
- ["**/*.pem", "deny"],
1885
- ["**/*.key", "deny"]
1886
- ]
1887
- };
1888
- var CODEX_PERMISSION_SECTIONS = [
1889
- { header: CODEX_PERMISSION_HEADERS.base, entries: CODEX_PERMISSION_PROFILE.base, quoteKeys: false },
1890
- { header: CODEX_PERMISSION_HEADERS.filesystem, entries: CODEX_PERMISSION_PROFILE.filesystem, quoteKeys: true },
1891
- { header: CODEX_PERMISSION_HEADERS.workspaceRoots, entries: CODEX_PERMISSION_PROFILE.workspaceRoots, quoteKeys: true }
1892
- ];
1893
- function renderCodexPermissionEntries(entries, quoteKeys) {
1894
- return entries.map(([key, value]) => `${quoteKeys ? JSON.stringify(key) : key} = ${JSON.stringify(value)}`).join("\n");
1895
- }
1896
- var CODEX_STALE_PERMISSIONS_WARNING = "Codex: permission profile differs from the stack default and was left untouched; re-run with --upgrade-permissions to replace it (a backup is created first), or edit it by hand. Overwriting discards your own permission changes, including any extra hardenings.";
1897
- function codexNormalizedHeader(header) {
1898
- return headerName(`[${header}]`) ?? header;
1899
- }
1900
- function readCodexRootValue(config, key) {
1901
- const lines = config.replace(/\r\n/g, "\n").split("\n");
1902
- const mask = multilineStringMask(lines);
1903
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1904
- const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
1905
- for (const [index, line] of lines.entries()) {
1906
- if (mask[index]) continue;
1907
- if (line.trim().startsWith("[")) break;
1908
- if (!keyPattern.test(line)) continue;
1909
- const match = CODEX_ASSIGNMENT.exec(line.trim());
1910
- return match === null ? void 0 : parseTomlString(match[2]);
1911
- }
1912
- return void 0;
1913
- }
1914
- function setCodexRootKey(config, key, value) {
1915
- const eol = config.includes("\r\n") ? "\r\n" : "\n";
1916
- const lines = config.replace(/\r\n/g, "\n").split("\n");
1917
- const mask = multilineStringMask(lines);
1918
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1919
- const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
1920
- const assignment = new RegExp(
1921
- `^(\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=\\s*)(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})(\\s*(?:#.*)?)$`
1922
- );
1923
- for (const [index, line] of lines.entries()) {
1924
- if (mask[index]) continue;
1925
- if (line.trim().startsWith("[")) break;
1926
- if (!keyPattern.test(line)) continue;
1927
- const match = assignment.exec(line);
1928
- lines[index] = match === null ? `${key} = ${value}` : `${match[1]}${value}${match[3]}`;
1929
- return lines.join(eol);
1930
- }
1931
- return upsertTomlRootKeyIfMissing(config, key, value);
1932
- }
1933
- function isCodexPermissionBlockCurrent(config, defaults) {
1934
- const expectedApproval = defaults["approval_policy"];
1935
- const expectedDefault = defaults["default_permissions"];
1936
- if (typeof expectedApproval !== "string" || typeof expectedDefault !== "string") return true;
1937
- if (readCodexRootValue(config, "approval_policy") !== expectedApproval) return false;
1938
- if (readCodexRootValue(config, "default_permissions") !== expectedDefault) return false;
1939
- return CODEX_PERMISSION_SECTIONS.every(
1940
- ({ header, entries, quoteKeys }) => (readTomlSection(config, codexNormalizedHeader(header)) ?? "").trim() === renderCodexPermissionEntries(entries, quoteKeys).trim()
1941
- );
1942
- }
1943
- function reseedCodexPermissionBlock(config, defaults) {
1944
- let out = setCodexRootKey(config, "approval_policy", tomlString(String(defaults["approval_policy"])));
1945
- out = setCodexRootKey(out, "default_permissions", tomlString(String(defaults["default_permissions"])));
1946
- for (const { header, entries, quoteKeys } of CODEX_PERMISSION_SECTIONS) {
1947
- out = upsertTomlSection(out, header, renderCodexPermissionEntries(entries, quoteKeys));
1948
- }
1949
- return out;
1950
- }
1951
- function parseCodexKey(raw) {
1952
- if (!raw.startsWith('"')) return raw;
1953
- try {
1954
- const value = JSON.parse(raw);
1955
- return typeof value === "string" ? value : null;
1956
- } catch {
1957
- return null;
1958
- }
1959
- }
1960
- function parseCodexValue(raw) {
1961
- try {
1962
- const value = JSON.parse(raw);
1963
- return Array.isArray(value) && value.every((item) => typeof item === "string") || !Array.isArray(value) && (value === null || typeof value !== "object") ? value : void 0;
1964
- } catch {
1965
- return void 0;
1966
- }
1967
- }
1968
- function parseCodexHeader(line) {
1969
- const match = CODEX_HEADER.exec(line.trim());
1970
- if (match === null) return null;
1971
- for (const quoted of match[1].match(/"(?:\\.|[^"\\\r\n])*"/g) ?? []) {
1972
- if (parseCodexKey(quoted) === null) return null;
1973
- }
1974
- return match[1].replace(/\s*\.\s*/g, ".");
1975
- }
1976
- function scanCodexToml(config) {
1977
- if (config.includes("'''") || config.includes('"""')) return null;
1978
- const root = /* @__PURE__ */ new Map();
1979
- const sections = /* @__PURE__ */ new Map();
1980
- const headers = /* @__PURE__ */ new Set();
1981
- const keys = /* @__PURE__ */ new Set();
1982
- let section = null;
1983
- for (const line of config.replace(/\r\n/g, "\n").split("\n")) {
1984
- const trimmed = line.trim();
1985
- if (trimmed === "" || trimmed.startsWith("#")) continue;
1986
- if (trimmed.startsWith("[")) {
1987
- const header = parseCodexHeader(trimmed);
1988
- if (header === null || headers.has(header)) return null;
1989
- headers.add(header);
1990
- if (header.startsWith(`${CODEX_PERMISSION_HEADERS.base}.`) && !CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) {
1991
- return null;
1992
- }
1993
- section = header;
1994
- if (CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) sections.set(header, /* @__PURE__ */ new Map());
1995
- continue;
1996
- }
1997
- const relevant = section === null || CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === section);
1998
- const match = CODEX_ASSIGNMENT.exec(trimmed);
1999
- if (!relevant && match === null) continue;
2000
- if (match === null) return null;
2001
- const key = parseCodexKey(match[1]);
2002
- const value = parseCodexValue(match[2]);
2003
- if (key === null || value === void 0) return null;
2004
- if (section === null && (key === "profile" || key === "sandbox_mode")) return null;
2005
- const scope = section ?? "<root>";
2006
- const declaration = `${scope}\0${key}`;
2007
- if (keys.has(declaration)) return null;
2008
- keys.add(declaration);
2009
- if (section === null) root.set(key, value);
2010
- else if (sections.has(section)) sections.get(section).set(key, value);
2011
- }
2012
- return { root, sections };
2013
- }
2014
- function hasCanonicalCodexPermissionProfile(scan) {
2015
- for (const { header, entries } of CODEX_PERMISSION_SECTIONS) {
2016
- const actual = scan.sections.get(header);
2017
- if (actual === void 0 || actual.size !== entries.length) return false;
2018
- for (const [key, expected] of entries) {
2019
- if (actual.get(key) !== expected) return false;
149
+ throw new Error(`Marcadores de estilo ambiguos o desconocidos en ${sourcePath}; revisa el bloque ${DEFAULT_SECTION}.`);
2020
150
  }
2021
151
  }
2022
- return true;
152
+ const installedContent = upsertMarkdownSection(originalContent, DEFAULT_SECTION, canonical);
153
+ if (originalContent !== null && originalContent !== installedContent) validateBackupRoot(options.rootDir);
154
+ const content = installedContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").replace(/\r\n?/g, "\n").trim();
155
+ return {
156
+ sourcePath,
157
+ content,
158
+ canonicalPath,
159
+ originalContent,
160
+ installedContent,
161
+ rootDir: options.rootDir,
162
+ backupRoot: options.rootDir === void 0 ? void 0 : path.join(options.rootDir, "backups")
163
+ };
2023
164
  }
2024
- function hasCodexManualApproval(configDir) {
2025
- const config = readTextIfExists(path11.join(configDir, "config.toml"));
2026
- if (config === null) return false;
2027
- const scan = scanCodexToml(config);
2028
- if (scan === null) return false;
2029
- try {
2030
- const defaults = loadCanonicalDefaults(stackRoot())["codex"];
2031
- const expectedApproval = defaults?.["approval_policy"];
2032
- const expectedPermissions = defaults?.["default_permissions"];
2033
- return typeof expectedApproval === "string" && typeof expectedPermissions === "string" && scan.root.get("approval_policy") === expectedApproval && scan.root.get("default_permissions") === expectedPermissions && hasCanonicalCodexPermissionProfile(scan);
2034
- } catch {
2035
- return false;
165
+ function applyWritingStyle(plan, dryRun = false) {
166
+ if (dryRun) return;
167
+ const current = readStyleText(plan.sourcePath, { rootDir: plan.rootDir });
168
+ if (current === plan.installedContent) return;
169
+ if (current !== plan.originalContent) {
170
+ throw new Error(`El estilo ${plan.sourcePath} cambi\xF3 durante la preparaci\xF3n; vuelve a ejecutar el comando.`);
2036
171
  }
2037
- }
2038
- var codexAdapter = {
2039
- id: "codex",
2040
- name: "Codex CLI",
2041
- detect: detectCodex,
2042
- reportCapabilities(configDir) {
2043
- const prompt = readTextIfExists(path11.join(configDir, "AGENTS.md"));
2044
- return createLocalCapabilityReport("codex", [
2045
- ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
2046
- id: "policy-guidance",
2047
- state: "prompt-only",
2048
- reason: "The managed policy prompt is advisory and cannot enforce the policy",
2049
- evidence: { source: "jorgex-stack-system-prompt", version: "1" }
2050
- }] : [],
2051
- ...hasCodexManualApproval(configDir) ? [{
2052
- id: "tool-approval",
2053
- state: "manual",
2054
- reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
2055
- evidence: { source: "jorgex-stack-codex-approval-policy", version: "1" }
2056
- }] : [],
2057
- {
2058
- id: "external-verification",
2059
- state: "unavailable",
2060
- reason: "External verification is available only through the external verifier"
2061
- }
2062
- ]);
2063
- },
2064
- injectEngramProtocol(ctx) {
2065
- return !hasEngramProtocol(ctx.configDir);
2066
- },
2067
- paths(configDir) {
2068
- const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path11.join(HOME, ".codex"));
2069
- const agentsHome = isRealConfigDir ? HOME : path11.dirname(configDir);
2070
- const skillsDir = path11.join(agentsHome, ".agents", "skills");
2071
- return {
2072
- systemPromptFile: path11.join(configDir, "AGENTS.md"),
2073
- agentsDir: path11.join(configDir, "agents"),
2074
- skillsDir,
2075
- // Los custom prompts de Codex están deprecados: los commands se
2076
- // instalan como skills (renderCommand produce <nombre>/SKILL.md).
2077
- commandsDir: skillsDir,
2078
- pluginsDir: null,
2079
- scriptsDir: path11.join(configDir, "scripts"),
2080
- outputStylesDir: null,
2081
- profilesDir: configDir
2082
- };
2083
- },
2084
- renderAgent(agent, models) {
2085
- const tierModel = resolveAgentModel(models, agent.name, agent.tier);
2086
- if (agent.mode === "primary") {
2087
- const profileLines = [
2088
- "# Managed by jorgex-stack \u2014 modo orquestador del agente principal.",
2089
- `# Uso: codex --profile ${agent.name}`,
2090
- `developer_instructions = ${tomlMultiline(agent.body)}`
2091
- ];
2092
- return [{ file: `${agent.name}.config.toml`, content: profileLines.join("\n") + "\n", kind: "profile" }];
2093
- }
2094
- const lines = [
2095
- `name = ${tomlString(agent.name)}`,
2096
- `description = ${tomlString(agent.description)}`
2097
- ];
2098
- if (tierModel.model !== "default") lines.push(`model = ${tomlString(tierModel.model)}`);
2099
- if (tierModel.variant) lines.push(`model_reasoning_effort = ${tomlString(tierModel.variant)}`);
2100
- lines.push(`sandbox_mode = ${tomlString(agent.readonly ? "read-only" : "workspace-write")}`);
2101
- lines.push(`developer_instructions = ${tomlMultiline(agent.body)}`);
2102
- return [{ file: `${agent.name}.toml`, content: lines.join("\n") + "\n", kind: "agent" }];
2103
- },
2104
- renderCommand(file, content) {
2105
- const name = file.replace(/\.md$/, "");
2106
- let description = name;
2107
- let body = content;
2108
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(content);
2109
- if (fm) {
2110
- const match = /(?:^|\n)description:\s*(.+)/.exec(fm[1]);
2111
- if (match) description = match[1].trim();
2112
- body = content.slice(fm[0].length);
2113
- }
2114
- body = body.replace(/\{\{input\}\}/g, "the user's request in this conversation");
2115
- return {
2116
- file: `${name}/SKILL.md`,
2117
- content: `---
2118
- name: ${name}
2119
- description: ${JSON.stringify(description)}
2120
- ---
2121
- ${body}`
2122
- };
2123
- },
2124
- planHooks(canonical, ctx) {
2125
- const actions = [];
2126
- const { scriptsDir } = this.paths(ctx.configDir);
2127
- const hooksFile = path11.join(ctx.configDir, "hooks.json");
2128
- const content = upsertNativeHooks(
2129
- readTextIfExists(hooksFile),
2130
- canonical,
2131
- scriptsDir,
2132
- (matcher) => matcher.split("|").some((p7) => p7.trim() === "Bash") ? "shell" : matcher
2133
- );
2134
- actions.push({ kind: "write", target: hooksFile, content });
2135
- ctx.warnings.push(
2136
- "Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
2137
- );
2138
- const scriptsSource = path11.join(ctx.stackDir, "scripts");
2139
- if (fs8.existsSync(scriptsSource)) {
2140
- for (const f of fs8.readdirSync(scriptsSource)) {
2141
- actions.push({ kind: "copy", source: path11.join(scriptsSource, f), target: path11.join(scriptsDir, f) });
2142
- }
2143
- }
2144
- return actions;
2145
- },
2146
- planMainConfig(canonical, ctx) {
2147
- const file = path11.join(ctx.configDir, "config.toml");
2148
- const original = readMcpConfig3(file);
2149
- const contentSource = original === null || original.trim() === "" ? null : original;
2150
- let content = contentSource;
2151
- const mcpOwnership = [];
2152
- const primaryModelOwnership = [];
2153
- const context7 = canonical.servers.context7;
2154
- if (context7 !== void 0) {
2155
- assertCompatibleContext73(context7, readTomlSection(contentSource, "mcp_servers.context7"), contentSource);
2156
- }
2157
- if (contentSource === null) {
2158
- const defaults = loadCanonicalDefaults(ctx.stackDir)["codex"] ?? {};
2159
- for (const [key, value] of Object.entries(defaults)) {
2160
- const line = `${key} = ${tomlString(String(value))}
2161
- `;
2162
- content = content === null ? line : line + content;
2163
- }
2164
- ctx.warnings.push(
2165
- "Codex: fresh config enables read-anywhere via the jorgex-read-anywhere permission profile; broad local reads can expose secrets not covered by deny rules."
2166
- );
2167
- content = upsertTomlSection(
2168
- content,
2169
- CODEX_PERMISSION_HEADERS.base,
2170
- renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.base, false)
2171
- );
2172
- content = upsertTomlSection(
2173
- content,
2174
- CODEX_PERMISSION_HEADERS.filesystem,
2175
- renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.filesystem, true)
2176
- );
2177
- content += [
2178
- `
2179
- [${CODEX_PERMISSION_HEADERS.workspaceRoots}]`,
2180
- renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.workspaceRoots, true),
2181
- ""
2182
- ].join("\n");
2183
- }
2184
- if (contentSource !== null) {
2185
- const codexDefaults = loadCanonicalDefaults(ctx.stackDir)["codex"];
2186
- if (codexDefaults !== void 0 && !isCodexPermissionBlockCurrent(contentSource, codexDefaults)) {
2187
- if (ctx.upgradePermissions === true) {
2188
- content = reseedCodexPermissionBlock(content, codexDefaults);
2189
- } else {
2190
- ctx.warnings.push(CODEX_STALE_PERMISSIONS_WARNING);
2191
- }
2192
- }
2193
- }
2194
- if (!hasTomlRootKey(content, PRIMARY_MODEL_FIELD2)) {
2195
- content = upsertTomlRootKeyIfMissing(content, PRIMARY_MODEL_FIELD2, PRIMARY_MODEL2);
2196
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD2) !== true) {
2197
- primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD2, owned: true });
2198
- }
2199
- }
2200
- if (!hasTomlRootKey(content, PRIMARY_CONTEXT_FIELD)) {
2201
- content = upsertTomlRootKeyIfMissing(content, PRIMARY_CONTEXT_FIELD, PRIMARY_CONTEXT_WINDOW);
2202
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_CONTEXT_FIELD) !== true) {
2203
- primaryModelOwnership.push({ field: PRIMARY_CONTEXT_FIELD, owned: true });
2204
- }
2205
- }
2206
- for (const [name, server] of Object.entries(canonical.servers)) {
2207
- const section = `mcp_servers.${name}`;
2208
- const existing = readTomlSection(content, section);
2209
- const owned = ctx.ownedMcpServers?.has(name) === true;
2210
- if (name === "context7" && existing !== null) {
2211
- if (owned && !isCanonicalContext7Server3(server, existing, content)) {
2212
- mcpOwnership.push({ server: name, owned: false });
2213
- }
2214
- continue;
2215
- }
2216
- if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
2217
- if (owned) {
2218
- if (isManagedOptionalStdioServer3(server, existing)) content = removeTomlSection(content, section);
2219
- mcpOwnership.push({ server: name, owned: false });
2220
- }
2221
- continue;
2222
- }
2223
- if (server.optional && existing !== null) {
2224
- if (!owned || !isManagedOptionalStdioServer3(server, existing)) {
2225
- if (owned) mcpOwnership.push({ server: name, owned: false });
2226
- ctx.warnings.push(`Codex: MCP opcional '${name}' ya pertenece a la configuraci\xF3n del usuario; se conserva.`);
2227
- continue;
2228
- }
2229
- }
2230
- if (server.transport === "stdio") {
2231
- if (server.command === "{{ENGRAM_BIN}}" && hasActiveEngramPlugin(ctx.configDir)) {
2232
- if (content !== null) content = removeTomlSection(content, section);
2233
- ctx.warnings.push(
2234
- "Codex: Engram ya est\xE1 integrado v\xEDa plugin de marketplace \u2014 no se registra el MCP para no duplicar las tools de memoria."
2235
- );
2236
- continue;
2237
- }
2238
- if (server.command === "{{ENGRAM_BIN}}" && ctx.engramBin === null) {
2239
- ctx.warnings.push(
2240
- "Engram no detectado: el MCP 'engram' no se registra. Inst\xE1lalo (github.com/Gentleman-Programming/engram) y re-ejecuta sync."
2241
- );
2242
- continue;
2243
- }
2244
- const command = server.command === "{{ENGRAM_BIN}}" ? ctx.engramBin : server.command;
2245
- content = upsertTomlSection(content, section, `command = ${tomlString(command)}
2246
- args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
2247
- if (server.optional && existing === null && !owned) mcpOwnership.push({ server: name, owned: true });
2248
- } else {
2249
- const previousSection = readTomlSection(content, section);
2250
- const prevUsesEnvHeaders = previousSection?.includes("env_http_headers") ?? false;
2251
- const literalPairs = [];
2252
- const refPairs = [];
2253
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
2254
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
2255
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2256
- const previousValue = !prevUsesEnvHeaders && previousSection !== null ? new RegExp(`(?:^|[{,]\\s*)"?${escaped}"?\\s*=\\s*"([^"]*)"`, "m").exec(previousSection)?.[1] : void 0;
2257
- if (previousValue) literalPairs.push(`${tomlString(key)} = ${tomlString(previousValue)}`);
2258
- else refPairs.push(`${tomlString(key)} = ${tomlString(envRef ? envRef[1] : raw)}`);
2259
- }
2260
- const body = [`url = ${tomlString(server.url)}`];
2261
- if (literalPairs.length > 0) body.push(`http_headers = { ${literalPairs.join(", ")} }`);
2262
- if (refPairs.length > 0) body.push(`env_http_headers = { ${refPairs.join(", ")} }`);
2263
- content = upsertTomlSection(content, section, body.join("\n"));
2264
- if (name === "context7" && existing === null && !owned) {
2265
- mcpOwnership.push({ server: name, owned: true });
2266
- }
2267
- }
2268
- }
2269
- if (content === null) return [];
2270
- if (!content.endsWith("\n")) content += "\n";
2271
- return [{
2272
- kind: "write",
2273
- target: file,
2274
- content,
2275
- ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
2276
- ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
2277
- }];
2278
- },
2279
- planUnmerge(mcp, hooks, ctx) {
2280
- const actions = [];
2281
- const { systemPromptFile } = this.paths(ctx.configDir);
2282
- const prompt = readTextIfExists(systemPromptFile);
2283
- if (prompt !== null) {
2284
- const content = removeSystemPromptSections(prompt);
2285
- actions.push({ kind: "write", target: systemPromptFile, content });
2286
- }
2287
- const configFile = path11.join(ctx.configDir, "config.toml");
2288
- const config = readMcpConfig3(configFile);
2289
- if (config !== null) {
2290
- let content = config;
2291
- const primaryModelOwnership = [];
2292
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_MODEL_FIELD2) === true) {
2293
- content = removeTomlRootKeyIfExact(content, PRIMARY_MODEL_FIELD2, PRIMARY_MODEL2);
2294
- primaryModelOwnership.push({ field: PRIMARY_MODEL_FIELD2, owned: false });
2295
- }
2296
- if (ctx.ownedPrimaryModelFields?.has(PRIMARY_CONTEXT_FIELD) === true) {
2297
- content = removeTomlRootKeyIfExact(content, PRIMARY_CONTEXT_FIELD, PRIMARY_CONTEXT_WINDOW);
2298
- primaryModelOwnership.push({ field: PRIMARY_CONTEXT_FIELD, owned: false });
2299
- }
2300
- const mcpOwnership = [];
2301
- for (const [name, server] of Object.entries(mcp.servers)) {
2302
- const section = `mcp_servers.${name}`;
2303
- if (name === "context7") {
2304
- const current = readTomlSection(content, section);
2305
- const canonical = isCanonicalContext7Server3(server, current, content);
2306
- const owned = ctx.ownedMcpServers?.has(name) === true;
2307
- if (owned) {
2308
- if (canonical) {
2309
- content = removeTomlSection(content, section);
2310
- }
2311
- mcpOwnership.push({ server: name, owned: false });
2312
- }
2313
- continue;
2314
- }
2315
- if (!server.optional) {
2316
- content = removeTomlSection(content, section);
2317
- continue;
2318
- }
2319
- if (ctx.ownedMcpServers?.has(name) === true) {
2320
- if (isManagedOptionalStdioServer3(server, readTomlSection(content, section))) {
2321
- content = removeTomlSection(content, section);
2322
- }
2323
- mcpOwnership.push({ server: name, owned: false });
2324
- }
2325
- }
2326
- actions.push({
2327
- kind: "write",
2328
- target: configFile,
2329
- content,
2330
- ...mcpOwnership.length > 0 ? { mcpOwnership } : {},
2331
- ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
2332
- });
2333
- }
2334
- const hooksFile = path11.join(ctx.configDir, "hooks.json");
2335
- const hooksJson = readTextIfExists(hooksFile);
2336
- if (hooksJson !== null) {
2337
- const content = removeNativeHooks(hooksJson, hooks);
2338
- if (content !== null) {
2339
- actions.push({ kind: "write", target: hooksFile, content: content.trim() === "{}" ? "" : content });
2340
- }
2341
- }
2342
- return actions;
172
+ if (current !== null) {
173
+ validateBackupRoot(plan.rootDir);
174
+ createBackup([plan.sourcePath], "writing-style", plan.backupRoot);
2343
175
  }
2344
- };
176
+ const mode = current === null ? 384 : fs.statSync(plan.sourcePath).mode & 511;
177
+ writeText(plan.sourcePath, plan.installedContent, mode);
178
+ }
2345
179
 
2346
180
  // src/lib/install-mode.ts
2347
- import fs9 from "fs";
2348
- import path12 from "path";
181
+ import fs2 from "fs";
182
+ import path2 from "path";
2349
183
  var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
2350
184
  var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
2351
185
  var DEFAULT_INSTALL_MODE_PREFERENCE = {
@@ -2353,10 +187,10 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
2353
187
  subagentConcurrency: "serial"
2354
188
  };
2355
189
  function installModePreferenceFile() {
2356
- return path12.join(dataDir(), "install-mode.json");
190
+ return path2.join(dataDir(), "install-mode.json");
2357
191
  }
2358
192
  function hasInstallModePreference(file = installModePreferenceFile()) {
2359
- return fs9.existsSync(file);
193
+ return fs2.existsSync(file);
2360
194
  }
2361
195
  function isInstallMode(value) {
2362
196
  return INSTALL_MODES.has(value);
@@ -2387,9 +221,9 @@ function normalizeInstallModePreference(value) {
2387
221
  throw new Error("Preferencia de instalaci\xF3n inv\xE1lida o corrupta.");
2388
222
  }
2389
223
  function loadInstallModePreference(file = installModePreferenceFile()) {
2390
- if (!fs9.existsSync(file)) return DEFAULT_INSTALL_MODE_PREFERENCE;
224
+ if (!fs2.existsSync(file)) return DEFAULT_INSTALL_MODE_PREFERENCE;
2391
225
  try {
2392
- const raw = fs9.readFileSync(file, "utf8");
226
+ const raw = fs2.readFileSync(file, "utf8");
2393
227
  return normalizeInstallModePreference(JSON.parse(raw));
2394
228
  } catch (error) {
2395
229
  const message = error instanceof Error ? error.message : String(error);
@@ -2427,10 +261,10 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
2427
261
  }
2428
262
 
2429
263
  // src/lib/manifest.ts
2430
- import fs10 from "fs";
2431
- import path13 from "path";
264
+ import fs3 from "fs";
265
+ import path3 from "path";
2432
266
  function manifestFile() {
2433
- return path13.join(dataDir(), "manifest.json");
267
+ return path3.join(dataDir(), "manifest.json");
2434
268
  }
2435
269
  function readManifest(file = manifestFile()) {
2436
270
  const raw = readTextIfExists(file);
@@ -2454,24 +288,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
2454
288
  writeText(file, JSON.stringify(manifest, null, 2) + "\n");
2455
289
  }
2456
290
  function findOrphans(prevOwned, currentTargets, root = HOME) {
2457
- return prevOwned.map((f) => path13.resolve(f)).filter(
2458
- (f) => !currentTargets.has(f) && path13.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs10.existsSync(f)
291
+ return prevOwned.map((f) => path3.resolve(f)).filter(
292
+ (f) => !currentTargets.has(f) && path3.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs3.existsSync(f)
2459
293
  );
2460
294
  }
2461
295
 
2462
296
  // src/components/system-prompt.ts
2463
- import path15 from "path";
2464
- import fs12 from "fs";
297
+ import path5 from "path";
298
+ import fs5 from "fs";
2465
299
 
2466
300
  // src/lib/mode-composition.ts
2467
- import fs11 from "fs";
2468
- import path14 from "path";
301
+ import fs4 from "fs";
302
+ import path4 from "path";
2469
303
  var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
2470
304
  var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
2471
305
  var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
2472
306
  var normalize = (value) => value.replace(/\r\n/g, "\n");
2473
307
  function loadProgrammaticAddendum(stackDir, fileName) {
2474
- return normalize(fs11.readFileSync(path14.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
308
+ return normalize(fs4.readFileSync(path4.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
2475
309
  }
2476
310
  function appendAddendum(base, addendum) {
2477
311
  const normalizedBase = normalize(base);
@@ -2517,7 +351,7 @@ function planSystemPrompt(adapter, ctx) {
2517
351
  const existing = readSystemPromptFile(target);
2518
352
  assertSystemPromptMarkers(existing, target);
2519
353
  const readModule = (file) => stripLeadingHtmlComments(
2520
- normalize2(fs12.readFileSync(path15.join(ctx.stackDir, "system-prompt", file), "utf8"))
354
+ normalize2(fs5.readFileSync(path5.join(ctx.stackDir, "system-prompt", file), "utf8"))
2521
355
  );
2522
356
  const modules = {
2523
357
  "system-prompt": composeProgrammaticSystemPrompt(ctx.stackDir, readModule("AGENTS.md"), ctx.mode),
@@ -2537,7 +371,7 @@ function planSystemPrompt(adapter, ctx) {
2537
371
  }
2538
372
 
2539
373
  // src/components/agents.ts
2540
- import path16 from "path";
374
+ import path6 from "path";
2541
375
  function planAgents(adapter, ctx) {
2542
376
  const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
2543
377
  const dirFor = {
@@ -2547,7 +381,7 @@ function planAgents(adapter, ctx) {
2547
381
  profile: profilesDir
2548
382
  };
2549
383
  const scriptsBase = scriptsDir.replace(/\\/g, "/");
2550
- return loadCanonicalAgents(path16.join(ctx.stackDir, "agents")).flatMap((agent) => {
384
+ return loadCanonicalAgents(path6.join(ctx.stackDir, "agents")).flatMap((agent) => {
2551
385
  const composedAgent = {
2552
386
  ...agent,
2553
387
  body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
@@ -2559,42 +393,42 @@ function planAgents(adapter, ctx) {
2559
393
  return [];
2560
394
  }
2561
395
  const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
2562
- return [{ kind: "write", target: path16.join(dir, rendered.file), content }];
396
+ return [{ kind: "write", target: path6.join(dir, rendered.file), content }];
2563
397
  });
2564
398
  });
2565
399
  }
2566
400
 
2567
401
  // src/components/skills.ts
2568
- import path17 from "path";
402
+ import path7 from "path";
2569
403
  function planSkills(adapter, ctx) {
2570
404
  const { skillsDir } = adapter.paths(ctx.configDir);
2571
- const source = path17.join(ctx.stackDir, "skills");
405
+ const source = path7.join(ctx.stackDir, "skills");
2572
406
  return listFilesRecursive(source).map((file) => {
2573
- const relative = path17.relative(source, file);
2574
- return { kind: "copy", source: file, target: path17.join(skillsDir, relative) };
407
+ const relative = path7.relative(source, file);
408
+ return { kind: "copy", source: file, target: path7.join(skillsDir, relative) };
2575
409
  });
2576
410
  }
2577
411
 
2578
412
  // src/components/commands.ts
2579
- import path18 from "path";
2580
- import fs13 from "fs";
413
+ import path8 from "path";
414
+ import fs6 from "fs";
2581
415
  function planCommands(adapter, ctx) {
2582
416
  const { commandsDir } = adapter.paths(ctx.configDir);
2583
- const source = path18.join(ctx.stackDir, "commands");
2584
- if (!fs13.existsSync(source)) return [];
417
+ const source = path8.join(ctx.stackDir, "commands");
418
+ if (!fs6.existsSync(source)) return [];
2585
419
  const commandFiles = [
2586
420
  ...listMarkdownFiles(source),
2587
- ...listMarkdownFiles(path18.join(source, adapter.id))
421
+ ...listMarkdownFiles(path8.join(source, adapter.id))
2588
422
  ];
2589
423
  return commandFiles.map(({ file, fullPath }) => {
2590
- const raw = fs13.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
424
+ const raw = fs6.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
2591
425
  const rendered = adapter.renderCommand(file, raw);
2592
- return { kind: "write", target: path18.join(commandsDir, rendered.file), content: rendered.content };
426
+ return { kind: "write", target: path8.join(commandsDir, rendered.file), content: rendered.content };
2593
427
  });
2594
428
  }
2595
429
  function listMarkdownFiles(dir) {
2596
- if (!fs13.existsSync(dir)) return [];
2597
- return fs13.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path18.join(dir, entry.name) }));
430
+ if (!fs6.existsSync(dir)) return [];
431
+ return fs6.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path8.join(dir, entry.name) }));
2598
432
  }
2599
433
 
2600
434
  // src/components/hooks.ts
@@ -2608,23 +442,24 @@ function planMcp(adapter, ctx) {
2608
442
  }
2609
443
 
2610
444
  // src/components/plugins.ts
2611
- import path19 from "path";
2612
- import fs14 from "fs";
445
+ import path9 from "path";
446
+ import fs7 from "fs";
2613
447
  function planPlugins(adapter, ctx) {
2614
448
  const { pluginsDir } = adapter.paths(ctx.configDir);
2615
449
  if (pluginsDir === null) return [];
2616
- const source = path19.join(ctx.stackDir, "plugins", adapter.id);
2617
- if (!fs14.existsSync(source)) return [];
2618
- return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
2619
- const target = path19.join(pluginsDir, path19.relative(source, sourceFile));
2620
- const raw = fs14.readFileSync(sourceFile, "utf8");
450
+ const source = path9.join(ctx.stackDir, "plugins", adapter.id);
451
+ if (!fs7.existsSync(source)) return [];
452
+ const excluded = new Set(adapter.excludedPluginBasenames ?? []);
453
+ return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).filter((f) => !excluded.has(path9.basename(f))).map((sourceFile) => {
454
+ const target = path9.join(pluginsDir, path9.relative(source, sourceFile));
455
+ const raw = fs7.readFileSync(sourceFile, "utf8");
2621
456
  let content = raw;
2622
457
  if (content.includes('"{{ENGRAM_BIN}}"')) {
2623
458
  content = content.replace(/"\{\{ENGRAM_BIN\}\}"/g, JSON.stringify(ctx.engramBin ?? "engram"));
2624
459
  }
2625
460
  if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
2626
461
  const protocol = stripLeadingHtmlComments(
2627
- fs14.readFileSync(path19.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
462
+ fs7.readFileSync(path9.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
2628
463
  );
2629
464
  content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
2630
465
  }
@@ -2635,10 +470,10 @@ function planPlugins(adapter, ctx) {
2635
470
  }
2636
471
 
2637
472
  // src/lib/external-tools.ts
2638
- import fs15 from "fs";
473
+ import fs8 from "fs";
2639
474
  import os from "os";
2640
- import path20 from "path";
2641
- import { execFileSync as execFileSync2 } from "child_process";
475
+ import path10 from "path";
476
+ import { execFileSync } from "child_process";
2642
477
  var PLAYWRIGHT_CLI = {
2643
478
  packageName: "@playwright/cli",
2644
479
  bin: "playwright-cli",
@@ -2683,9 +518,9 @@ function detectPlaywrightCli(env) {
2683
518
  if (binPath === null) {
2684
519
  const pnpmHome = resolvePnpmHome(actualEnv);
2685
520
  const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
2686
- const known = [path20.join(pnpmHome, name), path20.join(pnpmHome, "bin", name)].find((file) => {
521
+ const known = [path10.join(pnpmHome, name), path10.join(pnpmHome, "bin", name)].find((file) => {
2687
522
  try {
2688
- return fs15.statSync(file).isFile();
523
+ return fs8.statSync(file).isFile();
2689
524
  } catch {
2690
525
  return false;
2691
526
  }
@@ -2704,7 +539,7 @@ function resolvePnpmBin(env) {
2704
539
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
2705
540
  }
2706
541
  function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2707
- const paths = platform === "win32" ? path20.win32 : path20.posix;
542
+ const paths = platform === "win32" ? path10.win32 : path10.posix;
2708
543
  if (env.PNPM_HOME) return env.PNPM_HOME;
2709
544
  if (env.XDG_DATA_HOME) return paths.join(env.XDG_DATA_HOME, "pnpm");
2710
545
  if (platform === "darwin") return paths.join(homeDir, "Library", "pnpm");
@@ -2713,7 +548,7 @@ function resolvePnpmHome(env = process.env, platform = process.platform, homeDir
2713
548
  }
2714
549
  function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform, homeDir = os.homedir()) {
2715
550
  const pnpmHome = resolvePnpmHome(env, platform, homeDir);
2716
- const paths = platform === "win32" ? path20.win32 : path20.posix;
551
+ const paths = platform === "win32" ? path10.win32 : path10.posix;
2717
552
  if (!paths.isAbsolute(pnpmHome)) return { ok: false, reason: "PNPM_HOME debe ser una ruta absoluta; corrige la configuraci\xF3n antes de reintentar." };
2718
553
  const pathEntries = [pnpmHome, paths.join(pnpmHome, "bin")];
2719
554
  if (env.PATH !== void 0) pathEntries.push(env.PATH);
@@ -2725,7 +560,7 @@ function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform
2725
560
  const invocation = planDetectedBinCommand(pnpmBin, ["setup"]);
2726
561
  if (invocation === null) return { ok: false, reason: "No se pudo preparar la invocaci\xF3n de pnpm setup." };
2727
562
  try {
2728
- execFileSync2(invocation.command, invocation.args, { stdio: "inherit", env: childEnv });
563
+ execFileSync(invocation.command, invocation.args, { stdio: "inherit", env: childEnv });
2729
564
  return { ok: true, env: childEnv };
2730
565
  } catch (error) {
2731
566
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
@@ -2733,10 +568,10 @@ function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform
2733
568
  }
2734
569
  function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2735
570
  const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
2736
- const cacheDir = configuredPath ?? (platform === "win32" ? path20.join(env.LOCALAPPDATA ?? path20.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path20.join(homeDir, "Library", "Caches", "ms-playwright") : path20.join(env.XDG_CACHE_HOME ?? path20.join(homeDir, ".cache"), "ms-playwright"));
571
+ const cacheDir = configuredPath ?? (platform === "win32" ? path10.join(env.LOCALAPPDATA ?? path10.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path10.join(homeDir, "Library", "Caches", "ms-playwright") : path10.join(env.XDG_CACHE_HOME ?? path10.join(homeDir, ".cache"), "ms-playwright"));
2737
572
  if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
2738
573
  try {
2739
- const ready = fs15.readdirSync(cacheDir, { withFileTypes: true }).some(
574
+ const ready = fs8.readdirSync(cacheDir, { withFileTypes: true }).some(
2740
575
  (entry) => entry.isDirectory() && /^chromium(?:_headless_shell)?-/.test(entry.name)
2741
576
  );
2742
577
  return ready ? { status: "ready", path: cacheDir } : { status: "missing", path: cacheDir };
@@ -2761,16 +596,16 @@ function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
2761
596
  const rootCommand = planDetectedBinCommand(pnpmBin, ["root", "--global"]);
2762
597
  if (rootCommand === null) return false;
2763
598
  try {
2764
- const root = execFileSync2(rootCommand.command, rootCommand.args, {
599
+ const root = execFileSync(rootCommand.command, rootCommand.args, {
2765
600
  encoding: "utf8",
2766
601
  timeout: 5e3,
2767
602
  env,
2768
603
  cwd,
2769
604
  stdio: ["ignore", "pipe", "inherit"]
2770
605
  }).trim();
2771
- if (!path20.isAbsolute(root)) return false;
2772
- const packageFile = fs15.realpathSync(path20.join(root, "@playwright", "cli", "package.json"));
2773
- const installed = JSON.parse(fs15.readFileSync(packageFile, "utf8"));
606
+ if (!path10.isAbsolute(root)) return false;
607
+ const packageFile = fs8.realpathSync(path10.join(root, "@playwright", "cli", "package.json"));
608
+ const installed = JSON.parse(fs8.readFileSync(packageFile, "utf8"));
2774
609
  if (installed.name !== PLAYWRIGHT_CLI.packageName || installed.version !== PLAYWRIGHT_CLI.version) return false;
2775
610
  const probe = `
2776
611
  const { createRequire } = require('node:module');
@@ -2783,7 +618,7 @@ function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
2783
618
  } finally { await browser.close(); }
2784
619
  })().catch(error => { console.error(error.message); process.exitCode = 1; });
2785
620
  `;
2786
- execFileSync2(process.execPath, ["-e", probe, packageFile], {
621
+ execFileSync(process.execPath, ["-e", probe, packageFile], {
2787
622
  timeout: 25e3,
2788
623
  stdio: "inherit",
2789
624
  cwd,
@@ -2800,7 +635,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2800
635
  const preflight = planDetectedBinCommand(pnpmBin, ["bin", "--global"]);
2801
636
  if (preflight === null) return { ok: false, reason: "pnpm-command" };
2802
637
  try {
2803
- const globalBin = execFileSync2(preflight.command, preflight.args, {
638
+ const globalBin = execFileSync(preflight.command, preflight.args, {
2804
639
  encoding: "utf8",
2805
640
  stdio: ["ignore", "pipe", "inherit"],
2806
641
  ...env === void 0 ? {} : { env }
@@ -2813,10 +648,10 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2813
648
  const command = planPlaywrightCliCommand(action, pnpmBin);
2814
649
  const invocation = planDetectedBinCommand(command.command, command.args);
2815
650
  if (invocation === null) return { ok: false, reason: "pnpm-command" };
2816
- const cwd = action === "install-browser" ? fs15.mkdtempSync(path20.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
651
+ const cwd = action === "install-browser" ? fs8.mkdtempSync(path10.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
2817
652
  const childEnv = action === "install-browser" ? { ...env ?? process.env, NO_UPDATE_NOTIFIER: "1" } : env ?? process.env;
2818
653
  try {
2819
- execFileSync2(invocation.command, invocation.args, {
654
+ execFileSync(invocation.command, invocation.args, {
2820
655
  stdio: "inherit",
2821
656
  env: childEnv,
2822
657
  ...cwd === void 0 ? {} : { cwd }
@@ -2828,7 +663,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2828
663
  } catch {
2829
664
  return { ok: false, reason: "action-failed" };
2830
665
  } finally {
2831
- if (cwd !== void 0) fs15.rmSync(cwd, { recursive: true, force: true });
666
+ if (cwd !== void 0) fs8.rmSync(cwd, { recursive: true, force: true });
2832
667
  }
2833
668
  }
2834
669
 
@@ -2857,29 +692,29 @@ function inspectPlaywrightCapability(options = {}) {
2857
692
  }
2858
693
 
2859
694
  // src/lib/tool-preferences.ts
2860
- import fs16 from "fs";
2861
- import path21 from "path";
695
+ import fs9 from "fs";
696
+ import path11 from "path";
2862
697
  var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
2863
698
  var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
2864
699
  var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
2865
700
  var PLAYWRIGHT_RUNTIMES = ["opencode", "claude-code", "codex", "pi"];
2866
701
  function readPreference(file) {
2867
702
  try {
2868
- return { raw: fs16.readFileSync(file, "utf8"), errorCode: null };
703
+ return { raw: fs9.readFileSync(file, "utf8"), errorCode: null };
2869
704
  } catch (error) {
2870
705
  const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
2871
706
  return code === "ENOENT" ? { raw: null, errorCode: null } : { raw: null, errorCode: code };
2872
707
  }
2873
708
  }
2874
709
  function playwrightCliPreferenceFile(stateDir = dataDir()) {
2875
- return path21.join(stateDir, "playwright-cli.json");
710
+ return path11.join(stateDir, "playwright-cli.json");
2876
711
  }
2877
712
  function parsePlaywrightCliPreference(raw) {
2878
713
  try {
2879
714
  const value = JSON.parse(raw);
2880
- if (!isRecord2(value)) return void 0;
715
+ if (!isRecord(value)) return void 0;
2881
716
  if (value.version === 1 && typeof value.enabled === "boolean") return { version: 1, enabled: value.enabled };
2882
- if (value.version !== 2 || !isRecord2(value.enabled)) return void 0;
717
+ if (value.version !== 2 || !isRecord(value.enabled)) return void 0;
2883
718
  if (Object.entries(value.enabled).some(([runtime, enabled]) => !PLAYWRIGHT_RUNTIMES.includes(runtime) || typeof enabled !== "boolean")) return void 0;
2884
719
  return { version: 2, enabled: value.enabled };
2885
720
  } catch {
@@ -2918,12 +753,12 @@ function savePlaywrightCliPreference(file, enabled, selection) {
2918
753
  writeText(file, content);
2919
754
  }
2920
755
  function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
2921
- return path21.join(stateDir, "devtools-mcp.json");
756
+ return path11.join(stateDir, "devtools-mcp.json");
2922
757
  }
2923
758
  function primaryModelOwnershipFile(stateDir = dataDir()) {
2924
- return path21.join(stateDir, "primary-model.json");
759
+ return path11.join(stateDir, "primary-model.json");
2925
760
  }
2926
- function isRecord2(value) {
761
+ function isRecord(value) {
2927
762
  return value !== null && typeof value === "object" && !Array.isArray(value);
2928
763
  }
2929
764
  function isRuntimeId(value) {
@@ -2932,7 +767,7 @@ function isRuntimeId(value) {
2932
767
  function parseDevtoolsMcpState(raw) {
2933
768
  try {
2934
769
  const value = JSON.parse(raw);
2935
- if (!isRecord2(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord2(value.enabled) || !isRecord2(value.owned)) return null;
770
+ if (!isRecord(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord(value.enabled) || !isRecord(value.owned)) return null;
2936
771
  const enabled = {};
2937
772
  for (const [runtime, selected] of Object.entries(value.enabled)) {
2938
773
  if (!isRuntimeId(runtime) && runtime !== "pi" || typeof selected !== "boolean") return null;
@@ -2940,7 +775,7 @@ function parseDevtoolsMcpState(raw) {
2940
775
  }
2941
776
  const owned = {};
2942
777
  for (const [runtime, servers] of Object.entries(value.owned)) {
2943
- if (!isRuntimeId(runtime) || !isRecord2(servers)) return null;
778
+ if (!isRuntimeId(runtime) || !isRecord(servers)) return null;
2944
779
  const managed = {};
2945
780
  for (const [server, marked] of Object.entries(servers)) {
2946
781
  if (marked !== true) return null;
@@ -2996,13 +831,13 @@ function saveDevtoolsMcpOwnership(file, runtime, server, owned) {
2996
831
  function parsePrimaryModelOwnership(raw) {
2997
832
  try {
2998
833
  const value = JSON.parse(raw);
2999
- if (!isRecord2(value) || value.version !== PRIMARY_MODEL_OWNERSHIP_VERSION || !isRecord2(value.owned)) return null;
834
+ if (!isRecord(value) || value.version !== PRIMARY_MODEL_OWNERSHIP_VERSION || !isRecord(value.owned)) return null;
3000
835
  const owned = {};
3001
836
  for (const [runtime, configs] of Object.entries(value.owned)) {
3002
- if (!isRuntimeId(runtime) || !isRecord2(configs)) return null;
837
+ if (!isRuntimeId(runtime) || !isRecord(configs)) return null;
3003
838
  const markedConfigs = {};
3004
839
  for (const [configDir, fields] of Object.entries(configs)) {
3005
- if (configDir === "" || !isRecord2(fields)) return null;
840
+ if (configDir === "" || !isRecord(fields)) return null;
3006
841
  const markedFields = {};
3007
842
  for (const [field, state] of Object.entries(fields)) {
3008
843
  if (field === "" || state !== true) return null;
@@ -3031,7 +866,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
3031
866
  return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
3032
867
  }
3033
868
  function primaryModelConfigKey(configDir) {
3034
- const resolved = path21.resolve(configDir);
869
+ const resolved = path11.resolve(configDir);
3035
870
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
3036
871
  }
3037
872
  function loadPrimaryModelOwnership(file, runtime, configDir) {
@@ -3176,7 +1011,7 @@ function diffPlan(plan) {
3176
1011
  if (current === null) return { action, status: "create" };
3177
1012
  return { action, status: current === action.content ? "unchanged" : "update" };
3178
1013
  }
3179
- if (!fs17.existsSync(action.target)) return { action, status: "create" };
1014
+ if (!fs10.existsSync(action.target)) return { action, status: "create" };
3180
1015
  return { action, status: sameFileContent(action.source, action.target) ? "unchanged" : "update" };
3181
1016
  });
3182
1017
  }
@@ -3202,7 +1037,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE, playwr
3202
1037
  continue;
3203
1038
  }
3204
1039
  try {
3205
- for (const action of buildPlan(adapter, ctx)) targets.add(path22.resolve(action.target));
1040
+ for (const action of buildPlan(adapter, ctx)) targets.add(path12.resolve(action.target));
3206
1041
  } catch (error) {
3207
1042
  complete = false;
3208
1043
  warnings.push(
@@ -3212,6 +1047,40 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE, playwr
3212
1047
  }
3213
1048
  return { targets, complete, warnings };
3214
1049
  }
1050
+ function legacyOpencodePluginPath(configDir) {
1051
+ return path12.resolve(path12.join(configDir, "plugins", "engram.ts"));
1052
+ }
1053
+ async function runOfficialSetupForInstall(args) {
1054
+ let detectedVersion = null;
1055
+ if (args.runtime === "claude-code" && typeof args.engramBin === "string" && args.engramBin !== "") {
1056
+ try {
1057
+ detectedVersion = engramVersion(args.engramBin);
1058
+ } catch {
1059
+ detectedVersion = null;
1060
+ }
1061
+ }
1062
+ return runOfficialSetupIfNeeded(args.runtime, {
1063
+ command: args.command,
1064
+ dryRun: args.dryRun,
1065
+ targetDir: args.targetDir,
1066
+ engramBin: args.engramBin,
1067
+ configDir: args.configDir,
1068
+ homeDir: HOME,
1069
+ engramVersion: detectedVersion,
1070
+ ...args.runtime === "claude-code" ? { isExplicitClaudeConfigDir: process.env.CLAUDE_CONFIG_DIR !== void 0 } : {}
1071
+ });
1072
+ }
1073
+ function formatOfficialFailure(adapterName, official) {
1074
+ const detail = official.stderr ?? official.reason ?? "sin detalle";
1075
+ if (official.ok) return `${adapterName}: setup oficial Engram ok.`;
1076
+ if (official.incompleteRecovery === true) {
1077
+ return `${adapterName}: setup oficial Engram fall\xF3 (${detail}). Recuperaci\xF3n incompleta \u2014 revisa el backup ${official.backupId ?? "previo"}.`;
1078
+ }
1079
+ if (official.recovery === "complete") {
1080
+ return `${adapterName}: setup oficial Engram fall\xF3 (${detail}). Se restaur\xF3 el backup previo.`;
1081
+ }
1082
+ return `${adapterName}: setup oficial Engram fall\xF3 (${detail}).`;
1083
+ }
3215
1084
  async function runInstall(opts) {
3216
1085
  const showSummary = opts.showSummary !== false;
3217
1086
  if (showSummary) p.intro(`jorgex-stack ${opts.dryRun ? "install (dry-run)" : "install"}`);
@@ -3311,6 +1180,15 @@ async function runInstall(opts) {
3311
1180
  reportStatus(adapter.name, "failed");
3312
1181
  continue;
3313
1182
  }
1183
+ if (shouldRunOfficialSetup({ command: opts.command, dryRun: opts.dryRun, targetDir: opts.targetDir })) {
1184
+ const destinationError = validateOfficialSetupDestination(id, configDir, HOME);
1185
+ if (destinationError !== null) {
1186
+ p.log.error(`${adapter.name}: ${destinationError}`);
1187
+ exitCode = 1;
1188
+ reportStatus(adapter.name, "failed");
1189
+ continue;
1190
+ }
1191
+ }
3314
1192
  const ctx = {
3315
1193
  writingStyle,
3316
1194
  stackDir,
@@ -3355,19 +1233,75 @@ async function runInstall(opts) {
3355
1233
  reportStatus(adapter.name, "preview");
3356
1234
  continue;
3357
1235
  }
3358
- const writeManifest = () => {
1236
+ const writeManifest = async (official, opts2) => {
3359
1237
  if (!useManifest) return;
3360
- const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path22.resolve(a.target)));
1238
+ const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path12.resolve(a.target)));
3361
1239
  const keepTarget = (target) => !unmergeTargets.has(target);
3362
- const liveOwned = plan.map((a) => path22.resolve(a.target)).filter(keepTarget);
3363
- const previousOwned = (prevManifest?.owned ?? []).map((target) => path22.resolve(target)).filter(keepTarget);
3364
- const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
3365
- writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1240
+ const liveOwned = plan.map((a) => path12.resolve(a.target)).filter(keepTarget);
1241
+ const previousOwned = (prevManifest?.owned ?? []).map((target) => path12.resolve(target)).filter(keepTarget);
1242
+ const officialFailed = official?.ran === true && !official.ok;
1243
+ let owned;
1244
+ let pendingOrphans;
1245
+ if (!canOrphan) {
1246
+ owned = [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
1247
+ pendingOrphans = [];
1248
+ } else if (officialFailed) {
1249
+ owned = [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
1250
+ pendingOrphans = [...orphans];
1251
+ } else {
1252
+ const keepPending = (opts2?.keepPendingOrphans ?? []).map((t) => path12.resolve(t));
1253
+ owned = [...liveOwned];
1254
+ if (id === "opencode") {
1255
+ const legacy = legacyOpencodePluginPath(configDir);
1256
+ if (previousOwned.includes(legacy)) {
1257
+ let state = "unknown";
1258
+ try {
1259
+ fs10.statSync(legacy);
1260
+ state = "present";
1261
+ } catch (error) {
1262
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
1263
+ state = code === "ENOENT" ? "absent" : "unknown";
1264
+ }
1265
+ if (state !== "absent") {
1266
+ let retire = false;
1267
+ if (state === "present" && official?.ran === true && official.ok && official.ownershipTransferred) {
1268
+ try {
1269
+ retire = (await shouldRetireLegacyEngram({ configDir })).retire;
1270
+ } catch {
1271
+ retire = false;
1272
+ }
1273
+ }
1274
+ if (!retire && !owned.includes(legacy)) owned.push(legacy);
1275
+ }
1276
+ }
1277
+ }
1278
+ for (const pending of keepPending) {
1279
+ if (!owned.includes(pending)) owned.push(pending);
1280
+ }
1281
+ owned = [...new Set(owned)];
1282
+ pendingOrphans = [...keepPending];
1283
+ }
1284
+ writeRuntimeManifest(id, { configDir, owned, pendingOrphans, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
3366
1285
  };
3367
1286
  if (changes.length === 0 && orphans.length === 0) {
3368
- writeManifest();
3369
1287
  if (useManifest) persistConfigurationOwnershipChanges(id, configDir, plan);
3370
1288
  persistDevtoolsSelection();
1289
+ const official = await runOfficialSetupForInstall({
1290
+ runtime: id,
1291
+ configDir,
1292
+ engramBin,
1293
+ command: opts.command,
1294
+ dryRun: opts.dryRun,
1295
+ targetDir: opts.targetDir
1296
+ });
1297
+ if (official.ran && !official.ok) {
1298
+ await writeManifest(official);
1299
+ p.log.error(formatOfficialFailure(adapter.name, official));
1300
+ exitCode = 1;
1301
+ reportStatus(adapter.name, "failed");
1302
+ continue;
1303
+ }
1304
+ await writeManifest(official.ran ? official : void 0);
3371
1305
  p.log.success(`${adapter.name}: ya al d\xEDa (idempotente).`);
3372
1306
  successfulRuns++;
3373
1307
  successfulContexts.push({ adapter, ctx });
@@ -3391,11 +1325,6 @@ async function runInstall(opts) {
3391
1325
  const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
3392
1326
  if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
3393
1327
  applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
3394
- const pruneRoot = useManifest ? HOME : path22.dirname(configDir);
3395
- for (const orphan of orphans) {
3396
- fs17.rmSync(orphan, { force: true });
3397
- pruneEmptyDirs(orphan, pruneRoot);
3398
- }
3399
1328
  const verifyCtx = { ...ctx, warnings: [] };
3400
1329
  const dirty = diffPlan(buildPlan(adapter, verifyCtx)).filter((d) => d.status !== "unchanged");
3401
1330
  if (dirty.length > 0) {
@@ -3404,13 +1333,51 @@ async function runInstall(opts) {
3404
1333
  exitCode = 1;
3405
1334
  reportStatus(adapter.name, "failed");
3406
1335
  } else {
3407
- writeManifest();
3408
1336
  if (useManifest) persistConfigurationOwnershipChanges(id, configDir, plan);
3409
1337
  persistDevtoolsSelection();
3410
- p.log.success(`${adapter.name}: ${changes.length} archivos aplicados y verificados (idempotente).`);
3411
- successfulRuns++;
3412
- successfulContexts.push({ adapter, ctx });
3413
- reportStatus(adapter.name, "ok");
1338
+ const official = await runOfficialSetupForInstall({
1339
+ runtime: id,
1340
+ configDir,
1341
+ engramBin,
1342
+ command: opts.command,
1343
+ dryRun: opts.dryRun,
1344
+ targetDir: opts.targetDir
1345
+ });
1346
+ if (official.ran && !official.ok) {
1347
+ await writeManifest(official);
1348
+ p.log.error(formatOfficialFailure(adapter.name, official));
1349
+ exitCode = 1;
1350
+ reportStatus(adapter.name, "failed");
1351
+ } else {
1352
+ const pruneRoot = useManifest ? HOME : path12.dirname(configDir);
1353
+ let orphanFailed = null;
1354
+ let failedIndex = null;
1355
+ for (let index = 0; index < orphans.length; index++) {
1356
+ const orphan = orphans[index];
1357
+ if (path12.basename(orphan) === "engram.ts") continue;
1358
+ try {
1359
+ fs10.rmSync(orphan, { force: true });
1360
+ pruneEmptyDirs(orphan, pruneRoot);
1361
+ } catch (error) {
1362
+ orphanFailed = `${orphan} (${error instanceof Error ? error.message : String(error)})`;
1363
+ failedIndex = index;
1364
+ break;
1365
+ }
1366
+ }
1367
+ if (orphanFailed !== null) {
1368
+ const keepPending = failedIndex === null ? [...orphans] : orphans.slice(failedIndex);
1369
+ await writeManifest(official.ran ? official : void 0, { keepPendingOrphans: keepPending });
1370
+ p.log.error(`${adapter.name}: no se pudo eliminar hu\xE9rfano ${orphanFailed} \u2014 se conserva y no se reporta \xE9xito.`);
1371
+ exitCode = 1;
1372
+ reportStatus(adapter.name, "failed");
1373
+ } else {
1374
+ await writeManifest(official.ran ? official : void 0);
1375
+ p.log.success(`${adapter.name}: ${changes.length} archivos aplicados y verificados (idempotente).`);
1376
+ successfulRuns++;
1377
+ successfulContexts.push({ adapter, ctx });
1378
+ reportStatus(adapter.name, "ok");
1379
+ }
1380
+ }
3414
1381
  }
3415
1382
  }
3416
1383
  if (toolPlan?.actions.length) {
@@ -3523,33 +1490,33 @@ async function runInstall(opts) {
3523
1490
  }
3524
1491
 
3525
1492
  // src/uninstall.ts
3526
- import fs19 from "fs";
3527
- import path26 from "path";
1493
+ import fs12 from "fs";
1494
+ import path16 from "path";
3528
1495
  import * as p2 from "@clack/prompts";
3529
1496
 
3530
1497
  // src/lib/pi-projection-lifecycle.ts
3531
- import fs18 from "fs";
3532
- import path25 from "path";
1498
+ import fs11 from "fs";
1499
+ import path15 from "path";
3533
1500
  import { createHash } from "crypto";
3534
1501
 
3535
1502
  // src/adapters/pi.ts
3536
- import path23 from "path";
1503
+ import path13 from "path";
3537
1504
  function piSystemPromptFile(targetDir) {
3538
- const configDir = targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path23.join(HOME, ".pi", "agent") : path23.join(path23.resolve(targetDir), "pi-agent");
3539
- return path23.join(configDir, "AGENTS.md");
1505
+ const configDir = targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path13.join(HOME, ".pi", "agent") : path13.join(path13.resolve(targetDir), "pi-agent");
1506
+ return path13.join(configDir, "AGENTS.md");
3540
1507
  }
3541
1508
  var piAdapter = {
3542
1509
  id: "pi",
3543
1510
  paths(configDir) {
3544
- const piConfigDir = path23.dirname(piSystemPromptFile());
3545
- const agentsHome = samePath(configDir, piConfigDir) ? HOME : path23.join(path23.dirname(configDir), "home");
1511
+ const piConfigDir = path13.dirname(piSystemPromptFile());
1512
+ const agentsHome = samePath(configDir, piConfigDir) ? HOME : path13.join(path13.dirname(configDir), "home");
3546
1513
  return {
3547
- systemPromptFile: path23.join(configDir, "AGENTS.md"),
3548
- agentsDir: path23.join(configDir, "agents"),
3549
- skillsDir: path23.join(agentsHome, ".agents", "skills"),
3550
- commandsDir: path23.join(configDir, "prompts"),
1514
+ systemPromptFile: path13.join(configDir, "AGENTS.md"),
1515
+ agentsDir: path13.join(configDir, "agents"),
1516
+ skillsDir: path13.join(agentsHome, ".agents", "skills"),
1517
+ commandsDir: path13.join(configDir, "prompts"),
3551
1518
  pluginsDir: null,
3552
- scriptsDir: path23.join(configDir, "scripts"),
1519
+ scriptsDir: path13.join(configDir, "scripts"),
3553
1520
  outputStylesDir: null,
3554
1521
  profilesDir: null
3555
1522
  };
@@ -3569,7 +1536,7 @@ var piAdapter = {
3569
1536
  };
3570
1537
 
3571
1538
  // src/lib/pi-package-lifecycle.ts
3572
- import path24 from "path";
1539
+ import path14 from "path";
3573
1540
  var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
3574
1541
  "foundation-contract-v1",
3575
1542
  "runner-json-v1",
@@ -3721,7 +1688,7 @@ function parseReceiptShape(receiptJson) {
3721
1688
  return null;
3722
1689
  }
3723
1690
  if (engram === void 0) return "upgrade-required";
3724
- if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path24.isAbsolute(Reflect.get(engram, "binary"))) {
1691
+ if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path14.isAbsolute(Reflect.get(engram, "binary"))) {
3725
1692
  return null;
3726
1693
  }
3727
1694
  return parsed;
@@ -3758,7 +1725,7 @@ function planPiPackageLifecycle(input) {
3758
1725
  if (input.receiptJson !== null) {
3759
1726
  const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
3760
1727
  kind: input.scope.kind,
3761
- codingAgentDir: path24.resolve(input.scope.codingAgentDir)
1728
+ codingAgentDir: path14.resolve(input.scope.codingAgentDir)
3762
1729
  }, input.engramBin);
3763
1730
  if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
3764
1731
  if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
@@ -3793,7 +1760,7 @@ function planPiPackageLifecycle(input) {
3793
1760
  },
3794
1761
  receipt: expectedReceipt(input.candidate, "installing", {
3795
1762
  kind: input.scope.kind,
3796
- codingAgentDir: path24.resolve(input.scope.codingAgentDir)
1763
+ codingAgentDir: path14.resolve(input.scope.codingAgentDir)
3797
1764
  }, input.engramBin),
3798
1765
  ownership: ownership(true)
3799
1766
  };
@@ -3808,7 +1775,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
3808
1775
  const parsed = JSON.parse(body);
3809
1776
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3810
1777
  const record = parsed;
3811
- 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" || !path24.isAbsolute(record.package.root) || path24.resolve(packageRunner) !== path24.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
1778
+ 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" || !path14.isAbsolute(record.package.root) || path14.resolve(packageRunner) !== path14.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
3812
1779
  return null;
3813
1780
  }
3814
1781
  return record;
@@ -3956,10 +1923,10 @@ function validateOwnedOperationState(input) {
3956
1923
  }))) {
3957
1924
  return { kind: "blocked", reason: "receipt-untrusted" };
3958
1925
  }
3959
- if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path24.resolve(receipt.scope.codingAgentDir) !== path24.resolve(input.paths.codingAgentDir)) {
1926
+ if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path14.resolve(receipt.scope.codingAgentDir) !== path14.resolve(input.paths.codingAgentDir)) {
3960
1927
  return { kind: "blocked", reason: "source-divergent" };
3961
1928
  }
3962
- if (input.engramBin !== null && path24.resolve(receipt.engram.binary) !== path24.resolve(input.engramBin)) {
1929
+ if (input.engramBin !== null && path14.resolve(receipt.engram.binary) !== path14.resolve(input.engramBin)) {
3963
1930
  return { kind: "blocked", reason: "receipt-corrupt" };
3964
1931
  }
3965
1932
  const source = receipt.candidate.package.source;
@@ -4066,7 +2033,7 @@ var RETIRED_PLAYWRIGHT_FILES = [
4066
2033
  "references/video-recording.md"
4067
2034
  ];
4068
2035
  function retiredPlaywrightFiles(scope) {
4069
- return RETIRED_PLAYWRIGHT_FILES.map((file) => path25.resolve(scope.home, ".agents", "skills", "playwright-cli", file));
2036
+ return RETIRED_PLAYWRIGHT_FILES.map((file) => path15.resolve(scope.home, ".agents", "skills", "playwright-cli", file));
4070
2037
  }
4071
2038
  function handoffEnabled(input, kind) {
4072
2039
  return (kind === "devtools" ? input.devtoolsMcpEnabled : input.playwrightHandoffEnabled) === true;
@@ -4075,18 +2042,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
4075
2042
  function projectionScope(scope) {
4076
2043
  return {
4077
2044
  ...scope,
4078
- settingsFile: path25.join(scope.codingAgentDir, "settings.json")
2045
+ settingsFile: path15.join(scope.codingAgentDir, "settings.json")
4079
2046
  };
4080
2047
  }
4081
2048
  function isInside(root, file) {
4082
- const relative = path25.relative(path25.resolve(root), path25.resolve(file));
4083
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path25.sep}`) && !path25.isAbsolute(relative);
2049
+ const relative = path15.relative(path15.resolve(root), path15.resolve(file));
2050
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path15.sep}`) && !path15.isAbsolute(relative);
4084
2051
  }
4085
2052
  function isManagedPath(scope, file) {
4086
2053
  return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
4087
2054
  }
4088
2055
  function isAllowedPath(scope, file) {
4089
- return isManagedPath(scope, file) || path25.resolve(file) === path25.resolve(scope.receiptFile);
2056
+ return isManagedPath(scope, file) || path15.resolve(file) === path15.resolve(scope.receiptFile);
4090
2057
  }
4091
2058
  function projectedPiAdapter(scope) {
4092
2059
  const paths = piAdapter.paths(scope.codingAgentDir);
@@ -4094,7 +2061,7 @@ function projectedPiAdapter(scope) {
4094
2061
  ...piAdapter,
4095
2062
  paths: () => ({
4096
2063
  ...paths,
4097
- skillsDir: path25.join(scope.home, ".agents", "skills")
2064
+ skillsDir: path15.join(scope.home, ".agents", "skills")
4098
2065
  })
4099
2066
  };
4100
2067
  }
@@ -4138,20 +2105,20 @@ function projectionPlan(input, scope, includePrompt = true) {
4138
2105
  return actions;
4139
2106
  }
4140
2107
  function handoffPath(scope, kind) {
4141
- return path25.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
2108
+ return path15.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
4142
2109
  }
4143
2110
  function contentHash(content) {
4144
2111
  return createHash("sha256").update(content).digest("hex");
4145
2112
  }
4146
2113
  function canonicalActionContent(action) {
4147
- return action.kind === "write" ? action.content : fs18.readFileSync(action.source, "utf8");
2114
+ return action.kind === "write" ? action.content : fs11.readFileSync(action.source, "utf8");
4148
2115
  }
4149
2116
  function hasActionDrift(action, deps) {
4150
2117
  return deps.readText(action.target) !== canonicalActionContent(action);
4151
2118
  }
4152
2119
  function applyActions(actions, deps, handoffs = []) {
4153
2120
  for (const action of actions) {
4154
- const handoff = handoffs.find((entry) => path25.resolve(action.target) === entry.file);
2121
+ const handoff = handoffs.find((entry) => path15.resolve(action.target) === entry.file);
4155
2122
  if (handoff) {
4156
2123
  try {
4157
2124
  if (deps.readText(handoff.file) !== handoff.previous) return handoffConflict(handoff.kind, handoff.file);
@@ -4171,9 +2138,9 @@ function assertPlanContained(plan, scope) {
4171
2138
  }
4172
2139
  }
4173
2140
  function receiptFor(plan, scope) {
4174
- const systemPrompt = path25.join(scope.codingAgentDir, "AGENTS.md");
2141
+ const systemPrompt = path15.join(scope.codingAgentDir, "AGENTS.md");
4175
2142
  const owned = [...new Set(
4176
- plan.map((action) => path25.resolve(action.target)).filter((target) => target !== systemPrompt)
2143
+ plan.map((action) => path15.resolve(action.target)).filter((target) => target !== systemPrompt)
4177
2144
  )];
4178
2145
  const receipt = {
4179
2146
  schemaVersion: 1,
@@ -4186,7 +2153,7 @@ function receiptFor(plan, scope) {
4186
2153
  owned
4187
2154
  };
4188
2155
  for (const kind of HANDOFF_KINDS) {
4189
- const action = plan.find((entry) => path25.resolve(entry.target) === handoffPath(scope, kind));
2156
+ const action = plan.find((entry) => path15.resolve(entry.target) === handoffPath(scope, kind));
4190
2157
  if (action) receipt[kind] = { sha256: contentHash(canonicalActionContent(action)) };
4191
2158
  }
4192
2159
  return receipt;
@@ -4227,7 +2194,7 @@ function parseReceipt2(raw, expected) {
4227
2194
  if (matches(expectedOwned)) return { schemaVersion: 1, scope: expected.scope, owned: expectedOwned, ...digests };
4228
2195
  const retired = retiredPlaywrightFiles(expected.scope);
4229
2196
  if (retired.some((file) => expectedOwned.includes(file))) return null;
4230
- const skillsRoot = `${path25.resolve(expected.scope.home, ".agents", "skills")}${path25.sep}`;
2197
+ const skillsRoot = `${path15.resolve(expected.scope.home, ".agents", "skills")}${path15.sep}`;
4231
2198
  const insertion = expectedOwned.findIndex((file) => !file.startsWith(skillsRoot) || file > retired[0]);
4232
2199
  const legacyOwned = [...expectedOwned];
4233
2200
  legacyOwned.splice(insertion === -1 ? legacyOwned.length : insertion, 0, ...retired);
@@ -4246,13 +2213,13 @@ function realPiProjectionScope() {
4246
2213
  return {
4247
2214
  kind: "real",
4248
2215
  home: HOME,
4249
- codingAgentDir: path25.dirname(piSystemPromptFile()),
4250
- receiptFile: path25.join(dataDir(), "pi-projection-receipt.json")
2216
+ codingAgentDir: path15.dirname(piSystemPromptFile()),
2217
+ receiptFile: path15.join(dataDir(), "pi-projection-receipt.json")
4251
2218
  };
4252
2219
  }
4253
2220
  function readTextOnlyIfMissing(file) {
4254
2221
  try {
4255
- return fs18.readFileSync(file, "utf8");
2222
+ return fs11.readFileSync(file, "utf8");
4256
2223
  } catch (error) {
4257
2224
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT") return null;
4258
2225
  throw error;
@@ -4285,13 +2252,13 @@ function manifestOwned(manifest) {
4285
2252
  return new Set([
4286
2253
  ...manifest.runtimes.codex?.owned ?? [],
4287
2254
  ...manifest.runtimes.opencode?.owned ?? []
4288
- ].map((file) => path25.resolve(file)));
2255
+ ].map((file) => path15.resolve(file)));
4289
2256
  }
4290
2257
  function withoutManagedPromptSections(content) {
4291
2258
  return removeSystemPromptSections(content);
4292
2259
  }
4293
2260
  function uniquePaths(paths) {
4294
- return [...new Set(paths.map((file) => path25.resolve(file)))];
2261
+ return [...new Set(paths.map((file) => path15.resolve(file)))];
4295
2262
  }
4296
2263
  function blocked2(reason, paths, remedy) {
4297
2264
  return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
@@ -4398,7 +2365,7 @@ function preparePiProjectionUninstall(input, deps) {
4398
2365
  }
4399
2366
  const scope = projectionScope(input.scope);
4400
2367
  const expectedReceipt2 = expectedProjectionReceipt(input, scope);
4401
- const prompt = path25.join(scope.codingAgentDir, "AGENTS.md");
2368
+ const prompt = path15.join(scope.codingAgentDir, "AGENTS.md");
4402
2369
  let existingPrompt;
4403
2370
  try {
4404
2371
  existingPrompt = deps.readText(prompt);
@@ -4411,7 +2378,7 @@ function preparePiProjectionUninstall(input, deps) {
4411
2378
  return blocked2("projection-prompt-markers", [prompt], error instanceof Error ? error.message : String(error));
4412
2379
  }
4413
2380
  const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
4414
- const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path25.resolve(prompt), content: promptContent };
2381
+ const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path15.resolve(prompt), content: promptContent };
4415
2382
  const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
4416
2383
  if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
4417
2384
  let ownedToRemove = [];
@@ -4429,7 +2396,7 @@ function preparePiProjectionUninstall(input, deps) {
4429
2396
  return cleanupFailure([scope.receiptFile]);
4430
2397
  }
4431
2398
  ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
4432
- receiptFile = path25.resolve(scope.receiptFile);
2399
+ receiptFile = path15.resolve(scope.receiptFile);
4433
2400
  backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
4434
2401
  }
4435
2402
  const handoffs = [];
@@ -4507,16 +2474,16 @@ function runPiProjectionLifecycle(input, deps) {
4507
2474
  const prepared = preparePiProjectionUninstall(input, deps);
4508
2475
  return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
4509
2476
  }
4510
- const prompt = path25.join(scope.codingAgentDir, "AGENTS.md");
2477
+ const prompt = path15.join(scope.codingAgentDir, "AGENTS.md");
4511
2478
  try {
4512
2479
  assertSystemPromptMarkers(deps.readText(prompt), prompt);
4513
2480
  } catch (error) {
4514
2481
  return blocked2("projection-prompt-markers", [prompt], error instanceof Error ? error.message : String(error));
4515
2482
  }
4516
- if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path25.isAbsolute(input.pnpmBin))) {
2483
+ if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path15.isAbsolute(input.pnpmBin))) {
4517
2484
  return blocked2("projection-devtools-command", [handoffPath(scope, "devtools")], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
4518
2485
  }
4519
- if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path25.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
2486
+ if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path15.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
4520
2487
  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.");
4521
2488
  }
4522
2489
  const plan = projectionPlan(input, scope);
@@ -4529,12 +2496,12 @@ function runPiProjectionLifecycle(input, deps) {
4529
2496
  const expectedReceipt2 = receiptContent(receipt);
4530
2497
  const drifted = plan.filter((action) => hasActionDrift(action, deps));
4531
2498
  for (const { kind, file } of checked.handoffs) {
4532
- if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path25.resolve(action.target) === file)) {
2499
+ if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path15.resolve(action.target) === file)) {
4533
2500
  return input.operation === "doctor" ? { kind: "drift", paths: [file] } : handoffConflict(kind, file);
4534
2501
  }
4535
2502
  }
4536
2503
  if (input.operation === "doctor") {
4537
- const paths = drifted.map((action) => path25.resolve(action.target));
2504
+ const paths = drifted.map((action) => path15.resolve(action.target));
4538
2505
  paths.push(...removedHandoffs.map(({ file }) => file), ...retiredSkills);
4539
2506
  const currentSettings2 = deps.readText(scope.settingsFile);
4540
2507
  if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
@@ -4593,12 +2560,12 @@ function runPiProjectionLifecycle(input, deps) {
4593
2560
  return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0 };
4594
2561
  }
4595
2562
  function systemProjectionLifecycle(input) {
4596
- const targetRoot = input.targetDir === void 0 ? null : path25.resolve(input.targetDir);
2563
+ const targetRoot = input.targetDir === void 0 ? null : path15.resolve(input.targetDir);
4597
2564
  const scope = targetRoot === null ? realPiProjectionScope() : {
4598
2565
  kind: "target-dir",
4599
- home: path25.join(targetRoot, "home"),
4600
- codingAgentDir: path25.dirname(piSystemPromptFile(targetRoot)),
4601
- receiptFile: path25.join(targetRoot, "state", "pi-projection-receipt.json")
2566
+ home: path15.join(targetRoot, "home"),
2567
+ codingAgentDir: path15.dirname(piSystemPromptFile(targetRoot)),
2568
+ receiptFile: path15.join(targetRoot, "state", "pi-projection-receipt.json")
4602
2569
  };
4603
2570
  return {
4604
2571
  input: {
@@ -4619,14 +2586,14 @@ function systemProjectionLifecycle(input) {
4619
2586
  backup: (paths) => createBackup(
4620
2587
  paths,
4621
2588
  `pi-projection-${input.operation}`,
4622
- targetRoot === null ? void 0 : path25.join(targetRoot, "backups")
2589
+ targetRoot === null ? void 0 : path15.join(targetRoot, "backups")
4623
2590
  ),
4624
2591
  writeText,
4625
2592
  copyFile,
4626
2593
  removeFile: (file) => {
4627
- fs18.rmSync(file, { force: true });
4628
- if (retiredPlaywrightFiles(scope).includes(path25.resolve(file))) {
4629
- pruneEmptyDirs(file, path25.join(scope.home, ".agents", "skills"));
2594
+ fs11.rmSync(file, { force: true });
2595
+ if (retiredPlaywrightFiles(scope).includes(path15.resolve(file))) {
2596
+ pruneEmptyDirs(file, path15.join(scope.home, ".agents", "skills"));
4630
2597
  }
4631
2598
  },
4632
2599
  readManifest: targetRoot === null ? readManifest : () => ({ runtimes: {} })
@@ -4729,7 +2696,7 @@ async function runUninstall(opts) {
4729
2696
  if (!detection.installed) continue;
4730
2697
  const keepCtx = makeContext(keep, detection.configDir);
4731
2698
  if (!keepCtx) continue;
4732
- for (const action of buildContentPlan(keep, keepCtx)) retained.add(path26.resolve(action.target));
2699
+ for (const action of buildContentPlan(keep, keepCtx)) retained.add(path16.resolve(action.target));
4733
2700
  }
4734
2701
  }
4735
2702
  for (const id of opts.runtimes) {
@@ -4755,16 +2722,20 @@ async function runUninstall(opts) {
4755
2722
  exitCode = 1;
4756
2723
  continue;
4757
2724
  }
4758
- const mergedTargets = new Set(unmerge.map((a) => path26.resolve(a.target)));
2725
+ const mergedTargets = new Set(unmerge.map((a) => path16.resolve(a.target)));
4759
2726
  const usingRealConfig = opts.targetDir === void 0;
4760
2727
  const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
4761
- const pruneRoot = usingRealConfig ? HOME : path26.dirname(configDir);
2728
+ const pruneRoot = usingRealConfig ? HOME : path16.dirname(configDir);
4762
2729
  const planTargets = [
4763
- .../* @__PURE__ */ new Set([...buildContentPlan(adapter, ctx).map((a) => path26.resolve(a.target)), ...prevOwned.map((t) => path26.resolve(t))])
4764
- ].filter((t) => !mergedTargets.has(t) && fs19.existsSync(t));
4765
- const deleteTargets = planTargets.filter(
4766
- (t) => !retained.has(t) && !(ctx.preserveEngram && path26.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
4767
- );
2730
+ .../* @__PURE__ */ new Set([...buildContentPlan(adapter, ctx).map((a) => path16.resolve(a.target)), ...prevOwned.map((t) => path16.resolve(t))])
2731
+ ].filter((t) => !mergedTargets.has(t) && fs12.existsSync(t));
2732
+ const deleteTargets = planTargets.filter((t) => {
2733
+ if (retained.has(t) || !isContainedIn(t, pruneRoot)) return false;
2734
+ if (path16.basename(t) !== "engram.ts") return true;
2735
+ const state = inspectOpencodePluginFile(t);
2736
+ if (state === "official" || state === "unknown") return false;
2737
+ return !ctx.preserveEngram;
2738
+ });
4768
2739
  const sharedKept = planTargets.length - deleteTargets.length;
4769
2740
  p2.log.step(`${adapter.name} \u2192 ${configDir}`);
4770
2741
  p2.log.info(`${deleteTargets.length} archivos a borrar, ${unmerge.length} archivos compartidos a limpiar`);
@@ -4773,7 +2744,7 @@ async function runUninstall(opts) {
4773
2744
  let backup;
4774
2745
  try {
4775
2746
  backup = createBackup(
4776
- [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs19.existsSync(t))],
2747
+ [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs12.existsSync(t))],
4777
2748
  `uninstall-${id}`
4778
2749
  );
4779
2750
  } catch (error) {
@@ -4783,13 +2754,13 @@ async function runUninstall(opts) {
4783
2754
  }
4784
2755
  if (backup) p2.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
4785
2756
  for (const target of deleteTargets) {
4786
- fs19.rmSync(target, { force: true });
2757
+ fs12.rmSync(target, { force: true });
4787
2758
  pruneEmptyDirs(target, pruneRoot);
4788
2759
  }
4789
2760
  for (const action of unmerge) {
4790
2761
  if (action.kind !== "write") continue;
4791
2762
  if (action.content.trim() === "") {
4792
- fs19.rmSync(action.target, { force: true });
2763
+ fs12.rmSync(action.target, { force: true });
4793
2764
  } else {
4794
2765
  writeText(action.target, action.content);
4795
2766
  }
@@ -4843,12 +2814,12 @@ async function runUninstall(opts) {
4843
2814
  }
4844
2815
 
4845
2816
  // src/doctor.ts
4846
- import path27 from "path";
4847
- import fs20 from "fs";
2817
+ import path17 from "path";
2818
+ import fs13 from "fs";
4848
2819
  import * as p3 from "@clack/prompts";
4849
2820
  function readDoctorTextIfExists(file) {
4850
2821
  try {
4851
- return fs20.readFileSync(file, "utf8");
2822
+ return fs13.readFileSync(file, "utf8");
4852
2823
  } catch (error) {
4853
2824
  if (error.code === "ENOENT") return null;
4854
2825
  throw error;
@@ -4857,12 +2828,12 @@ function readDoctorTextIfExists(file) {
4857
2828
  var STALE_PERMISSIONS_MARKER = "differs from the stack default and was left untouched";
4858
2829
  var UPGRADE_PERMISSIONS_REMEDY = "jorgex-stack sync --upgrade-permissions --dry-run";
4859
2830
  function piAgentDir(targetDir) {
4860
- return targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path27.join(HOME, ".pi", "agent") : path27.join(targetDir, "pi-agent");
2831
+ return targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path17.join(HOME, ".pi", "agent") : path17.join(targetDir, "pi-agent");
4861
2832
  }
4862
2833
  function reportPiPermissions(targetDir) {
4863
2834
  const agentDir = piAgentDir(targetDir);
4864
- const configFile = path27.join(agentDir, "extensions", "pi-permission-system", "config.json");
4865
- const receiptFile = path27.join(agentDir, "jorgex-pi", "permissions-lifecycle.v1.json");
2835
+ const configFile = path17.join(agentDir, "extensions", "pi-permission-system", "config.json");
2836
+ const receiptFile = path17.join(agentDir, "jorgex-pi", "permissions-lifecycle.v1.json");
4866
2837
  let config;
4867
2838
  let receipt;
4868
2839
  try {
@@ -4885,11 +2856,6 @@ function reportPiPermissions(targetDir) {
4885
2856
  );
4886
2857
  return 1;
4887
2858
  }
4888
- function engramVersion(bin) {
4889
- const out = runDetectedBin(bin, ["--version"], 5e3);
4890
- if (out === null) return null;
4891
- return /(\d+\.\d+\.\d+)/.exec(out)?.[1] ?? out.trim().split("\n")[0] ?? null;
4892
- }
4893
2859
  function resolvePlaywrightDoctorState(input) {
4894
2860
  if (input.enabled !== true) return { status: "disabled" };
4895
2861
  if (input.cli.status === "not-in-path") return { status: "not-in-path" };
@@ -4904,7 +2870,7 @@ function resolvePlaywrightDoctorState(input) {
4904
2870
  return { status: "healthy" };
4905
2871
  }
4906
2872
  function context7KeyConfigured(id, configDir) {
4907
- const file = id === "codex" ? path27.join(configDir, "config.toml") : id === "claude-code" ? path27.join(path27.dirname(configDir), `${path27.basename(configDir)}.json`) : path27.join(configDir, "opencode.json");
2873
+ const file = id === "codex" ? path17.join(configDir, "config.toml") : id === "claude-code" ? path17.join(path17.dirname(configDir), `${path17.basename(configDir)}.json`) : path17.join(configDir, "opencode.json");
4908
2874
  const content = readTextIfExists(file);
4909
2875
  if (content === null) return null;
4910
2876
  const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
@@ -4924,7 +2890,7 @@ function reportWritingStyle(options, style, mode) {
4924
2890
  for (const id of runtimes) {
4925
2891
  const adapter = id === "pi" ? piAdapter : ADAPTERS[id];
4926
2892
  if (adapter === void 0) continue;
4927
- const configDir = id === "pi" ? options.targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path27.join(HOME, ".pi", "agent") : path27.join(options.targetDir, "pi-agent") : options.targetDir ?? ADAPTERS[id].detect().configDir;
2893
+ const configDir = id === "pi" ? options.targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path17.join(HOME, ".pi", "agent") : path17.join(options.targetDir, "pi-agent") : options.targetDir ?? ADAPTERS[id].detect().configDir;
4928
2894
  const file = adapter.paths(configDir).systemPromptFile;
4929
2895
  try {
4930
2896
  const content = readDoctorTextIfExists(file) ?? "";
@@ -4939,7 +2905,7 @@ function reportWritingStyle(options, style, mode) {
4939
2905
  problems++;
4940
2906
  }
4941
2907
  if (id === "codex") {
4942
- const override = path27.join(configDir, "AGENTS.override.md");
2908
+ const override = path17.join(configDir, "AGENTS.override.md");
4943
2909
  if ((readDoctorTextIfExists(override) ?? "").trim() !== "") {
4944
2910
  p3.log.warn(`Codex: ${override} no vac\xEDo puede ocultar el AGENTS.md gestionado; rev\xEDsalo sin modificarlo autom\xE1ticamente.`);
4945
2911
  problems++;
@@ -4952,6 +2918,34 @@ function reportWritingStyle(options, style, mode) {
4952
2918
  }
4953
2919
  return problems;
4954
2920
  }
2921
+ async function verifyOfficialForRuntime(runtime, configDir, engramBin, homeDir, isExplicitClaudeConfigDir) {
2922
+ if (runtime === "claude-code") {
2923
+ const { verifyOfficialSetup: verifyOfficialSetup2 } = await import("./claude-code-WY4JJAXP.js");
2924
+ const explicit = isExplicitClaudeConfigDir ?? process.env.CLAUDE_CONFIG_DIR !== void 0;
2925
+ const report2 = await verifyOfficialSetup2({ configDir, engramBin, homeDir, isExplicitClaudeConfigDir: explicit });
2926
+ return {
2927
+ ok: report2.ok,
2928
+ layers: report2.layers,
2929
+ ...report2.reason === void 0 ? {} : { reason: report2.reason }
2930
+ };
2931
+ }
2932
+ if (runtime === "codex") {
2933
+ const { verifyOfficialSetup: verifyOfficialSetup2 } = await import("./codex-WSWHXYJ3.js");
2934
+ const report2 = await verifyOfficialSetup2({ configDir, engramBin });
2935
+ return {
2936
+ ok: report2.ok,
2937
+ layers: report2.layers,
2938
+ ...report2.reason === void 0 ? {} : { reason: report2.reason }
2939
+ };
2940
+ }
2941
+ const { verifyOfficialSetup } = await import("./opencode-NUDGK6J7.js");
2942
+ const report = await verifyOfficialSetup({ configDir, engramBin });
2943
+ return {
2944
+ ok: report.ok,
2945
+ layers: report.layers,
2946
+ ...report.reason === void 0 ? {} : { reason: report.reason }
2947
+ };
2948
+ }
4955
2949
  async function runDoctor(options = {}) {
4956
2950
  p3.intro("jorgex-stack doctor");
4957
2951
  let writingStyle;
@@ -4973,6 +2967,7 @@ async function runDoctor(options = {}) {
4973
2967
  return problems > 0 ? 1 : 0;
4974
2968
  }
4975
2969
  const engramBin = detectEngram();
2970
+ let engramBinAvailable = false;
4976
2971
  if (engramBin === null) {
4977
2972
  p3.log.warn("Engram: NO detectado. El protocolo de memoria no funcionar\xE1 \u2014 inst\xE1lalo: github.com/Gentleman-Programming/engram");
4978
2973
  problems++;
@@ -4983,15 +2978,17 @@ async function runDoctor(options = {}) {
4983
2978
  problems++;
4984
2979
  } else {
4985
2980
  p3.log.success(`Engram: ${version} (${engramBin})`);
2981
+ engramBinAvailable = true;
4986
2982
  }
4987
2983
  }
4988
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path27.join(HOME, ".engram");
4989
- const engramDb = path27.join(engramDataDir, "engram.db");
4990
- if (fs20.existsSync(engramDb)) {
4991
- const sizeMb = (fs20.statSync(engramDb).size / 1024 / 1024).toFixed(1);
2984
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path17.join(HOME, ".engram");
2985
+ const engramDb = path17.join(engramDataDir, "engram.db");
2986
+ if (fs13.existsSync(engramDb)) {
2987
+ const sizeMb = (fs13.statSync(engramDb).size / 1024 / 1024).toFixed(1);
4992
2988
  p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
4993
2989
  }
4994
- if (!fs20.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
2990
+ const officialResults = [];
2991
+ if (!fs13.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
4995
2992
  const preferenceErrors = browserPreferenceErrors();
4996
2993
  const primaryOwnershipError = primaryModelOwnershipError();
4997
2994
  if (primaryOwnershipError !== null) {
@@ -5090,27 +3087,64 @@ async function runDoctor(options = {}) {
5090
3087
  p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
5091
3088
  problems++;
5092
3089
  }
5093
- if (adapter.id === "codex" && fs20.existsSync(path27.join(detection.configDir, "hooks.json"))) {
3090
+ if (adapter.id === "codex" && fs13.existsSync(path17.join(detection.configDir, "hooks.json"))) {
5094
3091
  p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
5095
3092
  }
5096
3093
  const key = context7KeyConfigured(adapter.id, detection.configDir);
5097
3094
  if (key === false) p3.log.info(`${adapter.name}: context7 sin key (opcional \u2014 con\xE9ctala cuando quieras).`);
3095
+ try {
3096
+ const explicitClaude = adapter.id === "claude-code" ? process.env.CLAUDE_CONFIG_DIR !== void 0 : void 0;
3097
+ const setup = await verifyOfficialForRuntime(
3098
+ adapter.id,
3099
+ detection.configDir,
3100
+ engramBin ?? "",
3101
+ HOME,
3102
+ explicitClaude
3103
+ );
3104
+ const exposed = setup.layers.includes("mcp") && engramBinAvailable;
3105
+ officialResults.push({ runtime: adapter.id, ok: setup.ok, layers: setup.layers, exposed });
3106
+ if (!setup.ok) {
3107
+ const reason = typeof setup.reason === "string" ? setup.reason.trim() : "";
3108
+ if (reason !== "") {
3109
+ p3.log.warn(`${adapter.name}: ${reason}`);
3110
+ } else {
3111
+ p3.log.warn(
3112
+ `${adapter.name}: setup oficial Engram incompleto (${setup.layers.join(", ")}) \u2192 ejecuta install.`
3113
+ );
3114
+ }
3115
+ problems++;
3116
+ }
3117
+ } catch (error) {
3118
+ p3.log.warn(
3119
+ `${adapter.name}: setup oficial Engram no verificable (${error instanceof Error ? error.message : String(error)}).`
3120
+ );
3121
+ problems++;
3122
+ }
3123
+ }
3124
+ if (officialResults.length === 0) {
3125
+ p3.log.info("Engram official setup: no hay runtimes instalados seleccionados para verificar.");
3126
+ p3.log.info("Engram official exposure: no verificado (sin runtimes instalados seleccionados).");
3127
+ } else {
3128
+ const setupSummary = officialResults.map((r) => `${r.runtime}=${r.ok ? "ok" : "missing"} (${r.layers.join(",")})`).join("; ");
3129
+ const exposureSummary = officialResults.map((r) => `${r.runtime}=${r.exposed ? "exposed" : "not-exposed"}`).join("; ");
3130
+ p3.log.info(`Engram official setup: ${setupSummary}.`);
3131
+ p3.log.info(`Engram official exposure: ${exposureSummary} (MCP registrado y binario disponible; sin certificar carga en runtime).`);
5098
3132
  }
5099
3133
  p3.outro(problems === 0 ? "Doctor: todo sano." : `Doctor: ${problems} aviso(s) \u2014 revisa arriba.`);
5100
3134
  return problems > 0 ? 1 : 0;
5101
3135
  }
5102
3136
 
5103
3137
  // src/update.ts
5104
- import fs23 from "fs";
5105
- import path30 from "path";
3138
+ import fs16 from "fs";
3139
+ import path20 from "path";
5106
3140
  import os3 from "os";
5107
- import { execFileSync as execFileSync5 } from "child_process";
3141
+ import { execFileSync as execFileSync4 } from "child_process";
5108
3142
  import * as p4 from "@clack/prompts";
5109
3143
 
5110
3144
  // src/lib/github.ts
5111
- import fs21 from "fs";
5112
- import path28 from "path";
5113
- import { execFileSync as execFileSync3 } from "child_process";
3145
+ import fs14 from "fs";
3146
+ import path18 from "path";
3147
+ import { execFileSync as execFileSync2 } from "child_process";
5114
3148
  import os2 from "os";
5115
3149
  import { Readable } from "stream";
5116
3150
  import { pipeline } from "stream/promises";
@@ -5179,19 +3213,19 @@ async function latestGithubCommit(repo) {
5179
3213
  }
5180
3214
  }
5181
3215
  function validateExtractedTree(destDir) {
5182
- const resolved = path28.resolve(destDir);
3216
+ const resolved = path18.resolve(destDir);
5183
3217
  const walk = (dir) => {
5184
3218
  let entries;
5185
3219
  try {
5186
- entries = fs21.readdirSync(dir, { withFileTypes: true });
3220
+ entries = fs14.readdirSync(dir, { withFileTypes: true });
5187
3221
  } catch {
5188
3222
  return false;
5189
3223
  }
5190
3224
  for (const entry of entries) {
5191
- const full = path28.join(dir, entry.name);
3225
+ const full = path18.join(dir, entry.name);
5192
3226
  let stat;
5193
3227
  try {
5194
- stat = fs21.lstatSync(full);
3228
+ stat = fs14.lstatSync(full);
5195
3229
  } catch {
5196
3230
  return false;
5197
3231
  }
@@ -5207,15 +3241,15 @@ function validateExtractedTree(destDir) {
5207
3241
  }
5208
3242
  function resolveTarBin() {
5209
3243
  if (process.platform !== "win32") return "tar";
5210
- const winTar = path28.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
5211
- return fs21.existsSync(winTar) ? winTar : "tar";
3244
+ const winTar = path18.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
3245
+ return fs14.existsSync(winTar) ? winTar : "tar";
5212
3246
  }
5213
3247
  async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
5214
3248
  const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
5215
- const tmp = path28.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
3249
+ const tmp = path18.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
5216
3250
  const fail = (reason) => {
5217
3251
  try {
5218
- fs21.rmSync(destDir, { recursive: true, force: true });
3252
+ fs14.rmSync(destDir, { recursive: true, force: true });
5219
3253
  } catch {
5220
3254
  }
5221
3255
  return { ok: false, reason };
@@ -5234,42 +3268,42 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
5234
3268
  if (!res.body) return fail("respuesta HTTP sin cuerpo");
5235
3269
  await pipeline(
5236
3270
  Readable.fromWeb(res.body),
5237
- fs21.createWriteStream(tmp)
3271
+ fs14.createWriteStream(tmp)
5238
3272
  );
5239
- fs21.rmSync(destDir, { recursive: true, force: true });
5240
- fs21.mkdirSync(destDir, { recursive: true });
3273
+ fs14.rmSync(destDir, { recursive: true, force: true });
3274
+ fs14.mkdirSync(destDir, { recursive: true });
5241
3275
  try {
5242
- execFileSync3(resolveTarBin(), ["-xzf", tmp, "--strip-components=1", "-C", destDir], { stdio: "pipe" });
3276
+ execFileSync2(resolveTarBin(), ["-xzf", tmp, "--strip-components=1", "-C", destDir], { stdio: "pipe" });
5243
3277
  } catch (err) {
5244
3278
  const e = err;
5245
3279
  const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
5246
3280
  return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
5247
3281
  }
5248
- const resolvedDest = path28.resolve(destDir);
5249
- const validateRoot = validateSubdir ? path28.resolve(resolvedDest, validateSubdir) : resolvedDest;
3282
+ const resolvedDest = path18.resolve(destDir);
3283
+ const validateRoot = validateSubdir ? path18.resolve(resolvedDest, validateSubdir) : resolvedDest;
5250
3284
  if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
5251
3285
  return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
5252
3286
  }
5253
- if (fs21.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
3287
+ if (fs14.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
5254
3288
  return fail("el \xE1rbol extra\xEDdo contiene symlinks o rutas fuera del destino");
5255
3289
  }
5256
- const validated = fs21.existsSync(validateRoot);
3290
+ const validated = fs14.existsSync(validateRoot);
5257
3291
  return { ok: true, validated };
5258
3292
  } catch (err) {
5259
3293
  const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
5260
3294
  return fail(timedOut ? "timeout de descarga (120s)" : err instanceof Error ? `fallo de red: ${err.message}` : "error desconocido");
5261
3295
  } finally {
5262
3296
  try {
5263
- fs21.rmSync(tmp, { force: true });
3297
+ fs14.rmSync(tmp, { force: true });
5264
3298
  } catch {
5265
3299
  }
5266
3300
  }
5267
3301
  }
5268
3302
 
5269
3303
  // src/lib/skill-update.ts
5270
- import fs22 from "fs";
5271
- import path29 from "path";
5272
- import { execFileSync as execFileSync4 } from "child_process";
3304
+ import fs15 from "fs";
3305
+ import path19 from "path";
3306
+ import { execFileSync as execFileSync3 } from "child_process";
5273
3307
  var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
5274
3308
  "agent-delegation",
5275
3309
  "lean-code",
@@ -5279,8 +3313,8 @@ var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
5279
3313
  "xreview"
5280
3314
  ]);
5281
3315
  function sameTextContentNormalized(a, b) {
5282
- const ba = fs22.readFileSync(a);
5283
- const bb = fs22.readFileSync(b);
3316
+ const ba = fs15.readFileSync(a);
3317
+ const bb = fs15.readFileSync(b);
5284
3318
  if (ba.equals(bb)) return true;
5285
3319
  const sa = ba.toString("utf8").replace(/\r\n/g, "\n");
5286
3320
  const sb = bb.toString("utf8").replace(/\r\n/g, "\n");
@@ -5288,10 +3322,10 @@ function sameTextContentNormalized(a, b) {
5288
3322
  }
5289
3323
  function diffSkillDirs(upstreamDir, localDir) {
5290
3324
  const upstreamFiles = new Set(
5291
- listFilesRecursive(upstreamDir).map((f) => path29.relative(upstreamDir, f))
3325
+ listFilesRecursive(upstreamDir).map((f) => path19.relative(upstreamDir, f))
5292
3326
  );
5293
3327
  const localFiles = new Set(
5294
- listFilesRecursive(localDir).map((f) => path29.relative(localDir, f))
3328
+ listFilesRecursive(localDir).map((f) => path19.relative(localDir, f))
5295
3329
  );
5296
3330
  const added = [];
5297
3331
  const modified = [];
@@ -5299,7 +3333,7 @@ function diffSkillDirs(upstreamDir, localDir) {
5299
3333
  for (const rel of upstreamFiles) {
5300
3334
  if (!localFiles.has(rel)) {
5301
3335
  added.push(rel);
5302
- } else if (!sameTextContentNormalized(path29.join(upstreamDir, rel), path29.join(localDir, rel))) {
3336
+ } else if (!sameTextContentNormalized(path19.join(upstreamDir, rel), path19.join(localDir, rel))) {
5303
3337
  modified.push(rel);
5304
3338
  }
5305
3339
  }
@@ -5317,7 +3351,7 @@ function diffSkillDirs(upstreamDir, localDir) {
5317
3351
  var DIFF_MAX_LINES = 400;
5318
3352
  function renderSkillDiff(upstreamDir, localDir) {
5319
3353
  try {
5320
- execFileSync4("git", ["diff", "--no-index", "--stat", "--", localDir, upstreamDir], {
3354
+ execFileSync3("git", ["diff", "--no-index", "--stat", "--", localDir, upstreamDir], {
5321
3355
  stdio: "pipe",
5322
3356
  encoding: "utf8"
5323
3357
  });
@@ -5334,7 +3368,7 @@ function renderSkillDiff(upstreamDir, localDir) {
5334
3368
  const stat = se.stdout;
5335
3369
  let fullDiff = "";
5336
3370
  try {
5337
- execFileSync4("git", ["diff", "--no-index", "--", localDir, upstreamDir], {
3371
+ execFileSync3("git", ["diff", "--no-index", "--", localDir, upstreamDir], {
5338
3372
  stdio: "pipe",
5339
3373
  encoding: "utf8"
5340
3374
  });
@@ -5357,12 +3391,12 @@ ${fullDiff}` : stat;
5357
3391
  function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
5358
3392
  const upstreamsFilePath = opts?.upstreamsFilePath;
5359
3393
  const localSkillsRoot = opts?.localSkillsRoot;
5360
- const backupsRoot2 = opts?.backupsRoot;
3394
+ const backupsRoot = opts?.backupsRoot;
5361
3395
  if (PROTECTED_SKILLS.has(name)) {
5362
3396
  throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
5363
3397
  }
5364
- const upstreamsFile = upstreamsFilePath ?? path29.join(path29.dirname(stackRoot()), "upstreams.json");
5365
- const raw = fs22.readFileSync(upstreamsFile, "utf8");
3398
+ const upstreamsFile = upstreamsFilePath ?? path19.join(path19.dirname(stackRoot()), "upstreams.json");
3399
+ const raw = fs15.readFileSync(upstreamsFile, "utf8");
5366
3400
  const data = JSON.parse(raw);
5367
3401
  const skillEntry = data?.skills?.[name];
5368
3402
  if (!skillEntry) {
@@ -5371,34 +3405,34 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
5371
3405
  if (skillEntry.kind === "release") {
5372
3406
  throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
5373
3407
  }
5374
- const skillsRoot = localSkillsRoot ?? path29.join(stackRoot(), "skills");
5375
- const localSkillDir = path29.join(skillsRoot, name);
3408
+ const skillsRoot = localSkillsRoot ?? path19.join(stackRoot(), "skills");
3409
+ const localSkillDir = path19.join(skillsRoot, name);
5376
3410
  const localFiles = listFilesRecursive(localSkillDir);
5377
3411
  if (localFiles.length > 0) {
5378
- createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
3412
+ createBackup(localFiles, `skill-update-${name}`, backupsRoot);
5379
3413
  }
5380
3414
  const stagingDir = `${localSkillDir}.staging-${process.pid}`;
5381
3415
  try {
5382
3416
  const upstreamFiles = listFilesRecursive(upstreamSkillDir);
5383
3417
  for (const src of upstreamFiles) {
5384
- const st = fs22.lstatSync(src);
3418
+ const st = fs15.lstatSync(src);
5385
3419
  if (st.isSymbolicLink()) {
5386
3420
  throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
5387
3421
  }
5388
- const rel = path29.relative(upstreamSkillDir, src);
5389
- const dest = path29.join(stagingDir, rel);
5390
- ensureDir(path29.dirname(dest));
3422
+ const rel = path19.relative(upstreamSkillDir, src);
3423
+ const dest = path19.join(stagingDir, rel);
3424
+ ensureDir(path19.dirname(dest));
5391
3425
  copyFile(src, dest);
5392
3426
  }
5393
3427
  const oldDir = `${localSkillDir}.old-${process.pid}`;
5394
- if (fs22.existsSync(localSkillDir)) {
5395
- fs22.renameSync(localSkillDir, oldDir);
3428
+ if (fs15.existsSync(localSkillDir)) {
3429
+ fs15.renameSync(localSkillDir, oldDir);
5396
3430
  }
5397
- fs22.renameSync(stagingDir, localSkillDir);
5398
- fs22.rmSync(oldDir, { recursive: true, force: true });
3431
+ fs15.renameSync(stagingDir, localSkillDir);
3432
+ fs15.rmSync(oldDir, { recursive: true, force: true });
5399
3433
  } catch (err) {
5400
3434
  try {
5401
- fs22.rmSync(stagingDir, { recursive: true, force: true });
3435
+ fs15.rmSync(stagingDir, { recursive: true, force: true });
5402
3436
  } catch {
5403
3437
  }
5404
3438
  throw err;
@@ -5412,8 +3446,8 @@ function rateLimitHint(prefix) {
5412
3446
  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.`;
5413
3447
  }
5414
3448
  function loadUpstreams() {
5415
- const file = path30.join(path30.dirname(stackRoot()), "upstreams.json");
5416
- return JSON.parse(fs23.readFileSync(file, "utf8"));
3449
+ const file = path20.join(path20.dirname(stackRoot()), "upstreams.json");
3450
+ return JSON.parse(fs16.readFileSync(file, "utf8"));
5417
3451
  }
5418
3452
  function skillsToScan(maintainer, upstreams) {
5419
3453
  return maintainer ? Object.keys(upstreams.skills) : [];
@@ -5442,11 +3476,39 @@ function complementsToScan(upstreams) {
5442
3476
  }
5443
3477
  function resolveComplementUpdateCheck(name, info, upstream) {
5444
3478
  const pinned = info.version ?? null;
5445
- if (!pinned)
3479
+ const strategy = info.strategy;
3480
+ if (strategy === "provider-managed") {
3481
+ if (!pinned) {
3482
+ if (upstream === null)
3483
+ return {
3484
+ level: "info",
3485
+ message: `${name}: provider-managed sin versi\xF3n fijada (rolling aceptado; no se pudo consultar el upstream).`
3486
+ };
3487
+ return {
3488
+ level: "info",
3489
+ message: `${name}: provider-managed ${upstream} (rolling aceptado; el provider gestiona la versi\xF3n).`
3490
+ };
3491
+ }
3492
+ if (upstream === null)
3493
+ return { level: "info", message: `${name}: provider-managed ${pinned} (no se pudo consultar el upstream).` };
3494
+ if (upstream === pinned)
3495
+ return { level: "success", message: `${name}: al d\xEDa con ${pinned} (provider-managed).` };
3496
+ return {
3497
+ level: "info",
3498
+ message: `${name}: provider-managed (referencia ${pinned}, upstream observado ${upstream}; el provider gestiona la versi\xF3n, sin re-pin requerido).`
3499
+ };
3500
+ }
3501
+ if (!pinned) {
3502
+ if (strategy === "exact")
3503
+ return {
3504
+ level: "error",
3505
+ message: `${name}: estrategia exact sin pin en upstreams.json \u2014 fija la versi\xF3n revisada antes de instalar.`
3506
+ };
5446
3507
  return {
5447
3508
  level: "warn",
5448
3509
  message: `${name}: sin pin en upstreams.json \u2014 fija la versi\xF3n revisada antes de instalar.`
5449
3510
  };
3511
+ }
5450
3512
  if (upstream === null)
5451
3513
  return { level: "info", message: `${name}: pin ${pinned} (no se pudo consultar el upstream).` };
5452
3514
  if (upstream === pinned)
@@ -5580,7 +3642,7 @@ async function runUpdateCheck(localVersion, includeBrowserState = true) {
5580
3642
  for (const { name, upstream } of complementQueries) {
5581
3643
  const report = resolveComplementUpdateCheck(name, upstreams.complements[name], upstream);
5582
3644
  p4.log[report.level](report.message);
5583
- if (report.level === "warn") movedComplement = true;
3645
+ if (report.level === "warn" || report.level === "error") movedComplement = true;
5584
3646
  if (report.level === "info") unknownComplement = true;
5585
3647
  }
5586
3648
  if (!movedComplement && !unknownComplement) {
@@ -5606,7 +3668,7 @@ function isEngramRunning() {
5606
3668
  const pgrep = lookPath("pgrep");
5607
3669
  if (!pgrep) return null;
5608
3670
  try {
5609
- execFileSync5(pgrep, ["-x", "engram"], { stdio: "ignore" });
3671
+ execFileSync4(pgrep, ["-x", "engram"], { stdio: "ignore" });
5610
3672
  return true;
5611
3673
  } catch {
5612
3674
  return false;
@@ -5616,8 +3678,8 @@ function isEngramRunning() {
5616
3678
  return null;
5617
3679
  }
5618
3680
  }
5619
- function isGitClone(projectRoot = path30.dirname(stackRoot())) {
5620
- return fs23.existsSync(path30.join(projectRoot, ".git"));
3681
+ function isGitClone(projectRoot = path20.dirname(stackRoot())) {
3682
+ return fs16.existsSync(path20.join(projectRoot, ".git"));
5621
3683
  }
5622
3684
  var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
5623
3685
  function resolvePnpm() {
@@ -5627,29 +3689,29 @@ function resolvePnpm() {
5627
3689
  }
5628
3690
  function cleanupTmp(dir) {
5629
3691
  try {
5630
- fs23.rmSync(dir, { recursive: true, force: true });
3692
+ fs16.rmSync(dir, { recursive: true, force: true });
5631
3693
  } catch {
5632
3694
  }
5633
3695
  }
5634
3696
  function updateStackGitClone() {
5635
- const projectRoot = path30.dirname(stackRoot());
3697
+ const projectRoot = path20.dirname(stackRoot());
5636
3698
  const git = lookPath("git");
5637
3699
  if (!git) throw new Error("git no encontrado en PATH.");
5638
3700
  const pnpm = resolvePnpm();
5639
3701
  p4.log.info("Ejecutando git pull\u2026");
5640
- execFileSync5(git, ["pull"], { cwd: projectRoot, stdio: "inherit" });
3702
+ execFileSync4(git, ["pull"], { cwd: projectRoot, stdio: "inherit" });
5641
3703
  p4.log.info("Ejecutando pnpm install\u2026");
5642
- execFileSync5(pnpm, ["install"], { cwd: projectRoot, stdio: "inherit" });
3704
+ execFileSync4(pnpm, ["install"], { cwd: projectRoot, stdio: "inherit" });
5643
3705
  p4.log.info("Ejecutando pnpm build\u2026");
5644
- execFileSync5(pnpm, ["build"], { cwd: projectRoot, stdio: "inherit" });
3706
+ execFileSync4(pnpm, ["build"], { cwd: projectRoot, stdio: "inherit" });
5645
3707
  }
5646
3708
  function updateStackGlobal() {
5647
3709
  const pnpm = resolvePnpm();
5648
3710
  p4.log.info("Ejecutando pnpm add -g jorgex-stack@latest\u2026");
5649
- execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
3711
+ execFileSync4(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
5650
3712
  }
5651
3713
  async function downloadSkillToTemp(repo, sha, skillPath) {
5652
- const root = fs23.mkdtempSync(path30.join(os3.tmpdir(), "jorgex-skill-"));
3714
+ const root = fs16.mkdtempSync(path20.join(os3.tmpdir(), "jorgex-skill-"));
5653
3715
  try {
5654
3716
  const result = await downloadRepoTarball(repo, sha, root, skillPath);
5655
3717
  if (!result.ok) {
@@ -5657,15 +3719,15 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
5657
3719
  return { error: result.reason };
5658
3720
  }
5659
3721
  if (skillPath) {
5660
- const sub = path30.resolve(path30.join(root, skillPath));
3722
+ const sub = path20.resolve(path20.join(root, skillPath));
5661
3723
  if (!isContainedIn(sub, root)) {
5662
3724
  cleanupTmp(root);
5663
3725
  return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
5664
3726
  }
5665
- if (fs23.existsSync(sub)) return { dir: sub, root };
3727
+ if (fs16.existsSync(sub)) return { dir: sub, root };
5666
3728
  const lastSeg = skillPath.split("/").pop();
5667
- const sub2 = path30.resolve(path30.join(root, lastSeg));
5668
- if (isContainedIn(sub2, root) && fs23.existsSync(sub2)) {
3729
+ const sub2 = path20.resolve(path20.join(root, lastSeg));
3730
+ if (isContainedIn(sub2, root) && fs16.existsSync(sub2)) {
5669
3731
  if (!validateExtractedTree(sub2)) {
5670
3732
  cleanupTmp(root);
5671
3733
  return { error: `el sub\xE1rbol "${lastSeg}" contiene symlinks o rutas fuera del destino` };
@@ -5684,11 +3746,11 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
5684
3746
  function pruneEngramDbBackups() {
5685
3747
  try {
5686
3748
  const dir = dataDir();
5687
- if (!fs23.existsSync(dir)) return;
5688
- const backups = fs23.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs23.statSync(path30.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
3749
+ if (!fs16.existsSync(dir)) return;
3750
+ const backups = fs16.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs16.statSync(path20.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
5689
3751
  for (const old of backups.slice(3)) {
5690
3752
  try {
5691
- fs23.rmSync(path30.join(dir, old.name));
3753
+ fs16.rmSync(path20.join(dir, old.name));
5692
3754
  } catch {
5693
3755
  }
5694
3756
  }
@@ -5696,18 +3758,18 @@ function pruneEngramDbBackups() {
5696
3758
  }
5697
3759
  }
5698
3760
  function rotateLockedBinary(binPath, sweepRoot = HOME) {
5699
- if (!fs23.existsSync(binPath)) return null;
5700
- const dir = path30.dirname(binPath);
5701
- const base = path30.basename(binPath);
3761
+ if (!fs16.existsSync(binPath)) return null;
3762
+ const dir = path20.dirname(binPath);
3763
+ const base = path20.basename(binPath);
5702
3764
  const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
5703
3765
  const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
5704
- const resolvedDir = path30.resolve(dir);
5705
- if (resolvedDir === path30.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
3766
+ const resolvedDir = path20.resolve(dir);
3767
+ if (resolvedDir === path20.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
5706
3768
  try {
5707
- for (const entry of fs23.readdirSync(dir)) {
3769
+ for (const entry of fs16.readdirSync(dir)) {
5708
3770
  if (oldPattern.test(entry)) {
5709
3771
  try {
5710
- fs23.rmSync(path30.join(dir, entry), { force: true });
3772
+ fs16.rmSync(path20.join(dir, entry), { force: true });
5711
3773
  } catch {
5712
3774
  }
5713
3775
  }
@@ -5715,8 +3777,8 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
5715
3777
  } catch {
5716
3778
  }
5717
3779
  }
5718
- const rotated = path30.join(dir, `${base}.old-${Date.now()}`);
5719
- fs23.renameSync(binPath, rotated);
3780
+ const rotated = path20.join(dir, `${base}.old-${Date.now()}`);
3781
+ fs16.renameSync(binPath, rotated);
5720
3782
  return rotated;
5721
3783
  }
5722
3784
  async function updateEngram(engramRepo, latestVersion) {
@@ -5725,19 +3787,19 @@ async function updateEngram(engramRepo, latestVersion) {
5725
3787
  "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)."
5726
3788
  );
5727
3789
  }
5728
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path30.join(HOME, ".engram");
5729
- const engramDb = path30.join(engramDataDir, "engram.db");
5730
- if (fs23.existsSync(engramDb)) {
3790
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path20.join(HOME, ".engram");
3791
+ const engramDb = path20.join(engramDataDir, "engram.db");
3792
+ if (fs16.existsSync(engramDb)) {
5731
3793
  const doBackup = await p4.confirm({
5732
3794
  message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
5733
3795
  initialValue: true
5734
3796
  });
5735
3797
  if (!p4.isCancel(doBackup) && doBackup) {
5736
3798
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5737
- const dest = path30.join(dataDir(), `engram-db-backup-${ts}.db`);
3799
+ const dest = path20.join(dataDir(), `engram-db-backup-${ts}.db`);
5738
3800
  try {
5739
- if (!fs23.existsSync(dataDir())) fs23.mkdirSync(dataDir(), { recursive: true });
5740
- fs23.copyFileSync(engramDb, dest);
3801
+ if (!fs16.existsSync(dataDir())) fs16.mkdirSync(dataDir(), { recursive: true });
3802
+ fs16.copyFileSync(engramDb, dest);
5741
3803
  p4.log.success(`DB respaldada en ${dest} (la DB original NO se modifica jam\xE1s).`);
5742
3804
  pruneEngramDbBackups();
5743
3805
  } catch (err) {
@@ -5750,7 +3812,7 @@ async function updateEngram(engramRepo, latestVersion) {
5750
3812
  if (brew) {
5751
3813
  let brewManages = false;
5752
3814
  try {
5753
- execFileSync5(brew, ["list", "engram"], { stdio: "pipe" });
3815
+ execFileSync4(brew, ["list", "engram"], { stdio: "pipe" });
5754
3816
  brewManages = true;
5755
3817
  } catch {
5756
3818
  }
@@ -5758,7 +3820,7 @@ async function updateEngram(engramRepo, latestVersion) {
5758
3820
  anyChannelTried = true;
5759
3821
  p4.log.info("Actualizando engram con brew\u2026");
5760
3822
  try {
5761
- execFileSync5(brew, ["upgrade", "engram"], { stdio: "inherit" });
3823
+ execFileSync4(brew, ["upgrade", "engram"], { stdio: "inherit" });
5762
3824
  return true;
5763
3825
  } catch (err) {
5764
3826
  p4.log.error(`brew upgrade engram fall\xF3: ${err instanceof Error ? err.message : err}`);
@@ -5782,15 +3844,15 @@ async function updateEngram(engramRepo, latestVersion) {
5782
3844
  }
5783
3845
  p4.log.info(`Actualizando engram con go install (${latestVersion})\u2026`);
5784
3846
  try {
5785
- execFileSync5(
3847
+ execFileSync4(
5786
3848
  go,
5787
3849
  ["install", `github.com/Gentleman-Programming/engram/cmd/engram@v${latestVersion}`],
5788
3850
  { stdio: "inherit" }
5789
3851
  );
5790
- const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs23.existsSync(bin ?? "") });
3852
+ const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs16.existsSync(bin ?? "") });
5791
3853
  if (rollbackOk.action === "restore") {
5792
3854
  try {
5793
- fs23.renameSync(rotated, bin);
3855
+ fs16.renameSync(rotated, bin);
5794
3856
  p4.log.warn(rollbackOk.messages.onRestore);
5795
3857
  } catch {
5796
3858
  p4.log.warn(rollbackOk.messages.onRenameFail);
@@ -5798,10 +3860,10 @@ async function updateEngram(engramRepo, latestVersion) {
5798
3860
  }
5799
3861
  return true;
5800
3862
  } catch (err) {
5801
- const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs23.existsSync(bin ?? "") });
3863
+ const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs16.existsSync(bin ?? "") });
5802
3864
  if (rollbackFail.action === "restore") {
5803
3865
  try {
5804
- fs23.renameSync(rotated, bin);
3866
+ fs16.renameSync(rotated, bin);
5805
3867
  p4.log.info(rollbackFail.messages.onRestore);
5806
3868
  } catch {
5807
3869
  p4.log.error(rollbackFail.messages.onRenameFail);
@@ -6072,7 +4134,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
6072
4134
  continue;
6073
4135
  }
6074
4136
  const { dir: tmpDir, root: tmpRoot } = tmpResult;
6075
- const localSkillDir = path30.join(stackRoot(), "skills", skillInfo.name);
4137
+ const localSkillDir = path20.join(stackRoot(), "skills", skillInfo.name);
6076
4138
  const diff = renderSkillDiff(tmpDir, localSkillDir);
6077
4139
  if (diff) {
6078
4140
  p4.log.info(`Diff de ${skillInfo.name}:
@@ -6164,7 +4226,7 @@ ${diff}`);
6164
4226
  }
6165
4227
 
6166
4228
  // src/models-picker.ts
6167
- import path31 from "path";
4229
+ import path21 from "path";
6168
4230
  import * as p5 from "@clack/prompts";
6169
4231
  var TIERS = ["strong", "standard", "cheap"];
6170
4232
  var EFFORTS = ["low", "medium", "high", "xhigh"];
@@ -6190,7 +4252,7 @@ function opencodeLiveModels(binPath) {
6190
4252
  }
6191
4253
  function agentsByTier() {
6192
4254
  const grouped = { strong: [], standard: [], cheap: [] };
6193
- for (const agent of loadCanonicalAgents(path31.join(stackRoot(), "agents"))) {
4255
+ for (const agent of loadCanonicalAgents(path21.join(stackRoot(), "agents"))) {
6194
4256
  if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
6195
4257
  }
6196
4258
  return grouped;
@@ -6375,16 +4437,16 @@ function cancelled() {
6375
4437
  }
6376
4438
 
6377
4439
  // src/lib/release.ts
6378
- import fs24 from "fs";
6379
- import path32 from "path";
6380
- import { execFileSync as execFileSync6 } from "child_process";
4440
+ import fs17 from "fs";
4441
+ import path22 from "path";
4442
+ import { execFileSync as execFileSync5 } from "child_process";
6381
4443
  import { fileURLToPath } from "url";
6382
4444
  function findPackageJson() {
6383
- let dir = path32.dirname(fileURLToPath(import.meta.url));
4445
+ let dir = path22.dirname(fileURLToPath(import.meta.url));
6384
4446
  for (let i = 0; i < 6; i++) {
6385
- const candidate = path32.join(dir, "package.json");
6386
- if (fs24.existsSync(candidate)) return candidate;
6387
- dir = path32.dirname(dir);
4447
+ const candidate = path22.join(dir, "package.json");
4448
+ if (fs17.existsSync(candidate)) return candidate;
4449
+ dir = path22.dirname(dir);
6388
4450
  }
6389
4451
  throw new Error("No se encontr\xF3 package.json cerca del CLI.");
6390
4452
  }
@@ -6393,7 +4455,7 @@ function readPackageVersion() {
6393
4455
  }
6394
4456
  function readPackageMetadata() {
6395
4457
  const packageJson = findPackageJson();
6396
- const raw = fs24.readFileSync(packageJson, "utf8");
4458
+ const raw = fs17.readFileSync(packageJson, "utf8");
6397
4459
  const parsed = JSON.parse(raw);
6398
4460
  const name = typeof parsed.name === "string" ? parsed.name.trim() : "";
6399
4461
  const version = typeof parsed.version === "string" ? parsed.version.trim() : "";
@@ -6405,8 +4467,8 @@ function readPackageMetadata() {
6405
4467
 
6406
4468
  // src/lib/pi-runtime.ts
6407
4469
  import os4 from "os";
6408
- import path33 from "path";
6409
- import fs25 from "fs";
4470
+ import path23 from "path";
4471
+ import fs18 from "fs";
6410
4472
  import { spawnSync } from "child_process";
6411
4473
  import { createHash as createHash2 } from "crypto";
6412
4474
 
@@ -6545,7 +4607,7 @@ async function resolvePiEngramRequirement(input, deps) {
6545
4607
  initialValue: false
6546
4608
  });
6547
4609
  if (!accepted) return { kind: "offer", accepted: false };
6548
- const installed = await deps.installNative({ version: "1.20.0", channels: ["brew", "go", "url"] });
4610
+ const installed = await deps.installNative({ version: "2.0.0", channels: ["brew", "go", "url"] });
6549
4611
  if (!installed) {
6550
4612
  return {
6551
4613
  kind: "blocked",
@@ -6603,7 +4665,7 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
6603
4665
  if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
6604
4666
  const packageValue = Reflect.get(record, "package");
6605
4667
  const result = Reflect.get(record, "result");
6606
- 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]) && path33.resolve(packageRunner) === path33.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
4668
+ 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]) && path23.resolve(packageRunner) === path23.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
6607
4669
  } catch {
6608
4670
  return false;
6609
4671
  }
@@ -6624,7 +4686,7 @@ function provisionalPendingDoctor(exitCode, stdout, stderr, packageRunner, candi
6624
4686
  if (JSON.stringify(Object.keys(packageValue).sort()) !== JSON.stringify(["name", "root", "version"])) return false;
6625
4687
  if (Reflect.get(packageValue, "name") !== "jorgex-pi") return false;
6626
4688
  if (Reflect.get(packageValue, "version") !== (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1])) return false;
6627
- if (path33.resolve(packageRunner) !== path33.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs")) return false;
4689
+ if (path23.resolve(packageRunner) !== path23.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs")) return false;
6628
4690
  const result = Reflect.get(record, "result");
6629
4691
  if (result === null || typeof result !== "object" || Array.isArray(result)) return false;
6630
4692
  if (JSON.stringify(Object.keys(result).sort()) !== JSON.stringify(["checks", "healthy"])) return false;
@@ -6653,7 +4715,7 @@ function provisionalPendingDoctor(exitCode, stdout, stderr, packageRunner, candi
6653
4715
  }
6654
4716
  function installPiFromVerifiedTarball(input, deps) {
6655
4717
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
6656
- const destination = input.targetDir === void 0 ? path33.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path33.join(path33.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
4718
+ const destination = input.targetDir === void 0 ? path23.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path23.join(path23.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
6657
4719
  const artifact = deps.download(destination);
6658
4720
  if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
6659
4721
  return { kind: "blocked", reason: "tarball-integrity" };
@@ -6661,7 +4723,7 @@ function installPiFromVerifiedTarball(input, deps) {
6661
4723
  deps.backupSettings();
6662
4724
  const scope = {
6663
4725
  kind: input.targetDir === void 0 ? "real" : "target-dir",
6664
- codingAgentDir: path33.resolve(paths.codingAgentDir)
4726
+ codingAgentDir: path23.resolve(paths.codingAgentDir)
6665
4727
  };
6666
4728
  const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
6667
4729
  deps.writeReceiptAtomic(`${JSON.stringify(installing)}
@@ -6692,30 +4754,30 @@ function installPiFromVerifiedTarball(input, deps) {
6692
4754
  return { kind: "installed", receipt };
6693
4755
  }
6694
4756
  function runtimePath(piExecutable) {
6695
- const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path33.dirname(piExecutable), path33.dirname(process.execPath), process.env.SystemRoot ? path33.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path33.dirname(piExecutable), path33.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
6696
- return [...new Set(entries.filter((entry) => entry !== null))].join(path33.delimiter);
4757
+ const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path23.dirname(piExecutable), path23.dirname(process.execPath), process.env.SystemRoot ? path23.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path23.dirname(piExecutable), path23.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
4758
+ return [...new Set(entries.filter((entry) => entry !== null))].join(path23.delimiter);
6697
4759
  }
6698
4760
  function targetPaths(targetDir, engramBin, piExecutable) {
6699
- const root = path33.resolve(targetDir);
6700
- const codingAgentDir = path33.join(root, "pi-agent");
6701
- const home = path33.join(root, "home");
6702
- const temporary = path33.join(root, "tmp");
4761
+ const root = path23.resolve(targetDir);
4762
+ const codingAgentDir = path23.join(root, "pi-agent");
4763
+ const home = path23.join(root, "home");
4764
+ const temporary = path23.join(root, "tmp");
6703
4765
  return {
6704
4766
  codingAgentDir,
6705
- receiptPath: path33.join(root, "state", "pi-receipt.json"),
6706
- packageRunner: path33.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
4767
+ receiptPath: path23.join(root, "state", "pi-receipt.json"),
4768
+ packageRunner: path23.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
6707
4769
  environment: {
6708
4770
  HOME: home,
6709
4771
  USERPROFILE: home,
6710
- APPDATA: path33.join(root, "appdata"),
6711
- LOCALAPPDATA: path33.join(root, "localappdata"),
6712
- XDG_CONFIG_HOME: path33.join(root, "xdg-config"),
6713
- XDG_DATA_HOME: path33.join(root, "xdg-data"),
6714
- XDG_CACHE_HOME: path33.join(root, "xdg-cache"),
4772
+ APPDATA: path23.join(root, "appdata"),
4773
+ LOCALAPPDATA: path23.join(root, "localappdata"),
4774
+ XDG_CONFIG_HOME: path23.join(root, "xdg-config"),
4775
+ XDG_DATA_HOME: path23.join(root, "xdg-data"),
4776
+ XDG_CACHE_HOME: path23.join(root, "xdg-cache"),
6715
4777
  TEMP: temporary,
6716
4778
  TMP: temporary,
6717
4779
  TMPDIR: temporary,
6718
- npm_config_cache: path33.join(root, "npm-cache"),
4780
+ npm_config_cache: path23.join(root, "npm-cache"),
6719
4781
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
6720
4782
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
6721
4783
  PI_CODING_AGENT_DIR: codingAgentDir,
@@ -6726,15 +4788,15 @@ function targetPaths(targetDir, engramBin, piExecutable) {
6726
4788
  }
6727
4789
  function userPaths(engramBin, piExecutable) {
6728
4790
  const home = os4.homedir();
6729
- const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path33.join(home, ".pi", "agent");
4791
+ const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path23.join(home, ".pi", "agent");
6730
4792
  return {
6731
4793
  codingAgentDir,
6732
- receiptPath: path33.join(dataDir(), "pi-receipt.json"),
6733
- packageRunner: path33.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
4794
+ receiptPath: path23.join(dataDir(), "pi-receipt.json"),
4795
+ packageRunner: path23.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
6734
4796
  environment: {
6735
4797
  HOME: home,
6736
- XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path33.join(home, ".config"),
6737
- XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path33.join(home, ".cache"),
4798
+ XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path23.join(home, ".config"),
4799
+ XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path23.join(home, ".cache"),
6738
4800
  TMPDIR: os4.tmpdir(),
6739
4801
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
6740
4802
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
@@ -6762,7 +4824,7 @@ function runPiRuntime(input, deps) {
6762
4824
  return { kind: "blocked", reason: "tarball-integrity" };
6763
4825
  }
6764
4826
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
6765
- const settingsJson = deps.readSettings(path33.join(paths.codingAgentDir, "settings.json"));
4827
+ const settingsJson = deps.readSettings(path23.join(paths.codingAgentDir, "settings.json"));
6766
4828
  const receiptJson = deps.readReceipt(paths.receiptPath);
6767
4829
  const lifecycleInput = {
6768
4830
  candidate: PI_RUNTIME_CANDIDATE,
@@ -6821,19 +4883,19 @@ function runPiRuntime(input, deps) {
6821
4883
  return result;
6822
4884
  }
6823
4885
  function readJsonFile(file) {
6824
- return JSON.parse(fs25.readFileSync(file, "utf8"));
4886
+ return JSON.parse(fs18.readFileSync(file, "utf8"));
6825
4887
  }
6826
4888
  function packageVersionFromExecutable(executable) {
6827
4889
  let current;
6828
4890
  try {
6829
- current = path33.dirname(fs25.realpathSync(executable));
4891
+ current = path23.dirname(fs18.realpathSync(executable));
6830
4892
  } catch {
6831
4893
  return null;
6832
4894
  }
6833
4895
  for (let depth = 0; depth < 8; depth++) {
6834
4896
  const manifests = [
6835
- path33.join(current, "package.json"),
6836
- path33.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
4897
+ path23.join(current, "package.json"),
4898
+ path23.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
6837
4899
  ];
6838
4900
  for (const manifest of manifests) {
6839
4901
  try {
@@ -6844,7 +4906,7 @@ function packageVersionFromExecutable(executable) {
6844
4906
  } catch {
6845
4907
  }
6846
4908
  }
6847
- const parent = path33.dirname(current);
4909
+ const parent = path23.dirname(current);
6848
4910
  if (parent === current) break;
6849
4911
  current = parent;
6850
4912
  }
@@ -6859,35 +4921,35 @@ function detectPiRuntime() {
6859
4921
  installed: executable !== null,
6860
4922
  executable,
6861
4923
  version: executable === null ? null : packageVersionFromExecutable(executable),
6862
- codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path33.join(home, ".pi", "agent")
4924
+ codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path23.join(home, ".pi", "agent")
6863
4925
  };
6864
4926
  }
6865
4927
  function hasManagedPiRuntime(targetDir) {
6866
- const receipt = targetDir === void 0 ? path33.join(dataDir(), "pi-receipt.json") : path33.join(path33.resolve(targetDir), "state", "pi-receipt.json");
6867
- return fs25.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
4928
+ const receipt = targetDir === void 0 ? path23.join(dataDir(), "pi-receipt.json") : path23.join(path23.resolve(targetDir), "state", "pi-receipt.json");
4929
+ return fs18.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
6868
4930
  }
6869
4931
  function resolvePiEngramBin(targetDir) {
6870
4932
  if (targetDir === void 0) return detectEngram();
6871
- const candidate = path33.join(path33.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
6872
- return fs25.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
4933
+ const candidate = path23.join(path23.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
4934
+ return fs18.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
6873
4935
  }
6874
4936
  function readOptional(file, fallback) {
6875
4937
  try {
6876
- return fs25.readFileSync(file, "utf8");
4938
+ return fs18.readFileSync(file, "utf8");
6877
4939
  } catch (error) {
6878
4940
  if (error.code === "ENOENT") return fallback;
6879
4941
  throw error;
6880
4942
  }
6881
4943
  }
6882
4944
  function hashPiTarball(file) {
6883
- const descriptor = fs25.openSync(file, "r");
4945
+ const descriptor = fs18.openSync(file, "r");
6884
4946
  const sha2562 = createHash2("sha256");
6885
4947
  const sha512 = createHash2("sha512");
6886
4948
  const buffer = Buffer.allocUnsafe(1024 * 1024);
6887
4949
  let bytes = 0;
6888
4950
  try {
6889
4951
  while (true) {
6890
- const read = fs25.readSync(descriptor, buffer, 0, buffer.length, null);
4952
+ const read = fs18.readSync(descriptor, buffer, 0, buffer.length, null);
6891
4953
  if (read === 0) break;
6892
4954
  bytes += read;
6893
4955
  const chunk = buffer.subarray(0, read);
@@ -6895,27 +4957,27 @@ function hashPiTarball(file) {
6895
4957
  sha512.update(chunk);
6896
4958
  }
6897
4959
  } finally {
6898
- fs25.closeSync(descriptor);
4960
+ fs18.closeSync(descriptor);
6899
4961
  }
6900
4962
  return { path: file, bytes, sha256: sha2562.digest("hex"), sha512: sha512.digest("hex") };
6901
4963
  }
6902
4964
  async function acquirePiTarball(destination) {
6903
- const existing = fs25.statSync(destination, { throwIfNoEntry: false });
4965
+ const existing = fs18.statSync(destination, { throwIfNoEntry: false });
6904
4966
  if (existing?.isFile()) {
6905
4967
  const observed = hashPiTarball(destination);
6906
4968
  if (observed.bytes === PI_RUNTIME_CANDIDATE.tarball.bytes && observed.sha256 === PI_RUNTIME_CANDIDATE.tarball.sha256 && observed.sha512 === PI_RUNTIME_CANDIDATE.tarball.sha512) {
6907
4969
  return observed;
6908
4970
  }
6909
- fs25.rmSync(destination, { force: true });
4971
+ fs18.rmSync(destination, { force: true });
6910
4972
  }
6911
- fs25.mkdirSync(path33.dirname(destination), { recursive: true });
4973
+ fs18.mkdirSync(path23.dirname(destination), { recursive: true });
6912
4974
  const partial = `${destination}.partial-${process.pid}`;
6913
4975
  const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
6914
4976
  redirect: "error",
6915
4977
  headers: { accept: "application/octet-stream" }
6916
4978
  });
6917
4979
  if (!response.ok || response.body === null) throw new Error(`No se pudo descargar jorgex-pi@${PI_RUNTIME_CANDIDATE.package.version} (${response.status}).`);
6918
- const descriptor = fs25.openSync(partial, "wx", 384);
4980
+ const descriptor = fs18.openSync(partial, "wx", 384);
6919
4981
  let bytes = 0;
6920
4982
  try {
6921
4983
  for await (const chunk of response.body) {
@@ -6923,15 +4985,15 @@ async function acquirePiTarball(destination) {
6923
4985
  bytes += buffer.length;
6924
4986
  if (bytes > PI_RUNTIME_CANDIDATE.tarball.bytes) throw new Error("El tarball de jorgex-pi excede el tama\xF1o fijado.");
6925
4987
  let offset = 0;
6926
- while (offset < buffer.length) offset += fs25.writeSync(descriptor, buffer, offset);
4988
+ while (offset < buffer.length) offset += fs18.writeSync(descriptor, buffer, offset);
6927
4989
  }
6928
4990
  } catch (error) {
6929
- fs25.closeSync(descriptor);
6930
- fs25.rmSync(partial, { force: true });
4991
+ fs18.closeSync(descriptor);
4992
+ fs18.rmSync(partial, { force: true });
6931
4993
  throw error;
6932
4994
  }
6933
- fs25.closeSync(descriptor);
6934
- fs25.renameSync(partial, destination);
4995
+ fs18.closeSync(descriptor);
4996
+ fs18.renameSync(partial, destination);
6935
4997
  return hashPiTarball(destination);
6936
4998
  }
6937
4999
  function runProcess(invocation) {
@@ -6981,7 +5043,7 @@ async function runPiRuntimeSystem(input) {
6981
5043
  remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
6982
5044
  };
6983
5045
  }
6984
- const destination = input.targetDir === void 0 ? path33.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path33.join(path33.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
5046
+ const destination = input.targetDir === void 0 ? path23.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path23.join(path23.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
6985
5047
  let artifact;
6986
5048
  try {
6987
5049
  artifact = await acquirePiTarball(destination);
@@ -7002,18 +5064,18 @@ async function runPiRuntimeSystem(input) {
7002
5064
  }, {
7003
5065
  download: () => artifact,
7004
5066
  backupSettings: () => createBackup(
7005
- [path33.join(paths.codingAgentDir, "settings.json")],
5067
+ [path23.join(paths.codingAgentDir, "settings.json")],
7006
5068
  "pi-package-install",
7007
- input.targetDir === void 0 ? void 0 : path33.join(path33.resolve(input.targetDir), "backups")
5069
+ input.targetDir === void 0 ? void 0 : path23.join(path23.resolve(input.targetDir), "backups")
7008
5070
  ),
7009
5071
  run: runProcess,
7010
- readSettings: () => readOptional(path33.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
7011
- rewriteSettings: (content) => writeText(path33.join(paths.codingAgentDir, "settings.json"), `${content}
5072
+ readSettings: () => readOptional(path23.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
5073
+ rewriteSettings: (content) => writeText(path23.join(paths.codingAgentDir, "settings.json"), `${content}
7012
5074
  `),
7013
5075
  writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
7014
5076
  });
7015
5077
  }
7016
- const packageRoot = path33.dirname(path33.dirname(paths.packageRunner));
5078
+ const packageRoot = path23.dirname(path23.dirname(paths.packageRunner));
7017
5079
  const writeReceipt = (receipt) => {
7018
5080
  writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
7019
5081
  `);
@@ -7031,13 +5093,13 @@ async function runPiRuntimeSystem(input) {
7031
5093
  value,
7032
5094
  {
7033
5095
  backupSettings: () => createBackup(
7034
- [path33.join(paths.codingAgentDir, "settings.json")],
5096
+ [path23.join(paths.codingAgentDir, "settings.json")],
7035
5097
  "pi-package-uninstall",
7036
- input.targetDir === void 0 ? void 0 : path33.join(path33.resolve(input.targetDir), "backups")
5098
+ input.targetDir === void 0 ? void 0 : path23.join(path23.resolve(input.targetDir), "backups")
7037
5099
  ),
7038
5100
  run: runProcess,
7039
- isPackageAbsent: () => !fs25.existsSync(packageRoot),
7040
- deleteReceipt: () => fs25.rmSync(paths.receiptPath, { force: true })
5101
+ isPackageAbsent: () => !fs18.existsSync(packageRoot),
5102
+ deleteReceipt: () => fs18.rmSync(paths.receiptPath, { force: true })
7041
5103
  }
7042
5104
  )
7043
5105
  };
@@ -7212,7 +5274,7 @@ async function runManagedPiSystem(input) {
7212
5274
  }
7213
5275
 
7214
5276
  // src/lib/quality-runner.ts
7215
- import path34 from "path";
5277
+ import path24 from "path";
7216
5278
  import { spawn, spawnSync as spawnSync2 } from "child_process";
7217
5279
  function normalizeLimit(value, name) {
7218
5280
  if (value === void 0) return void 0;
@@ -7232,10 +5294,10 @@ function normalizeTimeout(value) {
7232
5294
  return timeoutMs;
7233
5295
  }
7234
5296
  var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
7235
- function isRecord3(value) {
5297
+ function isRecord2(value) {
7236
5298
  return value !== null && typeof value === "object" && !Array.isArray(value);
7237
5299
  }
7238
- function hasText2(value) {
5300
+ function hasText(value) {
7239
5301
  return typeof value === "string" && value.trim() !== "";
7240
5302
  }
7241
5303
  function isDenseStringArray(value) {
@@ -7252,14 +5314,14 @@ function isQualityProfile(value) {
7252
5314
  return typeof value === "string" && QUALITY_PROFILES.includes(value);
7253
5315
  }
7254
5316
  function assertValidEnvironment(value, label) {
7255
- if (!isRecord3(value)) throw new Error(`Invalid ${label}`);
5317
+ if (!isRecord2(value)) throw new Error(`Invalid ${label}`);
7256
5318
  for (const [key, entry] of Object.entries(value)) {
7257
5319
  if (typeof entry !== "string") throw new Error(`Invalid ${label}.${key}`);
7258
5320
  }
7259
5321
  }
7260
5322
  function assertQualityPlanInput(value) {
7261
- if (!isRecord3(value)) throw new Error("Invalid quality plan");
7262
- if (!isRecord3(value.identity)) throw new Error("Invalid quality plan identity");
5323
+ if (!isRecord2(value)) throw new Error("Invalid quality plan");
5324
+ if (!isRecord2(value.identity)) throw new Error("Invalid quality plan identity");
7263
5325
  if (typeof value.identity.baseSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.baseSha)) {
7264
5326
  throw new Error("Invalid quality plan identity.baseSha");
7265
5327
  }
@@ -7271,7 +5333,7 @@ function assertQualityPlanInput(value) {
7271
5333
  const controlIds = /* @__PURE__ */ new Set();
7272
5334
  for (let index = 0; index < value.controls.length; index += 1) {
7273
5335
  const control = value.controls[index];
7274
- if (!isRecord3(control) || !hasText2(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
5336
+ if (!isRecord2(control) || !hasText(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
7275
5337
  throw new Error(`Invalid quality plan controls[${index}]`);
7276
5338
  }
7277
5339
  if (controlIds.has(control.id)) {
@@ -7287,9 +5349,9 @@ function assertQualityPlanInput(value) {
7287
5349
  const commandControlIds = /* @__PURE__ */ new Set();
7288
5350
  for (let index = 0; index < value.commands.length; index += 1) {
7289
5351
  const command = value.commands[index];
7290
- if (!isRecord3(command)) throw new Error(`Invalid quality plan commands[${index}]`);
5352
+ if (!isRecord2(command)) throw new Error(`Invalid quality plan commands[${index}]`);
7291
5353
  const timeoutMs = command.timeoutMs;
7292
- if (!hasText2(command.controlId) || !hasText2(command.commandId) || !hasText2(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
5354
+ if (!hasText(command.controlId) || !hasText(command.commandId) || !hasText(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
7293
5355
  throw new Error(`Invalid quality plan commands[${index}]`);
7294
5356
  }
7295
5357
  if (timeoutMs > MAX_NODE_TIMEOUT_MS) {
@@ -7344,7 +5406,7 @@ function killProcessTree(child) {
7344
5406
  if (pid === void 0) return;
7345
5407
  if (process.platform === "win32") {
7346
5408
  const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
7347
- const taskkill = path34.join(systemRoot, "System32", "taskkill.exe");
5409
+ const taskkill = path24.join(systemRoot, "System32", "taskkill.exe");
7348
5410
  const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
7349
5411
  shell: false,
7350
5412
  stdio: "ignore",
@@ -7532,7 +5594,7 @@ function missingRequiredResults(controls, commands) {
7532
5594
  const commandControlIds = new Set(commands.map((command) => command.controlId));
7533
5595
  const missing = /* @__PURE__ */ new Set();
7534
5596
  for (const control of controls) {
7535
- if (control.requirement !== "required" || !hasText2(control.id) || commandControlIds.has(control.id)) continue;
5597
+ if (control.requirement !== "required" || !hasText(control.id) || commandControlIds.has(control.id)) continue;
7536
5598
  missing.add(control.id);
7537
5599
  }
7538
5600
  return [...missing].map((controlId) => ({
@@ -7590,62 +5652,62 @@ async function runQualityPlan(input) {
7590
5652
  }
7591
5653
 
7592
5654
  // src/lib/engram-install.ts
7593
- import fs26 from "fs";
5655
+ import fs19 from "fs";
7594
5656
  import os5 from "os";
7595
- import path35 from "path";
7596
- import { execFileSync as execFileSync7 } from "child_process";
5657
+ import path25 from "path";
5658
+ import { execFileSync as execFileSync6 } from "child_process";
7597
5659
  import { createHash as createHash3 } from "crypto";
7598
5660
 
7599
5661
  // src/lib/engram-release.json
7600
5662
  var engram_release_default = {
7601
5663
  assets: {
7602
5664
  darwin_arm64: {
7603
- name: "engram_1.20.0_darwin_arm64.tar.gz",
7604
- sha256: "2363d5012f23e58878f86c3ddcc1f63cfe9dcf3eec7f413e70eaafcbb9d394cc",
7605
- size: 6949031
5665
+ name: "engram_2.0.0_darwin_arm64.tar.gz",
5666
+ sha256: "793c695153e0e76144dc53b006a7039771bd24115385856578bc4d6506d46d7f",
5667
+ size: 7210321
7606
5668
  },
7607
5669
  darwin_x64: {
7608
- name: "engram_1.20.0_darwin_amd64.tar.gz",
7609
- sha256: "3b9015dcfcdd9f823eb7ad0b590b1dbc4c25bb5d81dda3f84e623709815afe80",
7610
- size: 7367005
5670
+ name: "engram_2.0.0_darwin_amd64.tar.gz",
5671
+ sha256: "f1dcc343ac241c5001617113279b49dec698005550b538250ccf21655fc728ac",
5672
+ size: 7678428
7611
5673
  },
7612
5674
  linux_arm64: {
7613
- name: "engram_1.20.0_linux_arm64.tar.gz",
7614
- sha256: "7eb815910a76ae6cfa9a5d0161d3701e293dcca71f7743cffa62e236e5af59af",
7615
- size: 6673857
5675
+ name: "engram_2.0.0_linux_arm64.tar.gz",
5676
+ sha256: "a942e73ab424faaa6e2785d1563e0d9d7f20739944dae0c50071223301d44333",
5677
+ size: 6926960
7616
5678
  },
7617
5679
  linux_x64: {
7618
- name: "engram_1.20.0_linux_amd64.tar.gz",
7619
- sha256: "7dc3003318e303bee269a4772144f3ce01c8ec700bfd524aaec76770acd389ca",
7620
- size: 7236400
5680
+ name: "engram_2.0.0_linux_amd64.tar.gz",
5681
+ sha256: "23be1c2ce9739c455097ff864736213717b925b3e8821a988dfc619685a5abd5",
5682
+ size: 7544949
7621
5683
  },
7622
5684
  windows_arm64: {
7623
- name: "engram_1.20.0_windows_arm64.zip",
7624
- sha256: "eb93c6d7d5a4020ddcf1ff956909f3e90694f6447519433316b10f964f8f37b5",
7625
- size: 6723366
5685
+ name: "engram_2.0.0_windows_arm64.zip",
5686
+ sha256: "e4d9074ebf8a839573f62d7bd2719421fc428ab25a9affe82ca4b2d5555183d1",
5687
+ size: 6980876
7626
5688
  },
7627
5689
  windows_x64: {
7628
- name: "engram_1.20.0_windows_amd64.zip",
7629
- sha256: "c945c47d58367f943642cb8451fa45f208174407839f1dda33a975a05d6e465b",
7630
- size: 7416778
5690
+ name: "engram_2.0.0_windows_amd64.zip",
5691
+ sha256: "fba31e7221702f213954c2081aa83b38833e0e9b824a981f976f8cee2c74548c",
5692
+ size: 7738896
7631
5693
  }
7632
5694
  },
7633
- version: "1.20.0"
5695
+ version: "2.0.0"
7634
5696
  };
7635
5697
 
7636
5698
  // src/lib/engram-install.ts
7637
5699
  async function installMissingEngram(options = {}) {
7638
5700
  const platform = options.platform ?? process.platform;
7639
5701
  const homeDir = options.homeDir ?? os5.homedir();
7640
- if (!path35.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
5702
+ if (!path25.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
7641
5703
  const binaryName = platform === "win32" ? "engram.exe" : "engram";
7642
- const bin = path35.join(homeDir, ".local", "bin", binaryName);
5704
+ const bin = path25.join(homeDir, ".local", "bin", binaryName);
7643
5705
  let staging;
7644
5706
  try {
7645
- const existing = fs26.lstatSync(bin, { throwIfNoEntry: false });
5707
+ const existing = fs19.lstatSync(bin, { throwIfNoEntry: false });
7646
5708
  if (existing) {
7647
- if (!fs26.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
7648
- fs26.accessSync(bin, platform === "win32" ? fs26.constants.F_OK : fs26.constants.X_OK);
5709
+ if (!fs19.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
5710
+ fs19.accessSync(bin, platform === "win32" ? fs19.constants.F_OK : fs19.constants.X_OK);
7649
5711
  return { ok: true, bin };
7650
5712
  }
7651
5713
  const key = `${platform === "win32" ? "windows" : platform}_${options.arch ?? process.arch}`;
@@ -7674,24 +5736,24 @@ async function installMissingEngram(options = {}) {
7674
5736
  if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
7675
5737
  throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
7676
5738
  }
7677
- fs26.mkdirSync(path35.dirname(bin), { recursive: true });
7678
- staging = fs26.mkdtempSync(path35.join(path35.dirname(bin), ".engram-install-"));
7679
- const archivePath = path35.join(staging, asset.name);
7680
- fs26.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
7681
- const binary = execFileSync7(resolveTarBin(), ["-xOf", archivePath, binaryName], {
5739
+ fs19.mkdirSync(path25.dirname(bin), { recursive: true });
5740
+ staging = fs19.mkdtempSync(path25.join(path25.dirname(bin), ".engram-install-"));
5741
+ const archivePath = path25.join(staging, asset.name);
5742
+ fs19.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
5743
+ const binary = execFileSync6(resolveTarBin(), ["-xOf", archivePath, binaryName], {
7682
5744
  maxBuffer: 64 * 1024 * 1024,
7683
5745
  timeout: 3e4,
7684
5746
  stdio: ["ignore", "pipe", "pipe"]
7685
5747
  });
7686
5748
  if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
7687
- const stagedBin = path35.join(staging, binaryName);
7688
- fs26.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
7689
- fs26.linkSync(stagedBin, bin);
5749
+ const stagedBin = path25.join(staging, binaryName);
5750
+ fs19.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
5751
+ fs19.linkSync(stagedBin, bin);
7690
5752
  return { ok: true, bin };
7691
5753
  } catch (error) {
7692
5754
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
7693
5755
  } finally {
7694
- if (staging) fs26.rmSync(staging, { recursive: true, force: true });
5756
+ if (staging) fs19.rmSync(staging, { recursive: true, force: true });
7695
5757
  }
7696
5758
  }
7697
5759
 
@@ -8217,7 +6279,7 @@ Flags disponibles: jorgex-stack --help`
8217
6279
  return;
8218
6280
  }
8219
6281
  try {
8220
- const plan = JSON.parse(fs27.readFileSync(flags.positional[0], "utf8"));
6282
+ const plan = JSON.parse(fs20.readFileSync(flags.positional[0], "utf8"));
8221
6283
  const result = await runQualityPlan(plan);
8222
6284
  const serialized = serializeQualityReceipt(result.receipt);
8223
6285
  if (flags.receipt === void 0) process.stdout.write(`${serialized}
@@ -8603,13 +6665,22 @@ Flags disponibles: jorgex-stack --help`
8603
6665
  if (!flags.list) console.log("\nUsa: jorgex-stack restore <id>");
8604
6666
  return;
8605
6667
  }
8606
- const restored = restoreBackup(flags.positional[0]);
6668
+ const targetId = flags.positional[0];
6669
+ const expected = listBackups().find((b) => b.id === targetId)?.files.length;
6670
+ const restored = restoreBackup(targetId);
6671
+ if (expected !== void 0 && restored < expected) {
6672
+ console.error(
6673
+ `Restore incompleto del backup ${targetId}: restaurados ${restored}/${expected} archivos (omisiones de seguridad: symlinks, fuera de HOME o ilegibles). Revisa arriba y restaura a mano lo que falte.`
6674
+ );
6675
+ process.exitCode = 1;
6676
+ return;
6677
+ }
8607
6678
  console.log(`Restaurados ${restored} archivos.`);
8608
6679
  return;
8609
6680
  }
8610
6681
  }
8611
6682
  }
8612
- if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL2(process.argv[1]).href) {
6683
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href) {
8613
6684
  await main().catch((err) => {
8614
6685
  console.error(err instanceof Error ? err.message : String(err));
8615
6686
  process.exitCode = 1;