jorgex-stack 1.9.40 → 1.9.42

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
@@ -401,6 +401,12 @@ function readTomlSection(existing, section) {
401
401
  if (found === null) return null;
402
402
  return lines.slice(found.start + 1, found.end).join("\n");
403
403
  }
404
+ function hasTomlChildSection(existing, section) {
405
+ if (existing === null) return false;
406
+ const lines = existing.split(/\r?\n/);
407
+ const mask = multilineStringMask(lines);
408
+ return lines.some((line, index) => !mask[index] && headerName(line)?.startsWith(`${section}.`) === true);
409
+ }
404
410
 
405
411
  // src/lib/writing-style.ts
406
412
  var DEFAULT_SECTION = "writing-style-default";
@@ -830,19 +836,6 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
830
836
  });
831
837
  }
832
838
 
833
- // src/lib/git-guard.ts
834
- var DESTRUCTIVE_GIT_DENY = [
835
- "git reset*",
836
- "git clean*",
837
- "git checkout -- *",
838
- "git restore*",
839
- "git push*--force*",
840
- // posición libre: caza "git push origin main --force[-with-lease]"
841
- "git push -f*",
842
- "git switch*--discard-changes*"
843
- ];
844
- var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
845
-
846
839
  // src/lib/quality-capabilities.ts
847
840
  var QUALITY_CAPABILITY_NAMESPACE = "jorgex.quality.capabilities";
848
841
  var QUALITY_CAPABILITY_VERSION = 1;
@@ -917,9 +910,36 @@ function localQualityStatus(profile, status) {
917
910
  }
918
911
 
919
912
  // src/adapters/opencode.ts
913
+ var gitReadPrefix = "git --no-pager -c core.fsmonitor=false -c log.showSignature=false";
914
+ var gitReadCommands = [
915
+ "diff",
916
+ "diff --stat",
917
+ "diff --name-only",
918
+ "diff --cached",
919
+ "log",
920
+ "log --oneline -10"
921
+ ].map((action) => `${gitReadPrefix} ${action} --no-ext-diff --no-textconv --end-of-options`);
920
922
  function yamlString(value) {
921
923
  return JSON.stringify(value);
922
924
  }
925
+ function readMcpConfig(file) {
926
+ let content;
927
+ try {
928
+ content = fs6.readFileSync(file, "utf8");
929
+ } catch (error) {
930
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
931
+ if (code === "ENOENT") return null;
932
+ throw new Error(`OpenCode: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
933
+ }
934
+ if (content.trim() !== "") {
935
+ try {
936
+ if (objectValue(JSON.parse(content)) === null) throw new Error();
937
+ } catch {
938
+ throw new Error(`OpenCode: la configuraci\xF3n MCP en ${file} debe contener un objeto JSON v\xE1lido.`);
939
+ }
940
+ }
941
+ return content;
942
+ }
923
943
  function isManagedOptionalStdioServer(server, value) {
924
944
  if (!server.optional || server.transport !== "stdio" || value === null || typeof value !== "object" || Array.isArray(value)) {
925
945
  return false;
@@ -940,6 +960,31 @@ var PRIMARY_LIMIT_PREFIX = `provider.openai.models.${PRIMARY_MODEL_ID}.limit`;
940
960
  function objectValue(value) {
941
961
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
942
962
  }
963
+ function isCompatibleContext7Server(server, value) {
964
+ const current = objectValue(value);
965
+ if (server.transport !== "http" || typeof server.url !== "string" || current === null) return false;
966
+ return current.type === "remote" && current.url === server.url && (current.enabled === void 0 || current.enabled === true);
967
+ }
968
+ function canonicalContext7Server(server) {
969
+ const headers = {};
970
+ for (const [key, raw] of Object.entries(server.headers ?? {})) {
971
+ const envRef = /^\$\{(\w+)\}$/.exec(raw);
972
+ headers[key] = envRef ? `{env:${envRef[1]}}` : raw;
973
+ }
974
+ return {
975
+ type: "remote",
976
+ url: server.url,
977
+ ...Object.keys(headers).length > 0 ? { headers } : {}
978
+ };
979
+ }
980
+ function isCanonicalContext7Server(server, value) {
981
+ return isCompatibleContext7Server(server, value) && isDeepStrictEqual(value, canonicalContext7Server(server));
982
+ }
983
+ function assertCompatibleContext7(server, value) {
984
+ if (value !== void 0 && !isCompatibleContext7Server(server, value)) {
985
+ throw new Error("OpenCode: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint, tipo o estado nativo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
986
+ }
987
+ }
943
988
  function ensureObject(parent, key, fieldPath) {
944
989
  if (parent[key] === void 0) parent[key] = {};
945
990
  const value = objectValue(parent[key]);
@@ -1015,15 +1060,20 @@ var opencodeAdapter = {
1015
1060
  const tierModel = resolveAgentModel(models, agent.name, agent.tier);
1016
1061
  lines.push(`model: ${tierModel.model}`);
1017
1062
  if (tierModel.variant) lines.push(`variant: ${tierModel.variant}`);
1018
- lines.push("permission:");
1019
- lines.push(` edit: ${agent.readonly ? "deny" : "allow"}`);
1063
+ if (agent.readonly || agent.bash !== "full" || !agent.spawn) lines.push("permission:");
1064
+ if (agent.readonly) lines.push(" edit: deny");
1020
1065
  if (agent.bash === "none") lines.push(" bash: deny");
1021
- else if (agent.bash === "git-read") lines.push(' bash:\n "git diff*": allow\n "git log*": allow');
1022
- else {
1023
- const deny = DESTRUCTIVE_GIT_DENY.map((p7) => ` ${yamlString(p7)}: deny`).join("\n");
1024
- lines.push(` bash:
1025
- "*": allow
1026
- ${deny}`);
1066
+ else if (agent.bash === "git-read") {
1067
+ lines.push(' bash:\n "*": deny');
1068
+ for (const command of gitReadCommands) {
1069
+ lines.push(` ${yamlString(command)}: allow`, ` ${yamlString(`${command} *`)}: allow`);
1070
+ }
1071
+ const permission = objectValue(loadCanonicalDefaults(stackRoot())["opencode"]?.permission);
1072
+ const bash = objectValue(permission?.bash);
1073
+ if (bash === null) throw new Error("OpenCode: canonical Bash policy is required for git-read agents.");
1074
+ for (const [pattern, decision] of Object.entries(bash)) {
1075
+ if (decision === "deny") lines.push(` ${yamlString(pattern)}: deny`);
1076
+ }
1027
1077
  }
1028
1078
  if (!agent.spawn) lines.push(" task: deny");
1029
1079
  }
@@ -1033,7 +1083,11 @@ ${deny}`);
1033
1083
  content: `---
1034
1084
  ${lines.join("\n")}
1035
1085
  ---
1036
- ${agent.body}`,
1086
+ ${agent.body}${agent.bash === "git-read" ? `
1087
+
1088
+ Use only these read-only Git command prefixes; put refs and paths after --end-of-options:
1089
+ ${gitReadCommands.map((command) => `- \`${command}\``).join("\n")}
1090
+ ` : ""}`,
1037
1091
  kind: "agent"
1038
1092
  }
1039
1093
  ];
@@ -1113,12 +1167,19 @@ ${agent.body}`,
1113
1167
  planMainConfig(canonical, ctx) {
1114
1168
  const file = path9.join(ctx.configDir, "opencode.json");
1115
1169
  const { pluginsDir } = this.paths(ctx.configDir);
1116
- const original = readTextIfExists(file);
1170
+ const original = readMcpConfig(file);
1117
1171
  const contentSource = original === null || original.trim() === "" ? null : original;
1118
1172
  const isFreshConfig = contentSource === null;
1119
1173
  const mcpOwnership = [];
1120
1174
  const primaryModelOwnership = [];
1121
1175
  const content = upsertJson(contentSource, (root) => {
1176
+ const rawMcp = root["mcp"];
1177
+ if (rawMcp !== void 0 && objectValue(rawMcp) === null) {
1178
+ throw new Error("OpenCode: la clave 'mcp' debe ser un objeto; corr\xEDgela antes de reintentar sync.");
1179
+ }
1180
+ const existingMcp = rawMcp;
1181
+ const context7 = canonical.servers.context7;
1182
+ if (context7 !== void 0) assertCompatibleContext7(context7, existingMcp?.["context7"]);
1122
1183
  root["$schema"] ??= "https://opencode.ai/config.json";
1123
1184
  if (root[PRIMARY_MODEL_FIELD] === void 0) {
1124
1185
  root[PRIMARY_MODEL_FIELD] = PRIMARY_MODEL;
@@ -1145,13 +1206,19 @@ ${agent.body}`,
1145
1206
  if (isFreshConfig && defaults?.["permission"] !== void 0) {
1146
1207
  root["permission"] = defaults["permission"];
1147
1208
  ctx.warnings.push(
1148
- "OpenCode: fresh config enables read-anywhere via external_directory:*; edits, web egress and arbitrary bash remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
1209
+ "OpenCode: fresh config allows ordinary reads, edits, web access and Bash; sensitive operations ask, while protected paths and obvious destruction are denied. Native matching is not a universal filesystem sandbox."
1149
1210
  );
1150
1211
  }
1151
1212
  const mcp = root["mcp"] ??= {};
1152
1213
  for (const [name, server] of Object.entries(canonical.servers)) {
1153
1214
  const existing = mcp[name];
1154
1215
  const owned = ctx.ownedMcpServers?.has(name) === true;
1216
+ if (name === "context7" && existing !== void 0) {
1217
+ if (owned && !isCanonicalContext7Server(server, existing)) {
1218
+ mcpOwnership.push({ server: name, owned: false });
1219
+ }
1220
+ continue;
1221
+ }
1155
1222
  if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
1156
1223
  if (owned) {
1157
1224
  if (isManagedOptionalStdioServer(server, existing)) delete mcp[name];
@@ -1188,6 +1255,9 @@ ${agent.body}`,
1188
1255
  url: server.url,
1189
1256
  ...Object.keys(headers).length > 0 ? { headers } : {}
1190
1257
  };
1258
+ if (name === "context7" && existing === void 0 && !owned) {
1259
+ mcpOwnership.push({ server: name, owned: true });
1260
+ }
1191
1261
  }
1192
1262
  }
1193
1263
  if (pluginsDir !== null) {
@@ -1222,7 +1292,7 @@ ${agent.body}`,
1222
1292
  actions.push({ kind: "write", target: systemPromptFile, content });
1223
1293
  }
1224
1294
  const configFile = path9.join(ctx.configDir, "opencode.json");
1225
- const config = readTextIfExists(configFile);
1295
+ const config = readMcpConfig(configFile);
1226
1296
  if (config !== null) {
1227
1297
  const mcpOwnership = [];
1228
1298
  const primaryModelOwnership = [];
@@ -1259,9 +1329,22 @@ ${agent.body}`,
1259
1329
  for (const field of managedFields) {
1260
1330
  if (ctx.ownedPrimaryModelFields?.has(field) === true) primaryModelOwnership.push({ field, owned: false });
1261
1331
  }
1262
- const mcpBlock = root["mcp"];
1263
- if (mcpBlock) {
1332
+ const rawMcpBlock = root["mcp"];
1333
+ if (rawMcpBlock !== void 0 && objectValue(rawMcpBlock) === null) {
1334
+ throw new Error("OpenCode: la clave 'mcp' debe ser un objeto; corr\xEDgela antes de reintentar uninstall.");
1335
+ }
1336
+ const mcpBlock = rawMcpBlock;
1337
+ if (mcpBlock !== void 0) {
1264
1338
  for (const [name, server] of Object.entries(mcp.servers)) {
1339
+ if (name === "context7") {
1340
+ const canonical = isCanonicalContext7Server(server, mcpBlock[name]);
1341
+ const owned = ctx.ownedMcpServers?.has(name) === true;
1342
+ if (owned) {
1343
+ if (canonical) delete mcpBlock[name];
1344
+ mcpOwnership.push({ server: name, owned: false });
1345
+ }
1346
+ continue;
1347
+ }
1265
1348
  if (!server.optional) {
1266
1349
  delete mcpBlock[name];
1267
1350
  continue;
@@ -1318,6 +1401,11 @@ ${agent.body}`,
1318
1401
  import path10 from "path";
1319
1402
  import fs7 from "fs";
1320
1403
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1404
+
1405
+ // src/lib/git-guard.ts
1406
+ var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
1407
+
1408
+ // src/adapters/claude-code.ts
1321
1409
  function yamlString2(value) {
1322
1410
  return JSON.stringify(value);
1323
1411
  }
@@ -1331,6 +1419,47 @@ var ENGRAM_AGENT_TOOLS = [
1331
1419
  function isRecord(value) {
1332
1420
  return value !== null && typeof value === "object" && !Array.isArray(value);
1333
1421
  }
1422
+ function readMcpConfig2(file) {
1423
+ let content;
1424
+ try {
1425
+ content = fs7.readFileSync(file, "utf8");
1426
+ } catch (error) {
1427
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
1428
+ if (code === "ENOENT") return null;
1429
+ throw new Error(`Claude Code: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
1430
+ }
1431
+ if (content.trim() !== "") {
1432
+ try {
1433
+ if (!isRecord(JSON.parse(content))) throw new Error();
1434
+ } catch {
1435
+ throw new Error(`Claude Code: la configuraci\xF3n MCP en ${file} debe contener un objeto JSON v\xE1lido.`);
1436
+ }
1437
+ }
1438
+ return content;
1439
+ }
1440
+ function isCompatibleContext7Server2(server, value) {
1441
+ if (server.transport !== "http" || typeof server.url !== "string" || !isRecord(value)) return false;
1442
+ return value.type === "http" && value.url === server.url;
1443
+ }
1444
+ function canonicalContext7Server2(server) {
1445
+ const headers = {};
1446
+ for (const [key, raw] of Object.entries(server.headers ?? {})) {
1447
+ headers[key] = /^\$\{(\w+)\}$/.test(raw) ? "" : raw;
1448
+ }
1449
+ return {
1450
+ type: "http",
1451
+ url: server.url,
1452
+ ...Object.keys(headers).length > 0 ? { headers } : {}
1453
+ };
1454
+ }
1455
+ function isCanonicalContext7Server2(server, value) {
1456
+ return isCompatibleContext7Server2(server, value) && isDeepStrictEqual2(value, canonicalContext7Server2(server));
1457
+ }
1458
+ function assertCompatibleContext72(server, value) {
1459
+ if (value !== void 0 && !isCompatibleContext7Server2(server, value)) {
1460
+ throw new Error("Claude Code: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint o tipo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
1461
+ }
1462
+ }
1334
1463
  function hasClaudeManualApproval(configDir) {
1335
1464
  const content = readTextIfExists(path10.join(configDir, "settings.json"));
1336
1465
  if (content === null) return false;
@@ -1476,11 +1605,24 @@ ${agent.body}`,
1476
1605
  planMainConfig(canonical, ctx) {
1477
1606
  const file = path10.join(path10.dirname(ctx.configDir), `${path10.basename(ctx.configDir)}.json`);
1478
1607
  const mcpOwnership = [];
1479
- const content = upsertJson(readTextIfExists(file), (root) => {
1608
+ const content = upsertJson(readMcpConfig2(file), (root) => {
1609
+ const rawServers = root["mcpServers"];
1610
+ if (rawServers !== void 0 && !isRecord(rawServers)) {
1611
+ throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar sync.");
1612
+ }
1613
+ const existingServers = rawServers;
1614
+ const context7 = canonical.servers.context7;
1615
+ if (context7 !== void 0) assertCompatibleContext72(context7, existingServers?.["context7"]);
1480
1616
  const servers = root["mcpServers"] ??= {};
1481
1617
  for (const [name, server] of Object.entries(canonical.servers)) {
1482
1618
  const existing = servers[name];
1483
1619
  const owned = ctx.ownedMcpServers?.has(name) === true;
1620
+ if (name === "context7" && existing !== void 0) {
1621
+ if (owned && !isCanonicalContext7Server2(server, existing)) {
1622
+ mcpOwnership.push({ server: name, owned: false });
1623
+ }
1624
+ continue;
1625
+ }
1484
1626
  if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
1485
1627
  if (owned) {
1486
1628
  if (isManagedOptionalStdioServer2(server, existing)) delete servers[name];
@@ -1524,6 +1666,9 @@ ${agent.body}`,
1524
1666
  url: server.url,
1525
1667
  ...Object.keys(headers).length > 0 ? { headers } : {}
1526
1668
  };
1669
+ if (name === "context7" && existing === void 0 && !owned) {
1670
+ mcpOwnership.push({ server: name, owned: true });
1671
+ }
1527
1672
  }
1528
1673
  }
1529
1674
  });
@@ -1546,13 +1691,26 @@ ${agent.body}`,
1546
1691
  }
1547
1692
  }
1548
1693
  const mainFile = path10.join(path10.dirname(ctx.configDir), `${path10.basename(ctx.configDir)}.json`);
1549
- const main2 = readTextIfExists(mainFile);
1694
+ const main2 = readMcpConfig2(mainFile);
1550
1695
  if (main2 !== null) {
1551
1696
  const mcpOwnership = [];
1552
1697
  const content = upsertJson(main2, (root) => {
1553
- const servers = root["mcpServers"];
1554
- if (!servers) return;
1698
+ const rawServers = root["mcpServers"];
1699
+ if (rawServers === void 0) return;
1700
+ if (!isRecord(rawServers)) {
1701
+ throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar uninstall.");
1702
+ }
1703
+ const servers = rawServers;
1555
1704
  for (const [name, server] of Object.entries(mcp.servers)) {
1705
+ if (name === "context7") {
1706
+ const canonical = isCanonicalContext7Server2(server, servers[name]);
1707
+ const owned = ctx.ownedMcpServers?.has(name) === true;
1708
+ if (owned) {
1709
+ if (canonical) delete servers[name];
1710
+ mcpOwnership.push({ server: name, owned: false });
1711
+ }
1712
+ continue;
1713
+ }
1556
1714
  if (!server.optional) {
1557
1715
  delete servers[name];
1558
1716
  continue;
@@ -1576,6 +1734,15 @@ import fs8 from "fs";
1576
1734
  function tomlString(value) {
1577
1735
  return JSON.stringify(value);
1578
1736
  }
1737
+ function readMcpConfig3(file) {
1738
+ try {
1739
+ return fs8.readFileSync(file, "utf8");
1740
+ } catch (error) {
1741
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
1742
+ if (code === "ENOENT") return null;
1743
+ throw new Error(`Codex: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
1744
+ }
1745
+ }
1579
1746
  var PRIMARY_MODEL2 = '"gpt-5.6-sol"';
1580
1747
  var PRIMARY_CONTEXT_WINDOW = "872000";
1581
1748
  var PRIMARY_MODEL_FIELD2 = "model";
@@ -1612,6 +1779,67 @@ var CODEX_JSON_STRING_ARRAY = String.raw`\[(?:\s*${CODEX_JSON_STRING}(?:\s*,\s*$
1612
1779
  var CODEX_KEY = String.raw`(?:[A-Za-z0-9_-]+|${CODEX_JSON_STRING})`;
1613
1780
  var CODEX_ASSIGNMENT = new RegExp(String.raw`^\s*(${CODEX_KEY})\s*=\s*(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})\s*(?:#.*)?$`);
1614
1781
  var CODEX_HEADER = new RegExp(String.raw`^\[\s*(${CODEX_KEY}(?:\s*\.\s*${CODEX_KEY})*)\s*\]\s*(?:#.*)?$`);
1782
+ function tomlAssignment(section, key) {
1783
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1784
+ const keyPattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
1785
+ const lines = section.split(/\r?\n/);
1786
+ const mask = multilineStringMask(lines);
1787
+ for (const [index, line] of lines.entries()) {
1788
+ if (mask[index] || !keyPattern.test(line)) continue;
1789
+ const match = CODEX_ASSIGNMENT.exec(line);
1790
+ if (match) return { present: true, raw: match[2] };
1791
+ const equals = line.indexOf("=");
1792
+ const raw = equals === -1 ? void 0 : line.slice(equals + 1).trim().replace(/\s+#.*$/, "");
1793
+ return { present: true, ...raw?.startsWith("'") && raw.endsWith("'") ? { raw } : {} };
1794
+ }
1795
+ return { present: false };
1796
+ }
1797
+ function parseTomlString(raw) {
1798
+ if (raw === void 0) return void 0;
1799
+ const value = raw.trim();
1800
+ if (value.startsWith('"')) {
1801
+ try {
1802
+ const parsed = JSON.parse(value);
1803
+ return typeof parsed === "string" ? parsed : void 0;
1804
+ } catch {
1805
+ return void 0;
1806
+ }
1807
+ }
1808
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
1809
+ return value.slice(1, -1);
1810
+ }
1811
+ return void 0;
1812
+ }
1813
+ function context7HttpSection(server) {
1814
+ const body = [`url = ${tomlString(server.url)}`];
1815
+ const envHeaders = [];
1816
+ const literalHeaders = [];
1817
+ for (const [key, raw] of Object.entries(server.headers ?? {})) {
1818
+ const envRef = /^\$\{(\w+)\}$/.exec(raw);
1819
+ if (envRef) envHeaders.push(`${tomlString(key)} = ${tomlString(envRef[1])}`);
1820
+ else literalHeaders.push(`${tomlString(key)} = ${tomlString(raw)}`);
1821
+ }
1822
+ if (literalHeaders.length > 0) body.push(`http_headers = { ${literalHeaders.join(", ")} }`);
1823
+ if (envHeaders.length > 0) body.push(`env_http_headers = { ${envHeaders.join(", ")} }`);
1824
+ return body.join("\n");
1825
+ }
1826
+ function isCompatibleContext7Server3(server, section) {
1827
+ if (server.transport !== "http" || typeof server.url !== "string" || section === null) return false;
1828
+ const url = tomlAssignment(section, "url");
1829
+ const command = tomlAssignment(section, "command");
1830
+ const type = tomlAssignment(section, "type");
1831
+ const enabled = tomlAssignment(section, "enabled");
1832
+ const declaredType = type.present ? parseTomlString(type.raw) : "http";
1833
+ return url.present && parseTomlString(url.raw) === server.url && !command.present && declaredType === "http" && (!enabled.present || enabled.raw?.trim() === "true");
1834
+ }
1835
+ function isCanonicalContext7Server3(server, section, config) {
1836
+ return isCompatibleContext7Server3(server, section) && section.trim() === context7HttpSection(server) && !hasTomlChildSection(config, "mcp_servers.context7");
1837
+ }
1838
+ function assertCompatibleContext73(server, section, config) {
1839
+ if (section === null && hasTomlChildSection(config, "mcp_servers.context7") || section !== null && !isCompatibleContext7Server3(server, section)) {
1840
+ throw new Error("Codex: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint, tipo o estado nativo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
1841
+ }
1842
+ }
1615
1843
  var CODEX_PERMISSION_HEADERS = {
1616
1844
  base: "permissions.jorgex-read-anywhere",
1617
1845
  filesystem: "permissions.jorgex-read-anywhere.filesystem",
@@ -1851,11 +2079,15 @@ ${body}`
1851
2079
  },
1852
2080
  planMainConfig(canonical, ctx) {
1853
2081
  const file = path11.join(ctx.configDir, "config.toml");
1854
- const original = readTextIfExists(file);
2082
+ const original = readMcpConfig3(file);
1855
2083
  const contentSource = original === null || original.trim() === "" ? null : original;
1856
2084
  let content = contentSource;
1857
2085
  const mcpOwnership = [];
1858
2086
  const primaryModelOwnership = [];
2087
+ const context7 = canonical.servers.context7;
2088
+ if (context7 !== void 0) {
2089
+ assertCompatibleContext73(context7, readTomlSection(contentSource, "mcp_servers.context7"), contentSource);
2090
+ }
1859
2091
  if (contentSource === null) {
1860
2092
  const defaults = loadCanonicalDefaults(ctx.stackDir)["codex"] ?? {};
1861
2093
  for (const [key, value] of Object.entries(defaults)) {
@@ -1899,6 +2131,12 @@ ${body}`
1899
2131
  const section = `mcp_servers.${name}`;
1900
2132
  const existing = readTomlSection(content, section);
1901
2133
  const owned = ctx.ownedMcpServers?.has(name) === true;
2134
+ if (name === "context7" && existing !== null) {
2135
+ if (owned && !isCanonicalContext7Server3(server, existing, content)) {
2136
+ mcpOwnership.push({ server: name, owned: false });
2137
+ }
2138
+ continue;
2139
+ }
1902
2140
  if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
1903
2141
  if (owned) {
1904
2142
  if (isManagedOptionalStdioServer3(server, existing)) content = removeTomlSection(content, section);
@@ -1947,6 +2185,9 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1947
2185
  if (literalPairs.length > 0) body.push(`http_headers = { ${literalPairs.join(", ")} }`);
1948
2186
  if (refPairs.length > 0) body.push(`env_http_headers = { ${refPairs.join(", ")} }`);
1949
2187
  content = upsertTomlSection(content, section, body.join("\n"));
2188
+ if (name === "context7" && existing === null && !owned) {
2189
+ mcpOwnership.push({ server: name, owned: true });
2190
+ }
1950
2191
  }
1951
2192
  }
1952
2193
  if (content === null) return [];
@@ -1968,7 +2209,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1968
2209
  actions.push({ kind: "write", target: systemPromptFile, content });
1969
2210
  }
1970
2211
  const configFile = path11.join(ctx.configDir, "config.toml");
1971
- const config = readTextIfExists(configFile);
2212
+ const config = readMcpConfig3(configFile);
1972
2213
  if (config !== null) {
1973
2214
  let content = config;
1974
2215
  const primaryModelOwnership = [];
@@ -1983,6 +2224,18 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
1983
2224
  const mcpOwnership = [];
1984
2225
  for (const [name, server] of Object.entries(mcp.servers)) {
1985
2226
  const section = `mcp_servers.${name}`;
2227
+ if (name === "context7") {
2228
+ const current = readTomlSection(content, section);
2229
+ const canonical = isCanonicalContext7Server3(server, current, content);
2230
+ const owned = ctx.ownedMcpServers?.has(name) === true;
2231
+ if (owned) {
2232
+ if (canonical) {
2233
+ content = removeTomlSection(content, section);
2234
+ }
2235
+ mcpOwnership.push({ server: name, owned: false });
2236
+ }
2237
+ continue;
2238
+ }
1986
2239
  if (!server.optional) {
1987
2240
  content = removeTomlSection(content, section);
1988
2241
  continue;
@@ -2772,7 +3025,7 @@ function enabledMcpServers(runtime, explicitDevtoolsEnabled, useBrowserPreferenc
2772
3025
  function ownedMcpServers(runtime, useBrowserPreferences = true) {
2773
3026
  if (!useBrowserPreferences) return /* @__PURE__ */ new Set();
2774
3027
  const file = devtoolsMcpPreferenceFile();
2775
- return loadDevtoolsMcpOwnership(file, runtime, DEVTOOLS_MCP_SERVER) ? /* @__PURE__ */ new Set([DEVTOOLS_MCP_SERVER]) : /* @__PURE__ */ new Set();
3028
+ return new Set([DEVTOOLS_MCP_SERVER, "context7"].filter((server) => loadDevtoolsMcpOwnership(file, runtime, server)));
2776
3029
  }
2777
3030
  function persistConfigurationOwnershipChanges(runtime, configDir, plan) {
2778
3031
  const latest = /* @__PURE__ */ new Map();
@@ -2804,17 +3057,29 @@ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE,
2804
3057
  ownedPrimaryModelFields: useBrowserPreferences ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), adapter.id, configDir) : /* @__PURE__ */ new Set()
2805
3058
  };
2806
3059
  }
2807
- function buildPlan(adapter, ctx) {
3060
+ function buildContentPlan(adapter, ctx) {
2808
3061
  return [
2809
3062
  ...planSystemPrompt(adapter, ctx),
2810
3063
  ...planAgents(adapter, ctx),
2811
3064
  ...planSkills(adapter, ctx),
2812
3065
  ...planCommands(adapter, ctx),
2813
3066
  ...planHooks(adapter, ctx),
2814
- ...planMcp(adapter, ctx),
2815
3067
  ...planPlugins(adapter, ctx)
2816
3068
  ];
2817
3069
  }
3070
+ function buildPlan(adapter, ctx) {
3071
+ return [...planMcp(adapter, ctx), ...buildContentPlan(adapter, ctx)];
3072
+ }
3073
+ function preflightSelectedMcpConfigs(runtimes, targetDir) {
3074
+ for (const id of runtimes) {
3075
+ const adapter = ADAPTERS[id];
3076
+ if (!adapter) continue;
3077
+ const detection = adapter.detect();
3078
+ if (targetDir === void 0 && !detection.installed) continue;
3079
+ const ctx = makeContext(adapter, targetDir ?? detection.configDir, void 0, targetDir === void 0);
3080
+ if (ctx) planMcp(adapter, ctx);
3081
+ }
3082
+ }
2818
3083
  function diffPlan(plan) {
2819
3084
  return plan.map((action) => {
2820
3085
  if (action.kind === "write") {
@@ -2873,6 +3138,13 @@ async function runInstall(opts) {
2873
3138
  p.log.error(error instanceof Error ? error.message : String(error));
2874
3139
  return 1;
2875
3140
  }
3141
+ const modelMap = opts.runtimes.length > 0 ? loadModelMap() : {};
3142
+ try {
3143
+ preflightSelectedMcpConfigs(opts.runtimes, opts.targetDir);
3144
+ } catch (error) {
3145
+ p.log.error(error instanceof Error ? error.message : String(error));
3146
+ return 1;
3147
+ }
2876
3148
  let writingStyle;
2877
3149
  let preparedStyle;
2878
3150
  try {
@@ -2902,7 +3174,6 @@ async function runInstall(opts) {
2902
3174
  const playwrightCapability = opts.dryRun || !useManifest ? void 0 : opts.playwrightCapability ?? (shouldInspectPlaywright ? inspectPlaywrightCapability() : void 0);
2903
3175
  const effectivePlaywright = playwrightCapability?.effective;
2904
3176
  const plannedPlaywright = effectivePlaywright ?? (toolPlan !== null && toolPlan.actions.length > 0 ? false : void 0);
2905
- const modelMap = hasFileRuntimes ? loadModelMap() : {};
2906
3177
  if (preparedStyle !== void 0) {
2907
3178
  try {
2908
3179
  p.log.info(`Estilo de escritura: ${preparedStyle.sourcePath}${opts.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
@@ -2967,7 +3238,7 @@ async function runInstall(opts) {
2967
3238
  let diff = diffPlan(plan);
2968
3239
  let creates = diff.filter((d) => d.status === "create");
2969
3240
  let updates = diff.filter((d) => d.status === "update");
2970
- let changes = [...creates, ...updates];
3241
+ let changes = diff.filter((change) => change.status !== "unchanged");
2971
3242
  const prevManifest = useManifest ? readManifest().runtimes[id] : void 0;
2972
3243
  const orphans = canOrphan && prevManifest ? findOrphans(prevManifest.owned, current.targets) : [];
2973
3244
  p.log.step(`${adapter.name} \u2192 ${configDir}`);
@@ -3014,7 +3285,7 @@ async function runInstall(opts) {
3014
3285
  diff = diffPlan(plan);
3015
3286
  creates = diff.filter((d) => d.status === "create");
3016
3287
  updates = diff.filter((d) => d.status === "update");
3017
- changes = [...creates, ...updates];
3288
+ changes = diff.filter((change) => change.status !== "unchanged");
3018
3289
  }
3019
3290
  const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
3020
3291
  if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
@@ -3185,14 +3456,11 @@ var piAdapter = {
3185
3456
  injectEngramProtocol() {
3186
3457
  return true;
3187
3458
  },
3188
- // Pi 0.8.18 recompone estos marcadores desde su snapshot: no conoce los nuevos.
3459
+ // La guía Context7 se habilita al adoptar su registro HTTP en Pi.
3189
3460
  adaptSystemPromptSections(sections) {
3190
- const { context7, playwright, "chrome-devtools": devtools, ...legacy } = sections;
3191
- return {
3192
- ...legacy,
3193
- "system-prompt": [legacy["system-prompt"], context7].filter(Boolean).join("\n\n"),
3194
- browser: [playwright, devtools].filter(Boolean).join("\n\n")
3195
- };
3461
+ const modular = { ...sections };
3462
+ delete modular.context7;
3463
+ return modular;
3196
3464
  }
3197
3465
  };
3198
3466
 
@@ -4275,7 +4543,7 @@ async function runUninstall(opts) {
4275
4543
  if (!detection.installed) continue;
4276
4544
  const keepCtx = makeContext(keep, detection.configDir);
4277
4545
  if (!keepCtx) continue;
4278
- for (const action of buildPlan(keep, keepCtx)) retained.add(path26.resolve(action.target));
4546
+ for (const action of buildContentPlan(keep, keepCtx)) retained.add(path26.resolve(action.target));
4279
4547
  }
4280
4548
  }
4281
4549
  for (const id of opts.runtimes) {
@@ -4293,13 +4561,20 @@ async function runUninstall(opts) {
4293
4561
  const ctx = makeContext(adapter, configDir, void 0, useBrowserPreferences);
4294
4562
  if (!ctx) continue;
4295
4563
  ctx.preserveEngram = !removeEngram;
4296
- const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
4564
+ let unmerge;
4565
+ try {
4566
+ unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
4567
+ } catch (error) {
4568
+ p2.log.error(`${adapter.name}: no se pudo planificar la limpieza en ${configDir} \u2014 ${error instanceof Error ? error.message : String(error)}.`);
4569
+ exitCode = 1;
4570
+ continue;
4571
+ }
4297
4572
  const mergedTargets = new Set(unmerge.map((a) => path26.resolve(a.target)));
4298
4573
  const usingRealConfig = opts.targetDir === void 0;
4299
4574
  const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
4300
4575
  const pruneRoot = usingRealConfig ? HOME : path26.dirname(configDir);
4301
4576
  const planTargets = [
4302
- .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path26.resolve(a.target)), ...prevOwned.map((t) => path26.resolve(t))])
4577
+ .../* @__PURE__ */ new Set([...buildContentPlan(adapter, ctx).map((a) => path26.resolve(a.target)), ...prevOwned.map((t) => path26.resolve(t))])
4303
4578
  ].filter((t) => !mergedTargets.has(t) && fs19.existsSync(t));
4304
4579
  const deleteTargets = planTargets.filter(
4305
4580
  (t) => !retained.has(t) && !(ctx.preserveEngram && path26.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
@@ -4569,7 +4844,7 @@ async function runDoctor(options = {}) {
4569
4844
  pending = diffPlan(buildPlan(adapter, ctx)).filter((d) => d.status !== "unchanged").length;
4570
4845
  } catch (err) {
4571
4846
  p3.log.error(
4572
- `${adapter.name}: config ilegible en ${detection.configDir} \u2014 ${err instanceof Error ? err.message : err}`
4847
+ `${adapter.name}: configuraci\xF3n incompatible o ilegible en ${detection.configDir} \u2014 ${err instanceof Error ? err.message : err}`
4573
4848
  );
4574
4849
  problems++;
4575
4850
  continue;
@@ -5856,16 +6131,16 @@ import { createHash as createHash2 } from "crypto";
5856
6131
  var pi_runtime_pin_default = {
5857
6132
  package: {
5858
6133
  name: "jorgex-pi",
5859
- version: "0.8.18",
5860
- source: "npm:jorgex-pi@0.8.18"
6134
+ version: "0.8.20",
6135
+ source: "npm:jorgex-pi@0.8.20"
5861
6136
  },
5862
6137
  provenance: {
5863
- commit: "61d676b300707d61840165616f0880d8c6588ce7"
6138
+ commit: "c4d021382cd3b752eaa396044753ac7533b6645d"
5864
6139
  },
5865
6140
  tarball: {
5866
- bytes: 89122709,
5867
- sha256: "02e5577d95d19ae28de55c170d4b6b48e682fbe741c8eec26cd79587da2f829e",
5868
- sha512: "a9fa60be18b6a84dca6703bf38a1462482dd3ea00d9eb82aead06e9a86b3d92ce09b68da2968cb760d6b3ad8d5b2b36d53889bed227e7ea6791932ce25153220"
6141
+ bytes: 89127646,
6142
+ sha256: "c7bbb0dc587b10e8e04174589cc519ad7576d2cbbceb5d2d20614d313e1ee884",
6143
+ sha512: "189480a4d812048b65466156479d1d89f7fa7044471ff6a076b710c0014b0e50eaa0261d7a2a8381a5e71a598be61fe2ed714b6b1dacd5a5f955b59a412009c0"
5869
6144
  }
5870
6145
  };
5871
6146
 
@@ -5880,6 +6155,7 @@ var PI_RUNTIME_CANDIDATE = {
5880
6155
  capabilities: [
5881
6156
  "foundation-contract-v1",
5882
6157
  "stack-snapshot-v2",
6158
+ "modular-system-prompts-v1",
5883
6159
  "runtime-agents-v1",
5884
6160
  "permission-gated-tools-v1",
5885
6161
  "structured-questions-v1",
@@ -5887,6 +6163,7 @@ var PI_RUNTIME_CANDIDATE = {
5887
6163
  "goal-continuation-v1",
5888
6164
  "mcp-adapter-v1",
5889
6165
  "engram-runtime-tools-v1",
6166
+ "context7-http-v1",
5890
6167
  "chrome-devtools-handoff-v1",
5891
6168
  "playwright-handoff-v1",
5892
6169
  "runner-json-v1",
@@ -7573,6 +7850,7 @@ Flags disponibles: jorgex-stack --help`
7573
7850
  p6.intro(`jorgex-stack ${command}${flags.dryRun ? " (dry-run)" : ""}`);
7574
7851
  try {
7575
7852
  assertSelectedPromptFiles(runtimes, flags.targetDir);
7853
+ preflightSelectedMcpConfigs(fileRuntimes, flags.targetDir);
7576
7854
  const writingStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: flags.targetDir }), { rootDir: flags.targetDir });
7577
7855
  if (flags.targetDir === void 0) {
7578
7856
  const errors = browserPreferenceErrors();