ostacky 0.7.4 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
- "version": "0.7.4",
2
+ "version": "0.8.1",
3
3
  "repo": "JaimeHoracio/Ostacky",
4
- "tag": "v0.7.4",
4
+ "tag": "v0.8.1",
5
5
  "agents": [
6
6
  {
7
7
  "name": "ostacky",
8
8
  "file": "assets/agents/ostacky.md",
9
- "description": "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.7.4: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
- "version": "0.7.4",
11
- "sha256": "ffda231d1a9df1693f32460bdb6811077f7f53ba7902b2616d82231a3d6d43bb"
9
+ "description": "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.8.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
10
+ "version": "0.8.1",
11
+ "sha256": "1dea56ffe3866e830c5138b44ff3e7531324ddd2784b1d34a8956614a53c5ec9"
12
12
  }
13
13
  ],
14
14
  "commands": [
15
15
  {
16
16
  "name": "install-stack",
17
17
  "file": "assets/commands/install-stack.md",
18
- "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.4: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
19
- "version": "0.7.4",
20
- "sha256": "6887499074a82c3b4538e38ca501290cacd3afad676bd127662fed77e268f299"
18
+ "description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.1: controller plugin como alma hard-gate, Context7 removido del stack.",
19
+ "version": "0.8.1",
20
+ "sha256": "43ad96a63ceab8730cf0459838e7fad2953333307cfc664a849ca39f7ba7d532"
21
21
  },
22
22
  {
23
23
  "name": "opsx-sync",
24
24
  "file": "assets/commands/opsx-sync.md",
25
25
  "description": "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
26
- "version": "0.7.4",
26
+ "version": "0.8.1",
27
27
  "sha256": "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
28
28
  }
29
29
  ],
@@ -31,15 +31,15 @@
31
31
  {
32
32
  "name": "ostacky-controller",
33
33
  "file": "assets/mcp/ostacky-controller/",
34
- "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.4: 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.7.4",
36
- "sha256": "4f86b6829293024caeb3d09c83b61aee158c383a7bb053a49e5a33c7c55862d1"
34
+ "description": "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.1: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
35
+ "version": "0.8.1",
36
+ "sha256": "ec554bc97f9d9b1e64c0b42813547dedc923e86e326c43ae87afab88ba249b90"
37
37
  },
38
38
  {
39
39
  "name": "openspec",
40
40
  "file": "assets/mcp/openspec/",
41
41
  "description": "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
42
- "version": "0.7.4",
42
+ "version": "0.8.1",
43
43
  "sha256": "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
44
44
  }
45
45
  ],
@@ -48,105 +48,105 @@
48
48
  "name": "brainstorming",
49
49
  "file": "assets/skills/brainstorming/SKILL.md",
50
50
  "description": "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a implementación directa o openspec-propose) y open-exploration (exploración libre)",
51
- "version": "0.7.4",
52
- "sha256": "7b0020f31f346d8f2070219930cfb9341135d40941195f0ea50c3bd7f4a42990"
51
+ "version": "0.8.1",
52
+ "sha256": "87f8d6ed28205f9272792d3865d8cd3232ab5e511a604d6d63d41e3a02a5f422"
53
53
  },
54
54
  {
55
55
  "name": "execution-mode-evaluation",
56
56
  "file": "assets/skills/execution-mode-evaluation/SKILL.md",
57
57
  "description": "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
58
- "version": "0.7.4",
59
- "sha256": "e17f1e0309572841dc1fa537b1f32c7468eb70b3acf453b9ff5cc4fa8d94fd39"
58
+ "version": "0.8.1",
59
+ "sha256": "7b89dda29927c88f1eb13ffcea541a12423384c172f66c4f3d9b61efe0c7a163"
60
60
  },
61
61
  {
62
62
  "name": "tdd",
63
63
  "file": "assets/skills/tdd/SKILL.md",
64
64
  "description": "Skill de test-driven development (Superpowers)",
65
- "version": "0.7.4",
65
+ "version": "0.8.1",
66
66
  "sha256": "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
67
67
  },
68
68
  {
69
69
  "name": "subagent-driven-development",
70
70
  "file": "assets/skills/subagent-driven-development/SKILL.md",
71
71
  "description": "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
72
- "version": "0.7.4",
72
+ "version": "0.8.1",
73
73
  "sha256": "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
74
74
  },
75
75
  {
76
76
  "name": "dispatching-parallel-agents",
77
77
  "file": "assets/skills/dispatching-parallel-agents/SKILL.md",
78
78
  "description": "Skill de dispatch paralelo de agentes (Superpowers)",
79
- "version": "0.7.4",
79
+ "version": "0.8.1",
80
80
  "sha256": "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
81
81
  },
82
82
  {
83
83
  "name": "review",
84
84
  "file": "assets/skills/review/SKILL.md",
85
85
  "description": "Skill de revisión de código (Superpowers)",
86
- "version": "0.7.4",
86
+ "version": "0.8.1",
87
87
  "sha256": "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
88
88
  },
89
89
  {
90
90
  "name": "receiving-code-review",
91
91
  "file": "assets/skills/receiving-code-review/SKILL.md",
92
92
  "description": "Skill de recibir y procesar feedback de code review",
93
- "version": "0.7.4",
93
+ "version": "0.8.1",
94
94
  "sha256": "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
95
95
  },
96
96
  {
97
97
  "name": "openspec-propose",
98
98
  "file": "assets/skills/openspec-propose/SKILL.md",
99
99
  "description": "Skill de generación de proposal (OpenSpec)",
100
- "version": "0.7.4",
100
+ "version": "0.8.1",
101
101
  "sha256": "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
102
102
  },
103
103
  {
104
104
  "name": "openspec-apply-change",
105
105
  "file": "assets/skills/openspec-apply-change/SKILL.md",
106
106
  "description": "Skill de aplicación de change (OpenSpec)",
107
- "version": "0.7.4",
107
+ "version": "0.8.1",
108
108
  "sha256": "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
109
109
  },
110
110
  {
111
111
  "name": "openspec-archive-change",
112
112
  "file": "assets/skills/openspec-archive-change/SKILL.md",
113
113
  "description": "Skill de archivo de change (OpenSpec)",
114
- "version": "0.7.4",
114
+ "version": "0.8.1",
115
115
  "sha256": "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
116
116
  },
117
117
  {
118
118
  "name": "openspec-explore",
119
119
  "file": "assets/skills/openspec-explore/SKILL.md",
120
120
  "description": "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
121
- "version": "0.7.4",
121
+ "version": "0.8.1",
122
122
  "sha256": "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
123
123
  },
124
124
  {
125
125
  "name": "using-git-worktrees",
126
126
  "file": "assets/skills/using-git-worktrees/SKILL.md",
127
127
  "description": "Skill de uso de git worktrees para aislamiento de trabajo",
128
- "version": "0.7.4",
128
+ "version": "0.8.1",
129
129
  "sha256": "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
130
130
  },
131
131
  {
132
132
  "name": "using-superpowers",
133
133
  "file": "assets/skills/using-superpowers/SKILL.md",
134
134
  "description": "Skill de orquestación de Superpowers skills",
135
- "version": "0.7.4",
135
+ "version": "0.8.1",
136
136
  "sha256": "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
137
137
  },
138
138
  {
139
139
  "name": "writing-skills",
140
140
  "file": "assets/skills/writing-skills/SKILL.md",
141
141
  "description": "Skill de creación y edición de skills",
142
- "version": "0.7.4",
142
+ "version": "0.8.1",
143
143
  "sha256": "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
144
144
  },
145
145
  {
146
146
  "name": "graceful-degradation",
147
147
  "file": "assets/skills/graceful-degradation/SKILL.md",
148
148
  "description": "Skill de degradación graceful cuando múltiples tools están indisponibles",
149
- "version": "0.7.4",
149
+ "version": "0.8.1",
150
150
  "sha256": "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
151
151
  }
152
152
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ostacky",
3
- "version": "0.7.4",
3
+ "version": "0.8.1",
4
4
  "description": "Instalador interactivo de agentes y comandos para OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,212 +0,0 @@
1
- /**
2
- * Ostacky Guard — plugin hermano de Engram
3
- *
4
- * Bloquea lecturas sensibles sin autorización previa y bloquea cualquier tool
5
- * cuando el controller está en estado PENDING.
6
- *
7
- * Hard gate incluso en degraded: nunca uses bash para .env sin check_file_access → consume ALLOW con razón auditada.
8
- *
9
- * Se registra en `tool.execute.before` y lanza Error si la operación no está
10
- * permitida. OpenCode aborta el tool antes de tocar disco.
11
- */
12
-
13
- import type { Plugin } from "@opencode-ai/plugin"
14
- import { readFileSync, existsSync } from "node:fs"
15
- import { join, dirname, basename, resolve, relative } from "node:path"
16
-
17
- const SENSITIVE_DEFAULT = [
18
- "**/.env*",
19
- "**/.secrets/**",
20
- "**/*.pem",
21
- "**/*.key",
22
- "**/.aws/**",
23
- "**/.ssh/**",
24
- "**/credentials.json",
25
- "**/.npmrc",
26
- ]
27
-
28
- const BASH_SENSITIVE_RE =
29
- /(?:^|[^a-zA-Z0-9_.-])(\.env(\b|[_.-])|\.secrets\b|\.pem\b|\.key\b|credentials\.json|\.aws\b|\.ssh\b|\.npmrc\b)/i
30
-
31
- function getEnvPatterns(): string[] {
32
- const raw = process.env.OSTACKY_SENSITIVE_PATTERNS
33
- if (!raw) return SENSITIVE_DEFAULT
34
- return raw.split(",").map((s) => s.trim()).filter(Boolean)
35
- }
36
-
37
- function isSensitive(filePath: string, patterns: string[]): boolean {
38
- if (!filePath) return false
39
- const normalized = filePath.replace(/\\/g, "/")
40
- const lower = normalized.toLowerCase()
41
- if (lower.endsWith(".env.example") || lower.endsWith(".env.template") || lower.endsWith(".env.sample")) return false
42
- const base = lower.split("/").pop() || ""
43
- for (const pat of patterns) {
44
- if (pat.includes(".env") && base.startsWith(".env")) return true
45
- if (pat.includes(".secrets") && lower.includes(".secrets")) return true
46
- if (pat.includes("*.pem") && lower.endsWith(".pem")) return true
47
- if (pat.includes("*.key") && lower.endsWith(".key")) return true
48
- if (pat.includes(".aws") && lower.includes(".aws")) return true
49
- if (pat.includes(".ssh") && lower.includes(".ssh")) return true
50
- if (pat.includes("credentials.json") && lower.endsWith("credentials.json")) return true
51
- if (pat.includes(".npmrc") && lower.endsWith(".npmrc")) return true
52
- }
53
- if (/\.(pem|key)$/i.test(normalized)) return true
54
- if (base.startsWith(".env")) return true
55
- return false
56
- }
57
-
58
- function extractPathsFromBash(cmd: string): string[] {
59
- if (!cmd) return []
60
- const normalized = cmd.replace(/&&/g, ";").replace(/\|\|/g, ";")
61
- const segments = normalized.split(/[|;><\n]+/)
62
- const paths: string[] = []
63
- for (const seg of segments) {
64
- const trimmed = seg.trim()
65
- if (!trimmed) continue
66
- const tokens = trimmed.match(/(?:[^\s"'`\u0060\\]+|"[^"]*"|'[^']*'|`[^`]*`)+/g) || []
67
- for (let token of tokens) {
68
- const stripped = token.replace(/["'`]/g, "").replace(/\\/g, "")
69
- if (!stripped) continue
70
- if (["cat","grep","ls","echo","awk","sed","cut","head","tail","wc","find","xargs","bash","sh","zsh","env","printenv","node","bun","npm","npx","ls","cat"].includes(stripped)) continue
71
- if (stripped.startsWith("-")) continue
72
- const lower = stripped.toLowerCase()
73
- if (
74
- stripped.includes("/") ||
75
- stripped.includes(".") ||
76
- lower.startsWith(".env") ||
77
- lower.includes(".secrets") ||
78
- lower.endsWith(".pem") ||
79
- lower.endsWith(".key") ||
80
- lower.includes(".aws") ||
81
- lower.includes(".ssh") ||
82
- lower.endsWith("credentials.json") ||
83
- lower.endsWith(".npmrc")
84
- ) {
85
- const cleaned = stripped.replace(/[,:;)\]]+$/, "")
86
- if (cleaned) paths.push(cleaned)
87
- } else if (stripped === ".env") {
88
- paths.push(stripped)
89
- }
90
- }
91
- }
92
- return [...new Set(paths)]
93
- }
94
-
95
- function getStatePath(directory: string): string {
96
- if (process.env.OSTACKY_STATE_PATH) return process.env.OSTACKY_STATE_PATH
97
- const candidates = [join(directory, "opencode.json"), join(directory, "opencode.jsonc")]
98
- for (const cand of candidates) {
99
- try {
100
- const raw = readFileSync(cand, "utf-8")
101
- const json = JSON.parse(raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""))
102
- const envPath = (json as any)?.mcp?.["ostacky-controller"]?.environment?.OSTACKY_STATE_PATH
103
- if (typeof envPath === "string" && envPath) return envPath
104
- } catch {}
105
- }
106
- return join(directory, ".opencode", "ostacky-state.json")
107
- }
108
-
109
- function readState(directory: string): any | null {
110
- const p = getStatePath(directory)
111
- try {
112
- const raw = readFileSync(p, "utf-8")
113
- return JSON.parse(raw)
114
- } catch {
115
- return null
116
- }
117
- }
118
-
119
- export const OstackyGuard: Plugin = async (ctx) => {
120
- const patterns = getEnvPatterns()
121
-
122
- return {
123
- "tool.execute.before": async (input, output) => {
124
- const tool = (input as any).tool as string
125
- const args = (input as any).args as any
126
-
127
- // 10.2: Guard genérico de PENDING — bloquear cualquier tool no-controller cuando estado es PENDING
128
- const state = readState(ctx.directory)
129
- const pendingStates = ["ROUTE_DECISION_PENDING", "EXECUTION_DECISION_PENDING", "CLARIFICATION_PENDING"]
130
- if (state && pendingStates.includes(state.state)) {
131
- const allowedTools = ["consume_route_decision", "consume_execution_decision", "record_clarification", "abandon", "check_file_access", "consume_file_access_decision", "record_user_confirmation"]
132
- const isControllerTool = tool.startsWith("ostacky-controller_") || allowedTools.some((t) => tool.includes(t))
133
- if (!isControllerTool) {
134
- throw new Error(`BLOCKED: call consume_* first — controller is in ${state.state}. Use consume_route_decision / consume_execution_decision / record_clarification to proceed.`)
135
- }
136
- }
137
-
138
- // ── Guard hard para bash/write/edit (hardening-v2 P0) ──
139
- // Hard gate incluso en degraded, nunca uses bash para .env sin check_file_access → consume ALLOW con razón auditada
140
- if (tool === "bash") {
141
- const cmd: string = args?.command || args?.cmd || ""
142
- if (typeof cmd === "string" && cmd) {
143
- const normalized = cmd.replace(/["'`]/g, "").replace(/\\/g, "")
144
- const hasSensitivePattern = BASH_SENSITIVE_RE.test(normalized)
145
- const paths = extractPathsFromBash(cmd)
146
- const sensitivePaths = paths.filter((p) => isSensitive(p, patterns))
147
- // Si hay match de regex o paths sensibles, validar ALLOW
148
- if (hasSensitivePattern || sensitivePaths.length > 0) {
149
- // Si extrajimos paths sensibles, validar cada uno
150
- if (sensitivePaths.length > 0) {
151
- for (const p of sensitivePaths) {
152
- const s = readState(ctx.directory)
153
- // Normalizar p para lookup: probar p tal cual, y resolved, y basename para casos relativos
154
- const candidates = [p, resolve(ctx.directory, p), join(ctx.directory, p)]
155
- const allowed = candidates.some((c) => s?.allowedFiles?.[c] || s?.allowedFiles?.[p])
156
- // También chequear por basename si es .env (ej: .env vs /abs/.env)
157
- const baseAllowed = s?.allowedFiles?.[p] || s?.allowedFiles?.[basename(p)] || s?.allowedFiles?.[resolve(ctx.directory, p)]
158
- if (!allowed && !baseAllowed) {
159
- const denied = s?.deniedFiles?.[p] || s?.deniedFiles?.[basename(p)]
160
- if (denied) {
161
- throw new Error(`BLOCKED: bash contiene acceso sensible (${p}) (previously denied) — Llamá check_file_access con reason antes.`)
162
- }
163
- throw new Error(`BLOCKED: bash contiene acceso sensible (${p}). Llamá check_file_access con reason antes.`)
164
- }
165
- }
166
- } else if (hasSensitivePattern) {
167
- // Fallback genérico cuando regex matchea pero no extrajimos path (ej: cat .env con espacios raros)
168
- const s = readState(ctx.directory)
169
- // Buscar si hay algún allowed que cubra .env* genérico
170
- const hasAllowed = Object.keys(s?.allowedFiles || {}).some((k) => isSensitive(k, patterns))
171
- if (!hasAllowed) {
172
- throw new Error(`BLOCKED: bash contiene acceso sensible (.env). Llamá check_file_access con reason antes.`)
173
- }
174
- }
175
- }
176
- // Caso especial: obfuscación .e""nv → normalized ya quitó quotes, pero paths extraído ya lo cubre
177
- // Si normalized contiene .env después de strip, y no hay allowed, ya bloqueamos arriba
178
- }
179
- }
180
-
181
- // Extraer filePath para read/write/edit/grep/glob/read_mcp_resource
182
- let filePath: string | undefined
183
- if (tool === "read" || tool === "read_mcp_resource" || tool === "grep" || tool === "glob" || tool === "write" || tool === "edit") {
184
- filePath = args?.filePath || args?.path || args?.pattern || args?.uri || ""
185
- if (tool === "grep" && args?.include) filePath = args.include
186
- if (tool === "write" || tool === "edit") {
187
- filePath = args?.filePath || args?.path || ""
188
- }
189
- if (tool === "read_mcp_resource" && typeof args?.uri === "string") {
190
- try {
191
- const u = args.uri as string
192
- if (u.startsWith("file://")) filePath = u.slice(7)
193
- else if (u.includes("/")) filePath = u
194
- } catch {}
195
- }
196
- }
197
-
198
- // 9.1: Guard de credenciales (hard gate incluso en degraded)
199
- if (filePath && isSensitive(filePath, patterns)) {
200
- const s = readState(ctx.directory)
201
- const allowed = s?.allowedFiles?.[filePath] || s?.allowedFiles?.[resolve(ctx.directory, filePath)] || s?.allowedFiles?.[basename(filePath)]
202
- if (!allowed) {
203
- const denied = s?.deniedFiles?.[filePath] || s?.deniedFiles?.[basename(filePath)]
204
- if (denied) {
205
- throw new Error(`BLOCKED: File ${filePath} requires check_file_access (previously denied)`)
206
- }
207
- throw new Error(`BLOCKED: File ${filePath} requires check_file_access`)
208
- }
209
- }
210
- },
211
- }
212
- }