ostacky 0.8.0 → 0.8.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.
package/README.md CHANGED
@@ -116,7 +116,9 @@ npx ostacky install --scope global # ~/.config/opencode (XDG/APPDATA en Windows
116
116
  npx ostacky install --scope auto # local si existe .opencode/.git, si no global
117
117
  ```
118
118
 
119
- Descarga todos los agentes y commands definidos en el manifest y los escribe en `.opencode/` (scope `local`) o en `~/.config/opencode` (`global`). Herramientas (`tools/`) siempre quedan en `<proyecto>/.opencode/tools`.
119
+ Descarga todos los agentes y commands definidos en el manifest y los escribe en `.opencode/` (scope `local`) o en `~/.config/opencode` (`global`). Además copia los plugins **Ostacky-owned** `ostacky-plugin.ts` + `engram.ts` en `.opencode/plugins/` (el legacy `ostacky-guard.ts` fue fusionado en `ostacky-plugin.ts` y ya no se instala; `ostacky-controller.ts` legacy se limpia automáticamente si quedó de una instalación previa).
120
+
121
+ > **Coherencia local/global:** aunque elijas `global`, los plugins y herramientas (`tools/` con CodeGraph/Engram) permanecen siempre en `<proyecto>/.opencode` local para el hard-gate por worktree. En global solo se escribe el scope global + un espejo local de plugins. Para stack completo por proyecto ejecutá `npx ostacky install --scope local` dentro de cada repo.
120
122
 
121
123
  ### Agregar agentes o commands individualmente
122
124
 
@@ -148,15 +150,21 @@ Solo descarga los items que cambiaron de versión.
148
150
  ### Desinstalar
149
151
 
150
152
  ```bash
151
- npx ostacky uninstall
153
+ npx ostacky uninstall # pregunta local vs global, luego qué borrar
154
+ npx ostacky uninstall --scope local # fuerza scope local
155
+ npx ostacky uninstall --scope global # fuerza scope global
156
+ npx ostacky uninstall --scope auto # auto
152
157
  ```
153
158
 
154
- Borra todos los archivos instalados (los listados en `.opencode/ostacky-lock.json`). Antes de borrar, muestra un preview con los paths a eliminar y pide confirmación.
159
+ **Safe-delete:** solo borra lo trackeado en `.opencode/ostacky-lock.json` (agentes, commands, skills, MCPs) — nunca toca componentes previos del usuario que no estén en el lockfile. Antes de borrar muestra preview con `Scope: <ruta>` + lista de paths y pide confirmación. Los plugins Ostacky-owned (`ostacky-plugin.ts`, `engram.ts` + legacy `ostacky-guard.ts`/`ostacky-controller.ts` si quedaron) se limpian automáticamente via allowlist, nunca se borran plugins custom.
160
+
161
+ > Si el lockfile no existe o está vacío → `No hay nada instalado.` y no borra nada.
155
162
 
156
163
  #### Desinstalar un agente puntual
157
164
 
158
165
  ```bash
159
- npx ostacky uninstall agent <nombre>
166
+ npx ostacky uninstall agent <nombre> # también admite --scope local|global|auto
167
+ npx ostacky uninstall agent # sin nombre → selector de instalados
160
168
  ```
161
169
 
162
170
  Por ejemplo:
@@ -168,7 +176,8 @@ npx ostacky uninstall agent ostacky
168
176
  #### Desinstalar un command puntual
169
177
 
170
178
  ```bash
171
- npx ostacky uninstall command <nombre>
179
+ npx ostacky uninstall command <nombre> # también admite --scope
180
+ npx ostacky uninstall command # sin nombre → selector
172
181
  ```
173
182
 
174
183
  Por ejemplo:
@@ -179,6 +188,15 @@ npx ostacky uninstall command install-stack
179
188
 
180
189
  Si no especificás el nombre, el CLI te muestra un selector con los items instalados para que elijas cuáles desinstalar (puede ser uno o varios).
181
190
 
191
+ #### Desinstalar todo el stack (tools + config)
192
+
193
+ ```bash
194
+ npx ostacky uninstall-stack # pregunta scope, limpia mcp.codegraph/engram de opencode.json + .codegraph/ + .opencode/tools/
195
+ npx ostacky uninstall-stack --scope local
196
+ ```
197
+
198
+ No toca binarios globales ni datos de Engram, solo la configuración del proyecto.
199
+
182
200
  ### Otros
183
201
 
184
202
  ```bash
@@ -188,7 +206,7 @@ npx ostacky --help # muestra la ayuda
188
206
 
189
207
  ## Estructura generada
190
208
 
191
- Tras instalar, el proyecto queda así:
209
+ Tras instalar (`--scope local`), el proyecto queda así:
192
210
 
193
211
  ```
194
212
  .opencode/
@@ -212,44 +230,63 @@ Tras instalar, el proyecto queda así:
212
230
  │ ├── using-superpowers/
213
231
  │ ├── writing-skills/
214
232
  │ └── graceful-degradation/
215
- └── ostacky-lock.json ← versiones instaladas (agentes, commands y skills)
233
+ ├── plugins/
234
+ │ ├── ostacky-plugin.ts ← controller hard-gate (fusiona guard legacy)
235
+ │ └── engram.ts ← plugin Engram
236
+ ├── mcp/
237
+ │ ├── ostacky-controller/ ← MCP thin (observabilidad)
238
+ │ └── openspec/
239
+ ├── tools/
240
+ │ ├── codegraph/bin/codegraph
241
+ │ └── engram/bin/engram
242
+ ├── cache/codegraph/ ← cache local CodeGraph/Discovery
243
+ ├── ostacky-state.json ← estado por worktree (aislado)
244
+ └── ostacky-lock.json ← versiones instaladas (agentes, commands, skills, mcpServers)
216
245
  ```
217
246
 
247
+ > `assets/plugins/ostacky-guard.ts` fue **fusionado en `ostacky-plugin.ts` y eliminado** (re-export legacy). Fresh install solo escribe `ostacky-plugin.ts` + `engram.ts`; si venís de una versión previa con `ostacky-guard.ts` o `ostacky-controller.ts` legacy, el installer los elimina automáticamente.
248
+
218
249
  ### ostacky-lock.json
219
250
 
220
251
  ```json
221
252
  {
222
- "version": "0.8.0",
253
+ "version": "0.8.1",
223
254
  "lockedAt": "2025-01-01T00:00:00.000Z",
224
255
  "repo": "JaimeHoracio/Ostacky",
225
- "tag": "v0.8.0",
256
+ "tag": "v0.8.1",
226
257
  "agents": {
227
258
  "ostacky": {
228
- "version": "0.8.0",
259
+ "version": "0.8.1",
229
260
  "installedAt": "2025-01-01T00:00:00.000Z",
230
261
  "sha256": "abc123..."
231
262
  }
232
263
  },
233
264
  "commands": {
234
265
  "install-stack": {
235
- "version": "0.8.0",
266
+ "version": "0.8.1",
236
267
  "installedAt": "2025-01-01T00:00:00.000Z",
237
268
  "sha256": "def456..."
238
269
  },
239
270
  "opsx-sync": {
240
- "version": "0.8.0",
271
+ "version": "0.8.1",
241
272
  "installedAt": "2025-01-01T00:00:00.000Z",
242
273
  "sha256": "ghi789..."
243
274
  }
244
275
  },
245
276
  "skills": {
246
- "brainstorming": { "version": "0.8.0", ... },
247
- "execution-mode-evaluation": { "version": "0.8.0", ... },
248
- "openspec-propose": { "version": "0.8.0", ... }
277
+ "brainstorming": { "version": "0.8.1", ... },
278
+ "execution-mode-evaluation": { "version": "0.8.1", ... },
279
+ "openspec-propose": { "version": "0.8.1", ... }
280
+ },
281
+ "mcpServers": {
282
+ "ostacky-controller": { "version": "0.8.1", ... },
283
+ "openspec": { "version": "0.8.1", ... }
249
284
  }
250
285
  }
251
286
  ```
252
287
 
288
+ `plugins/` (`ostacky-plugin.ts`, `engram.ts`) y `tools/` **no** se trackean en el lockfile — son allowlist Ostacky-owned y se gestionan via `uninstall` (allowlist) y `uninstall-stack`.
289
+
253
290
  Se recomienda agregar `ostacky-lock.json` al control de versiones para que el equipo instale exactamente las mismas versiones.
254
291
 
255
292
  ## Después de la instalación
@@ -275,7 +312,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
275
312
  ## Seguridad
276
313
 
277
314
  - `opencode.jsonc` se versiona en el repo para compartir permisos y MCP de forma reproducible.
278
- - Las URLs de descarga usan **tags de GitHub** (ej. `v0.8.0`), nunca `main` — instalaciones reproducibles
315
+ - Las URLs de descarga usan **tags de GitHub** (ej. `v0.8.1`), nunca `main` — instalaciones reproducibles
279
316
  - Cada path de archivo descargado es validado para prevenir **path traversal**
280
317
  - Los archivos incluyen **checksum SHA-256** opcional; si el manifest lo define, el contenido se verifica antes de escribir
281
318
  - El cache local (`.opencode/cache/`) también valida integridad al servir archivos cacheados
@@ -291,7 +328,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
291
328
  - Variable `OSTACKY_SENSITIVE_PATTERNS` overridea los patrones por defecto (`**/.env*`, `**/.secrets/**`, `**/*.pem`, `**/*.key`, `**/.aws/**`, `**/.ssh/**`, `**/credentials.json`, `**/.npmrc`).
292
329
  - Ejemplo: `OSTACKY_SENSITIVE_PATTERNS="**/.env*,**/.secrets/**" bunx ostacky doctor` — `doctor` imprime el patrón efectivo.
293
330
  - Allowlist: `.env.example`, `.env.template`, `.env.sample` nunca se bloquean.
294
- - Fuente única: `src/security.ts` (`SENSITIVE_DEFAULT`, `isSensitive`, `BASH_SENSITIVE_RE`) — guard y controller usan la misma lógica (ver `doctor` para verificar).
331
+ - Fuente única: `src/security.ts` (`SENSITIVE_DEFAULT`, `isSensitive`, `BASH_SENSITIVE_RE`) — `ostacky-plugin.ts` importa de ahí (legacy `ostacky-guard.ts` eliminado, fusionado en el plugin). Ver `doctor` para verificar.
295
332
 
296
333
  #### Aislamiento de worktrees (harness-prod-hardening)
297
334
 
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  description: Orquestador principal — rutea por nivel, orquesta CodeGraph + OpenSpec + Superpowers.
3
3
  mode: primary
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  ---
6
6
 
7
- Sos **Ostacky v0.8.0**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
7
+ Sos **Ostacky v0.8.1**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
8
8
 
9
- > **Versión:** `0.8.0` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.0"** (o `v0.8.0` si te piden solo el número). No inventes otra versión.
9
+ > **Versión:** `0.8.1` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.1"** (o `v0.8.1` si te piden solo el número). No inventes otra versión.
10
10
 
11
11
  ## Reglas innegociables
12
12
 
@@ -5,13 +5,13 @@ agent: build
5
5
 
6
6
  Instala el stack tecnológico de desarrollo para OpenCode. **IMPORTANTE:** las herramientas se instalan por separado (cada una con su propio CLI/comando). `npx ostacky install` solo instala el agente y commands de Ostacky en `.opencode/`. Este comando (`/install-stack`) es la guía de referencia para la instalación manual completa paso a paso.
7
7
 
8
- **Nota:** A partir de v0.8.0, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, Context7, MCPs bundleados) además del agente y skills. Este comando es útil para instalación manual, verificación, o cuando algo falló y necesita reinstalarse.
8
+ **Nota:** A partir de v0.8.1, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, Context7, MCPs bundleados) además del agente y skills. Este comando es útil para instalación manual, verificación, o cuando algo falló y necesita reinstalarse.
9
9
 
10
10
  **RESTRICCIÓN ABSOLUTA:** instalar ÚNICAMENTE para OpenCode. Está terminantemente prohibido crear o modificar archivos en `.claude/`, `.kiro/`, `.cursor/`, `.gemini/`, `.codex/`, `.antigravity/`, `.windsurf/` o cualquier otro directorio de plataformas externas.
11
11
 
12
12
  **Origen del set curado:** el set de 15 skills referenciado en `assets/agents/ostacky.md` está bundleado en `assets/skills/` dentro del paquete npm. Context7 agrega su propio skill vía `npx ctx7 setup --opencode`. La definición del set y su trazabilidad viven en `manifest.json` y `.opencode/ostacky-lock.json`.
13
13
 
14
- ## Scope de instalación — local vs global (desde v0.8.0)
14
+ ## Scope de instalación — local vs global (desde v0.8.1)
15
15
 
16
16
  `npx ostacky install` soporta `--scope local|global|auto` (también `--scope=...`):
17
17
 
@@ -2371,7 +2371,7 @@ function safeHandler(fn, options = {}) {
2371
2371
 
2372
2372
  const server = new McpServer({
2373
2373
  name: 'ostacky-controller',
2374
- version: '0.8.0',
2374
+ version: '0.8.1',
2375
2375
  });
2376
2376
 
2377
2377
  server.registerTool(
@@ -2971,7 +2971,7 @@ function setupGracefulShutdown(ctrl) {
2971
2971
  }
2972
2972
 
2973
2973
  async function main() {
2974
- log('Starting ostacky-controller MCP v0.8.0...');
2974
+ log('Starting ostacky-controller MCP v0.8.1...');
2975
2975
  log('State path:', { path: statePath });
2976
2976
  // Clean up stale tmp/lock files from previous runs
2977
2977
  cleanupTmpFiles(statePath);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky-controller",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -20,6 +20,10 @@ import { isTrivial } from "../../src/tiered.ts"
20
20
  // ─── Constants ───────────────────────────────────────────────────────────────
21
21
 
22
22
  const MAX_STATE_FILE_SIZE = 2 * 1024 * 1024
23
+ const PING_INTERVAL_MS = 60_000
24
+ const IDLE_THRESHOLD_MS = 45_000
25
+ const PURPLE_TENUE = "\x1b[38;5;183m"
26
+ const PURPLE_RESET = "\x1b[0m"
23
27
 
24
28
  const STATES = Object.freeze({
25
29
  INTERPRETATION_PENDING: "INTERPRETATION_PENDING",
@@ -254,6 +258,9 @@ function isCodegraphAvailable(directory: string): boolean {
254
258
  const trivialBySession = new Map<string, boolean>()
255
259
  // Track discovery hit per requestId to avoid blocking after hit
256
260
  const discoveryHitByRequest = new Map<string, boolean>()
261
+ // ─── Heartbeat ping — evita sensación de trancado en tareas largas ──
262
+ let lastPingTs = 0
263
+ let pingInterval: ReturnType<typeof setInterval> | null = null
257
264
 
258
265
  // ─── Plugin ──────────────────────────────────────────────────────────────────
259
266
 
@@ -267,6 +274,30 @@ export const OstackyController: Plugin = async (ctx) => {
267
274
  let lastCheck: { revision: number; result: string } | null = null
268
275
  let checkCount = 0
269
276
 
277
+ // Heartbeat ping — purple tenue, evita sensación de trancado en tareas largas
278
+ if (!pingInterval) {
279
+ pingInterval = setInterval(async () => {
280
+ try {
281
+ const s = readState(ctx.directory)
282
+ if (!s || !["EXECUTING_INLINE", "EXECUTING_SUBAGENTS", "SYNC"].includes(s.state)) return
283
+ const now = Date.now()
284
+ const lastHeartbeat = s.lastHeartbeat || s.ts || now
285
+ const idle = now - lastHeartbeat
286
+ if (idle < IDLE_THRESHOLD_MS) return
287
+ if (now - lastPingTs < PING_INTERVAL_MS) return
288
+ lastPingTs = now
289
+ const completed = Object.values(s.tasks || {}).filter((t: any) => t.status === "COMPLETED").length
290
+ const total = s.expectedTaskCount ?? s.expectedTasks?.length ?? "?"
291
+ const pending = Array.isArray(s.expectedTasks) ? s.expectedTasks.filter((id: string) => !s.tasks?.[id] || s.tasks[id].status !== "COMPLETED").length : "?"
292
+ const msg = `🟣 ${PURPLE_TENUE}[OSTACKY]${PURPLE_RESET} ⏳ Sigo trabajando — ${s.state} • ${completed}/${total} (${pending} pendientes) • hace ${Math.round(idle/1000)}s sin output`
293
+ try { await (ctx as any).client?.tui?.showToast?.({ body: { message: msg, variant: "info" } } as any) } catch {}
294
+ try { await (ctx as any).client?.app?.log?.({ body: { service: "ostacky-ping", level: "info", message: msg } } as any) } catch {}
295
+ try { const st = readState(ctx.directory); if (st) { (st as any).lastPingTs = now; persistState(ctx.directory, st) } } catch {}
296
+ } catch {}
297
+ }, 30_000)
298
+ if (pingInterval && typeof (pingInterval as any).unref === 'function') (pingInterval as any).unref()
299
+ }
300
+
270
301
  return {
271
302
  // ── Tiered: suffix hint on user message (cache-friendly, no system replace) ──
272
303
  "chat.message": async (input: any, output: any) => {
@@ -527,6 +558,18 @@ export const OstackyController: Plugin = async (ctx) => {
527
558
  const s = readState((input as any).ctx?.directory ?? "")
528
559
  } catch {}
529
560
  }
561
+ // Heartbeat + color purple tenue: Ostacky vs modelo (fácil)
562
+ try {
563
+ const dir = ctx.directory
564
+ const s = readState(dir)
565
+ if (s) {
566
+ s.lastHeartbeat = Date.now()
567
+ try { persistState(dir, s) } catch {}
568
+ if (["EXECUTING_INLINE", "EXECUTING_SUBAGENTS", "SYNC"].includes(s.state) && output && typeof output.title === "string" && output.title && !output.title.includes("🟣")) {
569
+ output.title = `🟣 ${PURPLE_TENUE}[OSTACKY]${PURPLE_RESET} ${output.title}`
570
+ }
571
+ }
572
+ } catch {}
530
573
  },
531
574
 
532
575
  // ── Observable tools (MCP thin replacement) ──
@@ -622,6 +665,11 @@ export const OstackyController: Plugin = async (ctx) => {
622
665
  }
623
666
  },
624
667
 
668
+ dispose: async () => {
669
+ try { if (pingInterval) clearInterval(pingInterval) } catch {}
670
+ pingInterval = null
671
+ },
672
+
625
673
  "experimental.session.compacting": async (input: any, output: any) => {
626
674
  try {
627
675
  const statePath = getStatePath(ctx.directory)
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.1",
748
748
  repo: "JaimeHoracio/Ostacky",
749
- tag: "v0.8.0",
749
+ tag: "v0.8.1",
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.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
755
+ version: "0.8.1",
756
+ sha256: "1dea56ffe3866e830c5138b44ff3e7531324ddd2784b1d34a8956614a53c5ec9"
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.1: controller plugin como alma hard-gate, Context7 removido del stack.",
764
+ version: "0.8.1",
765
+ sha256: "43ad96a63ceab8730cf0459838e7fad2953333307cfc664a849ca39f7ba7d532"
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.1",
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.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.",
780
+ version: "0.8.1",
781
+ sha256: "ec554bc97f9d9b1e64c0b42813547dedc923e86e326c43ae87afab88ba249b90"
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.1",
788
788
  sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
789
789
  }
790
790
  ],
@@ -793,105 +793,105 @@ 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",
796
+ version: "0.8.1",
797
797
  sha256: "87f8d6ed28205f9272792d3865d8cd3232ab5e511a604d6d63d41e3a02a5f422"
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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.1",
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",
894
+ version: "0.8.1",
895
895
  sha256: "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
896
896
  }
897
897
  ]
@@ -1691,7 +1691,7 @@ var init_stack = __esm(() => {
1691
1691
  // package.json
1692
1692
  var package_default = {
1693
1693
  name: "ostacky",
1694
- version: "0.8.0",
1694
+ version: "0.8.1",
1695
1695
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1696
1696
  type: "module",
1697
1697
  bin: {
@@ -2866,7 +2866,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2866
2866
  params: {
2867
2867
  protocolVersion: "2025-03-26",
2868
2868
  capabilities: {},
2869
- clientInfo: { name: "ostacky-installer", version: "0.8.0" }
2869
+ clientInfo: { name: "ostacky-installer", version: "0.8.1" }
2870
2870
  }
2871
2871
  });
2872
2872
  });
@@ -3175,12 +3175,13 @@ function uninstallAll(paths) {
3175
3175
  for (const name of Object.keys(lockfile.mcpServers ?? {})) {
3176
3176
  uninstallMcpServer(name, paths);
3177
3177
  }
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
- }
3178
+ const ostackyPlugins = ["ostacky-plugin.ts", "engram.ts", "ostacky-guard.ts", "ostacky-controller.ts"];
3179
+ for (const file of ostackyPlugins) {
3180
+ const fp = join6(paths.plugins, file);
3181
+ if (existsSync5(fp)) {
3182
+ try {
3183
+ unlinkSync2(fp);
3184
+ } catch {}
3184
3185
  }
3185
3186
  }
3186
3187
  clearLockfile(paths.root);
@@ -3446,7 +3447,7 @@ async function doInstallAll(manifest, paths) {
3446
3447
  }
3447
3448
  }
3448
3449
  try {
3449
- const { copyFileSync: copyFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync8 } = await import("fs");
3450
+ const { copyFileSync: copyFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync8, rmSync: rmSync5 } = await import("fs");
3450
3451
  const { join: join9 } = await import("path");
3451
3452
  const { PACKAGE_ROOT: PACKAGE_ROOT2 } = await Promise.resolve().then(() => (init_github(), exports_github));
3452
3453
  const { findProjectRoot: findProjectRoot3 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
@@ -3456,10 +3457,12 @@ async function doInstallAll(manifest, paths) {
3456
3457
  mkdirSync5(paths.plugins, { recursive: true });
3457
3458
  copyFileSync4(src, dest);
3458
3459
  }
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"));
3460
+ for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
3461
+ const lp = join9(paths.plugins, legacy);
3462
+ if (existsSync8(lp))
3463
+ try {
3464
+ rmSync5(lp, { force: true });
3465
+ } catch {}
3463
3466
  }
3464
3467
  const srcEng = join9(PACKAGE_ROOT2, "assets", "plugins", "engram.ts");
3465
3468
  const destEng = join9(paths.plugins, "engram.ts");
@@ -3474,10 +3477,15 @@ async function doInstallAll(manifest, paths) {
3474
3477
  mkdirSync5(localPlugins, { recursive: true });
3475
3478
  if (existsSync8(src))
3476
3479
  copyFileSync4(src, join9(localPlugins, "ostacky-plugin.ts"));
3477
- else if (existsSync8(legacySrc))
3478
- copyFileSync4(legacySrc, join9(localPlugins, "ostacky-controller.ts"));
3479
3480
  if (existsSync8(srcEng))
3480
3481
  copyFileSync4(srcEng, join9(localPlugins, "engram.ts"));
3482
+ for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
3483
+ const lp = join9(localPlugins, legacy);
3484
+ if (existsSync8(lp))
3485
+ try {
3486
+ rmSync5(lp, { force: true });
3487
+ } catch {}
3488
+ }
3481
3489
  } catch {}
3482
3490
  }
3483
3491
  } catch {}
@@ -3718,10 +3726,10 @@ async function doUninstallTotal(paths) {
3718
3726
  for (const name of Object.keys(lockfile.mcpServers ?? {})) {
3719
3727
  pathsToDelete.push(join9(paths.mcp, name));
3720
3728
  }
3721
- ye(pathsToDelete.join(`
3722
- `), `Se eliminarán ${pathsToDelete.length} archivo(s) / directorio(s)`);
3729
+ ye([`Scope: ${paths.root}`, ...pathsToDelete].join(`
3730
+ `), `Se eliminarán ${pathsToDelete.length} item(s) trackeados en lockfile (safe-delete)`);
3723
3731
  const confirm = await me({
3724
- message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s)?`
3732
+ message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s) en ${paths.root}?`
3725
3733
  });
3726
3734
  onCancel(confirm);
3727
3735
  if (!confirm) {
@@ -3729,7 +3737,7 @@ async function doUninstallTotal(paths) {
3729
3737
  return;
3730
3738
  }
3731
3739
  uninstallAll(paths);
3732
- v2.success(`${pathsToDelete.length} item(s) eliminado(s).`);
3740
+ v2.success(`${pathsToDelete.length} item(s) eliminado(s) + plugins Ostacky-owned limpiados.`);
3733
3741
  const cleanEngram = await me({
3734
3742
  message: "¿Deseas remover la configuración de Engram del proyecto (mcp.engram)?"
3735
3743
  });
@@ -4010,9 +4018,122 @@ async function doUninstallMcpByName(name, paths) {
4010
4018
  }
4011
4019
  }
4012
4020
 
4021
+ // src/opencode.ts
4022
+ init_fs();
4023
+ import { execSync, execFileSync as execFileSync4 } from "node:child_process";
4024
+ function isOpencodeInstalled() {
4025
+ if (isCommandAvailable("opencode"))
4026
+ return true;
4027
+ try {
4028
+ execFileSync4("opencode", ["--version"], { stdio: "ignore" });
4029
+ return true;
4030
+ } catch {
4031
+ return false;
4032
+ }
4033
+ }
4034
+ function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
4035
+ if (platform === "win32") {
4036
+ return {
4037
+ display: "npm install -g opencode-ai",
4038
+ command: "npm",
4039
+ args: ["install", "-g", "opencode-ai"],
4040
+ note: "Alternativas Windows: choco install opencode | scoop install opencode | mise use -g github:anomalyco/opencode (ver https://opencode.ai/download). WSL recomendado."
4041
+ };
4042
+ }
4043
+ if (platform === "darwin") {
4044
+ return {
4045
+ display: "curl -fsSL https://opencode.ai/install | bash",
4046
+ command: "bash",
4047
+ args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4048
+ note: "Alternativa macOS: brew install anomalyco/tap/opencode (tap oficial, más actualizado que brew install opencode)"
4049
+ };
4050
+ }
4051
+ return {
4052
+ display: "curl -fsSL https://opencode.ai/install | bash",
4053
+ command: "bash",
4054
+ args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
4055
+ note: "Alternativas Linux: npm install -g opencode-ai | bun add -g opencode-ai | brew install anomalyco/tap/opencode | paru -S opencode (Arch)"
4056
+ };
4057
+ }
4058
+ async function ensureOpencodeInstalled() {
4059
+ if (isOpencodeInstalled())
4060
+ return;
4061
+ const platform = process.platform;
4062
+ const info = getOpencodeInstallCommand(platform);
4063
+ v2.warn("OpenCode no detectado en este sistema.");
4064
+ ye(`${info.display}
4065
+ Docs: https://opencode.ai/download${info.note ? `
4066
+ ${info.note}` : ""}`, `Instalación requerida (${platform})`);
4067
+ const shouldInstall = await me({
4068
+ message: "Ostacky no funciona sin OpenCode. ¿Querés instalar OpenCode ahora?",
4069
+ initialValue: true
4070
+ });
4071
+ if (BD(shouldInstall) || !shouldInstall) {
4072
+ fe("Instalación cancelada. Instalá OpenCode manualmente y volvé a ejecutar: https://opencode.ai/download");
4073
+ process.exit(1);
4074
+ }
4075
+ const spin = L2();
4076
+ spin.start(`Instalando OpenCode (${info.display})...`);
4077
+ try {
4078
+ if (platform === "win32") {
4079
+ execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "cmd.exe" });
4080
+ } else {
4081
+ if (!isCommandAvailable("curl")) {
4082
+ spin.stop("curl no disponible");
4083
+ v2.warn("curl no está instalado — se intentará con npm como fallback.");
4084
+ execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "/bin/bash" });
4085
+ } else {
4086
+ execSync("curl -fsSL https://opencode.ai/install | bash", { stdio: "inherit", shell: "/bin/bash" });
4087
+ }
4088
+ }
4089
+ spin.stop("Instalación ejecutada, verificando...");
4090
+ } catch (e2) {
4091
+ spin.stop("Fallo la instalación automática.");
4092
+ const msg = e2.message ?? String(e2);
4093
+ v2.error(`No se pudo instalar OpenCode automáticamente: ${msg}`);
4094
+ if (platform === "win32") {
4095
+ ye([
4096
+ "Intentá manualmente una de estas opciones:",
4097
+ " npm install -g opencode-ai",
4098
+ " choco install opencode",
4099
+ " scoop install opencode",
4100
+ " (WSL) curl -fsSL https://opencode.ai/install | bash",
4101
+ "Luego verificá: opencode --version",
4102
+ "Docs: https://opencode.ai/download"
4103
+ ].join(`
4104
+ `), "Acción manual requerida");
4105
+ } else {
4106
+ ye([
4107
+ "Intentá manualmente:",
4108
+ ` ${info.display}`,
4109
+ " npm install -g opencode-ai (si no tenés curl)",
4110
+ " brew install anomalyco/tap/opencode (macOS/Linux con brew)",
4111
+ "Luego verificá: opencode --version",
4112
+ "Docs: https://opencode.ai/download"
4113
+ ].join(`
4114
+ `), "Acción manual requerida");
4115
+ }
4116
+ process.exit(1);
4117
+ }
4118
+ if (!isOpencodeInstalled()) {
4119
+ v2.error("OpenCode aún no está disponible en el PATH después de la instalación.");
4120
+ ye([
4121
+ "Probá cerrar y reabrir la terminal y ejecutar:",
4122
+ " opencode --version",
4123
+ "Si persiste, instalá manualmente:",
4124
+ ` ${info.display}`,
4125
+ "Docs: https://opencode.ai/download"
4126
+ ].join(`
4127
+ `), "Verificación falló");
4128
+ process.exit(1);
4129
+ }
4130
+ v2.success("OpenCode instalado correctamente ✓");
4131
+ }
4132
+
4013
4133
  // src/prompts/index.ts
4014
4134
  async function runInteractiveMenu(scope) {
4015
4135
  we(" OpenCode Installer ");
4136
+ await ensureOpencodeInstalled();
4016
4137
  const manifest = await loadManifest();
4017
4138
  const paths = await resolveOpenCodePaths(scope ?? null);
4018
4139
  if (!paths) {
@@ -4092,6 +4213,7 @@ async function runInteractiveMenu(scope) {
4092
4213
  }
4093
4214
  async function runInstallCommand(scope) {
4094
4215
  we(" OpenCode Installer ");
4216
+ await ensureOpencodeInstalled();
4095
4217
  const manifest = await loadManifest();
4096
4218
  const paths = await resolveOpenCodePaths(scope ?? null);
4097
4219
  if (!paths) {
@@ -4105,6 +4227,7 @@ async function runInstallCommand(scope) {
4105
4227
  }
4106
4228
  async function runAddAgentCommand(scope) {
4107
4229
  we(" OpenCode Installer ");
4230
+ await ensureOpencodeInstalled();
4108
4231
  const manifest = await loadManifest();
4109
4232
  const paths = await resolveOpenCodePaths(scope ?? null);
4110
4233
  if (!paths) {
@@ -4117,6 +4240,7 @@ async function runAddAgentCommand(scope) {
4117
4240
  }
4118
4241
  async function runAddCommandCommand(scope) {
4119
4242
  we(" OpenCode Installer ");
4243
+ await ensureOpencodeInstalled();
4120
4244
  const manifest = await loadManifest();
4121
4245
  const paths = await resolveOpenCodePaths(scope ?? null);
4122
4246
  if (!paths) {
@@ -4129,6 +4253,7 @@ async function runAddCommandCommand(scope) {
4129
4253
  }
4130
4254
  async function runAddSkillCommand(scope) {
4131
4255
  we(" OpenCode Installer ");
4256
+ await ensureOpencodeInstalled();
4132
4257
  const manifest = await loadManifest();
4133
4258
  const paths = await resolveOpenCodePaths(scope ?? null);
4134
4259
  if (!paths) {
@@ -4141,6 +4266,7 @@ async function runAddSkillCommand(scope) {
4141
4266
  }
4142
4267
  async function runAddMcpCommand(scope) {
4143
4268
  we(" OpenCode Installer ");
4269
+ await ensureOpencodeInstalled();
4144
4270
  const manifest = await loadManifest();
4145
4271
  const paths = await resolveOpenCodePaths(scope ?? null);
4146
4272
  if (!paths) {
@@ -4152,6 +4278,7 @@ async function runAddMcpCommand(scope) {
4152
4278
  fe("Listo.");
4153
4279
  }
4154
4280
  async function runInstallStackCommand(scope) {
4281
+ await ensureOpencodeInstalled();
4155
4282
  if (scope === "global") {
4156
4283
  v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
4157
4284
  fe("Usá: npx ostacky install-stack --scope local");
@@ -4203,6 +4330,7 @@ async function runUninstallStackCommand(scope) {
4203
4330
  }
4204
4331
  async function runUpdateCommand(scope) {
4205
4332
  we(" OpenCode Installer ");
4333
+ await ensureOpencodeInstalled();
4206
4334
  const manifest = await loadLatestManifest();
4207
4335
  const paths = await resolveOpenCodePaths(scope ?? null);
4208
4336
  if (!paths) {
@@ -4366,7 +4494,7 @@ async function runUninstallMcpCommand(name, scope) {
4366
4494
  // src/cli.ts
4367
4495
  init_fs();
4368
4496
  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";
4497
+ import { join as join10, dirname as dirname8 } from "node:path";
4370
4498
  var HELP = `
4371
4499
  ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
4372
4500
 
@@ -4431,8 +4559,8 @@ var argvNoScope = withoutScopeArgs(process.argv);
4431
4559
  var [, , cmd, subcmd] = argvNoScope;
4432
4560
  async function runDoctorCommand() {
4433
4561
  const cwd = process.cwd();
4434
- const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
4435
- const statePath = join11(opencodeDir, "ostacky-state.json");
4562
+ const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
4563
+ const statePath = join10(opencodeDir, "ostacky-state.json");
4436
4564
  let hasError = false;
4437
4565
  let hasWarn = false;
4438
4566
  const check = (label, ok, warn = false) => {
@@ -4447,12 +4575,12 @@ async function runDoctorCommand() {
4447
4575
  }
4448
4576
  };
4449
4577
  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")
4578
+ join10(cwd, "assets", "plugins", "ostacky-plugin.ts"),
4579
+ join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
4580
+ join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
4581
+ join10(cwd, "assets", "plugins", "ostacky-controller.ts"),
4582
+ join10(opencodeDir, "plugins", "ostacky-controller.ts"),
4583
+ join10(cwd, ".opencode", "plugins", "ostacky-controller.ts")
4456
4584
  ];
4457
4585
  const pluginActive = pluginPaths.some((p2) => existsSync8(p2));
4458
4586
  try {
@@ -4525,8 +4653,8 @@ async function runDoctorCommand() {
4525
4653
  check(`controller: ${e2.message}`, false);
4526
4654
  }
4527
4655
  try {
4528
- const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
4529
- const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
4656
+ const lockPid = join10(opencodeDir, "ostacky-state.json.lock.pid");
4657
+ const lockTs = join10(opencodeDir, "ostacky-state.json.lock.timestamp");
4530
4658
  if (existsSync8(lockPid) || existsSync8(lockTs)) {
4531
4659
  let ageStr = "";
4532
4660
  try {
@@ -4551,15 +4679,15 @@ async function runDoctorCommand() {
4551
4679
  }
4552
4680
  const tools = ["codegraph", "engram"];
4553
4681
  for (const t of tools) {
4554
- const p2 = join11(opencodeDir, "tools", t, "bin", t);
4682
+ const p2 = join10(opencodeDir, "tools", t, "bin", t);
4555
4683
  const pExe = p2 + ".exe";
4556
4684
  check(`tool ${t}: ${existsSync8(p2) || existsSync8(pExe) ? "found" : "missing"}`, existsSync8(p2) || existsSync8(pExe), true);
4557
4685
  }
4558
4686
  try {
4559
- const manifest = JSON.parse(readFileSync6(join11(cwd, "manifest.json"), "utf-8"));
4687
+ const manifest = JSON.parse(readFileSync6(join10(cwd, "manifest.json"), "utf-8"));
4560
4688
  const expected = manifest.mcpServers?.find((x2) => x2.name === "ostacky-controller")?.sha256;
4561
4689
  if (expected) {
4562
- const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
4690
+ const actual = computeTreeHash(join10(cwd, "assets", "mcp", "ostacky-controller"));
4563
4691
  check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
4564
4692
  if (expected !== actual)
4565
4693
  console.log(" Run: bun run hash:update");
@@ -4574,7 +4702,7 @@ async function runDoctorCommand() {
4574
4702
  } catch {}
4575
4703
  }
4576
4704
  try {
4577
- const cacheDir = join11(opencodeDir, "cache", "codegraph");
4705
+ const cacheDir = join10(opencodeDir, "cache", "codegraph");
4578
4706
  if (!existsSync8(cacheDir)) {
4579
4707
  console.log("ℹ️ cache: no cache dir yet (ok)");
4580
4708
  } else {
@@ -4582,7 +4710,7 @@ async function runDoctorCommand() {
4582
4710
  let total = 0;
4583
4711
  for (const f2 of files) {
4584
4712
  try {
4585
- total += statSync3(join11(cacheDir, f2)).size;
4713
+ total += statSync3(join10(cacheDir, f2)).size;
4586
4714
  } catch {}
4587
4715
  }
4588
4716
  const totalMB = (total / 1048576).toFixed(2);
@@ -4603,7 +4731,7 @@ async function runDoctorCommand() {
4603
4731
  console.log(`⚠️ cache: check failed ${e2.message}`);
4604
4732
  }
4605
4733
  try {
4606
- const secPath = join11(cwd, "src", "security.ts");
4734
+ const secPath = join10(cwd, "src", "security.ts");
4607
4735
  if (!existsSync8(secPath)) {
4608
4736
  console.log("⚠️ src/security.ts: missing (source-of-truth)");
4609
4737
  hasWarn = true;
@@ -4635,8 +4763,8 @@ async function runDoctorCommand() {
4635
4763
  async function runStatusCommand(args) {
4636
4764
  const isJson = args.includes("--json");
4637
4765
  const cwd = process.cwd();
4638
- const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
4639
- const statePath = join11(opencodeDir, "ostacky-state.json");
4766
+ const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
4767
+ const statePath = join10(opencodeDir, "ostacky-state.json");
4640
4768
  if (!existsSync8(statePath)) {
4641
4769
  console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
4642
4770
  return;
@@ -4659,6 +4787,10 @@ async function runStatusCommand(args) {
4659
4787
  }
4660
4788
  }
4661
4789
  async function main() {
4790
+ const needsOpencode = !cmd || cmd === "install" || cmd === "install-stack" || cmd === "update" || cmd === "add" && ["agent", "command", "skill", "mcp"].includes(subcmd ?? "");
4791
+ if (needsOpencode) {
4792
+ await ensureOpencodeInstalled();
4793
+ }
4662
4794
  switch (cmd) {
4663
4795
  case "install":
4664
4796
  await runInstallCommand(scope);
package/manifest.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
- "version": "0.8.0",
2
+ "version": "0.8.1",
3
3
  "repo": "JaimeHoracio/Ostacky",
4
- "tag": "v0.8.0",
4
+ "tag": "v0.8.1",
5
5
  "agents": [
6
6
  {
7
7
  "name": "ostacky",
8
8
  "file": "assets/agents/ostacky.md",
9
- "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.",
10
- "version": "0.8.0",
11
- "sha256": "d9f94bb05a33a98d6ff0ea0b56c8eb6c3c8d456d1de1ccaac3f76ee2150b3c7b"
9
+ "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.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
+ "version": "0.8.1",
11
+ "sha256": "1dea56ffe3866e830c5138b44ff3e7531324ddd2784b1d34a8956614a53c5ec9"
12
12
  }
13
13
  ],
14
14
  "commands": [
15
15
  {
16
16
  "name": "install-stack",
17
17
  "file": "assets/commands/install-stack.md",
18
- "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.",
19
- "version": "0.8.0",
20
- "sha256": "1a8acef8dd59d80aa54fa0febb4bd86a31ed615dea5257ea7fa8ed4fd51836a5"
18
+ "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.1: controller plugin como alma hard-gate, Context7 removido del stack.",
19
+ "version": "0.8.1",
20
+ "sha256": "43ad96a63ceab8730cf0459838e7fad2953333307cfc664a849ca39f7ba7d532"
21
21
  },
22
22
  {
23
23
  "name": "opsx-sync",
24
24
  "file": "assets/commands/opsx-sync.md",
25
25
  "description": "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
26
- "version": "0.8.0",
26
+ "version": "0.8.1",
27
27
  "sha256": "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
28
28
  }
29
29
  ],
@@ -31,15 +31,15 @@
31
31
  {
32
32
  "name": "ostacky-controller",
33
33
  "file": "assets/mcp/ostacky-controller/",
34
- "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.",
35
- "version": "0.8.0",
36
- "sha256": "5c4787001143ad517714e9c44f85b20fac00ba4146631d68486a90b93aa58dcd"
34
+ "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.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.",
35
+ "version": "0.8.1",
36
+ "sha256": "ec554bc97f9d9b1e64c0b42813547dedc923e86e326c43ae87afab88ba249b90"
37
37
  },
38
38
  {
39
39
  "name": "openspec",
40
40
  "file": "assets/mcp/openspec/",
41
41
  "description": "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
42
- "version": "0.8.0",
42
+ "version": "0.8.1",
43
43
  "sha256": "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
44
44
  }
45
45
  ],
@@ -48,105 +48,105 @@
48
48
  "name": "brainstorming",
49
49
  "file": "assets/skills/brainstorming/SKILL.md",
50
50
  "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)",
51
- "version": "0.8.0",
51
+ "version": "0.8.1",
52
52
  "sha256": "87f8d6ed28205f9272792d3865d8cd3232ab5e511a604d6d63d41e3a02a5f422"
53
53
  },
54
54
  {
55
55
  "name": "execution-mode-evaluation",
56
56
  "file": "assets/skills/execution-mode-evaluation/SKILL.md",
57
57
  "description": "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
58
- "version": "0.8.0",
58
+ "version": "0.8.1",
59
59
  "sha256": "7b89dda29927c88f1eb13ffcea541a12423384c172f66c4f3d9b61efe0c7a163"
60
60
  },
61
61
  {
62
62
  "name": "tdd",
63
63
  "file": "assets/skills/tdd/SKILL.md",
64
64
  "description": "Skill de test-driven development (Superpowers)",
65
- "version": "0.8.0",
65
+ "version": "0.8.1",
66
66
  "sha256": "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
67
67
  },
68
68
  {
69
69
  "name": "subagent-driven-development",
70
70
  "file": "assets/skills/subagent-driven-development/SKILL.md",
71
71
  "description": "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
72
- "version": "0.8.0",
72
+ "version": "0.8.1",
73
73
  "sha256": "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
74
74
  },
75
75
  {
76
76
  "name": "dispatching-parallel-agents",
77
77
  "file": "assets/skills/dispatching-parallel-agents/SKILL.md",
78
78
  "description": "Skill de dispatch paralelo de agentes (Superpowers)",
79
- "version": "0.8.0",
79
+ "version": "0.8.1",
80
80
  "sha256": "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
81
81
  },
82
82
  {
83
83
  "name": "review",
84
84
  "file": "assets/skills/review/SKILL.md",
85
85
  "description": "Skill de revisión de código (Superpowers)",
86
- "version": "0.8.0",
86
+ "version": "0.8.1",
87
87
  "sha256": "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
88
88
  },
89
89
  {
90
90
  "name": "receiving-code-review",
91
91
  "file": "assets/skills/receiving-code-review/SKILL.md",
92
92
  "description": "Skill de recibir y procesar feedback de code review",
93
- "version": "0.8.0",
93
+ "version": "0.8.1",
94
94
  "sha256": "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
95
95
  },
96
96
  {
97
97
  "name": "openspec-propose",
98
98
  "file": "assets/skills/openspec-propose/SKILL.md",
99
99
  "description": "Skill de generación de proposal (OpenSpec)",
100
- "version": "0.8.0",
100
+ "version": "0.8.1",
101
101
  "sha256": "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
102
102
  },
103
103
  {
104
104
  "name": "openspec-apply-change",
105
105
  "file": "assets/skills/openspec-apply-change/SKILL.md",
106
106
  "description": "Skill de aplicación de change (OpenSpec)",
107
- "version": "0.8.0",
107
+ "version": "0.8.1",
108
108
  "sha256": "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
109
109
  },
110
110
  {
111
111
  "name": "openspec-archive-change",
112
112
  "file": "assets/skills/openspec-archive-change/SKILL.md",
113
113
  "description": "Skill de archivo de change (OpenSpec)",
114
- "version": "0.8.0",
114
+ "version": "0.8.1",
115
115
  "sha256": "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
116
116
  },
117
117
  {
118
118
  "name": "openspec-explore",
119
119
  "file": "assets/skills/openspec-explore/SKILL.md",
120
120
  "description": "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
121
- "version": "0.8.0",
121
+ "version": "0.8.1",
122
122
  "sha256": "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
123
123
  },
124
124
  {
125
125
  "name": "using-git-worktrees",
126
126
  "file": "assets/skills/using-git-worktrees/SKILL.md",
127
127
  "description": "Skill de uso de git worktrees para aislamiento de trabajo",
128
- "version": "0.8.0",
128
+ "version": "0.8.1",
129
129
  "sha256": "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
130
130
  },
131
131
  {
132
132
  "name": "using-superpowers",
133
133
  "file": "assets/skills/using-superpowers/SKILL.md",
134
134
  "description": "Skill de orquestación de Superpowers skills",
135
- "version": "0.8.0",
135
+ "version": "0.8.1",
136
136
  "sha256": "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
137
137
  },
138
138
  {
139
139
  "name": "writing-skills",
140
140
  "file": "assets/skills/writing-skills/SKILL.md",
141
141
  "description": "Skill de creación y edición de skills",
142
- "version": "0.8.0",
142
+ "version": "0.8.1",
143
143
  "sha256": "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
144
144
  },
145
145
  {
146
146
  "name": "graceful-degradation",
147
147
  "file": "assets/skills/graceful-degradation/SKILL.md",
148
148
  "description": "Skill de degradación graceful cuando múltiples tools están indisponibles",
149
- "version": "0.8.0",
149
+ "version": "0.8.1",
150
150
  "sha256": "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
151
151
  }
152
152
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Instalador interactivo de agentes y comandos para OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,18 +0,0 @@
1
- /**
2
- * @deprecated Fusionado en ostacky-plugin.ts — re-export legacy.
3
- * Single source: importa desde src/security.ts vía controller.
4
- * Fresh install solo escribe ostacky-plugin.ts + engram.ts.
5
- * Este archivo no debe ser instalado por ostacky install (deprecado).
6
- */
7
-
8
- import type { Plugin } from "@opencode-ai/plugin"
9
-
10
- // Re-export controller como guard por compatibilidad — no duplica isSensitive/BASH_SENSITIVE_RE
11
- export { OstackyController as OstackyGuard } from "./ostacky-plugin.ts"
12
-
13
- // También export default por compatibilidad con loaders que esperan default
14
- import { OstackyController } from "./ostacky-plugin.ts"
15
- export default OstackyController
16
-
17
- // Nota: isSensitive, BASH_SENSITIVE_RE, extractPathsFromBash vienen de src/security.ts
18
- // a través de ostacky-plugin.ts, no se copian aquí (verificación check:security).