ostacky 0.5.7 → 0.5.9
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 +16 -13
- package/assets/agents/ostacky.md +63 -43
- package/assets/commands/install-stack.md +1 -1
- package/assets/mcp/ostacky-controller/index.js +44 -10
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/tests/agent-contract.test.js +226 -0
- package/assets/tests/controller.test.js +187 -0
- package/assets/tests/validate-config.sh +18 -0
- package/dist/cli.js +39 -39
- package/dist/mcp/ostacky-controller/index.js +39 -10
- package/manifest.json +37 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Ostacky integra **5 herramientas** especializadas para que trabajen en conjunto
|
|
|
57
57
|
|
|
58
58
|
1. **CodeGraph** descubre el alcance del cambio sin escanear el repo entero (consulta `codegraph_context`, `codegraph_impact`).
|
|
59
59
|
2. **OpenSpec** documenta requisitos, contratos y escenarios de aceptación (proposal → design → spec → tasks).
|
|
60
|
-
3. **Superpowers** ejecuta con TDD, testing automatizado y review (
|
|
60
|
+
3. **Superpowers** ejecuta con TDD, testing automatizado y review (thinking → plans → tdd → review).
|
|
61
61
|
4. **Engram** persiste decisiones, bugs y descubrimientos con `mem_save` para que el agente no pierda contexto entre sesiones ni necesite re-ejecutar tool calls.
|
|
62
62
|
5. **Context7** provee documentación actualizada de librerías y APIs en tiempo real, sin depender de training data.
|
|
63
63
|
|
|
@@ -197,17 +197,20 @@ Tras instalar, el proyecto queda así:
|
|
|
197
197
|
│ ├── install-stack.md
|
|
198
198
|
│ └── opsx-sync.md
|
|
199
199
|
├── skills/
|
|
200
|
-
│ ├──
|
|
200
|
+
│ ├── thinking/
|
|
201
201
|
│ ├── writing-plans/
|
|
202
202
|
│ ├── tdd/
|
|
203
203
|
│ ├── review/
|
|
204
204
|
│ ├── execution-mode-evaluation/
|
|
205
205
|
│ ├── subagent-driven-development/
|
|
206
206
|
│ ├── dispatching-parallel-agents/
|
|
207
|
-
│ ├── openspec-explore/
|
|
208
207
|
│ ├── openspec-propose/
|
|
209
208
|
│ ├── openspec-apply-change/
|
|
210
|
-
│
|
|
209
|
+
│ ├── openspec-archive-change/
|
|
210
|
+
│ ├── receiving-code-review/
|
|
211
|
+
│ ├── using-git-worktrees/
|
|
212
|
+
│ ├── using-superpowers/
|
|
213
|
+
│ └── writing-skills/
|
|
211
214
|
└── ostacky-lock.json ← versiones instaladas (agentes, commands y skills)
|
|
212
215
|
```
|
|
213
216
|
|
|
@@ -215,33 +218,33 @@ Tras instalar, el proyecto queda así:
|
|
|
215
218
|
|
|
216
219
|
```json
|
|
217
220
|
{
|
|
218
|
-
"version": "0.5.
|
|
221
|
+
"version": "0.5.9",
|
|
219
222
|
"lockedAt": "2025-01-01T00:00:00.000Z",
|
|
220
223
|
"repo": "JaimeHoracio/Ostacky",
|
|
221
|
-
"tag": "v0.5.
|
|
224
|
+
"tag": "v0.5.9",
|
|
222
225
|
"agents": {
|
|
223
226
|
"ostacky": {
|
|
224
|
-
"version": "0.5.
|
|
227
|
+
"version": "0.5.9",
|
|
225
228
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
226
229
|
"sha256": "abc123..."
|
|
227
230
|
}
|
|
228
231
|
},
|
|
229
232
|
"commands": {
|
|
230
233
|
"install-stack": {
|
|
231
|
-
"version": "0.5.
|
|
234
|
+
"version": "0.5.9",
|
|
232
235
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
233
236
|
"sha256": "def456..."
|
|
234
237
|
},
|
|
235
238
|
"opsx-sync": {
|
|
236
|
-
"version": "0.5.
|
|
239
|
+
"version": "0.5.9",
|
|
237
240
|
"installedAt": "2025-01-01T00:00:00.000Z",
|
|
238
241
|
"sha256": "ghi789..."
|
|
239
242
|
}
|
|
240
243
|
},
|
|
241
244
|
"skills": {
|
|
242
|
-
"
|
|
243
|
-
"execution-mode-evaluation": { "version": "0.5.
|
|
244
|
-
"openspec-propose": { "version": "0.5.
|
|
245
|
+
"thinking": { "version": "0.5.9", ... },
|
|
246
|
+
"execution-mode-evaluation": { "version": "0.5.9", ... },
|
|
247
|
+
"openspec-propose": { "version": "0.5.9", ... }
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
```
|
|
@@ -271,7 +274,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
|
|
|
271
274
|
## Seguridad
|
|
272
275
|
|
|
273
276
|
- `opencode.jsonc` se versiona en el repo para compartir permisos y MCP de forma reproducible.
|
|
274
|
-
- Las URLs de descarga usan **tags de GitHub** (ej. `v0.5.
|
|
277
|
+
- Las URLs de descarga usan **tags de GitHub** (ej. `v0.5.9`), nunca `main` — instalaciones reproducibles
|
|
275
278
|
- Cada path de archivo descargado es validado para prevenir **path traversal**
|
|
276
279
|
- Los archivos incluyen **checksum SHA-256** opcional; si el manifest lo define, el contenido se verifica antes de escribir
|
|
277
280
|
- El cache local (`~/.opencode/cache/`) también valida integridad al servir archivos cacheados
|
package/assets/agents/ostacky.md
CHANGED
|
@@ -7,15 +7,19 @@ Sos **Ostacky**, el orquestador. Tu laburo es **interpretar qué quiere el usuar
|
|
|
7
7
|
|
|
8
8
|
## Reglas innegociables
|
|
9
9
|
|
|
10
|
-
1.
|
|
11
|
-
2.
|
|
12
|
-
3. **
|
|
10
|
+
1. **CodeGraph primero, siempre.** Nunca uses `rg`/`grep` en `Bash` para buscar código. Si CodeGraph puede responder, lo usás. `Grep` tool nativo solo para strings literales, nunca `Bash` con `rg`.
|
|
11
|
+
2. **`validate_edit` antes de `edit`, sin excepciones.** Si llamás `edit` sin `validate_edit` primero, desperdiciás un round-trip completo. El error "No changes to apply: oldString and newString are identical" significa que tus strings son idénticos — posible señal de que leíste contenido cacheado o que el cambio ya fue aplicado. `validate_edit` detecta esto y devuelve `ALREADY_APPLIED` en vez de fallar.
|
|
12
|
+
3. **Una pregunta por turno.** `question` tool es el final de tu mensaje. No generás más texto ni ejecutas tools mientras esperás.
|
|
13
|
+
4. **No edites sin leer fresco.** Jamás uses contenido cacheado de un turno anterior para un `edit` — siempre `Read` primero, luego `validate_edit`, luego `edit`.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
DESPUÉS de llamar al `question` tool, TU RESPUESTA TERMINÓ. No hay nada más que agregar. No generes texto explicativo después de la pregunta. No ejecutes tools. No justifiques. No resumas. La pregunta ES el cierre del turno.
|
|
15
|
+
## Stack
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
- **Controller** (`.opencode/mcp/ostacky-controller/index.js`): máquina de estados persistida. Valida transiciones, consume decisiones, autoriza side effects, persiste snapshots y tasks. No lo reemplazás con lógica inline.
|
|
18
|
+
- **CodeGraph**: contexto estructural del código. Tu **primera opción** para entender el código.
|
|
19
|
+
- **OpenSpec**: requisitos y contratos para cambios complejos.
|
|
20
|
+
- **Superpowers**: skills de ejecución, TDD, review, delegación.
|
|
21
|
+
- **Engram**: memoria persistente — saves por decisión/discovery, no por edit.
|
|
22
|
+
- **Context7**: documentación de APIs/librerías externas.
|
|
19
23
|
|
|
20
24
|
## Core Instructions — SINGLE SOURCE OF VERDAD
|
|
21
25
|
|
|
@@ -58,24 +62,20 @@ Si sentís la necesidad de agregar algo después de la pregunta, ES UNA SEÑAL D
|
|
|
58
62
|
|
|
59
63
|
**Trigger:** después de cada tarea completada, evaluá: ¿tomé una decisión, fixeé un bug, o aprendí algo no obvio? Si sí → `engram_mem_save`.
|
|
60
64
|
|
|
61
|
-
##
|
|
65
|
+
## Regla de oro
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
**Siempre describí tu interpretación al usuario ANTES de actuar.** Sin validación no ejecutes nada.
|
|
64
68
|
|
|
65
|
-
**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- No ejecutes tools mientras esperás respuesta
|
|
69
|
+
1. **Interpretá** — "Entendí que querés [X]. Esto afecta a [archivos/áreas]."
|
|
70
|
+
2. **Preguntá** — con `question` tool (nativo). Una pregunta por turno. **Esa pregunta es el final de tu mensaje.**
|
|
71
|
+
3. **Esperá** — la respuesta del usuario. No generes más texto ni ejecutes tools mientras esperás.
|
|
72
|
+
4. **Actuá** — según lo que dijo. La respuesta es **vinculante** y se consume una sola vez.
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
<HARD-STOP>
|
|
75
|
+
DESPUÉS de llamar al `question` tool, TU RESPUESTA TERMINÓ. No hay nada más que agregar. No generes texto explicativo después de la pregunta. No ejecutes tools. No justifiques. No resumas. La pregunta ES el cierre del turno.
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
- **OpenSpec**: requisitos y contratos para cambios complejos.
|
|
76
|
-
- **Superpowers**: skills de ejecución, TDD, review, delegación.
|
|
77
|
-
- **Engram**: memoria persistente — saves por decisión/discovery, no por edit.
|
|
78
|
-
- **Context7**: documentación de APIs/librerías externas.
|
|
77
|
+
Si sentís la necesidad de agregar algo después de la pregunta, ES UNA SEÑAL DE QUE LA PREGUNTA NO ESTÁ BIEN FORMULADA. Reescribí la pregunta para que sea autónoma.
|
|
78
|
+
</HARD-STOP>
|
|
79
79
|
|
|
80
80
|
## Flujo
|
|
81
81
|
|
|
@@ -90,27 +90,33 @@ Usá `question` tool (nativo). NUNCA uses `ask_user` MCP.
|
|
|
90
90
|
|
|
91
91
|
### 1. Discovery
|
|
92
92
|
|
|
93
|
-
1.
|
|
94
|
-
2.
|
|
95
|
-
3.
|
|
96
|
-
4.
|
|
97
|
-
5.
|
|
93
|
+
1. `engram_mem_context` — recuperá historial reciente. ¿Ya se analizó algo similar?
|
|
94
|
+
2. Si existe un change activo, leé `proposal.md`, `design.md`, `tasks.md` — solo estos tres, no todo el directorio.
|
|
95
|
+
3. **Primer tool de código: `codegraph_explore`** sobre el área afectada. Una llamada te da entry points, related symbols y key code snippets. No la reemplaces con `Grep` + `Read` + `Glob`.
|
|
96
|
+
4. Si vas a modificar símbolos específicos → `codegraph_impact` para ver el blast radius.
|
|
97
|
+
5. Leé con `Read` **solo** archivos que el grafo no cubrió (ej: archivos nuevos no indexados, o secciones específicas que necesitas ver literal).
|
|
98
|
+
6. Si CodeGraph no da base suficiente → reportá blocker. No caigas a `Grep` como workaround.
|
|
98
99
|
|
|
99
100
|
### 2. Clasificación por nivel y ruteo
|
|
100
101
|
|
|
102
|
+
Después de CodeGraph, clasificá usando **señales de scope, contratos, dependencias, riesgo e impacto**. El conteo de líneas es orientativo, no determinista.
|
|
103
|
+
|
|
101
104
|
| Señal | Nivel |
|
|
102
105
|
|---|---|
|
|
103
106
|
| 1 archivo, sin API pública, sin dependencias nuevas, <15 líneas | **Nivel 0** (trivial) |
|
|
104
107
|
| 1-2 archivos, sin API pública nueva, sin dependencias nuevas, <30 líneas | **Nivel 0+1** (chico no trivial) |
|
|
105
108
|
| Modifica API pública, agrega archivos/deps, refactor amplio, >30 líneas, impacto cross-module | **Nivel 1+** (requiere OpenSpec) |
|
|
106
109
|
|
|
107
|
-
Llamá `record_discovery` con `{ level, routeDecisionId }`. El controller devuelve `routeDecisionId` y `defaultChoice
|
|
110
|
+
Llamá `record_discovery` con `{ level, routeDecisionId }`. El controller devuelve `routeDecisionId` y `defaultChoice`:
|
|
111
|
+
- Nivel 0/0+1 → `defaultChoice: "DIRECT"` (Superpowers inline por defecto)
|
|
112
|
+
- Nivel 1+ → `defaultChoice: "SPEC"` (OpenSpec por defecto)
|
|
108
113
|
|
|
109
114
|
**Preguntale al usuario con `question` tool:**
|
|
115
|
+
|
|
110
116
|
> Nivel 0/0+1: "Esto es Nivel [0/0+1]. Por defecto lo ejecuto directo con Superpowers. ¿O preferís spec?"
|
|
111
117
|
> Nivel 1+: "Esto es Nivel 1+ porque [razón]. Recomiendo generar spec con OpenSpec. ¿O preferís ejecutar directo?"
|
|
112
118
|
|
|
113
|
-
La opción por defecto va primera. **La primera respuesta del usuario es vinculante.**
|
|
119
|
+
La opción por defecto va primera. **La primera respuesta del usuario es vinculante.** Si dice spec → `consume_route_decision` con `{ decisionId, choice: "SPEC" }`. Si dice directo → `{ choice: "DIRECT" }`. No reinterpretes, no preguntes de nuevo.
|
|
114
120
|
|
|
115
121
|
**HARD-STOP:** Después de esta pregunta, NO hagas nada más en este turno.
|
|
116
122
|
|
|
@@ -123,19 +129,25 @@ La opción por defecto va primera. **La primera respuesta del usuario es vincula
|
|
|
123
129
|
|
|
124
130
|
### 4. Execution
|
|
125
131
|
|
|
126
|
-
1. **Llamá `record_execution_analysis`** con el snapshot de análisis.
|
|
127
|
-
2. **Mostrá el análisis al usuario
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
1. **Llamá `record_execution_analysis`** con el snapshot de análisis (archivos por task, shared files, clusters, dependencias, estimación de líneas, recomendación INLINE/SUBAGENT_DRIVEN).
|
|
133
|
+
2. **Mostrá el análisis al usuario y preguntá** con `question` tool:
|
|
134
|
+
- Mapa de tasks → archivos
|
|
135
|
+
- Archivos compartidos
|
|
136
|
+
- Clusters
|
|
137
|
+
- Recomendación y razón
|
|
138
|
+
- "¿Cómo preferís ejecutar?" (inline / subagent-driven)
|
|
139
|
+
3. **La confirmación del usuario autoriza la ejecución.** Llamá `consume_execution_decision` con `{ decisionId, mode }`.
|
|
140
|
+
**HARD-STOP:** Después de esta pregunta, NO ejecutes `consume_execution_decision`.
|
|
141
|
+
4. **Ejecutá las tasks** — para cada una:
|
|
142
|
+
- Leé el archivo fresco con `Read` (jamás uses un contenido cacheado de un turno anterior) y **guardá el contenido**.
|
|
143
|
+
- **Antes de cualquier `edit`**, llamá `validate_edit` con `{ oldString, newString, content, taskId }`. **`content` es OBLIGATORIO — es el contenido que leíste con Read.** Previene el error "No changes to apply" y conflictos por archivos modificados externamente.
|
|
144
|
+
- ✅ `EDITABLE` → ejecutá `edit` con los mismos `oldString`/`newString`.
|
|
145
|
+
- ✅ `ALREADY_APPLIED` → **STOP**. No llames `edit`. No preguntes al usuario. Pasá a la próxima task inmediatamente. Este caso ocurre cuando oldString y newString son idénticos (cambio ya aplicado) o cuando newString ya está presente en el contenido.
|
|
146
|
+
- ❌ `CONFLICT` → reportá al usuario el `reason`, no edites. Si el reason dice "found N times", ampliá `oldString` con más contexto y volvé a validar.
|
|
147
|
+
- **HARD-STOP**: Si `oldString === newString`, NO llames `edit`. El cambio ya fue aplicado o no hay nada que hacer. Reportá al usuario si es necesario, pero no ejecutes la tool.
|
|
148
|
+
- Después de cada edit exitoso → `complete_task` con `{ taskId, filePath, fileHash }` (sin Engram por edit).
|
|
149
|
+
5. **Superpowers**: `tdd`, `review`, skills de ejecución.
|
|
150
|
+
6. **Subagentes** solo para trabajo realmente independiente (sin archivos compartidos). Son execution-only, no heredan ruteo.
|
|
139
151
|
|
|
140
152
|
### 5. Sync y cierre
|
|
141
153
|
|
|
@@ -150,13 +162,21 @@ La opción por defecto va primera. **La primera respuesta del usuario es vincula
|
|
|
150
162
|
|
|
151
163
|
## Guardrails
|
|
152
164
|
|
|
165
|
+
### Decisiones y estado
|
|
153
166
|
- Si una decisión ya está en OpenSpec, CodeGraph, o el controller → no la resolvés de nuevo.
|
|
154
167
|
- CodeGraph > intuición.
|
|
155
168
|
- `question` tool para todas las preguntas. Una por turno. HARD-STOP después.
|
|
156
169
|
- No cadenas de preguntas. Cuando el usuario responde, esa decisión está cerrada.
|
|
170
|
+
- No tool calls en el mismo mensaje que una pregunta.
|
|
157
171
|
- Fase gate: si estás en Execution o Sync, no volvás a Discovery o Specification automáticamente.
|
|
158
172
|
- Controller no disponible → reportá confianza reducida, default a inline, no ejecutes subagentes sin autorización.
|
|
159
|
-
-
|
|
160
|
-
|
|
173
|
+
- Browser/URL: solo si el usuario lo pide explícitamente.
|
|
174
|
+
|
|
175
|
+
### Eficiencia de tokens
|
|
176
|
+
- **CodeGraph primero, siempre.** Para entender código, buscar símbolos, callers, impact — una llamada a `codegraph_explore` reemplaza docenas de `Read` + `Grep`.
|
|
177
|
+
- **No leas archivos sin justificación.** Solo leé con `Read` lo que CodeGraph o el change activo justifiquen. Si `codegraph_node` con `includeCode: true` te da el cuerpo, no lo re-leas con `Read`.
|
|
178
|
+
- **`validate_edit` es obligatorio antes de `edit`.** Siempre pasá `content` (el resultado de Read). Un edit sin contenido genera un error de validación.
|
|
179
|
+
- **No repitas análisis.** Si ya llamaste `codegraph_explore` para un área en este request, no lo llames de nuevo para la misma área. Si ya tienes un snapshot en el controller, úsalo.
|
|
161
180
|
- **Una tool por intención.** Si `codegraph_explore` ya te da callers + blast radius, no llames `codegraph_callers` por separado.
|
|
181
|
+
- **Filtra output de comandos con `grep` en `Bash`** solo cuando sea filtrar (ej: `tsc 2>&1 | grep error`). Para buscar en el código, usa CodeGraph o el tool `Grep` nativo, nunca `Bash` con `rg`.
|
|
162
182
|
- **No expliques lo que vas a hacer antes de hacerlo** si el usuario no lo pidió. Ejecutá y reportá el resultado.
|
|
@@ -5,7 +5,7 @@ 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.5.
|
|
8
|
+
**Nota:** A partir de v0.5.9, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, Context7, MCPs bundleados) además del agente y skills. Este comando es útil para instalación manual, verificación, o cuando algo falló y necesita reinstalarse.
|
|
9
9
|
|
|
10
10
|
**RESTRICCIÓN ABSOLUTA:** instalar ÚNICAMENTE para OpenCode. Está terminantemente prohibido crear o modificar archivos en `.claude/`, `.kiro/`, `.cursor/`, `.gemini/`, `.codex/`, `.antigravity/`, `.windsurf/` o cualquier otro directorio de plataformas externas.
|
|
11
11
|
|
|
@@ -50,8 +50,13 @@ class OstackyController {
|
|
|
50
50
|
|
|
51
51
|
constructor(opts = {}) {
|
|
52
52
|
this.#statePath = opts.statePath;
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
if (opts.initialState) {
|
|
54
|
+
this.#state = { ...DEFAULT_STATE, ...opts.initialState };
|
|
55
|
+
this.#loaded = true;
|
|
56
|
+
} else {
|
|
57
|
+
this.#state = null;
|
|
58
|
+
this.#loaded = false;
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
#load() {
|
|
@@ -411,7 +416,7 @@ const controller = new OstackyController({ statePath });
|
|
|
411
416
|
|
|
412
417
|
const server = new McpServer({
|
|
413
418
|
name: 'ostacky-controller',
|
|
414
|
-
version: '0.5.
|
|
419
|
+
version: '0.5.9',
|
|
415
420
|
});
|
|
416
421
|
|
|
417
422
|
server.registerTool(
|
|
@@ -621,16 +626,38 @@ server.registerTool(
|
|
|
621
626
|
{
|
|
622
627
|
description:
|
|
623
628
|
'Validate an edit against current file content. Returns EDITABLE, ALREADY_APPLIED, or CONFLICT. ' +
|
|
624
|
-
'Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states.'
|
|
629
|
+
'Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states. ' +
|
|
630
|
+
'IMPORTANT: content parameter is REQUIRED. Read the file first, then pass the full content.',
|
|
625
631
|
inputSchema: z.object({
|
|
626
632
|
oldString: z.string().describe('The exact string to find in content (must be unique).'),
|
|
627
633
|
newString: z.string().describe('The replacement string.'),
|
|
628
|
-
content: z
|
|
634
|
+
content: z
|
|
635
|
+
.string()
|
|
636
|
+
.optional()
|
|
637
|
+
.describe('The current file content. REQUIRED — read the file first with Read tool.'),
|
|
629
638
|
taskId: z.string().optional().describe('Optional task ID for tracking.'),
|
|
630
639
|
}),
|
|
631
640
|
},
|
|
632
641
|
async ({ oldString, newString, content, taskId }) => {
|
|
633
|
-
log('tool:validate_edit', {
|
|
642
|
+
log('tool:validate_edit', {
|
|
643
|
+
taskId,
|
|
644
|
+
oldLen: oldString?.length,
|
|
645
|
+
newLen: newString?.length,
|
|
646
|
+
hasContent: !!content,
|
|
647
|
+
});
|
|
648
|
+
if (typeof content !== 'string' || typeof oldString !== 'string' || typeof newString !== 'string') {
|
|
649
|
+
return {
|
|
650
|
+
content: [
|
|
651
|
+
{
|
|
652
|
+
type: 'text',
|
|
653
|
+
text: JSON.stringify({
|
|
654
|
+
outcome: 'CONFLICT',
|
|
655
|
+
reason: 'Missing required fields: content, oldString, and newString are all required. Read the file first, then pass content to validate_edit.',
|
|
656
|
+
}),
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
};
|
|
660
|
+
}
|
|
634
661
|
const result = await controller.validateEdit({ oldString, newString, content, taskId });
|
|
635
662
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
636
663
|
}
|
|
@@ -663,7 +690,14 @@ async function main() {
|
|
|
663
690
|
log('ostacky-controller connected and ready');
|
|
664
691
|
}
|
|
665
692
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
693
|
+
const isDirectRun =
|
|
694
|
+
process.argv[1] && (process.argv[1].endsWith('/index.js') || process.argv[1].endsWith('\\index.js'));
|
|
695
|
+
|
|
696
|
+
if (isDirectRun) {
|
|
697
|
+
main().catch((error) => {
|
|
698
|
+
console.error('Fatal error:', error);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export { OstackyController };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const ASSETS_DIR = join(__dirname, '..');
|
|
10
|
+
const AGENT_FILE = join(ASSETS_DIR, 'agents', 'ostacky.md');
|
|
11
|
+
const CONTROLLER_FILE = join(ASSETS_DIR, 'mcp', 'ostacky-controller', 'index.js');
|
|
12
|
+
|
|
13
|
+
const agentContent = readFileSync(AGENT_FILE, 'utf8');
|
|
14
|
+
const controllerContent = readFileSync(CONTROLLER_FILE, 'utf8');
|
|
15
|
+
|
|
16
|
+
// Tools registered in the MCP controller
|
|
17
|
+
const MCP_TOOLS = [
|
|
18
|
+
'start_request', 'request_clarification', 'record_clarification',
|
|
19
|
+
'record_discovery', 'consume_route_decision', 'spec_complete',
|
|
20
|
+
'record_execution_analysis', 'consume_execution_decision',
|
|
21
|
+
'implementation_complete', 'sync_complete', 'block', 'replan',
|
|
22
|
+
'get_state', 'get_tasks', 'validate_edit', 'complete_task',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// CodeGraph tools referenced in agent instructions
|
|
26
|
+
const CODEGRAPH_TOOLS = [
|
|
27
|
+
'codegraph_explore', 'codegraph_node', 'codegraph_search',
|
|
28
|
+
'codegraph_callers', 'codegraph_callees', 'codegraph_impact',
|
|
29
|
+
'codegraph_files', 'codegraph_status',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Native OpenCode tools
|
|
33
|
+
const NATIVE_TOOLS = ['question', 'Read', 'Edit', 'Bash', 'Grep', 'Glob'];
|
|
34
|
+
|
|
35
|
+
const ALL_VALID_TOOLS = [...MCP_TOOLS, ...CODEGRAPH_TOOLS, ...NATIVE_TOOLS];
|
|
36
|
+
|
|
37
|
+
describe('Agent contract — ostacky.md', () => {
|
|
38
|
+
describe('Core Instructions section', () => {
|
|
39
|
+
it('has "Core Instructions" heading', () => {
|
|
40
|
+
assert.ok(agentContent.includes('## Core Instructions'),
|
|
41
|
+
'Missing "## Core Instructions" section');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has SINGLE SOURCE OF VERDAD marker', () => {
|
|
45
|
+
assert.ok(agentContent.includes('SINGLE SOURCE OF VERDAD'),
|
|
46
|
+
'Missing "SINGLE SOURCE OF VERDAD" marker');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('CodeGraph section', () => {
|
|
51
|
+
it('has CodeGraph — búsqueda de código subsection', () => {
|
|
52
|
+
assert.ok(agentContent.includes('### CodeGraph — búsqueda de código'),
|
|
53
|
+
'Missing "### CodeGraph — búsqueda de código" section');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('has tool table with codegraph_explore', () => {
|
|
57
|
+
assert.ok(agentContent.includes('codegraph_explore'),
|
|
58
|
+
'Missing codegraph_explore in tool table');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Engram section', () => {
|
|
63
|
+
it('has Engram — memoria persistente subsection', () => {
|
|
64
|
+
assert.ok(agentContent.includes('### Engram — memoria persistente'),
|
|
65
|
+
'Missing "### Engram — memoria persistente" section');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('has engram_mem_context in Discovery flow', () => {
|
|
69
|
+
assert.ok(agentContent.includes('engram_mem_context'),
|
|
70
|
+
'Missing engram_mem_context — agent should consult memory at start of request');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('has engram_mem_search reference', () => {
|
|
74
|
+
assert.ok(agentContent.includes('engram_mem_search'),
|
|
75
|
+
'Missing engram_mem_search — agent should search before deciding');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('has engram_mem_save reference', () => {
|
|
79
|
+
assert.ok(agentContent.includes('engram_mem_save'),
|
|
80
|
+
'Missing engram_mem_save — agent should save after significant work');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('HARD-STOP rules', () => {
|
|
85
|
+
it('has at least 2 HARD-STOP blocks (HTML tags or inline markers)', () => {
|
|
86
|
+
const htmlBlocks = (agentContent.match(/<HARD-STOP>/g) || []).length;
|
|
87
|
+
const inlineMarkers = (agentContent.match(/\*\*HARD-STOP\*\*/g) || []).length;
|
|
88
|
+
const total = htmlBlocks + inlineMarkers;
|
|
89
|
+
assert.ok(total >= 2,
|
|
90
|
+
`Expected at least 2 HARD-STOP blocks (HTML or inline), found ${total} (${htmlBlocks} HTML, ${inlineMarkers} inline)`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('has closing HARD-STOP tags matching opening tags', () => {
|
|
94
|
+
const opens = (agentContent.match(/<HARD-STOP>/g) || []).length;
|
|
95
|
+
const closes = (agentContent.match(/<\/HARD-STOP>/g) || []).length;
|
|
96
|
+
assert.equal(opens, closes,
|
|
97
|
+
`Mismatched HARD-STOP tags: ${opens} opens, ${closes} closes`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('has HARD-STOP in Regla de oro (global question stop)', () => {
|
|
101
|
+
const reglaDeOro = agentContent.substring(
|
|
102
|
+
agentContent.indexOf('## Regla de oro'),
|
|
103
|
+
agentContent.indexOf('## Flujo')
|
|
104
|
+
);
|
|
105
|
+
assert.ok(reglaDeOro.includes('<HARD-STOP>'),
|
|
106
|
+
'Missing HARD-STOP in Regla de Oro section — agent can continue after questions');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('Tool name references — no stale names', () => {
|
|
111
|
+
it('does NOT reference "ask_user" (non-existent tool)', () => {
|
|
112
|
+
const refs = agentContent.match(/ask_user/g);
|
|
113
|
+
const count = refs ? refs.length : 0;
|
|
114
|
+
assert.equal(count, 0,
|
|
115
|
+
`Found ${count} reference(s) to "ask_user" — this tool does not exist. Use "question" instead.`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('does NOT reference "codegraph_context" (stale name)', () => {
|
|
119
|
+
// Allow it in comments or descriptions, but not as an active tool call
|
|
120
|
+
const lines = agentContent.split('\n');
|
|
121
|
+
const offenders = lines.filter(line =>
|
|
122
|
+
line.includes('codegraph_context') &&
|
|
123
|
+
!line.startsWith('|') &&
|
|
124
|
+
!line.includes('NUNCA') &&
|
|
125
|
+
!line.includes('Prohibido')
|
|
126
|
+
);
|
|
127
|
+
assert.equal(offenders.length, 0,
|
|
128
|
+
`Found stale "codegraph_context" references (use codegraph_explore): ${offenders.join('; ')}`);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('references valid MCP tools in flow instructions', () => {
|
|
132
|
+
// Extract tool names used in flow steps (lines with backtick-quoted tool names)
|
|
133
|
+
const toolRefs = agentContent.match(/`([a-z_]+)`/g) || [];
|
|
134
|
+
const uniqueTools = [...new Set(toolRefs.map(t => t.replace(/`/g, '')))];
|
|
135
|
+
|
|
136
|
+
const flowTools = uniqueTools.filter(t =>
|
|
137
|
+
t.includes('request') || t.includes('record') || t.includes('consume') ||
|
|
138
|
+
t.includes('spec_') || t.includes('implementation') || t.includes('sync') ||
|
|
139
|
+
t.includes('validate') || t.includes('complete') || t.includes('block') ||
|
|
140
|
+
t.includes('replan') || t.includes('start_') || t.includes('get_')
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const invalidTools = flowTools.filter(t => !MCP_TOOLS.includes(t));
|
|
144
|
+
assert.equal(invalidTools.length, 0,
|
|
145
|
+
`References non-existent MCP tools: ${invalidTools.join(', ')}`);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('validate_edit instructions', () => {
|
|
150
|
+
it('mentions content as required parameter', () => {
|
|
151
|
+
assert.ok(
|
|
152
|
+
agentContent.includes('content') &&
|
|
153
|
+
(agentContent.includes('content es OBLIGATORIO') ||
|
|
154
|
+
agentContent.includes('`content` es OBLIGATORIO')),
|
|
155
|
+
'Missing explicit instruction that content is required for validate_edit'
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('instructs to Read file before validate_edit', () => {
|
|
160
|
+
const executionSection = agentContent.substring(
|
|
161
|
+
agentContent.indexOf('### 4. Execution'),
|
|
162
|
+
agentContent.indexOf('### 5. Sync')
|
|
163
|
+
);
|
|
164
|
+
assert.ok(executionSection.includes('Read'),
|
|
165
|
+
'Execution section should instruct to Read file before validate_edit');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Classification rules', () => {
|
|
170
|
+
it('has Nivel 0, 0+1, and 1+', () => {
|
|
171
|
+
assert.ok(agentContent.includes('Nivel 0'), 'Missing Nivel 0');
|
|
172
|
+
assert.ok(agentContent.includes('Nivel 0+1'), 'Missing Nivel 0+1');
|
|
173
|
+
assert.ok(agentContent.includes('Nivel 1+'), 'Missing Nivel 1+');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('has defaultChoice mapping', () => {
|
|
177
|
+
assert.ok(agentContent.includes('defaultChoice'), 'Missing defaultChoice mapping');
|
|
178
|
+
assert.ok(agentContent.includes('"DIRECT"'), 'Missing DIRECT default');
|
|
179
|
+
assert.ok(agentContent.includes('"SPEC"'), 'Missing SPEC default');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Guardrails', () => {
|
|
184
|
+
it('has Eficiencia de tokens section', () => {
|
|
185
|
+
assert.ok(agentContent.includes('### Eficiencia de tokens'),
|
|
186
|
+
'Missing "### Eficiencia de tokens" guardrail section');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('has Decisiones y estado section', () => {
|
|
190
|
+
assert.ok(agentContent.includes('### Decisiones y estado'),
|
|
191
|
+
'Missing "### Decisiones y estado" guardrail section');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('Controller contract — index.js', () => {
|
|
197
|
+
it('exports OstackyController', () => {
|
|
198
|
+
assert.ok(controllerContent.includes('export { OstackyController }'),
|
|
199
|
+
'Missing OstackyController export');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('has validate_edit tool registered', () => {
|
|
203
|
+
assert.ok(controllerContent.includes("server.registerTool(\n 'validate_edit'") ||
|
|
204
|
+
controllerContent.includes("server.registerTool('validate_edit'"),
|
|
205
|
+
'validate_edit not registered as MCP tool');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('validate_edit schema has content as optional (not required)', () => {
|
|
209
|
+
// Find the validate_edit schema section
|
|
210
|
+
const validateSection = controllerContent.substring(
|
|
211
|
+
controllerContent.indexOf("'validate_edit'"),
|
|
212
|
+
controllerContent.indexOf("'complete_task'")
|
|
213
|
+
);
|
|
214
|
+
assert.ok(validateSection.includes('content: z.string().optional()'),
|
|
215
|
+
'validate_edit content param should be z.string().optional() — agent may omit it');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('validate_edit handler validates content type', () => {
|
|
219
|
+
const validateSection = controllerContent.substring(
|
|
220
|
+
controllerContent.indexOf("'validate_edit'"),
|
|
221
|
+
controllerContent.indexOf("'complete_task'")
|
|
222
|
+
);
|
|
223
|
+
assert.ok(validateSection.includes("typeof content !== 'string'"),
|
|
224
|
+
'validate_edit handler should check typeof content');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { OstackyController } from '../mcp/ostacky-controller/index.js';
|
|
4
|
+
|
|
5
|
+
function createController(initialState = {}) {
|
|
6
|
+
return new OstackyController({
|
|
7
|
+
statePath: null,
|
|
8
|
+
initialState: {
|
|
9
|
+
state: 'INTERPRETATION_PENDING',
|
|
10
|
+
revision: 0,
|
|
11
|
+
requestId: 'test',
|
|
12
|
+
changeId: null,
|
|
13
|
+
routeDecisionId: null,
|
|
14
|
+
routeChoice: null,
|
|
15
|
+
executionDecisionId: null,
|
|
16
|
+
executionMode: null,
|
|
17
|
+
snapshots: { codegraph: null, execution: null },
|
|
18
|
+
tasks: {},
|
|
19
|
+
fileFingerprints: {},
|
|
20
|
+
error: null,
|
|
21
|
+
...initialState,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('OstackyController', () => {
|
|
27
|
+
describe('startRequest', () => {
|
|
28
|
+
it('transitions to INTERPRETATION_PENDING', async () => {
|
|
29
|
+
const ctrl = createController({ state: 'DONE' });
|
|
30
|
+
const result = await ctrl.startRequest({ requestId: 'req-1' });
|
|
31
|
+
assert.equal(result.state, 'INTERPRETATION_PENDING');
|
|
32
|
+
assert.equal(result.requestId, 'req-1');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('generates requestId when not provided', async () => {
|
|
36
|
+
const ctrl = createController({ state: 'DONE' });
|
|
37
|
+
const result = await ctrl.startRequest();
|
|
38
|
+
assert.ok(result.requestId.startsWith('req-'));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('validateEdit', () => {
|
|
43
|
+
it('returns CONFLICT when content is undefined (the bug we fixed)', async () => {
|
|
44
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
45
|
+
const result = await ctrl.validateEdit({
|
|
46
|
+
oldString: 'foo',
|
|
47
|
+
newString: 'bar',
|
|
48
|
+
content: undefined,
|
|
49
|
+
});
|
|
50
|
+
assert.equal(result.outcome, 'CONFLICT');
|
|
51
|
+
assert.ok(result.reason.includes('Missing required fields'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns CONFLICT when content is not a string', async () => {
|
|
55
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
56
|
+
const result = await ctrl.validateEdit({
|
|
57
|
+
oldString: 'foo',
|
|
58
|
+
newString: 'bar',
|
|
59
|
+
content: 123,
|
|
60
|
+
});
|
|
61
|
+
assert.equal(result.outcome, 'CONFLICT');
|
|
62
|
+
assert.ok(result.reason.includes('Missing required fields'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns EDITABLE when oldString found exactly once', async () => {
|
|
66
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
67
|
+
const result = await ctrl.validateEdit({
|
|
68
|
+
oldString: 'hello world',
|
|
69
|
+
newString: 'hello universe',
|
|
70
|
+
content: 'say hello world to everyone',
|
|
71
|
+
});
|
|
72
|
+
assert.equal(result.outcome, 'EDITABLE');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns ALREADY_APPLIED when oldString equals newString', async () => {
|
|
76
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
77
|
+
const result = await ctrl.validateEdit({
|
|
78
|
+
oldString: 'hello',
|
|
79
|
+
newString: 'hello',
|
|
80
|
+
content: 'hello world',
|
|
81
|
+
});
|
|
82
|
+
assert.equal(result.outcome, 'ALREADY_APPLIED');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns ALREADY_APPLIED when newString already present and oldString not found', async () => {
|
|
86
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
87
|
+
const result = await ctrl.validateEdit({
|
|
88
|
+
oldString: 'old text',
|
|
89
|
+
newString: 'new text',
|
|
90
|
+
content: 'already has new text here',
|
|
91
|
+
});
|
|
92
|
+
assert.equal(result.outcome, 'ALREADY_APPLIED');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns CONFLICT when oldString not found and newString not present', async () => {
|
|
96
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
97
|
+
const result = await ctrl.validateEdit({
|
|
98
|
+
oldString: 'nonexistent',
|
|
99
|
+
newString: 'replacement',
|
|
100
|
+
content: 'some content without the old string',
|
|
101
|
+
});
|
|
102
|
+
assert.equal(result.outcome, 'CONFLICT');
|
|
103
|
+
assert.ok(result.reason.includes('not found'));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns CONFLICT when oldString found multiple times', async () => {
|
|
107
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
108
|
+
const result = await ctrl.validateEdit({
|
|
109
|
+
oldString: 'dup',
|
|
110
|
+
newString: 'replaced',
|
|
111
|
+
content: 'dup and dup again',
|
|
112
|
+
});
|
|
113
|
+
assert.equal(result.outcome, 'CONFLICT');
|
|
114
|
+
assert.ok(result.reason.includes('2 times'));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('returns CONFLICT when not in executing state', async () => {
|
|
118
|
+
const ctrl = createController();
|
|
119
|
+
const result = await ctrl.validateEdit({
|
|
120
|
+
oldString: 'foo',
|
|
121
|
+
newString: 'bar',
|
|
122
|
+
content: 'foo bar baz',
|
|
123
|
+
});
|
|
124
|
+
assert.equal(result.outcome, 'CONFLICT');
|
|
125
|
+
assert.ok(result.reason.includes('Cannot validate edit from state'));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('state transitions', () => {
|
|
130
|
+
it('full flow: start → discovery → route → execution → sync → done', async () => {
|
|
131
|
+
const ctrl = createController({ state: 'DONE' });
|
|
132
|
+
let r = await ctrl.startRequest({ requestId: 'test' });
|
|
133
|
+
assert.equal(r.state, 'INTERPRETATION_PENDING');
|
|
134
|
+
|
|
135
|
+
r = await ctrl.recordDiscovery({ level: '0', routeDecisionId: 'rd-1' });
|
|
136
|
+
assert.equal(r.state, 'ROUTE_DECISION_PENDING');
|
|
137
|
+
assert.equal(r.defaultChoice, 'DIRECT');
|
|
138
|
+
|
|
139
|
+
r = await ctrl.consumeRouteDecision({ decisionId: 'rd-1', choice: 'DIRECT' });
|
|
140
|
+
assert.equal(r.state, 'EXECUTION_ANALYSIS');
|
|
141
|
+
|
|
142
|
+
r = await ctrl.recordExecutionAnalysis({ executionDecisionId: 'ed-1' });
|
|
143
|
+
assert.equal(r.state, 'EXECUTION_DECISION_PENDING');
|
|
144
|
+
|
|
145
|
+
r = await ctrl.consumeExecutionDecision({ decisionId: 'ed-1', mode: 'INLINE' });
|
|
146
|
+
assert.equal(r.state, 'EXECUTING_INLINE');
|
|
147
|
+
|
|
148
|
+
r = await ctrl.implementationComplete();
|
|
149
|
+
assert.equal(r.state, 'SYNC');
|
|
150
|
+
|
|
151
|
+
r = await ctrl.syncComplete();
|
|
152
|
+
assert.equal(r.state, 'DONE');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('rejects invalid transition: start from EXECUTING_INLINE', async () => {
|
|
156
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE', requestId: 'original' });
|
|
157
|
+
const r = await ctrl.startRequest({ requestId: 'nope' });
|
|
158
|
+
assert.equal(r.state, 'EXECUTING_INLINE');
|
|
159
|
+
assert.equal(r.requestId, 'original');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('level 1+ defaults to SPEC', async () => {
|
|
163
|
+
const ctrl = createController();
|
|
164
|
+
const r = await ctrl.recordDiscovery({ level: '1+', routeDecisionId: 'rd-2' });
|
|
165
|
+
assert.equal(r.defaultChoice, 'SPEC');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('completeTask', () => {
|
|
170
|
+
it('marks task as completed', async () => {
|
|
171
|
+
const ctrl = createController({ state: 'EXECUTING_INLINE' });
|
|
172
|
+
const r = await ctrl.completeTask({
|
|
173
|
+
taskId: 'task-1',
|
|
174
|
+
filePath: 'src/foo.ts',
|
|
175
|
+
fileHash: 'abc123',
|
|
176
|
+
});
|
|
177
|
+
assert.equal(r.status, 'COMPLETED');
|
|
178
|
+
assert.equal(r.totalCompleted, 1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('rejects completeTask from wrong state', async () => {
|
|
182
|
+
const ctrl = createController();
|
|
183
|
+
const r = await ctrl.completeTask({ taskId: 'task-1' });
|
|
184
|
+
assert.ok(r.error.includes('Cannot complete task from state'));
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -236,6 +236,24 @@ else
|
|
|
236
236
|
fi
|
|
237
237
|
echo ""
|
|
238
238
|
|
|
239
|
+
# ─── 14. Runtime tests (node:test) ──────────────────────────────────────────
|
|
240
|
+
echo "14. Runtime tests"
|
|
241
|
+
if command -v node &>/dev/null; then
|
|
242
|
+
TEST_OUTPUT=$(cd "$ASSETS_DIR" && node --test tests/controller.test.js tests/agent-contract.test.js 2>&1)
|
|
243
|
+
TEST_EXIT=$?
|
|
244
|
+
TEST_PASS=$(echo "$TEST_OUTPUT" | grep "^# pass" | awk '{print $3}')
|
|
245
|
+
TEST_FAIL=$(echo "$TEST_OUTPUT" | grep "^# fail" | awk '{print $3}')
|
|
246
|
+
if [ "$TEST_EXIT" -eq 0 ]; then
|
|
247
|
+
pass "runtime tests: ${TEST_PASS}/${TEST_PASS} passed"
|
|
248
|
+
else
|
|
249
|
+
fail "runtime tests: ${TEST_FAIL} failed out of $((TEST_PASS + TEST_FAIL))"
|
|
250
|
+
echo "$TEST_OUTPUT" | grep "not ok" | head -5
|
|
251
|
+
fi
|
|
252
|
+
else
|
|
253
|
+
warn "node not found — skipping runtime tests"
|
|
254
|
+
fi
|
|
255
|
+
echo ""
|
|
256
|
+
|
|
239
257
|
# ─── Summary ──────────────────────────────────────────────────────────────────
|
|
240
258
|
echo "═══════════════════════════════════════════════════════════"
|
|
241
259
|
echo " Summary"
|
package/dist/cli.js
CHANGED
|
@@ -161,7 +161,7 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
161
161
|
// package.json
|
|
162
162
|
var package_default = {
|
|
163
163
|
name: "ostacky",
|
|
164
|
-
version: "0.5.
|
|
164
|
+
version: "0.5.9",
|
|
165
165
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
166
166
|
type: "module",
|
|
167
167
|
bin: {
|
|
@@ -879,16 +879,16 @@ import { join as join5 } from "path";
|
|
|
879
879
|
import { existsSync as existsSync4 } from "fs";
|
|
880
880
|
// manifest.json
|
|
881
881
|
var manifest_default = {
|
|
882
|
-
version: "0.5.
|
|
882
|
+
version: "0.5.9",
|
|
883
883
|
repo: "JaimeHoracio/Ostacky",
|
|
884
|
-
tag: "v0.5.
|
|
884
|
+
tag: "v0.5.9",
|
|
885
885
|
agents: [
|
|
886
886
|
{
|
|
887
887
|
name: "ostacky",
|
|
888
888
|
file: "assets/agents/ostacky.md",
|
|
889
889
|
description: "Orquestador principal con ruteo por nivel de impacto, máquina de estados persistida (controller MCP), edición segura con 3 outcomes, y delegación en OpenSpec + Superpowers",
|
|
890
|
-
version: "0.5.
|
|
891
|
-
sha256: "
|
|
890
|
+
version: "0.5.9",
|
|
891
|
+
sha256: "ede9800f644d890fa81c4827592ca62ce00756a25fbdb594cb8a7b40cd7ca2fc"
|
|
892
892
|
}
|
|
893
893
|
],
|
|
894
894
|
commands: [
|
|
@@ -896,14 +896,14 @@ var manifest_default = {
|
|
|
896
896
|
name: "install-stack",
|
|
897
897
|
file: "assets/commands/install-stack.md",
|
|
898
898
|
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP)",
|
|
899
|
-
version: "0.5.
|
|
900
|
-
sha256: "
|
|
899
|
+
version: "0.5.9",
|
|
900
|
+
sha256: "598b54bed669586f832193226ebeddbda325325f8f237ba5104ab3ee58f2ac29"
|
|
901
901
|
},
|
|
902
902
|
{
|
|
903
903
|
name: "opsx-sync",
|
|
904
904
|
file: "assets/commands/opsx-sync.md",
|
|
905
905
|
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
906
|
-
version: "0.5.
|
|
906
|
+
version: "0.5.9",
|
|
907
907
|
sha256: "ed9948f1910743b672e1dfad496bc96972a224a98b063232be39290d43068c50"
|
|
908
908
|
}
|
|
909
909
|
],
|
|
@@ -912,8 +912,8 @@ var manifest_default = {
|
|
|
912
912
|
name: "ostacky-controller",
|
|
913
913
|
file: "assets/mcp/ostacky-controller/",
|
|
914
914
|
description: "Máquina de estados persistida para Ostacky: 13 tools MCP para ciclo completo de request (start_request → discovery → route → execution → sync → done), edición segura con validación de transiciones y persistencia atómica",
|
|
915
|
-
version: "0.5.
|
|
916
|
-
sha256: "
|
|
915
|
+
version: "0.5.9",
|
|
916
|
+
sha256: "68b42dfb65938e423a7c81d9e9b179c0d82533047d5c0205cf459f5aa2fc6fa2"
|
|
917
917
|
}
|
|
918
918
|
],
|
|
919
919
|
skills: [
|
|
@@ -921,99 +921,99 @@ var manifest_default = {
|
|
|
921
921
|
name: "thinking",
|
|
922
922
|
file: "assets/skills/thinking/SKILL.md",
|
|
923
923
|
description: "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a writing-plans o openspec-propose) y open-exploration (exploración libre)",
|
|
924
|
-
version: "0.5.
|
|
925
|
-
sha256: "
|
|
924
|
+
version: "0.5.9",
|
|
925
|
+
sha256: "6a1fdac86357f89e132013e56cb84c1920880c1c035a8dcf53f69e01824724bb"
|
|
926
926
|
},
|
|
927
927
|
{
|
|
928
928
|
name: "execution-mode-evaluation",
|
|
929
929
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
930
930
|
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
931
|
-
version: "0.5.
|
|
932
|
-
sha256: "
|
|
931
|
+
version: "0.5.9",
|
|
932
|
+
sha256: "048925aec576dff014eed0b44e2a553c635ae405f5a245773f480be9b84d18a7"
|
|
933
933
|
},
|
|
934
934
|
{
|
|
935
935
|
name: "writing-plans",
|
|
936
936
|
file: "assets/skills/writing-plans/SKILL.md",
|
|
937
937
|
description: "Skill de planificación de implementación (Superpowers) — execution handoff removido, Ostacky decide modo de ejecución",
|
|
938
|
-
version: "0.5.
|
|
939
|
-
sha256: "
|
|
938
|
+
version: "0.5.9",
|
|
939
|
+
sha256: "13f6e43522567505da90793934d414d290d56f4ea587ac696f66416af9e7633f"
|
|
940
940
|
},
|
|
941
941
|
{
|
|
942
942
|
name: "tdd",
|
|
943
943
|
file: "assets/skills/tdd/SKILL.md",
|
|
944
944
|
description: "Skill de test-driven development (Superpowers)",
|
|
945
|
-
version: "0.5.
|
|
946
|
-
sha256: "
|
|
945
|
+
version: "0.5.9",
|
|
946
|
+
sha256: "7eacd8ee81dc5c0b85065c0392e37ee10314661f7edb59dd8e70a9e0dae8f371"
|
|
947
947
|
},
|
|
948
948
|
{
|
|
949
949
|
name: "subagent-driven-development",
|
|
950
950
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
951
951
|
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
952
|
-
version: "0.5.
|
|
953
|
-
sha256: "
|
|
952
|
+
version: "0.5.9",
|
|
953
|
+
sha256: "cdacae2e5b86f6a642ad0316172cee7dc8d19e909ef4f9ab7fcf7e84a248dda3"
|
|
954
954
|
},
|
|
955
955
|
{
|
|
956
956
|
name: "dispatching-parallel-agents",
|
|
957
957
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
958
958
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
959
|
-
version: "0.5.
|
|
960
|
-
sha256: "
|
|
959
|
+
version: "0.5.9",
|
|
960
|
+
sha256: "281edf0c38f358497c7e2066fa8217a2ba3e2a39b4205c4af0c41d328fc035a1"
|
|
961
961
|
},
|
|
962
962
|
{
|
|
963
963
|
name: "review",
|
|
964
964
|
file: "assets/skills/review/SKILL.md",
|
|
965
965
|
description: "Skill de revisión de código (Superpowers)",
|
|
966
|
-
version: "0.5.
|
|
967
|
-
sha256: "
|
|
966
|
+
version: "0.5.9",
|
|
967
|
+
sha256: "14831d9ef3746ef6e2e6047c868fe7aa296d01f0644227ce776fe99436357b1a"
|
|
968
968
|
},
|
|
969
969
|
{
|
|
970
970
|
name: "receiving-code-review",
|
|
971
971
|
file: "assets/skills/receiving-code-review/SKILL.md",
|
|
972
972
|
description: "Skill de recibir y procesar feedback de code review",
|
|
973
|
-
version: "0.5.
|
|
974
|
-
sha256: "
|
|
973
|
+
version: "0.5.9",
|
|
974
|
+
sha256: "0a5780e8a41539d15428114b7c46f36670acc16aba0db3348d36be1175433872"
|
|
975
975
|
},
|
|
976
976
|
{
|
|
977
977
|
name: "openspec-propose",
|
|
978
978
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
979
979
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
980
|
-
version: "0.5.
|
|
981
|
-
sha256: "
|
|
980
|
+
version: "0.5.9",
|
|
981
|
+
sha256: "3306b21ba9cb8c1611e8ba335126982f3767f7d317b5ed506c57a14c5f88b263"
|
|
982
982
|
},
|
|
983
983
|
{
|
|
984
984
|
name: "openspec-apply-change",
|
|
985
985
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
986
986
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
987
|
-
version: "0.5.
|
|
988
|
-
sha256: "
|
|
987
|
+
version: "0.5.9",
|
|
988
|
+
sha256: "31c1313c3de4616a07efd89306c4c058175d018f42d4381cc7da7030fd03ba30"
|
|
989
989
|
},
|
|
990
990
|
{
|
|
991
991
|
name: "openspec-archive-change",
|
|
992
992
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
993
993
|
description: "Skill de archivo de change (OpenSpec)",
|
|
994
|
-
version: "0.5.
|
|
995
|
-
sha256: "
|
|
994
|
+
version: "0.5.9",
|
|
995
|
+
sha256: "5bf65d1848457bd57be1d21707e55fe6746cdee33bce2f32f12d87661b6aa3a4"
|
|
996
996
|
},
|
|
997
997
|
{
|
|
998
998
|
name: "using-git-worktrees",
|
|
999
999
|
file: "assets/skills/using-git-worktrees/SKILL.md",
|
|
1000
1000
|
description: "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
1001
|
-
version: "0.5.
|
|
1002
|
-
sha256: "
|
|
1001
|
+
version: "0.5.9",
|
|
1002
|
+
sha256: "33433c24f753d566cde16c0931f70d746b6b0cb4d1f3daaa619e928b009e4fc9"
|
|
1003
1003
|
},
|
|
1004
1004
|
{
|
|
1005
1005
|
name: "using-superpowers",
|
|
1006
1006
|
file: "assets/skills/using-superpowers/SKILL.md",
|
|
1007
1007
|
description: "Skill de orquestación de Superpowers skills",
|
|
1008
|
-
version: "0.5.
|
|
1009
|
-
sha256: "
|
|
1008
|
+
version: "0.5.9",
|
|
1009
|
+
sha256: "fba542003aa788b5edb4ae2d1135773a1dde8111d1dd60378e2f61b6695d46fc"
|
|
1010
1010
|
},
|
|
1011
1011
|
{
|
|
1012
1012
|
name: "writing-skills",
|
|
1013
1013
|
file: "assets/skills/writing-skills/SKILL.md",
|
|
1014
1014
|
description: "Skill de creación y edición de skills",
|
|
1015
|
-
version: "0.5.
|
|
1016
|
-
sha256: "
|
|
1015
|
+
version: "0.5.9",
|
|
1016
|
+
sha256: "7f74ffe049283803640d78f8c9ac1ea5f5e626dedb885ecff29a87c5b5d41598"
|
|
1017
1017
|
}
|
|
1018
1018
|
]
|
|
1019
1019
|
};
|
|
@@ -1204,7 +1204,7 @@ function clearLockfile(opencodeRoot) {
|
|
|
1204
1204
|
const lockfile = readLockfile(opencodeRoot);
|
|
1205
1205
|
if (!lockfile) {
|
|
1206
1206
|
writeLockfile(opencodeRoot, {
|
|
1207
|
-
version: "0.5.
|
|
1207
|
+
version: "0.5.9",
|
|
1208
1208
|
lockedAt: new Date().toISOString(),
|
|
1209
1209
|
repo: "",
|
|
1210
1210
|
tag: "",
|
|
@@ -19539,8 +19539,13 @@ class OstackyController {
|
|
|
19539
19539
|
#loaded;
|
|
19540
19540
|
constructor(opts = {}) {
|
|
19541
19541
|
this.#statePath = opts.statePath;
|
|
19542
|
-
|
|
19543
|
-
|
|
19542
|
+
if (opts.initialState) {
|
|
19543
|
+
this.#state = { ...DEFAULT_STATE, ...opts.initialState };
|
|
19544
|
+
this.#loaded = true;
|
|
19545
|
+
} else {
|
|
19546
|
+
this.#state = null;
|
|
19547
|
+
this.#loaded = false;
|
|
19548
|
+
}
|
|
19544
19549
|
}
|
|
19545
19550
|
#load() {
|
|
19546
19551
|
if (this.#loaded)
|
|
@@ -19884,7 +19889,7 @@ var statePath = process.env.OSTACKY_STATE_PATH || ".opencode/ostacky-state.json"
|
|
|
19884
19889
|
var controller = new OstackyController({ statePath });
|
|
19885
19890
|
var server = new McpServer({
|
|
19886
19891
|
name: "ostacky-controller",
|
|
19887
|
-
version: "0.5.
|
|
19892
|
+
version: "0.5.9"
|
|
19888
19893
|
});
|
|
19889
19894
|
server.registerTool("start_request", {
|
|
19890
19895
|
description: "Start or resume a new request in the state machine. Call this first.",
|
|
@@ -20019,15 +20024,33 @@ server.registerTool("get_tasks", {
|
|
|
20019
20024
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
20020
20025
|
});
|
|
20021
20026
|
server.registerTool("validate_edit", {
|
|
20022
|
-
description: "Validate an edit against current file content. Returns EDITABLE, ALREADY_APPLIED, or CONFLICT. " + "Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states.",
|
|
20027
|
+
description: "Validate an edit against current file content. Returns EDITABLE, ALREADY_APPLIED, or CONFLICT. " + "Call BEFORE executing an edit tool. Only valid in EXECUTING_INLINE or EXECUTING_SUBAGENTS states. " + "IMPORTANT: content parameter is REQUIRED. Read the file first, then pass the full content.",
|
|
20023
20028
|
inputSchema: object({
|
|
20024
20029
|
oldString: string2().describe("The exact string to find in content (must be unique)."),
|
|
20025
20030
|
newString: string2().describe("The replacement string."),
|
|
20026
|
-
content: string2().describe("The current file content
|
|
20031
|
+
content: string2().optional().describe("The current file content. REQUIRED — read the file first with Read tool."),
|
|
20027
20032
|
taskId: string2().optional().describe("Optional task ID for tracking.")
|
|
20028
20033
|
})
|
|
20029
20034
|
}, async ({ oldString, newString, content, taskId }) => {
|
|
20030
|
-
log("tool:validate_edit", {
|
|
20035
|
+
log("tool:validate_edit", {
|
|
20036
|
+
taskId,
|
|
20037
|
+
oldLen: oldString?.length,
|
|
20038
|
+
newLen: newString?.length,
|
|
20039
|
+
hasContent: !!content
|
|
20040
|
+
});
|
|
20041
|
+
if (typeof content !== "string" || typeof oldString !== "string" || typeof newString !== "string") {
|
|
20042
|
+
return {
|
|
20043
|
+
content: [
|
|
20044
|
+
{
|
|
20045
|
+
type: "text",
|
|
20046
|
+
text: JSON.stringify({
|
|
20047
|
+
outcome: "CONFLICT",
|
|
20048
|
+
reason: "Missing required fields: content, oldString, and newString are all required. Read the file first, then pass content to validate_edit."
|
|
20049
|
+
})
|
|
20050
|
+
}
|
|
20051
|
+
]
|
|
20052
|
+
};
|
|
20053
|
+
}
|
|
20031
20054
|
const result = await controller.validateEdit({ oldString, newString, content, taskId });
|
|
20032
20055
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
20033
20056
|
});
|
|
@@ -20050,7 +20073,13 @@ async function main() {
|
|
|
20050
20073
|
await server.connect(transport);
|
|
20051
20074
|
log("ostacky-controller connected and ready");
|
|
20052
20075
|
}
|
|
20053
|
-
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20076
|
+
var isDirectRun = process.argv[1] && (process.argv[1].endsWith("/index.js") || process.argv[1].endsWith("\\index.js"));
|
|
20077
|
+
if (isDirectRun) {
|
|
20078
|
+
main().catch((error2) => {
|
|
20079
|
+
console.error("Fatal error:", error2);
|
|
20080
|
+
process.exit(1);
|
|
20081
|
+
});
|
|
20082
|
+
}
|
|
20083
|
+
export {
|
|
20084
|
+
OstackyController
|
|
20085
|
+
};
|
package/manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.5.
|
|
2
|
+
"version": "0.5.9",
|
|
3
3
|
"repo": "JaimeHoracio/Ostacky",
|
|
4
|
-
"tag": "v0.5.
|
|
4
|
+
"tag": "v0.5.9",
|
|
5
5
|
"agents": [
|
|
6
6
|
{
|
|
7
7
|
"name": "ostacky",
|
|
8
8
|
"file": "assets/agents/ostacky.md",
|
|
9
9
|
"description": "Orquestador principal con ruteo por nivel de impacto, máquina de estados persistida (controller MCP), edición segura con 3 outcomes, y delegación en OpenSpec + Superpowers",
|
|
10
|
-
"version": "0.5.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"version": "0.5.9",
|
|
11
|
+
"sha256": "ede9800f644d890fa81c4827592ca62ce00756a25fbdb594cb8a7b40cd7ca2fc"
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
14
|
"commands": [
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"name": "install-stack",
|
|
17
17
|
"file": "assets/commands/install-stack.md",
|
|
18
18
|
"description": "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP)",
|
|
19
|
-
"version": "0.5.
|
|
20
|
-
"sha256": "
|
|
19
|
+
"version": "0.5.9",
|
|
20
|
+
"sha256": "598b54bed669586f832193226ebeddbda325325f8f237ba5104ab3ee58f2ac29"
|
|
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.5.
|
|
26
|
+
"version": "0.5.9",
|
|
27
27
|
"sha256": "ed9948f1910743b672e1dfad496bc96972a224a98b063232be39290d43068c50"
|
|
28
28
|
}
|
|
29
29
|
],
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"name": "ostacky-controller",
|
|
33
33
|
"file": "assets/mcp/ostacky-controller/",
|
|
34
34
|
"description": "Máquina de estados persistida para Ostacky: 13 tools MCP para ciclo completo de request (start_request → discovery → route → execution → sync → done), edición segura con validación de transiciones y persistencia atómica",
|
|
35
|
-
"version": "0.5.
|
|
36
|
-
"sha256": "
|
|
35
|
+
"version": "0.5.9",
|
|
36
|
+
"sha256": "68b42dfb65938e423a7c81d9e9b179c0d82533047d5c0205cf459f5aa2fc6fa2"
|
|
37
37
|
}
|
|
38
38
|
],
|
|
39
39
|
"skills": [
|
|
@@ -41,99 +41,99 @@
|
|
|
41
41
|
"name": "thinking",
|
|
42
42
|
"file": "assets/skills/thinking/SKILL.md",
|
|
43
43
|
"description": "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a writing-plans o openspec-propose) y open-exploration (exploración libre)",
|
|
44
|
-
"version": "0.5.
|
|
45
|
-
"sha256": "
|
|
44
|
+
"version": "0.5.9",
|
|
45
|
+
"sha256": "6a1fdac86357f89e132013e56cb84c1920880c1c035a8dcf53f69e01824724bb"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"name": "execution-mode-evaluation",
|
|
49
49
|
"file": "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
50
50
|
"description": "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
51
|
-
"version": "0.5.
|
|
52
|
-
"sha256": "
|
|
51
|
+
"version": "0.5.9",
|
|
52
|
+
"sha256": "048925aec576dff014eed0b44e2a553c635ae405f5a245773f480be9b84d18a7"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"name": "writing-plans",
|
|
56
56
|
"file": "assets/skills/writing-plans/SKILL.md",
|
|
57
57
|
"description": "Skill de planificación de implementación (Superpowers) — execution handoff removido, Ostacky decide modo de ejecución",
|
|
58
|
-
"version": "0.5.
|
|
59
|
-
"sha256": "
|
|
58
|
+
"version": "0.5.9",
|
|
59
|
+
"sha256": "13f6e43522567505da90793934d414d290d56f4ea587ac696f66416af9e7633f"
|
|
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.5.
|
|
66
|
-
"sha256": "
|
|
65
|
+
"version": "0.5.9",
|
|
66
|
+
"sha256": "7eacd8ee81dc5c0b85065c0392e37ee10314661f7edb59dd8e70a9e0dae8f371"
|
|
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.5.
|
|
73
|
-
"sha256": "
|
|
72
|
+
"version": "0.5.9",
|
|
73
|
+
"sha256": "cdacae2e5b86f6a642ad0316172cee7dc8d19e909ef4f9ab7fcf7e84a248dda3"
|
|
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.5.
|
|
80
|
-
"sha256": "
|
|
79
|
+
"version": "0.5.9",
|
|
80
|
+
"sha256": "281edf0c38f358497c7e2066fa8217a2ba3e2a39b4205c4af0c41d328fc035a1"
|
|
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.5.
|
|
87
|
-
"sha256": "
|
|
86
|
+
"version": "0.5.9",
|
|
87
|
+
"sha256": "14831d9ef3746ef6e2e6047c868fe7aa296d01f0644227ce776fe99436357b1a"
|
|
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.5.
|
|
94
|
-
"sha256": "
|
|
93
|
+
"version": "0.5.9",
|
|
94
|
+
"sha256": "0a5780e8a41539d15428114b7c46f36670acc16aba0db3348d36be1175433872"
|
|
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.5.
|
|
101
|
-
"sha256": "
|
|
100
|
+
"version": "0.5.9",
|
|
101
|
+
"sha256": "3306b21ba9cb8c1611e8ba335126982f3767f7d317b5ed506c57a14c5f88b263"
|
|
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.5.
|
|
108
|
-
"sha256": "
|
|
107
|
+
"version": "0.5.9",
|
|
108
|
+
"sha256": "31c1313c3de4616a07efd89306c4c058175d018f42d4381cc7da7030fd03ba30"
|
|
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.5.
|
|
115
|
-
"sha256": "
|
|
114
|
+
"version": "0.5.9",
|
|
115
|
+
"sha256": "5bf65d1848457bd57be1d21707e55fe6746cdee33bce2f32f12d87661b6aa3a4"
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
"name": "using-git-worktrees",
|
|
119
119
|
"file": "assets/skills/using-git-worktrees/SKILL.md",
|
|
120
120
|
"description": "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
121
|
-
"version": "0.5.
|
|
122
|
-
"sha256": "
|
|
121
|
+
"version": "0.5.9",
|
|
122
|
+
"sha256": "33433c24f753d566cde16c0931f70d746b6b0cb4d1f3daaa619e928b009e4fc9"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "using-superpowers",
|
|
126
126
|
"file": "assets/skills/using-superpowers/SKILL.md",
|
|
127
127
|
"description": "Skill de orquestación de Superpowers skills",
|
|
128
|
-
"version": "0.5.
|
|
129
|
-
"sha256": "
|
|
128
|
+
"version": "0.5.9",
|
|
129
|
+
"sha256": "fba542003aa788b5edb4ae2d1135773a1dde8111d1dd60378e2f61b6695d46fc"
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"name": "writing-skills",
|
|
133
133
|
"file": "assets/skills/writing-skills/SKILL.md",
|
|
134
134
|
"description": "Skill de creación y edición de skills",
|
|
135
|
-
"version": "0.5.
|
|
136
|
-
"sha256": "
|
|
135
|
+
"version": "0.5.9",
|
|
136
|
+
"sha256": "7f74ffe049283803640d78f8c9ac1ea5f5e626dedb885ecff29a87c5b5d41598"
|
|
137
137
|
}
|
|
138
138
|
]
|
|
139
139
|
}
|