ostacky 0.8.8 → 0.9.0
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 +12 -12
- package/assets/agents/ostacky.md +7 -7
- package/assets/commands/install-stack.md +37 -29
- package/assets/docs/ostacky-reference.md +1 -1
- package/assets/mcp/ostacky-controller/controller-core.js +2 -0
- package/assets/mcp/ostacky-controller/index.js +248 -67
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/plugins/controller-core.ts +2 -0
- package/assets/plugins/engram.ts +648 -646
- package/assets/plugins/ostacky-plugin.ts +258 -214
- package/dist/cli.js +266 -96
- package/manifest.json +28 -28
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -621,31 +621,31 @@ var init_fs = __esm(() => {
|
|
|
621
621
|
var manifest_default;
|
|
622
622
|
var init_manifest = __esm(() => {
|
|
623
623
|
manifest_default = {
|
|
624
|
-
version: "0.
|
|
624
|
+
version: "0.9.0",
|
|
625
625
|
repo: "JaimeHoracio/Ostacky",
|
|
626
|
-
tag: "v0.
|
|
626
|
+
tag: "v0.9.0",
|
|
627
627
|
agents: [
|
|
628
628
|
{
|
|
629
629
|
name: "ostacky",
|
|
630
630
|
file: "assets/agents/ostacky.md",
|
|
631
|
-
description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.
|
|
632
|
-
version: "0.
|
|
633
|
-
sha256: "
|
|
631
|
+
description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.9.0: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
|
|
632
|
+
version: "0.9.0",
|
|
633
|
+
sha256: "ecd046b5eb1d604f5e88bc7e5c7c2bda7d6fe63786fad085a038253202c59eeb"
|
|
634
634
|
}
|
|
635
635
|
],
|
|
636
636
|
commands: [
|
|
637
637
|
{
|
|
638
638
|
name: "install-stack",
|
|
639
639
|
file: "assets/commands/install-stack.md",
|
|
640
|
-
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.
|
|
641
|
-
version: "0.
|
|
642
|
-
sha256: "
|
|
640
|
+
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.9.0: controller plugin como alma hard-gate, Context7 removido del stack.",
|
|
641
|
+
version: "0.9.0",
|
|
642
|
+
sha256: "5cdceea5d8c4ef8eb2d1dcc441ad05f87c8c666dcf9810eb0766c9446840b57c"
|
|
643
643
|
},
|
|
644
644
|
{
|
|
645
645
|
name: "opsx-sync",
|
|
646
646
|
file: "assets/commands/opsx-sync.md",
|
|
647
647
|
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
648
|
-
version: "0.
|
|
648
|
+
version: "0.9.0",
|
|
649
649
|
sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
|
|
650
650
|
}
|
|
651
651
|
],
|
|
@@ -653,15 +653,15 @@ var init_manifest = __esm(() => {
|
|
|
653
653
|
{
|
|
654
654
|
name: "ostacky-controller",
|
|
655
655
|
file: "assets/mcp/ostacky-controller/",
|
|
656
|
-
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.
|
|
657
|
-
version: "0.
|
|
658
|
-
sha256: "
|
|
656
|
+
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.9.0: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
|
|
657
|
+
version: "0.9.0",
|
|
658
|
+
sha256: "29857bfbe1b67a0cf1e7ee6215b991fe86ab3f2765feae8f099af5fac610afc1"
|
|
659
659
|
},
|
|
660
660
|
{
|
|
661
661
|
name: "openspec",
|
|
662
662
|
file: "assets/mcp/openspec/",
|
|
663
663
|
description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
|
|
664
|
-
version: "0.
|
|
664
|
+
version: "0.9.0",
|
|
665
665
|
sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
|
|
666
666
|
}
|
|
667
667
|
],
|
|
@@ -670,105 +670,105 @@ var init_manifest = __esm(() => {
|
|
|
670
670
|
name: "brainstorming",
|
|
671
671
|
file: "assets/skills/brainstorming/SKILL.md",
|
|
672
672
|
description: "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a implementación directa o openspec-propose) y open-exploration (exploración libre)",
|
|
673
|
-
version: "0.
|
|
673
|
+
version: "0.9.0",
|
|
674
674
|
sha256: "f1013a86a8f27914d85a716e23c7fd32db1786d93615e8b0834b13d184308549"
|
|
675
675
|
},
|
|
676
676
|
{
|
|
677
677
|
name: "execution-mode-evaluation",
|
|
678
678
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
679
679
|
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
680
|
-
version: "0.
|
|
680
|
+
version: "0.9.0",
|
|
681
681
|
sha256: "2158a6864c0912e868a9d720db7fd79441e6cc298e3320833d2527c9727c852e"
|
|
682
682
|
},
|
|
683
683
|
{
|
|
684
684
|
name: "tdd",
|
|
685
685
|
file: "assets/skills/tdd/SKILL.md",
|
|
686
686
|
description: "Skill de test-driven development (Superpowers)",
|
|
687
|
-
version: "0.
|
|
687
|
+
version: "0.9.0",
|
|
688
688
|
sha256: "f0ebfbbc43a8868d31a3fd2df37ae7f60fe8add09c6475862395a68269e5ff7f"
|
|
689
689
|
},
|
|
690
690
|
{
|
|
691
691
|
name: "subagent-driven-development",
|
|
692
692
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
693
693
|
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
694
|
-
version: "0.
|
|
694
|
+
version: "0.9.0",
|
|
695
695
|
sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
|
|
696
696
|
},
|
|
697
697
|
{
|
|
698
698
|
name: "dispatching-parallel-agents",
|
|
699
699
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
700
700
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
701
|
-
version: "0.
|
|
701
|
+
version: "0.9.0",
|
|
702
702
|
sha256: "7e7b712ef742d5631e302e6a71583e2bbbd33094da9281abfb84b43505fc3ef1"
|
|
703
703
|
},
|
|
704
704
|
{
|
|
705
705
|
name: "review",
|
|
706
706
|
file: "assets/skills/review/SKILL.md",
|
|
707
707
|
description: "Skill de revisión de código (Superpowers)",
|
|
708
|
-
version: "0.
|
|
708
|
+
version: "0.9.0",
|
|
709
709
|
sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
|
|
710
710
|
},
|
|
711
711
|
{
|
|
712
712
|
name: "receiving-code-review",
|
|
713
713
|
file: "assets/skills/receiving-code-review/SKILL.md",
|
|
714
714
|
description: "Skill de recibir y procesar feedback de code review",
|
|
715
|
-
version: "0.
|
|
715
|
+
version: "0.9.0",
|
|
716
716
|
sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
|
|
717
717
|
},
|
|
718
718
|
{
|
|
719
719
|
name: "openspec-propose",
|
|
720
720
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
721
721
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
722
|
-
version: "0.
|
|
722
|
+
version: "0.9.0",
|
|
723
723
|
sha256: "6a517f750f24c008208156a2d653e97ed30b1e53878aeac5ce394e573db0678f"
|
|
724
724
|
},
|
|
725
725
|
{
|
|
726
726
|
name: "openspec-apply-change",
|
|
727
727
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
728
728
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
729
|
-
version: "0.
|
|
729
|
+
version: "0.9.0",
|
|
730
730
|
sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
|
|
731
731
|
},
|
|
732
732
|
{
|
|
733
733
|
name: "openspec-archive-change",
|
|
734
734
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
735
735
|
description: "Skill de archivo de change (OpenSpec)",
|
|
736
|
-
version: "0.
|
|
736
|
+
version: "0.9.0",
|
|
737
737
|
sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
|
|
738
738
|
},
|
|
739
739
|
{
|
|
740
740
|
name: "openspec-explore",
|
|
741
741
|
file: "assets/skills/openspec-explore/SKILL.md",
|
|
742
742
|
description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
|
|
743
|
-
version: "0.
|
|
743
|
+
version: "0.9.0",
|
|
744
744
|
sha256: "9eafc3a8c693b8d9916867ef764d49e5222da717fde501c957bb3f6c312d2415"
|
|
745
745
|
},
|
|
746
746
|
{
|
|
747
747
|
name: "using-git-worktrees",
|
|
748
748
|
file: "assets/skills/using-git-worktrees/SKILL.md",
|
|
749
749
|
description: "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
750
|
-
version: "0.
|
|
750
|
+
version: "0.9.0",
|
|
751
751
|
sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
|
|
752
752
|
},
|
|
753
753
|
{
|
|
754
754
|
name: "using-superpowers",
|
|
755
755
|
file: "assets/skills/using-superpowers/SKILL.md",
|
|
756
756
|
description: "Skill de orquestación de Superpowers skills",
|
|
757
|
-
version: "0.
|
|
757
|
+
version: "0.9.0",
|
|
758
758
|
sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
|
|
759
759
|
},
|
|
760
760
|
{
|
|
761
761
|
name: "writing-skills",
|
|
762
762
|
file: "assets/skills/writing-skills/SKILL.md",
|
|
763
763
|
description: "Skill de creación y edición de skills",
|
|
764
|
-
version: "0.
|
|
764
|
+
version: "0.9.0",
|
|
765
765
|
sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
|
|
766
766
|
},
|
|
767
767
|
{
|
|
768
768
|
name: "graceful-degradation",
|
|
769
769
|
file: "assets/skills/graceful-degradation/SKILL.md",
|
|
770
770
|
description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
|
|
771
|
-
version: "0.
|
|
771
|
+
version: "0.9.0",
|
|
772
772
|
sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
|
|
773
773
|
}
|
|
774
774
|
]
|
|
@@ -1058,9 +1058,14 @@ function setMcpEntryAtProjectRoot(projectRoot, name, entry) {
|
|
|
1058
1058
|
const config = readOpenCodeConfig(configPath);
|
|
1059
1059
|
if (!config)
|
|
1060
1060
|
throw new Error(`Error parseando ${configPath}`);
|
|
1061
|
-
if (!config.mcp)
|
|
1061
|
+
if (!config.mcp || typeof config.mcp !== "object")
|
|
1062
1062
|
config.mcp = {};
|
|
1063
|
-
|
|
1063
|
+
const mcp = config.mcp;
|
|
1064
|
+
if (!mcp.servers || typeof mcp.servers !== "object")
|
|
1065
|
+
mcp.servers = {};
|
|
1066
|
+
mcp.servers[name] = entry;
|
|
1067
|
+
if (name in mcp && name !== "servers")
|
|
1068
|
+
delete mcp[name];
|
|
1064
1069
|
writeOpenCodeConfig(configPath, config);
|
|
1065
1070
|
}
|
|
1066
1071
|
function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
|
|
@@ -1079,16 +1084,30 @@ function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
|
|
|
1079
1084
|
};
|
|
1080
1085
|
}
|
|
1081
1086
|
let changed = false;
|
|
1082
|
-
if ("plugin" in config) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1087
|
+
if ("plugin" in config && Array.isArray(config.plugin)) {
|
|
1088
|
+
const entries = config.plugin;
|
|
1089
|
+
const kept = entries.filter((e) => !isSuperpowersPluginEntry(e));
|
|
1090
|
+
if (kept.length !== entries.length) {
|
|
1091
|
+
if (kept.length === 0)
|
|
1092
|
+
delete config.plugin;
|
|
1093
|
+
else
|
|
1094
|
+
config.plugin = kept;
|
|
1095
|
+
changed = true;
|
|
1096
|
+
}
|
|
1085
1097
|
}
|
|
1086
1098
|
if (changed) {
|
|
1087
1099
|
writeOpenCodeConfig(configPath, config);
|
|
1088
|
-
return { success: true, message: "Config actualizada (
|
|
1100
|
+
return { success: true, message: "Config actualizada (entradas superpowers eliminadas)" };
|
|
1089
1101
|
}
|
|
1090
1102
|
return { success: true, message: "Config de OpenCode ya está limpia" };
|
|
1091
1103
|
}
|
|
1104
|
+
function isSuperpowersPluginEntry(entry) {
|
|
1105
|
+
try {
|
|
1106
|
+
return JSON.stringify(entry).toLowerCase().includes("superpowers");
|
|
1107
|
+
} catch {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1092
1111
|
var init_config = __esm(() => {
|
|
1093
1112
|
init_fs();
|
|
1094
1113
|
});
|
|
@@ -1136,7 +1155,9 @@ function configureLocalTool(projectRoot, name, command) {
|
|
|
1136
1155
|
setMcpEntryAtProjectRoot(projectRoot, name, {
|
|
1137
1156
|
type: "local",
|
|
1138
1157
|
command,
|
|
1139
|
-
|
|
1158
|
+
disabled: false,
|
|
1159
|
+
timeout: { catalog: 30000, execution: 300000 },
|
|
1160
|
+
protocol: "legacy"
|
|
1140
1161
|
});
|
|
1141
1162
|
}
|
|
1142
1163
|
function buildLocalMcpCommand(executable, args, platform = process.platform) {
|
|
@@ -1495,10 +1516,112 @@ var init_stack = __esm(() => {
|
|
|
1495
1516
|
init_config();
|
|
1496
1517
|
init_github();
|
|
1497
1518
|
});
|
|
1519
|
+
|
|
1520
|
+
// src/doctor-v2.ts
|
|
1521
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
1522
|
+
import { join as join10, delimiter, isAbsolute } from "path";
|
|
1523
|
+
function checkDoctorV2(projectRoot) {
|
|
1524
|
+
const lines = [];
|
|
1525
|
+
const cwd = projectRoot;
|
|
1526
|
+
const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
|
|
1527
|
+
const pluginPaths = [
|
|
1528
|
+
join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
|
|
1529
|
+
join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
|
|
1530
|
+
join10(cwd, "assets", "plugins", "ostacky-plugin.ts")
|
|
1531
|
+
];
|
|
1532
|
+
const found = pluginPaths.find((p) => existsSync8(p));
|
|
1533
|
+
if (found) {
|
|
1534
|
+
try {
|
|
1535
|
+
const src = readFileSync6(found, "utf-8");
|
|
1536
|
+
const isV2 = src.includes("Plugin.define") && src.includes("@opencode/plugin") && !src.includes("@opencode-ai/plugin");
|
|
1537
|
+
if (isV2)
|
|
1538
|
+
lines.push(`✅ controller: plugin V2 (${found})`);
|
|
1539
|
+
else
|
|
1540
|
+
lines.push(`⚠️ controller: plugin legacy V1 en ${found} — no corre en V2, reinstalá con ostacky`);
|
|
1541
|
+
} catch {
|
|
1542
|
+
lines.push(`⚠️ controller: no se pudo leer ${found}`);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const configPath = findOpenCodeConfig(cwd);
|
|
1546
|
+
if (!configPath) {
|
|
1547
|
+
lines.push("ℹ️ config: no hay opencode.json ni opencode.jsonc");
|
|
1548
|
+
return lines;
|
|
1549
|
+
}
|
|
1550
|
+
let config = null;
|
|
1551
|
+
try {
|
|
1552
|
+
config = readOpenCodeConfig(configPath);
|
|
1553
|
+
} catch {
|
|
1554
|
+
lines.push(`⚠️ config: no se pudo parsear ${configPath}`);
|
|
1555
|
+
return lines;
|
|
1556
|
+
}
|
|
1557
|
+
if (!config) {
|
|
1558
|
+
lines.push(`⚠️ config: no se pudo parsear ${configPath}`);
|
|
1559
|
+
return lines;
|
|
1560
|
+
}
|
|
1561
|
+
const mcp = config.mcp ?? {};
|
|
1562
|
+
const servers = mcp.servers ?? {};
|
|
1563
|
+
const serverNames = Object.keys(servers);
|
|
1564
|
+
if (serverNames.length > 0)
|
|
1565
|
+
lines.push(`✅ mcp.servers: OK (${serverNames.length}: ${serverNames.join(", ")})`);
|
|
1566
|
+
for (const [key, value] of Object.entries(mcp)) {
|
|
1567
|
+
if (key === "servers" || key === "timeout")
|
|
1568
|
+
continue;
|
|
1569
|
+
if (value && typeof value === "object" && (("command" in value) || ("enabled" in value) || ("url" in value))) {
|
|
1570
|
+
lines.push(`⚠️ mcp.${key} legacy (forma V1) — mové a mcp.servers.${key} con disabled (install lo migra con restore ante fallo)`);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
const renames = {
|
|
1574
|
+
permission: "permissions[]",
|
|
1575
|
+
agent: "agents",
|
|
1576
|
+
command: "commands",
|
|
1577
|
+
reference: "references",
|
|
1578
|
+
provider: "providers",
|
|
1579
|
+
snapshot: "snapshots",
|
|
1580
|
+
attachment: "media"
|
|
1581
|
+
};
|
|
1582
|
+
for (const [oldKey, newKey] of Object.entries(renames)) {
|
|
1583
|
+
if (oldKey in config) {
|
|
1584
|
+
lines.push(`⚠️ ${oldKey} legacy ajeno — V2 usa ${newKey}; se normaliza en memoria, migrá cuando quieras (doctor no lo modifica)`);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
1588
|
+
if (!entry || typeof entry !== "object")
|
|
1589
|
+
continue;
|
|
1590
|
+
const cmd = entry.command;
|
|
1591
|
+
if (entry.type === "local" && Array.isArray(cmd) && typeof cmd[0] === "string") {
|
|
1592
|
+
const bin = cmd[0];
|
|
1593
|
+
const ok = isAbsolute(bin) ? existsSync8(bin) : lookupOnPath(bin) !== null;
|
|
1594
|
+
if (!ok)
|
|
1595
|
+
lines.push(`⚠️ mcp.servers.${name} command no existe (${bin}) — ¿proyecto movido? re-corré install`);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return lines;
|
|
1599
|
+
}
|
|
1600
|
+
function lookupOnPath(name) {
|
|
1601
|
+
try {
|
|
1602
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1603
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
1604
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
1605
|
+
if (!dir)
|
|
1606
|
+
continue;
|
|
1607
|
+
try {
|
|
1608
|
+
if (existsSync8(join10(dir, name + suffix)))
|
|
1609
|
+
return join10(dir, name + suffix);
|
|
1610
|
+
if (suffix && existsSync8(join10(dir, name)))
|
|
1611
|
+
return join10(dir, name);
|
|
1612
|
+
} catch {}
|
|
1613
|
+
}
|
|
1614
|
+
} catch {}
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
var init_doctor_v2 = __esm(() => {
|
|
1618
|
+
init_config();
|
|
1619
|
+
init_fs();
|
|
1620
|
+
});
|
|
1498
1621
|
// package.json
|
|
1499
1622
|
var package_default = {
|
|
1500
1623
|
name: "ostacky",
|
|
1501
|
-
version: "0.
|
|
1624
|
+
version: "0.9.0",
|
|
1502
1625
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1503
1626
|
type: "module",
|
|
1504
1627
|
bin: {
|
|
@@ -2525,7 +2648,9 @@ function createMcpConfigEntry(name, nodeExecutable, serverPath, statePath) {
|
|
|
2525
2648
|
const entry = {
|
|
2526
2649
|
type: "local",
|
|
2527
2650
|
command: [nodeExecutable, serverPath],
|
|
2528
|
-
|
|
2651
|
+
disabled: false,
|
|
2652
|
+
timeout: { catalog: 30000, execution: 300000 },
|
|
2653
|
+
protocol: "legacy"
|
|
2529
2654
|
};
|
|
2530
2655
|
if (name === "ostacky-controller" && statePath) {
|
|
2531
2656
|
entry.environment = { OSTACKY_STATE_PATH: statePath };
|
|
@@ -2675,7 +2800,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2675
2800
|
params: {
|
|
2676
2801
|
protocolVersion: "2025-03-26",
|
|
2677
2802
|
capabilities: {},
|
|
2678
|
-
clientInfo: { name: "ostacky-installer", version: "0.
|
|
2803
|
+
clientInfo: { name: "ostacky-installer", version: "0.9.0" }
|
|
2679
2804
|
}
|
|
2680
2805
|
});
|
|
2681
2806
|
});
|
|
@@ -2956,8 +3081,17 @@ function uninstallMcpServer(name, paths) {
|
|
|
2956
3081
|
const config = readOpenCodeConfig(configPath);
|
|
2957
3082
|
if (config) {
|
|
2958
3083
|
const mcp = config.mcp;
|
|
2959
|
-
if (mcp
|
|
2960
|
-
|
|
3084
|
+
if (mcp) {
|
|
3085
|
+
const servers = mcp.servers;
|
|
3086
|
+
if (servers && name in servers) {
|
|
3087
|
+
delete servers[name];
|
|
3088
|
+
if (Object.keys(servers).length === 0) {
|
|
3089
|
+
delete mcp.servers;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (name in mcp && name !== "servers") {
|
|
3093
|
+
delete mcp[name];
|
|
3094
|
+
}
|
|
2961
3095
|
if (Object.keys(mcp).length === 0) {
|
|
2962
3096
|
delete config.mcp;
|
|
2963
3097
|
}
|
|
@@ -3799,6 +3933,23 @@ function isOpencodeInstalled() {
|
|
|
3799
3933
|
return false;
|
|
3800
3934
|
}
|
|
3801
3935
|
}
|
|
3936
|
+
function parseOpencodeVersion(output) {
|
|
3937
|
+
const m = /v?(\d+)\.\d+\.\d+/.exec(output ?? "");
|
|
3938
|
+
if (!m)
|
|
3939
|
+
return null;
|
|
3940
|
+
const major = parseInt(m[1], 10);
|
|
3941
|
+
return Number.isNaN(major) ? null : major;
|
|
3942
|
+
}
|
|
3943
|
+
function getOpencodeVersion() {
|
|
3944
|
+
try {
|
|
3945
|
+
return execFileSync4("opencode", ["--version"], {
|
|
3946
|
+
encoding: "utf-8",
|
|
3947
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3948
|
+
}).trim();
|
|
3949
|
+
} catch {
|
|
3950
|
+
return null;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3802
3953
|
function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
|
|
3803
3954
|
if (platform === "win32") {
|
|
3804
3955
|
return {
|
|
@@ -3824,8 +3975,17 @@ function getOpencodeInstallCommand(platform = process.platform, _arch = process.
|
|
|
3824
3975
|
};
|
|
3825
3976
|
}
|
|
3826
3977
|
async function ensureOpencodeInstalled() {
|
|
3827
|
-
if (isOpencodeInstalled())
|
|
3978
|
+
if (isOpencodeInstalled()) {
|
|
3979
|
+
const raw = getOpencodeVersion();
|
|
3980
|
+
const major = raw ? parseOpencodeVersion(raw) : null;
|
|
3981
|
+
if (major !== null && major < 2) {
|
|
3982
|
+
v2.warn(`OpenCode V1 detectado (${raw}). Ostacky requiere OpenCode V2.`);
|
|
3983
|
+
ye(`El instalador V2 reemplaza el binario V1. Desinstalá V1 (package manager) e instalá V2, luego re-ejecutá.
|
|
3984
|
+
Docs: https://opencode.ai/v2/docs/migrate-v1`, "Actualización requerida");
|
|
3985
|
+
process.exit(1);
|
|
3986
|
+
}
|
|
3828
3987
|
return;
|
|
3988
|
+
}
|
|
3829
3989
|
const platform = process.platform;
|
|
3830
3990
|
const info = getOpencodeInstallCommand(platform);
|
|
3831
3991
|
v2.warn("OpenCode no detectado en este sistema.");
|
|
@@ -4242,8 +4402,8 @@ async function runUninstallMcpCommand(name, scope) {
|
|
|
4242
4402
|
|
|
4243
4403
|
// src/cli.ts
|
|
4244
4404
|
init_fs();
|
|
4245
|
-
import { existsSync as
|
|
4246
|
-
import { join as
|
|
4405
|
+
import { existsSync as existsSync9, statSync as statSync3, readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
4406
|
+
import { join as join11, dirname as dirname8 } from "node:path";
|
|
4247
4407
|
var HELP = `
|
|
4248
4408
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4249
4409
|
|
|
@@ -4314,8 +4474,8 @@ var argvNoScope = withoutScopeArgs(process.argv);
|
|
|
4314
4474
|
var [, , cmd, subcmd] = argvNoScope;
|
|
4315
4475
|
async function runDoctorCommand() {
|
|
4316
4476
|
const cwd = process.cwd();
|
|
4317
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4318
|
-
const statePath =
|
|
4477
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4478
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4319
4479
|
let hasError = false;
|
|
4320
4480
|
let hasWarn = false;
|
|
4321
4481
|
const check = (label, ok, warn = false) => {
|
|
@@ -4330,23 +4490,23 @@ async function runDoctorCommand() {
|
|
|
4330
4490
|
}
|
|
4331
4491
|
};
|
|
4332
4492
|
const pluginPaths = [
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4493
|
+
join11(cwd, "assets", "plugins", "ostacky-plugin.ts"),
|
|
4494
|
+
join11(opencodeDir, "plugins", "ostacky-plugin.ts"),
|
|
4495
|
+
join11(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
|
|
4496
|
+
join11(cwd, "assets", "plugins", "ostacky-controller.ts"),
|
|
4497
|
+
join11(opencodeDir, "plugins", "ostacky-controller.ts"),
|
|
4498
|
+
join11(cwd, ".opencode", "plugins", "ostacky-controller.ts")
|
|
4339
4499
|
];
|
|
4340
|
-
const pluginActive = pluginPaths.some((p) =>
|
|
4500
|
+
const pluginActive = pluginPaths.some((p) => existsSync9(p));
|
|
4341
4501
|
try {
|
|
4342
|
-
if (!
|
|
4502
|
+
if (!existsSync9(statePath)) {
|
|
4343
4503
|
if (pluginActive)
|
|
4344
4504
|
console.log(`✅ controller: plugin active (no state yet)`);
|
|
4345
4505
|
else
|
|
4346
4506
|
check("controller: state file missing", false, true);
|
|
4347
4507
|
} else {
|
|
4348
4508
|
const stat = statSync3(statePath);
|
|
4349
|
-
const raw =
|
|
4509
|
+
const raw = readFileSync7(statePath, "utf-8");
|
|
4350
4510
|
const parsed = JSON.parse(raw);
|
|
4351
4511
|
if (pluginActive) {
|
|
4352
4512
|
console.log(`✅ controller: plugin active (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`);
|
|
@@ -4410,15 +4570,15 @@ async function runDoctorCommand() {
|
|
|
4410
4570
|
check(`controller: ${e.message}`, false);
|
|
4411
4571
|
}
|
|
4412
4572
|
try {
|
|
4413
|
-
const lockPid =
|
|
4414
|
-
const lockTs =
|
|
4415
|
-
if (
|
|
4573
|
+
const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
|
|
4574
|
+
const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
|
|
4575
|
+
if (existsSync9(lockPid) || existsSync9(lockTs)) {
|
|
4416
4576
|
let ageStr = "";
|
|
4417
4577
|
try {
|
|
4418
|
-
const ts = parseInt(
|
|
4578
|
+
const ts = parseInt(readFileSync7(lockTs, "utf-8"), 10);
|
|
4419
4579
|
const age = Date.now() - ts;
|
|
4420
4580
|
ageStr = `${Math.floor(age / 1000)}s`;
|
|
4421
|
-
const pid =
|
|
4581
|
+
const pid = readFileSync7(lockPid, "utf-8").trim();
|
|
4422
4582
|
let alive = false;
|
|
4423
4583
|
try {
|
|
4424
4584
|
process.kill(parseInt(pid, 10), 0);
|
|
@@ -4436,15 +4596,15 @@ async function runDoctorCommand() {
|
|
|
4436
4596
|
}
|
|
4437
4597
|
const tools = ["codegraph", "engram"];
|
|
4438
4598
|
for (const t of tools) {
|
|
4439
|
-
const p =
|
|
4599
|
+
const p = join11(opencodeDir, "tools", t, "bin", t);
|
|
4440
4600
|
const pExe = p + ".exe";
|
|
4441
|
-
check(`tool ${t}: ${
|
|
4601
|
+
check(`tool ${t}: ${existsSync9(p) || existsSync9(pExe) ? "found" : "missing"}`, existsSync9(p) || existsSync9(pExe), true);
|
|
4442
4602
|
}
|
|
4443
4603
|
try {
|
|
4444
|
-
const manifest = JSON.parse(
|
|
4604
|
+
const manifest = JSON.parse(readFileSync7(join11(cwd, "manifest.json"), "utf-8"));
|
|
4445
4605
|
const expected = manifest.mcpServers?.find((x) => x.name === "ostacky-controller")?.sha256;
|
|
4446
4606
|
if (expected) {
|
|
4447
|
-
const actual = computeTreeHash(
|
|
4607
|
+
const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4448
4608
|
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4449
4609
|
if (expected !== actual)
|
|
4450
4610
|
console.log(" Run: bun run hash:update");
|
|
@@ -4452,22 +4612,32 @@ async function runDoctorCommand() {
|
|
|
4452
4612
|
} catch {
|
|
4453
4613
|
check("manifest: not found", false, true);
|
|
4454
4614
|
}
|
|
4455
|
-
|
|
4615
|
+
try {
|
|
4616
|
+
await Promise.resolve().then(() => init_doctor_v2());
|
|
4617
|
+
for (const line of checkDoctorV2(cwd)) {
|
|
4618
|
+
console.log(line);
|
|
4619
|
+
if (line.startsWith("⚠️") || line.startsWith("❌"))
|
|
4620
|
+
hasWarn = true;
|
|
4621
|
+
}
|
|
4622
|
+
} catch {
|
|
4623
|
+
check("doctor-v2: check failed", false, true);
|
|
4624
|
+
}
|
|
4625
|
+
if (existsSync9(statePath)) {
|
|
4456
4626
|
try {
|
|
4457
|
-
const s = JSON.parse(
|
|
4627
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4458
4628
|
if (s.allowedFiles || s.deniedFiles) {}
|
|
4459
4629
|
} catch {}
|
|
4460
4630
|
}
|
|
4461
4631
|
try {
|
|
4462
|
-
const cacheDir =
|
|
4463
|
-
if (!
|
|
4632
|
+
const cacheDir = join11(opencodeDir, "cache", "codegraph");
|
|
4633
|
+
if (!existsSync9(cacheDir)) {
|
|
4464
4634
|
console.log("ℹ️ cache: no cache dir yet (ok)");
|
|
4465
4635
|
} else {
|
|
4466
4636
|
const files = readdirSync3(cacheDir);
|
|
4467
4637
|
let total = 0;
|
|
4468
4638
|
for (const f of files) {
|
|
4469
4639
|
try {
|
|
4470
|
-
total += statSync3(
|
|
4640
|
+
total += statSync3(join11(cacheDir, f)).size;
|
|
4471
4641
|
} catch {}
|
|
4472
4642
|
}
|
|
4473
4643
|
const totalMB = (total / 1048576).toFixed(2);
|
|
@@ -4478,7 +4648,7 @@ async function runDoctorCommand() {
|
|
|
4478
4648
|
console.log(`✅ cache: OK (${files.length} files, ${totalMB}MB)`);
|
|
4479
4649
|
}
|
|
4480
4650
|
try {
|
|
4481
|
-
const s = JSON.parse(
|
|
4651
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4482
4652
|
if (s.cacheHitCount !== undefined) {
|
|
4483
4653
|
console.log(`ℹ️ cacheHitCount=${s.cacheHitCount} cacheMissCount=${s.cacheMissCount || 0} tokenSavingEstimate=${s.tokenSavingEstimate || 0}`);
|
|
4484
4654
|
}
|
|
@@ -4488,28 +4658,28 @@ async function runDoctorCommand() {
|
|
|
4488
4658
|
console.log(`⚠️ cache: check failed ${e.message}`);
|
|
4489
4659
|
}
|
|
4490
4660
|
try {
|
|
4491
|
-
const coreSrc =
|
|
4492
|
-
const coreMcp =
|
|
4493
|
-
const corePlugin =
|
|
4494
|
-
const coreSync =
|
|
4661
|
+
const coreSrc = join11(cwd, "src", "controller-core.ts");
|
|
4662
|
+
const coreMcp = join11(cwd, "assets", "mcp", "ostacky-controller", "controller-core.js");
|
|
4663
|
+
const corePlugin = join11(cwd, "assets", "plugins", "controller-core.ts");
|
|
4664
|
+
const coreSync = existsSync9(coreSrc) && existsSync9(coreMcp) && existsSync9(corePlugin);
|
|
4495
4665
|
if (coreSync) {
|
|
4496
|
-
const srcHash =
|
|
4497
|
-
const mcpHash =
|
|
4498
|
-
const ok =
|
|
4666
|
+
const srcHash = readFileSync7(coreSrc, "utf-8").slice(0, 100);
|
|
4667
|
+
const mcpHash = readFileSync7(coreMcp, "utf-8").slice(0, 100);
|
|
4668
|
+
const ok = readFileSync7(coreMcp, "utf-8").includes("STATES") && readFileSync7(corePlugin, "utf-8").includes("STATES");
|
|
4499
4669
|
check(`controller-core: synced (${ok ? "STATES present" : "mismatch"})`, ok);
|
|
4500
4670
|
if (!ok)
|
|
4501
4671
|
console.log(" Run: bun run scripts/sync-controller-core.ts (if exists) or copy src/controller-core.ts");
|
|
4502
4672
|
} else {
|
|
4503
4673
|
check("controller-core: synced", false, true);
|
|
4504
4674
|
}
|
|
4505
|
-
const auditPath =
|
|
4506
|
-
if (
|
|
4675
|
+
const auditPath = join11(opencodeDir, "ostacky-audit.jsonl");
|
|
4676
|
+
if (existsSync9(auditPath)) {
|
|
4507
4677
|
const sz = statSync3(auditPath).size;
|
|
4508
|
-
const lines =
|
|
4678
|
+
const lines = readFileSync7(auditPath, "utf-8").split(`
|
|
4509
4679
|
`).filter(Boolean).length;
|
|
4510
4680
|
check(`audit: jsonl ${lines} entries, ${(sz / 1024).toFixed(1)}KB`, sz < 512000);
|
|
4511
|
-
if (
|
|
4512
|
-
const s = JSON.parse(
|
|
4681
|
+
if (existsSync9(statePath)) {
|
|
4682
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4513
4683
|
const stateSize = statSync3(statePath).size;
|
|
4514
4684
|
check(`state size <50KB (${(stateSize / 1024).toFixed(1)}KB)`, stateSize < 51200);
|
|
4515
4685
|
if (s.audit && s.audit.length > 20)
|
|
@@ -4520,16 +4690,16 @@ async function runDoctorCommand() {
|
|
|
4520
4690
|
} else {
|
|
4521
4691
|
console.log("ℹ️ audit: jsonl not yet created (will be created on next audit)");
|
|
4522
4692
|
}
|
|
4523
|
-
const ostackyMd =
|
|
4693
|
+
const ostackyMd = readFileSync7(join11(cwd, "assets", "agents", "ostacky.md"), "utf-8");
|
|
4524
4694
|
const hasLevels = ostackyMd.includes("LEVEL_THRESHOLDS") || ostackyMd.includes("classifyLevel");
|
|
4525
4695
|
check("levels: unified via tiered.ts", hasLevels, true);
|
|
4526
4696
|
const hasHonesty = ostackyMd.includes("Principios de honestidad");
|
|
4527
4697
|
check("honesty: 7 SHALL", hasHonesty);
|
|
4528
4698
|
if (!hasHonesty)
|
|
4529
4699
|
console.log(" Expected: ## Principios de honestidad (SHALL) in ostacky.md");
|
|
4530
|
-
const pluginPath =
|
|
4531
|
-
if (
|
|
4532
|
-
const plugin =
|
|
4700
|
+
const pluginPath = join11(cwd, "assets", "plugins", "ostacky-plugin.ts");
|
|
4701
|
+
if (existsSync9(pluginPath)) {
|
|
4702
|
+
const plugin = readFileSync7(pluginPath, "utf-8");
|
|
4533
4703
|
const hasSpecGuard = plugin.includes("getDiscoverySnapshot") && plugin.includes("specSnapshot");
|
|
4534
4704
|
const hasNoOverwrite = plugin.includes("No edites sin Read fresco") || plugin.includes("specSnapshot");
|
|
4535
4705
|
check("spec: no-overwrite guard", hasNoOverwrite, true);
|
|
@@ -4540,12 +4710,12 @@ async function runDoctorCommand() {
|
|
|
4540
4710
|
console.log(`⚠️ honesty/spec checks failed: ${e.message}`);
|
|
4541
4711
|
}
|
|
4542
4712
|
try {
|
|
4543
|
-
const giPath =
|
|
4544
|
-
if (!
|
|
4713
|
+
const giPath = join11(cwd, ".gitignore");
|
|
4714
|
+
if (!existsSync9(giPath)) {
|
|
4545
4715
|
console.log("⚠️ .gitignore: missing (run npx ostacky install --scope local to create)");
|
|
4546
4716
|
hasWarn = true;
|
|
4547
4717
|
} else {
|
|
4548
|
-
const gi =
|
|
4718
|
+
const gi = readFileSync7(giPath, "utf-8");
|
|
4549
4719
|
const needed = [
|
|
4550
4720
|
".opencode/tools/",
|
|
4551
4721
|
".opencode/cache/",
|
|
@@ -4565,12 +4735,12 @@ async function runDoctorCommand() {
|
|
|
4565
4735
|
}
|
|
4566
4736
|
} catch {}
|
|
4567
4737
|
try {
|
|
4568
|
-
const secPath =
|
|
4569
|
-
if (!
|
|
4738
|
+
const secPath = join11(cwd, "src", "security.ts");
|
|
4739
|
+
if (!existsSync9(secPath)) {
|
|
4570
4740
|
console.log("⚠️ src/security.ts: missing (source-of-truth)");
|
|
4571
4741
|
hasWarn = true;
|
|
4572
4742
|
} else {
|
|
4573
|
-
const sec =
|
|
4743
|
+
const sec = readFileSync7(secPath, "utf-8");
|
|
4574
4744
|
const hasSensitiveDefault = sec.includes("SENSITIVE_DEFAULT");
|
|
4575
4745
|
const hasBashRe = sec.includes("BASH_SENSITIVE_RE");
|
|
4576
4746
|
const hasIsSensitive = sec.includes("function isSensitive");
|
|
@@ -4584,7 +4754,7 @@ async function runDoctorCommand() {
|
|
|
4584
4754
|
}
|
|
4585
4755
|
} catch {}
|
|
4586
4756
|
try {
|
|
4587
|
-
const s = JSON.parse(
|
|
4757
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4588
4758
|
if (s.sensitiveAccess?.blockedAttempts > 0) {
|
|
4589
4759
|
console.log(`ℹ️ sensitiveAccess: blockedAttempts includes bash (${s.sensitiveAccess.blockedAttempts})`);
|
|
4590
4760
|
}
|
|
@@ -4597,14 +4767,14 @@ async function runDoctorCommand() {
|
|
|
4597
4767
|
async function runStatusCommand(args) {
|
|
4598
4768
|
const isJson = args.includes("--json");
|
|
4599
4769
|
const cwd = process.cwd();
|
|
4600
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4601
|
-
const statePath =
|
|
4602
|
-
if (!
|
|
4770
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4771
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4772
|
+
if (!existsSync9(statePath)) {
|
|
4603
4773
|
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4604
4774
|
return;
|
|
4605
4775
|
}
|
|
4606
4776
|
try {
|
|
4607
|
-
const parsed = JSON.parse(
|
|
4777
|
+
const parsed = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4608
4778
|
const completed = Object.values(parsed.tasks || {}).filter((t) => t.status === "COMPLETED").length;
|
|
4609
4779
|
const expected = parsed.expectedTaskCount ?? parsed.expectedTasks?.length ?? Object.keys(parsed.tasks || {}).length;
|
|
4610
4780
|
const degraded = parsed.degraded ? " degraded" : "";
|