jorgex-stack 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,16 +8,24 @@ Portable multi-agent harness: one configuration source — 15 agents, 17 skills,
8
8
 
9
9
  Install and run via npm without cloning the repository:
10
10
 
11
+ ```bash
12
+ # First installation
13
+ pnpm dlx jorgex-stack install
14
+
15
+ # Already installed: apply the latest published stack while keeping the existing model selection
16
+ pnpm dlx jorgex-stack sync
11
17
  ```
12
- pnpm dlx jorgex-stack install # install runtimes; first OpenCode setup selects connected models
13
- pnpm dlx jorgex-stack models # model picker by runtime and tier (strong/standard/cheap)
14
- pnpm dlx jorgex-stack sync # alias of install (same idempotent apply)
15
- pnpm dlx jorgex-stack doctor # checks that everything is healthy (Engram, drift, hooks, keys)
16
- pnpm dlx jorgex-stack update # interactive: scans stack + Engram, multiselect, diff/confirm
17
- # With --check: report only, no changes
18
- # With --yes: batch mode (report only)
19
- pnpm dlx jorgex-stack restore # restores a backup
20
- pnpm dlx jorgex-stack uninstall # uninstalls our files and keeps user data (Engram intact)
18
+
19
+ Other important commands:
20
+
21
+ ```bash
22
+ pnpm dlx jorgex-stack doctor # check Engram, config drift, hooks and keys
23
+ pnpm dlx jorgex-stack models # change models by runtime, tier or subagent
24
+ pnpm dlx jorgex-stack update --check # report available stack, Engram and skill updates
25
+ pnpm dlx jorgex-stack update # interactively review and apply available updates
26
+ pnpm dlx jorgex-stack restore --list # list automatic backups
27
+ pnpm dlx jorgex-stack restore <id> # restore one backup
28
+ pnpm dlx jorgex-stack uninstall # remove managed files; keep Engram data intact
21
29
  ```
22
30
 
23
31
  For development from a clone, run the same commands through `pnpm cli <command>` (see [Development](#development)).
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import * as p6 from "@clack/prompts";
5
5
  import { pathToFileURL as pathToFileURL2 } from "url";
6
6
 
7
7
  // src/install.ts
8
- import fs14 from "fs";
8
+ import fs13 from "fs";
9
9
  import path19 from "path";
10
10
  import * as p from "@clack/prompts";
11
11
 
@@ -546,8 +546,6 @@ var opencodeAdapter = {
546
546
  ${deny}`);
547
547
  }
548
548
  if (!agent.spawn) lines.push(" task: deny");
549
- lines.push("tools:");
550
- lines.push(` write: ${agent.readonly ? "false" : "true"}`);
551
549
  }
552
550
  return [
553
551
  {
@@ -803,16 +801,7 @@ description: ${yamlString2(agent.description)}
803
801
  keep-coding-instructions: true
804
802
  ---
805
803
  ${agent.body}`;
806
- const skillDescription = `${agent.description} Invoke to switch into ${agent.name} mode and apply its flow to the current task.`;
807
- const skill = `---
808
- name: ${agent.name}
809
- description: ${yamlString2(skillDescription)}
810
- ---
811
- ${agent.body}`;
812
- return [
813
- { file: `${agent.name}.md`, content: style, kind: "output-style" },
814
- { file: `${agent.name}/SKILL.md`, content: skill, kind: "skill" }
815
- ];
804
+ return [{ file: `${agent.name}.md`, content: style, kind: "output-style" }];
816
805
  }
817
806
  const lines = [`name: ${agent.name}`, `description: ${yamlString2(agent.description)}`];
818
807
  const tools = toolsFor(agent);
@@ -995,16 +984,7 @@ var codexAdapter = {
995
984
  `# Uso: codex --profile ${agent.name}`,
996
985
  `developer_instructions = ${tomlMultiline(agent.body)}`
997
986
  ];
998
- const skillDescription = `${agent.description} Invoke to switch into ${agent.name} mode and apply its flow to the current task.`;
999
- const skill = `---
1000
- name: ${agent.name}
1001
- description: ${JSON.stringify(skillDescription)}
1002
- ---
1003
- ${agent.body}`;
1004
- return [
1005
- { file: `${agent.name}.config.toml`, content: profileLines.join("\n") + "\n", kind: "profile" },
1006
- { file: `${agent.name}/SKILL.md`, content: skill, kind: "skill" }
1007
- ];
987
+ return [{ file: `${agent.name}.config.toml`, content: profileLines.join("\n") + "\n", kind: "profile" }];
1008
988
  }
1009
989
  const lines = [
1010
990
  `name = ${tomlString(agent.name)}`,
@@ -1379,13 +1359,6 @@ import path13 from "path";
1379
1359
  var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
1380
1360
  var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
1381
1361
  var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
1382
- var LEGACY_SKILL_DELEGATION_SECTION = /\n?##\s+Formato obligatorio[\s\S]*$/;
1383
- var LEGACY_DELEGATION_LINE = /- For each `→ \[agent\]: \.\.\.` line, launch the corresponding specialist\./;
1384
- var LEGACY_PROGRAMMATIC_PHRASES = [
1385
- [/\bResult contract\b/g, "strict JSON handoff"],
1386
- [/Status \/ Delegations \/ Risks/g, "status, delegations, and risks"],
1387
- [LEGACY_DELEGATION_LINE, "- Process the JSON `delegations[]` array and launch the corresponding specialist."]
1388
- ];
1389
1362
  var normalize = (value) => value.replace(/\r\n/g, "\n");
1390
1363
  function loadProgrammaticAddendum(stackDir, fileName) {
1391
1364
  return normalize(fs9.readFileSync(path13.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
@@ -1399,10 +1372,7 @@ ${addendum}
1399
1372
  `;
1400
1373
  }
1401
1374
  function stripLegacyResultContract(body) {
1402
- return LEGACY_PROGRAMMATIC_PHRASES.reduce(
1403
- (text2, [pattern, replacement]) => text2.replace(pattern, replacement),
1404
- normalize(body).replace(LEGACY_RESULT_CONTRACT_SECTION, "")
1405
- ).trimEnd();
1375
+ return normalize(body).replace(LEGACY_RESULT_CONTRACT_SECTION, "").replace(/\bResult contract\b/g, "strict JSON handoff").trimEnd();
1406
1376
  }
1407
1377
  function concurrencyRule(concurrency) {
1408
1378
  if (concurrency === "parallel") {
@@ -1422,22 +1392,13 @@ function composeProgrammaticSystemPrompt(stackDir, content, mode) {
1422
1392
  }
1423
1393
  function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
1424
1394
  if (mode !== "programmatic") return normalize(agent.body);
1425
- const fileName = agent.mode === "primary" ? "orchestrator.addendum.md" : "subagent.addendum.md";
1426
- let addendum = loadProgrammaticAddendum(stackDir, fileName);
1427
1395
  if (agent.mode === "primary") {
1428
- addendum = addendum.replace("{{CONCURRENCY_RULE}}", concurrencyRule(concurrency ?? "serial"));
1396
+ const addendum2 = loadProgrammaticAddendum(stackDir, "orchestrator.addendum.md").replace("{{CONCURRENCY_RULE}}", concurrencyRule(concurrency ?? "serial"));
1397
+ return appendAddendum(agent.body, addendum2);
1429
1398
  }
1399
+ const addendum = loadProgrammaticAddendum(stackDir, "subagent.addendum.md");
1430
1400
  return appendAddendum(stripLegacyResultContract(agent.body), addendum);
1431
1401
  }
1432
- function composeProgrammaticSkillBody(stackDir, skillPath, content, mode) {
1433
- if (mode !== "programmatic") return normalize(content);
1434
- if (skillPath !== path13.join("agent-delegation", "SKILL.md")) return normalize(content);
1435
- const base = normalize(content).replace(
1436
- "Las delegaciones van **en tu output final**, en el formato de abajo. El orquestador las lee y decide a qui\xE9n invocar.",
1437
- "Las delegaciones van como strings en el JSON final `delegations[]`. El orquestador las lee y decide a qui\xE9n invocar."
1438
- ).replace(LEGACY_SKILL_DELEGATION_SECTION, "").trimEnd();
1439
- return appendAddendum(base, loadProgrammaticAddendum(stackDir, "agent-delegation.addendum.md"));
1440
- }
1441
1402
 
1442
1403
  // src/components/system-prompt.ts
1443
1404
  var normalize2 = (s) => s.replace(/\r\n/g, "\n");
@@ -1461,12 +1422,11 @@ function planSystemPrompt(adapter, ctx) {
1461
1422
  // src/components/agents.ts
1462
1423
  import path15 from "path";
1463
1424
  function planAgents(adapter, ctx) {
1464
- const { agentsDir, commandsDir, outputStylesDir, skillsDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
1425
+ const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
1465
1426
  const dirFor = {
1466
1427
  agent: agentsDir,
1467
1428
  command: commandsDir,
1468
1429
  "output-style": outputStylesDir,
1469
- skill: skillsDir,
1470
1430
  profile: profilesDir
1471
1431
  };
1472
1432
  const scriptsBase = scriptsDir.replace(/\\/g, "/");
@@ -1489,43 +1449,35 @@ function planAgents(adapter, ctx) {
1489
1449
 
1490
1450
  // src/components/skills.ts
1491
1451
  import path16 from "path";
1492
- import fs11 from "fs";
1493
1452
  function planSkills(adapter, ctx) {
1494
1453
  const { skillsDir } = adapter.paths(ctx.configDir);
1495
1454
  const source = path16.join(ctx.stackDir, "skills");
1496
1455
  return listFilesRecursive(source).map((file) => {
1497
1456
  const relative = path16.relative(source, file);
1498
- if (relative === path16.join("agent-delegation", "SKILL.md") && ctx.mode === "programmatic") {
1499
- return {
1500
- kind: "write",
1501
- target: path16.join(skillsDir, relative),
1502
- content: composeProgrammaticSkillBody(ctx.stackDir, relative, fs11.readFileSync(file, "utf8"), ctx.mode)
1503
- };
1504
- }
1505
1457
  return { kind: "copy", source: file, target: path16.join(skillsDir, relative) };
1506
1458
  });
1507
1459
  }
1508
1460
 
1509
1461
  // src/components/commands.ts
1510
1462
  import path17 from "path";
1511
- import fs12 from "fs";
1463
+ import fs11 from "fs";
1512
1464
  function planCommands(adapter, ctx) {
1513
1465
  const { commandsDir } = adapter.paths(ctx.configDir);
1514
1466
  const source = path17.join(ctx.stackDir, "commands");
1515
- if (!fs12.existsSync(source)) return [];
1467
+ if (!fs11.existsSync(source)) return [];
1516
1468
  const commandFiles = [
1517
1469
  ...listMarkdownFiles(source),
1518
1470
  ...listMarkdownFiles(path17.join(source, adapter.id))
1519
1471
  ];
1520
1472
  return commandFiles.map(({ file, fullPath }) => {
1521
- const raw = fs12.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
1473
+ const raw = fs11.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
1522
1474
  const rendered = adapter.renderCommand(file, raw);
1523
1475
  return { kind: "write", target: path17.join(commandsDir, rendered.file), content: rendered.content };
1524
1476
  });
1525
1477
  }
1526
1478
  function listMarkdownFiles(dir) {
1527
- if (!fs12.existsSync(dir)) return [];
1528
- return fs12.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path17.join(dir, entry.name) }));
1479
+ if (!fs11.existsSync(dir)) return [];
1480
+ 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) }));
1529
1481
  }
1530
1482
 
1531
1483
  // src/components/hooks.ts
@@ -1540,22 +1492,22 @@ function planMcp(adapter, ctx) {
1540
1492
 
1541
1493
  // src/components/plugins.ts
1542
1494
  import path18 from "path";
1543
- import fs13 from "fs";
1495
+ import fs12 from "fs";
1544
1496
  function planPlugins(adapter, ctx) {
1545
1497
  const { pluginsDir } = adapter.paths(ctx.configDir);
1546
1498
  if (pluginsDir === null) return [];
1547
1499
  const source = path18.join(ctx.stackDir, "plugins", adapter.id);
1548
- if (!fs13.existsSync(source)) return [];
1500
+ if (!fs12.existsSync(source)) return [];
1549
1501
  return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
1550
1502
  const target = path18.join(pluginsDir, path18.relative(source, sourceFile));
1551
- const raw = fs13.readFileSync(sourceFile, "utf8");
1503
+ const raw = fs12.readFileSync(sourceFile, "utf8");
1552
1504
  let content = raw;
1553
1505
  if (content.includes('"{{ENGRAM_BIN}}"')) {
1554
1506
  content = content.replace(/"\{\{ENGRAM_BIN\}\}"/g, JSON.stringify(ctx.engramBin ?? "engram"));
1555
1507
  }
1556
1508
  if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
1557
1509
  const protocol = stripLeadingHtmlComments(
1558
- fs13.readFileSync(path18.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
1510
+ fs12.readFileSync(path18.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
1559
1511
  );
1560
1512
  content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
1561
1513
  }
@@ -1602,7 +1554,7 @@ function diffPlan(plan) {
1602
1554
  if (current === null) return { action, status: "create" };
1603
1555
  return { action, status: current === action.content ? "unchanged" : "update" };
1604
1556
  }
1605
- if (!fs14.existsSync(action.target)) return { action, status: "create" };
1557
+ if (!fs13.existsSync(action.target)) return { action, status: "create" };
1606
1558
  return { action, status: sameFileContent(action.source, action.target) ? "unchanged" : "update" };
1607
1559
  });
1608
1560
  }
@@ -1734,7 +1686,7 @@ async function runInstall(opts) {
1734
1686
  applyChanges(changes);
1735
1687
  const pruneRoot = useManifest ? HOME : path19.dirname(configDir);
1736
1688
  for (const orphan of orphans) {
1737
- fs14.rmSync(orphan, { force: true });
1689
+ fs13.rmSync(orphan, { force: true });
1738
1690
  pruneEmptyDirs(orphan, pruneRoot);
1739
1691
  }
1740
1692
  const verifyCtx = { ...ctx, warnings: [] };
@@ -1757,7 +1709,7 @@ async function runInstall(opts) {
1757
1709
  }
1758
1710
 
1759
1711
  // src/uninstall.ts
1760
- import fs15 from "fs";
1712
+ import fs14 from "fs";
1761
1713
  import path20 from "path";
1762
1714
  import * as p2 from "@clack/prompts";
1763
1715
  async function runUninstall(opts) {
@@ -1812,7 +1764,7 @@ async function runUninstall(opts) {
1812
1764
  const pruneRoot = usingRealConfig ? HOME : path20.dirname(configDir);
1813
1765
  const planTargets = [
1814
1766
  .../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path20.resolve(a.target)), ...prevOwned.map((t) => path20.resolve(t))])
1815
- ].filter((t) => !mergedTargets.has(t) && fs15.existsSync(t));
1767
+ ].filter((t) => !mergedTargets.has(t) && fs14.existsSync(t));
1816
1768
  const deleteTargets = planTargets.filter(
1817
1769
  (t) => !retained.has(t) && !(ctx.preserveEngram && path20.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
1818
1770
  );
@@ -1822,18 +1774,18 @@ async function runUninstall(opts) {
1822
1774
  if (sharedKept > 0) p2.log.info(`${sharedKept} archivos se conservan: otros runtimes instalados los siguen usando.`);
1823
1775
  if (opts.dryRun) continue;
1824
1776
  const backup = createBackup(
1825
- [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs15.existsSync(t))],
1777
+ [...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs14.existsSync(t))],
1826
1778
  `uninstall-${id}`
1827
1779
  );
1828
1780
  if (backup) p2.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
1829
1781
  for (const target of deleteTargets) {
1830
- fs15.rmSync(target, { force: true });
1782
+ fs14.rmSync(target, { force: true });
1831
1783
  pruneEmptyDirs(target, pruneRoot);
1832
1784
  }
1833
1785
  for (const action of unmerge) {
1834
1786
  if (action.kind !== "write") continue;
1835
1787
  if (action.content.trim() === "") {
1836
- fs15.rmSync(action.target, { force: true });
1788
+ fs14.rmSync(action.target, { force: true });
1837
1789
  } else {
1838
1790
  writeText(action.target, action.content);
1839
1791
  }
@@ -1847,7 +1799,7 @@ async function runUninstall(opts) {
1847
1799
 
1848
1800
  // src/doctor.ts
1849
1801
  import path21 from "path";
1850
- import fs16 from "fs";
1802
+ import fs15 from "fs";
1851
1803
  import * as p3 from "@clack/prompts";
1852
1804
  function engramVersion(bin) {
1853
1805
  const out = runDetectedBin(bin, ["--version"], 5e3);
@@ -1880,11 +1832,11 @@ async function runDoctor() {
1880
1832
  }
1881
1833
  const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path21.join(HOME, ".engram");
1882
1834
  const engramDb = path21.join(engramDataDir, "engram.db");
1883
- if (fs16.existsSync(engramDb)) {
1884
- const sizeMb = (fs16.statSync(engramDb).size / 1024 / 1024).toFixed(1);
1835
+ if (fs15.existsSync(engramDb)) {
1836
+ const sizeMb = (fs15.statSync(engramDb).size / 1024 / 1024).toFixed(1);
1885
1837
  p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
1886
1838
  }
1887
- if (!fs16.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
1839
+ if (!fs15.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
1888
1840
  const manifest = readManifest();
1889
1841
  const modePreference = loadInstallModePreference();
1890
1842
  const current = collectAllCurrentTargets(modePreference);
@@ -1923,10 +1875,10 @@ async function runDoctor() {
1923
1875
  p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
1924
1876
  problems++;
1925
1877
  }
1926
- if (adapter.id === "codex" && fs16.existsSync(path21.join(detection.configDir, "hooks.json"))) {
1878
+ if (adapter.id === "codex" && fs15.existsSync(path21.join(detection.configDir, "hooks.json"))) {
1927
1879
  p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
1928
1880
  }
1929
- if (adapter.id === "codex" && fs16.existsSync(path21.join(detection.configDir, "AGENTS.override.md"))) {
1881
+ if (adapter.id === "codex" && fs15.existsSync(path21.join(detection.configDir, "AGENTS.override.md"))) {
1930
1882
  p3.log.warn(
1931
1883
  "Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
1932
1884
  );
@@ -1940,14 +1892,14 @@ async function runDoctor() {
1940
1892
  }
1941
1893
 
1942
1894
  // src/update.ts
1943
- import fs19 from "fs";
1895
+ import fs18 from "fs";
1944
1896
  import path24 from "path";
1945
1897
  import os3 from "os";
1946
1898
  import { execFileSync as execFileSync4 } from "child_process";
1947
1899
  import * as p4 from "@clack/prompts";
1948
1900
 
1949
1901
  // src/lib/github.ts
1950
- import fs17 from "fs";
1902
+ import fs16 from "fs";
1951
1903
  import path22 from "path";
1952
1904
  import { execFileSync as execFileSync2 } from "child_process";
1953
1905
  import os2 from "os";
@@ -2022,7 +1974,7 @@ function validateExtractedTree(destDir) {
2022
1974
  const walk = (dir) => {
2023
1975
  let entries;
2024
1976
  try {
2025
- entries = fs17.readdirSync(dir, { withFileTypes: true });
1977
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
2026
1978
  } catch {
2027
1979
  return false;
2028
1980
  }
@@ -2030,7 +1982,7 @@ function validateExtractedTree(destDir) {
2030
1982
  const full = path22.join(dir, entry.name);
2031
1983
  let stat;
2032
1984
  try {
2033
- stat = fs17.lstatSync(full);
1985
+ stat = fs16.lstatSync(full);
2034
1986
  } catch {
2035
1987
  return false;
2036
1988
  }
@@ -2047,14 +1999,14 @@ function validateExtractedTree(destDir) {
2047
1999
  function resolveTarBin() {
2048
2000
  if (process.platform !== "win32") return "tar";
2049
2001
  const winTar = path22.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
2050
- return fs17.existsSync(winTar) ? winTar : "tar";
2002
+ return fs16.existsSync(winTar) ? winTar : "tar";
2051
2003
  }
2052
2004
  async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
2053
2005
  const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
2054
2006
  const tmp = path22.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
2055
2007
  const fail = (reason) => {
2056
2008
  try {
2057
- fs17.rmSync(destDir, { recursive: true, force: true });
2009
+ fs16.rmSync(destDir, { recursive: true, force: true });
2058
2010
  } catch {
2059
2011
  }
2060
2012
  return { ok: false, reason };
@@ -2073,10 +2025,10 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
2073
2025
  if (!res.body) return fail("respuesta HTTP sin cuerpo");
2074
2026
  await pipeline(
2075
2027
  Readable.fromWeb(res.body),
2076
- fs17.createWriteStream(tmp)
2028
+ fs16.createWriteStream(tmp)
2077
2029
  );
2078
- fs17.rmSync(destDir, { recursive: true, force: true });
2079
- fs17.mkdirSync(destDir, { recursive: true });
2030
+ fs16.rmSync(destDir, { recursive: true, force: true });
2031
+ fs16.mkdirSync(destDir, { recursive: true });
2080
2032
  try {
2081
2033
  execFileSync2(resolveTarBin(), ["-xzf", tmp, "--strip-components=1", "-C", destDir], { stdio: "pipe" });
2082
2034
  } catch (err) {
@@ -2089,30 +2041,30 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
2089
2041
  if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
2090
2042
  return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
2091
2043
  }
2092
- if (fs17.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
2044
+ if (fs16.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
2093
2045
  return fail("el \xE1rbol extra\xEDdo contiene symlinks o rutas fuera del destino");
2094
2046
  }
2095
- const validated = fs17.existsSync(validateRoot);
2047
+ const validated = fs16.existsSync(validateRoot);
2096
2048
  return { ok: true, validated };
2097
2049
  } catch (err) {
2098
2050
  const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
2099
2051
  return fail(timedOut ? "timeout de descarga (120s)" : err instanceof Error ? `fallo de red: ${err.message}` : "error desconocido");
2100
2052
  } finally {
2101
2053
  try {
2102
- fs17.rmSync(tmp, { force: true });
2054
+ fs16.rmSync(tmp, { force: true });
2103
2055
  } catch {
2104
2056
  }
2105
2057
  }
2106
2058
  }
2107
2059
 
2108
2060
  // src/lib/skill-update.ts
2109
- import fs18 from "fs";
2061
+ import fs17 from "fs";
2110
2062
  import path23 from "path";
2111
2063
  import { execFileSync as execFileSync3 } from "child_process";
2112
2064
  var PROTECTED_SKILLS = /* @__PURE__ */ new Set(["agent-delegation", "work-lifecycle", "xreview"]);
2113
2065
  function sameTextContentNormalized(a, b) {
2114
- const ba = fs18.readFileSync(a);
2115
- const bb = fs18.readFileSync(b);
2066
+ const ba = fs17.readFileSync(a);
2067
+ const bb = fs17.readFileSync(b);
2116
2068
  if (ba.equals(bb)) return true;
2117
2069
  const sa = ba.toString("utf8").replace(/\r\n/g, "\n");
2118
2070
  const sb = bb.toString("utf8").replace(/\r\n/g, "\n");
@@ -2194,7 +2146,7 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
2194
2146
  throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
2195
2147
  }
2196
2148
  const upstreamsFile = upstreamsFilePath ?? path23.join(path23.dirname(stackRoot()), "upstreams.json");
2197
- const raw = fs18.readFileSync(upstreamsFile, "utf8");
2149
+ const raw = fs17.readFileSync(upstreamsFile, "utf8");
2198
2150
  const data = JSON.parse(raw);
2199
2151
  const skillEntry = data?.skills?.[name];
2200
2152
  if (!skillEntry) {
@@ -2213,7 +2165,7 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
2213
2165
  try {
2214
2166
  const upstreamFiles = listFilesRecursive(upstreamSkillDir);
2215
2167
  for (const src of upstreamFiles) {
2216
- const st = fs18.lstatSync(src);
2168
+ const st = fs17.lstatSync(src);
2217
2169
  if (st.isSymbolicLink()) {
2218
2170
  throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
2219
2171
  }
@@ -2223,14 +2175,14 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
2223
2175
  copyFile(src, dest);
2224
2176
  }
2225
2177
  const oldDir = `${localSkillDir}.old-${process.pid}`;
2226
- if (fs18.existsSync(localSkillDir)) {
2227
- fs18.renameSync(localSkillDir, oldDir);
2178
+ if (fs17.existsSync(localSkillDir)) {
2179
+ fs17.renameSync(localSkillDir, oldDir);
2228
2180
  }
2229
- fs18.renameSync(stagingDir, localSkillDir);
2230
- fs18.rmSync(oldDir, { recursive: true, force: true });
2181
+ fs17.renameSync(stagingDir, localSkillDir);
2182
+ fs17.rmSync(oldDir, { recursive: true, force: true });
2231
2183
  } catch (err) {
2232
2184
  try {
2233
- fs18.rmSync(stagingDir, { recursive: true, force: true });
2185
+ fs17.rmSync(stagingDir, { recursive: true, force: true });
2234
2186
  } catch {
2235
2187
  }
2236
2188
  throw err;
@@ -2245,7 +2197,7 @@ function rateLimitHint(prefix) {
2245
2197
  }
2246
2198
  function loadUpstreams() {
2247
2199
  const file = path24.join(path24.dirname(stackRoot()), "upstreams.json");
2248
- return JSON.parse(fs19.readFileSync(file, "utf8"));
2200
+ return JSON.parse(fs18.readFileSync(file, "utf8"));
2249
2201
  }
2250
2202
  function skillsToScan(maintainer, upstreams) {
2251
2203
  return maintainer ? Object.keys(upstreams.skills) : [];
@@ -2358,7 +2310,7 @@ function isEngramRunning() {
2358
2310
  }
2359
2311
  }
2360
2312
  function isGitClone(projectRoot = path24.dirname(stackRoot())) {
2361
- return fs19.existsSync(path24.join(projectRoot, ".git"));
2313
+ return fs18.existsSync(path24.join(projectRoot, ".git"));
2362
2314
  }
2363
2315
  var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
2364
2316
  function resolvePnpm() {
@@ -2368,7 +2320,7 @@ function resolvePnpm() {
2368
2320
  }
2369
2321
  function cleanupTmp(dir) {
2370
2322
  try {
2371
- fs19.rmSync(dir, { recursive: true, force: true });
2323
+ fs18.rmSync(dir, { recursive: true, force: true });
2372
2324
  } catch {
2373
2325
  }
2374
2326
  }
@@ -2390,7 +2342,7 @@ function updateStackGlobal() {
2390
2342
  execFileSync4(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
2391
2343
  }
2392
2344
  async function downloadSkillToTemp(repo, sha, skillPath) {
2393
- const root = fs19.mkdtempSync(path24.join(os3.tmpdir(), "jorgex-skill-"));
2345
+ const root = fs18.mkdtempSync(path24.join(os3.tmpdir(), "jorgex-skill-"));
2394
2346
  try {
2395
2347
  const result = await downloadRepoTarball(repo, sha, root, skillPath);
2396
2348
  if (!result.ok) {
@@ -2403,10 +2355,10 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
2403
2355
  cleanupTmp(root);
2404
2356
  return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
2405
2357
  }
2406
- if (fs19.existsSync(sub)) return { dir: sub, root };
2358
+ if (fs18.existsSync(sub)) return { dir: sub, root };
2407
2359
  const lastSeg = skillPath.split("/").pop();
2408
2360
  const sub2 = path24.resolve(path24.join(root, lastSeg));
2409
- if (isContainedIn(sub2, root) && fs19.existsSync(sub2)) {
2361
+ if (isContainedIn(sub2, root) && fs18.existsSync(sub2)) {
2410
2362
  if (!validateExtractedTree(sub2)) {
2411
2363
  cleanupTmp(root);
2412
2364
  return { error: `el sub\xE1rbol "${lastSeg}" contiene symlinks o rutas fuera del destino` };
@@ -2425,11 +2377,11 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
2425
2377
  function pruneEngramDbBackups() {
2426
2378
  try {
2427
2379
  const dir = dataDir();
2428
- if (!fs19.existsSync(dir)) return;
2429
- const backups = fs19.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs19.statSync(path24.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2380
+ if (!fs18.existsSync(dir)) return;
2381
+ const backups = fs18.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs18.statSync(path24.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2430
2382
  for (const old of backups.slice(3)) {
2431
2383
  try {
2432
- fs19.rmSync(path24.join(dir, old.name));
2384
+ fs18.rmSync(path24.join(dir, old.name));
2433
2385
  } catch {
2434
2386
  }
2435
2387
  }
@@ -2437,7 +2389,7 @@ function pruneEngramDbBackups() {
2437
2389
  }
2438
2390
  }
2439
2391
  function rotateLockedBinary(binPath, sweepRoot = HOME) {
2440
- if (!fs19.existsSync(binPath)) return null;
2392
+ if (!fs18.existsSync(binPath)) return null;
2441
2393
  const dir = path24.dirname(binPath);
2442
2394
  const base = path24.basename(binPath);
2443
2395
  const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
@@ -2445,10 +2397,10 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
2445
2397
  const resolvedDir = path24.resolve(dir);
2446
2398
  if (resolvedDir === path24.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
2447
2399
  try {
2448
- for (const entry of fs19.readdirSync(dir)) {
2400
+ for (const entry of fs18.readdirSync(dir)) {
2449
2401
  if (oldPattern.test(entry)) {
2450
2402
  try {
2451
- fs19.rmSync(path24.join(dir, entry), { force: true });
2403
+ fs18.rmSync(path24.join(dir, entry), { force: true });
2452
2404
  } catch {
2453
2405
  }
2454
2406
  }
@@ -2457,7 +2409,7 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
2457
2409
  }
2458
2410
  }
2459
2411
  const rotated = path24.join(dir, `${base}.old-${Date.now()}`);
2460
- fs19.renameSync(binPath, rotated);
2412
+ fs18.renameSync(binPath, rotated);
2461
2413
  return rotated;
2462
2414
  }
2463
2415
  async function updateEngram(engramRepo, latestVersion) {
@@ -2468,7 +2420,7 @@ async function updateEngram(engramRepo, latestVersion) {
2468
2420
  }
2469
2421
  const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path24.join(HOME, ".engram");
2470
2422
  const engramDb = path24.join(engramDataDir, "engram.db");
2471
- if (fs19.existsSync(engramDb)) {
2423
+ if (fs18.existsSync(engramDb)) {
2472
2424
  const doBackup = await p4.confirm({
2473
2425
  message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
2474
2426
  initialValue: true
@@ -2477,8 +2429,8 @@ async function updateEngram(engramRepo, latestVersion) {
2477
2429
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2478
2430
  const dest = path24.join(dataDir(), `engram-db-backup-${ts}.db`);
2479
2431
  try {
2480
- if (!fs19.existsSync(dataDir())) fs19.mkdirSync(dataDir(), { recursive: true });
2481
- fs19.copyFileSync(engramDb, dest);
2432
+ if (!fs18.existsSync(dataDir())) fs18.mkdirSync(dataDir(), { recursive: true });
2433
+ fs18.copyFileSync(engramDb, dest);
2482
2434
  p4.log.success(`DB respaldada en ${dest} (la DB original NO se modifica jam\xE1s).`);
2483
2435
  pruneEngramDbBackups();
2484
2436
  } catch (err) {
@@ -2528,10 +2480,10 @@ async function updateEngram(engramRepo, latestVersion) {
2528
2480
  ["install", `github.com/Gentleman-Programming/engram/cmd/engram@v${latestVersion}`],
2529
2481
  { stdio: "inherit" }
2530
2482
  );
2531
- const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs19.existsSync(bin ?? "") });
2483
+ const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs18.existsSync(bin ?? "") });
2532
2484
  if (rollbackOk.action === "restore") {
2533
2485
  try {
2534
- fs19.renameSync(rotated, bin);
2486
+ fs18.renameSync(rotated, bin);
2535
2487
  p4.log.warn(rollbackOk.messages.onRestore);
2536
2488
  } catch {
2537
2489
  p4.log.warn(rollbackOk.messages.onRenameFail);
@@ -2539,10 +2491,10 @@ async function updateEngram(engramRepo, latestVersion) {
2539
2491
  }
2540
2492
  return true;
2541
2493
  } catch (err) {
2542
- const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs19.existsSync(bin ?? "") });
2494
+ const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs18.existsSync(bin ?? "") });
2543
2495
  if (rollbackFail.action === "restore") {
2544
2496
  try {
2545
- fs19.renameSync(rotated, bin);
2497
+ fs18.renameSync(rotated, bin);
2546
2498
  p4.log.info(rollbackFail.messages.onRestore);
2547
2499
  } catch {
2548
2500
  p4.log.error(rollbackFail.messages.onRenameFail);
@@ -3063,7 +3015,7 @@ function cancelled() {
3063
3015
  }
3064
3016
 
3065
3017
  // src/lib/release.ts
3066
- import fs20 from "fs";
3018
+ import fs19 from "fs";
3067
3019
  import path26 from "path";
3068
3020
  import { execFileSync as execFileSync5 } from "child_process";
3069
3021
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -3071,7 +3023,7 @@ function findPackageJson() {
3071
3023
  let dir = path26.dirname(fileURLToPath2(import.meta.url));
3072
3024
  for (let i = 0; i < 6; i++) {
3073
3025
  const candidate = path26.join(dir, "package.json");
3074
- if (fs20.existsSync(candidate)) return candidate;
3026
+ if (fs19.existsSync(candidate)) return candidate;
3075
3027
  dir = path26.dirname(dir);
3076
3028
  }
3077
3029
  throw new Error("No se encontr\xF3 package.json cerca del CLI.");
@@ -3081,7 +3033,7 @@ function readPackageVersion() {
3081
3033
  }
3082
3034
  function readPackageMetadata() {
3083
3035
  const packageJson = findPackageJson();
3084
- const raw = fs20.readFileSync(packageJson, "utf8");
3036
+ const raw = fs19.readFileSync(packageJson, "utf8");
3085
3037
  const parsed = JSON.parse(raw);
3086
3038
  const name = typeof parsed.name === "string" ? parsed.name.trim() : "";
3087
3039
  const version = typeof parsed.version === "string" ? parsed.version.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI y OpenCode",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  # Agentes canónicos
2
2
 
3
- Una sola fuente por agente. El instalador los traduce al formato de cada runtime (PRD §6): Markdown+frontmatter para Claude Code y OpenCode, TOML para Codex.
3
+ Una sola fuente por agente. El instalador los traduce al formato de cada runtime (PRD §6): Markdown+frontmatter para Claude Code y OpenCode, TOML para Codex. El workflow completo del orchestrator vive únicamente en `skills/orchestrator/SKILL.md`; el agente primary es un wrapper corto que obliga a cargar esa skill.
4
4
 
5
5
  ## Frontmatter canónico
6
6
 
@@ -19,7 +19,7 @@ Una sola fuente por agente. El instalador los traduce al formato de cada runtime
19
19
  | Canónico | Claude Code | Codex | OpenCode |
20
20
  |---|---|---|---|
21
21
  | `tier` | alias `fable`/`opus`/`sonnet`/`haiku` según model-map | `model` + `model_reasoning_effort` | `provider/model` del model-map |
22
- | `readonly: true` | `tools: Read, Grep, Glob` (+Bash si aplica) | `sandbox_mode = "read-only"` | `tools: { write: false }`, `permission: { edit: deny }` |
22
+ | `readonly: true` | `tools: Read, Grep, Glob` (+Bash si aplica) | `sandbox_mode = "read-only"` | `permission: { edit: deny }` |
23
23
  | `bash: git-read` | `Bash` completo en tools — la restricción a git read es solo de prompt (el frontmatter de Claude Code no tiene esa granularidad) | con `readonly: true` el sandbox read-only impide escrituras; con `readonly: false` la restricción es solo de prompt | `permission.bash: { "git diff*": allow, "git log*": allow }` (única aplicación real) |
24
24
  | `bash: none` | sin `Bash` en tools | sandbox read-only | `permission: { bash: deny }` |
25
25
  | `spawn: false` | sin tool `Agent`/`Task` | n/a | `permission: { task: deny }` |