ostacky 0.8.7 → 0.8.8

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.8.7",
249
+ "version": "0.8.8",
250
250
  "lockedAt": "2025-01-01T00:00:00.000Z",
251
251
  "repo": "JaimeHoracio/Ostacky",
252
- "tag": "v0.8.7",
252
+ "tag": "v0.8.8",
253
253
  "agents": {
254
254
  "ostacky": {
255
- "version": "0.8.7",
255
+ "version": "0.8.8",
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.8.7",
262
+ "version": "0.8.8",
263
263
  "installedAt": "2025-01-01T00:00:00.000Z",
264
264
  "sha256": "def456..."
265
265
  },
266
266
  "opsx-sync": {
267
- "version": "0.8.7",
267
+ "version": "0.8.8",
268
268
  "installedAt": "2025-01-01T00:00:00.000Z",
269
269
  "sha256": "ghi789..."
270
270
  }
271
271
  },
272
272
  "skills": {
273
- "brainstorming": { "version": "0.8.7", ... },
274
- "execution-mode-evaluation": { "version": "0.8.7", ... },
275
- "openspec-propose": { "version": "0.8.7", ... }
273
+ "brainstorming": { "version": "0.8.8", ... },
274
+ "execution-mode-evaluation": { "version": "0.8.8", ... },
275
+ "openspec-propose": { "version": "0.8.8", ... }
276
276
  },
277
277
  "mcpServers": {
278
- "ostacky-controller": { "version": "0.8.7", ... },
279
- "openspec": { "version": "0.8.7", ... }
278
+ "ostacky-controller": { "version": "0.8.8", ... },
279
+ "openspec": { "version": "0.8.8", ... }
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.8.7`), nunca `main` — instalaciones reproducibles
311
+ - Las URLs de descarga usan **tags de GitHub** (ej. `v0.8.8`), 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
@@ -324,7 +324,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
324
324
  - Variable `OSTACKY_SENSITIVE_PATTERNS` overridea los patrones por defecto (`**/.env*`, `**/.secrets/**`, `**/*.pem`, `**/*.key`, `**/.aws/**`, `**/.ssh/**`, `**/credentials.json`, `**/.npmrc`).
325
325
  - Ejemplo: `OSTACKY_SENSITIVE_PATTERNS="**/.env*,**/.secrets/**" bunx ostacky doctor` — `doctor` imprime el patrón efectivo.
326
326
  - Allowlist: `.env.example`, `.env.template`, `.env.sample` nunca se bloquean.
327
- - Fuente única: `src/security.ts` (`SENSITIVE_DEFAULT`, `isSensitive`, `BASH_SENSITIVE_RE`) — `ostacky-plugin.ts` importa de ahí (legacy `ostacky-guard.ts` eliminado, fusionado en el plugin). Ver `doctor` para verificar.
327
+ - Fuente única: `src/security.ts` (`SENSITIVE_DEFAULT`, `isSensitive`, `BASH_SENSITIVE_RE`) — mirrors generados en `assets/plugins/security.ts` 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). Ver `doctor` para verificar.
328
328
 
329
329
  #### Aislamiento de worktrees (harness-prod-hardening)
330
330
 
@@ -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.8.7 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.8.8 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.8.7
4
+ version: 0.8.8
5
5
  ---
6
6
 
7
- Sos **Ostacky v0.8.7**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
7
+ Sos **Ostacky v0.8.8**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
8
8
 
9
- > **Versión:** `0.8.7` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.7"** (o `v0.8.7` si te piden solo el número). No inventes otra versión.
9
+ > **Versión:** `0.8.8` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.8"** (o `v0.8.8` si te piden solo el número). No inventes otra versión.
10
10
 
11
11
  ## Reglas innegociables
12
12
 
@@ -5,13 +5,13 @@ agent: build
5
5
 
6
6
  Instala el stack tecnológico de desarrollo para OpenCode. **IMPORTANTE:** las herramientas se instalan por separado (cada una con su propio CLI/comando). `npx ostacky install` solo instala el agente y commands de Ostacky en `.opencode/`. Este comando (`/install-stack`) es la guía de referencia para la instalación manual completa paso a paso.
7
7
 
8
- **Nota:** A partir de v0.8.7, `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.8.8, `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.8.7)
14
+ ## Scope de instalación — local vs global (desde v0.8.8)
15
15
 
16
16
  `npx ostacky install` soporta `--scope local|global|auto` (también `--scope=...`):
17
17
 
@@ -64,14 +64,22 @@ Principio: **eficacia > recorte** — si recorte rompiera caché y saliera más
64
64
 
65
65
  ## Security single-source
66
66
 
67
- `src/security.ts` único origen de `SENSITIVE_DEFAULT`/`BASH_SENSITIVE_RE`/`isSensitive`/`extractPathsFromBash`; plugin y guard importan, no copian. `BASH_SENSITIVE_RE` arriba. `OSTACKY_SENSITIVE_PATTERNS` override.
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.
68
68
 
69
69
  ## Tiered single-source
70
70
 
71
- `src/tiered.ts` único origen de `isTrivial(msg,state)` + `getControllerState(dir)`. Ambos plugins importan lógica idéntica, no duplican regex. Ver `assets/plugins/ostacky-plugin.ts` y `assets/plugins/engram.ts`.
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`.
72
+
73
+ ## Controller single-source
74
+
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.
72
76
 
73
77
  ## Prompt-efficiency
74
78
 
75
79
  - `ostacky.md` 109→72 líneas (diet estable cacheable). Tiered vía suffix, no reemplazo `system[0]`.
76
80
  - `controller` descriptions <150 chars
77
81
  - `MEMORY_INSTRUCTIONS` lazy: siempre pointer (~1 línea) en `system.transform`; full vive en `assets/docs/engram-protocol.md` on-demand via `Read` (ahorro ~1.2k en FULL, trivial ya era pointer)
82
+
83
+ ## Propose Flow (1+)
84
+
85
+ `openspec-propose` SHALL Paso 0: `getDiscoverySnapshot(query)` + `getEngramDedup` → si miss `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot` **antes** de `openspec new`. Cita símbolos existentes en `proposal/design`; sin esto dispara `WARN:codegraphBypass` y `design.md` alucina API. Ver `assets/skills/openspec-propose/SKILL.md: Paso 0` y `brainstorming` (router por nivel: `brainstorming` decide 0/0+1/1+, `openspec-explore` es stance sin nivel).
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * controller-core — Single source of truth para TRANSITIONS, STATES, DEFAULT_STATE y helpers.
3
- * Extraído de assets/mcp/ostacky-controller/index.js y assets/plugins/ostacky-plugin.ts
4
- * para eliminar duplicación (D1). Ambos importan de acá.
3
+ * Generado desde src/controller-core.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
5
4
  */
6
5
 
7
6
  import { SENSITIVE_DEFAULT } from "./security.js";
@@ -142,6 +141,7 @@ export const DEFAULT_STATE = {
142
141
  watchdogEnabled: true,
143
142
  ts: Date.now(),
144
143
  specNotInSync: false,
144
+ engramBypassCount: 0,
145
145
  };
146
146
 
147
147
  export function safeJsonStringify(obj, pretty = false) {
@@ -2576,7 +2576,7 @@ function safeHandler(fn, options = {}) {
2576
2576
 
2577
2577
  const server = new McpServer({
2578
2578
  name: 'ostacky-controller',
2579
- version: '0.8.7',
2579
+ version: '0.8.8',
2580
2580
  });
2581
2581
 
2582
2582
  server.registerTool(
@@ -3176,7 +3176,7 @@ function setupGracefulShutdown(ctrl) {
3176
3176
  }
3177
3177
 
3178
3178
  async function main() {
3179
- log('Starting ostacky-controller MCP v0.8.7...');
3179
+ log('Starting ostacky-controller MCP v0.8.8...');
3180
3180
  log('State path:', { path: statePath });
3181
3181
  // Clean up stale tmp/lock files from previous runs
3182
3182
  cleanupTmpFiles(statePath);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky-controller",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * security.js — source-of-truth mirror of src/security.ts for controller (Node)
3
- * Generado desde src/security.ts — mantener sincronizado via `bun run hash:check` y test controller-source
3
+ * Generado desde src/security.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
4
4
  */
5
5
 
6
+ // ─── Sensitive guard — source-of-truth (hardening-v2 D1) ───────────────
7
+
6
8
  export const SENSITIVE_DEFAULT = [
7
9
  "**/.env*",
8
10
  "**/.secrets/**",
@@ -14,9 +16,20 @@ export const SENSITIVE_DEFAULT = [
14
16
  "**/.npmrc",
15
17
  ];
16
18
 
19
+ /**
20
+ * Regex que detecta acceso sensible en comandos bash.
21
+ * Cubre .env, .secrets, .pem, .key, .aws, .ssh, credentials.json, .npmrc
22
+ * Normalizar antes (strip quotes/backslashes) y testear con `BASH_SENSITIVE_RE.test(normalized)`.
23
+ */
17
24
  export const BASH_SENSITIVE_RE =
18
25
  /(?:^|[^a-zA-Z0-9_.-])(\.env(\b|[_.-])|\.secrets\b|\.pem\b|\.key\b|credentials\.json|\.aws\b|\.ssh\b|\.npmrc\b)/i;
19
26
 
27
+ /**
28
+ * Returns true if filePath is considered sensitive given patterns.
29
+ * - Allowlist: .env.example / .env.template / .env.sample nunca son sensibles.
30
+ * - Normaliza backslashes a "/" para Windows.
31
+ * - patterns: array de globs como SENSITIVE_DEFAULT; si no se pasa usa SENSITIVE_DEFAULT.
32
+ */
20
33
  export function isSensitive(filePath, patterns = SENSITIVE_DEFAULT) {
21
34
  if (!filePath) return false;
22
35
  const normalized = filePath.replace(/\\/g, "/");
@@ -38,25 +51,37 @@ export function isSensitive(filePath, patterns = SENSITIVE_DEFAULT) {
38
51
  if (pat.includes("credentials.json") && lower.endsWith("credentials.json")) return true;
39
52
  if (pat.includes(".npmrc") && lower.endsWith(".npmrc")) return true;
40
53
  }
54
+ // fallback genérico por extensión
41
55
  if (/\.(pem|key)$/i.test(normalized)) return true;
42
56
  if (base.startsWith(".env")) return true;
43
57
  return false;
44
58
  }
45
59
 
60
+ /**
61
+ * Extrae paths candidatos de un comando bash para chequear sensibilidad.
62
+ * Tokeniza por separadores shell `| ; && || > >> <` y respeta quotes.
63
+ * Normaliza quitando quotes y backslashes para detectar obfuscaciones como .e""nv.
64
+ */
46
65
  export function extractPathsFromBash(cmd) {
47
66
  if (!cmd) return [];
67
+ // Normalizar && y || a ; para split único
48
68
  const normalized = cmd.replace(/&&/g, ";").replace(/\|\|/g, ";");
69
+ // Split por separadores shell
49
70
  const segments = normalized.split(/[|;><\n]+/);
50
71
  const paths = [];
51
72
  for (const seg of segments) {
52
73
  const trimmed = seg.trim();
53
74
  if (!trimmed) continue;
75
+ // Tokeniza respetando quotes — captura tokens entre quotes o sin espacios
54
76
  const tokens = trimmed.match(/(?:[^\s"'`\\]+|"[^"]*"|'[^']*'|`[^`]*`)+/g) || [];
55
77
  for (let token of tokens) {
78
+ // Normalización anti-obfuscación: quitar quotes y backslashes
56
79
  const stripped = token.replace(/["'`]/g, "").replace(/\\/g, "");
57
80
  if (!stripped) continue;
58
- if (["cat","grep","ls","echo","awk","sed","cut","head","tail","wc","find","xargs","bash","sh","zsh","env","printenv","node","bun","npm","npx","ls"].includes(stripped)) continue;
81
+ // Filtrar comandos comunes y flags
82
+ if (["cat", "grep", "ls", "echo", "awk", "sed", "cut", "head", "tail", "wc", "find", "xargs", "bash", "sh", "zsh", "env", "printenv", "node", "bun", "npm", "npx", "ls"].includes(stripped)) continue;
59
83
  if (stripped.startsWith("-")) continue;
84
+ // Heurística de path: contiene / o . o es .env* o sensible
60
85
  const lower = stripped.toLowerCase();
61
86
  if (
62
87
  stripped.includes("/") ||
@@ -70,6 +95,7 @@ export function extractPathsFromBash(cmd) {
70
95
  lower.endsWith("credentials.json") ||
71
96
  lower.endsWith(".npmrc")
72
97
  ) {
98
+ // Limpiar trailing chars como : , )
73
99
  const cleaned = stripped.replace(/[,:;)\]]+$/, "");
74
100
  if (cleaned) paths.push(cleaned);
75
101
  } else if (stripped === ".env") {
@@ -77,9 +103,14 @@ export function extractPathsFromBash(cmd) {
77
103
  }
78
104
  }
79
105
  }
106
+ // También buscar paths con regex global como fallback
107
+ // (detecta .env en strings complejos)
80
108
  return [...new Set(paths)];
81
109
  }
82
110
 
111
+ /**
112
+ * Lee patrones sensibles desde env OSTACKY_SENSITIVE_PATTERNS o usa default.
113
+ */
83
114
  export function getSensitivePatterns() {
84
115
  const raw = process.env.OSTACKY_SENSITIVE_PATTERNS;
85
116
  if (!raw) return SENSITIVE_DEFAULT;
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * controller-core — Single source of truth para TRANSITIONS, STATES, DEFAULT_STATE y helpers.
3
- * Extraído de assets/mcp/ostacky-controller/index.js y assets/plugins/ostacky-plugin.ts
4
- * para eliminar duplicación (D1). Ambos importan de acá.
3
+ * Generado desde src/controller-core.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
5
4
  */
6
5
 
7
6
  import { SENSITIVE_DEFAULT } from "./security.js";
@@ -142,6 +141,7 @@ export const DEFAULT_STATE: any = {
142
141
  watchdogEnabled: true,
143
142
  ts: Date.now(),
144
143
  specNotInSync: false,
144
+ engramBypassCount: 0,
145
145
  };
146
146
 
147
147
  export function safeJsonStringify(obj: any, pretty = false): string {
@@ -5,7 +5,7 @@
5
5
  * Mantiene máquina de 13 estados en-process y aplica hard gates en tool.execute.before.
6
6
  * MCP queda thin solo para observabilidad (get_*).
7
7
  *
8
- * Single source security: importa desde src/security.ts (no copia regex).
8
+ * Single source security: mirrors generados desde src/security.ts via scripts/sync-controller-core.ts
9
9
  * Cache único: getDiscoverySnapshot es único entrypoint.
10
10
  * Tiered cache-friendly: isTrivial sin reemplazar system[0], suffix hint + SKIP.
11
11
  * CodeGraph preventivo: bloquea Read/Grep masivo sin Discovery hit.
@@ -14,8 +14,8 @@
14
14
  import type { Plugin } from "@opencode-ai/plugin"
15
15
  import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync, statSync, readdirSync, unlinkSync } from "node:fs"
16
16
  import { join, dirname, basename, resolve, relative } from "node:path"
17
- import { SENSITIVE_DEFAULT, BASH_SENSITIVE_RE, isSensitive, extractPathsFromBash } from "../../src/security.ts"
18
- import { isTrivial } from "../../src/tiered.ts"
17
+ import { SENSITIVE_DEFAULT, BASH_SENSITIVE_RE, isSensitive, extractPathsFromBash } from "./security.ts"
18
+ import { isTrivial } from "./tiered.ts"
19
19
  import { STATES, TRANSITIONS, DEFAULT_STATE } from "./controller-core.ts"
20
20
 
21
21
  // ─── Constants ───────────────────────────────────────────────────────────────
@@ -139,6 +139,8 @@ function isCodegraphAvailable(directory: string): boolean {
139
139
  const trivialBySession = new Map<string, boolean>()
140
140
  // Track discovery hit per requestId to avoid blocking after hit
141
141
  const discoveryHitByRequest = new Map<string, boolean>()
142
+ // Track Engram hit per session to audit bypass (soft, no BLOCK)
143
+ const engramHitBySession = new Map<string, boolean>()
142
144
  // ─── Heartbeat ping — evita sensación de trancado en tareas largas ──
143
145
  let lastPingTs = 0
144
146
  let pingInterval: ReturnType<typeof setInterval> | null = null
@@ -279,6 +281,26 @@ export const OstackyController: Plugin = async (ctx) => {
279
281
  lastCheck = { revision: freshState.revision, result: "ALLOW" }
280
282
  }
281
283
 
284
+ // ── 1.6) Engram soft gate (auditable, no BLOCK) — refuerza SHALL 1 de ostacky.md ──
285
+ // Si propone/cambia nivel sin haber consultado Engram, cuenta bypass (exime trivial+DONE)
286
+ const isEngramRelevantTool = tool.includes("record_discovery") || (tool.includes("openspec") && (tool.includes("propose") || (typeof args?.filePath === "string" && args.filePath.includes("openspec/changes")) || (typeof args?.path === "string" && args.path.includes("openspec/changes"))))
287
+ if (isEngramRelevantTool) {
288
+ const hasEngramHit = engramHitBySession.get(sessionId) ?? false
289
+ const isTrivialForEngram = trivialBySession.get(sessionId) ?? false
290
+ const stateForEngram = freshState
291
+ const shouldAudit = !hasEngramHit && !isTrivialForEngram && stateForEngram && ["DISCOVERY", "ROUTE_DECISION_PENDING", "SPECIFICATION", "INTERPRETATION_PENDING"].includes(stateForEngram.state)
292
+ if (shouldAudit) {
293
+ try {
294
+ const st = readState(ctx.directory)
295
+ if (st) {
296
+ st.engramBypassCount = (st.engramBypassCount || 0) + 1
297
+ // no persist cada vez si es muy frecuente: single-writer ya persiste en after, pero acá persistimos directo para auditoría
298
+ persistState(ctx.directory, st)
299
+ }
300
+ } catch {}
301
+ }
302
+ }
303
+
282
304
  // ── 1.5) Router determinista: openspec-propose bloquea si 1+ no-downgradeable sin Alternatives ──
283
305
  const isOpenspecPropose = tool.includes("openspec") && (tool.includes("propose") || args?.filePath?.includes("openspec/changes") || args?.path?.includes("openspec/changes"))
284
306
  if (isOpenspecPropose) {
@@ -487,8 +509,16 @@ export const OstackyController: Plugin = async (ctx) => {
487
509
  if (text && !text.includes("null") && text.length > 10) {
488
510
  discoveryHitByRequest.set(sessionId, true)
489
511
  }
512
+ // Engram hit via discovery snapshot (contiene engramHits)
513
+ if (text && text.includes("engramHits")) {
514
+ engramHitBySession.set(sessionId, true)
515
+ }
490
516
  } catch {}
491
517
  }
518
+ // Track Engram hit (mem_search / mem_context / dedup) para soft gate 1.6
519
+ if (tool.includes("engram_mem_search") || tool.includes("engram_mem_context") || tool.includes("mem_search") || tool.includes("mem_context") || tool.includes("getEngramDedup") || tool.includes("engram_mem_")) {
520
+ engramHitBySession.set(sessionId, true)
521
+ }
492
522
  // Update state metrics for cache hit (best-effort)
493
523
  if (tool.includes("getDiscoverySnapshot") && output) {
494
524
  try {
@@ -549,6 +579,7 @@ export const OstackyController: Plugin = async (ctx) => {
549
579
  tokenSavingEstimate: state?.tokenSavingEstimate ?? 0,
550
580
  stateCheckCount: state?.stateCheckCount ?? 0,
551
581
  codegraphBypassCount: state?.codegraphBypassCount ?? 0,
582
+ engramBypassCount: state?.engramBypassCount ?? 0,
552
583
  revision: state?.revision ?? 0,
553
584
  }
554
585
  },
@@ -605,6 +636,7 @@ export const OstackyController: Plugin = async (ctx) => {
605
636
  if (sid) {
606
637
  trivialBySession.delete(sid)
607
638
  discoveryHitByRequest.delete(sid)
639
+ engramHitBySession.delete(sid)
608
640
  }
609
641
  }
610
642
  },
@@ -0,0 +1,118 @@
1
+ /**
2
+ * security.ts — mirror de src/security.ts para plugin (self-contained, solo Sensitive guard)
3
+ * Generado desde src/security.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
4
+ */
5
+
6
+ // ─── Sensitive guard — source-of-truth (hardening-v2 D1) ───────────────
7
+
8
+ export const SENSITIVE_DEFAULT: string[] = [
9
+ "**/.env*",
10
+ "**/.secrets/**",
11
+ "**/*.pem",
12
+ "**/*.key",
13
+ "**/.aws/**",
14
+ "**/.ssh/**",
15
+ "**/credentials.json",
16
+ "**/.npmrc",
17
+ ];
18
+
19
+ /**
20
+ * Regex que detecta acceso sensible en comandos bash.
21
+ * Cubre .env, .secrets, .pem, .key, .aws, .ssh, credentials.json, .npmrc
22
+ * Normalizar antes (strip quotes/backslashes) y testear con `BASH_SENSITIVE_RE.test(normalized)`.
23
+ */
24
+ export const BASH_SENSITIVE_RE =
25
+ /(?:^|[^a-zA-Z0-9_.-])(\.env(\b|[_.-])|\.secrets\b|\.pem\b|\.key\b|credentials\.json|\.aws\b|\.ssh\b|\.npmrc\b)/i;
26
+
27
+ /**
28
+ * Returns true if filePath is considered sensitive given patterns.
29
+ * - Allowlist: .env.example / .env.template / .env.sample nunca son sensibles.
30
+ * - Normaliza backslashes a "/" para Windows.
31
+ * - patterns: array de globs como SENSITIVE_DEFAULT; si no se pasa usa SENSITIVE_DEFAULT.
32
+ */
33
+ export function isSensitive(filePath: string, patterns: string[] = SENSITIVE_DEFAULT): boolean {
34
+ if (!filePath) return false;
35
+ const normalized = filePath.replace(/\\/g, "/");
36
+ const lower = normalized.toLowerCase();
37
+ if (
38
+ lower.endsWith(".env.example") ||
39
+ lower.endsWith(".env.template") ||
40
+ lower.endsWith(".env.sample")
41
+ )
42
+ return false;
43
+ const base = lower.split("/").pop() || "";
44
+ for (const pat of patterns) {
45
+ if (pat.includes(".env") && base.startsWith(".env")) return true;
46
+ if (pat.includes(".secrets") && lower.includes(".secrets")) return true;
47
+ if (pat.includes("*.pem") && lower.endsWith(".pem")) return true;
48
+ if (pat.includes("*.key") && lower.endsWith(".key")) return true;
49
+ if (pat.includes(".aws") && lower.includes(".aws")) return true;
50
+ if (pat.includes(".ssh") && lower.includes(".ssh")) return true;
51
+ if (pat.includes("credentials.json") && lower.endsWith("credentials.json")) return true;
52
+ if (pat.includes(".npmrc") && lower.endsWith(".npmrc")) return true;
53
+ }
54
+ // fallback genérico por extensión
55
+ if (/\.(pem|key)$/i.test(normalized)) return true;
56
+ if (base.startsWith(".env")) return true;
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Extrae paths candidatos de un comando bash para chequear sensibilidad.
62
+ * Tokeniza por separadores shell `| ; && || > >> <` y respeta quotes.
63
+ * Normaliza quitando quotes y backslashes para detectar obfuscaciones como .e""nv.
64
+ */
65
+ export function extractPathsFromBash(cmd: string): string[] {
66
+ if (!cmd) return [];
67
+ // Normalizar && y || a ; para split único
68
+ const normalized = cmd.replace(/&&/g, ";").replace(/\|\|/g, ";");
69
+ // Split por separadores shell
70
+ const segments = normalized.split(/[|;><\n]+/);
71
+ const paths: string[] = [];
72
+ for (const seg of segments) {
73
+ const trimmed = seg.trim();
74
+ if (!trimmed) continue;
75
+ // Tokeniza respetando quotes — captura tokens entre quotes o sin espacios
76
+ const tokens = trimmed.match(/(?:[^\s"'`\\]+|"[^"]*"|'[^']*'|`[^`]*`)+/g) || [];
77
+ for (let token of tokens) {
78
+ // Normalización anti-obfuscación: quitar quotes y backslashes
79
+ const stripped = token.replace(/["'`]/g, "").replace(/\\/g, "");
80
+ if (!stripped) continue;
81
+ // Filtrar comandos comunes y flags
82
+ if (["cat", "grep", "ls", "echo", "awk", "sed", "cut", "head", "tail", "wc", "find", "xargs", "bash", "sh", "zsh", "env", "printenv", "node", "bun", "npm", "npx", "ls"].includes(stripped)) continue;
83
+ if (stripped.startsWith("-")) continue;
84
+ // Heurística de path: contiene / o . o es .env* o sensible
85
+ const lower = stripped.toLowerCase();
86
+ if (
87
+ stripped.includes("/") ||
88
+ stripped.includes(".") ||
89
+ lower.startsWith(".env") ||
90
+ lower.includes(".secrets") ||
91
+ lower.endsWith(".pem") ||
92
+ lower.endsWith(".key") ||
93
+ lower.includes(".aws") ||
94
+ lower.includes(".ssh") ||
95
+ lower.endsWith("credentials.json") ||
96
+ lower.endsWith(".npmrc")
97
+ ) {
98
+ // Limpiar trailing chars como : , )
99
+ const cleaned = stripped.replace(/[,:;)\]]+$/, "");
100
+ if (cleaned) paths.push(cleaned);
101
+ } else if (stripped === ".env") {
102
+ paths.push(stripped);
103
+ }
104
+ }
105
+ }
106
+ // También buscar paths con regex global como fallback
107
+ // (detecta .env en strings complejos)
108
+ return [...new Set(paths)];
109
+ }
110
+
111
+ /**
112
+ * Lee patrones sensibles desde env OSTACKY_SENSITIVE_PATTERNS o usa default.
113
+ */
114
+ export function getSensitivePatterns(): string[] {
115
+ const raw = process.env.OSTACKY_SENSITIVE_PATTERNS;
116
+ if (!raw) return SENSITIVE_DEFAULT;
117
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
118
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * tiered.ts — mirror de src/tiered.ts para plugin (self-contained)
3
+ * Generado desde src/tiered.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
4
+ */
5
+
6
+ /**
7
+ * Tiered helpers — single source of truth for trivial detection y niveles.
8
+ *
9
+ * Ambos plugins (ostacky-plugin.ts y engram.ts) importan de acá.
10
+ * No duplicar regex. Cache-friendly: isTrivial no muta system[0], solo decide hint.
11
+ * LEVEL_THRESHOLDS es la única definición de niveles (D6).
12
+ */
13
+
14
+ export const LEVEL_THRESHOLDS = {
15
+ "0": { maxFiles: 1, maxLines: 15, hasAPI: false, desc: "1 archivo, sin API, <15 líneas" },
16
+ "0+1": { maxFiles: 2, maxLines: 30, hasAPI: false, desc: "1-2 archivos, sin API, <30 líneas" },
17
+ "1+": { maxFiles: Infinity, maxLines: Infinity, hasAPI: true, desc: "API, deps, >30 líneas, cross-module" },
18
+ } as const;
19
+
20
+ export function classifyLevel({ fileCount, estLines, hasAPI }: { fileCount: number; estLines: number; hasAPI: boolean }): "0" | "0+1" | "1+" {
21
+ if (hasAPI || estLines > 30 || fileCount > 2) return "1+";
22
+ if (estLines > 15 || fileCount > 1) return "0+1";
23
+ return "0";
24
+ }
25
+
26
+ export function isTrivial(msg: string, state: string): boolean {
27
+ if (!msg || state !== "DONE") return false
28
+ if (msg.trim().length >= 30) return false
29
+ if (!/^(hola|hey|gracias|buenas|hi|hello)\b/i.test(msg.trim())) return false
30
+ if (/(necesito|quiero|agregá|fix|bug|feature|auth|spec|implementar)/i.test(msg)) return false
31
+ return true
32
+ }
33
+
34
+ export function getControllerState(directory: string): string {
35
+ try {
36
+ const { readFileSync } = require("node:fs") as typeof import("node:fs")
37
+ const { join } = require("node:path") as typeof import("node:path")
38
+ const statePath =
39
+ process.env.OSTACKY_STATE_PATH || join(directory, ".opencode", "ostacky-state.json")
40
+ const raw = readFileSync(statePath, "utf-8")
41
+ const j = JSON.parse(raw)
42
+ return j.state ?? "DONE"
43
+ } catch {
44
+ return "DONE"
45
+ }
46
+ }
@@ -129,6 +129,7 @@ Return: Summary of what you found and what you fixed.
129
129
  **Need full context:** Understanding requires seeing entire system
130
130
  **Exploratory debugging:** You don't know what's broken yet
131
131
  **Shared state:** Agents would interfere (editing same files, using same resources)
132
+ **Ostacky execution:** If `execution-mode-evaluation` already produced `fileClusters/sharedFiles` — **DO NOT use this skill**; use `subagent-driven-development` dispatch por clusters (respeta `sharedFiles` y `sequentialDeps` del Controller, evita pisar mismo archivo desde 2 agentes).
132
133
 
133
134
  ## Real Example from Session
134
135
 
@@ -39,10 +39,11 @@ Depending on what the user brings, you might:
39
39
  - Find analogies
40
40
 
41
41
  **Investigate the codebase**
42
- - Map existing architecture relevant to the discussion
42
+ - Map existing architecture relevant to the discussion (ground with `codegraph_codegraph_explore` + `engram_mem_search` if pre-1+ or without active change)
43
43
  - Find integration points
44
44
  - Identify patterns already in use
45
45
  - Surface hidden complexity
46
+ - **Level note:** Si necesitás ruteo por nivel Ostacky (0/0+1/1+) usa `brainstorming` en vez de este stance; este skill asume ya estás en flujo OpenSpec (1+).
46
47
 
47
48
  **Compare options**
48
49
  - Brainstorm multiple approaches
@@ -26,7 +26,9 @@ When ready to implement, run /opsx-apply
26
26
 
27
27
  **Steps**
28
28
 
29
- 1. **Check Engram for similar changes** — `engram_mem_search` with keywords from the user's description. If a similar change was proposed or implemented before, surface it to the user. They may want to extend the existing work instead of starting fresh.
29
+ 0. **Ground with CodeGraph + Engram (SHALL — Ostacky)** — Antes de `openspec new`, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Cita símbolos existentes en `proposal/design`; sin esto el `design.md` puede alucinar API inexistente y dispara `WARN:codegraphBypass` en `doctor`.
30
+
31
+ 1. **Check Engram for similar changes** — `engram_mem_search` con keywords del *paso 0* (reusar si ya fue hit; no duplicar `mem_search` mismo `requestId`). Si hay change similar previo, surfacealo. Pueden extender trabajo existente en vez de empezar de cero.
30
32
 
31
33
  2. **If no clear input provided, ask what they want to build**
32
34
 
@@ -28,6 +28,8 @@ Write the test first. Watch it fail. Write minimal code to pass.
28
28
 
29
29
  Thinking "skip TDD just this once"? Stop. That's rationalization.
30
30
 
31
+ > **Ostacky ground (SHALL):** Antes de RED, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si hit → reusar símbolos/decisiones; si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Evita testear API inexistente y respeta `discovery-cache (único)` de `ostacky.md §1`. Ver `execution-mode-evaluation` para cache TTL.
32
+
31
33
  ## The Iron Law
32
34
 
33
35
  ```
package/dist/cli.js CHANGED
@@ -186,7 +186,7 @@ var require_picocolors = __commonJS(function(exports, module) {
186
186
  });
187
187
 
188
188
  // src/security.ts
189
- import { createHash } from "crypto";
189
+ import { createHash } from "node:crypto";
190
190
  function validateFilePath(filePath) {
191
191
  if (filePath.includes("..") || filePath.startsWith("/") || filePath.startsWith("\\") || /^[a-zA-Z]:/.test(filePath)) {
192
192
  throw new Error(`Ruta de archivo inválida: "${filePath}"`);
@@ -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.8.7",
624
+ version: "0.8.8",
625
625
  repo: "JaimeHoracio/Ostacky",
626
- tag: "v0.8.7",
626
+ tag: "v0.8.8",
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.8.7: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
632
- version: "0.8.7",
633
- sha256: "191a317be68bde517f6d3877ec3a016720617d7382944fff0d08d31b985a6644"
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.8.8: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
632
+ version: "0.8.8",
633
+ sha256: "bac9b7bc91d9d958bee8c6b2e094b1e1124bcc7f3e23cd13769ebbc5766f4747"
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.8.7: controller plugin como alma hard-gate, Context7 removido del stack.",
641
- version: "0.8.7",
642
- sha256: "315d8fcdf2a945cef863bb4576838f95d7eb4a0acca38c12575dc80ecc4724dc"
640
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.8: controller plugin como alma hard-gate, Context7 removido del stack.",
641
+ version: "0.8.8",
642
+ sha256: "28ddf7ac8a2472faa1a5dbd14e22bc11e0b8715971907b3fab739b9933aa7dad"
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.8.7",
648
+ version: "0.8.8",
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.8.7: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
657
- version: "0.8.7",
658
- sha256: "9de463763ea6538c03b9307e7e36dcd2ae854dcc460bd25981e75f4f70df0e66"
656
+ description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.8: 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.8.8",
658
+ sha256: "a1d9744ffe70e976c7c96343bf101c1dedf615b27058559a376c0e559a341328"
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.8.7",
664
+ version: "0.8.8",
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.8.7",
673
+ version: "0.8.8",
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.8.7",
680
+ version: "0.8.8",
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.8.7",
688
- sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
687
+ version: "0.8.8",
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.8.7",
694
+ version: "0.8.8",
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.8.7",
702
- sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
701
+ version: "0.8.8",
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.8.7",
708
+ version: "0.8.8",
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.8.7",
715
+ version: "0.8.8",
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.8.7",
723
- sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
722
+ version: "0.8.8",
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.8.7",
729
+ version: "0.8.8",
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.8.7",
736
+ version: "0.8.8",
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.8.7",
744
- sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
743
+ version: "0.8.8",
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.8.7",
750
+ version: "0.8.8",
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.8.7",
757
+ version: "0.8.8",
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.8.7",
764
+ version: "0.8.8",
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.8.7",
771
+ version: "0.8.8",
772
772
  sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
773
773
  }
774
774
  ]
@@ -1155,6 +1155,8 @@ function copyEngramPlugin(projectRoot) {
1155
1155
  function copyOstackyControllerPlugin(projectRoot) {
1156
1156
  const pluginSource = join7(PACKAGE_ROOT, "assets", "plugins", "ostacky-plugin.ts");
1157
1157
  const coreSource = join7(PACKAGE_ROOT, "assets", "plugins", "controller-core.ts");
1158
+ const securitySource = join7(PACKAGE_ROOT, "assets", "plugins", "security.ts");
1159
+ const tieredSource = join7(PACKAGE_ROOT, "assets", "plugins", "tiered.ts");
1158
1160
  const pluginsDir = join7(projectRoot, ".opencode", "plugins");
1159
1161
  if (!existsSync6(pluginSource)) {
1160
1162
  throw new Error(`Plugin bundleado de OstackyController no encontrado: ${pluginSource}`);
@@ -1163,6 +1165,10 @@ function copyOstackyControllerPlugin(projectRoot) {
1163
1165
  copyFileSync3(pluginSource, join7(pluginsDir, "ostacky-plugin.ts"));
1164
1166
  if (existsSync6(coreSource))
1165
1167
  copyFileSync3(coreSource, join7(pluginsDir, "controller-core.ts"));
1168
+ if (existsSync6(securitySource))
1169
+ copyFileSync3(securitySource, join7(pluginsDir, "security.ts"));
1170
+ if (existsSync6(tieredSource))
1171
+ copyFileSync3(tieredSource, join7(pluginsDir, "tiered.ts"));
1166
1172
  }
1167
1173
  function buildEngramDownloadUrl(tag, platform = process.platform, arch = process.arch) {
1168
1174
  const target = getEngramReleaseTarget(platform, arch);
@@ -1492,14 +1498,14 @@ var init_stack = __esm(() => {
1492
1498
  // package.json
1493
1499
  var package_default = {
1494
1500
  name: "ostacky",
1495
- version: "0.8.7",
1501
+ version: "0.8.8",
1496
1502
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1497
1503
  type: "module",
1498
1504
  bin: {
1499
1505
  ostacky: "./dist/cli.js"
1500
1506
  },
1501
1507
  scripts: {
1502
- prebuild: "bun run scripts/sync-version.ts",
1508
+ prebuild: "bun run scripts/sync-version.ts && bun run scripts/sync-controller-core.ts",
1503
1509
  dev: "bun run src/cli.ts",
1504
1510
  build: "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
1505
1511
  start: "node dist/cli.js",
@@ -1510,7 +1516,7 @@ var package_default = {
1510
1516
  "hash:update": "bun run scripts/update-manifest-hashes.ts",
1511
1517
  "manifest:prepare": "bun run scripts/update-manifest-hashes.ts",
1512
1518
  "manifest:check": "bun run scripts/update-manifest-hashes.ts --check",
1513
- prepublishOnly: "bun run manifest:prepare && bun run build"
1519
+ prepublishOnly: "bun run scripts/sync-controller-core.ts && bun run manifest:prepare && bun run build"
1514
1520
  },
1515
1521
  files: [
1516
1522
  "dist/",
@@ -2669,7 +2675,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2669
2675
  params: {
2670
2676
  protocolVersion: "2025-03-26",
2671
2677
  capabilities: {},
2672
- clientInfo: { name: "ostacky-installer", version: "0.8.7" }
2678
+ clientInfo: { name: "ostacky-installer", version: "0.8.8" }
2673
2679
  }
2674
2680
  });
2675
2681
  });
@@ -2978,7 +2984,15 @@ function uninstallAll(paths) {
2978
2984
  for (const name of Object.keys(lockfile.mcpServers ?? {})) {
2979
2985
  uninstallMcpServer(name, paths);
2980
2986
  }
2981
- const ostackyPlugins = ["ostacky-plugin.ts", "engram.ts", "ostacky-guard.ts", "ostacky-controller.ts"];
2987
+ const ostackyPlugins = [
2988
+ "ostacky-plugin.ts",
2989
+ "engram.ts",
2990
+ "ostacky-guard.ts",
2991
+ "ostacky-controller.ts",
2992
+ "controller-core.ts",
2993
+ "security.ts",
2994
+ "tiered.ts"
2995
+ ];
2982
2996
  for (const file of ostackyPlugins) {
2983
2997
  const fp = join6(paths.plugins, file);
2984
2998
  if (existsSync5(fp)) {
@@ -4347,6 +4361,8 @@ async function runDoctorCommand() {
4347
4361
  console.log(`⚠️ degraded: confirmation not audited in controller (degradedEditsCount=${parsed.degradedEditsCount})`);
4348
4362
  if (parsed.codegraphBypassCount > 0)
4349
4363
  console.log(`⚠️ codegraphBypassCount=${parsed.codegraphBypassCount} (inefficient: codegraph bypass)`);
4364
+ if (parsed.engramBypassCount > 0)
4365
+ console.log(`⚠️ engramBypassCount=${parsed.engramBypassCount} (contradiction check bypassed — sin mem_search)`);
4350
4366
  if (parsed.stateOversizedCount > 0)
4351
4367
  console.log(`⚠️ stateOversizedCount=${parsed.stateOversizedCount} snapshots perdidos`);
4352
4368
  if (parsed.sensitiveAccess)
package/manifest.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
- "version": "0.8.7",
2
+ "version": "0.8.8",
3
3
  "repo": "JaimeHoracio/Ostacky",
4
- "tag": "v0.8.7",
4
+ "tag": "v0.8.8",
5
5
  "agents": [
6
6
  {
7
7
  "name": "ostacky",
8
8
  "file": "assets/agents/ostacky.md",
9
- "description": "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.8.7: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
- "version": "0.8.7",
11
- "sha256": "191a317be68bde517f6d3877ec3a016720617d7382944fff0d08d31b985a6644"
9
+ "description": "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.8.8: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
+ "version": "0.8.8",
11
+ "sha256": "bac9b7bc91d9d958bee8c6b2e094b1e1124bcc7f3e23cd13769ebbc5766f4747"
12
12
  }
13
13
  ],
14
14
  "commands": [
15
15
  {
16
16
  "name": "install-stack",
17
17
  "file": "assets/commands/install-stack.md",
18
- "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.7: controller plugin como alma hard-gate, Context7 removido del stack.",
19
- "version": "0.8.7",
20
- "sha256": "315d8fcdf2a945cef863bb4576838f95d7eb4a0acca38c12575dc80ecc4724dc"
18
+ "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.8: controller plugin como alma hard-gate, Context7 removido del stack.",
19
+ "version": "0.8.8",
20
+ "sha256": "28ddf7ac8a2472faa1a5dbd14e22bc11e0b8715971907b3fab739b9933aa7dad"
21
21
  },
22
22
  {
23
23
  "name": "opsx-sync",
24
24
  "file": "assets/commands/opsx-sync.md",
25
25
  "description": "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
26
- "version": "0.8.7",
26
+ "version": "0.8.8",
27
27
  "sha256": "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
28
28
  }
29
29
  ],
@@ -31,15 +31,15 @@
31
31
  {
32
32
  "name": "ostacky-controller",
33
33
  "file": "assets/mcp/ostacky-controller/",
34
- "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.7: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
35
- "version": "0.8.7",
36
- "sha256": "9de463763ea6538c03b9307e7e36dcd2ae854dcc460bd25981e75f4f70df0e66"
34
+ "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.8: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
35
+ "version": "0.8.8",
36
+ "sha256": "a1d9744ffe70e976c7c96343bf101c1dedf615b27058559a376c0e559a341328"
37
37
  },
38
38
  {
39
39
  "name": "openspec",
40
40
  "file": "assets/mcp/openspec/",
41
41
  "description": "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
42
- "version": "0.8.7",
42
+ "version": "0.8.8",
43
43
  "sha256": "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
44
44
  }
45
45
  ],
@@ -48,105 +48,105 @@
48
48
  "name": "brainstorming",
49
49
  "file": "assets/skills/brainstorming/SKILL.md",
50
50
  "description": "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a implementación directa o openspec-propose) y open-exploration (exploración libre)",
51
- "version": "0.8.7",
51
+ "version": "0.8.8",
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.8.7",
58
+ "version": "0.8.8",
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.8.7",
66
- "sha256": "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
65
+ "version": "0.8.8",
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.8.7",
72
+ "version": "0.8.8",
73
73
  "sha256": "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
74
74
  },
75
75
  {
76
76
  "name": "dispatching-parallel-agents",
77
77
  "file": "assets/skills/dispatching-parallel-agents/SKILL.md",
78
78
  "description": "Skill de dispatch paralelo de agentes (Superpowers)",
79
- "version": "0.8.7",
80
- "sha256": "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
79
+ "version": "0.8.8",
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.8.7",
86
+ "version": "0.8.8",
87
87
  "sha256": "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
88
88
  },
89
89
  {
90
90
  "name": "receiving-code-review",
91
91
  "file": "assets/skills/receiving-code-review/SKILL.md",
92
92
  "description": "Skill de recibir y procesar feedback de code review",
93
- "version": "0.8.7",
93
+ "version": "0.8.8",
94
94
  "sha256": "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
95
95
  },
96
96
  {
97
97
  "name": "openspec-propose",
98
98
  "file": "assets/skills/openspec-propose/SKILL.md",
99
99
  "description": "Skill de generación de proposal (OpenSpec)",
100
- "version": "0.8.7",
101
- "sha256": "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
100
+ "version": "0.8.8",
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.8.7",
107
+ "version": "0.8.8",
108
108
  "sha256": "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
109
109
  },
110
110
  {
111
111
  "name": "openspec-archive-change",
112
112
  "file": "assets/skills/openspec-archive-change/SKILL.md",
113
113
  "description": "Skill de archivo de change (OpenSpec)",
114
- "version": "0.8.7",
114
+ "version": "0.8.8",
115
115
  "sha256": "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
116
116
  },
117
117
  {
118
118
  "name": "openspec-explore",
119
119
  "file": "assets/skills/openspec-explore/SKILL.md",
120
120
  "description": "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
121
- "version": "0.8.7",
122
- "sha256": "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
121
+ "version": "0.8.8",
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.8.7",
128
+ "version": "0.8.8",
129
129
  "sha256": "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
130
130
  },
131
131
  {
132
132
  "name": "using-superpowers",
133
133
  "file": "assets/skills/using-superpowers/SKILL.md",
134
134
  "description": "Skill de orquestación de Superpowers skills",
135
- "version": "0.8.7",
135
+ "version": "0.8.8",
136
136
  "sha256": "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
137
137
  },
138
138
  {
139
139
  "name": "writing-skills",
140
140
  "file": "assets/skills/writing-skills/SKILL.md",
141
141
  "description": "Skill de creación y edición de skills",
142
- "version": "0.8.7",
142
+ "version": "0.8.8",
143
143
  "sha256": "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
144
144
  },
145
145
  {
146
146
  "name": "graceful-degradation",
147
147
  "file": "assets/skills/graceful-degradation/SKILL.md",
148
148
  "description": "Skill de degradación graceful cuando múltiples tools están indisponibles",
149
- "version": "0.8.7",
149
+ "version": "0.8.8",
150
150
  "sha256": "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
151
151
  }
152
152
  ]
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "ostacky",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "Instalador interactivo de agentes y comandos para OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ostacky": "./dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "prebuild": "bun run scripts/sync-version.ts",
10
+ "prebuild": "bun run scripts/sync-version.ts && bun run scripts/sync-controller-core.ts",
11
11
  "dev": "bun run src/cli.ts",
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",
@@ -18,7 +18,7 @@
18
18
  "hash:update": "bun run scripts/update-manifest-hashes.ts",
19
19
  "manifest:prepare": "bun run scripts/update-manifest-hashes.ts",
20
20
  "manifest:check": "bun run scripts/update-manifest-hashes.ts --check",
21
- "prepublishOnly": "bun run manifest:prepare && bun run build"
21
+ "prepublishOnly": "bun run scripts/sync-controller-core.ts && bun run manifest:prepare && bun run build"
22
22
  },
23
23
  "files": [
24
24
  "dist/",