ostacky 0.4.1 → 0.5.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.
@@ -57,21 +57,17 @@ Si el cambio tiene **≤2 tasks de implementación** Y **no comparten archivos e
57
57
 
58
58
  ```json
59
59
  {
60
- "mode": "inline",
61
- "confidence": 1.0,
60
+ "recommendation": "INLINE",
62
61
  "reasons": ["Cambio pequeño (≤2 tasks independientes sin archivos compartidos). Inline directo."],
63
62
  "codegraphUsed": [],
64
- "globalRuleTriggered": "early-exit",
65
- "taskAnalysis": {
66
- "taskCount": <N>,
67
- "sharedFiles": {},
68
- "fileClusters": [<cada task como cluster propio>],
69
- "clusterCount": <taskCount>,
70
- "sequentialDeps": [],
71
- "estLines": <estimación>,
72
- "hasExplicitContract": false
73
- },
74
- "phaseRecommendations": []
63
+ "taskCount": <N>,
64
+ "sharedFiles": {},
65
+ "fileClusters": [<cada task como cluster propio>],
66
+ "clusterCount": <taskCount>,
67
+ "sequentialDeps": [],
68
+ "estLines": <estimación>,
69
+ "hasExplicitContract": false,
70
+ "filesPerTask": {}
75
71
  }
76
72
  ```
77
73
 
@@ -296,61 +292,55 @@ f. SI NINGUNA condición anterior se cumplió
296
292
 
297
293
  ---
298
294
 
299
- ### Paso 4: Devolver decisión
295
+ ### Paso 4: Devolver decisión al controller
300
296
 
301
- El output DEBE ser un JSON válido en este formato:
297
+ El output DEBE ser un JSON válido en este formato, que se pasa a `controller.recordExecutionAnalysis()`:
302
298
 
303
299
  ```json
304
300
  {
305
- "mode": "inline" | "subagent-driven",
306
- "confidence": 0.0 - 1.0,
301
+ "recommendation": "INLINE" | "SUBAGENT_DRIVEN",
307
302
  "reasons": ["razón principal", "razón secundaria"],
308
303
  "codegraphUsed": ["codegraph_context"],
309
- "globalRuleTriggered": "1",
310
- "taskAnalysis": {
311
- "taskCount": 13,
312
- "sharedFiles": {
313
- "src/logging.ts": ["2.2", "2.4", "2.5"]
314
- },
315
- "fileClusters": [
316
- ["2.2", "2.4", "2.5"],
317
- ["2.1"],
318
- ["2.3"],
319
- ["3.1"]
320
- ],
321
- "clusterCount": 4,
322
- "sequentialDeps": [],
323
- "estLines": 45,
324
- "hasExplicitContract": false
304
+ "taskCount": 13,
305
+ "sharedFiles": {
306
+ "src/logging.ts": ["2.2", "2.4", "2.5"]
325
307
  },
326
- "phaseRecommendations": [
327
- {
328
- "phase": "2. Core Implementation",
329
- "mode": "inline",
330
- "reason": "Archivos compartidos dentro de la fase: src/logging.ts"
331
- },
332
- {
333
- "phase": "4. Testing",
334
- "mode": "subagent-driven",
335
- "reason": "4 tests independientes sin archivos compartidos"
336
- }
337
- ]
308
+ "fileClusters": [
309
+ ["2.2", "2.4", "2.5"],
310
+ ["2.1"],
311
+ ["2.3"],
312
+ ["3.1"]
313
+ ],
314
+ "clusterCount": 4,
315
+ "sequentialDeps": [],
316
+ "estLines": 45,
317
+ "hasExplicitContract": false,
318
+ "filesPerTask": {
319
+ "2.1": ["src/init.ts"],
320
+ "2.2": ["src/logging.ts"]
321
+ }
338
322
  }
339
323
  ```
340
324
 
341
- **Campos:**
325
+ **Campos del snapshot (contrato con controller.buildExecutionSnapshot):**
342
326
 
343
327
  | Campo | Descripción |
344
328
  |-------|-------------|
345
- | `mode` | Modo global: `inline` o `subagent-driven` |
346
- | `confidence` | Qué tan seguro estás de la decisión (0.0 = nada, 1.0 = totalmente). Bajar si CodeGraph no se pudo usar. |
347
- | `reasons` | Array de strings. La primera razón es la principal (por qué se gatilló la regla global). |
348
- | `codegraphUsed` | Lista de tool calls de CodeGraph que se ejecutaron. Vacío si el Paso 0 encontró datos suficientes. |
349
- | `globalRuleTriggered` | Código de la regla que decidió el modo global ("1", "2a", "2b", "3a", "3b", "3c"). Para trazabilidad. |
350
- | `taskAnalysis` | Mapa global construido en el Paso 2. Incluir siempre para trazabilidad. |
351
- | `taskAnalysis.fileClusters` | Array de arrays: cada sub-array es un cluster de tareas que comparten archivos. Fundamental para la decisión. |
352
- | `taskAnalysis.clusterCount` | Número total de clusters. `clusterCount == 1` → todo conectado. `clusterCount == taskCount` → todo independiente. |
353
- | `phaseRecommendations` | Array con el modo recomendado por fase. Cada entrada tiene `phase`, `mode` y `reason`. Vacío si el modo global es subagent-driven o si tasks.md no tiene fases separadas. |
329
+ | `recommendation` | `INLINE` o `SUBAGENT_DRIVEN`. Basado en las reglas de precedencia. |
330
+ | `reasons` | Array de strings. La primera razón es la principal. |
331
+ | `codegraphUsed` | Lista de tool calls de CodeGraph ejecutados. |
332
+ | `filesPerTask` | Mapa: taskId [archivos que modifica] |
333
+ | `sharedFiles` | Mapa: archivo [tasks que lo tocan] |
334
+ | `fileClusters` | Componentes conectados por archivos compartidos. |
335
+ | `taskCount` | Total de tasks de implementación. |
336
+ | `clusterCount` | `clusterCount == 1` → todo conectado. `== taskCount` → todo independiente. |
337
+ | `sequentialDeps` | Dependencias secuenciales entre tasks. |
338
+ | `estLines` | Estimación conservadora de líneas totales. |
339
+ | `hasExplicitContract` | `true` si design.md tiene contratos explícitos entre tasks. |
340
+
341
+ **⚠️ Este skill provee ANÁLISIS y RECOMENDACIÓN, no autorización.** No preguntes al usuario el modo de ejecución — eso lo hace Ostacky (el coordinador) después de recibir este snapshot y mostrarlo al usuario con `ask_user`.
342
+
343
+ **Consistencia de campos:** `recommendation` es el campo canónico para el modo. Si generaste `mode` en versiones anteriores, migrá a `recommendation` para compatibilidad con el controller.
354
344
 
355
345
  ---
356
346
 
@@ -1,10 +1,18 @@
1
1
  ---
2
2
  name: subagent-driven-development
3
- description: Use when executing implementation plans with independent tasks in the current session
3
+ description: Use when Ostacky has confirmed subagent-driven execution mode and dispatches you with clusters of independent tasks
4
4
  ---
5
5
 
6
6
  # Subagent-Driven Development
7
7
 
8
+ **Prerequisite:** This skill runs AFTER the coordinator (Ostacky) has:
9
+ 1. Performed execution analysis (via `execution-mode-evaluation` skill)
10
+ 2. Presented evidence and recommendation to the user
11
+ 3. Received explicit user confirmation for subagent-driven mode
12
+ 4. Called `controller.consumeExecutionDecision({ mode: "SUBAGENT_DRIVEN" })`
13
+
14
+ Subagents are execution-only. They never create routing decisions, re-analyze execution mode, or duplicate discovery that the coordinator already resolved.
15
+
8
16
  Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.
9
17
 
10
18
  **Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.
@@ -49,7 +49,7 @@ This structure informs the task decomposition. Each task should produce self-con
49
49
  ```markdown
50
50
  # [Feature Name] Implementation Plan
51
51
 
52
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
52
+ > **For agentic workers:** Tasks use checkbox (`- [ ]`) syntax for tracking. Execution mode is determined by the coordinator after plan completion.
53
53
 
54
54
  **Goal:** [One sentence describing what this builds]
55
55
 
@@ -131,22 +131,6 @@ After writing the complete plan, look at the spec with fresh eyes and check the
131
131
 
132
132
  If you find issues, fix them inline. No need to re-review — just fix and move on. If you find a spec requirement with no task, add the task.
133
133
 
134
- ## Execution Handoff
134
+ ## Execution
135
135
 
136
- After saving the plan, offer execution choice:
137
-
138
- **"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Two execution options:**
139
-
140
- **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
141
-
142
- **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
143
-
144
- **Which approach?"**
145
-
146
- **If Subagent-Driven chosen:**
147
- - **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
148
- - Fresh subagent per task + two-stage review
149
-
150
- **If Inline Execution chosen:**
151
- - **REQUIRED SUB-SKILL:** Use superpowers:executing-plans
152
- - Batch execution with checkpoints for review
136
+ Ostacky (the coordinator) handles execution-mode analysis and user confirmation after the plan is ready. This skill only writes the plan — it does NOT offer the user an execution mode choice. If an execution-mode prompt appears, defer to the coordinator.
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.4.1",
164
+ version: "0.5.1",
165
165
  description: "Instalador interactivo de agentes y comandos para OpenCode",
166
166
  type: "module",
167
167
  bin: {
@@ -876,110 +876,126 @@ var L2 = () => {
876
876
  import { join as join5 } from "path";
877
877
  // manifest.json
878
878
  var manifest_default = {
879
- version: "0.4.1",
879
+ version: "0.5.1",
880
880
  repo: "JaimeHoracio/Ostacky",
881
- tag: "v0.4.1",
881
+ tag: "v0.5.1",
882
882
  agents: [
883
883
  {
884
884
  name: "ostacky",
885
885
  file: "assets/agents/ostacky.md",
886
- description: "Agente principal de desarrollo con ejecución inline-first, análisis transparente de archivos compartidos y pre-edit guard simplificado",
887
- version: "0.4.1",
888
- sha256: "1287c743d073b85513ca05c7222ade892aef73a2f4ac7e59ac6d828d0d265b22"
886
+ 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",
887
+ version: "0.5.1",
888
+ sha256: "355c729ee13f0cd99b9a16e0c203dd83808ec9fb5a764012521794011a91a9f1"
889
889
  }
890
890
  ],
891
891
  commands: [
892
892
  {
893
893
  name: "install-stack",
894
894
  file: "assets/commands/install-stack.md",
895
- description: "Instala el stack tecnológico del proyecto",
896
- version: "0.4.1",
897
- sha256: null
895
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP)",
896
+ version: "0.5.1",
897
+ sha256: "e8b75efe505766d47dd4d38cfb055b6801552cd457d99bce018c196a346e4560"
898
898
  },
899
899
  {
900
900
  name: "opsx-sync",
901
901
  file: "assets/commands/opsx-sync.md",
902
- description: "Sincroniza delta specs del change activo (wrapper de openspec update)",
903
- version: "0.4.1",
904
- sha256: null
902
+ description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
903
+ version: "0.5.1",
904
+ sha256: "ed9948f1910743b672e1dfad496bc96972a224a98b063232be39290d43068c50"
905
+ }
906
+ ],
907
+ mcpServers: [
908
+ {
909
+ name: "ask-user",
910
+ file: "assets/mcp/ask-user-server/",
911
+ description: "MCP server con tool ask_user para consultar al usuario con bloqueo de ejecución",
912
+ version: "0.5.1",
913
+ sha256: "74deeadfde30dfd9e3fe5e6fb75ab9e024aa7150a40518b2f40354c392e8fb5d"
914
+ },
915
+ {
916
+ name: "ostacky-controller",
917
+ file: "assets/mcp/ostacky-controller/",
918
+ description: "Máquina de estados persistida para Ostacky: validación de transiciones, consumo de decisiones, autorización de side effects, edición segura y snapshots reutilizables",
919
+ version: "0.5.1",
920
+ sha256: "c66291cf170a7579e611580e8ac6f4bb15857d7e71b43fb793d92ea3a0013951"
905
921
  }
906
922
  ],
907
923
  skills: [
908
924
  {
909
925
  name: "execution-mode-evaluation",
910
926
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
911
- description: "Skill de decision de modo de ejecucion inline vs subagent-driven con CodeGraph y early exit para cambios pequeños",
912
- version: "0.4.1",
913
- sha256: "cd7404518893742ede707ea7b8393cd5d28d0602f0900c2f16f8470756869b7d"
927
+ description: "Skill de análisis de modo de ejecución output reconciliado con controller snapshot contract (recommendation field)",
928
+ version: "0.5.1",
929
+ sha256: "0bf115b939c906e276040a9bd741c1b84d658da29e3c3fa2cb5e74e4c72792a1"
914
930
  },
915
931
  {
916
932
  name: "brainstorming",
917
933
  file: "assets/skills/brainstorming/SKILL.md",
918
934
  description: "Skill de descubrimiento y exploración (Superpowers)",
919
- version: "0.4.1",
935
+ version: "0.5.1",
920
936
  sha256: "ad15458c54912007ae3f708c698a11f0a4a58e2a7c7860a1ae8874db573c9e98"
921
937
  },
922
938
  {
923
939
  name: "writing-plans",
924
940
  file: "assets/skills/writing-plans/SKILL.md",
925
- description: "Skill de planificación de implementación (Superpowers)",
926
- version: "0.4.1",
927
- sha256: "4d675df944b9c846e9066ff971861ad1c9673cd2c88375081b00a75872ad621a"
941
+ description: "Skill de planificación de implementación (Superpowers) — execution handoff removido, Ostacky decide modo de ejecución",
942
+ version: "0.5.1",
943
+ sha256: "408bca4652677d59bb35cdbe7459b01555b4ca11bf44217d95f98f3641af88b2"
928
944
  },
929
945
  {
930
946
  name: "tdd",
931
947
  file: "assets/skills/tdd/SKILL.md",
932
948
  description: "Skill de test-driven development (Superpowers)",
933
- version: "0.4.1",
949
+ version: "0.5.1",
934
950
  sha256: "7eacd8ee81dc5c0b85065c0392e37ee10314661f7edb59dd8e70a9e0dae8f371"
935
951
  },
936
952
  {
937
953
  name: "subagent-driven-development",
938
954
  file: "assets/skills/subagent-driven-development/SKILL.md",
939
- description: "Skill de ejecución con subagentes (Superpowers)",
940
- version: "0.4.1",
941
- sha256: "14e0f685b1dc1d7e02b7226d18f037fe4b6cba49b512a1fadf67cfad2a9dcc96"
955
+ description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
956
+ version: "0.5.1",
957
+ sha256: "2d0c880deae34ab6882c93f6c8de666013498f0e84d7741116dd0835e2780a1b"
942
958
  },
943
959
  {
944
960
  name: "dispatching-parallel-agents",
945
961
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
946
962
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
947
- version: "0.4.1",
963
+ version: "0.5.1",
948
964
  sha256: "281edf0c38f358497c7e2066fa8217a2ba3e2a39b4205c4af0c41d328fc035a1"
949
965
  },
950
966
  {
951
967
  name: "review",
952
968
  file: "assets/skills/review/SKILL.md",
953
969
  description: "Skill de revisión de código (Superpowers)",
954
- version: "0.4.1",
970
+ version: "0.5.1",
955
971
  sha256: "c78d3beff45e9bb6b5e9e1d7155910f379e082d7aaf3c61b90d651d09e0d2e2a"
956
972
  },
957
973
  {
958
974
  name: "openspec-explore",
959
975
  file: "assets/skills/openspec-explore/SKILL.md",
960
976
  description: "Skill de exploración pre-spec (OpenSpec)",
961
- version: "0.4.1",
977
+ version: "0.5.1",
962
978
  sha256: "1c75cab8672c2eb8b12f27a0b31f0b7fdb73c21c72583c1e8a566e09fe4108a6"
963
979
  },
964
980
  {
965
981
  name: "openspec-propose",
966
982
  file: "assets/skills/openspec-propose/SKILL.md",
967
983
  description: "Skill de generación de proposal (OpenSpec)",
968
- version: "0.4.1",
984
+ version: "0.5.1",
969
985
  sha256: "ab88e9aa22db09559ccf7987d08adc0469b7c6fbe46498e63a3b64926f19e66b"
970
986
  },
971
987
  {
972
988
  name: "openspec-apply-change",
973
989
  file: "assets/skills/openspec-apply-change/SKILL.md",
974
990
  description: "Skill de aplicación de change (OpenSpec)",
975
- version: "0.4.1",
991
+ version: "0.5.1",
976
992
  sha256: "847c2f88a66e8d5946c1fec422a5367c89eca5340839b659bbaaeb6f0bdac487"
977
993
  },
978
994
  {
979
995
  name: "openspec-archive-change",
980
996
  file: "assets/skills/openspec-archive-change/SKILL.md",
981
997
  description: "Skill de archivo de change (OpenSpec)",
982
- version: "0.4.1",
998
+ version: "0.5.1",
983
999
  sha256: "3d35905ec40a081b8fd77b144426e540c67b1f50cc5801b655313a4fdfaa087a"
984
1000
  }
985
1001
  ]
@@ -1053,6 +1069,11 @@ function getBundledSkillPath(name) {
1053
1069
  validateFilePath(name);
1054
1070
  return join2(BUNDLED_SKILLS_DIR, name);
1055
1071
  }
1072
+ var BUNDLED_MCP_DIR = join2(PACKAGE_ROOT, "assets", "mcp");
1073
+ function getBundledMcpPath(name) {
1074
+ validateFilePath(name);
1075
+ return join2(BUNDLED_MCP_DIR, name);
1076
+ }
1056
1077
  function getRawUrl(repo, tag, path) {
1057
1078
  return `${GITHUB_RAW}/${repo}/${tag}/${path}`;
1058
1079
  }
@@ -1094,7 +1115,7 @@ async function fetchLatestManifest() {
1094
1115
  }
1095
1116
  async function downloadFile(manifest, filePath) {
1096
1117
  validateFilePath(filePath);
1097
- const item = [...manifest.agents, ...manifest.commands, ...manifest.skills ?? []].find((i) => i.file === filePath);
1118
+ const item = [...manifest.agents, ...manifest.commands, ...manifest.skills ?? [], ...manifest.mcpServers ?? []].find((i) => i.file === filePath);
1098
1119
  const expectedHash = item?.sha256 ?? null;
1099
1120
  const cached = getCached(manifest.repo, manifest.tag, filePath, expectedHash);
1100
1121
  if (cached !== null)
@@ -1165,19 +1186,21 @@ function clearLockfile(opencodeRoot) {
1165
1186
  const lockfile = readLockfile(opencodeRoot);
1166
1187
  if (!lockfile) {
1167
1188
  writeLockfile(opencodeRoot, {
1168
- version: "0.4.0",
1189
+ version: "0.5.1",
1169
1190
  lockedAt: new Date().toISOString(),
1170
1191
  repo: "",
1171
1192
  tag: "",
1172
1193
  agents: {},
1173
1194
  commands: {},
1174
- skills: {}
1195
+ skills: {},
1196
+ mcpServers: {}
1175
1197
  });
1176
1198
  return;
1177
1199
  }
1178
1200
  lockfile.agents = {};
1179
1201
  lockfile.commands = {};
1180
1202
  lockfile.skills = {};
1203
+ lockfile.mcpServers = {};
1181
1204
  lockfile.lockedAt = new Date().toISOString();
1182
1205
  writeLockfile(opencodeRoot, lockfile);
1183
1206
  }
@@ -1272,6 +1295,8 @@ function upsertLockfile(paths, type, item, manifest, contentHash) {
1272
1295
  };
1273
1296
  if (!existing.skills)
1274
1297
  existing.skills = {};
1298
+ if (!existing.mcpServers)
1299
+ existing.mcpServers = {};
1275
1300
  existing[type][item.name] = {
1276
1301
  version: item.version,
1277
1302
  installedAt: new Date().toISOString(),
@@ -1310,6 +1335,51 @@ async function installSkill(item, manifest, paths) {
1310
1335
  copyDirRecursive(src, dest);
1311
1336
  upsertLockfile(paths, "skills", item, manifest, treeHash);
1312
1337
  }
1338
+ async function installMcpServer(item, manifest, paths) {
1339
+ const src = getBundledMcpPath(item.name);
1340
+ if (!existsSync3(src)) {
1341
+ throw new Error(`MCP server bundleado no encontrado: ${src}. ` + `¿Falta el directorio assets/mcp/${item.name}/?`);
1342
+ }
1343
+ const treeHash = computeTreeHash(src);
1344
+ if (item.sha256 && treeHash !== item.sha256) {
1345
+ throw new Error(`Tree hash inválido para MCP server "${item.name}"
1346
+ ` + ` esperado: ${item.sha256}
1347
+ ` + ` recibido: ${treeHash}`);
1348
+ }
1349
+ const dest = join4(paths.root, "mcp", item.name);
1350
+ if (existsSync3(dest)) {
1351
+ rmSync(dest, { recursive: true, force: true });
1352
+ }
1353
+ copyDirRecursive(src, dest);
1354
+ upsertLockfile(paths, "mcpServers", item, manifest, treeHash);
1355
+ const projectRoot = findProjectRoot();
1356
+ const configPath = findOpenCodeConfig(projectRoot);
1357
+ if (configPath) {
1358
+ const raw = readFileSync3(configPath, "utf-8");
1359
+ const config = JSON.parse(raw);
1360
+ if (!config.mcp)
1361
+ config.mcp = {};
1362
+ config.mcp[item.name] = {
1363
+ type: "local",
1364
+ command: ["node", `.opencode/mcp/${item.name}/index.js`],
1365
+ enabled: true
1366
+ };
1367
+ writeFileSync3(configPath, JSON.stringify(config, null, 2) + `
1368
+ `, "utf-8");
1369
+ }
1370
+ }
1371
+ function findOpenCodeConfig(projectRoot) {
1372
+ const candidates = ["opencode.json", "opencode.jsonc"];
1373
+ for (const name of candidates) {
1374
+ const full = join4(projectRoot, name);
1375
+ if (existsSync3(full))
1376
+ return full;
1377
+ }
1378
+ return null;
1379
+ }
1380
+ function isMcpServerInstalled(name, paths) {
1381
+ return existsSync3(join4(paths.root, "mcp", name, "index.js"));
1382
+ }
1313
1383
  function isAgentInstalled(name, paths) {
1314
1384
  return existsSync3(join4(paths.agents, `${name}.md`));
1315
1385
  }
@@ -1355,6 +1425,34 @@ function uninstallSkill(name, paths) {
1355
1425
  removeFromLockfile(paths.root, "skills", name);
1356
1426
  return true;
1357
1427
  }
1428
+ function uninstallMcpServer(name, paths) {
1429
+ const dirPath = join4(paths.root, "mcp", name);
1430
+ try {
1431
+ if (existsSync3(dirPath)) {
1432
+ rmSync(dirPath, { recursive: true, force: true });
1433
+ }
1434
+ } catch {
1435
+ return false;
1436
+ }
1437
+ const projectRoot = findProjectRoot();
1438
+ const configPath = findOpenCodeConfig(projectRoot);
1439
+ if (configPath) {
1440
+ try {
1441
+ const raw = readFileSync3(configPath, "utf-8");
1442
+ const config = JSON.parse(raw);
1443
+ if (config.mcp && config.mcp[name]) {
1444
+ delete config.mcp[name];
1445
+ if (Object.keys(config.mcp).length === 0) {
1446
+ delete config.mcp;
1447
+ }
1448
+ writeFileSync3(configPath, JSON.stringify(config, null, 2) + `
1449
+ `, "utf-8");
1450
+ }
1451
+ } catch {}
1452
+ }
1453
+ removeFromLockfile(paths.root, "mcpServers", name);
1454
+ return true;
1455
+ }
1358
1456
  function uninstallAll(paths) {
1359
1457
  const lockfile = readLockfile(paths.root);
1360
1458
  if (!lockfile)
@@ -1368,6 +1466,9 @@ function uninstallAll(paths) {
1368
1466
  for (const name of Object.keys(lockfile.skills ?? {})) {
1369
1467
  uninstallSkill(name, paths);
1370
1468
  }
1469
+ for (const name of Object.keys(lockfile.mcpServers ?? {})) {
1470
+ uninstallMcpServer(name, paths);
1471
+ }
1371
1472
  clearLockfile(paths.root);
1372
1473
  }
1373
1474
  function isCommandAvailable(cmd) {
@@ -1659,6 +1760,14 @@ function getUpdateCandidates(manifest, paths) {
1659
1760
  candidates.push({ type: "skills", item, installedVersion: installed });
1660
1761
  }
1661
1762
  }
1763
+ for (const item of manifest.mcpServers ?? []) {
1764
+ if (!isMcpServerInstalled(item.name, paths))
1765
+ continue;
1766
+ const installed = getInstalledVersion(lockfile, "mcpServers", item.name);
1767
+ if (installed !== item.version) {
1768
+ candidates.push({ type: "mcpServers", item, installedVersion: installed });
1769
+ }
1770
+ }
1662
1771
  return candidates;
1663
1772
  }
1664
1773
  function formatVersionDiff(from, to) {
@@ -1672,6 +1781,8 @@ function kindLabel(type) {
1672
1781
  return "command";
1673
1782
  case "skills":
1674
1783
  return "skill";
1784
+ case "mcpServers":
1785
+ return "MCP";
1675
1786
  }
1676
1787
  }
1677
1788
  async function doInstallAll(manifest, paths) {
@@ -1707,6 +1818,16 @@ async function doInstallAll(manifest, paths) {
1707
1818
  errors++;
1708
1819
  }
1709
1820
  }
1821
+ for (const mcp of manifest.mcpServers ?? []) {
1822
+ spin.start(`Instalando MCP server: ${mcp.name} (${mcp.version})`);
1823
+ try {
1824
+ await installMcpServer(mcp, manifest, paths);
1825
+ spin.stop(`MCP server instalado: ${mcp.name} (${mcp.version})`);
1826
+ } catch (e2) {
1827
+ spin.stop(`Error en ${mcp.name}: ${e2.message}`);
1828
+ errors++;
1829
+ }
1830
+ }
1710
1831
  if (errors === 0) {
1711
1832
  v2.info("Instalando stack de herramientas...");
1712
1833
  await doInstallStack();
@@ -1834,8 +1955,10 @@ async function doUpdate(manifest, paths) {
1834
1955
  await installAgent(item, manifest, paths);
1835
1956
  } else if (type === "commands") {
1836
1957
  await installCommand(item, manifest, paths);
1837
- } else {
1958
+ } else if (type === "skills") {
1838
1959
  await installSkill(item, manifest, paths);
1960
+ } else {
1961
+ await installMcpServer(item, manifest, paths);
1839
1962
  }
1840
1963
  spin.stop(`Actualizado: ${item.name} (${item.version})`);
1841
1964
  updated++;
@@ -1963,7 +2086,7 @@ async function runUpdateCommand() {
1963
2086
  }
1964
2087
  async function doUninstallTotal(paths) {
1965
2088
  const lockfile = readLockfile(paths.root);
1966
- if (!lockfile || Object.keys(lockfile.agents).length === 0 && Object.keys(lockfile.commands).length === 0 && Object.keys(lockfile.skills ?? {}).length === 0) {
2089
+ if (!lockfile || Object.keys(lockfile.agents).length === 0 && Object.keys(lockfile.commands).length === 0 && Object.keys(lockfile.skills ?? {}).length === 0 && Object.keys(lockfile.mcpServers ?? {}).length === 0) {
1967
2090
  v2.info("No hay nada instalado.");
1968
2091
  return;
1969
2092
  }
@@ -1977,6 +2100,9 @@ async function doUninstallTotal(paths) {
1977
2100
  for (const name of Object.keys(lockfile.skills ?? {})) {
1978
2101
  pathsToDelete.push(join5(paths.skills, name));
1979
2102
  }
2103
+ for (const name of Object.keys(lockfile.mcpServers ?? {})) {
2104
+ pathsToDelete.push(join5(paths.root, "mcp", name));
2105
+ }
1980
2106
  ye(pathsToDelete.join(`
1981
2107
  `), `Se eliminarán ${pathsToDelete.length} archivo(s) / directorio(s)`);
1982
2108
  const confirm = await me({