ostacky 0.10.1 → 0.10.3

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
@@ -263,17 +263,14 @@ function initGitRepo(dir) {
263
263
  execFileSync("git", ["init"], { cwd: dir, stdio: "pipe" });
264
264
  }
265
265
  function getGlobalOpenCodeDir(opts) {
266
- const platform = opts?.platform ?? process.platform;
267
266
  const env = opts?.env ?? process.env;
268
267
  const home = opts?.home ?? homedir();
268
+ const explicit = env.OPENCODE_CONFIG_DIR?.trim();
269
+ if (explicit)
270
+ return explicit;
269
271
  const xdg = env.XDG_CONFIG_HOME?.trim();
270
272
  if (xdg)
271
273
  return join(xdg, "opencode");
272
- if (platform === "win32") {
273
- const appdata = env.APPDATA?.trim();
274
- if (appdata)
275
- return join(appdata, "opencode");
276
- }
277
274
  return join(home, ".config", "opencode");
278
275
  }
279
276
  function getOpenCodeDirForScope(scope, cwd = process.cwd()) {
@@ -641,31 +638,31 @@ var init_fs = __esm(() => {
641
638
  var manifest_default;
642
639
  var init_manifest = __esm(() => {
643
640
  manifest_default = {
644
- version: "0.10.1",
641
+ version: "0.10.3",
645
642
  repo: "JaimeHoracio/Ostacky",
646
- tag: "v0.10.1",
643
+ tag: "v0.10.3",
647
644
  agents: [
648
645
  {
649
646
  name: "ostacky",
650
647
  file: "assets/agents/ostacky.md",
651
- description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.10.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
652
- version: "0.10.1",
653
- sha256: "f82687e38d1ad2e88cce1e962299c9bd8b9265d53fe3c7997114efddb39898b3"
648
+ description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.10.3: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
649
+ version: "0.10.3",
650
+ sha256: "c8f18e81f3cfb2778656bd3e9b391d8521bec7797436a8539e1b7f0b7160597c"
654
651
  }
655
652
  ],
656
653
  commands: [
657
654
  {
658
655
  name: "install-stack",
659
656
  file: "assets/commands/install-stack.md",
660
- description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.10.1: controller plugin como alma hard-gate, Context7 removido del stack.",
661
- version: "0.10.1",
662
- sha256: "07e70cbaec57e27b5984e4a693486e34a8f52585b1183da7dd88c5dd69c7c8d5"
657
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.10.3: controller plugin como alma hard-gate, Context7 removido del stack.",
658
+ version: "0.10.3",
659
+ sha256: "eefea7d0efb9e89184bf2bbdcd8912928eb168d75e7b129526010fb1a5e81d50"
663
660
  },
664
661
  {
665
662
  name: "opsx-sync",
666
663
  file: "assets/commands/opsx-sync.md",
667
664
  description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
668
- version: "0.10.1",
665
+ version: "0.10.3",
669
666
  sha256: "f883f9ad96f7d75778631d5cc13d8a1a9c3e6ec41396bbce8ce13cff2e66fca2"
670
667
  }
671
668
  ],
@@ -673,16 +670,18 @@ var init_manifest = __esm(() => {
673
670
  {
674
671
  name: "ostacky-controller",
675
672
  file: "assets/mcp/ostacky-controller/",
676
- description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.10.1: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
677
- version: "0.10.1",
678
- sha256: "d10efacd03f72f5fa1cca0abb22046a9862de7e98f635308f6898c5cbd67407e"
673
+ description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.10.3: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
674
+ version: "0.10.3",
675
+ sha256: "f2d20b33b02b7a452f389b603c4ae65d5e8c079fc12e38f2641689816b3024f1",
676
+ sourceSha256: "f12314dcce27073f6aa2a83dcb7efe2a82a83404fe43926ec2da49d2c33a17ad"
679
677
  },
680
678
  {
681
679
  name: "openspec",
682
680
  file: "assets/mcp/openspec/",
683
681
  description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
684
- version: "0.10.1",
685
- sha256: "bdb283fe8ec2dfcba2ec4f38191f4039e58cd7ae12e44e66310783b42c827638"
682
+ version: "0.10.3",
683
+ sha256: "038ef43eaa1816251626610522f874a6c932528c00237f94f5354f18ed59ef53",
684
+ sourceSha256: "bdb283fe8ec2dfcba2ec4f38191f4039e58cd7ae12e44e66310783b42c827638"
686
685
  }
687
686
  ],
688
687
  skills: [
@@ -690,105 +689,105 @@ var init_manifest = __esm(() => {
690
689
  name: "brainstorming",
691
690
  file: "assets/skills/brainstorming/SKILL.md",
692
691
  description: "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a implementación directa o openspec-propose) y open-exploration (exploración libre)",
693
- version: "0.10.1",
692
+ version: "0.10.3",
694
693
  sha256: "ba76eff6fb5bea08d3b26622089ea4739b7b542b3677a46071b2576f3f2001c7"
695
694
  },
696
695
  {
697
696
  name: "execution-mode-evaluation",
698
697
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
699
698
  description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
700
- version: "0.10.1",
699
+ version: "0.10.3",
701
700
  sha256: "9090132ccfe31dcf34b6d5f399c1135dcb1044c504d4beb026e0c7d9e09e7c0d"
702
701
  },
703
702
  {
704
703
  name: "tdd",
705
704
  file: "assets/skills/tdd/SKILL.md",
706
705
  description: "Skill de test-driven development (Superpowers)",
707
- version: "0.10.1",
706
+ version: "0.10.3",
708
707
  sha256: "cce54e81cce46ba973066f9b43e74994a7dbf21aa756146c27689241cf62704d"
709
708
  },
710
709
  {
711
710
  name: "subagent-driven-development",
712
711
  file: "assets/skills/subagent-driven-development/SKILL.md",
713
712
  description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
714
- version: "0.10.1",
713
+ version: "0.10.3",
715
714
  sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
716
715
  },
717
716
  {
718
717
  name: "dispatching-parallel-agents",
719
718
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
720
719
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
721
- version: "0.10.1",
720
+ version: "0.10.3",
722
721
  sha256: "7e7b712ef742d5631e302e6a71583e2bbbd33094da9281abfb84b43505fc3ef1"
723
722
  },
724
723
  {
725
724
  name: "review",
726
725
  file: "assets/skills/review/SKILL.md",
727
726
  description: "Skill de revisión de código (Superpowers)",
728
- version: "0.10.1",
727
+ version: "0.10.3",
729
728
  sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
730
729
  },
731
730
  {
732
731
  name: "receiving-code-review",
733
732
  file: "assets/skills/receiving-code-review/SKILL.md",
734
733
  description: "Skill de recibir y procesar feedback de code review",
735
- version: "0.10.1",
734
+ version: "0.10.3",
736
735
  sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
737
736
  },
738
737
  {
739
738
  name: "openspec-propose",
740
739
  file: "assets/skills/openspec-propose/SKILL.md",
741
740
  description: "Skill de generación de proposal (OpenSpec)",
742
- version: "0.10.1",
741
+ version: "0.10.3",
743
742
  sha256: "9049b944cea88a5428fd5d77b411a0ed32d169ad68acda911b68365fac08f38f"
744
743
  },
745
744
  {
746
745
  name: "openspec-apply-change",
747
746
  file: "assets/skills/openspec-apply-change/SKILL.md",
748
747
  description: "Skill de aplicación de change (OpenSpec)",
749
- version: "0.10.1",
748
+ version: "0.10.3",
750
749
  sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
751
750
  },
752
751
  {
753
752
  name: "openspec-archive-change",
754
753
  file: "assets/skills/openspec-archive-change/SKILL.md",
755
754
  description: "Skill de archivo de change (OpenSpec)",
756
- version: "0.10.1",
755
+ version: "0.10.3",
757
756
  sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
758
757
  },
759
758
  {
760
759
  name: "openspec-explore",
761
760
  file: "assets/skills/openspec-explore/SKILL.md",
762
761
  description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
763
- version: "0.10.1",
762
+ version: "0.10.3",
764
763
  sha256: "9eafc3a8c693b8d9916867ef764d49e5222da717fde501c957bb3f6c312d2415"
765
764
  },
766
765
  {
767
766
  name: "using-git-worktrees",
768
767
  file: "assets/skills/using-git-worktrees/SKILL.md",
769
768
  description: "Skill de uso de git worktrees para aislamiento de trabajo",
770
- version: "0.10.1",
769
+ version: "0.10.3",
771
770
  sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
772
771
  },
773
772
  {
774
773
  name: "using-superpowers",
775
774
  file: "assets/skills/using-superpowers/SKILL.md",
776
775
  description: "Skill de orquestación de Superpowers skills",
777
- version: "0.10.1",
776
+ version: "0.10.3",
778
777
  sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
779
778
  },
780
779
  {
781
780
  name: "writing-skills",
782
781
  file: "assets/skills/writing-skills/SKILL.md",
783
782
  description: "Skill de creación y edición de skills",
784
- version: "0.10.1",
783
+ version: "0.10.3",
785
784
  sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
786
785
  },
787
786
  {
788
787
  name: "graceful-degradation",
789
788
  file: "assets/skills/graceful-degradation/SKILL.md",
790
789
  description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
791
- version: "0.10.1",
790
+ version: "0.10.3",
792
791
  sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
793
792
  }
794
793
  ]
@@ -840,9 +839,11 @@ function getBundledMcpPath(name) {
840
839
  validateFilePath(name);
841
840
  return join3(BUNDLED_MCP_DIR, name);
842
841
  }
843
- function getBundledExpectedHash(category, name) {
842
+ function getBundledExpectedHash(category, name, field = "sha256") {
844
843
  const items = manifest_default[category] ?? [];
845
- return items.find((item) => item.name === name)?.sha256 ?? null;
844
+ const found = items.find((item) => item.name === name);
845
+ const value = found?.[field];
846
+ return typeof value === "string" ? value : null;
846
847
  }
847
848
  function getRawUrl(repo, tag, path) {
848
849
  return `${GITHUB_RAW}/${repo}/${tag}/${path}`;
@@ -1337,13 +1338,16 @@ async function installCodeGraph(toolsDir) {
1337
1338
  message: indexWarning ? `CodeGraph instalado y configurado para OpenCode. ${indexWarning}` : "CodeGraph instalado localmente y configurado para OpenCode"
1338
1339
  };
1339
1340
  }
1340
- function setupOpenSpec(projectRoot = findProjectRoot()) {
1341
- const useBun = isCommandAvailable("bun");
1341
+ function setupOpenSpec(projectRoot = findProjectRoot(), deps = {}) {
1342
+ const execFile = deps.execFileSync ?? execFileSync3;
1343
+ const isAvailable = deps.isCommandAvailable ?? isCommandAvailable;
1344
+ const invocationFor = deps.getCommandInvocation ?? getCommandInvocation;
1345
+ const useBun = isAvailable("bun");
1342
1346
  const fail = (msg) => ({ success: false, message: msg });
1343
1347
  const tryDirect = () => {
1344
1348
  try {
1345
- const invocation = getCommandInvocation(useBun ? "bunx" : "npx", useBun ? [OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"] : ["--yes", OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"]);
1346
- execFileSync3(invocation.command, invocation.args, {
1349
+ const invocation = invocationFor(useBun ? "bunx" : "npx", useBun ? [OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"] : ["--yes", OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"]);
1350
+ execFile(invocation.command, invocation.args, {
1347
1351
  stdio: "pipe",
1348
1352
  timeout: 120000,
1349
1353
  cwd: projectRoot
@@ -1354,20 +1358,20 @@ function setupOpenSpec(projectRoot = findProjectRoot()) {
1354
1358
  }
1355
1359
  };
1356
1360
  const tryInstallGlobal = () => {
1357
- if (!useBun && !isCommandAvailable("npm")) {
1361
+ if (!useBun && !isAvailable("npm")) {
1358
1362
  return { ok: false, error: "Ni bun ni npm están disponibles para instalar OpenSpec globalmente" };
1359
1363
  }
1360
1364
  const pkgManager = useBun ? "bun" : "npm";
1361
1365
  const installArgs = useBun ? ["add", "-g", OPENSPEC_NPM_PACKAGE] : ["install", "-g", OPENSPEC_NPM_PACKAGE];
1362
1366
  try {
1363
- const invocation = getCommandInvocation(pkgManager, installArgs);
1364
- execFileSync3(invocation.command, invocation.args, { stdio: "pipe", timeout: 120000 });
1367
+ const invocation = invocationFor(pkgManager, installArgs);
1368
+ execFile(invocation.command, invocation.args, { stdio: "pipe", timeout: 120000 });
1365
1369
  } catch (installErr) {
1366
1370
  return { ok: false, error: `Instalación global falló: ${installErr.message}` };
1367
1371
  }
1368
1372
  try {
1369
- const retryInvocation = getCommandInvocation("openspec", ["init", "--tools", "opencode", "--force"]);
1370
- execFileSync3(retryInvocation.command, retryInvocation.args, {
1373
+ const retryInvocation = invocationFor("openspec", ["init", "--tools", "opencode", "--force"]);
1374
+ execFile(retryInvocation.command, retryInvocation.args, {
1371
1375
  stdio: "pipe",
1372
1376
  timeout: 120000,
1373
1377
  cwd: projectRoot
@@ -1576,8 +1580,60 @@ var init_stack = __esm(() => {
1576
1580
  // src/doctor-v2.ts
1577
1581
  import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
1578
1582
  import { join as join10, delimiter, isAbsolute } from "path";
1583
+ function globalOstackyArtifacts(globalDir = getGlobalOpenCodeDir()) {
1584
+ const found = [];
1585
+ const candidates = [
1586
+ join10(globalDir, "agent", "ostacky.md"),
1587
+ join10(globalDir, "plugins", "ostacky-controller", "index.ts"),
1588
+ join10(globalDir, "plugin", "ostacky-controller", "index.ts"),
1589
+ join10(globalDir, "command", "ostacky.md")
1590
+ ];
1591
+ for (const p of candidates) {
1592
+ try {
1593
+ if (existsSync8(p))
1594
+ found.push(p);
1595
+ } catch {}
1596
+ }
1597
+ for (const cfgName of ["opencode.json", "opencode.jsonc"]) {
1598
+ const cfg = join10(globalDir, cfgName);
1599
+ try {
1600
+ if (!existsSync8(cfg))
1601
+ continue;
1602
+ const raw = readFileSync6(cfg, "utf-8");
1603
+ const json = JSON.parse(raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""));
1604
+ const servers = json?.mcp?.servers ?? json?.mcp ?? {};
1605
+ if (servers && servers["ostacky-controller"])
1606
+ found.push(cfg);
1607
+ } catch {}
1608
+ }
1609
+ return found;
1610
+ }
1611
+ function globalPluginScopeAware(globalDir = getGlobalOpenCodeDir()) {
1612
+ const candidates = [
1613
+ join10(globalDir, "plugins", "ostacky-controller", "index.ts"),
1614
+ join10(globalDir, "plugin", "ostacky-controller", "index.ts"),
1615
+ join10(globalDir, "plugins", "ostacky-controller", "index.js"),
1616
+ join10(globalDir, "plugin", "ostacky-controller", "index.js")
1617
+ ];
1618
+ for (const p of candidates) {
1619
+ try {
1620
+ if (!existsSync8(p))
1621
+ continue;
1622
+ return readFileSync6(p, "utf-8").includes("ostacky-controller-global");
1623
+ } catch {}
1624
+ }
1625
+ return null;
1626
+ }
1579
1627
  function checkDoctorV2(projectRoot) {
1580
1628
  const lines = [];
1629
+ const globalArtifacts = globalOstackyArtifacts();
1630
+ if (globalArtifacts.length > 0) {
1631
+ if (globalPluginScopeAware() === false) {
1632
+ lines.push(`⚠️ plugin global legacy (id compartido): OpenCode V2 conserva la copia global y deshabilita la local ("Duplicate plugin ID: ostacky-controller") — los gates los aplica el plugin global, no el local. Actualizá el global (npx ostacky install --scope global) o limpiá con: npx ostacky uninstall --scope global`);
1633
+ } else {
1634
+ lines.push(`⚠️ Instalación global convive con la local: ${globalArtifacts.join(", ")}. El plugin global cede la precedencia al local (ids distintos, sin colisión); el resto de los artefactos globales queda como residuo si no los usás. Limpiá con: npx ostacky uninstall --scope global`);
1635
+ }
1636
+ }
1581
1637
  const cwd = projectRoot;
1582
1638
  const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
1583
1639
  const pluginPaths = [
@@ -1735,7 +1791,7 @@ var init_doctor_v2 = __esm(() => {
1735
1791
  // package.json
1736
1792
  var package_default = {
1737
1793
  name: "ostacky",
1738
- version: "0.10.1",
1794
+ version: "0.10.3",
1739
1795
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1740
1796
  type: "module",
1741
1797
  bin: {
@@ -1744,15 +1800,17 @@ var package_default = {
1744
1800
  scripts: {
1745
1801
  prebuild: "bun run scripts/sync-version.ts && bun run scripts/sync-controller-core.ts",
1746
1802
  dev: "bun run src/cli.ts",
1747
- build: "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
1803
+ build: "bun run build:cli && bun run build:mcp",
1804
+ "build:cli": "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
1805
+ "build:mcp": "bun run scripts/build-mcp-bundles.ts",
1748
1806
  start: "node dist/cli.js",
1749
1807
  test: "bun test --isolate",
1750
1808
  "test:safe": "bun run scripts/run-tests-isolated.ts",
1751
1809
  "check:skills": "bun run scripts/check-skill-sync.ts",
1752
1810
  "hash:check": "bun run scripts/update-manifest-hashes.ts --check",
1753
- "hash:update": "bun run scripts/update-manifest-hashes.ts",
1754
- prepublishOnly: "bun run build",
1755
- update: "bun run scripts/check-skill-sync.ts && bun run scripts/sync-controller-core.ts && bun run scripts/update-manifest-hashes.ts"
1811
+ "hash:update": "bun run build:mcp && bun run scripts/update-manifest-hashes.ts",
1812
+ prepublishOnly: "bun run build && bun run hash:update",
1813
+ update: "bun run scripts/check-skill-sync.ts && bun run scripts/sync-controller-core.ts && bun run hash:update"
1756
1814
  },
1757
1815
  files: [
1758
1816
  "dist/",
@@ -1772,6 +1830,7 @@ var package_default = {
1772
1830
  },
1773
1831
  devDependencies: {
1774
1832
  "@types/bun": "latest",
1833
+ openspec: "^0.0.0",
1775
1834
  typescript: "^5.0.0"
1776
1835
  },
1777
1836
  publishConfig: {
@@ -2914,7 +2973,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2914
2973
  params: {
2915
2974
  protocolVersion: "2025-03-26",
2916
2975
  capabilities: {},
2917
- clientInfo: { name: "ostacky-installer", version: "0.10.1" }
2976
+ clientInfo: { name: "ostacky-installer", version: "0.10.3" }
2918
2977
  }
2919
2978
  });
2920
2979
  });
@@ -2987,8 +3046,8 @@ function upsertLockfile(paths, type, item, manifest, contentHash) {
2987
3046
  existing.lockedAt = new Date().toISOString();
2988
3047
  writeLockfile(paths.root, existing);
2989
3048
  }
2990
- function assertBundledTreeHash(category, name, actualHash) {
2991
- const expected = getBundledExpectedHash(category, name);
3049
+ function assertBundledTreeHash(category, name, actualHash, field = "sha256") {
3050
+ const expected = getBundledExpectedHash(category, name, field);
2992
3051
  if (expected && actualHash !== expected) {
2993
3052
  const labels = {
2994
3053
  skills: "skill",
@@ -2996,7 +3055,7 @@ function assertBundledTreeHash(category, name, actualHash) {
2996
3055
  agents: "agente",
2997
3056
  commands: "command"
2998
3057
  };
2999
- throw new Error(`Tree hash inválido para ${labels[category] ?? category} "${name}"
3058
+ throw new Error(`Hash inválido para ${labels[category] ?? category} "${name}" (${field})
3000
3059
  esperado: ${expected}
3001
3060
  recibido: ${actualHash}`);
3002
3061
  }
@@ -3007,6 +3066,13 @@ function readBundledAsset(relativePath) {
3007
3066
  return null;
3008
3067
  return readFileSync5(fullPath, "utf-8");
3009
3068
  }
3069
+ function mcpArtifactHash(name, packageRoot = PACKAGE_ROOT) {
3070
+ const bundlePath = join6(packageRoot, "dist", "mcp", name, "index.js");
3071
+ if (existsSync5(bundlePath)) {
3072
+ return { field: "sha256", actual: sha256(readFileSync5(bundlePath, "utf-8")) };
3073
+ }
3074
+ return { field: "sourceSha256", actual: computeTreeHash(join6(packageRoot, "assets", "mcp", name)) };
3075
+ }
3010
3076
  async function installAgent(item, manifest, paths) {
3011
3077
  let content;
3012
3078
  try {
@@ -3077,8 +3143,10 @@ async function installMcpServer(item, manifest, paths) {
3077
3143
  if (!existsSync5(src)) {
3078
3144
  throw new Error(`MCP server bundleado no encontrado: ${src}. ` + `¿Falta el directorio assets/mcp/${item.name}/?`);
3079
3145
  }
3080
- const treeHash = computeTreeHash(src);
3081
- assertBundledTreeHash("mcpServers", item.name, treeHash);
3146
+ const bundlePath = join6(PACKAGE_ROOT, "dist", "mcp", item.name, "index.js");
3147
+ const hasBundle = existsSync5(bundlePath);
3148
+ const { field, actual: artifactHash } = mcpArtifactHash(item.name);
3149
+ assertBundledTreeHash("mcpServers", item.name, artifactHash, field);
3082
3150
  const nodeExecutable = getVerifiedNodeExecutable();
3083
3151
  await Promise.resolve().then(() => init_fs());
3084
3152
  const projectRoot = paths.root === getGlobalOpenCodeDir() ? paths.root : dirname4(paths.root);
@@ -3087,9 +3155,8 @@ async function installMcpServer(item, manifest, paths) {
3087
3155
  const statePath = item.name === "ostacky-controller" ? join6(paths.root, "ostacky-state.json") : undefined;
3088
3156
  try {
3089
3157
  mkdirSync3(staging, { recursive: true });
3090
- const bundledPath = join6(PACKAGE_ROOT, "dist", "mcp", item.name, "index.js");
3091
- if (existsSync5(bundledPath)) {
3092
- copyFileSync2(bundledPath, join6(staging, "index.js"));
3158
+ if (hasBundle) {
3159
+ copyFileSync2(bundlePath, join6(staging, "index.js"));
3093
3160
  writeFileSync5(join6(staging, "package.json"), JSON.stringify({ type: "module" }) + `
3094
3161
  `, "utf-8");
3095
3162
  } else {
@@ -3125,7 +3192,7 @@ async function installMcpServer(item, manifest, paths) {
3125
3192
  const promotion = promoteStagedDirectory(staging, dest);
3126
3193
  try {
3127
3194
  setMcpEntryAtProjectRoot(projectRoot, item.name, createMcpConfigEntry(item.name, nodeExecutable, join6(dest, "index.js"), statePath));
3128
- upsertLockfile(paths, "mcpServers", item, manifest, treeHash);
3195
+ upsertLockfile(paths, "mcpServers", item, manifest, artifactHash);
3129
3196
  promotion.commit();
3130
3197
  } catch (error) {
3131
3198
  try {
@@ -4612,6 +4679,7 @@ import { existsSync as existsSync9, statSync as statSync3, readFileSync as readF
4612
4679
  import { join as join11, dirname as dirname8 } from "node:path";
4613
4680
  import { spawnSync } from "node:child_process";
4614
4681
  init_fs();
4682
+ init_github();
4615
4683
 
4616
4684
  // src/version-check.ts
4617
4685
  function parseMajor(version) {
@@ -4899,16 +4967,18 @@ async function runDoctorCommand() {
4899
4967
  check(`tool ${t}: ${existsSync9(p) || existsSync9(pExe) ? "found" : "missing"}`, existsSync9(p) || existsSync9(pExe), true);
4900
4968
  }
4901
4969
  try {
4902
- const manifest = JSON.parse(readFileSync7(join11(cwd, "manifest.json"), "utf-8"));
4903
- const expected = manifest.mcpServers?.find((x) => x.name === "ostacky-controller")?.sha256;
4970
+ const name = "ostacky-controller";
4971
+ const { field, actual } = mcpArtifactHash(name);
4972
+ const expected = getBundledExpectedHash("mcpServers", name, field);
4904
4973
  if (expected) {
4905
- const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
4906
- check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
4974
+ check(`manifest ${field}: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
4907
4975
  if (expected !== actual)
4908
4976
  console.log(" Run: bun run hash:update");
4977
+ } else {
4978
+ check(`manifest ${field}: sin entrada para ${name}`, false, true);
4909
4979
  }
4910
4980
  } catch {
4911
- check("manifest: not found", false, true);
4981
+ check("manifest: no se pudo verificar", false, true);
4912
4982
  }
4913
4983
  try {
4914
4984
  await Promise.resolve().then(() => init_doctor_v2());
@@ -5099,9 +5169,15 @@ async function main() {
5099
5169
  await ensureOpencodeInstalled();
5100
5170
  }
5101
5171
  switch (cmd) {
5102
- case "install":
5172
+ case "install": {
5173
+ await Promise.resolve().then(() => init_doctor_v2());
5174
+ const globalArtifacts = globalOstackyArtifacts();
5175
+ if (scope !== "global" && globalArtifacts.length > 0) {
5176
+ console.error(`Aviso: hay artefactos de Ostacky en la instalación global (${globalArtifacts.join(", ")}). ` + `Podés limpiarlos con: npx ostacky uninstall --scope global`);
5177
+ }
5103
5178
  await runInstallCommand(scope, { noStack });
5104
5179
  break;
5180
+ }
5105
5181
  case "install-stack":
5106
5182
  if (scope === "global") {
5107
5183
  console.error(`Aviso: el stack (CodeGraph/Engram) se instala siempre local por proyecto. Ignorando --scope global y usando local.`);