jorgex-stack 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,16 +1,30 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ HOME,
4
+ QUALITY_PROFILES,
5
+ canonicalJson,
6
+ createQualityReceipt,
7
+ dataDir,
8
+ evaluateQualityPolicy,
9
+ samePath,
10
+ serializeQualityReceipt,
11
+ sha256,
12
+ stackRoot,
13
+ validateQualityReceipt
14
+ } from "./chunk-IO2FFZOH.js";
2
15
 
3
16
  // src/cli.ts
4
17
  import * as p6 from "@clack/prompts";
18
+ import fs24 from "fs";
5
19
  import { pathToFileURL as pathToFileURL2 } from "url";
6
20
 
7
21
  // src/install.ts
8
22
  import fs15 from "fs";
9
- import path21 from "path";
23
+ import path20 from "path";
10
24
  import * as p from "@clack/prompts";
11
25
 
12
26
  // src/adapters/opencode.ts
13
- import path7 from "path";
27
+ import path6 from "path";
14
28
  import fs3 from "fs";
15
29
  import { pathToFileURL } from "url";
16
30
 
@@ -67,37 +81,12 @@ function loadCanonicalDefaults(stackDir) {
67
81
  }
68
82
 
69
83
  // src/lib/model-map.ts
70
- import path4 from "path";
71
- import { existsSync as existsSync2 } from "fs";
72
-
73
- // src/lib/paths.ts
74
- import os from "os";
75
- import path2 from "path";
84
+ import path3 from "path";
76
85
  import { existsSync } from "fs";
77
- import { fileURLToPath } from "url";
78
- var HOME = os.homedir();
79
- function stackRoot() {
80
- let dir = path2.dirname(fileURLToPath(import.meta.url));
81
- for (let i = 0; i < 6; i++) {
82
- const candidate = path2.join(dir, "stack");
83
- if (existsSync(path2.join(candidate, "system-prompt", "AGENTS.md"))) return candidate;
84
- dir = path2.dirname(dir);
85
- }
86
- throw new Error("No se encontr\xF3 stack/ relativa al CLI \u2014 instalaci\xF3n rota.");
87
- }
88
- function dataDir() {
89
- return path2.join(HOME, ".jorgex-stack");
90
- }
91
- function samePath(a, b) {
92
- const resolvedA = path2.resolve(a);
93
- const resolvedB = path2.resolve(b);
94
- if (process.platform === "win32") return resolvedA.toLowerCase() === resolvedB.toLowerCase();
95
- return resolvedA === resolvedB;
96
- }
97
86
 
98
87
  // src/lib/fsx.ts
99
88
  import fs2 from "fs";
100
- import path3 from "path";
89
+ import path2 from "path";
101
90
  function readTextIfExists(file) {
102
91
  try {
103
92
  return fs2.readFileSync(file, "utf8");
@@ -110,7 +99,7 @@ function ensureDir(dir) {
110
99
  }
111
100
  var tmpCounter = 0;
112
101
  function tmpPath(target) {
113
- return path3.join(path3.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
102
+ return path2.join(path2.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
114
103
  }
115
104
  function renameInto(tmp, target) {
116
105
  try {
@@ -121,23 +110,23 @@ function renameInto(tmp, target) {
121
110
  }
122
111
  }
123
112
  function writeText(file, content) {
124
- ensureDir(path3.dirname(file));
113
+ ensureDir(path2.dirname(file));
125
114
  const tmp = tmpPath(file);
126
115
  fs2.writeFileSync(tmp, content, "utf8");
127
116
  renameInto(tmp, file);
128
117
  }
129
118
  function copyFile(source, target) {
130
- ensureDir(path3.dirname(target));
119
+ ensureDir(path2.dirname(target));
131
120
  const tmp = tmpPath(target);
132
121
  fs2.copyFileSync(source, tmp);
133
122
  renameInto(tmp, target);
134
123
  }
135
124
  function isContainedIn(child, root) {
136
- const rel = path3.relative(path3.resolve(root), path3.resolve(child));
137
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${path3.sep}`) && !path3.isAbsolute(rel);
125
+ const rel = path2.relative(path2.resolve(root), path2.resolve(child));
126
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${path2.sep}`) && !path2.isAbsolute(rel);
138
127
  }
139
128
  function pruneEmptyDirs(file, root) {
140
- let dir = path3.dirname(file);
129
+ let dir = path2.dirname(file);
141
130
  while (dir.startsWith(root) && dir !== root) {
142
131
  try {
143
132
  if (fs2.readdirSync(dir).length > 0) return;
@@ -145,7 +134,7 @@ function pruneEmptyDirs(file, root) {
145
134
  } catch {
146
135
  return;
147
136
  }
148
- dir = path3.dirname(dir);
137
+ dir = path2.dirname(dir);
149
138
  }
150
139
  }
151
140
  function sameFileContent(a, b) {
@@ -162,7 +151,7 @@ function listFilesRecursive(dir) {
162
151
  if (!fs2.existsSync(dir)) return out;
163
152
  const walk = (d) => {
164
153
  for (const entry of fs2.readdirSync(d, { withFileTypes: true })) {
165
- const p7 = path3.join(d, entry.name);
154
+ const p7 = path2.join(d, entry.name);
166
155
  if (entry.isDirectory()) walk(p7);
167
156
  else out.push(p7);
168
157
  }
@@ -197,7 +186,7 @@ var DEFAULT_MODEL_MAP = {
197
186
  }
198
187
  };
199
188
  function modelMapFile() {
200
- return path4.join(dataDir(), "model-map.json");
189
+ return path3.join(dataDir(), "model-map.json");
201
190
  }
202
191
  function loadModelMap() {
203
192
  const raw = readTextIfExists(modelMapFile());
@@ -216,22 +205,22 @@ function loadModelMap() {
216
205
  }
217
206
  function ensureModelMapFile() {
218
207
  const file = modelMapFile();
219
- if (!existsSync2(file)) {
208
+ if (!existsSync(file)) {
220
209
  writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
221
210
  }
222
211
  return file;
223
212
  }
224
213
 
225
214
  // src/lib/detect.ts
226
- import path5 from "path";
227
- import { existsSync as existsSync3, statSync } from "fs";
215
+ import path4 from "path";
216
+ import { existsSync as existsSync2, statSync } from "fs";
228
217
  import { execFileSync } from "child_process";
229
218
  function lookPath(cmd) {
230
219
  const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
231
- for (const dir of (process.env.PATH ?? "").split(path5.delimiter)) {
220
+ for (const dir of (process.env.PATH ?? "").split(path4.delimiter)) {
232
221
  if (!dir) continue;
233
222
  for (const ext of exts) {
234
- const candidate = path5.join(dir, cmd + ext);
223
+ const candidate = path4.join(dir, cmd + ext);
235
224
  if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
236
225
  }
237
226
  }
@@ -260,50 +249,50 @@ function runDetectedBin(bin, args, timeoutMs) {
260
249
  }
261
250
  }
262
251
  function detectOpenCode() {
263
- const configDir = process.env.OPENCODE_CONFIG_DIR ?? path5.join(HOME, ".config", "opencode");
252
+ const configDir = process.env.OPENCODE_CONFIG_DIR ?? path4.join(HOME, ".config", "opencode");
264
253
  const binPath = lookPath("opencode");
265
254
  return {
266
255
  id: "opencode",
267
256
  name: "OpenCode",
268
- installed: binPath !== null || existsSync3(configDir),
257
+ installed: binPath !== null || existsSync2(configDir),
269
258
  binPath,
270
259
  configDir
271
260
  };
272
261
  }
273
262
  function detectClaudeCode() {
274
- const configDir = path5.join(HOME, ".claude");
263
+ const configDir = path4.join(HOME, ".claude");
275
264
  const binPath = lookPath("claude");
276
265
  return {
277
266
  id: "claude-code",
278
267
  name: "Claude Code",
279
- installed: binPath !== null || existsSync3(configDir),
268
+ installed: binPath !== null || existsSync2(configDir),
280
269
  binPath,
281
270
  configDir
282
271
  };
283
272
  }
284
273
  function detectCodex() {
285
- const configDir = process.env.CODEX_HOME ?? path5.join(HOME, ".codex");
274
+ const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
286
275
  const binPath = lookPath("codex");
287
276
  return {
288
277
  id: "codex",
289
278
  name: "Codex CLI",
290
- installed: binPath !== null || existsSync3(configDir),
279
+ installed: binPath !== null || existsSync2(configDir),
291
280
  binPath,
292
281
  configDir
293
282
  };
294
283
  }
295
284
  function detectEngram() {
296
285
  const fromEnv = process.env.ENGRAM_BIN;
297
- if (fromEnv && existsSync3(fromEnv)) return fromEnv;
286
+ if (fromEnv && existsSync2(fromEnv)) return fromEnv;
298
287
  const onPath = lookPath("engram");
299
288
  if (onPath) return onPath;
300
289
  const candidates = [
301
- path5.join(HOME, "go", "bin", "engram.exe"),
302
- path5.join(HOME, "go", "bin", "engram"),
303
- path5.join(HOME, ".local", "bin", "engram.exe"),
304
- path5.join(HOME, ".local", "bin", "engram")
290
+ path4.join(HOME, "go", "bin", "engram.exe"),
291
+ path4.join(HOME, "go", "bin", "engram"),
292
+ path4.join(HOME, ".local", "bin", "engram.exe"),
293
+ path4.join(HOME, ".local", "bin", "engram")
305
294
  ];
306
- for (const c of candidates) if (existsSync3(c)) return c;
295
+ for (const c of candidates) if (existsSync2(c)) return c;
307
296
  return null;
308
297
  }
309
298
 
@@ -472,9 +461,9 @@ function readTomlSection(existing, section) {
472
461
  }
473
462
 
474
463
  // src/lib/hooks-format.ts
475
- import path6 from "path";
464
+ import path5 from "path";
476
465
  function hookScriptNames(canonical) {
477
- 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) => path6.basename(s));
466
+ return Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
478
467
  }
479
468
  function plainHookCommands(canonical) {
480
469
  return new Set(
@@ -521,7 +510,7 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
521
510
  ...h.timeout !== void 0 ? { timeout: h.timeout } : {}
522
511
  }))
523
512
  };
524
- const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path6.basename(s));
513
+ const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
525
514
  const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
526
515
  const index = list.findIndex((existingEntry) => {
527
516
  const e = existingEntry;
@@ -601,16 +590,16 @@ var opencodeAdapter = {
601
590
  paths(configDir) {
602
591
  const isRealConfigDir = samePath(
603
592
  configDir,
604
- process.env.OPENCODE_CONFIG_DIR ?? path7.join(HOME, ".config", "opencode")
593
+ process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode")
605
594
  );
606
- const agentsHome = isRealConfigDir ? HOME : path7.dirname(configDir);
595
+ const agentsHome = isRealConfigDir ? HOME : path6.dirname(configDir);
607
596
  return {
608
- systemPromptFile: path7.join(configDir, "AGENTS.md"),
609
- agentsDir: path7.join(configDir, "agents"),
610
- skillsDir: path7.join(agentsHome, ".agents", "skills"),
611
- commandsDir: path7.join(configDir, "commands"),
612
- pluginsDir: path7.join(configDir, "plugins"),
613
- scriptsDir: path7.join(configDir, "scripts"),
597
+ systemPromptFile: path6.join(configDir, "AGENTS.md"),
598
+ agentsDir: path6.join(configDir, "agents"),
599
+ skillsDir: path6.join(agentsHome, ".agents", "skills"),
600
+ commandsDir: path6.join(configDir, "commands"),
601
+ pluginsDir: path6.join(configDir, "plugins"),
602
+ scriptsDir: path6.join(configDir, "scripts"),
614
603
  outputStylesDir: null,
615
604
  profilesDir: null
616
605
  };
@@ -668,12 +657,12 @@ ${agent.body}`,
668
657
  ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
669
658
  continue;
670
659
  }
671
- const script = `scripts/${path7.basename(match[1])}`;
660
+ const script = `scripts/${path6.basename(match[1])}`;
672
661
  (bashEntries[includes] ??= []).push(script);
673
662
  }
674
663
  }
675
664
  }
676
- const hooksFile = path7.join(ctx.configDir, "hooks.json");
665
+ const hooksFile = path6.join(ctx.configDir, "hooks.json");
677
666
  const content = upsertJson(readTextIfExists(hooksFile), (root) => {
678
667
  const afterValue = root["tool.execute.after"] ??= {};
679
668
  if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
@@ -708,16 +697,16 @@ ${agent.body}`,
708
697
  }
709
698
  });
710
699
  actions.push({ kind: "write", target: hooksFile, content });
711
- const scriptsSource = path7.join(ctx.stackDir, "scripts");
700
+ const scriptsSource = path6.join(ctx.stackDir, "scripts");
712
701
  if (fs3.existsSync(scriptsSource)) {
713
702
  for (const f of fs3.readdirSync(scriptsSource)) {
714
- actions.push({ kind: "copy", source: path7.join(scriptsSource, f), target: path7.join(scriptsDir, f) });
703
+ actions.push({ kind: "copy", source: path6.join(scriptsSource, f), target: path6.join(scriptsDir, f) });
715
704
  }
716
705
  }
717
706
  return actions;
718
707
  },
719
708
  planMainConfig(canonical, ctx) {
720
- const file = path7.join(ctx.configDir, "opencode.json");
709
+ const file = path6.join(ctx.configDir, "opencode.json");
721
710
  const { pluginsDir } = this.paths(ctx.configDir);
722
711
  const original = readTextIfExists(file);
723
712
  const contentSource = original === null || original.trim() === "" ? null : original;
@@ -829,7 +818,7 @@ ${agent.body}`,
829
818
  content = removeMarkdownSection(content, "browser");
830
819
  actions.push({ kind: "write", target: systemPromptFile, content });
831
820
  }
832
- const configFile = path7.join(ctx.configDir, "opencode.json");
821
+ const configFile = path6.join(ctx.configDir, "opencode.json");
833
822
  const config = readTextIfExists(configFile);
834
823
  if (config !== null) {
835
824
  const mcpOwnership = [];
@@ -897,7 +886,7 @@ ${agent.body}`,
897
886
  ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
898
887
  });
899
888
  }
900
- const hooksFile = path7.join(ctx.configDir, "hooks.json");
889
+ const hooksFile = path6.join(ctx.configDir, "hooks.json");
901
890
  const hooksJson = readTextIfExists(hooksFile);
902
891
  if (hooksJson !== null) {
903
892
  const ourScripts = hookScriptNames(hooks);
@@ -923,7 +912,7 @@ ${agent.body}`,
923
912
  };
924
913
 
925
914
  // src/adapters/claude-code.ts
926
- import path8 from "path";
915
+ import path7 from "path";
927
916
  import fs4 from "fs";
928
917
  function yamlString2(value) {
929
918
  return JSON.stringify(value);
@@ -944,8 +933,8 @@ function toolsFor(agent) {
944
933
  return tools.join(", ");
945
934
  }
946
935
  function hasEngramPlugin(configDir) {
947
- if (fs4.existsSync(path8.join(configDir, "plugins", "marketplaces", "engram"))) return true;
948
- const registry = readTextIfExists(path8.join(configDir, "plugins", "installed_plugins.json"));
936
+ if (fs4.existsSync(path7.join(configDir, "plugins", "marketplaces", "engram"))) return true;
937
+ const registry = readTextIfExists(path7.join(configDir, "plugins", "installed_plugins.json"));
949
938
  if (registry === null) return false;
950
939
  try {
951
940
  const parsed = JSON.parse(registry);
@@ -972,13 +961,13 @@ var claudeCodeAdapter = {
972
961
  },
973
962
  paths(configDir) {
974
963
  return {
975
- systemPromptFile: path8.join(configDir, "CLAUDE.md"),
976
- agentsDir: path8.join(configDir, "agents"),
977
- skillsDir: path8.join(configDir, "skills"),
978
- commandsDir: path8.join(configDir, "commands"),
964
+ systemPromptFile: path7.join(configDir, "CLAUDE.md"),
965
+ agentsDir: path7.join(configDir, "agents"),
966
+ skillsDir: path7.join(configDir, "skills"),
967
+ commandsDir: path7.join(configDir, "commands"),
979
968
  pluginsDir: null,
980
- scriptsDir: path8.join(configDir, "scripts"),
981
- outputStylesDir: path8.join(configDir, "output-styles"),
969
+ scriptsDir: path7.join(configDir, "scripts"),
970
+ outputStylesDir: path7.join(configDir, "output-styles"),
982
971
  profilesDir: null
983
972
  };
984
973
  },
@@ -1024,9 +1013,9 @@ ${agent.body}`,
1024
1013
  planHooks(canonical, ctx) {
1025
1014
  const actions = [];
1026
1015
  const { scriptsDir } = this.paths(ctx.configDir);
1027
- const original = readTextIfExists(path8.join(ctx.configDir, "settings.json"));
1016
+ const original = readTextIfExists(path7.join(ctx.configDir, "settings.json"));
1028
1017
  const contentSource = original === null || original.trim() === "" ? null : original;
1029
- const settingsFile = path8.join(ctx.configDir, "settings.json");
1018
+ const settingsFile = path7.join(ctx.configDir, "settings.json");
1030
1019
  let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
1031
1020
  const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
1032
1021
  if (contentSource === null && defaults?.["permissions"] !== void 0) {
@@ -1040,16 +1029,16 @@ ${agent.body}`,
1040
1029
  });
1041
1030
  }
1042
1031
  actions.push({ kind: "write", target: settingsFile, content });
1043
- const scriptsSource = path8.join(ctx.stackDir, "scripts");
1032
+ const scriptsSource = path7.join(ctx.stackDir, "scripts");
1044
1033
  if (fs4.existsSync(scriptsSource)) {
1045
1034
  for (const f of fs4.readdirSync(scriptsSource)) {
1046
- actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
1035
+ actions.push({ kind: "copy", source: path7.join(scriptsSource, f), target: path7.join(scriptsDir, f) });
1047
1036
  }
1048
1037
  }
1049
1038
  return actions;
1050
1039
  },
1051
1040
  planMainConfig(canonical, ctx) {
1052
- const file = path8.join(path8.dirname(ctx.configDir), `${path8.basename(ctx.configDir)}.json`);
1041
+ const file = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
1053
1042
  const mcpOwnership = [];
1054
1043
  const content = upsertJson(readTextIfExists(file), (root) => {
1055
1044
  const servers = root["mcpServers"] ??= {};
@@ -1114,7 +1103,7 @@ ${agent.body}`,
1114
1103
  content = removeMarkdownSection(content, "browser");
1115
1104
  actions.push({ kind: "write", target: systemPromptFile, content });
1116
1105
  }
1117
- const settingsFile = path8.join(ctx.configDir, "settings.json");
1106
+ const settingsFile = path7.join(ctx.configDir, "settings.json");
1118
1107
  const settings = readTextIfExists(settingsFile);
1119
1108
  if (settings !== null) {
1120
1109
  const content = removeNativeHooks(settings, hooks);
@@ -1122,7 +1111,7 @@ ${agent.body}`,
1122
1111
  actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
1123
1112
  }
1124
1113
  }
1125
- const mainFile = path8.join(path8.dirname(ctx.configDir), `${path8.basename(ctx.configDir)}.json`);
1114
+ const mainFile = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
1126
1115
  const main2 = readTextIfExists(mainFile);
1127
1116
  if (main2 !== null) {
1128
1117
  const mcpOwnership = [];
@@ -1148,7 +1137,7 @@ ${agent.body}`,
1148
1137
  };
1149
1138
 
1150
1139
  // src/adapters/codex.ts
1151
- import path9 from "path";
1140
+ import path8 from "path";
1152
1141
  import fs5 from "fs";
1153
1142
  function tomlString(value) {
1154
1143
  return JSON.stringify(value);
@@ -1172,15 +1161,15 @@ function isManagedOptionalStdioServer3(server, section) {
1172
1161
  return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
1173
1162
  }
1174
1163
  function hasActiveEngramPlugin(configDir) {
1175
- const config = readTextIfExists(path9.join(configDir, "config.toml"));
1164
+ const config = readTextIfExists(path8.join(configDir, "config.toml"));
1176
1165
  if (config === null) return false;
1177
1166
  const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
1178
1167
  return match !== null && !/enabled\s*=\s*false/.test(match[1]);
1179
1168
  }
1180
1169
  function hasEngramProtocol(configDir) {
1181
1170
  if (hasActiveEngramPlugin(configDir)) return true;
1182
- if (fs5.existsSync(path9.join(configDir, "engram-instructions.md"))) return true;
1183
- const config = readTextIfExists(path9.join(configDir, "config.toml"));
1171
+ if (fs5.existsSync(path8.join(configDir, "engram-instructions.md"))) return true;
1172
+ const config = readTextIfExists(path8.join(configDir, "config.toml"));
1184
1173
  return config !== null && /engram-instructions\.md/.test(config);
1185
1174
  }
1186
1175
  var codexAdapter = {
@@ -1191,18 +1180,18 @@ var codexAdapter = {
1191
1180
  return !hasEngramProtocol(ctx.configDir);
1192
1181
  },
1193
1182
  paths(configDir) {
1194
- const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path9.join(HOME, ".codex"));
1195
- const agentsHome = isRealConfigDir ? HOME : path9.dirname(configDir);
1196
- const skillsDir = path9.join(agentsHome, ".agents", "skills");
1183
+ const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path8.join(HOME, ".codex"));
1184
+ const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
1185
+ const skillsDir = path8.join(agentsHome, ".agents", "skills");
1197
1186
  return {
1198
- systemPromptFile: path9.join(configDir, "AGENTS.md"),
1199
- agentsDir: path9.join(configDir, "agents"),
1187
+ systemPromptFile: path8.join(configDir, "AGENTS.md"),
1188
+ agentsDir: path8.join(configDir, "agents"),
1200
1189
  skillsDir,
1201
1190
  // Los custom prompts de Codex están deprecados: los commands se
1202
1191
  // instalan como skills (renderCommand produce <nombre>/SKILL.md).
1203
1192
  commandsDir: skillsDir,
1204
1193
  pluginsDir: null,
1205
- scriptsDir: path9.join(configDir, "scripts"),
1194
+ scriptsDir: path8.join(configDir, "scripts"),
1206
1195
  outputStylesDir: null,
1207
1196
  profilesDir: configDir
1208
1197
  };
@@ -1250,7 +1239,7 @@ ${body}`
1250
1239
  planHooks(canonical, ctx) {
1251
1240
  const actions = [];
1252
1241
  const { scriptsDir } = this.paths(ctx.configDir);
1253
- const hooksFile = path9.join(ctx.configDir, "hooks.json");
1242
+ const hooksFile = path8.join(ctx.configDir, "hooks.json");
1254
1243
  const content = upsertNativeHooks(
1255
1244
  readTextIfExists(hooksFile),
1256
1245
  canonical,
@@ -1261,16 +1250,16 @@ ${body}`
1261
1250
  ctx.warnings.push(
1262
1251
  "Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
1263
1252
  );
1264
- const scriptsSource = path9.join(ctx.stackDir, "scripts");
1253
+ const scriptsSource = path8.join(ctx.stackDir, "scripts");
1265
1254
  if (fs5.existsSync(scriptsSource)) {
1266
1255
  for (const f of fs5.readdirSync(scriptsSource)) {
1267
- actions.push({ kind: "copy", source: path9.join(scriptsSource, f), target: path9.join(scriptsDir, f) });
1256
+ actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
1268
1257
  }
1269
1258
  }
1270
1259
  return actions;
1271
1260
  },
1272
1261
  planMainConfig(canonical, ctx) {
1273
- const file = path9.join(ctx.configDir, "config.toml");
1262
+ const file = path8.join(ctx.configDir, "config.toml");
1274
1263
  const original = readTextIfExists(file);
1275
1264
  const contentSource = original === null || original.trim() === "" ? null : original;
1276
1265
  let content = contentSource;
@@ -1406,7 +1395,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1406
1395
  content = removeMarkdownSection(content, "browser");
1407
1396
  actions.push({ kind: "write", target: systemPromptFile, content });
1408
1397
  }
1409
- const configFile = path9.join(ctx.configDir, "config.toml");
1398
+ const configFile = path8.join(ctx.configDir, "config.toml");
1410
1399
  const config = readTextIfExists(configFile);
1411
1400
  if (config !== null) {
1412
1401
  let content = config;
@@ -1441,7 +1430,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1441
1430
  ...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
1442
1431
  });
1443
1432
  }
1444
- const hooksFile = path9.join(ctx.configDir, "hooks.json");
1433
+ const hooksFile = path8.join(ctx.configDir, "hooks.json");
1445
1434
  const hooksJson = readTextIfExists(hooksFile);
1446
1435
  if (hooksJson !== null) {
1447
1436
  const content = removeNativeHooks(hooksJson, hooks);
@@ -1455,7 +1444,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1455
1444
 
1456
1445
  // src/lib/install-mode.ts
1457
1446
  import fs6 from "fs";
1458
- import path10 from "path";
1447
+ import path9 from "path";
1459
1448
  var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
1460
1449
  var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
1461
1450
  var DEFAULT_INSTALL_MODE_PREFERENCE = {
@@ -1463,7 +1452,7 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
1463
1452
  subagentConcurrency: "serial"
1464
1453
  };
1465
1454
  function installModePreferenceFile() {
1466
- return path10.join(dataDir(), "install-mode.json");
1455
+ return path9.join(dataDir(), "install-mode.json");
1467
1456
  }
1468
1457
  function hasInstallModePreference(file = installModePreferenceFile()) {
1469
1458
  return fs6.existsSync(file);
@@ -1538,11 +1527,11 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
1538
1527
 
1539
1528
  // src/lib/backup.ts
1540
1529
  import fs7 from "fs";
1541
- import path11 from "path";
1530
+ import path10 from "path";
1542
1531
  import crypto from "crypto";
1543
1532
  var KEEP_BACKUPS = 10;
1544
1533
  function backupsRoot() {
1545
- return path11.join(dataDir(), "backups");
1534
+ return path10.join(dataDir(), "backups");
1546
1535
  }
1547
1536
  function compositeChecksum(files) {
1548
1537
  const hash = crypto.createHash("sha256");
@@ -1561,19 +1550,19 @@ function createBackup(files, label, root = backupsRoot()) {
1561
1550
  if (latest?.checksum === checksum) return latest;
1562
1551
  const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
1563
1552
  let id = base;
1564
- let dir = path11.join(root, id);
1553
+ let dir = path10.join(root, id);
1565
1554
  for (let n = 1; fs7.existsSync(dir); n++) {
1566
1555
  id = `${base}-${n}`;
1567
- dir = path11.join(root, id);
1556
+ dir = path10.join(root, id);
1568
1557
  }
1569
- ensureDir(path11.join(dir, "files"));
1558
+ ensureDir(path10.join(dir, "files"));
1570
1559
  const entries = existing.map((original, i) => {
1571
- const stored = path11.join(dir, "files", `${String(i).padStart(4, "0")}-${path11.basename(original)}`);
1560
+ const stored = path10.join(dir, "files", `${String(i).padStart(4, "0")}-${path10.basename(original)}`);
1572
1561
  fs7.copyFileSync(original, stored);
1573
1562
  return { original, stored };
1574
1563
  });
1575
1564
  const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
1576
- writeText(path11.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
1565
+ writeText(path10.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
1577
1566
  pruneBackups(root);
1578
1567
  return info;
1579
1568
  }
@@ -1582,7 +1571,7 @@ function listBackups(root = backupsRoot()) {
1582
1571
  const infos = [];
1583
1572
  for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
1584
1573
  if (!entry.isDirectory()) continue;
1585
- const manifest = readTextIfExists(path11.join(root, entry.name, "manifest.json"));
1574
+ const manifest = readTextIfExists(path10.join(root, entry.name, "manifest.json"));
1586
1575
  if (manifest === null) continue;
1587
1576
  try {
1588
1577
  infos.push(JSON.parse(manifest));
@@ -1599,7 +1588,7 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
1599
1588
  for (const { original, stored } of info.files) {
1600
1589
  if (!fs7.existsSync(stored)) continue;
1601
1590
  if (!isContainedIn(original, boundary)) continue;
1602
- ensureDir(path11.dirname(original));
1591
+ ensureDir(path10.dirname(original));
1603
1592
  fs7.copyFileSync(stored, original);
1604
1593
  restored++;
1605
1594
  }
@@ -1608,15 +1597,15 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
1608
1597
  function pruneBackups(root) {
1609
1598
  const all = listBackups(root);
1610
1599
  for (const old of all.slice(KEEP_BACKUPS)) {
1611
- fs7.rmSync(path11.join(root, old.id), { recursive: true, force: true });
1600
+ fs7.rmSync(path10.join(root, old.id), { recursive: true, force: true });
1612
1601
  }
1613
1602
  }
1614
1603
 
1615
1604
  // src/lib/manifest.ts
1616
1605
  import fs8 from "fs";
1617
- import path12 from "path";
1606
+ import path11 from "path";
1618
1607
  function manifestFile() {
1619
- return path12.join(dataDir(), "manifest.json");
1608
+ return path11.join(dataDir(), "manifest.json");
1620
1609
  }
1621
1610
  function readManifest(file = manifestFile()) {
1622
1611
  const raw = readTextIfExists(file);
@@ -1640,24 +1629,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
1640
1629
  writeText(file, JSON.stringify(manifest, null, 2) + "\n");
1641
1630
  }
1642
1631
  function findOrphans(prevOwned, currentTargets, root = HOME) {
1643
- return prevOwned.map((f) => path12.resolve(f)).filter(
1644
- (f) => !currentTargets.has(f) && path12.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs8.existsSync(f)
1632
+ return prevOwned.map((f) => path11.resolve(f)).filter(
1633
+ (f) => !currentTargets.has(f) && path11.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs8.existsSync(f)
1645
1634
  );
1646
1635
  }
1647
1636
 
1648
1637
  // src/components/system-prompt.ts
1649
- import path14 from "path";
1638
+ import path13 from "path";
1650
1639
  import fs10 from "fs";
1651
1640
 
1652
1641
  // src/lib/mode-composition.ts
1653
1642
  import fs9 from "fs";
1654
- import path13 from "path";
1643
+ import path12 from "path";
1655
1644
  var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
1656
1645
  var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
1657
1646
  var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
1658
1647
  var normalize = (value) => value.replace(/\r\n/g, "\n");
1659
1648
  function loadProgrammaticAddendum(stackDir, fileName) {
1660
- return normalize(fs9.readFileSync(path13.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
1649
+ return normalize(fs9.readFileSync(path12.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
1661
1650
  }
1662
1651
  function appendAddendum(base, addendum) {
1663
1652
  const normalizedBase = normalize(base);
@@ -1700,15 +1689,15 @@ function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
1700
1689
  var normalize2 = (s) => s.replace(/\r\n/g, "\n");
1701
1690
  function planSystemPrompt(adapter, ctx) {
1702
1691
  const target = adapter.paths(ctx.configDir).systemPromptFile;
1703
- const agentsMd = normalize2(fs10.readFileSync(path14.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
1692
+ const agentsMd = normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
1704
1693
  const protocol = stripLeadingHtmlComments(
1705
- normalize2(fs10.readFileSync(path14.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
1694
+ normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
1706
1695
  );
1707
1696
  const composedAgentsMd = composeProgrammaticSystemPrompt(ctx.stackDir, agentsMd, ctx.mode);
1708
1697
  const browser = [
1709
1698
  ctx.playwrightCliEnabled ? "browser-playwright.md" : null,
1710
1699
  ctx.enabledMcpServers?.has(DEVTOOLS_MCP_SERVER) ? "browser-chrome-devtools.md" : null
1711
- ].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(path14.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
1700
+ ].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
1712
1701
  let content = readTextIfExists(target);
1713
1702
  content = upsertMarkdownSection(content, "system-prompt", composedAgentsMd);
1714
1703
  if (adapter.injectEngramProtocol(ctx)) {
@@ -1721,7 +1710,7 @@ function planSystemPrompt(adapter, ctx) {
1721
1710
  }
1722
1711
 
1723
1712
  // src/components/agents.ts
1724
- import path15 from "path";
1713
+ import path14 from "path";
1725
1714
  function planAgents(adapter, ctx) {
1726
1715
  const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
1727
1716
  const dirFor = {
@@ -1731,7 +1720,7 @@ function planAgents(adapter, ctx) {
1731
1720
  profile: profilesDir
1732
1721
  };
1733
1722
  const scriptsBase = scriptsDir.replace(/\\/g, "/");
1734
- return loadCanonicalAgents(path15.join(ctx.stackDir, "agents")).flatMap((agent) => {
1723
+ return loadCanonicalAgents(path14.join(ctx.stackDir, "agents")).flatMap((agent) => {
1735
1724
  const composedAgent = {
1736
1725
  ...agent,
1737
1726
  body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
@@ -1743,42 +1732,42 @@ function planAgents(adapter, ctx) {
1743
1732
  return [];
1744
1733
  }
1745
1734
  const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
1746
- return [{ kind: "write", target: path15.join(dir, rendered.file), content }];
1735
+ return [{ kind: "write", target: path14.join(dir, rendered.file), content }];
1747
1736
  });
1748
1737
  });
1749
1738
  }
1750
1739
 
1751
1740
  // src/components/skills.ts
1752
- import path16 from "path";
1741
+ import path15 from "path";
1753
1742
  function planSkills(adapter, ctx) {
1754
1743
  const { skillsDir } = adapter.paths(ctx.configDir);
1755
- const source = path16.join(ctx.stackDir, "skills");
1744
+ const source = path15.join(ctx.stackDir, "skills");
1756
1745
  return listFilesRecursive(source).map((file) => {
1757
- const relative = path16.relative(source, file);
1758
- return { kind: "copy", source: file, target: path16.join(skillsDir, relative) };
1746
+ const relative = path15.relative(source, file);
1747
+ return { kind: "copy", source: file, target: path15.join(skillsDir, relative) };
1759
1748
  });
1760
1749
  }
1761
1750
 
1762
1751
  // src/components/commands.ts
1763
- import path17 from "path";
1752
+ import path16 from "path";
1764
1753
  import fs11 from "fs";
1765
1754
  function planCommands(adapter, ctx) {
1766
1755
  const { commandsDir } = adapter.paths(ctx.configDir);
1767
- const source = path17.join(ctx.stackDir, "commands");
1756
+ const source = path16.join(ctx.stackDir, "commands");
1768
1757
  if (!fs11.existsSync(source)) return [];
1769
1758
  const commandFiles = [
1770
1759
  ...listMarkdownFiles(source),
1771
- ...listMarkdownFiles(path17.join(source, adapter.id))
1760
+ ...listMarkdownFiles(path16.join(source, adapter.id))
1772
1761
  ];
1773
1762
  return commandFiles.map(({ file, fullPath }) => {
1774
1763
  const raw = fs11.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
1775
1764
  const rendered = adapter.renderCommand(file, raw);
1776
- return { kind: "write", target: path17.join(commandsDir, rendered.file), content: rendered.content };
1765
+ return { kind: "write", target: path16.join(commandsDir, rendered.file), content: rendered.content };
1777
1766
  });
1778
1767
  }
1779
1768
  function listMarkdownFiles(dir) {
1780
1769
  if (!fs11.existsSync(dir)) return [];
1781
- return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path17.join(dir, entry.name) }));
1770
+ return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path16.join(dir, entry.name) }));
1782
1771
  }
1783
1772
 
1784
1773
  // src/components/hooks.ts
@@ -1792,15 +1781,15 @@ function planMcp(adapter, ctx) {
1792
1781
  }
1793
1782
 
1794
1783
  // src/components/plugins.ts
1795
- import path18 from "path";
1784
+ import path17 from "path";
1796
1785
  import fs12 from "fs";
1797
1786
  function planPlugins(adapter, ctx) {
1798
1787
  const { pluginsDir } = adapter.paths(ctx.configDir);
1799
1788
  if (pluginsDir === null) return [];
1800
- const source = path18.join(ctx.stackDir, "plugins", adapter.id);
1789
+ const source = path17.join(ctx.stackDir, "plugins", adapter.id);
1801
1790
  if (!fs12.existsSync(source)) return [];
1802
1791
  return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
1803
- const target = path18.join(pluginsDir, path18.relative(source, sourceFile));
1792
+ const target = path17.join(pluginsDir, path17.relative(source, sourceFile));
1804
1793
  const raw = fs12.readFileSync(sourceFile, "utf8");
1805
1794
  let content = raw;
1806
1795
  if (content.includes('"{{ENGRAM_BIN}}"')) {
@@ -1808,7 +1797,7 @@ function planPlugins(adapter, ctx) {
1808
1797
  }
1809
1798
  if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
1810
1799
  const protocol = stripLeadingHtmlComments(
1811
- fs12.readFileSync(path18.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
1800
+ fs12.readFileSync(path17.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
1812
1801
  );
1813
1802
  content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
1814
1803
  }
@@ -1820,8 +1809,8 @@ function planPlugins(adapter, ctx) {
1820
1809
 
1821
1810
  // src/lib/external-tools.ts
1822
1811
  import fs13 from "fs";
1823
- import os2 from "os";
1824
- import path19 from "path";
1812
+ import os from "os";
1813
+ import path18 from "path";
1825
1814
  import { execFileSync as execFileSync2 } from "child_process";
1826
1815
  var PLAYWRIGHT_CLI = {
1827
1816
  packageName: "@playwright/cli",
@@ -1872,9 +1861,9 @@ function resolvePnpmBin() {
1872
1861
  const pnpmCmd = lookPath("pnpm.cmd");
1873
1862
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
1874
1863
  }
1875
- function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os2.homedir()) {
1864
+ function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
1876
1865
  const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
1877
- const cacheDir = configuredPath ?? (platform === "win32" ? path19.join(env.LOCALAPPDATA ?? path19.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path19.join(homeDir, "Library", "Caches", "ms-playwright") : path19.join(env.XDG_CACHE_HOME ?? path19.join(homeDir, ".cache"), "ms-playwright"));
1866
+ const cacheDir = configuredPath ?? (platform === "win32" ? path18.join(env.LOCALAPPDATA ?? path18.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path18.join(homeDir, "Library", "Caches", "ms-playwright") : path18.join(env.XDG_CACHE_HOME ?? path18.join(homeDir, ".cache"), "ms-playwright"));
1878
1867
  if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
1879
1868
  try {
1880
1869
  const ready = fs13.readdirSync(cacheDir, { withFileTypes: true }).some(
@@ -1922,7 +1911,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin()) {
1922
1911
 
1923
1912
  // src/lib/tool-preferences.ts
1924
1913
  import fs14 from "fs";
1925
- import path20 from "path";
1914
+ import path19 from "path";
1926
1915
  var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
1927
1916
  var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
1928
1917
  var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
@@ -1935,7 +1924,7 @@ function readPreference(file) {
1935
1924
  }
1936
1925
  }
1937
1926
  function playwrightCliPreferenceFile(stateDir = dataDir()) {
1938
- return path20.join(stateDir, "playwright-cli.json");
1927
+ return path19.join(stateDir, "playwright-cli.json");
1939
1928
  }
1940
1929
  function parsePlaywrightCliPreference(raw) {
1941
1930
  try {
@@ -1965,10 +1954,10 @@ function savePlaywrightCliPreference(file, enabled) {
1965
1954
  writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
1966
1955
  }
1967
1956
  function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
1968
- return path20.join(stateDir, "devtools-mcp.json");
1957
+ return path19.join(stateDir, "devtools-mcp.json");
1969
1958
  }
1970
1959
  function primaryModelOwnershipFile(stateDir = dataDir()) {
1971
- return path20.join(stateDir, "primary-model.json");
1960
+ return path19.join(stateDir, "primary-model.json");
1972
1961
  }
1973
1962
  function isRecord(value) {
1974
1963
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -2078,7 +2067,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
2078
2067
  return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
2079
2068
  }
2080
2069
  function primaryModelConfigKey(configDir) {
2081
- const resolved = path20.resolve(configDir);
2070
+ const resolved = path19.resolve(configDir);
2082
2071
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
2083
2072
  }
2084
2073
  function loadPrimaryModelOwnership(file, runtime, configDir) {
@@ -2224,7 +2213,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2224
2213
  continue;
2225
2214
  }
2226
2215
  try {
2227
- for (const action of buildPlan(adapter, ctx)) targets.add(path21.resolve(action.target));
2216
+ for (const action of buildPlan(adapter, ctx)) targets.add(path20.resolve(action.target));
2228
2217
  } catch (error) {
2229
2218
  complete = false;
2230
2219
  warnings.push(
@@ -2326,10 +2315,10 @@ async function runInstall(opts) {
2326
2315
  }
2327
2316
  const writeManifest = () => {
2328
2317
  if (!useManifest) return;
2329
- const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path21.resolve(a.target)));
2318
+ const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path20.resolve(a.target)));
2330
2319
  const keepTarget = (target) => !unmergeTargets.has(target);
2331
- const liveOwned = plan.map((a) => path21.resolve(a.target)).filter(keepTarget);
2332
- const previousOwned = (prevManifest?.owned ?? []).map((target) => path21.resolve(target)).filter(keepTarget);
2320
+ const liveOwned = plan.map((a) => path20.resolve(a.target)).filter(keepTarget);
2321
+ const previousOwned = (prevManifest?.owned ?? []).map((target) => path20.resolve(target)).filter(keepTarget);
2333
2322
  const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
2334
2323
  writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
2335
2324
  };
@@ -2358,7 +2347,7 @@ async function runInstall(opts) {
2358
2347
  const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
2359
2348
  if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
2360
2349
  applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
2361
- const pruneRoot = useManifest ? HOME : path21.dirname(configDir);
2350
+ const pruneRoot = useManifest ? HOME : path20.dirname(configDir);
2362
2351
  for (const orphan of orphans) {
2363
2352
  fs15.rmSync(orphan, { force: true });
2364
2353
  pruneEmptyDirs(orphan, pruneRoot);
@@ -2443,27 +2432,27 @@ async function runInstall(opts) {
2443
2432
 
2444
2433
  // src/uninstall.ts
2445
2434
  import fs17 from "fs";
2446
- import path25 from "path";
2435
+ import path24 from "path";
2447
2436
  import * as p2 from "@clack/prompts";
2448
2437
 
2449
2438
  // src/lib/pi-projection-lifecycle.ts
2450
2439
  import fs16 from "fs";
2451
- import path24 from "path";
2440
+ import path23 from "path";
2452
2441
 
2453
2442
  // src/adapters/pi.ts
2454
- import path22 from "path";
2443
+ import path21 from "path";
2455
2444
  var piAdapter = {
2456
2445
  id: "pi",
2457
2446
  paths(configDir) {
2458
- const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path22.join(HOME, ".pi", "agent");
2459
- const agentsHome = samePath(configDir, piConfigDir) ? HOME : path22.join(path22.dirname(configDir), "home");
2447
+ const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path21.join(HOME, ".pi", "agent");
2448
+ const agentsHome = samePath(configDir, piConfigDir) ? HOME : path21.join(path21.dirname(configDir), "home");
2460
2449
  return {
2461
- systemPromptFile: path22.join(configDir, "AGENTS.md"),
2462
- agentsDir: path22.join(configDir, "agents"),
2463
- skillsDir: path22.join(agentsHome, ".agents", "skills"),
2464
- commandsDir: path22.join(configDir, "prompts"),
2450
+ systemPromptFile: path21.join(configDir, "AGENTS.md"),
2451
+ agentsDir: path21.join(configDir, "agents"),
2452
+ skillsDir: path21.join(agentsHome, ".agents", "skills"),
2453
+ commandsDir: path21.join(configDir, "prompts"),
2465
2454
  pluginsDir: null,
2466
- scriptsDir: path22.join(configDir, "scripts"),
2455
+ scriptsDir: path21.join(configDir, "scripts"),
2467
2456
  outputStylesDir: null,
2468
2457
  profilesDir: null
2469
2458
  };
@@ -2477,7 +2466,7 @@ var piAdapter = {
2477
2466
  };
2478
2467
 
2479
2468
  // src/lib/pi-package-lifecycle.ts
2480
- import path23 from "path";
2469
+ import path22 from "path";
2481
2470
  var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
2482
2471
  "foundation-contract-v1",
2483
2472
  "runner-json-v1",
@@ -2609,7 +2598,7 @@ function parseReceiptShape(receiptJson) {
2609
2598
  return null;
2610
2599
  }
2611
2600
  if (engram === void 0) return "upgrade-required";
2612
- if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path23.isAbsolute(Reflect.get(engram, "binary"))) {
2601
+ if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path22.isAbsolute(Reflect.get(engram, "binary"))) {
2613
2602
  return null;
2614
2603
  }
2615
2604
  return parsed;
@@ -2646,7 +2635,7 @@ function planPiPackageLifecycle(input) {
2646
2635
  if (input.receiptJson !== null) {
2647
2636
  const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
2648
2637
  kind: input.scope.kind,
2649
- codingAgentDir: path23.resolve(input.scope.codingAgentDir)
2638
+ codingAgentDir: path22.resolve(input.scope.codingAgentDir)
2650
2639
  }, input.engramBin);
2651
2640
  if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
2652
2641
  if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
@@ -2681,7 +2670,7 @@ function planPiPackageLifecycle(input) {
2681
2670
  },
2682
2671
  receipt: expectedReceipt(input.candidate, "installing", {
2683
2672
  kind: input.scope.kind,
2684
- codingAgentDir: path23.resolve(input.scope.codingAgentDir)
2673
+ codingAgentDir: path22.resolve(input.scope.codingAgentDir)
2685
2674
  }, input.engramBin),
2686
2675
  ownership: ownership(true)
2687
2676
  };
@@ -2696,7 +2685,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
2696
2685
  const parsed = JSON.parse(body);
2697
2686
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2698
2687
  const record = parsed;
2699
- if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path23.isAbsolute(record.package.root) || path23.resolve(packageRunner) !== path23.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
2688
+ if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path22.isAbsolute(record.package.root) || path22.resolve(packageRunner) !== path22.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
2700
2689
  return null;
2701
2690
  }
2702
2691
  return record;
@@ -2782,10 +2771,10 @@ function validateOwnedOperationState(input) {
2782
2771
  }))) {
2783
2772
  return { kind: "blocked", reason: "receipt-untrusted" };
2784
2773
  }
2785
- if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path23.resolve(receipt.scope.codingAgentDir) !== path23.resolve(input.paths.codingAgentDir)) {
2774
+ if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path22.resolve(receipt.scope.codingAgentDir) !== path22.resolve(input.paths.codingAgentDir)) {
2786
2775
  return { kind: "blocked", reason: "source-divergent" };
2787
2776
  }
2788
- if (input.engramBin !== null && path23.resolve(receipt.engram.binary) !== path23.resolve(input.engramBin)) {
2777
+ if (input.engramBin !== null && path22.resolve(receipt.engram.binary) !== path22.resolve(input.engramBin)) {
2789
2778
  return { kind: "blocked", reason: "receipt-corrupt" };
2790
2779
  }
2791
2780
  const source = receipt.candidate.package.source;
@@ -2882,18 +2871,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
2882
2871
  function projectionScope(scope) {
2883
2872
  return {
2884
2873
  ...scope,
2885
- settingsFile: path24.join(scope.codingAgentDir, "settings.json")
2874
+ settingsFile: path23.join(scope.codingAgentDir, "settings.json")
2886
2875
  };
2887
2876
  }
2888
2877
  function isInside(root, file) {
2889
- const relative = path24.relative(path24.resolve(root), path24.resolve(file));
2890
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path24.sep}`) && !path24.isAbsolute(relative);
2878
+ const relative = path23.relative(path23.resolve(root), path23.resolve(file));
2879
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path23.sep}`) && !path23.isAbsolute(relative);
2891
2880
  }
2892
2881
  function isManagedPath(scope, file) {
2893
2882
  return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
2894
2883
  }
2895
2884
  function isAllowedPath(scope, file) {
2896
- return isManagedPath(scope, file) || path24.resolve(file) === path24.resolve(scope.receiptFile);
2885
+ return isManagedPath(scope, file) || path23.resolve(file) === path23.resolve(scope.receiptFile);
2897
2886
  }
2898
2887
  function projectedPiAdapter(scope) {
2899
2888
  const paths = piAdapter.paths(scope.codingAgentDir);
@@ -2901,7 +2890,7 @@ function projectedPiAdapter(scope) {
2901
2890
  ...piAdapter,
2902
2891
  paths: () => ({
2903
2892
  ...paths,
2904
- skillsDir: path24.join(scope.home, ".agents", "skills")
2893
+ skillsDir: path23.join(scope.home, ".agents", "skills")
2905
2894
  })
2906
2895
  };
2907
2896
  }
@@ -2941,9 +2930,9 @@ function assertPlanContained(plan, scope) {
2941
2930
  }
2942
2931
  }
2943
2932
  function receiptFor(plan, scope) {
2944
- const systemPrompt = path24.join(scope.codingAgentDir, "AGENTS.md");
2933
+ const systemPrompt = path23.join(scope.codingAgentDir, "AGENTS.md");
2945
2934
  const owned = [...new Set(
2946
- plan.map((action) => path24.resolve(action.target)).filter((target) => target !== systemPrompt)
2935
+ plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
2947
2936
  )];
2948
2937
  return {
2949
2938
  schemaVersion: 1,
@@ -2995,8 +2984,8 @@ function realPiProjectionScope() {
2995
2984
  return {
2996
2985
  kind: "real",
2997
2986
  home: HOME,
2998
- codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path24.join(HOME, ".pi", "agent"),
2999
- receiptFile: path24.join(dataDir(), "pi-projection-receipt.json")
2987
+ codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path23.join(HOME, ".pi", "agent"),
2988
+ receiptFile: path23.join(dataDir(), "pi-projection-receipt.json")
3000
2989
  };
3001
2990
  }
3002
2991
  function readTextOnlyIfMissing(file) {
@@ -3034,13 +3023,13 @@ function manifestOwned(manifest) {
3034
3023
  return new Set([
3035
3024
  ...manifest.runtimes.codex?.owned ?? [],
3036
3025
  ...manifest.runtimes.opencode?.owned ?? []
3037
- ].map((file) => path24.resolve(file)));
3026
+ ].map((file) => path23.resolve(file)));
3038
3027
  }
3039
3028
  function withoutManagedPromptSections(content) {
3040
3029
  return ["system-prompt", "engram-protocol", "browser"].reduce((current, section) => removeMarkdownSection(current, section), content);
3041
3030
  }
3042
3031
  function uniquePaths(paths) {
3043
- return [...new Set(paths.map((file) => path24.resolve(file)))];
3032
+ return [...new Set(paths.map((file) => path23.resolve(file)))];
3044
3033
  }
3045
3034
  function blocked2(reason, paths, remedy) {
3046
3035
  return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
@@ -3113,7 +3102,7 @@ function preparePiProjectionUninstall(input, deps) {
3113
3102
  }
3114
3103
  const scope = projectionScope(input.scope);
3115
3104
  const expectedReceipt2 = expectedProjectionReceipt(input, scope);
3116
- const prompt = path24.join(scope.codingAgentDir, "AGENTS.md");
3105
+ const prompt = path23.join(scope.codingAgentDir, "AGENTS.md");
3117
3106
  let existingPrompt;
3118
3107
  try {
3119
3108
  existingPrompt = deps.readText(prompt);
@@ -3121,7 +3110,7 @@ function preparePiProjectionUninstall(input, deps) {
3121
3110
  return cleanupFailure([prompt]);
3122
3111
  }
3123
3112
  const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
3124
- const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path24.resolve(prompt), content: promptContent };
3113
+ const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path23.resolve(prompt), content: promptContent };
3125
3114
  const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
3126
3115
  if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3127
3116
  let ownedToRemove = [];
@@ -3137,7 +3126,7 @@ function preparePiProjectionUninstall(input, deps) {
3137
3126
  return cleanupFailure([scope.receiptFile]);
3138
3127
  }
3139
3128
  ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
3140
- receiptFile = path24.resolve(scope.receiptFile);
3129
+ receiptFile = path23.resolve(scope.receiptFile);
3141
3130
  backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
3142
3131
  }
3143
3132
  const backup = backupExisting(backupTargets, deps);
@@ -3193,7 +3182,7 @@ function runPiProjectionLifecycle(input, deps) {
3193
3182
  const expectedReceipt2 = receiptContent(receipt);
3194
3183
  const drifted = plan.filter((action) => hasActionDrift(action, deps));
3195
3184
  if (input.operation === "doctor") {
3196
- const paths = drifted.map((action) => path24.resolve(action.target));
3185
+ const paths = drifted.map((action) => path23.resolve(action.target));
3197
3186
  const currentSettings2 = deps.readText(scope.settingsFile);
3198
3187
  if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
3199
3188
  paths.push(scope.settingsFile);
@@ -3224,12 +3213,12 @@ function runPiProjectionLifecycle(input, deps) {
3224
3213
  return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged };
3225
3214
  }
3226
3215
  function systemProjectionLifecycle(input) {
3227
- const targetRoot = input.targetDir === void 0 ? null : path24.resolve(input.targetDir);
3216
+ const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
3228
3217
  const scope = targetRoot === null ? realPiProjectionScope() : {
3229
3218
  kind: "target-dir",
3230
- home: path24.join(targetRoot, "home"),
3231
- codingAgentDir: path24.join(targetRoot, "pi-agent"),
3232
- receiptFile: path24.join(targetRoot, "state", "pi-projection-receipt.json")
3219
+ home: path23.join(targetRoot, "home"),
3220
+ codingAgentDir: path23.join(targetRoot, "pi-agent"),
3221
+ receiptFile: path23.join(targetRoot, "state", "pi-projection-receipt.json")
3233
3222
  };
3234
3223
  return {
3235
3224
  input: {
@@ -3245,7 +3234,7 @@ function systemProjectionLifecycle(input) {
3245
3234
  backup: (paths) => createBackup(
3246
3235
  paths,
3247
3236
  `pi-projection-${input.operation}`,
3248
- targetRoot === null ? void 0 : path24.join(targetRoot, "backups")
3237
+ targetRoot === null ? void 0 : path23.join(targetRoot, "backups")
3249
3238
  ),
3250
3239
  writeText,
3251
3240
  copyFile,
@@ -3326,7 +3315,7 @@ async function runUninstall(opts) {
3326
3315
  if (!detection.installed) continue;
3327
3316
  const keepCtx = makeContext(keep, detection.configDir);
3328
3317
  if (!keepCtx) continue;
3329
- for (const action of buildPlan(keep, keepCtx)) retained.add(path25.resolve(action.target));
3318
+ for (const action of buildPlan(keep, keepCtx)) retained.add(path24.resolve(action.target));
3330
3319
  }
3331
3320
  }
3332
3321
  for (const id of opts.runtimes) {
@@ -3345,15 +3334,15 @@ async function runUninstall(opts) {
3345
3334
  if (!ctx) continue;
3346
3335
  ctx.preserveEngram = !removeEngram;
3347
3336
  const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
3348
- const mergedTargets = new Set(unmerge.map((a) => path25.resolve(a.target)));
3337
+ const mergedTargets = new Set(unmerge.map((a) => path24.resolve(a.target)));
3349
3338
  const usingRealConfig = opts.targetDir === void 0;
3350
3339
  const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
3351
- const pruneRoot = usingRealConfig ? HOME : path25.dirname(configDir);
3340
+ const pruneRoot = usingRealConfig ? HOME : path24.dirname(configDir);
3352
3341
  const planTargets = [
3353
- .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path25.resolve(a.target)), ...prevOwned.map((t) => path25.resolve(t))])
3342
+ .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path24.resolve(a.target)), ...prevOwned.map((t) => path24.resolve(t))])
3354
3343
  ].filter((t) => !mergedTargets.has(t) && fs17.existsSync(t));
3355
3344
  const deleteTargets = planTargets.filter(
3356
- (t) => !retained.has(t) && !(ctx.preserveEngram && path25.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
3345
+ (t) => !retained.has(t) && !(ctx.preserveEngram && path24.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
3357
3346
  );
3358
3347
  const sharedKept = planTargets.length - deleteTargets.length;
3359
3348
  p2.log.step(`${adapter.name} \u2192 ${configDir}`);
@@ -3433,7 +3422,7 @@ async function runUninstall(opts) {
3433
3422
  }
3434
3423
 
3435
3424
  // src/doctor.ts
3436
- import path26 from "path";
3425
+ import path25 from "path";
3437
3426
  import fs18 from "fs";
3438
3427
  import * as p3 from "@clack/prompts";
3439
3428
  function engramVersion(bin) {
@@ -3453,7 +3442,7 @@ function resolvePlaywrightDoctorState(input) {
3453
3442
  return { status: "healthy" };
3454
3443
  }
3455
3444
  function context7KeyConfigured(id, configDir) {
3456
- const file = id === "codex" ? path26.join(configDir, "config.toml") : id === "claude-code" ? path26.join(path26.dirname(configDir), `${path26.basename(configDir)}.json`) : path26.join(configDir, "opencode.json");
3445
+ const file = id === "codex" ? path25.join(configDir, "config.toml") : id === "claude-code" ? path25.join(path25.dirname(configDir), `${path25.basename(configDir)}.json`) : path25.join(configDir, "opencode.json");
3457
3446
  const content = readTextIfExists(file);
3458
3447
  if (content === null) return null;
3459
3448
  const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
@@ -3476,8 +3465,8 @@ async function runDoctor() {
3476
3465
  p3.log.success(`Engram: ${version} (${engramBin})`);
3477
3466
  }
3478
3467
  }
3479
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path26.join(HOME, ".engram");
3480
- const engramDb = path26.join(engramDataDir, "engram.db");
3468
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path25.join(HOME, ".engram");
3469
+ const engramDb = path25.join(engramDataDir, "engram.db");
3481
3470
  if (fs18.existsSync(engramDb)) {
3482
3471
  const sizeMb = (fs18.statSync(engramDb).size / 1024 / 1024).toFixed(1);
3483
3472
  p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
@@ -3557,10 +3546,10 @@ async function runDoctor() {
3557
3546
  p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
3558
3547
  problems++;
3559
3548
  }
3560
- if (adapter.id === "codex" && fs18.existsSync(path26.join(detection.configDir, "hooks.json"))) {
3549
+ if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "hooks.json"))) {
3561
3550
  p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
3562
3551
  }
3563
- if (adapter.id === "codex" && fs18.existsSync(path26.join(detection.configDir, "AGENTS.override.md"))) {
3552
+ if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "AGENTS.override.md"))) {
3564
3553
  p3.log.warn(
3565
3554
  "Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
3566
3555
  );
@@ -3575,16 +3564,16 @@ async function runDoctor() {
3575
3564
 
3576
3565
  // src/update.ts
3577
3566
  import fs21 from "fs";
3578
- import path29 from "path";
3579
- import os4 from "os";
3567
+ import path28 from "path";
3568
+ import os3 from "os";
3580
3569
  import { execFileSync as execFileSync5 } from "child_process";
3581
3570
  import * as p4 from "@clack/prompts";
3582
3571
 
3583
3572
  // src/lib/github.ts
3584
3573
  import fs19 from "fs";
3585
- import path27 from "path";
3574
+ import path26 from "path";
3586
3575
  import { execFileSync as execFileSync3 } from "child_process";
3587
- import os3 from "os";
3576
+ import os2 from "os";
3588
3577
  import { Readable } from "stream";
3589
3578
  import { pipeline } from "stream/promises";
3590
3579
  var cachedToken;
@@ -3652,7 +3641,7 @@ async function latestGithubCommit(repo) {
3652
3641
  }
3653
3642
  }
3654
3643
  function validateExtractedTree(destDir) {
3655
- const resolved = path27.resolve(destDir);
3644
+ const resolved = path26.resolve(destDir);
3656
3645
  const walk = (dir) => {
3657
3646
  let entries;
3658
3647
  try {
@@ -3661,7 +3650,7 @@ function validateExtractedTree(destDir) {
3661
3650
  return false;
3662
3651
  }
3663
3652
  for (const entry of entries) {
3664
- const full = path27.join(dir, entry.name);
3653
+ const full = path26.join(dir, entry.name);
3665
3654
  let stat;
3666
3655
  try {
3667
3656
  stat = fs19.lstatSync(full);
@@ -3680,12 +3669,12 @@ function validateExtractedTree(destDir) {
3680
3669
  }
3681
3670
  function resolveTarBin() {
3682
3671
  if (process.platform !== "win32") return "tar";
3683
- const winTar = path27.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
3672
+ const winTar = path26.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
3684
3673
  return fs19.existsSync(winTar) ? winTar : "tar";
3685
3674
  }
3686
3675
  async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
3687
3676
  const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
3688
- const tmp = path27.join(os3.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
3677
+ const tmp = path26.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
3689
3678
  const fail = (reason) => {
3690
3679
  try {
3691
3680
  fs19.rmSync(destDir, { recursive: true, force: true });
@@ -3718,8 +3707,8 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
3718
3707
  const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
3719
3708
  return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
3720
3709
  }
3721
- const resolvedDest = path27.resolve(destDir);
3722
- const validateRoot = validateSubdir ? path27.resolve(resolvedDest, validateSubdir) : resolvedDest;
3710
+ const resolvedDest = path26.resolve(destDir);
3711
+ const validateRoot = validateSubdir ? path26.resolve(resolvedDest, validateSubdir) : resolvedDest;
3723
3712
  if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
3724
3713
  return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
3725
3714
  }
@@ -3741,7 +3730,7 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
3741
3730
 
3742
3731
  // src/lib/skill-update.ts
3743
3732
  import fs20 from "fs";
3744
- import path28 from "path";
3733
+ import path27 from "path";
3745
3734
  import { execFileSync as execFileSync4 } from "child_process";
3746
3735
  var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
3747
3736
  "agent-delegation",
@@ -3760,10 +3749,10 @@ function sameTextContentNormalized(a, b) {
3760
3749
  }
3761
3750
  function diffSkillDirs(upstreamDir, localDir) {
3762
3751
  const upstreamFiles = new Set(
3763
- listFilesRecursive(upstreamDir).map((f) => path28.relative(upstreamDir, f))
3752
+ listFilesRecursive(upstreamDir).map((f) => path27.relative(upstreamDir, f))
3764
3753
  );
3765
3754
  const localFiles = new Set(
3766
- listFilesRecursive(localDir).map((f) => path28.relative(localDir, f))
3755
+ listFilesRecursive(localDir).map((f) => path27.relative(localDir, f))
3767
3756
  );
3768
3757
  const added = [];
3769
3758
  const modified = [];
@@ -3771,7 +3760,7 @@ function diffSkillDirs(upstreamDir, localDir) {
3771
3760
  for (const rel of upstreamFiles) {
3772
3761
  if (!localFiles.has(rel)) {
3773
3762
  added.push(rel);
3774
- } else if (!sameTextContentNormalized(path28.join(upstreamDir, rel), path28.join(localDir, rel))) {
3763
+ } else if (!sameTextContentNormalized(path27.join(upstreamDir, rel), path27.join(localDir, rel))) {
3775
3764
  modified.push(rel);
3776
3765
  }
3777
3766
  }
@@ -3833,7 +3822,7 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
3833
3822
  if (PROTECTED_SKILLS.has(name)) {
3834
3823
  throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
3835
3824
  }
3836
- const upstreamsFile = upstreamsFilePath ?? path28.join(path28.dirname(stackRoot()), "upstreams.json");
3825
+ const upstreamsFile = upstreamsFilePath ?? path27.join(path27.dirname(stackRoot()), "upstreams.json");
3837
3826
  const raw = fs20.readFileSync(upstreamsFile, "utf8");
3838
3827
  const data = JSON.parse(raw);
3839
3828
  const skillEntry = data?.skills?.[name];
@@ -3843,8 +3832,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
3843
3832
  if (skillEntry.kind === "release") {
3844
3833
  throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
3845
3834
  }
3846
- const skillsRoot = localSkillsRoot ?? path28.join(stackRoot(), "skills");
3847
- const localSkillDir = path28.join(skillsRoot, name);
3835
+ const skillsRoot = localSkillsRoot ?? path27.join(stackRoot(), "skills");
3836
+ const localSkillDir = path27.join(skillsRoot, name);
3848
3837
  const localFiles = listFilesRecursive(localSkillDir);
3849
3838
  if (localFiles.length > 0) {
3850
3839
  createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
@@ -3857,9 +3846,9 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
3857
3846
  if (st.isSymbolicLink()) {
3858
3847
  throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
3859
3848
  }
3860
- const rel = path28.relative(upstreamSkillDir, src);
3861
- const dest = path28.join(stagingDir, rel);
3862
- ensureDir(path28.dirname(dest));
3849
+ const rel = path27.relative(upstreamSkillDir, src);
3850
+ const dest = path27.join(stagingDir, rel);
3851
+ ensureDir(path27.dirname(dest));
3863
3852
  copyFile(src, dest);
3864
3853
  }
3865
3854
  const oldDir = `${localSkillDir}.old-${process.pid}`;
@@ -3884,7 +3873,7 @@ function rateLimitHint(prefix) {
3884
3873
  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.`;
3885
3874
  }
3886
3875
  function loadUpstreams() {
3887
- const file = path29.join(path29.dirname(stackRoot()), "upstreams.json");
3876
+ const file = path28.join(path28.dirname(stackRoot()), "upstreams.json");
3888
3877
  return JSON.parse(fs21.readFileSync(file, "utf8"));
3889
3878
  }
3890
3879
  function skillsToScan(maintainer, upstreams) {
@@ -4034,8 +4023,8 @@ function isEngramRunning() {
4034
4023
  return null;
4035
4024
  }
4036
4025
  }
4037
- function isGitClone(projectRoot = path29.dirname(stackRoot())) {
4038
- return fs21.existsSync(path29.join(projectRoot, ".git"));
4026
+ function isGitClone(projectRoot = path28.dirname(stackRoot())) {
4027
+ return fs21.existsSync(path28.join(projectRoot, ".git"));
4039
4028
  }
4040
4029
  var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
4041
4030
  function resolvePnpm() {
@@ -4050,7 +4039,7 @@ function cleanupTmp(dir) {
4050
4039
  }
4051
4040
  }
4052
4041
  function updateStackGitClone() {
4053
- const projectRoot = path29.dirname(stackRoot());
4042
+ const projectRoot = path28.dirname(stackRoot());
4054
4043
  const git = lookPath("git");
4055
4044
  if (!git) throw new Error("git no encontrado en PATH.");
4056
4045
  const pnpm = resolvePnpm();
@@ -4067,7 +4056,7 @@ function updateStackGlobal() {
4067
4056
  execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
4068
4057
  }
4069
4058
  async function downloadSkillToTemp(repo, sha, skillPath) {
4070
- const root = fs21.mkdtempSync(path29.join(os4.tmpdir(), "jorgex-skill-"));
4059
+ const root = fs21.mkdtempSync(path28.join(os3.tmpdir(), "jorgex-skill-"));
4071
4060
  try {
4072
4061
  const result = await downloadRepoTarball(repo, sha, root, skillPath);
4073
4062
  if (!result.ok) {
@@ -4075,14 +4064,14 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
4075
4064
  return { error: result.reason };
4076
4065
  }
4077
4066
  if (skillPath) {
4078
- const sub = path29.resolve(path29.join(root, skillPath));
4067
+ const sub = path28.resolve(path28.join(root, skillPath));
4079
4068
  if (!isContainedIn(sub, root)) {
4080
4069
  cleanupTmp(root);
4081
4070
  return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
4082
4071
  }
4083
4072
  if (fs21.existsSync(sub)) return { dir: sub, root };
4084
4073
  const lastSeg = skillPath.split("/").pop();
4085
- const sub2 = path29.resolve(path29.join(root, lastSeg));
4074
+ const sub2 = path28.resolve(path28.join(root, lastSeg));
4086
4075
  if (isContainedIn(sub2, root) && fs21.existsSync(sub2)) {
4087
4076
  if (!validateExtractedTree(sub2)) {
4088
4077
  cleanupTmp(root);
@@ -4103,10 +4092,10 @@ function pruneEngramDbBackups() {
4103
4092
  try {
4104
4093
  const dir = dataDir();
4105
4094
  if (!fs21.existsSync(dir)) return;
4106
- const backups = fs21.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs21.statSync(path29.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
4095
+ const backups = fs21.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs21.statSync(path28.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
4107
4096
  for (const old of backups.slice(3)) {
4108
4097
  try {
4109
- fs21.rmSync(path29.join(dir, old.name));
4098
+ fs21.rmSync(path28.join(dir, old.name));
4110
4099
  } catch {
4111
4100
  }
4112
4101
  }
@@ -4115,17 +4104,17 @@ function pruneEngramDbBackups() {
4115
4104
  }
4116
4105
  function rotateLockedBinary(binPath, sweepRoot = HOME) {
4117
4106
  if (!fs21.existsSync(binPath)) return null;
4118
- const dir = path29.dirname(binPath);
4119
- const base = path29.basename(binPath);
4107
+ const dir = path28.dirname(binPath);
4108
+ const base = path28.basename(binPath);
4120
4109
  const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
4121
4110
  const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
4122
- const resolvedDir = path29.resolve(dir);
4123
- if (resolvedDir === path29.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
4111
+ const resolvedDir = path28.resolve(dir);
4112
+ if (resolvedDir === path28.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
4124
4113
  try {
4125
4114
  for (const entry of fs21.readdirSync(dir)) {
4126
4115
  if (oldPattern.test(entry)) {
4127
4116
  try {
4128
- fs21.rmSync(path29.join(dir, entry), { force: true });
4117
+ fs21.rmSync(path28.join(dir, entry), { force: true });
4129
4118
  } catch {
4130
4119
  }
4131
4120
  }
@@ -4133,7 +4122,7 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
4133
4122
  } catch {
4134
4123
  }
4135
4124
  }
4136
- const rotated = path29.join(dir, `${base}.old-${Date.now()}`);
4125
+ const rotated = path28.join(dir, `${base}.old-${Date.now()}`);
4137
4126
  fs21.renameSync(binPath, rotated);
4138
4127
  return rotated;
4139
4128
  }
@@ -4143,8 +4132,8 @@ async function updateEngram(engramRepo, latestVersion) {
4143
4132
  "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)."
4144
4133
  );
4145
4134
  }
4146
- const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path29.join(HOME, ".engram");
4147
- const engramDb = path29.join(engramDataDir, "engram.db");
4135
+ const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path28.join(HOME, ".engram");
4136
+ const engramDb = path28.join(engramDataDir, "engram.db");
4148
4137
  if (fs21.existsSync(engramDb)) {
4149
4138
  const doBackup = await p4.confirm({
4150
4139
  message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
@@ -4152,7 +4141,7 @@ async function updateEngram(engramRepo, latestVersion) {
4152
4141
  });
4153
4142
  if (!p4.isCancel(doBackup) && doBackup) {
4154
4143
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4155
- const dest = path29.join(dataDir(), `engram-db-backup-${ts}.db`);
4144
+ const dest = path28.join(dataDir(), `engram-db-backup-${ts}.db`);
4156
4145
  try {
4157
4146
  if (!fs21.existsSync(dataDir())) fs21.mkdirSync(dataDir(), { recursive: true });
4158
4147
  fs21.copyFileSync(engramDb, dest);
@@ -4489,7 +4478,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
4489
4478
  continue;
4490
4479
  }
4491
4480
  const { dir: tmpDir, root: tmpRoot } = tmpResult;
4492
- const localSkillDir = path29.join(stackRoot(), "skills", skillInfo.name);
4481
+ const localSkillDir = path28.join(stackRoot(), "skills", skillInfo.name);
4493
4482
  const diff = renderSkillDiff(tmpDir, localSkillDir);
4494
4483
  if (diff) {
4495
4484
  p4.log.info(`Diff de ${skillInfo.name}:
@@ -4575,7 +4564,7 @@ ${diff}`);
4575
4564
  }
4576
4565
 
4577
4566
  // src/models-picker.ts
4578
- import path30 from "path";
4567
+ import path29 from "path";
4579
4568
  import * as p5 from "@clack/prompts";
4580
4569
  var TIERS = ["strong", "standard", "cheap"];
4581
4570
  var EFFORTS = ["low", "medium", "high", "xhigh"];
@@ -4599,7 +4588,7 @@ function opencodeLiveModels(binPath) {
4599
4588
  }
4600
4589
  function agentsByTier() {
4601
4590
  const grouped = { strong: [], standard: [], cheap: [] };
4602
- for (const agent of loadCanonicalAgents(path30.join(stackRoot(), "agents"))) {
4591
+ for (const agent of loadCanonicalAgents(path29.join(stackRoot(), "agents"))) {
4603
4592
  if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
4604
4593
  }
4605
4594
  return grouped;
@@ -4780,15 +4769,15 @@ function cancelled() {
4780
4769
 
4781
4770
  // src/lib/release.ts
4782
4771
  import fs22 from "fs";
4783
- import path31 from "path";
4772
+ import path30 from "path";
4784
4773
  import { execFileSync as execFileSync6 } from "child_process";
4785
- import { fileURLToPath as fileURLToPath2 } from "url";
4774
+ import { fileURLToPath } from "url";
4786
4775
  function findPackageJson() {
4787
- let dir = path31.dirname(fileURLToPath2(import.meta.url));
4776
+ let dir = path30.dirname(fileURLToPath(import.meta.url));
4788
4777
  for (let i = 0; i < 6; i++) {
4789
- const candidate = path31.join(dir, "package.json");
4778
+ const candidate = path30.join(dir, "package.json");
4790
4779
  if (fs22.existsSync(candidate)) return candidate;
4791
- dir = path31.dirname(dir);
4780
+ dir = path30.dirname(dir);
4792
4781
  }
4793
4782
  throw new Error("No se encontr\xF3 package.json cerca del CLI.");
4794
4783
  }
@@ -4808,8 +4797,8 @@ function readPackageMetadata() {
4808
4797
  }
4809
4798
 
4810
4799
  // src/lib/pi-runtime.ts
4811
- import os5 from "os";
4812
- import path32 from "path";
4800
+ import os4 from "os";
4801
+ import path31 from "path";
4813
4802
  import fs23 from "fs";
4814
4803
  import { spawnSync } from "child_process";
4815
4804
  import { createHash } from "crypto";
@@ -4963,14 +4952,14 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
4963
4952
  if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
4964
4953
  const packageValue = Reflect.get(record, "package");
4965
4954
  const result = Reflect.get(record, "result");
4966
- return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path32.resolve(packageRunner) === path32.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
4955
+ return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path31.resolve(packageRunner) === path31.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
4967
4956
  } catch {
4968
4957
  return false;
4969
4958
  }
4970
4959
  }
4971
4960
  function installPiFromVerifiedTarball(input, deps) {
4972
4961
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
4973
- const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
4962
+ const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
4974
4963
  const artifact = deps.download(destination);
4975
4964
  if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
4976
4965
  return { kind: "blocked", reason: "tarball-integrity" };
@@ -4978,7 +4967,7 @@ function installPiFromVerifiedTarball(input, deps) {
4978
4967
  deps.backupSettings();
4979
4968
  const scope = {
4980
4969
  kind: input.targetDir === void 0 ? "real" : "target-dir",
4981
- codingAgentDir: path32.resolve(paths.codingAgentDir)
4970
+ codingAgentDir: path31.resolve(paths.codingAgentDir)
4982
4971
  };
4983
4972
  const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
4984
4973
  deps.writeReceiptAtomic(`${JSON.stringify(installing)}
@@ -5007,30 +4996,30 @@ function installPiFromVerifiedTarball(input, deps) {
5007
4996
  return { kind: "installed", receipt };
5008
4997
  }
5009
4998
  function runtimePath(piExecutable) {
5010
- const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), process.env.SystemRoot ? path32.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
5011
- return [...new Set(entries.filter((entry) => entry !== null))].join(path32.delimiter);
4999
+ const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), process.env.SystemRoot ? path31.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
5000
+ return [...new Set(entries.filter((entry) => entry !== null))].join(path31.delimiter);
5012
5001
  }
5013
5002
  function targetPaths(targetDir, engramBin, piExecutable) {
5014
- const root = path32.resolve(targetDir);
5015
- const codingAgentDir = path32.join(root, "pi-agent");
5016
- const home = path32.join(root, "home");
5017
- const temporary = path32.join(root, "tmp");
5003
+ const root = path31.resolve(targetDir);
5004
+ const codingAgentDir = path31.join(root, "pi-agent");
5005
+ const home = path31.join(root, "home");
5006
+ const temporary = path31.join(root, "tmp");
5018
5007
  return {
5019
5008
  codingAgentDir,
5020
- receiptPath: path32.join(root, "state", "pi-receipt.json"),
5021
- packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5009
+ receiptPath: path31.join(root, "state", "pi-receipt.json"),
5010
+ packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5022
5011
  environment: {
5023
5012
  HOME: home,
5024
5013
  USERPROFILE: home,
5025
- APPDATA: path32.join(root, "appdata"),
5026
- LOCALAPPDATA: path32.join(root, "localappdata"),
5027
- XDG_CONFIG_HOME: path32.join(root, "xdg-config"),
5028
- XDG_DATA_HOME: path32.join(root, "xdg-data"),
5029
- XDG_CACHE_HOME: path32.join(root, "xdg-cache"),
5014
+ APPDATA: path31.join(root, "appdata"),
5015
+ LOCALAPPDATA: path31.join(root, "localappdata"),
5016
+ XDG_CONFIG_HOME: path31.join(root, "xdg-config"),
5017
+ XDG_DATA_HOME: path31.join(root, "xdg-data"),
5018
+ XDG_CACHE_HOME: path31.join(root, "xdg-cache"),
5030
5019
  TEMP: temporary,
5031
5020
  TMP: temporary,
5032
5021
  TMPDIR: temporary,
5033
- npm_config_cache: path32.join(root, "npm-cache"),
5022
+ npm_config_cache: path31.join(root, "npm-cache"),
5034
5023
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
5035
5024
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
5036
5025
  PI_CODING_AGENT_DIR: codingAgentDir,
@@ -5040,17 +5029,17 @@ function targetPaths(targetDir, engramBin, piExecutable) {
5040
5029
  };
5041
5030
  }
5042
5031
  function userPaths(engramBin, piExecutable) {
5043
- const home = os5.homedir();
5044
- const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent");
5032
+ const home = os4.homedir();
5033
+ const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent");
5045
5034
  return {
5046
5035
  codingAgentDir,
5047
- receiptPath: path32.join(dataDir(), "pi-receipt.json"),
5048
- packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5036
+ receiptPath: path31.join(dataDir(), "pi-receipt.json"),
5037
+ packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
5049
5038
  environment: {
5050
5039
  HOME: home,
5051
- XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path32.join(home, ".config"),
5052
- XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path32.join(home, ".cache"),
5053
- TMPDIR: os5.tmpdir(),
5040
+ XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path31.join(home, ".config"),
5041
+ XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path31.join(home, ".cache"),
5042
+ TMPDIR: os4.tmpdir(),
5054
5043
  NPM_CONFIG_IGNORE_SCRIPTS: "true",
5055
5044
  NPM_CONFIG_UPDATE_NOTIFIER: "false",
5056
5045
  PI_CODING_AGENT_DIR: codingAgentDir,
@@ -5077,7 +5066,7 @@ function runPiRuntime(input, deps) {
5077
5066
  return { kind: "blocked", reason: "tarball-integrity" };
5078
5067
  }
5079
5068
  const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
5080
- const settingsJson = deps.readSettings(path32.join(paths.codingAgentDir, "settings.json"));
5069
+ const settingsJson = deps.readSettings(path31.join(paths.codingAgentDir, "settings.json"));
5081
5070
  const receiptJson = deps.readReceipt(paths.receiptPath);
5082
5071
  const lifecycleInput = {
5083
5072
  candidate: PI_RUNTIME_CANDIDATE,
@@ -5137,14 +5126,14 @@ function readJsonFile(file) {
5137
5126
  function packageVersionFromExecutable(executable) {
5138
5127
  let current;
5139
5128
  try {
5140
- current = path32.dirname(fs23.realpathSync(executable));
5129
+ current = path31.dirname(fs23.realpathSync(executable));
5141
5130
  } catch {
5142
5131
  return null;
5143
5132
  }
5144
5133
  for (let depth = 0; depth < 8; depth++) {
5145
5134
  const manifests = [
5146
- path32.join(current, "package.json"),
5147
- path32.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
5135
+ path31.join(current, "package.json"),
5136
+ path31.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
5148
5137
  ];
5149
5138
  for (const manifest of manifests) {
5150
5139
  try {
@@ -5155,7 +5144,7 @@ function packageVersionFromExecutable(executable) {
5155
5144
  } catch {
5156
5145
  }
5157
5146
  }
5158
- const parent = path32.dirname(current);
5147
+ const parent = path31.dirname(current);
5159
5148
  if (parent === current) break;
5160
5149
  current = parent;
5161
5150
  }
@@ -5163,23 +5152,23 @@ function packageVersionFromExecutable(executable) {
5163
5152
  }
5164
5153
  function detectPiRuntime() {
5165
5154
  const executable = lookPath("pi");
5166
- const home = os5.homedir();
5155
+ const home = os4.homedir();
5167
5156
  return {
5168
5157
  id: "pi",
5169
5158
  name: "Pi",
5170
5159
  installed: executable !== null,
5171
5160
  executable,
5172
5161
  version: executable === null ? null : packageVersionFromExecutable(executable),
5173
- codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent")
5162
+ codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent")
5174
5163
  };
5175
5164
  }
5176
5165
  function hasManagedPiRuntime(targetDir) {
5177
- const receipt = targetDir === void 0 ? path32.join(dataDir(), "pi-receipt.json") : path32.join(path32.resolve(targetDir), "state", "pi-receipt.json");
5166
+ const receipt = targetDir === void 0 ? path31.join(dataDir(), "pi-receipt.json") : path31.join(path31.resolve(targetDir), "state", "pi-receipt.json");
5178
5167
  return fs23.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
5179
5168
  }
5180
5169
  function resolvePiEngramBin(targetDir) {
5181
5170
  if (targetDir === void 0) return detectEngram();
5182
- const candidate = path32.join(path32.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
5171
+ const candidate = path31.join(path31.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
5183
5172
  return fs23.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
5184
5173
  }
5185
5174
  function readOptional(file, fallback) {
@@ -5192,7 +5181,7 @@ function readOptional(file, fallback) {
5192
5181
  }
5193
5182
  function hashPiTarball(file) {
5194
5183
  const descriptor = fs23.openSync(file, "r");
5195
- const sha256 = createHash("sha256");
5184
+ const sha2562 = createHash("sha256");
5196
5185
  const sha512 = createHash("sha512");
5197
5186
  const buffer = Buffer.allocUnsafe(1024 * 1024);
5198
5187
  let bytes = 0;
@@ -5202,13 +5191,13 @@ function hashPiTarball(file) {
5202
5191
  if (read === 0) break;
5203
5192
  bytes += read;
5204
5193
  const chunk = buffer.subarray(0, read);
5205
- sha256.update(chunk);
5194
+ sha2562.update(chunk);
5206
5195
  sha512.update(chunk);
5207
5196
  }
5208
5197
  } finally {
5209
5198
  fs23.closeSync(descriptor);
5210
5199
  }
5211
- return { path: file, bytes, sha256: sha256.digest("hex"), sha512: sha512.digest("hex") };
5200
+ return { path: file, bytes, sha256: sha2562.digest("hex"), sha512: sha512.digest("hex") };
5212
5201
  }
5213
5202
  async function acquirePiTarball(destination) {
5214
5203
  const existing = fs23.statSync(destination, { throwIfNoEntry: false });
@@ -5219,7 +5208,7 @@ async function acquirePiTarball(destination) {
5219
5208
  }
5220
5209
  fs23.rmSync(destination, { force: true });
5221
5210
  }
5222
- fs23.mkdirSync(path32.dirname(destination), { recursive: true });
5211
+ fs23.mkdirSync(path31.dirname(destination), { recursive: true });
5223
5212
  const partial = `${destination}.partial-${process.pid}`;
5224
5213
  const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
5225
5214
  redirect: "error",
@@ -5292,7 +5281,7 @@ async function runPiRuntimeSystem(input) {
5292
5281
  remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
5293
5282
  };
5294
5283
  }
5295
- const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
5284
+ const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
5296
5285
  let artifact;
5297
5286
  try {
5298
5287
  artifact = await acquirePiTarball(destination);
@@ -5312,18 +5301,18 @@ async function runPiRuntimeSystem(input) {
5312
5301
  }, {
5313
5302
  download: () => artifact,
5314
5303
  backupSettings: () => createBackup(
5315
- [path32.join(paths.codingAgentDir, "settings.json")],
5304
+ [path31.join(paths.codingAgentDir, "settings.json")],
5316
5305
  "pi-package-install",
5317
- input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
5306
+ input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
5318
5307
  ),
5319
5308
  run: runProcess,
5320
- readSettings: () => readOptional(path32.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
5321
- rewriteSettings: (content) => writeText(path32.join(paths.codingAgentDir, "settings.json"), `${content}
5309
+ readSettings: () => readOptional(path31.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
5310
+ rewriteSettings: (content) => writeText(path31.join(paths.codingAgentDir, "settings.json"), `${content}
5322
5311
  `),
5323
5312
  writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
5324
5313
  });
5325
5314
  }
5326
- const packageRoot = path32.dirname(path32.dirname(paths.packageRunner));
5315
+ const packageRoot = path31.dirname(path31.dirname(paths.packageRunner));
5327
5316
  const writeReceipt = (receipt) => {
5328
5317
  writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
5329
5318
  `);
@@ -5341,9 +5330,9 @@ async function runPiRuntimeSystem(input) {
5341
5330
  value,
5342
5331
  {
5343
5332
  backupSettings: () => createBackup(
5344
- [path32.join(paths.codingAgentDir, "settings.json")],
5333
+ [path31.join(paths.codingAgentDir, "settings.json")],
5345
5334
  "pi-package-uninstall",
5346
- input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
5335
+ input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
5347
5336
  ),
5348
5337
  run: runProcess,
5349
5338
  isPackageAbsent: () => !fs23.existsSync(packageRoot),
@@ -5443,9 +5432,390 @@ async function runManagedPiSystem(input) {
5443
5432
  });
5444
5433
  }
5445
5434
 
5435
+ // src/lib/quality-runner.ts
5436
+ import path32 from "path";
5437
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
5438
+ function normalizeLimit(value, name) {
5439
+ if (value === void 0) return void 0;
5440
+ if (!Number.isSafeInteger(value) || value < 0) {
5441
+ throw new Error(`${name} must be a non-negative safe integer`);
5442
+ }
5443
+ return value;
5444
+ }
5445
+ var MAX_NODE_TIMEOUT_MS = 2147483647;
5446
+ var TERMINATION_GRACE_MS = 250;
5447
+ var TASKKILL_TIMEOUT_MS = 2e3;
5448
+ function normalizeTimeout(value) {
5449
+ const timeoutMs = normalizeLimit(value, "timeoutMs");
5450
+ if (timeoutMs !== void 0 && timeoutMs > MAX_NODE_TIMEOUT_MS) {
5451
+ throw new Error(`timeoutMs must not exceed ${MAX_NODE_TIMEOUT_MS}`);
5452
+ }
5453
+ return timeoutMs;
5454
+ }
5455
+ var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
5456
+ function isRecord2(value) {
5457
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5458
+ }
5459
+ function hasText(value) {
5460
+ return typeof value === "string" && value.trim() !== "";
5461
+ }
5462
+ function isDenseStringArray(value) {
5463
+ if (!Array.isArray(value)) return false;
5464
+ for (let index = 0; index < value.length; index += 1) {
5465
+ if (!Object.prototype.hasOwnProperty.call(value, String(index)) || typeof value[index] !== "string") return false;
5466
+ }
5467
+ return true;
5468
+ }
5469
+ function isNonNegativeSafeInteger(value) {
5470
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
5471
+ }
5472
+ function isQualityProfile(value) {
5473
+ return typeof value === "string" && QUALITY_PROFILES.includes(value);
5474
+ }
5475
+ function assertValidEnvironment(value, label) {
5476
+ if (!isRecord2(value)) throw new Error(`Invalid ${label}`);
5477
+ for (const [key, entry] of Object.entries(value)) {
5478
+ if (typeof entry !== "string") throw new Error(`Invalid ${label}.${key}`);
5479
+ }
5480
+ }
5481
+ function assertQualityPlanInput(value) {
5482
+ if (!isRecord2(value)) throw new Error("Invalid quality plan");
5483
+ if (!isRecord2(value.identity)) throw new Error("Invalid quality plan identity");
5484
+ if (typeof value.identity.baseSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.baseSha)) {
5485
+ throw new Error("Invalid quality plan identity.baseSha");
5486
+ }
5487
+ if (typeof value.identity.headSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.headSha)) {
5488
+ throw new Error("Invalid quality plan identity.headSha");
5489
+ }
5490
+ if (!isQualityProfile(value.profile)) throw new Error("Invalid quality plan profile");
5491
+ if (!Array.isArray(value.controls)) throw new Error("Invalid quality plan controls");
5492
+ const controlIds = /* @__PURE__ */ new Set();
5493
+ for (let index = 0; index < value.controls.length; index += 1) {
5494
+ const control = value.controls[index];
5495
+ if (!isRecord2(control) || !hasText(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
5496
+ throw new Error(`Invalid quality plan controls[${index}]`);
5497
+ }
5498
+ if (controlIds.has(control.id)) {
5499
+ throw new Error(`Duplicate quality plan control id: ${control.id}`);
5500
+ }
5501
+ controlIds.add(control.id);
5502
+ if (Object.prototype.hasOwnProperty.call(control, "notApplicable") && typeof control.notApplicable !== "boolean") {
5503
+ throw new Error(`Invalid quality plan controls[${index}].notApplicable`);
5504
+ }
5505
+ }
5506
+ if (!Array.isArray(value.commands)) throw new Error("Invalid quality plan commands");
5507
+ const commandIds = /* @__PURE__ */ new Set();
5508
+ const commandControlIds = /* @__PURE__ */ new Set();
5509
+ for (let index = 0; index < value.commands.length; index += 1) {
5510
+ const command = value.commands[index];
5511
+ if (!isRecord2(command)) throw new Error(`Invalid quality plan commands[${index}]`);
5512
+ const timeoutMs = command.timeoutMs;
5513
+ if (!hasText(command.controlId) || !hasText(command.commandId) || !hasText(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
5514
+ throw new Error(`Invalid quality plan commands[${index}]`);
5515
+ }
5516
+ if (timeoutMs > MAX_NODE_TIMEOUT_MS) {
5517
+ throw new Error(`Invalid quality plan commands[${index}].timeoutMs: maximum is ${MAX_NODE_TIMEOUT_MS}`);
5518
+ }
5519
+ if (!controlIds.has(command.controlId)) {
5520
+ throw new Error(`Unknown quality plan command control id: ${command.controlId}`);
5521
+ }
5522
+ if (commandIds.has(command.commandId)) {
5523
+ throw new Error(`Duplicate quality plan command id: ${command.commandId}`);
5524
+ }
5525
+ if (commandControlIds.has(command.controlId)) {
5526
+ throw new Error(`Multiple quality plan commands for control id: ${command.controlId}`);
5527
+ }
5528
+ commandIds.add(command.commandId);
5529
+ commandControlIds.add(command.controlId);
5530
+ if (command.maxOutputBytes !== void 0 && !isNonNegativeSafeInteger(command.maxOutputBytes)) {
5531
+ throw new Error(`Invalid quality plan commands[${index}].maxOutputBytes`);
5532
+ }
5533
+ if (command.env !== void 0) assertValidEnvironment(command.env, `quality plan commands[${index}].env`);
5534
+ }
5535
+ }
5536
+ function appendOutput(captured, stream, chunk, maxOutputBytes) {
5537
+ if (maxOutputBytes === void 0) {
5538
+ captured[stream].push(chunk);
5539
+ return false;
5540
+ }
5541
+ const remaining = maxOutputBytes - captured.bytes;
5542
+ if (remaining <= 0) return chunk.length > 0;
5543
+ if (chunk.length > remaining) {
5544
+ captured[stream].push(chunk.subarray(0, remaining));
5545
+ captured.bytes += remaining;
5546
+ return true;
5547
+ }
5548
+ captured[stream].push(chunk);
5549
+ captured.bytes += chunk.length;
5550
+ return captured.bytes >= maxOutputBytes;
5551
+ }
5552
+ function decodeOutput(chunks, maxOutputBytes) {
5553
+ const output = Buffer.concat(chunks);
5554
+ if (maxOutputBytes === void 0 || output.length === 0) return output.toString("utf8");
5555
+ let end = output.length;
5556
+ let decoded = output.subarray(0, end).toString("utf8");
5557
+ while (Buffer.byteLength(decoded, "utf8") > maxOutputBytes && end > 0) {
5558
+ end -= 1;
5559
+ decoded = output.subarray(0, end).toString("utf8");
5560
+ }
5561
+ return decoded;
5562
+ }
5563
+ function killProcessTree(child) {
5564
+ const pid = child.pid;
5565
+ if (pid === void 0) return;
5566
+ if (process.platform === "win32") {
5567
+ const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
5568
+ const taskkill = path32.join(systemRoot, "System32", "taskkill.exe");
5569
+ const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
5570
+ shell: false,
5571
+ stdio: "ignore",
5572
+ timeout: TASKKILL_TIMEOUT_MS,
5573
+ windowsHide: true
5574
+ });
5575
+ if (result.error !== void 0 || result.status !== 0) {
5576
+ try {
5577
+ child.kill("SIGKILL");
5578
+ } catch {
5579
+ }
5580
+ }
5581
+ return;
5582
+ }
5583
+ try {
5584
+ process.kill(-pid, "SIGKILL");
5585
+ } catch {
5586
+ try {
5587
+ child.kill("SIGKILL");
5588
+ } catch {
5589
+ }
5590
+ }
5591
+ }
5592
+ function resultFromCaptured(input, status, exitCode, startedAt, captured, maxOutputBytes, reason) {
5593
+ return {
5594
+ commandId: input.commandId,
5595
+ status,
5596
+ exitCode,
5597
+ durationMs: Math.max(0, Date.now() - startedAt),
5598
+ output: {
5599
+ stdout: decodeOutput(captured.stdout, maxOutputBytes),
5600
+ stderr: decodeOutput(captured.stderr, maxOutputBytes)
5601
+ },
5602
+ ...reason === void 0 ? {} : { reason }
5603
+ };
5604
+ }
5605
+ async function runQualityCommand(input, deps = { terminate: killProcessTree }) {
5606
+ const timeoutMs = normalizeTimeout(input.timeoutMs);
5607
+ const maxOutputBytes = normalizeLimit(input.maxOutputBytes, "maxOutputBytes");
5608
+ if (timeoutMs === void 0) throw new Error("timeoutMs is required");
5609
+ const planned = planDetectedBinCommand(input.executable, [...input.argv]);
5610
+ const startedAt = Date.now();
5611
+ const captured = { stdout: [], stderr: [], bytes: 0 };
5612
+ if (planned === null) {
5613
+ return resultFromCaptured(input, "error", null, startedAt, captured, maxOutputBytes, "unsafe-command");
5614
+ }
5615
+ return new Promise((resolve) => {
5616
+ let settled = false;
5617
+ let terminationReason;
5618
+ let spawnError;
5619
+ let timeout;
5620
+ let terminationDeadline;
5621
+ let child;
5622
+ let cleanedUp = false;
5623
+ let onStdoutData = (_chunk) => {
5624
+ };
5625
+ let onStderrData = (_chunk) => {
5626
+ };
5627
+ let onError = (_error) => {
5628
+ };
5629
+ const onErrorSink = () => {
5630
+ };
5631
+ let onClose = (_exitCode) => {
5632
+ };
5633
+ const cleanupChild = () => {
5634
+ if (cleanedUp || child === void 0) return;
5635
+ cleanedUp = true;
5636
+ child.removeListener("close", onClose);
5637
+ child.removeListener("error", onError);
5638
+ child.on("error", onErrorSink);
5639
+ child.stdout?.removeListener("data", onStdoutData);
5640
+ child.stderr?.removeListener("data", onStderrData);
5641
+ child.stdout?.destroy();
5642
+ child.stderr?.destroy();
5643
+ child.unref();
5644
+ };
5645
+ const settle = (result) => {
5646
+ if (settled) return;
5647
+ settled = true;
5648
+ if (timeout !== void 0) clearTimeout(timeout);
5649
+ if (terminationDeadline !== void 0) clearTimeout(terminationDeadline);
5650
+ cleanupChild();
5651
+ resolve(result);
5652
+ };
5653
+ try {
5654
+ child = spawn(planned.command, planned.args, {
5655
+ detached: true,
5656
+ env: input.env === void 0 ? {} : { ...input.env },
5657
+ shell: false,
5658
+ stdio: ["ignore", "pipe", "pipe"],
5659
+ windowsHide: true
5660
+ });
5661
+ } catch (error) {
5662
+ settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
5663
+ return;
5664
+ }
5665
+ const stopFor = (reason) => {
5666
+ if (settled || terminationReason !== void 0) return;
5667
+ terminationReason = reason;
5668
+ try {
5669
+ deps.terminate(child);
5670
+ } catch {
5671
+ }
5672
+ if (settled) return;
5673
+ terminationDeadline = setTimeout(() => {
5674
+ try {
5675
+ deps.terminate(child);
5676
+ } catch {
5677
+ }
5678
+ cleanupChild();
5679
+ settle(resultFromCaptured(input, "error", null, startedAt, captured, maxOutputBytes, "termination-timeout"));
5680
+ }, TERMINATION_GRACE_MS);
5681
+ };
5682
+ const onOutput = (stream, chunk) => {
5683
+ if (settled || appendOutput(captured, stream, chunk, maxOutputBytes)) {
5684
+ if (!settled && maxOutputBytes !== void 0) stopFor("output-limit");
5685
+ }
5686
+ };
5687
+ onStdoutData = (chunk) => onOutput("stdout", chunk);
5688
+ onStderrData = (chunk) => onOutput("stderr", chunk);
5689
+ onError = (error) => {
5690
+ spawnError = error;
5691
+ if (terminationReason === void 0 && (error.code === "ENOENT" || error.code === "EACCES")) {
5692
+ settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
5693
+ }
5694
+ };
5695
+ onClose = (exitCode) => {
5696
+ if (terminationReason === "timeout") {
5697
+ settle(resultFromCaptured(input, "timeout", exitCode, startedAt, captured, maxOutputBytes));
5698
+ return;
5699
+ }
5700
+ if (terminationReason === "output-limit") {
5701
+ settle(resultFromCaptured(input, "error", exitCode, startedAt, captured, maxOutputBytes, "output-limit"));
5702
+ return;
5703
+ }
5704
+ if (spawnError !== void 0) {
5705
+ settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
5706
+ return;
5707
+ }
5708
+ settle(resultFromCaptured(
5709
+ input,
5710
+ exitCode === 0 ? "pass" : "fail",
5711
+ exitCode,
5712
+ startedAt,
5713
+ captured,
5714
+ maxOutputBytes
5715
+ ));
5716
+ };
5717
+ child.stdout?.on("data", onStdoutData);
5718
+ child.stderr?.on("data", onStderrData);
5719
+ child.once("error", onError);
5720
+ child.once("close", onClose);
5721
+ timeout = setTimeout(() => stopFor("timeout"), timeoutMs);
5722
+ });
5723
+ }
5724
+ function resultEvidence(result) {
5725
+ const exitCode = result.exitCode === null ? "none" : String(result.exitCode);
5726
+ return `exit=${exitCode}; status=${result.status}; durationMs=${result.durationMs}`;
5727
+ }
5728
+ function resultReason(result) {
5729
+ switch (result.status) {
5730
+ case "pass":
5731
+ return void 0;
5732
+ case "fail":
5733
+ return "nonzero-exit";
5734
+ case "timeout":
5735
+ return "timeout";
5736
+ case "unavailable":
5737
+ return result.reason ?? "unavailable";
5738
+ case "error":
5739
+ return result.reason ?? "error";
5740
+ }
5741
+ }
5742
+ function receiptResultFor(controlId, result) {
5743
+ const evidence = resultEvidence(result);
5744
+ if (result.status === "pass") return { controlId, status: "pass", evidence };
5745
+ return {
5746
+ controlId,
5747
+ status: result.status === "fail" ? "fail" : "incomplete",
5748
+ evidence,
5749
+ reason: resultReason(result)
5750
+ };
5751
+ }
5752
+ function missingRequiredResults(controls, commands) {
5753
+ const commandControlIds = new Set(commands.map((command) => command.controlId));
5754
+ const missing = /* @__PURE__ */ new Set();
5755
+ for (const control of controls) {
5756
+ if (control.requirement !== "required" || !hasText(control.id) || commandControlIds.has(control.id)) continue;
5757
+ missing.add(control.id);
5758
+ }
5759
+ return [...missing].map((controlId) => ({
5760
+ controlId,
5761
+ status: "incomplete",
5762
+ evidence: "required control has no declared command",
5763
+ reason: "required-control-missing"
5764
+ }));
5765
+ }
5766
+ function receiptCommandFor(command, result) {
5767
+ return {
5768
+ commandId: command.commandId,
5769
+ executable: command.executable,
5770
+ argv: command.argv,
5771
+ exitCode: result.exitCode ?? -1,
5772
+ durationMs: result.durationMs,
5773
+ output: result.output
5774
+ };
5775
+ }
5776
+ async function runQualityPlan(input) {
5777
+ assertQualityPlanInput(input);
5778
+ const policy = { controls: input.controls, profile: input.profile };
5779
+ const policyDigest = sha256(canonicalJson(policy));
5780
+ const identity = {
5781
+ profile: input.profile,
5782
+ baseSha: input.identity.baseSha,
5783
+ headSha: input.identity.headSha,
5784
+ policyDigest
5785
+ };
5786
+ const commands = [];
5787
+ const results = [];
5788
+ for (const command of input.commands) {
5789
+ const result = await runQualityCommand(command);
5790
+ commands.push(receiptCommandFor(command, result));
5791
+ results.push(receiptResultFor(command.controlId, result));
5792
+ }
5793
+ results.push(...missingRequiredResults(input.controls, input.commands));
5794
+ const evaluation = evaluateQualityPolicy({
5795
+ profile: input.profile,
5796
+ controls: input.controls,
5797
+ results
5798
+ });
5799
+ const receipt = createQualityReceipt({
5800
+ authority: "local",
5801
+ identity,
5802
+ commands,
5803
+ results
5804
+ });
5805
+ validateQualityReceipt(receipt, identity);
5806
+ return { evaluation, receipt };
5807
+ }
5808
+
5446
5809
  // src/cli.ts
5447
5810
  var VERSION = readPackageVersion();
5448
- var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall"];
5811
+ var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
5812
+ var QUALITY_REJECTED_VALUE_FLAGS = /* @__PURE__ */ new Set([
5813
+ "--agents",
5814
+ "-a",
5815
+ "--target-dir",
5816
+ "--mode",
5817
+ "--subagent-concurrency"
5818
+ ]);
5449
5819
  async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
5450
5820
  if (!runtimes.includes("opencode") || loadModelMap().opencode) return true;
5451
5821
  const canPrompt = command === "install" && !flags.yes && !flags.dryRun && process.stdout.isTTY;
@@ -5458,7 +5828,7 @@ async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
5458
5828
  );
5459
5829
  return false;
5460
5830
  }
5461
- function parseFlags(args) {
5831
+ function parseFlags(args, allowReceipt = false) {
5462
5832
  const flags = {
5463
5833
  agents: [],
5464
5834
  dryRun: false,
@@ -5474,6 +5844,7 @@ function parseFlags(args) {
5474
5844
  removePlaywright: false,
5475
5845
  devtools: false,
5476
5846
  noDevtools: false,
5847
+ receipt: void 0,
5477
5848
  positional: [],
5478
5849
  unknownFlags: []
5479
5850
  };
@@ -5484,6 +5855,26 @@ function parseFlags(args) {
5484
5855
  };
5485
5856
  for (let i = 0; i < args.length; i++) {
5486
5857
  const arg = args[i];
5858
+ if (allowReceipt) {
5859
+ if (arg === "--help" || arg === "-h") flags.help = true;
5860
+ else if (arg === "--version" || arg === "-v") flags.version = true;
5861
+ else if (arg === "--receipt") {
5862
+ const [value, nextIndex] = readValue(i);
5863
+ if (value === void 0 || value === "") flags.unknownFlags.push(arg);
5864
+ else flags.receipt = value;
5865
+ i = nextIndex;
5866
+ } else if (arg.startsWith("--receipt=")) {
5867
+ const value = arg.slice(10);
5868
+ if (value === "") flags.unknownFlags.push(arg);
5869
+ else flags.receipt = value;
5870
+ } else if (QUALITY_REJECTED_VALUE_FLAGS.has(arg)) {
5871
+ flags.unknownFlags.push(arg);
5872
+ const [, nextIndex] = readValue(i);
5873
+ i = nextIndex;
5874
+ } else if (arg.startsWith("-")) flags.unknownFlags.push(arg);
5875
+ else flags.positional.push(arg);
5876
+ continue;
5877
+ }
5487
5878
  if (arg === "--agents" || arg === "-a") {
5488
5879
  const [value, nextIndex] = readValue(i);
5489
5880
  flags.agents = (value ?? "").split(",").filter(Boolean);
@@ -5635,7 +6026,7 @@ function parseCliArgs(argv) {
5635
6026
  };
5636
6027
  }
5637
6028
  const command = isCommand ? first ?? "install" : "install";
5638
- const flags = parseFlags(isCommand ? rest : argv);
6029
+ const flags = parseFlags(isCommand ? rest : argv, command === "quality");
5639
6030
  if (first === "--help" || first === "-h" || flags.help) return { action: "help", command, flags };
5640
6031
  if (first === "--version" || first === "-v" || flags.version) return { action: "version", command, flags };
5641
6032
  if (flags.unknownFlags.length > 0) return { action: "unknown-flags", command, flags };
@@ -5727,8 +6118,9 @@ Comandos:
5727
6118
  doctor Estado: Engram, drift de config, hooks de Codex, key de context7
5728
6119
  restore --list para ver backups \xB7 'restore <id>' para restaurar
5729
6120
  uninstall Retira SOLO lo gestionado por el stack (con backup).
5730
- Engram se CONSERVA por defecto (memorias, binario y registro);
5731
- desregistrarlo exige --remove-engram o el s\xED expl\xEDcito
6121
+ Engram se CONSERVA por defecto (memorias, binario y registro);
6122
+ desregistrarlo exige --remove-engram o el s\xED expl\xEDcito
6123
+ quality Ejecuta un plan JSON expl\xEDcito y emite un receipt local
5732
6124
 
5733
6125
  Opciones:
5734
6126
  --agents, -a opencode,claude-code,codex,pi Runtimes destino (default: detectados)
@@ -5744,6 +6136,7 @@ Opciones:
5744
6136
  memorias y binario quedan intactos igualmente
5745
6137
  --remove-playwright (uninstall) retira solo el paquete global de Playwright;
5746
6138
  nunca perfiles, cach\xE9 ni navegadores
6139
+ --receipt <path> (quality) escribe el receipt en ese path de forma at\xF3mica
5747
6140
 
5748
6141
  Ver PRD.md para el dise\xF1o completo.`);
5749
6142
  }
@@ -5770,12 +6163,43 @@ Flags disponibles: jorgex-stack --help`
5770
6163
  return;
5771
6164
  }
5772
6165
  const { command, flags } = parsed;
5773
- if (flags.targetDir !== void 0 && flags.agents.length !== 1) {
6166
+ if (command !== "quality" && flags.targetDir !== void 0 && flags.agents.length !== 1) {
5774
6167
  console.error("--target-dir requiere exactamente un runtime en --agents.");
5775
6168
  process.exitCode = 1;
5776
6169
  return;
5777
6170
  }
5778
6171
  switch (command) {
6172
+ case "quality": {
6173
+ if (flags.targetDir !== void 0 || flags.agents.length > 0 || flags.dryRun || flags.yes || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 || flags.list || flags.check || flags.removeEngram || flags.playwright || flags.removePlaywright || flags.devtools || flags.noDevtools) {
6174
+ console.error("quality solo admite <plan.json> y, opcionalmente, --receipt <path>.");
6175
+ process.exitCode = 1;
6176
+ return;
6177
+ }
6178
+ if (flags.positional.length !== 1) {
6179
+ console.error("Uso: jorgex-stack quality <plan.json> [--receipt <path>]");
6180
+ process.exitCode = 1;
6181
+ return;
6182
+ }
6183
+ if (flags.receipt !== void 0 && flags.receipt.trim() === "") {
6184
+ console.error("--receipt requiere un path no vac\xEDo.");
6185
+ process.exitCode = 1;
6186
+ return;
6187
+ }
6188
+ try {
6189
+ const plan = JSON.parse(fs24.readFileSync(flags.positional[0], "utf8"));
6190
+ const result = await runQualityPlan(plan);
6191
+ const serialized = serializeQualityReceipt(result.receipt);
6192
+ if (flags.receipt === void 0) process.stdout.write(`${serialized}
6193
+ `);
6194
+ else writeText(flags.receipt, `${serialized}
6195
+ `);
6196
+ process.exitCode = result.evaluation.status === "pass" ? 0 : 1;
6197
+ } catch (error) {
6198
+ console.error(error instanceof Error ? error.message : String(error));
6199
+ process.exitCode = 1;
6200
+ }
6201
+ return;
6202
+ }
5779
6203
  case "install":
5780
6204
  case "sync": {
5781
6205
  const runtimes = await resolveRuntimes(flags, command === "install");