ostacky 0.9.3 → 0.9.5
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 +27 -25
- package/assets/agents/ostacky.md +5 -4
- package/assets/commands/install-stack.md +2 -2
- package/assets/mcp/ostacky-controller/bun.lock +20 -0
- package/assets/mcp/ostacky-controller/index.js +185 -29
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-B2Q9_Vxp.d.cts → auth--n_Yzytt.d.cts} +14 -9
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-CVhIKulL.d.mts.map → auth--n_Yzytt.d.cts.map} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-CfDMXiII.cjs → auth-4-zilcqg.cjs} +3 -2
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/auth-4-zilcqg.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-CUe6YdwF.mjs → auth-CGP0BDVq.mjs} +3 -2
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/auth-CGP0BDVq.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-CVhIKulL.d.mts → auth-YUQV3RRv.d.mts} +14 -9
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/{auth-B2Q9_Vxp.d.cts.map → auth-YUQV3RRv.d.mts.map} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/index.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/index.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/index.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/index.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/internal.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/internal.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/internal.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/internal.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/package.json +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/README.md +3 -3
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-CplOkyLC.mjs → ajvProvider-CEoC__sr.mjs} +483 -38
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CEoC__sr.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-BdSo9BII.d.mts → ajvProvider-CgI-5L6O.d.mts} +25 -8
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-BdSo9BII.d.mts.map → ajvProvider-CgI-5L6O.d.mts.map} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-B_4czhCS.d.cts → ajvProvider-FOQRM7up.d.cts} +24 -7
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-B_4czhCS.d.cts.map → ajvProvider-FOQRM7up.d.cts.map} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{ajvProvider-Brn-Q2J_.cjs → ajvProvider-ZaoO9afR.cjs} +483 -38
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/ajvProvider-ZaoO9afR.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{cfWorkerProvider-ojcZaap3.cjs → cfWorkerProvider-BwbPzsdI.cjs} +25 -15
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-BwbPzsdI.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{cfWorkerProvider-BYRW5af5.d.mts → cfWorkerProvider-C03CSigr.d.mts} +21 -6
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-C03CSigr.d.mts.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{cfWorkerProvider-D9GAttn8.d.cts → cfWorkerProvider-doTkZbTr.d.cts} +20 -5
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-doTkZbTr.d.cts.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{cfWorkerProvider-Bd-iTHAF.mjs → cfWorkerProvider-p3WaZPqB.mjs} +26 -15
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-p3WaZPqB.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{createMcpHandler-DzBzpE4_.d.cts → createMcpHandler-BLVe3qmU.d.cts} +137 -32
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-BLVe3qmU.d.cts.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{createMcpHandler-CEckaSEs.d.mts → createMcpHandler-Bt6U_Fqb.d.mts} +138 -33
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-Bt6U_Fqb.d.mts.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/dialects-D8eXzoGv.cjs +46 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/dialects-D8eXzoGv.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs +34 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.cjs +209 -257
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.cjs.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.d.cts +45 -3
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.d.cts.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.d.mts +46 -4
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.d.mts.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.mjs +201 -252
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/index.mjs.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{mcp-DM1iHYFo.cjs → mcp-DUcCQSTW.cjs} +458 -35
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/mcp-DUcCQSTW.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{mcp-IJurDZVN.mjs → mcp-Dw2OlZ1f.mjs} +389 -32
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/mcp-Dw2OlZ1f.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsBrowser.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsBrowser.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsBrowser.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsBrowser.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsNode.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsNode.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsNode.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.cjs +2 -2
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.mjs +2 -2
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{src-BX_fm-dB.cjs → src-CgFsfg3E.cjs} +161 -48
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/src-CgFsfg3E.cjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{src-D86MbS1I.mjs → src-D-y6h4N7.mjs} +161 -48
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs.map +1 -0
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.cjs +30 -3
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.cjs.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.d.cts +9 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.d.cts.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.d.mts +9 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.d.mts.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.mjs +30 -3
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/stdio.mjs.map +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{types-mS0yxCL0.d.mts → types-DUs7mGBv.d.mts} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/{types-mS0yxCL0.d.mts.map → types-DUs7mGBv.d.mts.map} +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/ajv.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/ajv.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/ajv.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/ajv.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/cfWorker.cjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/cfWorker.d.cts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/cfWorker.d.mts +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/validators/cfWorker.mjs +1 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/package.json +4 -4
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/plugins/ostacky-controller/index.ts +53 -11
- package/dist/cli.js +93 -53
- package/manifest.json +28 -28
- package/package.json +5 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/auth-CUe6YdwF.mjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/core/dist/auth-CfDMXiII.cjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/ajvProvider-Brn-Q2J_.cjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CplOkyLC.mjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-BYRW5af5.d.mts.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-Bd-iTHAF.mjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-D9GAttn8.d.cts.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/cfWorkerProvider-ojcZaap3.cjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-CEckaSEs.d.mts.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-DzBzpE4_.d.cts.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/mcp-DM1iHYFo.cjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/mcp-IJurDZVN.mjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/src-BX_fm-dB.cjs.map +0 -1
- package/assets/mcp/ostacky-controller/node_modules/@modelcontextprotocol/server/dist/src-D86MbS1I.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -23,24 +23,26 @@ opencode
|
|
|
23
23
|
|
|
24
24
|
Abre la interfaz de terminal interactiva. **Para elegir el agente Ostacky: usá `Tab` para navegar entre agentes** en el panel lateral, o escribí `/agent Ostacky` en el chat.
|
|
25
25
|
|
|
26
|
-
### Web UI — para usar desde el navegador
|
|
26
|
+
### Web UI — para usar desde el navegador (OpenCode v2)
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
opencode
|
|
29
|
+
opencode pair
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Abre
|
|
32
|
+
Abre el link de un solo uso (expira en 5 minutos) o escaneá el QR en el navegador para cargar la web UI con cookie de sesión (30 días). **Para elegir el agente Ostacky: abrí el combo/selector de agentes** en la barra superior y seleccioná "Ostacky". Útil para sesiones largas, trabajo en equipo o cuando querés compartir pantalla sin compartir terminal.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
> Por defecto el server corre en `http://127.0.0.1:49374` solo en localhost. Cambialo con `opencode service` (ver tabla). Docs: https://opencode.ai/v2/docs/cli/web
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
| `opencode web
|
|
41
|
-
| `opencode
|
|
42
|
-
| `
|
|
43
|
-
| `opencode
|
|
36
|
+
Opciones útiles (v2):
|
|
37
|
+
|
|
38
|
+
| Comando | Qué hace |
|
|
39
|
+
| -------------------------------------------------- | --------------------------------------------------------------- |
|
|
40
|
+
| `opencode pair` | Muestra URL + QR para conectar web/app (links de un uso, 5 min) |
|
|
41
|
+
| `opencode serve --hostname 0.0.0.0 --port 4096` | Server dedicado en foreground (host remoto, Docker/systemd) |
|
|
42
|
+
| `opencode --server http://127.0.0.1:4096` | Conectar un cliente TUI a un server activo |
|
|
43
|
+
| `opencode service set hostname 0.0.0.0` | Escuchar en red local (luego `opencode service start`) |
|
|
44
|
+
| `opencode service set port 49374` | Puerto fijo en vez del default del channel |
|
|
45
|
+
| `opencode service set password "un-secreto-largo"` | Reemplaza password generado (rota todas las sesiones) |
|
|
44
46
|
|
|
45
47
|
> 💡 Si no tenés OpenCode instalado aún: `curl -fsSL https://opencode.ai/install | bash`
|
|
46
48
|
|
|
@@ -248,37 +250,37 @@ Tras instalar (`--scope local`), el proyecto queda así:
|
|
|
248
250
|
|
|
249
251
|
```json
|
|
250
252
|
{
|
|
251
|
-
"version": "0.9.
|
|
253
|
+
"version": "0.9.5",
|
|
252
254
|
"lockedAt": "2025-01-01T00:00:00.000Z",
|
|
253
255
|
"repo": "JaimeHoracio/Ostacky",
|
|
254
|
-
"tag": "v0.9.
|
|
256
|
+
"tag": "v0.9.5",
|
|
255
257
|
"agents": {
|
|
256
258
|
"ostacky": {
|
|
257
|
-
"version": "0.9.
|
|
259
|
+
"version": "0.9.5",
|
|
258
260
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
259
261
|
"sha256": "abc123..."
|
|
260
262
|
}
|
|
261
263
|
},
|
|
262
264
|
"commands": {
|
|
263
265
|
"install-stack": {
|
|
264
|
-
"version": "0.9.
|
|
266
|
+
"version": "0.9.5",
|
|
265
267
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
266
268
|
"sha256": "def456..."
|
|
267
269
|
},
|
|
268
270
|
"opsx-sync": {
|
|
269
|
-
"version": "0.9.
|
|
271
|
+
"version": "0.9.5",
|
|
270
272
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
271
273
|
"sha256": "ghi789..."
|
|
272
274
|
}
|
|
273
275
|
},
|
|
274
276
|
"skills": {
|
|
275
|
-
"brainstorming": { "version": "0.9.
|
|
276
|
-
"execution-mode-evaluation": { "version": "0.9.
|
|
277
|
-
"openspec-propose": { "version": "0.9.
|
|
277
|
+
"brainstorming": { "version": "0.9.5", ... },
|
|
278
|
+
"execution-mode-evaluation": { "version": "0.9.5", ... },
|
|
279
|
+
"openspec-propose": { "version": "0.9.5", ... }
|
|
278
280
|
},
|
|
279
281
|
"mcpServers": {
|
|
280
|
-
"ostacky-controller": { "version": "0.9.
|
|
281
|
-
"openspec": { "version": "0.9.
|
|
282
|
+
"ostacky-controller": { "version": "0.9.5", ... },
|
|
283
|
+
"openspec": { "version": "0.9.5", ... }
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
```
|
|
@@ -293,7 +295,7 @@ Al terminar la instalación, el flujo normal es:
|
|
|
293
295
|
|
|
294
296
|
1. **Iniciar OpenCode** (si no está corriendo):
|
|
295
297
|
- **TUI:** `opencode` en tu terminal → seleccioná el agente con `/agents` (o atajos **Shift+Tab** / **Ctrl+X A**)
|
|
296
|
-
- **Web:** `opencode
|
|
298
|
+
- **Web:** `opencode pair` y abrí el link impreso (ej. `http://127.0.0.1:49374/auth/...`) o escaneá el QR → abrí el selector de agentes de la interfaz y elegí `ostacky`
|
|
297
299
|
2. OpenCode detecta automáticamente los archivos nuevos en `.opencode/` al iniciar, no necesita recarga manual.
|
|
298
300
|
3. Para usar el agente, seleccionalo con `/agents` y escribí tu mensaje en el chat. (En OpenCode v2, `@` es solo para referenciar archivos, no agentes.)
|
|
299
301
|
|
|
@@ -310,7 +312,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
|
|
|
310
312
|
## Seguridad
|
|
311
313
|
|
|
312
314
|
- `opencode.jsonc` se versiona en el repo para compartir permisos y MCP de forma reproducible.
|
|
313
|
-
- Las URLs de descarga usan **tags de GitHub** (ej. `v0.9.
|
|
315
|
+
- Las URLs de descarga usan **tags de GitHub** (ej. `v0.9.5`), nunca `main` — instalaciones reproducibles
|
|
314
316
|
- Cada path de archivo descargado es validado para prevenir **path traversal**
|
|
315
317
|
- Los archivos incluyen **checksum SHA-256** opcional; si el manifest lo define, el contenido se verifica antes de escribir
|
|
316
318
|
- El cache local (`.opencode/cache/`) también valida integridad al servir archivos cacheados
|
|
@@ -367,7 +369,7 @@ CodeGraph está instalado en `.opencode/tools/codegraph/bin/codegraph` y se conf
|
|
|
367
369
|
| `.opencode/tools/codegraph/bin/codegraph status` | Muestra estado del index y archivos pendientes |
|
|
368
370
|
| `.opencode/tools/codegraph/bin/codegraph install --target opencode --location local --yes` | Configura CodeGraph para OpenCode y genera AGENTS.md |
|
|
369
371
|
|
|
370
|
-
> **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.
|
|
372
|
+
> **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.5 el instalador usa el patrón resiliente de Engram (strip 0 + búsqueda recursiva) y loguea `projectRoot|toolsDir` para diagnosticar scope.
|
|
371
373
|
|
|
372
374
|
## Licencia
|
|
373
375
|
|
package/assets/agents/ostacky.md
CHANGED
|
@@ -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.
|
|
4
|
+
version: 0.9.5
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Sos **Ostacky v0.9.
|
|
7
|
+
Sos **Ostacky v0.9.5**, 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.
|
|
9
|
+
> **Versión:** `0.9.5` (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.5"** (o `v0.9.5` si te piden solo el número). No inventes otra versión.
|
|
10
10
|
|
|
11
11
|
## Reglas innegociables
|
|
12
12
|
|
|
@@ -23,6 +23,7 @@ Sos **Ostacky v0.9.3**, orquestás, no implementás. Interpretás, clasificás (
|
|
|
23
23
|
> **Regla de oro:** Nunca escribas instrucciones genéricas tipo `“sé cuidadoso / no introduzcas errores / no inventes”`. Son no-verificables y el LLM las ignora (instruction fatigue). Cada SHALL abajo es **verificable** (comando, evidencia o artefacto). `tasks.md` también SHALL ser verificable — no behavioral.
|
|
24
24
|
|
|
25
25
|
1. **Engram contradiction check SHALL.** Antes de proponer o discutir cambios (Nivel 0+1/1+, **eximido si `isTrivial && DONE`** — ej: "cambiar título" no paga `mem_search`), SHALL `mem_search` por keywords del pedido (max 5 keywords = sustantivos del pedido + `topic_key` del change activo si lo hay); si hit `type:decision|architecture` con contradicción semántica (ej: pide Zustand y hay #571 “usar Redux por X”), SHALL mostrar diff `“Antes decidimos X el <fecha> por Z (topic_key), ahora pedís ¬X”`, llamar `request_clarification` y esperar. Si Engram `degraded`/timeout → SHALL advertir “sin memoria” y continuar (no block). Si usuario confirma override → SHALL `mem_save` mismo `topic_key` + `mem_compare supersedes` + `record_user_confirmation`.
|
|
26
|
+
1b. **Code-contradiction check SHALL.** Antes de ejecutar (Nivel 0+1/1+, **eximido si `isTrivial && DONE`**), si `codegraph_impact`/evidencia del código contradice el pedido (API pública con N callers afectada, invariante verificado roto): SHALL mostrar evidencia con cita (`símbolo, archivo:línea`), advertir el riesgo concreto, hacer UNA pregunta y esperar. Si el usuario insiste → SHALL `record_user_confirmation` + ejecutar. Nunca termina en bloqueo: el protocolo siempre cierra en acatamiento si insiste.
|
|
26
27
|
2. **Solo propuestas que ayudan SHALL.** Toda propuesta SHALL incluir `por qué ayuda + tradeoff + evidencia` (CodeGraph symbol, Engram hit) o literal `“no verificado”` + best practice 2026. No sugerir por sugerir.
|
|
27
28
|
3. **Pocas honestas > muchas de relleno SHALL.** Si solo hay una opción honesta, dar una. Si hay 2-3, tabla `coste|riesgo|complejidad`. No inventar para llenar.
|
|
28
29
|
4. **Si no hay propuesta honesta SHALL decirlo.** Literal: `“No hay propuesta honesta que aporte vs no hacer nada en este contexto.”`
|
|
@@ -84,7 +85,7 @@ Router `brainstorming`↔`OpenSpec` por `level`/`estLines`/`fileCount`/`hasAPI`
|
|
|
84
85
|
### 4. Execution
|
|
85
86
|
|
|
86
87
|
1. `skill(execution-mode-evaluation)` en memoria, reusa discovery.
|
|
87
|
-
2.
|
|
88
|
+
2. Gate ÚNICO (sin segunda pregunta): presentar resumen único `Nivel + DIRECT/SPEC + INLINE/SUBAGENT + archivos + ~líneas` (+ comparativa tokens|tiempo si `1+` multi-archivo) → UNA sola pregunta `¿Procedo?` (el usuario puede modificar). Si hay Riesgo concreto verificable aunque sea 0+1, el resumen incluye `Riesgo concreto: … → Mitigación: …`; sin riesgo verificable no se inventa ninguno. Todo override a advertencia del agente (de memoria o técnica) SHALL pasar por `record_user_confirmation`. Si confirma → `record_execution_analysis` + `consume_execution_decision` seguidos sin volver a preguntar. `Nivel 0` y mismo-archivo → recomendación fija `INLINE/DIRECT` sin alternativa. `1+` multi-archivo → tabla `INLINE (1x contexto, más lento) vs SUBAGENT (Nx contexto, más rápido, reglas 2b/3c intactas)` + recomendación; nunca forzar `INLINE`.
|
|
88
89
|
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
90
|
|
|
90
91
|
### 5. Sync y cierre
|
|
@@ -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.
|
|
8
|
+
**Nota:** A partir de v0.9.5, `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.
|
|
14
|
+
## Scope de instalación — local vs global (desde v0.9.5)
|
|
15
15
|
|
|
16
16
|
`npx ostacky install` soporta `--scope local|global|auto` (también `--scope=...`):
|
|
17
17
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 2,
|
|
3
|
+
"configVersion": 0,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "ostacky-controller",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
9
|
+
"zod": "^4.0.0",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.1.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-YdFj5gMRHr0gYNAhTbQDgjLHiEJdIAUYxomhKseUNye7ZGlISQmUoLh2sveFFCiQ7j2YMffT1kgSND0FSfIFjQ=="],
|
|
15
|
+
|
|
16
|
+
"@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.1.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.1.0", "zod": "^4.2.0" } }, "sha512-FdvwZU5N03O77Suh35JqjAqq5R1ZTEPFtwu6N4BgBq28qrQtrCnpbLL5U2pwYCuE194jAw0xnlOVmIqhfoYuUQ=="],
|
|
17
|
+
|
|
18
|
+
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -42,6 +42,13 @@ import {
|
|
|
42
42
|
getMaxTasks,
|
|
43
43
|
} from './controller-core.js';
|
|
44
44
|
|
|
45
|
+
// --- approved-but-blocked recovery (sin timers) ---
|
|
46
|
+
// El PENDING que espera confirmación del usuario puede esperar indefinidamente (por diseño):
|
|
47
|
+
// acá NO hay timeouts ni watchdogs de idle (ver tests/controller-source.test.ts).
|
|
48
|
+
// Esto solo cuenta consumes fallidos cuando el usuario YA aprobó pero el consume no completa,
|
|
49
|
+
// y al umbral habilita recovery (refresh_decision) sin ejecutar nada por sí mismo.
|
|
50
|
+
const CONSUME_FAIL_THRESHOLD = 3;
|
|
51
|
+
|
|
45
52
|
// --- Audit JSONL (D5) — single source: src/audit-jsonl.ts ---
|
|
46
53
|
function getAuditPath(projectRoot) {
|
|
47
54
|
return join(projectRoot, '.opencode', 'ostacky-audit.jsonl');
|
|
@@ -1042,6 +1049,27 @@ class OstackyController {
|
|
|
1042
1049
|
return suggestions[state] || `Unexpected state: ${state}`;
|
|
1043
1050
|
}
|
|
1044
1051
|
|
|
1052
|
+
// --- approved-but-blocked: contador de consumes fallidos + hint de recovery ---
|
|
1053
|
+
async #noteConsumeFailure(via, reason) {
|
|
1054
|
+
const count = (this.#state.consumeFailedCount || 0) + 1;
|
|
1055
|
+
this.#state.consumeFailedCount = count;
|
|
1056
|
+
await this.#audit(
|
|
1057
|
+
'WARN',
|
|
1058
|
+
'consume_failed',
|
|
1059
|
+
`${via}: ${reason} (attempt ${count}/${CONSUME_FAIL_THRESHOLD})${count >= CONSUME_FAIL_THRESHOLD ? ' — recovery available: call refresh_decision for fresh IDs, then retry' : ''}`
|
|
1060
|
+
);
|
|
1061
|
+
// Durability: el fallo y su evidencia nunca deben perderse en un restart
|
|
1062
|
+
await this.#flushAudit(true);
|
|
1063
|
+
return count;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
#consumeRecoveryHint(count) {
|
|
1067
|
+
if (count >= CONSUME_FAIL_THRESHOLD) {
|
|
1068
|
+
return 'Recovery: call refresh_decision for fresh decision IDs, then retry the consume. The user approval still stands — only the IDs are repaired.';
|
|
1069
|
+
}
|
|
1070
|
+
return undefined;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1045
1073
|
// --- 3.3: Degraded mode ---
|
|
1046
1074
|
#enterDegradedMode(reason) {
|
|
1047
1075
|
this.#degraded = true;
|
|
@@ -1100,6 +1128,7 @@ class OstackyController {
|
|
|
1100
1128
|
fileFingerprints: {},
|
|
1101
1129
|
expectedTasks: null,
|
|
1102
1130
|
expectedTaskCount: null,
|
|
1131
|
+
consumeFailedCount: 0,
|
|
1103
1132
|
error: null,
|
|
1104
1133
|
});
|
|
1105
1134
|
await this.#audit(
|
|
@@ -1138,6 +1167,7 @@ class OstackyController {
|
|
|
1138
1167
|
);
|
|
1139
1168
|
await this.#transition(to, { error: null });
|
|
1140
1169
|
await this.#audit('DISCOVERY', 'record_clarification');
|
|
1170
|
+
await this.#flushAudit(true);
|
|
1141
1171
|
return { state: this.#state.state, revision: this.#state.revision };
|
|
1142
1172
|
}
|
|
1143
1173
|
|
|
@@ -1221,6 +1251,7 @@ class OstackyController {
|
|
|
1221
1251
|
lastProposal,
|
|
1222
1252
|
});
|
|
1223
1253
|
await this.#audit('ROUTE_DECISION_PENDING', 'record_discovery', `level=${level}, default=${defaultChoice}`);
|
|
1254
|
+
await this.#flushAudit(true);
|
|
1224
1255
|
// 8.2: reasoning sin plan → WARN
|
|
1225
1256
|
if (!shownToUser && !isTrivial) {
|
|
1226
1257
|
const auditId = `aud-${Date.now()}-${this.#state.auditSeq}`;
|
|
@@ -1352,13 +1383,25 @@ class OstackyController {
|
|
|
1352
1383
|
'consume_route_decision'
|
|
1353
1384
|
);
|
|
1354
1385
|
}
|
|
1355
|
-
if (this.#state.routeDecisionId !== decisionId)
|
|
1356
|
-
|
|
1386
|
+
if (this.#state.routeDecisionId !== decisionId) {
|
|
1387
|
+
const fails = await this.#noteConsumeFailure('consume_route_decision', 'Decision ID mismatch');
|
|
1388
|
+
const err = this.#makeError('Decision ID mismatch', 'consume_route_decision');
|
|
1389
|
+
const hint = this.#consumeRecoveryHint(fails);
|
|
1390
|
+
return hint ? { ...err, recovery: hint } : err;
|
|
1391
|
+
}
|
|
1357
1392
|
const to = this.#isAllowedTransition(this.#state.state, 'consume_route_decision', choice);
|
|
1358
|
-
if (!to)
|
|
1359
|
-
|
|
1360
|
-
|
|
1393
|
+
if (!to) {
|
|
1394
|
+
const fails = await this.#noteConsumeFailure('consume_route_decision', `choice ${choice} not allowed`);
|
|
1395
|
+
const err = this.#makeError(
|
|
1396
|
+
`Route ${choice} not allowed from ${this.#state.state}`,
|
|
1397
|
+
'consume_route_decision'
|
|
1398
|
+
);
|
|
1399
|
+
const hint = this.#consumeRecoveryHint(fails);
|
|
1400
|
+
return hint ? { ...err, recovery: hint } : err;
|
|
1401
|
+
}
|
|
1402
|
+
await this.#transition(to, { routeChoice: choice, consumeFailedCount: 0 });
|
|
1361
1403
|
await this.#audit(to, 'consume_route_decision', `choice=${choice}`);
|
|
1404
|
+
await this.#flushAudit(true);
|
|
1362
1405
|
return { state: this.#state.state, revision: this.#state.revision, routeChoice: choice };
|
|
1363
1406
|
}
|
|
1364
1407
|
|
|
@@ -1562,6 +1605,7 @@ class OstackyController {
|
|
|
1562
1605
|
lastProposal: execLastProposal,
|
|
1563
1606
|
});
|
|
1564
1607
|
await this.#audit('EXECUTION_DECISION_PENDING', 'record_execution_analysis');
|
|
1608
|
+
await this.#flushAudit(true);
|
|
1565
1609
|
// fix-execution-analysis-validation: degraded/early-exit defaulted snapshot → WARN
|
|
1566
1610
|
if (_snapshotDefaulted) {
|
|
1567
1611
|
const auditId = `aud-${Date.now()}-${this.#state.auditSeq}`;
|
|
@@ -1678,16 +1722,54 @@ class OstackyController {
|
|
|
1678
1722
|
'consume_execution_decision'
|
|
1679
1723
|
);
|
|
1680
1724
|
}
|
|
1681
|
-
if (this.#state.executionDecisionId !== decisionId)
|
|
1682
|
-
|
|
1725
|
+
if (this.#state.executionDecisionId !== decisionId) {
|
|
1726
|
+
const fails = await this.#noteConsumeFailure('consume_execution_decision', 'Decision ID mismatch');
|
|
1727
|
+
const err = this.#makeError('Decision ID mismatch', 'consume_execution_decision');
|
|
1728
|
+
const hint = this.#consumeRecoveryHint(fails);
|
|
1729
|
+
return hint ? { ...err, recovery: hint } : err;
|
|
1730
|
+
}
|
|
1683
1731
|
const to = this.#isAllowedTransition(this.#state.state, 'consume_execution_decision', mode);
|
|
1684
|
-
if (!to)
|
|
1685
|
-
|
|
1686
|
-
|
|
1732
|
+
if (!to) {
|
|
1733
|
+
const fails = await this.#noteConsumeFailure('consume_execution_decision', `mode ${mode} not allowed`);
|
|
1734
|
+
const err = this.#makeError(
|
|
1735
|
+
`Mode ${mode} not allowed from ${this.#state.state}`,
|
|
1736
|
+
'consume_execution_decision'
|
|
1737
|
+
);
|
|
1738
|
+
const hint = this.#consumeRecoveryHint(fails);
|
|
1739
|
+
return hint ? { ...err, recovery: hint } : err;
|
|
1740
|
+
}
|
|
1741
|
+
await this.#transition(to, { executionMode: mode, consumeFailedCount: 0 });
|
|
1687
1742
|
await this.#audit(to, 'consume_execution_decision', `mode=${mode}`);
|
|
1743
|
+
await this.#flushAudit(true);
|
|
1688
1744
|
return { state: this.#state.state, revision: this.#state.revision, executionMode: mode };
|
|
1689
1745
|
}
|
|
1690
1746
|
|
|
1747
|
+
/**
|
|
1748
|
+
* Regenera decision IDs frescos dentro del PENDING actual.
|
|
1749
|
+
* Recovery para "usuario aprobó pero el consume no completa" (IDs viejos tras
|
|
1750
|
+
* restart/compactación). No ejecuta nada: solo repara el tubo, la aprobación sigue valiendo.
|
|
1751
|
+
*/
|
|
1752
|
+
async refreshDecision() {
|
|
1753
|
+
this.#load();
|
|
1754
|
+
const s = this.#state.state;
|
|
1755
|
+
const prevFails = this.#state.consumeFailedCount || 0;
|
|
1756
|
+
if (s === 'ROUTE_DECISION_PENDING') {
|
|
1757
|
+
const routeDecisionId = 'route-' + Date.now();
|
|
1758
|
+
await this.#transition(s, { routeDecisionId, consumeFailedCount: 0 });
|
|
1759
|
+
await this.#audit(s, 'refresh_decision', `fresh routeDecisionId after ${prevFails} failed consume(s)`);
|
|
1760
|
+
await this.#flushAudit(true);
|
|
1761
|
+
return { state: this.#state.state, revision: this.#state.revision, routeDecisionId };
|
|
1762
|
+
}
|
|
1763
|
+
if (s === 'EXECUTION_DECISION_PENDING') {
|
|
1764
|
+
const executionDecisionId = 'exec-' + Date.now();
|
|
1765
|
+
await this.#transition(s, { executionDecisionId, consumeFailedCount: 0 });
|
|
1766
|
+
await this.#audit(s, 'refresh_decision', `fresh executionDecisionId after ${prevFails} failed consume(s)`);
|
|
1767
|
+
await this.#flushAudit(true);
|
|
1768
|
+
return { state: this.#state.state, revision: this.#state.revision, executionDecisionId };
|
|
1769
|
+
}
|
|
1770
|
+
return this.#makeError(`No decision to refresh from state ${s}`, 'refresh_decision');
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1691
1773
|
async implementationComplete({ force } = {}) {
|
|
1692
1774
|
this.#load();
|
|
1693
1775
|
const to = this.#isAllowedTransition(this.#state.state, 'implementation_complete');
|
|
@@ -2300,6 +2382,47 @@ class OstackyController {
|
|
|
2300
2382
|
};
|
|
2301
2383
|
}
|
|
2302
2384
|
|
|
2385
|
+
// --- fix-new-file-gate: ligadura validate → complete por filePath (mapa + fallback legacy) ---
|
|
2386
|
+
_getValidatedFor(filePath) {
|
|
2387
|
+
const lv = this.#state.lastValidated;
|
|
2388
|
+
if (!lv || !filePath) return null;
|
|
2389
|
+
if (lv.byFile && typeof lv.byFile === 'object' && lv.byFile[filePath]) return lv.byFile[filePath];
|
|
2390
|
+
if (lv.filePath === filePath) return lv; // fallback slot legacy (single)
|
|
2391
|
+
return null;
|
|
2392
|
+
}
|
|
2393
|
+
async _setValidatedFor(filePath, hash, mode) {
|
|
2394
|
+
const prev = this.#state.lastValidated;
|
|
2395
|
+
let byFile = {};
|
|
2396
|
+
if (prev) {
|
|
2397
|
+
if (prev.byFile && typeof prev.byFile === 'object') byFile = { ...prev.byFile };
|
|
2398
|
+
else if (prev.filePath)
|
|
2399
|
+
byFile[prev.filePath] = { hash: prev.hash ?? null, ts: prev.ts ?? 0, mode: prev.mode ?? 'replace' };
|
|
2400
|
+
}
|
|
2401
|
+
if (filePath) byFile[filePath] = { hash: hash ?? null, ts: Date.now(), mode: mode || 'replace' };
|
|
2402
|
+
const keys = Object.keys(byFile);
|
|
2403
|
+
if (keys.length > 50) {
|
|
2404
|
+
keys.sort((a, b) => (byFile[a].ts || 0) - (byFile[b].ts || 0));
|
|
2405
|
+
for (const k of keys.slice(0, keys.length - 50)) delete byFile[k];
|
|
2406
|
+
}
|
|
2407
|
+
this.#state.lastValidated = {
|
|
2408
|
+
byFile,
|
|
2409
|
+
filePath: filePath || null,
|
|
2410
|
+
hash: hash ?? null,
|
|
2411
|
+
ts: Date.now(),
|
|
2412
|
+
mode: mode || 'replace',
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
async _clearValidatedFor(filePath) {
|
|
2416
|
+
const lv = this.#state.lastValidated;
|
|
2417
|
+
if (lv && lv.byFile && typeof lv.byFile === 'object') {
|
|
2418
|
+
if (filePath && lv.byFile[filePath]) delete lv.byFile[filePath];
|
|
2419
|
+
if (Object.keys(lv.byFile).length === 0) this.#state.lastValidated = null;
|
|
2420
|
+
else this.#state.lastValidated = { byFile: lv.byFile, filePath: null, hash: null, ts: Date.now() };
|
|
2421
|
+
} else {
|
|
2422
|
+
this.#state.lastValidated = null;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2303
2426
|
// --- O6: Validate edit with fast fingerprint + D6/D4 hard gate + 10.4 freshness ---
|
|
2304
2427
|
async validateEdit({ oldString, newString, content, taskId, filePath } = {}) {
|
|
2305
2428
|
this.#load();
|
|
@@ -2323,8 +2446,10 @@ class OstackyController {
|
|
|
2323
2446
|
await this.#persist();
|
|
2324
2447
|
} catch {}
|
|
2325
2448
|
}
|
|
2326
|
-
// 10.4: validación de frescura — content debe coincidir con disco si filePath dado
|
|
2327
|
-
|
|
2449
|
+
// 10.4: validación de frescura — content debe coincidir con disco si filePath dado.
|
|
2450
|
+
// fix-new-file-gate: con oldString "" (intento create) no hay nada que comparar; la rama
|
|
2451
|
+
// create más abajo produce el error preciso (file exists vs EDITABLE).
|
|
2452
|
+
if (filePath && typeof content === 'string' && oldString !== '') {
|
|
2328
2453
|
try {
|
|
2329
2454
|
const projectRoot = getProjectRoot(this.#statePath);
|
|
2330
2455
|
const absolutePath =
|
|
@@ -2353,12 +2478,8 @@ class OstackyController {
|
|
|
2353
2478
|
? resolve(filePath)
|
|
2354
2479
|
: resolve(projectRoot, filePath);
|
|
2355
2480
|
const currentHash = fastFingerprint(absolutePath);
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
hashArg === currentHash &&
|
|
2359
|
-
this.#state.lastValidated?.filePath === filePath &&
|
|
2360
|
-
this.#state.lastValidated?.hash === currentHash
|
|
2361
|
-
) {
|
|
2481
|
+
const _prevValidated = this._getValidatedFor(filePath);
|
|
2482
|
+
if (currentHash && hashArg === currentHash && _prevValidated && _prevValidated.hash === currentHash) {
|
|
2362
2483
|
try {
|
|
2363
2484
|
content = readFileSync(absolutePath, 'utf8');
|
|
2364
2485
|
} catch {
|
|
@@ -2374,11 +2495,8 @@ class OstackyController {
|
|
|
2374
2495
|
}
|
|
2375
2496
|
}
|
|
2376
2497
|
// 5.3: optimization — si fastFingerprint no cambió, no re-enviar content completo
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
filePath &&
|
|
2380
|
-
this.#state.lastValidated?.filePath === filePath
|
|
2381
|
-
) {
|
|
2498
|
+
const _optValidated = filePath ? this._getValidatedFor(filePath) : null;
|
|
2499
|
+
if ((typeof content !== 'string' || content.length === 0) && filePath && _optValidated) {
|
|
2382
2500
|
try {
|
|
2383
2501
|
const projectRoot = getProjectRoot(this.#statePath);
|
|
2384
2502
|
const absolutePath =
|
|
@@ -2386,7 +2504,7 @@ class OstackyController {
|
|
|
2386
2504
|
? resolve(filePath)
|
|
2387
2505
|
: resolve(projectRoot, filePath);
|
|
2388
2506
|
const currentHash = fastFingerprint(absolutePath);
|
|
2389
|
-
if (currentHash && currentHash ===
|
|
2507
|
+
if (currentHash && currentHash === _optValidated.hash) {
|
|
2390
2508
|
try {
|
|
2391
2509
|
const diskContent = readFileSync(absolutePath, 'utf8');
|
|
2392
2510
|
content = diskContent;
|
|
@@ -2394,6 +2512,29 @@ class OstackyController {
|
|
|
2394
2512
|
}
|
|
2395
2513
|
} catch {}
|
|
2396
2514
|
}
|
|
2515
|
+
// fix-new-file-gate: modo create — oldString "" + archivo inexistente en disco
|
|
2516
|
+
if (typeof oldString === 'string' && oldString === '' && typeof newString === 'string' && filePath) {
|
|
2517
|
+
if (newString.length === 0) {
|
|
2518
|
+
return { outcome: 'CONFLICT', reason: 'newString required for create' };
|
|
2519
|
+
}
|
|
2520
|
+
try {
|
|
2521
|
+
const projectRoot = getProjectRoot(this.#statePath);
|
|
2522
|
+
const absolutePath =
|
|
2523
|
+
filePath.startsWith('/') || /^[A-Za-z]:/.test(filePath)
|
|
2524
|
+
? resolve(filePath)
|
|
2525
|
+
: resolve(projectRoot, filePath);
|
|
2526
|
+
if (existsSync(absolutePath)) {
|
|
2527
|
+
return { outcome: 'CONFLICT', reason: 'file exists, use replace mode', filePath };
|
|
2528
|
+
}
|
|
2529
|
+
} catch {
|
|
2530
|
+
return { outcome: 'CONFLICT', reason: 'create validation failed', filePath };
|
|
2531
|
+
}
|
|
2532
|
+
await this._setValidatedFor(filePath, null, 'create');
|
|
2533
|
+
try {
|
|
2534
|
+
await this.#persist();
|
|
2535
|
+
} catch {}
|
|
2536
|
+
return { outcome: 'EDITABLE', taskId, mode: 'create' };
|
|
2537
|
+
}
|
|
2397
2538
|
if (typeof content !== 'string' || typeof oldString !== 'string' || typeof newString !== 'string') {
|
|
2398
2539
|
return { outcome: 'CONFLICT', reason: 'Missing required fields: content, oldString, newString' };
|
|
2399
2540
|
}
|
|
@@ -2438,7 +2579,7 @@ class OstackyController {
|
|
|
2438
2579
|
: resolve(projectRoot, filePath)
|
|
2439
2580
|
: null;
|
|
2440
2581
|
const hash = absolutePath ? fastFingerprint(absolutePath) : null;
|
|
2441
|
-
this
|
|
2582
|
+
await this._setValidatedFor(filePath || null, hash, 'replace');
|
|
2442
2583
|
await this.#persist();
|
|
2443
2584
|
} catch {}
|
|
2444
2585
|
return { outcome: 'EDITABLE', taskId };
|
|
@@ -2483,7 +2624,9 @@ class OstackyController {
|
|
|
2483
2624
|
!this.#state.fileFingerprints?.[filePath] &&
|
|
2484
2625
|
!Object.values(this.#state.tasks || {}).some((t) => t.filePath === filePath))
|
|
2485
2626
|
);
|
|
2486
|
-
|
|
2627
|
+
// fix-new-file-gate: ligadura por-archivo (create o replace); se consume solo ese path
|
|
2628
|
+
const _ligadura = filePath ? this._getValidatedFor(filePath) : this.#state.lastValidated;
|
|
2629
|
+
if (!_ligadura) {
|
|
2487
2630
|
if (!isNewFile) {
|
|
2488
2631
|
return {
|
|
2489
2632
|
error: 'validate required',
|
|
@@ -2498,7 +2641,7 @@ class OstackyController {
|
|
|
2498
2641
|
`complete_task without prior validate_edit for ${filePath || taskId}${isNewFile ? ' (new file, WARN not BLOCK)' : ''}`
|
|
2499
2642
|
);
|
|
2500
2643
|
} else {
|
|
2501
|
-
this
|
|
2644
|
+
await this._clearValidatedFor(filePath);
|
|
2502
2645
|
}
|
|
2503
2646
|
|
|
2504
2647
|
// --- Generico: verificar task antes de marcar completa (evita invento)
|
|
@@ -2853,7 +2996,7 @@ function safeHandler(fn, options = {}) {
|
|
|
2853
2996
|
|
|
2854
2997
|
const server = new McpServer({
|
|
2855
2998
|
name: 'ostacky-controller',
|
|
2856
|
-
version: '0.9.
|
|
2999
|
+
version: '0.9.5',
|
|
2857
3000
|
});
|
|
2858
3001
|
|
|
2859
3002
|
server.registerTool(
|
|
@@ -2971,6 +3114,19 @@ server.registerTool(
|
|
|
2971
3114
|
})
|
|
2972
3115
|
);
|
|
2973
3116
|
|
|
3117
|
+
server.registerTool(
|
|
3118
|
+
'refresh_decision',
|
|
3119
|
+
{
|
|
3120
|
+
description:
|
|
3121
|
+
'Regenerate fresh decision IDs within the current PENDING state (recovery when the user approved but consume keeps failing with stale IDs; executes nothing)',
|
|
3122
|
+
inputSchema: z.object({}),
|
|
3123
|
+
},
|
|
3124
|
+
safeHandler(async () => {
|
|
3125
|
+
log('tool:refresh_decision');
|
|
3126
|
+
return await controller.refreshDecision();
|
|
3127
|
+
})
|
|
3128
|
+
);
|
|
3129
|
+
|
|
2974
3130
|
server.registerTool(
|
|
2975
3131
|
'implementation_complete',
|
|
2976
3132
|
{
|
|
@@ -3442,7 +3598,7 @@ function setupGracefulShutdown(ctrl) {
|
|
|
3442
3598
|
}
|
|
3443
3599
|
|
|
3444
3600
|
async function main() {
|
|
3445
|
-
log('Starting ostacky-controller MCP v0.9.
|
|
3601
|
+
log('Starting ostacky-controller MCP v0.9.5...');
|
|
3446
3602
|
log('State path:', { path: statePath });
|
|
3447
3603
|
// Clean up stale tmp/lock files from previous runs
|
|
3448
3604
|
cleanupTmpFiles(statePath);
|