ostacky 0.9.1 → 0.9.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/README.md CHANGED
@@ -246,37 +246,37 @@ Tras instalar (`--scope local`), el proyecto queda así:
246
246
 
247
247
  ```json
248
248
  {
249
- "version": "0.9.1",
249
+ "version": "0.9.2",
250
250
  "lockedAt": "2025-01-01T00:00:00.000Z",
251
251
  "repo": "JaimeHoracio/Ostacky",
252
- "tag": "v0.9.1",
252
+ "tag": "v0.9.2",
253
253
  "agents": {
254
254
  "ostacky": {
255
- "version": "0.9.1",
255
+ "version": "0.9.2",
256
256
  "installedAt": "2025-01-01T00:00:00.000Z",
257
257
  "sha256": "abc123..."
258
258
  }
259
259
  },
260
260
  "commands": {
261
261
  "install-stack": {
262
- "version": "0.9.1",
262
+ "version": "0.9.2",
263
263
  "installedAt": "2025-01-01T00:00:00.000Z",
264
264
  "sha256": "def456..."
265
265
  },
266
266
  "opsx-sync": {
267
- "version": "0.9.1",
267
+ "version": "0.9.2",
268
268
  "installedAt": "2025-01-01T00:00:00.000Z",
269
269
  "sha256": "ghi789..."
270
270
  }
271
271
  },
272
272
  "skills": {
273
- "brainstorming": { "version": "0.9.1", ... },
274
- "execution-mode-evaluation": { "version": "0.9.1", ... },
275
- "openspec-propose": { "version": "0.9.1", ... }
273
+ "brainstorming": { "version": "0.9.2", ... },
274
+ "execution-mode-evaluation": { "version": "0.9.2", ... },
275
+ "openspec-propose": { "version": "0.9.2", ... }
276
276
  },
277
277
  "mcpServers": {
278
- "ostacky-controller": { "version": "0.9.1", ... },
279
- "openspec": { "version": "0.9.1", ... }
278
+ "ostacky-controller": { "version": "0.9.2", ... },
279
+ "openspec": { "version": "0.9.2", ... }
280
280
  }
281
281
  }
282
282
  ```
@@ -308,7 +308,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
308
308
  ## Seguridad
309
309
 
310
310
  - `opencode.jsonc` se versiona en el repo para compartir permisos y MCP de forma reproducible.
311
- - Las URLs de descarga usan **tags de GitHub** (ej. `v0.9.1`), nunca `main` — instalaciones reproducibles
311
+ - Las URLs de descarga usan **tags de GitHub** (ej. `v0.9.2`), nunca `main` — instalaciones reproducibles
312
312
  - Cada path de archivo descargado es validado para prevenir **path traversal**
313
313
  - Los archivos incluyen **checksum SHA-256** opcional; si el manifest lo define, el contenido se verifica antes de escribir
314
314
  - El cache local (`.opencode/cache/`) también valida integridad al servir archivos cacheados
@@ -365,7 +365,7 @@ CodeGraph está instalado en `.opencode/tools/codegraph/bin/codegraph` y se conf
365
365
  | `.opencode/tools/codegraph/bin/codegraph status` | Muestra estado del index y archivos pendientes |
366
366
  | `.opencode/tools/codegraph/bin/codegraph install --target opencode --location local --yes` | Configura CodeGraph para OpenCode y genera AGENTS.md |
367
367
 
368
- > **Windows:** si ves `Command failed: cmd.exe /d /c call ... codegraph.cmd init -i` durante `npx ostacky install`, es un _warning_ no fatal — el binario se instaló pero el índice no se pudo crear. Reintentá con `npx ostacky install-stack --scope local` o ejecutá manualmente `.opencode\tools\codegraph\bin\codegraph.exe init -i` (o `codegraph.cmd` si no hay `.exe`) dentro del proyecto. Desde v0.9.1 el instalador usa el patrón resiliente de Engram (strip 0 + búsqueda recursiva) y loguea `projectRoot|toolsDir` para diagnosticar scope.
368
+ > **Windows:** si ves `Command failed: cmd.exe /d /c call ... codegraph.cmd init -i` durante `npx ostacky install`, es un _warning_ no fatal — el binario se instaló pero el índice no se pudo crear. Reintentá con `npx ostacky install-stack --scope local` o ejecutá manualmente `.opencode\tools\codegraph\bin\codegraph.exe init -i` (o `codegraph.cmd` si no hay `.exe`) dentro del proyecto. Desde v0.9.2 el instalador usa el patrón resiliente de Engram (strip 0 + búsqueda recursiva) y loguea `projectRoot|toolsDir` para diagnosticar scope.
369
369
 
370
370
  ## Licencia
371
371
 
@@ -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.9.1
4
+ version: 0.9.2
5
5
  ---
6
6
 
7
- Sos **Ostacky v0.9.1**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
7
+ Sos **Ostacky v0.9.2**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
8
8
 
9
- > **Versión:** `0.9.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.9.1"** (o `v0.9.1` si te piden solo el número). No inventes otra versión.
9
+ > **Versión:** `0.9.2` (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.9.2"** (o `v0.9.2` si te piden solo el número). No inventes otra versión.
10
10
 
11
11
  ## Reglas innegociables
12
12
 
@@ -85,13 +85,13 @@ Router `brainstorming`↔`OpenSpec` por `level`/`estLines`/`fileCount`/`hasAPI`
85
85
 
86
86
  1. `skill(execution-mode-evaluation)` en memoria, reusa discovery.
87
87
  2. Mostrar análisis → `¿Procedo?` → `record_execution_analysis` → `consume_execution_decision`.
88
- 3. Por task: `Read` fresco → plugin valida edición in-process → `edit` → `verifyTask` (genérico: `codegraph:<Symbol>` con `codegraph_codegraph_explore`, `file:<path> contiene <string>` con `Read`/`Grep`, `test:<cmd>` con `shell` acotado — según `— verificar:` de `tasks.md`) → solo si `verifyTask.ok` → `complete_task`. **NUNCA marques `complete_task` a ojo**; si `verifyTask` falla, reintentá el fix.
88
+ 3. Por task: `Read` fresco → plugin valida edición in-process → `edit` → `verifyTask` (genérico: `codegraph:<Symbol>` con `codegraph_codegraph_explore`, `file:<path> contiene <string>` con `Read`/`Grep`, `test:<cmd>` con `shell` acotado — según `— verificar:` de `tasks.md`) → solo si `verifyTask.ok` → `complete_task`. **NUNCA marques `complete_task` a ojo**; si `verifyTask` falla, reintentá el fix. Vale para SPEC y DIRECT, INLINE y SUBAGENTS.
89
89
 
90
90
  ### 5. Sync y cierre
91
91
 
92
92
  1. Tests + review
93
93
  2. `saveSessionClose` → `mem_session_summary` + `set_handoff` paralelo
94
- 3. `verifyIntegrity` → **si `pending.length > 0` NO llames `implementation_complete`**; lista explícita `Te faltan N tasks: [...]` y pide `complete_task`. Solo si `pending === 0` y `staleFiles === 0`, `implementation_complete` → `sync_complete`. **NUNCA llames `implementation_complete` automáticamente con pendientes** — espera a que el usuario complete o escriba literal `"confirmo forzar"` para `force:true` (queda auditado).
94
+ 3. `verifyIntegrity` → **si `pending.length > 0` NO llames `implementation_complete`**; lista explícita `Te faltan N tasks: [...]` y pide `complete_task`. Si `staleFiles.length > 0` por refino post-complete (archivo ya correcto, sin edición pendiente): llamá `refresh_fingerprint({filePath})` por cada archivo y reintentá `implementation_complete` sin `force`. Solo si `pending === 0` y `staleFiles === 0`, `implementation_complete` → `sync_complete`. **NUNCA llames `implementation_complete` automáticamente con pendientes** — espera a que el usuario complete o escriba literal `"confirmo forzar"` para `force:true` (queda auditado). Vale para SPEC y DIRECT, INLINE y SUBAGENTS.
95
95
 
96
96
  ## Guardrails
97
97
 
@@ -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.9.1, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, 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.9.2, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, 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. 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.9.1)
14
+ ## Scope de instalación — local vs global (desde v0.9.2)
15
15
 
16
16
  `npx ostacky install` soporta `--scope local|global|auto` (también `--scope=...`):
17
17
 
@@ -48,6 +48,10 @@ Allowlist: `.env.example/.template/.sample` nunca bloquea. `extractPathsFromBash
48
48
 
49
49
  `content` requerido salvo `content:"hash:"+fastFingerprint` cuando `lastValidated.filePath==filePath && fingerprint==hash` y fingerprint coincide con disco → EDITABLE sin body. Hash stale → CONFLICT `stale fingerprint`.
50
50
 
51
+ ## refresh_fingerprint (revalidación sin churn)
52
+
53
+ Tras refino post-complete (archivo ya correcto, sin edición pendiente): `refresh_fingerprint({filePath})` o `refresh_fingerprint({taskId})` re-sincroniza `tasks[].fileHash` + `fileFingerprints` con disco, sin `oldString/newString`. Vale para SPEC y DIRECT, en `EXECUTING_INLINE`, `EXECUTING_SUBAGENTS` y `SPECIFICATION`. Luego `verifyIntegrity.ok` → `implementation_complete` sin `force`.
54
+
51
55
  ## Enforcement (plugin)
52
56
 
53
57
  El plugin hace cumplir `PENDING` en `ctx.tool.hook("execute.before")`; `lastCheck={revision, result}` cachea ALLOW por revisión, revalida si cambia o >5 tools; `BLOCKED` nunca cacheado. Métricas `stateCheckCount` cuentan checks del plugin (no del LLM).
@@ -64,15 +68,15 @@ Principio: **eficacia > recorte** — si recorte rompiera caché y saliera más
64
68
 
65
69
  ## Security single-source
66
70
 
67
- `src/security.ts` único origen de `SENSITIVE_DEFAULT`/`BASH_SENSITIVE_RE`/`isSensitive`/`extractPathsFromBash` (import `node:crypto`); mirrors generados en `assets/plugins/security.ts` (solo Sensitive guard, sin crypto) y `assets/mcp/ostacky-controller/security.js` vía `bun run scripts/sync-controller-core.ts`. `ostacky-plugin.ts` importa de `./security.ts` self-contained (no `../../src`). `BASH_SENSITIVE_RE` arriba. `OSTACKY_SENSITIVE_PATTERNS` override.
71
+ `src/security.ts` único origen de `SENSITIVE_DEFAULT`/`BASH_SENSITIVE_RE`/`isSensitive`/`extractPathsFromBash` (import `node:crypto`); mirrors generados en `assets/plugins/ostacky-controller/security.ts` (solo Sensitive guard, sin crypto) y `assets/mcp/ostacky-controller/security.js` vía `bun run scripts/sync-controller-core.ts`. `assets/plugins/ostacky-controller/index.ts` importa de `./security.ts` self-contained (no `../../src`). `BASH_SENSITIVE_RE` arriba. `OSTACKY_SENSITIVE_PATTERNS` override.
68
72
 
69
73
  ## Tiered single-source
70
74
 
71
- `src/tiered.ts` único origen de `isTrivial(msg,state)` + `getControllerState(dir)`. `ostacky-plugin.ts` importa de `./tiered.ts` mirror (generado vía `sync-controller-core.ts`), `engram.ts` inlined (misma regex, evita import dinámico). No duplicar regex. Ver `assets/plugins/ostacky-plugin.ts` y `assets/plugins/engram.ts`.
75
+ `src/tiered.ts` único origen de `isTrivial(msg,state)` + `getControllerState(dir)`. `index.ts` importa de `./tiered.ts` mirror (generado vía `sync-controller-core.ts`), `engram.ts` inlined (misma regex, evita import dinámico). No duplicar regex. Ver `assets/plugins/ostacky-controller/` y `assets/plugins/engram.ts`.
72
76
 
73
77
  ## Controller single-source
74
78
 
75
- `src/controller-core.ts` CANÓNICO para `STATES/TRANSITIONS/DEFAULT_STATE`; mirrors generados en `assets/plugins/controller-core.ts` (TS) y `assets/mcp/ostacky-controller/controller-core.js` (JS strippado) vía `bun run scripts/sync-controller-core.ts` (prebuild). No editar mirrors. `ostacky-plugin.ts` importa de `./controller-core.ts` self-contained.
79
+ `src/controller-core.ts` CANÓNICO para `STATES/TRANSITIONS/DEFAULT_STATE`; mirrors generados en `assets/plugins/ostacky-controller/controller-core.ts` (TS) y `assets/mcp/ostacky-controller/controller-core.js` (JS strippado) vía `bun run scripts/sync-controller-core.ts` (prebuild). No editar mirrors. `index.ts` importa de `./controller-core.ts` self-contained. Import siempre `./security.ts` (el server V2 no resuelve `.js→.ts`).
76
80
 
77
81
  ## Prompt-efficiency
78
82
 
@@ -411,6 +411,22 @@ function fastFingerprint(filePath) {
411
411
  }
412
412
  }
413
413
 
414
+ // Refresh honesto sin churn: resuelve a absoluto vía projectRoot para no depender de cwd.
415
+ // Usado por stale-checks, complete_task y refreshFingerprint (fix post-complete refine).
416
+ function fingerprintFor(filePath, statePath) {
417
+ try {
418
+ if (!filePath) return null;
419
+ const projectRoot = getProjectRoot(statePath);
420
+ const abs =
421
+ filePath.startsWith('/') || /^[A-Za-z]:/.test(filePath)
422
+ ? resolve(filePath)
423
+ : resolve(projectRoot, filePath);
424
+ return fastFingerprint(abs);
425
+ } catch {
426
+ return null;
427
+ }
428
+ }
429
+
414
430
  class OstackyController {
415
431
  #statePath;
416
432
  #state;
@@ -1697,14 +1713,14 @@ class OstackyController {
1697
1713
  try {
1698
1714
  for (const [taskId, info] of Object.entries(this.#state.tasks || {})) {
1699
1715
  if (info.status !== 'COMPLETED' || !info.filePath || !info.fileHash) continue;
1700
- const current = fastFingerprint(info.filePath);
1716
+ const current = fingerprintFor(info.filePath, this.#statePath);
1701
1717
  if (!current) staleFiles.push(`${taskId}:${info.filePath} (missing)`);
1702
1718
  else if (current !== info.fileHash) staleFiles.push(`${taskId}:${info.filePath} (stale fingerprint)`);
1703
1719
  seenFp2.add(info.filePath);
1704
1720
  }
1705
1721
  for (const [fp, stored] of Object.entries(this.#state.fileFingerprints || {})) {
1706
1722
  if (seenFp2.has(fp)) continue;
1707
- const cur = fastFingerprint(fp);
1723
+ const cur = fingerprintFor(fp, this.#statePath);
1708
1724
  if (!cur) staleFiles.push(`${fp} (missing)`);
1709
1725
  else if (cur !== stored) staleFiles.push(`${fp} (stale fingerprint)`);
1710
1726
  }
@@ -1764,7 +1780,7 @@ class OstackyController {
1764
1780
  verificationFailed: verificationFailed.length ? verificationFailed : undefined,
1765
1781
  current_state: this.#state.state,
1766
1782
  attempted_transition: 'implementation_complete',
1767
- suggestion: `${detail} Completá cada task con Read fresco → validate_edit → edit → complete_task (con verificación) y luego reintentá implementation_complete sin force. Solo si querés descartar trabajo pendiente, escribí literal "confirmo forzar" en tu mensaje y reintentá con {force:true}. No propongo cancelar; propongo completar.`,
1783
+ suggestion: `${detail} Si refinaste archivos tras el complete (fingerprint stale), llamá refresh_fingerprint({filePath}) por cada archivo y reintentá implementation_complete sin force. Solo si hay edición real pendiente: Read fresco → validate_edit → edit → complete_task. Solo si querés descartar trabajo pendiente, escribí literal "confirmo forzar" en tu mensaje y reintentá con {force:true}. No propongo cancelar; propongo completar.`,
1768
1784
  };
1769
1785
  }
1770
1786
  if (hasBlocking && force) {
@@ -1785,7 +1801,7 @@ class OstackyController {
1785
1801
  verificationFailed: verificationFailed.length ? verificationFailed : undefined,
1786
1802
  current_state: this.#state.state,
1787
1803
  attempted_transition: 'implementation_complete',
1788
- suggestion: `${detail} Para forzar el cierre, escribí literal "confirmo forzar" en tu mensaje (quedará auditado) y reintentá con {force:true}. Si no querés forzar, completá los pendientes y reintentá sin force.`,
1804
+ suggestion: `${detail} Para forzar el cierre, escribí literal "confirmo forzar" en tu mensaje (quedará auditado) y reintentá con {force:true}. Si no querés forzar y es solo stale por refino, llamá refresh_fingerprint({filePath}) y reintentá sin force.`,
1789
1805
  };
1790
1806
  }
1791
1807
  const all = [...pending, ...staleFiles, ...verificationFailed].join(',');
@@ -1969,14 +1985,14 @@ class OstackyController {
1969
1985
  try {
1970
1986
  for (const [taskId, info] of Object.entries(this.#state.tasks || {})) {
1971
1987
  if (info.status !== 'COMPLETED' || !info.filePath || !info.fileHash) continue;
1972
- const current = fastFingerprint(info.filePath);
1988
+ const current = fingerprintFor(info.filePath, this.#statePath);
1973
1989
  if (!current) staleFiles.push(`${taskId}:${info.filePath} (missing)`);
1974
1990
  else if (current !== info.fileHash) staleFiles.push(`${taskId}:${info.filePath} (stale fingerprint)`);
1975
1991
  seenFp.add(info.filePath);
1976
1992
  }
1977
1993
  for (const [fp, stored] of Object.entries(this.#state.fileFingerprints || {})) {
1978
1994
  if (seenFp.has(fp)) continue;
1979
- const current = fastFingerprint(fp);
1995
+ const current = fingerprintFor(fp, this.#statePath);
1980
1996
  if (!current) staleFiles.push(`${fp} (missing)`);
1981
1997
  else if (current !== stored) staleFiles.push(`${fp} (stale fingerprint)`);
1982
1998
  }
@@ -2448,11 +2464,11 @@ class OstackyController {
2448
2464
  }
2449
2465
  if (!this.#state.tasks) this.#state.tasks = {};
2450
2466
 
2451
- // O6: Use fast fingerprint if no hash provided
2452
- const effectiveHash = fileHash || (filePath ? fastFingerprint(filePath) : null);
2467
+ // O6: Use fast fingerprint if no hash provided (absoluto vía projectRoot)
2468
+ const effectiveHash = fileHash || (filePath ? fingerprintFor(filePath, this.#statePath) : null);
2453
2469
  // 1.6: fingerprint obligatorio si archivo existe
2454
2470
  if (filePath) {
2455
- const existsCheck = fastFingerprint(filePath);
2471
+ const existsCheck = fingerprintFor(filePath, this.#statePath);
2456
2472
  if (existsCheck && !effectiveHash) {
2457
2473
  return { error: 'fingerprint required: file exists but fileHash is null' };
2458
2474
  }
@@ -2561,6 +2577,58 @@ class OstackyController {
2561
2577
  };
2562
2578
  }
2563
2579
 
2580
+ /**
2581
+ * Refresh honesto sin churn: re-sincroniza fingerprints con disco tras refino
2582
+ * post-complete. Vale para SPEC y DIRECT, en EXECUTING_INLINE, EXECUTING_SUBAGENTS
2583
+ * y SPECIFICATION. No exige oldString/newString ni reeditar.
2584
+ */
2585
+ async refreshFingerprint({ filePath, taskId } = {}) {
2586
+ this.#load();
2587
+ const allowed = ['EXECUTING_INLINE', 'EXECUTING_SUBAGENTS', 'SPECIFICATION'];
2588
+ if (!allowed.includes(this.#state.state)) {
2589
+ return { error: `Cannot refresh fingerprint from state ${this.#state.state}`, ok: false };
2590
+ }
2591
+ if (taskId && !isValidTaskId(taskId)) return { error: `invalid taskId: ${taskId}`, ok: false };
2592
+ let target = filePath || null;
2593
+ if (!target && taskId && this.#state.tasks?.[taskId]?.filePath) {
2594
+ target = this.#state.tasks[taskId].filePath;
2595
+ }
2596
+ if (!target) return { error: 'filePath or taskId with file is required', ok: false };
2597
+ if (!isPathInsideProject(target, this.#statePath)) {
2598
+ return { error: `filePath outside projectRoot: ${target}`, ok: false, filePath: target };
2599
+ }
2600
+ if (this.isSensitiveFile(target) && !this.#state.allowedFiles?.[target]) {
2601
+ return { error: `BLOCKED: File ${target} requires check_file_access`, ok: false, filePath: target };
2602
+ }
2603
+ const projectRoot = getProjectRoot(this.#statePath);
2604
+ const abs =
2605
+ target.startsWith('/') || /^[A-Za-z]:/.test(target) ? resolve(target) : resolve(projectRoot, target);
2606
+ if (!existsSync(abs)) return { error: `file not found: ${target}`, ok: false, filePath: target };
2607
+ const current = fingerprintFor(target, this.#statePath);
2608
+ if (!current) return { error: `cannot fingerprint: ${target}`, ok: false, filePath: target };
2609
+ const updatedTasks = [];
2610
+ if (!this.#state.fileFingerprints) this.#state.fileFingerprints = {};
2611
+ this.#state.fileFingerprints[target] = current;
2612
+ if (taskId && this.#state.tasks?.[taskId]) {
2613
+ this.#state.tasks[taskId].fileHash = current;
2614
+ if (!this.#state.tasks[taskId].filePath) this.#state.tasks[taskId].filePath = target;
2615
+ updatedTasks.push(taskId);
2616
+ }
2617
+ for (const [tid, info] of Object.entries(this.#state.tasks || {})) {
2618
+ if (info.filePath === target && !updatedTasks.includes(tid)) {
2619
+ info.fileHash = current;
2620
+ updatedTasks.push(tid);
2621
+ }
2622
+ }
2623
+ await this.#persist();
2624
+ await this.#audit(
2625
+ 'EXECUTING',
2626
+ 'refresh_fingerprint',
2627
+ `filePath=${target} tasks=${updatedTasks.join(',') || '-'}`
2628
+ );
2629
+ return { ok: true, filePath: target, fingerprint: current, updatedTasks };
2630
+ }
2631
+
2564
2632
  async verifyTask({ taskId, filePath: filePathParam } = {}) {
2565
2633
  this.#load();
2566
2634
  if (!taskId) return { error: 'taskId required' };
@@ -2785,7 +2853,7 @@ function safeHandler(fn, options = {}) {
2785
2853
 
2786
2854
  const server = new McpServer({
2787
2855
  name: 'ostacky-controller',
2788
- version: '0.9.1',
2856
+ version: '0.9.2',
2789
2857
  });
2790
2858
 
2791
2859
  server.registerTool(
@@ -3326,6 +3394,23 @@ server.registerTool(
3326
3394
  })
3327
3395
  );
3328
3396
 
3397
+ server.registerTool(
3398
+ 'refresh_fingerprint',
3399
+ {
3400
+ description:
3401
+ 'Re-sincroniza fingerprints con disco tras refino post-complete, sin reeditar. ' +
3402
+ 'Vale para SPEC y DIRECT, en EXECUTING_INLINE, EXECUTING_SUBAGENTS y SPECIFICATION.',
3403
+ inputSchema: z.object({
3404
+ filePath: z.string().optional().describe('File path refinado tras complete.'),
3405
+ taskId: z.string().optional().describe('Task ID (si se omite filePath se usa su archivo).'),
3406
+ }),
3407
+ },
3408
+ safeHandler(async ({ filePath, taskId }) => {
3409
+ log('tool:refresh_fingerprint', { filePath, taskId });
3410
+ return await controller.refreshFingerprint({ filePath, taskId });
3411
+ })
3412
+ );
3413
+
3329
3414
  /**
3330
3415
  * Graceful shutdown: clean up tmp/lock files and flush state.
3331
3416
  */
@@ -3357,7 +3442,7 @@ function setupGracefulShutdown(ctrl) {
3357
3442
  }
3358
3443
 
3359
3444
  async function main() {
3360
- log('Starting ostacky-controller MCP v0.9.1...');
3445
+ log('Starting ostacky-controller MCP v0.9.2...');
3361
3446
  log('State path:', { path: statePath });
3362
3447
  // Clean up stale tmp/lock files from previous runs
3363
3448
  cleanupTmpFiles(statePath);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky-controller",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -14,7 +14,10 @@
14
14
  * hooks (event.sessionID) rather than relying on a session.created event.
15
15
  */
16
16
 
17
- import { Plugin } from "@opencode/plugin"
17
+ // NOTA: sin `import { Plugin }` runtime a propósito — el server V2 no resuelve
18
+ // `@opencode/plugin` desde `.opencode/plugins/` y `Plugin.define()` es
19
+ // passthrough ({id, setup}). `import type` se borra al transpilar.
20
+ import type { Plugin } from "@opencode/plugin"
18
21
  import { join, dirname, basename, delimiter } from "node:path"
19
22
  import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from "node:fs"
20
23
  import { spawn, spawnSync } from "node:child_process"
@@ -197,7 +200,8 @@ function stripJsoncComments(text: string): string {
197
200
 
198
201
  // ─── Plugin ──────────────────────────────────────────────────────────────────
199
202
 
200
- export default Plugin.define({
203
+ // Objeto literal directo (ver nota en imports).
204
+ export default {
201
205
  id: "engram",
202
206
  async setup(ctx) {
203
207
  const directory = ctx.location.directory
@@ -645,4 +649,4 @@ export default Plugin.define({
645
649
 
646
650
  return () => eventController.abort()
647
651
  },
648
- })
652
+ }
@@ -11,7 +11,11 @@
11
11
  * CodeGraph preventivo: bloquea Read/Grep masivo sin Discovery hit.
12
12
  */
13
13
 
14
- import { Plugin } from "@opencode/plugin"
14
+ // NOTA: sin `import { Plugin } from "@opencode/plugin"` a propósito — el server
15
+ // V2 no resuelve ese paquete desde `.opencode/plugins/` (Die ResolveMessage) y
16
+ // `Plugin.define()` es passthrough ({id, setup}). Objeto literal + `import type`
17
+ // (solo tipos, borrado al transpilar) cargan sin dependencias externas.
18
+ import type { Plugin } from "@opencode/plugin"
15
19
  import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync, statSync, readdirSync, unlinkSync } from "node:fs"
16
20
  import { join, dirname, basename, resolve, relative, delimiter } from "node:path"
17
21
  import { SENSITIVE_DEFAULT, BASH_SENSITIVE_RE, isSensitive, extractPathsFromBash } from "./security.ts"
@@ -178,7 +182,9 @@ function extractPathsFromPatch(patchText: string): string[] {
178
182
 
179
183
  // ─── Plugin ──────────────────────────────────────────────────────────────────
180
184
 
181
- export default Plugin.define({
185
+ // Objeto literal directo (ver nota en imports): el server V2 acepta
186
+ // `{ id, setup }` sin pasar por `Plugin.define()`.
187
+ export default {
182
188
  id: "ostacky-controller",
183
189
  async setup(ctx) {
184
190
  const directory = ctx.location.directory
@@ -374,9 +380,27 @@ export default Plugin.define({
374
380
  const isCodeFile = /\.(ts|js|tsx|jsx|mts|cts)$/i.test(targetPath) || targetPath.includes("src/") || targetPath.includes("assets/") || args?.pattern?.includes("*.ts")
375
381
  // harden-task-integrity 1.3: eximir reads de tasks.md del gate (no requiere CodeGraph)
376
382
  const isSpecTasksPath = targetPath.includes("openspec/changes") || targetPath.includes("openspec/specs")
383
+ // Docs y markdown nunca son código: no exigir Discovery (fix over-blocking assets/docs)
384
+ const isDocsPath = targetPath.includes("assets/docs") || targetPath.includes("/docs/") || /\.md$/i.test(targetPath) || /\.json$/i.test(targetPath)
377
385
  // Grep on *.md should not be blocked
378
386
  const isLiteralGrep = tool === "grep" && (args?.include?.endsWith(".md") || args?.include?.endsWith(".json"))
379
- if (isCodeFile && !isLiteralGrep && !isSpecTasksPath) {
387
+ // Refresh honesto: revalidación post-complete necesita Read fresco sin Discovery hit.
388
+ // Vale para SPEC y DIRECT, INLINE y SUBAGENTS: si el path está trackeado en
389
+ // tasks/fileFingerprints/lastValidated, no exigir Discovery.
390
+ let isTaskTracked = false
391
+ try {
392
+ const st = freshState
393
+ if (st && targetPath) {
394
+ const fps = Object.keys(st.fileFingerprints || {})
395
+ const taskFiles = Object.values(st.tasks || {}).map((t: any) => t.filePath).filter(Boolean)
396
+ const lvFile = (st.lastValidated as any)?.filePath || null
397
+ isTaskTracked =
398
+ fps.some((fp) => targetPath.endsWith(fp) || fp.endsWith(targetPath) || targetPath.includes(fp) || fp.includes(targetPath)) ||
399
+ taskFiles.some((fp: string) => targetPath.endsWith(fp) || fp.endsWith(targetPath) || targetPath.includes(fp) || fp.includes(targetPath)) ||
400
+ (lvFile ? targetPath.endsWith(lvFile) || lvFile.endsWith(targetPath) : false)
401
+ }
402
+ } catch {}
403
+ if (isCodeFile && !isLiteralGrep && !isSpecTasksPath && !isDocsPath && !isTaskTracked) {
380
404
  const hasDiscoveryHit = discoveryHitByRequest.get(sessionId) ?? getDiscoveryCacheHit(directory)
381
405
  const codegraphOk = isCodegraphAvailable(directory)
382
406
  if (codegraphOk && !hasDiscoveryHit) {
@@ -420,7 +444,9 @@ export default Plugin.define({
420
444
  // ── 3.5) Shell file-mutation gate (harden-task-integrity: shell mutante requiere validate_edit) ──
421
445
  if (tool === "shell" && freshState && ["EXECUTING_INLINE", "EXECUTING_SUBAGENTS"].includes(freshState.state)) {
422
446
  const cmdForMutation: string = args?.command || args?.cmd || ""
423
- const isMutating = /[>]{1,2}\s*\S+|\bsed\b[^|;]*-i|\btruncate\b|\btee\b|\bcp\s+|\bmv\s+|python.*open.*w/.test(cmdForMutation)
447
+ // Fix falso positivo: `2>&1`, `1>&2`, `2>/dev/null` son redirects de stderr, no mutación.
448
+ const cmdSansStderr = cmdForMutation.replace(/2>&1/g, "").replace(/1>&2/g, "").replace(/2>\s*\/dev\/null/g, "")
449
+ const isMutating = /(^|[^0-9&])>{1,2}\s*(?!&)\S+|\bsed\b[^|;]*-i|\btruncate\b|\btee\b|\bcp\s+|\bmv\s+|python.*open.*w/.test(cmdSansStderr)
424
450
  if (isMutating) {
425
451
  const mPaths = extractPathsFromBash(cmdForMutation)
426
452
  for (const p of mPaths) {
@@ -711,4 +737,4 @@ export default Plugin.define({
711
737
  eventController.abort()
712
738
  }
713
739
  },
714
- })
740
+ }
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Tiered helpers — single source of truth for trivial detection y niveles.
8
8
  *
9
- * Ambos plugins (ostacky-plugin.ts y engram.ts) importan de acá.
9
+ * Consumido por el plugin ostacky-controller/index.ts y engram.ts (mirror en package dir).
10
10
  * No duplicar regex. Cache-friendly: isTrivial no muta system[0], solo decide hint.
11
11
  * LEVEL_THRESHOLDS es la única definición de niveles (D6).
12
12
  */
package/dist/cli.js CHANGED
@@ -621,31 +621,31 @@ var init_fs = __esm(() => {
621
621
  var manifest_default;
622
622
  var init_manifest = __esm(() => {
623
623
  manifest_default = {
624
- version: "0.9.1",
624
+ version: "0.9.2",
625
625
  repo: "JaimeHoracio/Ostacky",
626
- tag: "v0.9.1",
626
+ tag: "v0.9.2",
627
627
  agents: [
628
628
  {
629
629
  name: "ostacky",
630
630
  file: "assets/agents/ostacky.md",
631
- 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.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
632
- version: "0.9.1",
633
- sha256: "8186994cc9d92585310d8f4c67255e5169f1f3b3d7fc5b3563c2fa79f1203d24"
631
+ 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.2: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
632
+ version: "0.9.2",
633
+ sha256: "f588cd07f67f73393f23601b025d1f5980a7db4efe194f248ff4d09cc98b04d4"
634
634
  }
635
635
  ],
636
636
  commands: [
637
637
  {
638
638
  name: "install-stack",
639
639
  file: "assets/commands/install-stack.md",
640
- description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.9.1: controller plugin como alma hard-gate, Context7 removido del stack.",
641
- version: "0.9.1",
642
- sha256: "af62710619308920f7be8743ed43c726d3591ba2af6535c3cc3ca6db95ad9715"
640
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.9.2: controller plugin como alma hard-gate, Context7 removido del stack.",
641
+ version: "0.9.2",
642
+ sha256: "288e2fdd0df24378b92c15a169b846bd9cbc3290dfaf09597293e6f5240f562e"
643
643
  },
644
644
  {
645
645
  name: "opsx-sync",
646
646
  file: "assets/commands/opsx-sync.md",
647
647
  description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
648
- version: "0.9.1",
648
+ version: "0.9.2",
649
649
  sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
650
650
  }
651
651
  ],
@@ -653,15 +653,15 @@ var init_manifest = __esm(() => {
653
653
  {
654
654
  name: "ostacky-controller",
655
655
  file: "assets/mcp/ostacky-controller/",
656
- description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.9.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.",
657
- version: "0.9.1",
658
- sha256: "d5d4712e5d823186c267f63cea776feddec4700f1b15286796586efb60c65241"
656
+ description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.9.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.",
657
+ version: "0.9.2",
658
+ sha256: "1fa9251efbf98552a8eb5f88a2f741021b82b21bc37f96d5253849f9209c949a"
659
659
  },
660
660
  {
661
661
  name: "openspec",
662
662
  file: "assets/mcp/openspec/",
663
663
  description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
664
- version: "0.9.1",
664
+ version: "0.9.2",
665
665
  sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
666
666
  }
667
667
  ],
@@ -670,105 +670,105 @@ var init_manifest = __esm(() => {
670
670
  name: "brainstorming",
671
671
  file: "assets/skills/brainstorming/SKILL.md",
672
672
  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)",
673
- version: "0.9.1",
673
+ version: "0.9.2",
674
674
  sha256: "f1013a86a8f27914d85a716e23c7fd32db1786d93615e8b0834b13d184308549"
675
675
  },
676
676
  {
677
677
  name: "execution-mode-evaluation",
678
678
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
679
679
  description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
680
- version: "0.9.1",
680
+ version: "0.9.2",
681
681
  sha256: "2158a6864c0912e868a9d720db7fd79441e6cc298e3320833d2527c9727c852e"
682
682
  },
683
683
  {
684
684
  name: "tdd",
685
685
  file: "assets/skills/tdd/SKILL.md",
686
686
  description: "Skill de test-driven development (Superpowers)",
687
- version: "0.9.1",
687
+ version: "0.9.2",
688
688
  sha256: "f0ebfbbc43a8868d31a3fd2df37ae7f60fe8add09c6475862395a68269e5ff7f"
689
689
  },
690
690
  {
691
691
  name: "subagent-driven-development",
692
692
  file: "assets/skills/subagent-driven-development/SKILL.md",
693
693
  description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
694
- version: "0.9.1",
694
+ version: "0.9.2",
695
695
  sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
696
696
  },
697
697
  {
698
698
  name: "dispatching-parallel-agents",
699
699
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
700
700
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
701
- version: "0.9.1",
701
+ version: "0.9.2",
702
702
  sha256: "7e7b712ef742d5631e302e6a71583e2bbbd33094da9281abfb84b43505fc3ef1"
703
703
  },
704
704
  {
705
705
  name: "review",
706
706
  file: "assets/skills/review/SKILL.md",
707
707
  description: "Skill de revisión de código (Superpowers)",
708
- version: "0.9.1",
708
+ version: "0.9.2",
709
709
  sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
710
710
  },
711
711
  {
712
712
  name: "receiving-code-review",
713
713
  file: "assets/skills/receiving-code-review/SKILL.md",
714
714
  description: "Skill de recibir y procesar feedback de code review",
715
- version: "0.9.1",
715
+ version: "0.9.2",
716
716
  sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
717
717
  },
718
718
  {
719
719
  name: "openspec-propose",
720
720
  file: "assets/skills/openspec-propose/SKILL.md",
721
721
  description: "Skill de generación de proposal (OpenSpec)",
722
- version: "0.9.1",
722
+ version: "0.9.2",
723
723
  sha256: "6a517f750f24c008208156a2d653e97ed30b1e53878aeac5ce394e573db0678f"
724
724
  },
725
725
  {
726
726
  name: "openspec-apply-change",
727
727
  file: "assets/skills/openspec-apply-change/SKILL.md",
728
728
  description: "Skill de aplicación de change (OpenSpec)",
729
- version: "0.9.1",
729
+ version: "0.9.2",
730
730
  sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
731
731
  },
732
732
  {
733
733
  name: "openspec-archive-change",
734
734
  file: "assets/skills/openspec-archive-change/SKILL.md",
735
735
  description: "Skill de archivo de change (OpenSpec)",
736
- version: "0.9.1",
736
+ version: "0.9.2",
737
737
  sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
738
738
  },
739
739
  {
740
740
  name: "openspec-explore",
741
741
  file: "assets/skills/openspec-explore/SKILL.md",
742
742
  description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
743
- version: "0.9.1",
743
+ version: "0.9.2",
744
744
  sha256: "9eafc3a8c693b8d9916867ef764d49e5222da717fde501c957bb3f6c312d2415"
745
745
  },
746
746
  {
747
747
  name: "using-git-worktrees",
748
748
  file: "assets/skills/using-git-worktrees/SKILL.md",
749
749
  description: "Skill de uso de git worktrees para aislamiento de trabajo",
750
- version: "0.9.1",
750
+ version: "0.9.2",
751
751
  sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
752
752
  },
753
753
  {
754
754
  name: "using-superpowers",
755
755
  file: "assets/skills/using-superpowers/SKILL.md",
756
756
  description: "Skill de orquestación de Superpowers skills",
757
- version: "0.9.1",
757
+ version: "0.9.2",
758
758
  sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
759
759
  },
760
760
  {
761
761
  name: "writing-skills",
762
762
  file: "assets/skills/writing-skills/SKILL.md",
763
763
  description: "Skill de creación y edición de skills",
764
- version: "0.9.1",
764
+ version: "0.9.2",
765
765
  sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
766
766
  },
767
767
  {
768
768
  name: "graceful-degradation",
769
769
  file: "assets/skills/graceful-degradation/SKILL.md",
770
770
  description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
771
- version: "0.9.1",
771
+ version: "0.9.2",
772
772
  sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
773
773
  }
774
774
  ]
@@ -1178,22 +1178,25 @@ function copyEngramPlugin(projectRoot) {
1178
1178
  copyFileSync3(pluginSource, join7(pluginsDir, "engram.ts"));
1179
1179
  }
1180
1180
  function copyOstackyControllerPlugin(projectRoot) {
1181
- const pluginSource = join7(PACKAGE_ROOT, "assets", "plugins", "ostacky-plugin.ts");
1182
- const coreSource = join7(PACKAGE_ROOT, "assets", "plugins", "controller-core.ts");
1183
- const securitySource = join7(PACKAGE_ROOT, "assets", "plugins", "security.ts");
1184
- const tieredSource = join7(PACKAGE_ROOT, "assets", "plugins", "tiered.ts");
1181
+ const packageSource = join7(PACKAGE_ROOT, "assets", "plugins", "ostacky-controller");
1185
1182
  const pluginsDir = join7(projectRoot, ".opencode", "plugins");
1186
- if (!existsSync6(pluginSource)) {
1187
- throw new Error(`Plugin bundleado de OstackyController no encontrado: ${pluginSource}`);
1183
+ const packageDest = join7(pluginsDir, "ostacky-controller");
1184
+ if (!existsSync6(join7(packageSource, "index.ts"))) {
1185
+ throw new Error(`Plugin bundleado de OstackyController no encontrado: ${packageSource}`);
1186
+ }
1187
+ mkdirSync4(packageDest, { recursive: true });
1188
+ for (const f of ["index.ts", "controller-core.ts", "security.ts", "tiered.ts"]) {
1189
+ const src = join7(packageSource, f);
1190
+ if (existsSync6(src))
1191
+ copyFileSync3(src, join7(packageDest, f));
1192
+ }
1193
+ for (const f of ["ostacky-plugin.ts", "controller-core.ts", "security.ts", "tiered.ts"]) {
1194
+ const legacy = join7(pluginsDir, f);
1195
+ try {
1196
+ if (existsSync6(legacy))
1197
+ rmSync4(legacy, { force: true });
1198
+ } catch {}
1188
1199
  }
1189
- mkdirSync4(pluginsDir, { recursive: true });
1190
- copyFileSync3(pluginSource, join7(pluginsDir, "ostacky-plugin.ts"));
1191
- if (existsSync6(coreSource))
1192
- copyFileSync3(coreSource, join7(pluginsDir, "controller-core.ts"));
1193
- if (existsSync6(securitySource))
1194
- copyFileSync3(securitySource, join7(pluginsDir, "security.ts"));
1195
- if (existsSync6(tieredSource))
1196
- copyFileSync3(tieredSource, join7(pluginsDir, "tiered.ts"));
1197
1200
  }
1198
1201
  function buildEngramDownloadUrl(tag, platform = process.platform, arch = process.arch) {
1199
1202
  const target = getEngramReleaseTarget(platform, arch);
@@ -1506,6 +1509,23 @@ function uninstallStackConfig(paths) {
1506
1509
  removed.push(".opencode/tools/");
1507
1510
  } catch {}
1508
1511
  }
1512
+ const ownedPlugins = [
1513
+ "ostacky-controller",
1514
+ "engram.ts",
1515
+ "ostacky-plugin.ts",
1516
+ "controller-core.ts",
1517
+ "security.ts",
1518
+ "tiered.ts"
1519
+ ];
1520
+ for (const name of ownedPlugins) {
1521
+ const p = join7(paths.plugins, name);
1522
+ if (existsSync6(p)) {
1523
+ try {
1524
+ rmSync4(p, { recursive: true, force: true });
1525
+ removed.push(`.opencode/plugins/${name}`);
1526
+ } catch {}
1527
+ }
1528
+ }
1509
1529
  if (removed.length === 0) {
1510
1530
  return { success: true, message: "No había configuración del stack para remover" };
1511
1531
  }
@@ -1522,13 +1542,15 @@ var init_stack = __esm(() => {
1522
1542
  });
1523
1543
 
1524
1544
  // src/doctor-v2.ts
1525
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
1545
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
1526
1546
  import { join as join10, delimiter, isAbsolute } from "path";
1527
1547
  function checkDoctorV2(projectRoot) {
1528
1548
  const lines = [];
1529
1549
  const cwd = projectRoot;
1530
1550
  const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
1531
1551
  const pluginPaths = [
1552
+ join10(opencodeDir, "plugins", "ostacky-controller", "index.ts"),
1553
+ join10(cwd, ".opencode", "plugins", "ostacky-controller", "index.ts"),
1532
1554
  join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
1533
1555
  join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
1534
1556
  join10(cwd, "assets", "plugins", "ostacky-plugin.ts")
@@ -1546,6 +1568,33 @@ function checkDoctorV2(projectRoot) {
1546
1568
  lines.push(`⚠️ controller: no se pudo leer ${found}`);
1547
1569
  }
1548
1570
  }
1571
+ const pluginsDir = join10(opencodeDir, "plugins");
1572
+ for (const f of ["controller-core.ts", "security.ts", "tiered.ts"]) {
1573
+ try {
1574
+ const p = join10(pluginsDir, f);
1575
+ if (existsSync8(p) && !readFileSync6(p, "utf-8").includes("export default")) {
1576
+ lines.push(`⚠️ plugins/${f} suelto sin default — el server lo carga como plugin y falla; mové a plugins/ostacky-controller/ o re-corré install (doctor no lo modifica)`);
1577
+ }
1578
+ } catch {}
1579
+ }
1580
+ try {
1581
+ const home = process.env.HOME || process.env.USERPROFILE || "";
1582
+ const globalPlugins = home ? join10(home, ".config", "opencode", "plugins") : "";
1583
+ if (globalPlugins) {
1584
+ let entries = [];
1585
+ try {
1586
+ entries = readdirSync3(globalPlugins).filter((e) => e.endsWith(".ts"));
1587
+ } catch {}
1588
+ for (const e of entries) {
1589
+ try {
1590
+ const src = readFileSync6(join10(globalPlugins, e), "utf-8");
1591
+ if (src.includes("@opencode-ai/") || !src.includes("export default")) {
1592
+ lines.push(`⚠️ global ${e} con formato V1 (@opencode-ai/* o sin default) — no corre en server V2; actualizalo o borralo a mano (doctor no lo modifica)`);
1593
+ }
1594
+ } catch {}
1595
+ }
1596
+ }
1597
+ } catch {}
1549
1598
  const configPath = findOpenCodeConfig(cwd);
1550
1599
  if (!configPath) {
1551
1600
  lines.push("ℹ️ config: no hay opencode.json ni opencode.jsonc");
@@ -1625,7 +1674,7 @@ var init_doctor_v2 = __esm(() => {
1625
1674
  // package.json
1626
1675
  var package_default = {
1627
1676
  name: "ostacky",
1628
- version: "0.9.1",
1677
+ version: "0.9.2",
1629
1678
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1630
1679
  type: "module",
1631
1680
  bin: {
@@ -1637,13 +1686,7 @@ var package_default = {
1637
1686
  build: "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
1638
1687
  start: "node dist/cli.js",
1639
1688
  test: "bun test",
1640
- "check:skills": "bun run scripts/check-skill-sync.ts",
1641
- "check:skills:fix": "bun run scripts/check-skill-sync.ts --fix",
1642
- "hash:check": "bun run scripts/update-manifest-hashes.ts --check",
1643
- "hash:update": "bun run scripts/update-manifest-hashes.ts",
1644
- "manifest:prepare": "bun run scripts/update-manifest-hashes.ts",
1645
- "manifest:check": "bun run scripts/update-manifest-hashes.ts --check",
1646
- prepublishOnly: "bun run scripts/sync-controller-core.ts && bun run manifest:prepare && bun run build"
1689
+ update: "bun run scripts/check-skill-sync.ts && bun run scripts/sync-controller-core.ts && bun run scripts/update-manifest-hashes.ts"
1647
1690
  },
1648
1691
  files: [
1649
1692
  "dist/",
@@ -1659,7 +1702,7 @@ var package_default = {
1659
1702
  ],
1660
1703
  license: "MIT",
1661
1704
  dependencies: {
1662
- "@clack/prompts": "^0.9.1"
1705
+ "@clack/prompts": "^0.9.2"
1663
1706
  },
1664
1707
  devDependencies: {
1665
1708
  "@types/bun": "latest",
@@ -2804,7 +2847,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2804
2847
  params: {
2805
2848
  protocolVersion: "2025-03-26",
2806
2849
  capabilities: {},
2807
- clientInfo: { name: "ostacky-installer", version: "0.9.1" }
2850
+ clientInfo: { name: "ostacky-installer", version: "0.9.2" }
2808
2851
  }
2809
2852
  });
2810
2853
  });
@@ -3145,6 +3188,12 @@ function uninstallAll(paths) {
3145
3188
  } catch {}
3146
3189
  }
3147
3190
  }
3191
+ const packageDir = join6(paths.plugins, "ostacky-controller");
3192
+ if (existsSync5(packageDir)) {
3193
+ try {
3194
+ rmSync3(packageDir, { recursive: true, force: true });
3195
+ } catch {}
3196
+ }
3148
3197
  clearLockfile(paths.root);
3149
3198
  }
3150
3199
 
@@ -3377,13 +3426,17 @@ async function doInstallAll(manifest, paths) {
3377
3426
  const { join } = await import("path");
3378
3427
  await Promise.resolve().then(() => init_github());
3379
3428
  const { findProjectRoot } = await Promise.resolve().then(() => (init_fs(), {}));
3380
- const src = join(PACKAGE_ROOT, "assets", "plugins", "ostacky-plugin.ts");
3381
- const dest = join(paths.plugins, "ostacky-plugin.ts");
3382
- if (existsSync(src)) {
3383
- mkdirSync(paths.plugins, { recursive: true });
3384
- copyFileSync(src, dest);
3429
+ const packageSrc = join(PACKAGE_ROOT, "assets", "plugins", "ostacky-controller");
3430
+ const packageDest = join(paths.plugins, "ostacky-controller");
3431
+ if (existsSync(join(packageSrc, "index.ts"))) {
3432
+ mkdirSync(packageDest, { recursive: true });
3433
+ for (const f of ["index.ts", "controller-core.ts", "security.ts", "tiered.ts"]) {
3434
+ const s = join(packageSrc, f);
3435
+ if (existsSync(s))
3436
+ copyFileSync(s, join(packageDest, f));
3437
+ }
3385
3438
  }
3386
- for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
3439
+ for (const legacy of ["ostacky-plugin.ts", "controller-core.ts", "security.ts", "tiered.ts", "ostacky-guard.ts", "ostacky-controller.ts"]) {
3387
3440
  const lp = join(paths.plugins, legacy);
3388
3441
  if (existsSync(lp))
3389
3442
  try {
@@ -4412,7 +4465,7 @@ async function runUninstallMcpCommand(name, scope) {
4412
4465
 
4413
4466
  // src/cli.ts
4414
4467
  init_fs();
4415
- import { existsSync as existsSync9, statSync as statSync3, readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
4468
+ import { existsSync as existsSync9, statSync as statSync3, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
4416
4469
  import { join as join11, dirname as dirname8 } from "node:path";
4417
4470
  var HELP = `
4418
4471
  ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
@@ -4500,6 +4553,9 @@ async function runDoctorCommand() {
4500
4553
  }
4501
4554
  };
4502
4555
  const pluginPaths = [
4556
+ join11(cwd, "assets", "plugins", "ostacky-controller", "index.ts"),
4557
+ join11(opencodeDir, "plugins", "ostacky-controller", "index.ts"),
4558
+ join11(cwd, ".opencode", "plugins", "ostacky-controller", "index.ts"),
4503
4559
  join11(cwd, "assets", "plugins", "ostacky-plugin.ts"),
4504
4560
  join11(opencodeDir, "plugins", "ostacky-plugin.ts"),
4505
4561
  join11(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
@@ -4643,7 +4699,7 @@ async function runDoctorCommand() {
4643
4699
  if (!existsSync9(cacheDir)) {
4644
4700
  console.log("ℹ️ cache: no cache dir yet (ok)");
4645
4701
  } else {
4646
- const files = readdirSync3(cacheDir);
4702
+ const files = readdirSync4(cacheDir);
4647
4703
  let total = 0;
4648
4704
  for (const f of files) {
4649
4705
  try {
@@ -4707,7 +4763,7 @@ async function runDoctorCommand() {
4707
4763
  check("honesty: 7 SHALL", hasHonesty);
4708
4764
  if (!hasHonesty)
4709
4765
  console.log(" Expected: ## Principios de honestidad (SHALL) in ostacky.md");
4710
- const pluginPath = join11(cwd, "assets", "plugins", "ostacky-plugin.ts");
4766
+ const pluginPath = join11(cwd, "assets", "plugins", "ostacky-controller", "index.ts");
4711
4767
  if (existsSync9(pluginPath)) {
4712
4768
  const plugin = readFileSync7(pluginPath, "utf-8");
4713
4769
  const hasSpecGuard = plugin.includes("getDiscoverySnapshot") && plugin.includes("specSnapshot");
package/manifest.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
- "version": "0.9.1",
2
+ "version": "0.9.2",
3
3
  "repo": "JaimeHoracio/Ostacky",
4
- "tag": "v0.9.1",
4
+ "tag": "v0.9.2",
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.9.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
- "version": "0.9.1",
11
- "sha256": "8186994cc9d92585310d8f4c67255e5169f1f3b3d7fc5b3563c2fa79f1203d24"
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.9.2: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
+ "version": "0.9.2",
11
+ "sha256": "f588cd07f67f73393f23601b025d1f5980a7db4efe194f248ff4d09cc98b04d4"
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.9.1: controller plugin como alma hard-gate, Context7 removido del stack.",
19
- "version": "0.9.1",
20
- "sha256": "af62710619308920f7be8743ed43c726d3591ba2af6535c3cc3ca6db95ad9715"
18
+ "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.9.2: controller plugin como alma hard-gate, Context7 removido del stack.",
19
+ "version": "0.9.2",
20
+ "sha256": "288e2fdd0df24378b92c15a169b846bd9cbc3290dfaf09597293e6f5240f562e"
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.9.1",
26
+ "version": "0.9.2",
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.9.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.9.1",
36
- "sha256": "d5d4712e5d823186c267f63cea776feddec4700f1b15286796586efb60c65241"
34
+ "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.9.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.",
35
+ "version": "0.9.2",
36
+ "sha256": "1fa9251efbf98552a8eb5f88a2f741021b82b21bc37f96d5253849f9209c949a"
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.9.1",
42
+ "version": "0.9.2",
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.9.1",
51
+ "version": "0.9.2",
52
52
  "sha256": "f1013a86a8f27914d85a716e23c7fd32db1786d93615e8b0834b13d184308549"
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.9.1",
58
+ "version": "0.9.2",
59
59
  "sha256": "2158a6864c0912e868a9d720db7fd79441e6cc298e3320833d2527c9727c852e"
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.9.1",
65
+ "version": "0.9.2",
66
66
  "sha256": "f0ebfbbc43a8868d31a3fd2df37ae7f60fe8add09c6475862395a68269e5ff7f"
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.9.1",
72
+ "version": "0.9.2",
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.9.1",
79
+ "version": "0.9.2",
80
80
  "sha256": "7e7b712ef742d5631e302e6a71583e2bbbd33094da9281abfb84b43505fc3ef1"
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.9.1",
86
+ "version": "0.9.2",
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.9.1",
93
+ "version": "0.9.2",
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.9.1",
100
+ "version": "0.9.2",
101
101
  "sha256": "6a517f750f24c008208156a2d653e97ed30b1e53878aeac5ce394e573db0678f"
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.9.1",
107
+ "version": "0.9.2",
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.9.1",
114
+ "version": "0.9.2",
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.9.1",
121
+ "version": "0.9.2",
122
122
  "sha256": "9eafc3a8c693b8d9916867ef764d49e5222da717fde501c957bb3f6c312d2415"
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.9.1",
128
+ "version": "0.9.2",
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.9.1",
135
+ "version": "0.9.2",
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.9.1",
142
+ "version": "0.9.2",
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.9.1",
149
+ "version": "0.9.2",
150
150
  "sha256": "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
151
151
  }
152
152
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Instalador interactivo de agentes y comandos para OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,13 +12,7 @@
12
12
  "build": "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
13
13
  "start": "node dist/cli.js",
14
14
  "test": "bun test",
15
- "check:skills": "bun run scripts/check-skill-sync.ts",
16
- "check:skills:fix": "bun run scripts/check-skill-sync.ts --fix",
17
- "hash:check": "bun run scripts/update-manifest-hashes.ts --check",
18
- "hash:update": "bun run scripts/update-manifest-hashes.ts",
19
- "manifest:prepare": "bun run scripts/update-manifest-hashes.ts",
20
- "manifest:check": "bun run scripts/update-manifest-hashes.ts --check",
21
- "prepublishOnly": "bun run scripts/sync-controller-core.ts && bun run manifest:prepare && bun run build"
15
+ "update": "bun run scripts/check-skill-sync.ts && bun run scripts/sync-controller-core.ts && bun run scripts/update-manifest-hashes.ts"
22
16
  },
23
17
  "files": [
24
18
  "dist/",
@@ -34,7 +28,7 @@
34
28
  ],
35
29
  "license": "MIT",
36
30
  "dependencies": {
37
- "@clack/prompts": "^0.9.1"
31
+ "@clack/prompts": "^0.9.2"
38
32
  },
39
33
  "devDependencies": {
40
34
  "@types/bun": "latest",