ostacky 0.8.0 → 0.8.2

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
@@ -744,31 +744,31 @@ var init_fs = __esm(() => {
744
744
  var manifest_default;
745
745
  var init_manifest = __esm(() => {
746
746
  manifest_default = {
747
- version: "0.8.0",
747
+ version: "0.8.2",
748
748
  repo: "JaimeHoracio/Ostacky",
749
- tag: "v0.8.0",
749
+ tag: "v0.8.2",
750
750
  agents: [
751
751
  {
752
752
  name: "ostacky",
753
753
  file: "assets/agents/ostacky.md",
754
- 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.8.0: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
755
- version: "0.8.0",
756
- sha256: "d9f94bb05a33a98d6ff0ea0b56c8eb6c3c8d456d1de1ccaac3f76ee2150b3c7b"
754
+ 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.8.2: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
755
+ version: "0.8.2",
756
+ sha256: "7b2be94d05cd371fab5be5844ef7d82e542e84c2869d8171ccd63dc14e922dc3"
757
757
  }
758
758
  ],
759
759
  commands: [
760
760
  {
761
761
  name: "install-stack",
762
762
  file: "assets/commands/install-stack.md",
763
- description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.0: controller plugin como alma hard-gate, Context7 removido del stack.",
764
- version: "0.8.0",
765
- sha256: "1a8acef8dd59d80aa54fa0febb4bd86a31ed615dea5257ea7fa8ed4fd51836a5"
763
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.2: controller plugin como alma hard-gate, Context7 removido del stack.",
764
+ version: "0.8.2",
765
+ sha256: "24802dc29675a584dacf4d0ac8a944dd935ee0f96a4e804a9b1c5bdb3709438c"
766
766
  },
767
767
  {
768
768
  name: "opsx-sync",
769
769
  file: "assets/commands/opsx-sync.md",
770
770
  description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
771
- version: "0.8.0",
771
+ version: "0.8.2",
772
772
  sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
773
773
  }
774
774
  ],
@@ -776,15 +776,15 @@ var init_manifest = __esm(() => {
776
776
  {
777
777
  name: "ostacky-controller",
778
778
  file: "assets/mcp/ostacky-controller/",
779
- description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.0: 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.",
780
- version: "0.8.0",
781
- sha256: "5c4787001143ad517714e9c44f85b20fac00ba4146631d68486a90b93aa58dcd"
779
+ description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.2: 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.",
780
+ version: "0.8.2",
781
+ sha256: "a9d45b027a88e8037a4e29704b84f78a1d03d41654ba7445a3091a0c6767d094"
782
782
  },
783
783
  {
784
784
  name: "openspec",
785
785
  file: "assets/mcp/openspec/",
786
786
  description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
787
- version: "0.8.0",
787
+ version: "0.8.2",
788
788
  sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
789
789
  }
790
790
  ],
@@ -793,106 +793,106 @@ var init_manifest = __esm(() => {
793
793
  name: "brainstorming",
794
794
  file: "assets/skills/brainstorming/SKILL.md",
795
795
  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)",
796
- version: "0.8.0",
797
- sha256: "87f8d6ed28205f9272792d3865d8cd3232ab5e511a604d6d63d41e3a02a5f422"
796
+ version: "0.8.2",
797
+ sha256: "4c54604d4680bc695b7049c70cf8680296d8d2602e5790ddfc1cce7ba7c6f1fc"
798
798
  },
799
799
  {
800
800
  name: "execution-mode-evaluation",
801
801
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
802
802
  description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
803
- version: "0.8.0",
803
+ version: "0.8.2",
804
804
  sha256: "7b89dda29927c88f1eb13ffcea541a12423384c172f66c4f3d9b61efe0c7a163"
805
805
  },
806
806
  {
807
807
  name: "tdd",
808
808
  file: "assets/skills/tdd/SKILL.md",
809
809
  description: "Skill de test-driven development (Superpowers)",
810
- version: "0.8.0",
810
+ version: "0.8.2",
811
811
  sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
812
812
  },
813
813
  {
814
814
  name: "subagent-driven-development",
815
815
  file: "assets/skills/subagent-driven-development/SKILL.md",
816
816
  description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
817
- version: "0.8.0",
817
+ version: "0.8.2",
818
818
  sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
819
819
  },
820
820
  {
821
821
  name: "dispatching-parallel-agents",
822
822
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
823
823
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
824
- version: "0.8.0",
824
+ version: "0.8.2",
825
825
  sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
826
826
  },
827
827
  {
828
828
  name: "review",
829
829
  file: "assets/skills/review/SKILL.md",
830
830
  description: "Skill de revisión de código (Superpowers)",
831
- version: "0.8.0",
831
+ version: "0.8.2",
832
832
  sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
833
833
  },
834
834
  {
835
835
  name: "receiving-code-review",
836
836
  file: "assets/skills/receiving-code-review/SKILL.md",
837
837
  description: "Skill de recibir y procesar feedback de code review",
838
- version: "0.8.0",
838
+ version: "0.8.2",
839
839
  sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
840
840
  },
841
841
  {
842
842
  name: "openspec-propose",
843
843
  file: "assets/skills/openspec-propose/SKILL.md",
844
844
  description: "Skill de generación de proposal (OpenSpec)",
845
- version: "0.8.0",
845
+ version: "0.8.2",
846
846
  sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
847
847
  },
848
848
  {
849
849
  name: "openspec-apply-change",
850
850
  file: "assets/skills/openspec-apply-change/SKILL.md",
851
851
  description: "Skill de aplicación de change (OpenSpec)",
852
- version: "0.8.0",
852
+ version: "0.8.2",
853
853
  sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
854
854
  },
855
855
  {
856
856
  name: "openspec-archive-change",
857
857
  file: "assets/skills/openspec-archive-change/SKILL.md",
858
858
  description: "Skill de archivo de change (OpenSpec)",
859
- version: "0.8.0",
859
+ version: "0.8.2",
860
860
  sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
861
861
  },
862
862
  {
863
863
  name: "openspec-explore",
864
864
  file: "assets/skills/openspec-explore/SKILL.md",
865
865
  description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
866
- version: "0.8.0",
866
+ version: "0.8.2",
867
867
  sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
868
868
  },
869
869
  {
870
870
  name: "using-git-worktrees",
871
871
  file: "assets/skills/using-git-worktrees/SKILL.md",
872
872
  description: "Skill de uso de git worktrees para aislamiento de trabajo",
873
- version: "0.8.0",
873
+ version: "0.8.2",
874
874
  sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
875
875
  },
876
876
  {
877
877
  name: "using-superpowers",
878
878
  file: "assets/skills/using-superpowers/SKILL.md",
879
879
  description: "Skill de orquestación de Superpowers skills",
880
- version: "0.8.0",
880
+ version: "0.8.2",
881
881
  sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
882
882
  },
883
883
  {
884
884
  name: "writing-skills",
885
885
  file: "assets/skills/writing-skills/SKILL.md",
886
886
  description: "Skill de creación y edición de skills",
887
- version: "0.8.0",
887
+ version: "0.8.2",
888
888
  sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
889
889
  },
890
890
  {
891
891
  name: "graceful-degradation",
892
892
  file: "assets/skills/graceful-degradation/SKILL.md",
893
893
  description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
894
- version: "0.8.0",
895
- sha256: "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
894
+ version: "0.8.2",
895
+ sha256: "79d0eeb2116382cb5db5b753277a333b7ca5682f4a541170b4a6158125c2cb50"
896
896
  }
897
897
  ]
898
898
  };
@@ -1260,12 +1260,6 @@ function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
1260
1260
  delete config.plugin;
1261
1261
  changed = true;
1262
1262
  }
1263
- if (config.mcp && typeof config.mcp === "object" && "context7" in config.mcp) {
1264
- delete config.mcp.context7;
1265
- if (Object.keys(config.mcp).length === 0)
1266
- delete config.mcp;
1267
- changed = true;
1268
- }
1269
1263
  if (changed) {
1270
1264
  writeOpenCodeConfig(configPath, config);
1271
1265
  return { success: true, message: "Config actualizada (plugin legacy eliminado)" };
@@ -1303,11 +1297,21 @@ function resolveToolInstallLocation(toolsDir) {
1303
1297
  }
1304
1298
  function runTool(binary, args, cwd, timeout = 30000) {
1305
1299
  const invocation = getCommandInvocation(binary, args);
1306
- execFileSync3(invocation.command, invocation.args, {
1307
- cwd,
1308
- stdio: "pipe",
1309
- timeout
1310
- });
1300
+ try {
1301
+ execFileSync3(invocation.command, invocation.args, {
1302
+ cwd,
1303
+ stdio: "pipe",
1304
+ timeout
1305
+ });
1306
+ } catch (error) {
1307
+ const err = error;
1308
+ const stdout = err.stdout ? Buffer.from(err.stdout).toString().trim() : "";
1309
+ const stderr = err.stderr ? Buffer.from(err.stderr).toString().trim() : "";
1310
+ const detail = [stderr, stdout].filter(Boolean).join(`
1311
+ `) || err.message;
1312
+ const invocationStr = `${invocation.command} ${invocation.args.join(" ")}`;
1313
+ throw new Error(`${detail} (invocación: ${invocationStr}, cwd: ${cwd ?? process.cwd()})`);
1314
+ }
1311
1315
  }
1312
1316
  function findToolBinary(toolDir, name) {
1313
1317
  const binDir = join7(toolDir, "bin");
@@ -1396,18 +1400,36 @@ async function installCodeGraph(toolsDir) {
1396
1400
  const ext = process.platform === "win32" ? "zip" : "tar.gz";
1397
1401
  const url = `https://github.com/colbymchenry/codegraph/releases/download/${tag}/codegraph-${target}.${ext}`;
1398
1402
  try {
1399
- archivePromotion = await downloadAndExtractWithRetry(url, cgToolDir, 1, 180000, 2);
1403
+ archivePromotion = await downloadAndExtractWithRetry(url, cgToolDir, 0, 180000, 2);
1400
1404
  } catch (e2) {
1401
1405
  return {
1402
1406
  success: false,
1403
1407
  message: `Error descargando CodeGraph ${tag}: ${e2.message}`
1404
1408
  };
1405
1409
  }
1406
- localBin = findToolBinary(cgToolDir, "codegraph");
1407
- if (!localBin) {
1410
+ const expectedBin = join7(cgToolDir, "bin", getExecutableName("codegraph"));
1411
+ let found = findToolBinary(cgToolDir, "codegraph");
1412
+ if (!found)
1413
+ found = findBinaryInDir(cgToolDir, "codegraph");
1414
+ if (!found) {
1408
1415
  return failAfterExtraction(`Descarga de CodeGraph ${tag} completada pero no se encontró el binario en ${join7(cgToolDir, "bin")}.`);
1409
1416
  }
1410
- if (process.platform !== "win32") {
1417
+ try {
1418
+ if (found !== expectedBin) {
1419
+ mkdirSync4(join7(cgToolDir, "bin"), { recursive: true });
1420
+ if (found.toLowerCase().endsWith(".cmd") && expectedBin.toLowerCase().endsWith(".exe")) {
1421
+ localBin = found;
1422
+ } else {
1423
+ copyFileSync3(found, expectedBin);
1424
+ localBin = expectedBin;
1425
+ }
1426
+ } else {
1427
+ localBin = found;
1428
+ }
1429
+ } catch (error) {
1430
+ return failAfterExtraction(`CodeGraph fue descargado pero no se pudo materializar el binario local: ${error.message}`);
1431
+ }
1432
+ if (process.platform !== "win32" && localBin) {
1411
1433
  try {
1412
1434
  chmodSync(localBin, 493);
1413
1435
  } catch {}
@@ -1422,7 +1444,8 @@ async function installCodeGraph(toolsDir) {
1422
1444
  try {
1423
1445
  runTool(localBin, ["init", "-i"], projectRoot, 120000);
1424
1446
  } catch (error) {
1425
- indexWarning = `El índice no se pudo inicializar todavía: ${error.message}`;
1447
+ const msg = error.message;
1448
+ indexWarning = `El índice no se pudo inicializar todavía: ${msg}. Sugerencia: ejecutá manualmente \`${localBin} init -i\` en ${projectRoot} o \`npx ostacky install-stack --scope local\` para reintentar. Si el path del binario (${localBin}) apunta a ${cgToolDir} y esperabas otro proyecto, verificá que corriste el comando dentro del proyecto correcto (con .git) y con --scope local.`;
1426
1449
  }
1427
1450
  try {
1428
1451
  configureLocalTool(projectRoot, "codegraph", buildLocalMcpCommand(localBin, ["serve", "--mcp"]));
@@ -1621,7 +1644,7 @@ function uninstallStackConfig(paths) {
1621
1644
  const mcp = config.mcp;
1622
1645
  let changed = false;
1623
1646
  if (mcp) {
1624
- for (const name of ["codegraph", "engram", "context7"]) {
1647
+ for (const name of ["codegraph", "engram"]) {
1625
1648
  if (name in mcp) {
1626
1649
  delete mcp[name];
1627
1650
  removed.push(`mcp.${name}`);
@@ -1691,7 +1714,7 @@ var init_stack = __esm(() => {
1691
1714
  // package.json
1692
1715
  var package_default = {
1693
1716
  name: "ostacky",
1694
- version: "0.8.0",
1717
+ version: "0.8.2",
1695
1718
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1696
1719
  type: "module",
1697
1720
  bin: {
@@ -2866,7 +2889,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2866
2889
  params: {
2867
2890
  protocolVersion: "2025-03-26",
2868
2891
  capabilities: {},
2869
- clientInfo: { name: "ostacky-installer", version: "0.8.0" }
2892
+ clientInfo: { name: "ostacky-installer", version: "0.8.2" }
2870
2893
  }
2871
2894
  });
2872
2895
  });
@@ -3175,12 +3198,13 @@ function uninstallAll(paths) {
3175
3198
  for (const name of Object.keys(lockfile.mcpServers ?? {})) {
3176
3199
  uninstallMcpServer(name, paths);
3177
3200
  }
3178
- if (existsSync5(paths.mcp)) {
3179
- for (const entry of readdirSync2(paths.mcp)) {
3180
- const dirPath = join6(paths.mcp, entry);
3181
- if (statSync2(dirPath).isDirectory()) {
3182
- uninstallMcpServer(entry, paths);
3183
- }
3201
+ const ostackyPlugins = ["ostacky-plugin.ts", "engram.ts", "ostacky-guard.ts", "ostacky-controller.ts"];
3202
+ for (const file of ostackyPlugins) {
3203
+ const fp = join6(paths.plugins, file);
3204
+ if (existsSync5(fp)) {
3205
+ try {
3206
+ unlinkSync2(fp);
3207
+ } catch {}
3184
3208
  }
3185
3209
  }
3186
3210
  clearLockfile(paths.root);
@@ -3365,6 +3389,16 @@ async function doInstallStack(toolsDir, projectRoot) {
3365
3389
  const spin = L2();
3366
3390
  let allOk = true;
3367
3391
  const resolvedProjectRoot = projectRoot ?? dirname6(dirname6(toolsDir ?? join8(process.cwd(), ".opencode", "tools")));
3392
+ const resolvedToolsDir = toolsDir ?? join8(resolvedProjectRoot, ".opencode", "tools");
3393
+ v2.info(`Stack → projectRoot: ${resolvedProjectRoot} | toolsDir: ${resolvedToolsDir}`);
3394
+ try {
3395
+ const { homedir: homedir2 } = await import("os");
3396
+ const home = homedir2();
3397
+ const inHome = resolvedProjectRoot.replace(/\\/g, "/") === home.replace(/\\/g, "/");
3398
+ if (inHome && !existsSync7(join8(home, ".git"))) {
3399
+ v2.warn(`Estás instalando el stack en tu home (${home}). Si esperabas instalar en un proyecto, hacé cd al proyecto y usá --scope local.`);
3400
+ }
3401
+ } catch {}
3368
3402
  spin.start("Instalando CodeGraph...");
3369
3403
  const cg = await installCodeGraph(toolsDir);
3370
3404
  spin.stop(cg.success ? `✓ ${cg.message}` : `✗ ${cg.message}`);
@@ -3395,6 +3429,7 @@ async function doInstallAll(manifest, paths) {
3395
3429
  const spin = L2();
3396
3430
  let errors = 0;
3397
3431
  const isGlobal = isGlobalScope(paths);
3432
+ v2.info(`Scope → ${isGlobal ? "global" : "local"} | opencodeDir: ${paths.root} | tools: ${paths.tools}`);
3398
3433
  if (!isGlobal) {
3399
3434
  ensureToolDirs(paths.tools, ["codegraph", "engram"]);
3400
3435
  } else {
@@ -3446,7 +3481,7 @@ async function doInstallAll(manifest, paths) {
3446
3481
  }
3447
3482
  }
3448
3483
  try {
3449
- const { copyFileSync: copyFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync8 } = await import("fs");
3484
+ const { copyFileSync: copyFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync8, rmSync: rmSync5 } = await import("fs");
3450
3485
  const { join: join9 } = await import("path");
3451
3486
  const { PACKAGE_ROOT: PACKAGE_ROOT2 } = await Promise.resolve().then(() => (init_github(), exports_github));
3452
3487
  const { findProjectRoot: findProjectRoot3 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
@@ -3456,10 +3491,12 @@ async function doInstallAll(manifest, paths) {
3456
3491
  mkdirSync5(paths.plugins, { recursive: true });
3457
3492
  copyFileSync4(src, dest);
3458
3493
  }
3459
- const legacySrc = join9(PACKAGE_ROOT2, "assets", "plugins", "ostacky-controller.ts");
3460
- if (!existsSync8(src) && existsSync8(legacySrc)) {
3461
- mkdirSync5(paths.plugins, { recursive: true });
3462
- copyFileSync4(legacySrc, join9(paths.plugins, "ostacky-controller.ts"));
3494
+ for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
3495
+ const lp = join9(paths.plugins, legacy);
3496
+ if (existsSync8(lp))
3497
+ try {
3498
+ rmSync5(lp, { force: true });
3499
+ } catch {}
3463
3500
  }
3464
3501
  const srcEng = join9(PACKAGE_ROOT2, "assets", "plugins", "engram.ts");
3465
3502
  const destEng = join9(paths.plugins, "engram.ts");
@@ -3474,10 +3511,15 @@ async function doInstallAll(manifest, paths) {
3474
3511
  mkdirSync5(localPlugins, { recursive: true });
3475
3512
  if (existsSync8(src))
3476
3513
  copyFileSync4(src, join9(localPlugins, "ostacky-plugin.ts"));
3477
- else if (existsSync8(legacySrc))
3478
- copyFileSync4(legacySrc, join9(localPlugins, "ostacky-controller.ts"));
3479
3514
  if (existsSync8(srcEng))
3480
3515
  copyFileSync4(srcEng, join9(localPlugins, "engram.ts"));
3516
+ for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
3517
+ const lp = join9(localPlugins, legacy);
3518
+ if (existsSync8(lp))
3519
+ try {
3520
+ rmSync5(lp, { force: true });
3521
+ } catch {}
3522
+ }
3481
3523
  } catch {}
3482
3524
  }
3483
3525
  } catch {}
@@ -3718,10 +3760,10 @@ async function doUninstallTotal(paths) {
3718
3760
  for (const name of Object.keys(lockfile.mcpServers ?? {})) {
3719
3761
  pathsToDelete.push(join9(paths.mcp, name));
3720
3762
  }
3721
- ye(pathsToDelete.join(`
3722
- `), `Se eliminarán ${pathsToDelete.length} archivo(s) / directorio(s)`);
3763
+ ye([`Scope: ${paths.root}`, ...pathsToDelete].join(`
3764
+ `), `Se eliminarán ${pathsToDelete.length} item(s) trackeados en lockfile (safe-delete)`);
3723
3765
  const confirm = await me({
3724
- message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s)?`
3766
+ message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s) en ${paths.root}?`
3725
3767
  });
3726
3768
  onCancel(confirm);
3727
3769
  if (!confirm) {
@@ -3729,7 +3771,7 @@ async function doUninstallTotal(paths) {
3729
3771
  return;
3730
3772
  }
3731
3773
  uninstallAll(paths);
3732
- v2.success(`${pathsToDelete.length} item(s) eliminado(s).`);
3774
+ v2.success(`${pathsToDelete.length} item(s) eliminado(s) + plugins Ostacky-owned limpiados.`);
3733
3775
  const cleanEngram = await me({
3734
3776
  message: "¿Deseas remover la configuración de Engram del proyecto (mcp.engram)?"
3735
3777
  });
@@ -4010,9 +4052,122 @@ async function doUninstallMcpByName(name, paths) {
4010
4052
  }
4011
4053
  }
4012
4054
 
4055
+ // src/opencode.ts
4056
+ init_fs();
4057
+ import { execSync, execFileSync as execFileSync4 } from "node:child_process";
4058
+ function isOpencodeInstalled() {
4059
+ if (isCommandAvailable("opencode"))
4060
+ return true;
4061
+ try {
4062
+ execFileSync4("opencode", ["--version"], { stdio: "ignore" });
4063
+ return true;
4064
+ } catch {
4065
+ return false;
4066
+ }
4067
+ }
4068
+ function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
4069
+ if (platform === "win32") {
4070
+ return {
4071
+ display: "npm install -g opencode-ai",
4072
+ command: "npm",
4073
+ args: ["install", "-g", "opencode-ai"],
4074
+ note: "Alternativas Windows: choco install opencode | scoop install opencode | mise use -g github:anomalyco/opencode (ver https://opencode.ai/download). WSL recomendado."
4075
+ };
4076
+ }
4077
+ if (platform === "darwin") {
4078
+ return {
4079
+ display: "curl -fsSL https://opencode.ai/install | bash",
4080
+ command: "bash",
4081
+ args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4082
+ note: "Alternativa macOS: brew install anomalyco/tap/opencode (tap oficial, más actualizado que brew install opencode)"
4083
+ };
4084
+ }
4085
+ return {
4086
+ display: "curl -fsSL https://opencode.ai/install | bash",
4087
+ command: "bash",
4088
+ args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4089
+ note: "Alternativas Linux: npm install -g opencode-ai | bun add -g opencode-ai | brew install anomalyco/tap/opencode | paru -S opencode (Arch)"
4090
+ };
4091
+ }
4092
+ async function ensureOpencodeInstalled() {
4093
+ if (isOpencodeInstalled())
4094
+ return;
4095
+ const platform = process.platform;
4096
+ const info = getOpencodeInstallCommand(platform);
4097
+ v2.warn("OpenCode no detectado en este sistema.");
4098
+ ye(`${info.display}
4099
+ Docs: https://opencode.ai/download${info.note ? `
4100
+ ${info.note}` : ""}`, `Instalación requerida (${platform})`);
4101
+ const shouldInstall = await me({
4102
+ message: "Ostacky no funciona sin OpenCode. ¿Querés instalar OpenCode ahora?",
4103
+ initialValue: true
4104
+ });
4105
+ if (BD(shouldInstall) || !shouldInstall) {
4106
+ fe("Instalación cancelada. Instalá OpenCode manualmente y volvé a ejecutar: https://opencode.ai/download");
4107
+ process.exit(1);
4108
+ }
4109
+ const spin = L2();
4110
+ spin.start(`Instalando OpenCode (${info.display})...`);
4111
+ try {
4112
+ if (platform === "win32") {
4113
+ execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "cmd.exe" });
4114
+ } else {
4115
+ if (!isCommandAvailable("curl")) {
4116
+ spin.stop("curl no disponible");
4117
+ v2.warn("curl no está instalado — se intentará con npm como fallback.");
4118
+ execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "/bin/bash" });
4119
+ } else {
4120
+ execSync("curl -fsSL https://opencode.ai/install | bash", { stdio: "inherit", shell: "/bin/bash" });
4121
+ }
4122
+ }
4123
+ spin.stop("Instalación ejecutada, verificando...");
4124
+ } catch (e2) {
4125
+ spin.stop("Fallo la instalación automática.");
4126
+ const msg = e2.message ?? String(e2);
4127
+ v2.error(`No se pudo instalar OpenCode automáticamente: ${msg}`);
4128
+ if (platform === "win32") {
4129
+ ye([
4130
+ "Intentá manualmente una de estas opciones:",
4131
+ " npm install -g opencode-ai",
4132
+ " choco install opencode",
4133
+ " scoop install opencode",
4134
+ " (WSL) curl -fsSL https://opencode.ai/install | bash",
4135
+ "Luego verificá: opencode --version",
4136
+ "Docs: https://opencode.ai/download"
4137
+ ].join(`
4138
+ `), "Acción manual requerida");
4139
+ } else {
4140
+ ye([
4141
+ "Intentá manualmente:",
4142
+ ` ${info.display}`,
4143
+ " npm install -g opencode-ai (si no tenés curl)",
4144
+ " brew install anomalyco/tap/opencode (macOS/Linux con brew)",
4145
+ "Luego verificá: opencode --version",
4146
+ "Docs: https://opencode.ai/download"
4147
+ ].join(`
4148
+ `), "Acción manual requerida");
4149
+ }
4150
+ process.exit(1);
4151
+ }
4152
+ if (!isOpencodeInstalled()) {
4153
+ v2.error("OpenCode aún no está disponible en el PATH después de la instalación.");
4154
+ ye([
4155
+ "Probá cerrar y reabrir la terminal y ejecutar:",
4156
+ " opencode --version",
4157
+ "Si persiste, instalá manualmente:",
4158
+ ` ${info.display}`,
4159
+ "Docs: https://opencode.ai/download"
4160
+ ].join(`
4161
+ `), "Verificación falló");
4162
+ process.exit(1);
4163
+ }
4164
+ v2.success("OpenCode instalado correctamente ✓");
4165
+ }
4166
+
4013
4167
  // src/prompts/index.ts
4014
4168
  async function runInteractiveMenu(scope) {
4015
4169
  we(" OpenCode Installer ");
4170
+ await ensureOpencodeInstalled();
4016
4171
  const manifest = await loadManifest();
4017
4172
  const paths = await resolveOpenCodePaths(scope ?? null);
4018
4173
  if (!paths) {
@@ -4092,6 +4247,7 @@ async function runInteractiveMenu(scope) {
4092
4247
  }
4093
4248
  async function runInstallCommand(scope) {
4094
4249
  we(" OpenCode Installer ");
4250
+ await ensureOpencodeInstalled();
4095
4251
  const manifest = await loadManifest();
4096
4252
  const paths = await resolveOpenCodePaths(scope ?? null);
4097
4253
  if (!paths) {
@@ -4105,6 +4261,7 @@ async function runInstallCommand(scope) {
4105
4261
  }
4106
4262
  async function runAddAgentCommand(scope) {
4107
4263
  we(" OpenCode Installer ");
4264
+ await ensureOpencodeInstalled();
4108
4265
  const manifest = await loadManifest();
4109
4266
  const paths = await resolveOpenCodePaths(scope ?? null);
4110
4267
  if (!paths) {
@@ -4117,6 +4274,7 @@ async function runAddAgentCommand(scope) {
4117
4274
  }
4118
4275
  async function runAddCommandCommand(scope) {
4119
4276
  we(" OpenCode Installer ");
4277
+ await ensureOpencodeInstalled();
4120
4278
  const manifest = await loadManifest();
4121
4279
  const paths = await resolveOpenCodePaths(scope ?? null);
4122
4280
  if (!paths) {
@@ -4129,6 +4287,7 @@ async function runAddCommandCommand(scope) {
4129
4287
  }
4130
4288
  async function runAddSkillCommand(scope) {
4131
4289
  we(" OpenCode Installer ");
4290
+ await ensureOpencodeInstalled();
4132
4291
  const manifest = await loadManifest();
4133
4292
  const paths = await resolveOpenCodePaths(scope ?? null);
4134
4293
  if (!paths) {
@@ -4141,6 +4300,7 @@ async function runAddSkillCommand(scope) {
4141
4300
  }
4142
4301
  async function runAddMcpCommand(scope) {
4143
4302
  we(" OpenCode Installer ");
4303
+ await ensureOpencodeInstalled();
4144
4304
  const manifest = await loadManifest();
4145
4305
  const paths = await resolveOpenCodePaths(scope ?? null);
4146
4306
  if (!paths) {
@@ -4152,6 +4312,7 @@ async function runAddMcpCommand(scope) {
4152
4312
  fe("Listo.");
4153
4313
  }
4154
4314
  async function runInstallStackCommand(scope) {
4315
+ await ensureOpencodeInstalled();
4155
4316
  if (scope === "global") {
4156
4317
  v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
4157
4318
  fe("Usá: npx ostacky install-stack --scope local");
@@ -4203,6 +4364,7 @@ async function runUninstallStackCommand(scope) {
4203
4364
  }
4204
4365
  async function runUpdateCommand(scope) {
4205
4366
  we(" OpenCode Installer ");
4367
+ await ensureOpencodeInstalled();
4206
4368
  const manifest = await loadLatestManifest();
4207
4369
  const paths = await resolveOpenCodePaths(scope ?? null);
4208
4370
  if (!paths) {
@@ -4366,7 +4528,7 @@ async function runUninstallMcpCommand(name, scope) {
4366
4528
  // src/cli.ts
4367
4529
  init_fs();
4368
4530
  import { existsSync as existsSync8, statSync as statSync3, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "node:fs";
4369
- import { join as join11, dirname as dirname8 } from "node:path";
4531
+ import { join as join10, dirname as dirname8 } from "node:path";
4370
4532
  var HELP = `
4371
4533
  ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
4372
4534
 
@@ -4431,8 +4593,8 @@ var argvNoScope = withoutScopeArgs(process.argv);
4431
4593
  var [, , cmd, subcmd] = argvNoScope;
4432
4594
  async function runDoctorCommand() {
4433
4595
  const cwd = process.cwd();
4434
- const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
4435
- const statePath = join11(opencodeDir, "ostacky-state.json");
4596
+ const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
4597
+ const statePath = join10(opencodeDir, "ostacky-state.json");
4436
4598
  let hasError = false;
4437
4599
  let hasWarn = false;
4438
4600
  const check = (label, ok, warn = false) => {
@@ -4447,12 +4609,12 @@ async function runDoctorCommand() {
4447
4609
  }
4448
4610
  };
4449
4611
  const pluginPaths = [
4450
- join11(cwd, "assets", "plugins", "ostacky-plugin.ts"),
4451
- join11(opencodeDir, "plugins", "ostacky-plugin.ts"),
4452
- join11(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
4453
- join11(cwd, "assets", "plugins", "ostacky-controller.ts"),
4454
- join11(opencodeDir, "plugins", "ostacky-controller.ts"),
4455
- join11(cwd, ".opencode", "plugins", "ostacky-controller.ts")
4612
+ join10(cwd, "assets", "plugins", "ostacky-plugin.ts"),
4613
+ join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
4614
+ join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
4615
+ join10(cwd, "assets", "plugins", "ostacky-controller.ts"),
4616
+ join10(opencodeDir, "plugins", "ostacky-controller.ts"),
4617
+ join10(cwd, ".opencode", "plugins", "ostacky-controller.ts")
4456
4618
  ];
4457
4619
  const pluginActive = pluginPaths.some((p2) => existsSync8(p2));
4458
4620
  try {
@@ -4525,8 +4687,8 @@ async function runDoctorCommand() {
4525
4687
  check(`controller: ${e2.message}`, false);
4526
4688
  }
4527
4689
  try {
4528
- const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
4529
- const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
4690
+ const lockPid = join10(opencodeDir, "ostacky-state.json.lock.pid");
4691
+ const lockTs = join10(opencodeDir, "ostacky-state.json.lock.timestamp");
4530
4692
  if (existsSync8(lockPid) || existsSync8(lockTs)) {
4531
4693
  let ageStr = "";
4532
4694
  try {
@@ -4551,15 +4713,15 @@ async function runDoctorCommand() {
4551
4713
  }
4552
4714
  const tools = ["codegraph", "engram"];
4553
4715
  for (const t of tools) {
4554
- const p2 = join11(opencodeDir, "tools", t, "bin", t);
4716
+ const p2 = join10(opencodeDir, "tools", t, "bin", t);
4555
4717
  const pExe = p2 + ".exe";
4556
4718
  check(`tool ${t}: ${existsSync8(p2) || existsSync8(pExe) ? "found" : "missing"}`, existsSync8(p2) || existsSync8(pExe), true);
4557
4719
  }
4558
4720
  try {
4559
- const manifest = JSON.parse(readFileSync6(join11(cwd, "manifest.json"), "utf-8"));
4721
+ const manifest = JSON.parse(readFileSync6(join10(cwd, "manifest.json"), "utf-8"));
4560
4722
  const expected = manifest.mcpServers?.find((x2) => x2.name === "ostacky-controller")?.sha256;
4561
4723
  if (expected) {
4562
- const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
4724
+ const actual = computeTreeHash(join10(cwd, "assets", "mcp", "ostacky-controller"));
4563
4725
  check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
4564
4726
  if (expected !== actual)
4565
4727
  console.log(" Run: bun run hash:update");
@@ -4574,7 +4736,7 @@ async function runDoctorCommand() {
4574
4736
  } catch {}
4575
4737
  }
4576
4738
  try {
4577
- const cacheDir = join11(opencodeDir, "cache", "codegraph");
4739
+ const cacheDir = join10(opencodeDir, "cache", "codegraph");
4578
4740
  if (!existsSync8(cacheDir)) {
4579
4741
  console.log("ℹ️ cache: no cache dir yet (ok)");
4580
4742
  } else {
@@ -4582,7 +4744,7 @@ async function runDoctorCommand() {
4582
4744
  let total = 0;
4583
4745
  for (const f2 of files) {
4584
4746
  try {
4585
- total += statSync3(join11(cacheDir, f2)).size;
4747
+ total += statSync3(join10(cacheDir, f2)).size;
4586
4748
  } catch {}
4587
4749
  }
4588
4750
  const totalMB = (total / 1048576).toFixed(2);
@@ -4603,7 +4765,7 @@ async function runDoctorCommand() {
4603
4765
  console.log(`⚠️ cache: check failed ${e2.message}`);
4604
4766
  }
4605
4767
  try {
4606
- const secPath = join11(cwd, "src", "security.ts");
4768
+ const secPath = join10(cwd, "src", "security.ts");
4607
4769
  if (!existsSync8(secPath)) {
4608
4770
  console.log("⚠️ src/security.ts: missing (source-of-truth)");
4609
4771
  hasWarn = true;
@@ -4635,8 +4797,8 @@ async function runDoctorCommand() {
4635
4797
  async function runStatusCommand(args) {
4636
4798
  const isJson = args.includes("--json");
4637
4799
  const cwd = process.cwd();
4638
- const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
4639
- const statePath = join11(opencodeDir, "ostacky-state.json");
4800
+ const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
4801
+ const statePath = join10(opencodeDir, "ostacky-state.json");
4640
4802
  if (!existsSync8(statePath)) {
4641
4803
  console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
4642
4804
  return;
@@ -4659,6 +4821,10 @@ async function runStatusCommand(args) {
4659
4821
  }
4660
4822
  }
4661
4823
  async function main() {
4824
+ const needsOpencode = !cmd || cmd === "install" || cmd === "install-stack" || cmd === "update" || cmd === "add" && ["agent", "command", "skill", "mcp"].includes(subcmd ?? "");
4825
+ if (needsOpencode) {
4826
+ await ensureOpencodeInstalled();
4827
+ }
4662
4828
  switch (cmd) {
4663
4829
  case "install":
4664
4830
  await runInstallCommand(scope);