ostacky 0.9.7 → 0.10.1

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.
@@ -25,14 +25,13 @@ Determinar el modo de ejecución óptimo entre **inline** y **subagent-driven**
25
25
 
26
26
  ## Procedimiento
27
27
 
28
- ### Paso 0: Verificar discovery-cache primero (ÚNICO entrypoint)
29
-
30
- **SHALL** llamar `src/discovery-cache.ts` `getDiscoverySnapshot(query)` y `getEngramDedup(query, requestId)` antes de cualquier `codegraph_codegraph_explore` o `engram_mem_search`. Si hit válido (TTL+gitDiffHash) → **reusar** `codegraph+engramHits` para construir `sharedFiles/fileClusters`; solo `codegraph_impact` para símbolos no cubiertos. Si miss o área difiere >30% → fetch fresco y `putDiscoverySnapshot`. Segunda llamada misma query en mismo `requestId` SHALL ser dedup (no `redundantCallCount`).
31
-
32
- ### Paso 0.1: Consultar Engram por decisiones previas (solo si no hay dedup hit)
33
-
34
- Si `getEngramDedup` fue miss, `engram_mem_search` con keywords del cambio. Si existe decisión previa, usar como referencia no vinculante.
35
-
28
+ ### Paso 0: Reusar el discovery ya obtenido en este request
29
+
30
+ Si en este mismo request ya llamaste `codegraph_codegraph_explore` para el área, **reusá ese output** (sigue en tu contexto) para construir `sharedFiles/fileClusters`; solo `codegraph_impact` para símbolos no cubiertos. Si el área difiere materialmente (overlap <30%) o todavía no hiciste discovery → `codegraph_codegraph_explore` fresco. No hay cache de archivo: no repitas la misma query sin motivo.
31
+
32
+ ### Paso 0.1: Consultar Engram por decisiones previas
33
+
34
+ `engram_mem_search` con keywords del cambio, salvo que ya lo hayas hecho para el mismo tema en este request. Si existe decisión previa, usarla como referencia no vinculante.
36
35
  ### Paso 0.5: Early exit para cambios pequeños
37
36
 
38
37
  Si el change tiene **≤2 tasks** Y **no comparten archivos entre sí** → devolver directamente:
@@ -48,10 +47,10 @@ Si el change tiene **≤2 tasks** Y **no comparten archivos entre sí** → devo
48
47
  }
49
48
  ```
50
49
 
51
- ### Paso 1: Obtener datos de CodeGraph (solo si discovery-cache miss)
52
-
53
- Si Paso 0 fue hit → **SKIP este paso**, derivar `fileClusters/sharedFiles` del snapshot (`state.snapshots.codegraph` + `tasks.md`). Snapshot resultante SHALL usar `codegraphUsed:["discovery-cache"]` (válido, no genera `WARN:execution_without_codegraph` si `snapshots.codegraph` existe) y `reuseDiscovery:true`.
54
- Si miss:
50
+ ### Paso 1: Obtener datos de CodeGraph (solo si no hiciste discovery en este request)
51
+
52
+ Si ya hay discovery en contexto → **SKIP este paso**; derivar `fileClusters/sharedFiles` del output (`state.snapshots.codegraph` + `tasks.md`) con `codegraphUsed:["codegraph_explore"]` y `reuseDiscovery:true`.
53
+ Si no:
55
54
  ```
56
55
  codegraph_codegraph_explore con query: "<área del cambio>"
57
56
  ```
@@ -26,9 +26,9 @@ When ready to implement, run /opsx-apply
26
26
 
27
27
  **Steps**
28
28
 
29
- 0. **Ground with CodeGraph + Engram (SHALL — Ostacky)** — Antes de `openspec new`, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Cita símbolos existentes en `proposal/design`; sin esto el `design.md` puede alucinar API inexistente y dispara `WARN:codegraphBypass` en `doctor`.
29
+ 0. **Ground with CodeGraph + Engram (SHALL — Ostacky)** — Antes de `openspec new`, usá `codegraph_codegraph_explore` + `engram_mem_search` sobre el área (si no los hiciste ya en este request). Citá símbolos existentes en `proposal/design`; sin esto el `design.md` puede alucinar API inexistente y dispara `WARN:codegraphBypass` en `doctor`.
30
30
 
31
- 1. **Check Engram for similar changes** — `engram_mem_search` con keywords del *paso 0* (reusar si ya fue hit; no duplicar `mem_search` mismo `requestId`). Si hay change similar previo, surfacealo. Pueden extender trabajo existente en vez de empezar de cero.
31
+ 1. **Check Engram for similar changes** — `engram_mem_search` con keywords del *paso 0* (no repetir la misma query en el mismo request). Si hay change similar previo, surfacealo. Pueden extender trabajo existente en vez de empezar de cero.
32
32
 
33
33
  2. **If no clear input provided, ask what they want to build**
34
34
 
@@ -28,7 +28,7 @@ Write the test first. Watch it fail. Write minimal code to pass.
28
28
 
29
29
  Thinking "skip TDD just this once"? Stop. That's rationalization.
30
30
 
31
- > **Ostacky ground (SHALL):** Antes de RED, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si hit → reusar símbolos/decisiones; si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Evita testear API inexistente y respeta `discovery-cache (único)` de `ostacky.md §1`. Ver `execution-mode-evaluation` para cache TTL.
31
+ > **Ostacky ground (SHALL):** Antes de RED, verificá el área con `codegraph_codegraph_explore` + `engram_mem_search` (si no los hiciste ya en este request). Citá símbolos existentes para no testear API inexistente. Ver `execution-mode-evaluation`.
32
32
 
33
33
  ## The Iron Law
34
34
 
package/dist/cli.js CHANGED
@@ -641,31 +641,31 @@ var init_fs = __esm(() => {
641
641
  var manifest_default;
642
642
  var init_manifest = __esm(() => {
643
643
  manifest_default = {
644
- version: "0.9.7",
644
+ version: "0.10.1",
645
645
  repo: "JaimeHoracio/Ostacky",
646
- tag: "v0.9.7",
646
+ tag: "v0.10.1",
647
647
  agents: [
648
648
  {
649
649
  name: "ostacky",
650
650
  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.9.7: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
652
- version: "0.9.7",
653
- sha256: "4481017881dadef3cabf516053d13930b5c948f879f8996fcb03ca5133073199"
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"
654
654
  }
655
655
  ],
656
656
  commands: [
657
657
  {
658
658
  name: "install-stack",
659
659
  file: "assets/commands/install-stack.md",
660
- description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.9.7: controller plugin como alma hard-gate, Context7 removido del stack.",
661
- version: "0.9.7",
662
- sha256: "0e1f52a236843fffbf166c1b381c52907abe19887e2472f667642ecbcb0f43a8"
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"
663
663
  },
664
664
  {
665
665
  name: "opsx-sync",
666
666
  file: "assets/commands/opsx-sync.md",
667
667
  description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
668
- version: "0.9.7",
668
+ version: "0.10.1",
669
669
  sha256: "f883f9ad96f7d75778631d5cc13d8a1a9c3e6ec41396bbce8ce13cff2e66fca2"
670
670
  }
671
671
  ],
@@ -673,15 +673,15 @@ var init_manifest = __esm(() => {
673
673
  {
674
674
  name: "ostacky-controller",
675
675
  file: "assets/mcp/ostacky-controller/",
676
- description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.9.7: 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.9.7",
678
- sha256: "41bfad0ddf21fac992c72e2a4e590445d075a5f8e8cb30a165728dff23a5d41a"
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"
679
679
  },
680
680
  {
681
681
  name: "openspec",
682
682
  file: "assets/mcp/openspec/",
683
683
  description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
684
- version: "0.9.7",
684
+ version: "0.10.1",
685
685
  sha256: "bdb283fe8ec2dfcba2ec4f38191f4039e58cd7ae12e44e66310783b42c827638"
686
686
  }
687
687
  ],
@@ -690,105 +690,105 @@ var init_manifest = __esm(() => {
690
690
  name: "brainstorming",
691
691
  file: "assets/skills/brainstorming/SKILL.md",
692
692
  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.9.7",
694
- sha256: "f1013a86a8f27914d85a716e23c7fd32db1786d93615e8b0834b13d184308549"
693
+ version: "0.10.1",
694
+ sha256: "ba76eff6fb5bea08d3b26622089ea4739b7b542b3677a46071b2576f3f2001c7"
695
695
  },
696
696
  {
697
697
  name: "execution-mode-evaluation",
698
698
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
699
699
  description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
700
- version: "0.9.7",
701
- sha256: "2158a6864c0912e868a9d720db7fd79441e6cc298e3320833d2527c9727c852e"
700
+ version: "0.10.1",
701
+ sha256: "9090132ccfe31dcf34b6d5f399c1135dcb1044c504d4beb026e0c7d9e09e7c0d"
702
702
  },
703
703
  {
704
704
  name: "tdd",
705
705
  file: "assets/skills/tdd/SKILL.md",
706
706
  description: "Skill de test-driven development (Superpowers)",
707
- version: "0.9.7",
708
- sha256: "f0ebfbbc43a8868d31a3fd2df37ae7f60fe8add09c6475862395a68269e5ff7f"
707
+ version: "0.10.1",
708
+ sha256: "cce54e81cce46ba973066f9b43e74994a7dbf21aa756146c27689241cf62704d"
709
709
  },
710
710
  {
711
711
  name: "subagent-driven-development",
712
712
  file: "assets/skills/subagent-driven-development/SKILL.md",
713
713
  description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
714
- version: "0.9.7",
714
+ version: "0.10.1",
715
715
  sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
716
716
  },
717
717
  {
718
718
  name: "dispatching-parallel-agents",
719
719
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
720
720
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
721
- version: "0.9.7",
721
+ version: "0.10.1",
722
722
  sha256: "7e7b712ef742d5631e302e6a71583e2bbbd33094da9281abfb84b43505fc3ef1"
723
723
  },
724
724
  {
725
725
  name: "review",
726
726
  file: "assets/skills/review/SKILL.md",
727
727
  description: "Skill de revisión de código (Superpowers)",
728
- version: "0.9.7",
728
+ version: "0.10.1",
729
729
  sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
730
730
  },
731
731
  {
732
732
  name: "receiving-code-review",
733
733
  file: "assets/skills/receiving-code-review/SKILL.md",
734
734
  description: "Skill de recibir y procesar feedback de code review",
735
- version: "0.9.7",
735
+ version: "0.10.1",
736
736
  sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
737
737
  },
738
738
  {
739
739
  name: "openspec-propose",
740
740
  file: "assets/skills/openspec-propose/SKILL.md",
741
741
  description: "Skill de generación de proposal (OpenSpec)",
742
- version: "0.9.7",
743
- sha256: "6a517f750f24c008208156a2d653e97ed30b1e53878aeac5ce394e573db0678f"
742
+ version: "0.10.1",
743
+ sha256: "9049b944cea88a5428fd5d77b411a0ed32d169ad68acda911b68365fac08f38f"
744
744
  },
745
745
  {
746
746
  name: "openspec-apply-change",
747
747
  file: "assets/skills/openspec-apply-change/SKILL.md",
748
748
  description: "Skill de aplicación de change (OpenSpec)",
749
- version: "0.9.7",
749
+ version: "0.10.1",
750
750
  sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
751
751
  },
752
752
  {
753
753
  name: "openspec-archive-change",
754
754
  file: "assets/skills/openspec-archive-change/SKILL.md",
755
755
  description: "Skill de archivo de change (OpenSpec)",
756
- version: "0.9.7",
756
+ version: "0.10.1",
757
757
  sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
758
758
  },
759
759
  {
760
760
  name: "openspec-explore",
761
761
  file: "assets/skills/openspec-explore/SKILL.md",
762
762
  description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
763
- version: "0.9.7",
763
+ version: "0.10.1",
764
764
  sha256: "9eafc3a8c693b8d9916867ef764d49e5222da717fde501c957bb3f6c312d2415"
765
765
  },
766
766
  {
767
767
  name: "using-git-worktrees",
768
768
  file: "assets/skills/using-git-worktrees/SKILL.md",
769
769
  description: "Skill de uso de git worktrees para aislamiento de trabajo",
770
- version: "0.9.7",
770
+ version: "0.10.1",
771
771
  sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
772
772
  },
773
773
  {
774
774
  name: "using-superpowers",
775
775
  file: "assets/skills/using-superpowers/SKILL.md",
776
776
  description: "Skill de orquestación de Superpowers skills",
777
- version: "0.9.7",
777
+ version: "0.10.1",
778
778
  sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
779
779
  },
780
780
  {
781
781
  name: "writing-skills",
782
782
  file: "assets/skills/writing-skills/SKILL.md",
783
783
  description: "Skill de creación y edición de skills",
784
- version: "0.9.7",
784
+ version: "0.10.1",
785
785
  sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
786
786
  },
787
787
  {
788
788
  name: "graceful-degradation",
789
789
  file: "assets/skills/graceful-degradation/SKILL.md",
790
790
  description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
791
- version: "0.9.7",
791
+ version: "0.10.1",
792
792
  sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
793
793
  }
794
794
  ]
@@ -1046,11 +1046,19 @@ function readOpenCodeConfig(configPath) {
1046
1046
  return null;
1047
1047
  }
1048
1048
  }
1049
- function writeOpenCodeConfig(configPath, config) {
1049
+ function writeOpenCodeConfig(configPath, config, options = {}) {
1050
+ const next = JSON.stringify(config, null, 2) + `
1051
+ `;
1052
+ try {
1053
+ if (options.backup !== false && existsSync4(configPath)) {
1054
+ const prev = readFileSync4(configPath, "utf-8");
1055
+ if (prev !== next)
1056
+ writeFileSync4(`${configPath}.bak`, prev, "utf-8");
1057
+ }
1058
+ } catch {}
1050
1059
  const tmpPath = `${configPath}.tmp.${process.pid}.${Date.now()}`;
1051
1060
  try {
1052
- writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + `
1053
- `, "utf-8");
1061
+ writeFileSync4(tmpPath, next, "utf-8");
1054
1062
  renameSync2(tmpPath, configPath);
1055
1063
  } finally {
1056
1064
  if (existsSync4(tmpPath))
@@ -1060,13 +1068,13 @@ function writeOpenCodeConfig(configPath, config) {
1060
1068
  function ensureOpenCodeConfig(projectRoot) {
1061
1069
  const existing = findOpenCodeConfig(projectRoot);
1062
1070
  if (existing)
1063
- return existing;
1071
+ return { path: existing, created: false };
1064
1072
  const configPath = join5(projectRoot, "opencode.json");
1065
1073
  writeOpenCodeConfig(configPath, {
1066
1074
  $schema: "https://opencode.ai/config.json",
1067
1075
  mcp: {}
1068
- });
1069
- return configPath;
1076
+ }, { backup: false });
1077
+ return { path: configPath, created: true };
1070
1078
  }
1071
1079
  function findOpenCodeConfig(projectRoot) {
1072
1080
  const candidates = ["opencode.json", "opencode.jsonc"];
@@ -1078,7 +1086,7 @@ function findOpenCodeConfig(projectRoot) {
1078
1086
  return null;
1079
1087
  }
1080
1088
  function setMcpEntryAtProjectRoot(projectRoot, name, entry) {
1081
- const configPath = ensureOpenCodeConfig(projectRoot);
1089
+ const { path: configPath, created } = ensureOpenCodeConfig(projectRoot);
1082
1090
  const config = readOpenCodeConfig(configPath);
1083
1091
  if (!config)
1084
1092
  throw new Error(`Error parseando ${configPath}`);
@@ -1090,7 +1098,7 @@ function setMcpEntryAtProjectRoot(projectRoot, name, entry) {
1090
1098
  mcp.servers[name] = entry;
1091
1099
  if (name in mcp && name !== "servers")
1092
1100
  delete mcp[name];
1093
- writeOpenCodeConfig(configPath, config);
1101
+ writeOpenCodeConfig(configPath, config, { backup: !created });
1094
1102
  }
1095
1103
  function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
1096
1104
  const configPath = findOpenCodeConfig(projectRoot);
@@ -1138,13 +1146,13 @@ var init_config = __esm(() => {
1138
1146
 
1139
1147
  // src/stack.ts
1140
1148
  import { chmodSync, copyFileSync as copyFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync4, unlinkSync as unlinkSync3 } from "fs";
1141
- import { basename as basename2, dirname as dirname5, join as join7, resolve as resolve3 } from "path";
1149
+ import { basename as basename3, dirname as dirname5, join as join7, resolve as resolve3 } from "path";
1142
1150
  import { execFileSync as execFileSync3 } from "child_process";
1143
1151
  function resolveToolInstallLocation(toolsDir) {
1144
1152
  const resolvedToolsDir = resolve3(toolsDir ?? join7(findProjectRoot(), ".opencode", "tools"));
1145
1153
  const possibleOpenCodeDir = dirname5(resolvedToolsDir);
1146
1154
  return {
1147
- projectRoot: basename2(possibleOpenCodeDir) === ".opencode" ? dirname5(possibleOpenCodeDir) : findProjectRoot(),
1155
+ projectRoot: basename3(possibleOpenCodeDir) === ".opencode" ? dirname5(possibleOpenCodeDir) : findProjectRoot(),
1148
1156
  toolsDir: resolvedToolsDir
1149
1157
  };
1150
1158
  }
@@ -1727,7 +1735,7 @@ var init_doctor_v2 = __esm(() => {
1727
1735
  // package.json
1728
1736
  var package_default = {
1729
1737
  name: "ostacky",
1730
- version: "0.9.7",
1738
+ version: "0.10.1",
1731
1739
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1732
1740
  type: "module",
1733
1741
  bin: {
@@ -2748,7 +2756,7 @@ import {
2748
2756
  rmSync as rmSync3
2749
2757
  } from "fs";
2750
2758
  import { execFileSync as execFileSync2, spawn } from "child_process";
2751
- import { dirname as dirname4, join as join6 } from "path";
2759
+ import { dirname as dirname4, join as join6, basename as basename2 } from "path";
2752
2760
  import { pathToFileURL } from "url";
2753
2761
  function createMcpConfigEntry(name, nodeExecutable, serverPath, statePath) {
2754
2762
  const entry = {
@@ -2906,20 +2914,25 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2906
2914
  params: {
2907
2915
  protocolVersion: "2025-03-26",
2908
2916
  capabilities: {},
2909
- clientInfo: { name: "ostacky-installer", version: "0.9.7" }
2917
+ clientInfo: { name: "ostacky-installer", version: "0.10.1" }
2910
2918
  }
2911
2919
  });
2912
2920
  });
2913
2921
  } finally {
2914
2922
  await new Promise((r) => setTimeout(r, 200));
2915
2923
  if (statePath) {
2916
- for (const p of [
2924
+ const probeDir = dirname4(statePath);
2925
+ const probeBase = basename2(statePath);
2926
+ const probePrefix = probeBase + ".";
2927
+ for (const p of readdirSync2(probeDir).filter((entry) => entry === probeBase || entry.startsWith(probePrefix)).map((entry) => join6(probeDir, entry)).concat([
2917
2928
  statePath,
2918
2929
  statePath + ".backup",
2930
+ statePath + ".backup.1",
2931
+ statePath + ".backup.2",
2919
2932
  statePath + ".lock.pid",
2920
2933
  statePath + ".lock.timestamp",
2921
2934
  statePath + ".tmp." + process.pid
2922
- ]) {
2935
+ ])) {
2923
2936
  try {
2924
2937
  if (existsSync5(p))
2925
2938
  rmSync3(p, { force: true });
@@ -3425,6 +3438,15 @@ async function doInstallStack(toolsDir, projectRoot) {
3425
3438
  let allOk = true;
3426
3439
  const resolvedProjectRoot = projectRoot ?? dirname6(dirname6(toolsDir ?? join8(process.cwd(), ".opencode", "tools")));
3427
3440
  const resolvedToolsDir = toolsDir ?? join8(resolvedProjectRoot, ".opencode", "tools");
3441
+ try {
3442
+ await Promise.resolve().then(() => init_fs());
3443
+ const globalDir = getGlobalOpenCodeDir().replace(/\\/g, "/").toLowerCase();
3444
+ const toolsNorm = resolvedToolsDir.replace(/\\/g, "/").toLowerCase();
3445
+ if (toolsNorm === globalDir || toolsNorm.startsWith(globalDir + "/")) {
3446
+ v2.warn("Scope global: el stack (CodeGraph/Engram) es siempre local por proyecto — omitido aquí. Instalalo por proyecto con `npx ostacky install-stack --scope local`.");
3447
+ return true;
3448
+ }
3449
+ } catch {}
3428
3450
  v2.info(`Stack → projectRoot: ${resolvedProjectRoot} | toolsDir: ${resolvedToolsDir}`);
3429
3451
  try {
3430
3452
  const { homedir } = await import("os");
@@ -4133,25 +4155,25 @@ function getOpencodeVersion() {
4133
4155
  function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
4134
4156
  if (platform === "win32") {
4135
4157
  return {
4136
- display: "npm install -g opencode-ai",
4158
+ display: "npm install -g @opencode/cli",
4137
4159
  command: "npm",
4138
- args: ["install", "-g", "opencode-ai"],
4139
- note: "Alternativas Windows: choco install opencode | scoop install opencode | mise use -g github:anomalyco/opencode (ver https://opencode.ai/download). WSL recomendado."
4160
+ args: ["install", "-g", "@opencode/cli"],
4161
+ note: "Alternativas Windows: binario suelto desde https://opencode.ai/download | WSL recomendado (OpenCode no soporta package managers de Windows)."
4140
4162
  };
4141
4163
  }
4142
4164
  if (platform === "darwin") {
4143
4165
  return {
4144
- display: "curl -fsSL https://opencode.ai/install | bash",
4166
+ display: "curl -fsSL https://opencode.ai/v2/install | bash",
4145
4167
  command: "bash",
4146
- args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4147
- note: "Alternativa macOS: brew install anomalyco/tap/opencode (tap oficial, más actualizado que brew install opencode)"
4168
+ args: ["-c", "curl -fsSL https://opencode.ai/v2/install | bash"],
4169
+ note: "Alternativa macOS: brew install anomalyco/tap/opencode-v2 (tap oficial) | npm install -g @opencode/cli"
4148
4170
  };
4149
4171
  }
4150
4172
  return {
4151
- display: "curl -fsSL https://opencode.ai/install | bash",
4173
+ display: "curl -fsSL https://opencode.ai/v2/install | bash",
4152
4174
  command: "bash",
4153
- args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4154
- note: "Alternativas Linux: npm install -g opencode-ai | bun add -g opencode-ai | brew install anomalyco/tap/opencode | paru -S opencode (Arch)"
4175
+ args: ["-c", "curl -fsSL https://opencode.ai/v2/install | bash"],
4176
+ note: "Alternativas Linux: npm install -g @opencode/cli | bun install -g --trust @opencode/cli | brew install anomalyco/tap/opencode-v2 | paru -S opencode-beta (Arch)"
4155
4177
  };
4156
4178
  }
4157
4179
  async function ensureOpencodeInstalled() {
@@ -4184,14 +4206,14 @@ ${info.note}` : ""}`, `Instalación requerida (${platform})`);
4184
4206
  spin.start(`Instalando OpenCode (${info.display})...`);
4185
4207
  try {
4186
4208
  if (platform === "win32") {
4187
- execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "cmd.exe" });
4209
+ execSync("npm install -g @opencode/cli", { stdio: "inherit", shell: "cmd.exe" });
4188
4210
  } else {
4189
4211
  if (!isCommandAvailable("curl")) {
4190
4212
  spin.stop("curl no disponible");
4191
4213
  v2.warn("curl no está instalado — se intentará con npm como fallback.");
4192
- execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "/bin/bash" });
4214
+ execSync("npm install -g @opencode/cli", { stdio: "inherit", shell: "/bin/bash" });
4193
4215
  } else {
4194
- execSync("curl -fsSL https://opencode.ai/install | bash", { stdio: "inherit", shell: "/bin/bash" });
4216
+ execSync("curl -fsSL https://opencode.ai/v2/install | bash", { stdio: "inherit", shell: "/bin/bash" });
4195
4217
  }
4196
4218
  }
4197
4219
  spin.stop("Instalación ejecutada, verificando...");
@@ -4202,10 +4224,10 @@ ${info.note}` : ""}`, `Instalación requerida (${platform})`);
4202
4224
  if (platform === "win32") {
4203
4225
  ye([
4204
4226
  "Intentá manualmente una de estas opciones:",
4205
- " npm install -g opencode-ai",
4206
- " choco install opencode",
4207
- " scoop install opencode",
4208
- " (WSL) curl -fsSL https://opencode.ai/install | bash",
4227
+ " npm install -g @opencode/cli",
4228
+ " bun install -g --trust @opencode/cli",
4229
+ " binario suelto: https://opencode.ai/download",
4230
+ " (WSL) curl -fsSL https://opencode.ai/v2/install | bash",
4209
4231
  "Luego verificá: opencode --version",
4210
4232
  "Docs: https://opencode.ai/download"
4211
4233
  ].join(`
@@ -4214,8 +4236,8 @@ ${info.note}` : ""}`, `Instalación requerida (${platform})`);
4214
4236
  ye([
4215
4237
  "Intentá manualmente:",
4216
4238
  ` ${info.display}`,
4217
- " npm install -g opencode-ai (si no tenés curl)",
4218
- " brew install anomalyco/tap/opencode (macOS/Linux con brew)",
4239
+ " npm install -g @opencode/cli (si no tenés curl)",
4240
+ " brew install anomalyco/tap/opencode-v2 (macOS/Linux con brew)",
4219
4241
  "Luego verificá: opencode --version",
4220
4242
  "Docs: https://opencode.ai/download"
4221
4243
  ].join(`
@@ -4265,7 +4287,7 @@ async function runInteractiveMenu(scope) {
4265
4287
  onCancel(action);
4266
4288
  switch (action) {
4267
4289
  case "all":
4268
- if (!await doInstallAll(manifest, paths))
4290
+ if (!await doInstallAll(manifest, paths, { scope: scope ?? "local" }))
4269
4291
  process.exitCode = 1;
4270
4292
  printPostInstallSteps();
4271
4293
  fe(process.exitCode ? "Instalación parcial." : "Listo.");
@@ -4291,6 +4313,11 @@ async function runInteractiveMenu(scope) {
4291
4313
  fe("Listo.");
4292
4314
  break;
4293
4315
  case "stack": {
4316
+ if ((scope ?? "local") === "global") {
4317
+ v2.warn("El stack (CodeGraph/Engram) es siempre local por proyecto. En scope global se omite; usá `npx ostacky install-stack --scope local` dentro del proyecto.");
4318
+ fe("Listo.");
4319
+ break;
4320
+ }
4294
4321
  ensureToolDirs(paths.tools, ["codegraph", "engram"]);
4295
4322
  const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
4296
4323
  if (!stackOk)
@@ -4971,9 +4998,8 @@ async function runDoctorCommand() {
4971
4998
  const pluginPath = join11(cwd, "assets", "plugins", "ostacky-controller", "index.ts");
4972
4999
  if (existsSync9(pluginPath)) {
4973
5000
  const plugin = readFileSync7(pluginPath, "utf-8");
4974
- const hasSpecGuard = plugin.includes("getDiscoverySnapshot") && plugin.includes("specSnapshot");
4975
- const hasNoOverwrite = plugin.includes("No edites sin Read fresco") || plugin.includes("specSnapshot");
4976
- check("spec: no-overwrite guard", hasNoOverwrite, true);
5001
+ const hasNoOverwrite = plugin.includes("shouldBlockCodeRead") && plugin.includes("isCodeReadGated") && plugin.includes("isTaskTracked") && plugin.includes("isShellPathExempt");
5002
+ check("spec/tracked read-guard (shouldBlockCodeRead + exemptions)", hasNoOverwrite, true);
4977
5003
  }
4978
5004
  const hasSyncProactive = ostackyMd.includes("Noté que lo que acordamos");
4979
5005
  check("sync: proactive WARN", hasSyncProactive, true);