ostacky 0.8.8 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/assets/agents/ostacky.md +7 -7
- package/assets/commands/install-stack.md +38 -30
- 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 +288 -108
- package/manifest.json +28 -28
- package/package.json +2 -2
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.1",
|
|
625
625
|
repo: "JaimeHoracio/Ostacky",
|
|
626
|
-
tag: "v0.
|
|
626
|
+
tag: "v0.9.1",
|
|
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.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
|
|
632
|
+
version: "0.9.1",
|
|
633
|
+
sha256: "8186994cc9d92585310d8f4c67255e5169f1f3b3d7fc5b3563c2fa79f1203d24"
|
|
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.1: controller plugin como alma hard-gate, Context7 removido del stack.",
|
|
641
|
+
version: "0.9.1",
|
|
642
|
+
sha256: "af62710619308920f7be8743ed43c726d3591ba2af6535c3cc3ca6db95ad9715"
|
|
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.1",
|
|
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.1: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
|
|
657
|
+
version: "0.9.1",
|
|
658
|
+
sha256: "d5d4712e5d823186c267f63cea776feddec4700f1b15286796586efb60c65241"
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
772
772
|
sha256: "363449bd9e6f800a0a3168667cf78f767fb105d86e4a4adadcc649d418139303"
|
|
773
773
|
}
|
|
774
774
|
]
|
|
@@ -820,6 +820,10 @@ function getBundledMcpPath(name) {
|
|
|
820
820
|
validateFilePath(name);
|
|
821
821
|
return join3(BUNDLED_MCP_DIR, name);
|
|
822
822
|
}
|
|
823
|
+
function getBundledExpectedHash(category, name) {
|
|
824
|
+
const items = manifest_default[category] ?? [];
|
|
825
|
+
return items.find((item) => item.name === name)?.sha256 ?? null;
|
|
826
|
+
}
|
|
823
827
|
function getRawUrl(repo, tag, path) {
|
|
824
828
|
return `${GITHUB_RAW}/${repo}/${tag}/${path}`;
|
|
825
829
|
}
|
|
@@ -1058,9 +1062,14 @@ function setMcpEntryAtProjectRoot(projectRoot, name, entry) {
|
|
|
1058
1062
|
const config = readOpenCodeConfig(configPath);
|
|
1059
1063
|
if (!config)
|
|
1060
1064
|
throw new Error(`Error parseando ${configPath}`);
|
|
1061
|
-
if (!config.mcp)
|
|
1065
|
+
if (!config.mcp || typeof config.mcp !== "object")
|
|
1062
1066
|
config.mcp = {};
|
|
1063
|
-
|
|
1067
|
+
const mcp = config.mcp;
|
|
1068
|
+
if (!mcp.servers || typeof mcp.servers !== "object")
|
|
1069
|
+
mcp.servers = {};
|
|
1070
|
+
mcp.servers[name] = entry;
|
|
1071
|
+
if (name in mcp && name !== "servers")
|
|
1072
|
+
delete mcp[name];
|
|
1064
1073
|
writeOpenCodeConfig(configPath, config);
|
|
1065
1074
|
}
|
|
1066
1075
|
function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
|
|
@@ -1079,16 +1088,30 @@ function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
|
|
|
1079
1088
|
};
|
|
1080
1089
|
}
|
|
1081
1090
|
let changed = false;
|
|
1082
|
-
if ("plugin" in config) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1091
|
+
if ("plugin" in config && Array.isArray(config.plugin)) {
|
|
1092
|
+
const entries = config.plugin;
|
|
1093
|
+
const kept = entries.filter((e) => !isSuperpowersPluginEntry(e));
|
|
1094
|
+
if (kept.length !== entries.length) {
|
|
1095
|
+
if (kept.length === 0)
|
|
1096
|
+
delete config.plugin;
|
|
1097
|
+
else
|
|
1098
|
+
config.plugin = kept;
|
|
1099
|
+
changed = true;
|
|
1100
|
+
}
|
|
1085
1101
|
}
|
|
1086
1102
|
if (changed) {
|
|
1087
1103
|
writeOpenCodeConfig(configPath, config);
|
|
1088
|
-
return { success: true, message: "Config actualizada (
|
|
1104
|
+
return { success: true, message: "Config actualizada (entradas superpowers eliminadas)" };
|
|
1089
1105
|
}
|
|
1090
1106
|
return { success: true, message: "Config de OpenCode ya está limpia" };
|
|
1091
1107
|
}
|
|
1108
|
+
function isSuperpowersPluginEntry(entry) {
|
|
1109
|
+
try {
|
|
1110
|
+
return JSON.stringify(entry).toLowerCase().includes("superpowers");
|
|
1111
|
+
} catch {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1092
1115
|
var init_config = __esm(() => {
|
|
1093
1116
|
init_fs();
|
|
1094
1117
|
});
|
|
@@ -1136,7 +1159,9 @@ function configureLocalTool(projectRoot, name, command) {
|
|
|
1136
1159
|
setMcpEntryAtProjectRoot(projectRoot, name, {
|
|
1137
1160
|
type: "local",
|
|
1138
1161
|
command,
|
|
1139
|
-
|
|
1162
|
+
disabled: false,
|
|
1163
|
+
timeout: { catalog: 30000, execution: 300000 },
|
|
1164
|
+
protocol: "legacy"
|
|
1140
1165
|
});
|
|
1141
1166
|
}
|
|
1142
1167
|
function buildLocalMcpCommand(executable, args, platform = process.platform) {
|
|
@@ -1495,10 +1520,112 @@ var init_stack = __esm(() => {
|
|
|
1495
1520
|
init_config();
|
|
1496
1521
|
init_github();
|
|
1497
1522
|
});
|
|
1523
|
+
|
|
1524
|
+
// src/doctor-v2.ts
|
|
1525
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
1526
|
+
import { join as join10, delimiter, isAbsolute } from "path";
|
|
1527
|
+
function checkDoctorV2(projectRoot) {
|
|
1528
|
+
const lines = [];
|
|
1529
|
+
const cwd = projectRoot;
|
|
1530
|
+
const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
|
|
1531
|
+
const pluginPaths = [
|
|
1532
|
+
join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
|
|
1533
|
+
join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
|
|
1534
|
+
join10(cwd, "assets", "plugins", "ostacky-plugin.ts")
|
|
1535
|
+
];
|
|
1536
|
+
const found = pluginPaths.find((p) => existsSync8(p));
|
|
1537
|
+
if (found) {
|
|
1538
|
+
try {
|
|
1539
|
+
const src = readFileSync6(found, "utf-8");
|
|
1540
|
+
const isV2 = src.includes("Plugin.define") && src.includes("@opencode/plugin") && !src.includes("@opencode-ai/plugin");
|
|
1541
|
+
if (isV2)
|
|
1542
|
+
lines.push(`✅ controller: plugin V2 (${found})`);
|
|
1543
|
+
else
|
|
1544
|
+
lines.push(`⚠️ controller: plugin legacy V1 en ${found} — no corre en V2, reinstalá con ostacky`);
|
|
1545
|
+
} catch {
|
|
1546
|
+
lines.push(`⚠️ controller: no se pudo leer ${found}`);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
const configPath = findOpenCodeConfig(cwd);
|
|
1550
|
+
if (!configPath) {
|
|
1551
|
+
lines.push("ℹ️ config: no hay opencode.json ni opencode.jsonc");
|
|
1552
|
+
return lines;
|
|
1553
|
+
}
|
|
1554
|
+
let config = null;
|
|
1555
|
+
try {
|
|
1556
|
+
config = readOpenCodeConfig(configPath);
|
|
1557
|
+
} catch {
|
|
1558
|
+
lines.push(`⚠️ config: no se pudo parsear ${configPath}`);
|
|
1559
|
+
return lines;
|
|
1560
|
+
}
|
|
1561
|
+
if (!config) {
|
|
1562
|
+
lines.push(`⚠️ config: no se pudo parsear ${configPath}`);
|
|
1563
|
+
return lines;
|
|
1564
|
+
}
|
|
1565
|
+
const mcp = config.mcp ?? {};
|
|
1566
|
+
const servers = mcp.servers ?? {};
|
|
1567
|
+
const serverNames = Object.keys(servers);
|
|
1568
|
+
if (serverNames.length > 0)
|
|
1569
|
+
lines.push(`✅ mcp.servers: OK (${serverNames.length}: ${serverNames.join(", ")})`);
|
|
1570
|
+
for (const [key, value] of Object.entries(mcp)) {
|
|
1571
|
+
if (key === "servers" || key === "timeout")
|
|
1572
|
+
continue;
|
|
1573
|
+
if (value && typeof value === "object" && (("command" in value) || ("enabled" in value) || ("url" in value))) {
|
|
1574
|
+
lines.push(`⚠️ mcp.${key} legacy (forma V1) — mové a mcp.servers.${key} con disabled (install lo migra con restore ante fallo)`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const renames = {
|
|
1578
|
+
permission: "permissions[]",
|
|
1579
|
+
agent: "agents",
|
|
1580
|
+
command: "commands",
|
|
1581
|
+
reference: "references",
|
|
1582
|
+
provider: "providers",
|
|
1583
|
+
snapshot: "snapshots",
|
|
1584
|
+
attachment: "media"
|
|
1585
|
+
};
|
|
1586
|
+
for (const [oldKey, newKey] of Object.entries(renames)) {
|
|
1587
|
+
if (oldKey in config) {
|
|
1588
|
+
lines.push(`⚠️ ${oldKey} legacy ajeno — V2 usa ${newKey}; se normaliza en memoria, migrá cuando quieras (doctor no lo modifica)`);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
1592
|
+
if (!entry || typeof entry !== "object")
|
|
1593
|
+
continue;
|
|
1594
|
+
const cmd = entry.command;
|
|
1595
|
+
if (entry.type === "local" && Array.isArray(cmd) && typeof cmd[0] === "string") {
|
|
1596
|
+
const bin = cmd[0];
|
|
1597
|
+
const ok = isAbsolute(bin) ? existsSync8(bin) : lookupOnPath(bin) !== null;
|
|
1598
|
+
if (!ok)
|
|
1599
|
+
lines.push(`⚠️ mcp.servers.${name} command no existe (${bin}) — ¿proyecto movido? re-corré install`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return lines;
|
|
1603
|
+
}
|
|
1604
|
+
function lookupOnPath(name) {
|
|
1605
|
+
try {
|
|
1606
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1607
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
1608
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
1609
|
+
if (!dir)
|
|
1610
|
+
continue;
|
|
1611
|
+
try {
|
|
1612
|
+
if (existsSync8(join10(dir, name + suffix)))
|
|
1613
|
+
return join10(dir, name + suffix);
|
|
1614
|
+
if (suffix && existsSync8(join10(dir, name)))
|
|
1615
|
+
return join10(dir, name);
|
|
1616
|
+
} catch {}
|
|
1617
|
+
}
|
|
1618
|
+
} catch {}
|
|
1619
|
+
return null;
|
|
1620
|
+
}
|
|
1621
|
+
var init_doctor_v2 = __esm(() => {
|
|
1622
|
+
init_config();
|
|
1623
|
+
init_fs();
|
|
1624
|
+
});
|
|
1498
1625
|
// package.json
|
|
1499
1626
|
var package_default = {
|
|
1500
1627
|
name: "ostacky",
|
|
1501
|
-
version: "0.
|
|
1628
|
+
version: "0.9.1",
|
|
1502
1629
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1503
1630
|
type: "module",
|
|
1504
1631
|
bin: {
|
|
@@ -1532,7 +1659,7 @@ var package_default = {
|
|
|
1532
1659
|
],
|
|
1533
1660
|
license: "MIT",
|
|
1534
1661
|
dependencies: {
|
|
1535
|
-
"@clack/prompts": "^0.9.
|
|
1662
|
+
"@clack/prompts": "^0.9.1"
|
|
1536
1663
|
},
|
|
1537
1664
|
devDependencies: {
|
|
1538
1665
|
"@types/bun": "latest",
|
|
@@ -2525,7 +2652,9 @@ function createMcpConfigEntry(name, nodeExecutable, serverPath, statePath) {
|
|
|
2525
2652
|
const entry = {
|
|
2526
2653
|
type: "local",
|
|
2527
2654
|
command: [nodeExecutable, serverPath],
|
|
2528
|
-
|
|
2655
|
+
disabled: false,
|
|
2656
|
+
timeout: { catalog: 30000, execution: 300000 },
|
|
2657
|
+
protocol: "legacy"
|
|
2529
2658
|
};
|
|
2530
2659
|
if (name === "ostacky-controller" && statePath) {
|
|
2531
2660
|
entry.environment = { OSTACKY_STATE_PATH: statePath };
|
|
@@ -2675,7 +2804,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2675
2804
|
params: {
|
|
2676
2805
|
protocolVersion: "2025-03-26",
|
|
2677
2806
|
capabilities: {},
|
|
2678
|
-
clientInfo: { name: "ostacky-installer", version: "0.
|
|
2807
|
+
clientInfo: { name: "ostacky-installer", version: "0.9.1" }
|
|
2679
2808
|
}
|
|
2680
2809
|
});
|
|
2681
2810
|
});
|
|
@@ -2743,6 +2872,20 @@ function upsertLockfile(paths, type, item, manifest, contentHash) {
|
|
|
2743
2872
|
existing.lockedAt = new Date().toISOString();
|
|
2744
2873
|
writeLockfile(paths.root, existing);
|
|
2745
2874
|
}
|
|
2875
|
+
function assertBundledTreeHash(category, name, actualHash) {
|
|
2876
|
+
const expected = getBundledExpectedHash(category, name);
|
|
2877
|
+
if (expected && actualHash !== expected) {
|
|
2878
|
+
const labels = {
|
|
2879
|
+
skills: "skill",
|
|
2880
|
+
mcpServers: "MCP server",
|
|
2881
|
+
agents: "agente",
|
|
2882
|
+
commands: "command"
|
|
2883
|
+
};
|
|
2884
|
+
throw new Error(`Tree hash inválido para ${labels[category] ?? category} "${name}"
|
|
2885
|
+
esperado: ${expected}
|
|
2886
|
+
recibido: ${actualHash}`);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2746
2889
|
function readBundledAsset(relativePath) {
|
|
2747
2890
|
const fullPath = join6(PACKAGE_ROOT, relativePath);
|
|
2748
2891
|
if (!existsSync5(fullPath))
|
|
@@ -2783,11 +2926,7 @@ async function installSkill(item, manifest, paths) {
|
|
|
2783
2926
|
throw new Error(`Skill bundleada no encontrada: ${src}. ¿Falta el directorio assets/skills/${item.name}/?`);
|
|
2784
2927
|
}
|
|
2785
2928
|
const treeHash = computeTreeHash(src);
|
|
2786
|
-
|
|
2787
|
-
throw new Error(`Tree hash inválido para skill "${item.name}"
|
|
2788
|
-
esperado: ${item.sha256}
|
|
2789
|
-
recibido: ${treeHash}`);
|
|
2790
|
-
}
|
|
2929
|
+
assertBundledTreeHash("skills", item.name, treeHash);
|
|
2791
2930
|
const dest = join6(paths.skills, item.name);
|
|
2792
2931
|
if (existsSync5(dest)) {
|
|
2793
2932
|
rmSync3(dest, { recursive: true, force: true });
|
|
@@ -2824,11 +2963,7 @@ async function installMcpServer(item, manifest, paths) {
|
|
|
2824
2963
|
throw new Error(`MCP server bundleado no encontrado: ${src}. ` + `¿Falta el directorio assets/mcp/${item.name}/?`);
|
|
2825
2964
|
}
|
|
2826
2965
|
const treeHash = computeTreeHash(src);
|
|
2827
|
-
|
|
2828
|
-
throw new Error(`Tree hash inválido para MCP server "${item.name}"
|
|
2829
|
-
esperado: ${item.sha256}
|
|
2830
|
-
recibido: ${treeHash}`);
|
|
2831
|
-
}
|
|
2966
|
+
assertBundledTreeHash("mcpServers", item.name, treeHash);
|
|
2832
2967
|
const nodeExecutable = getVerifiedNodeExecutable();
|
|
2833
2968
|
const projectRoot = dirname4(paths.root);
|
|
2834
2969
|
const dest = join6(paths.mcp, item.name);
|
|
@@ -2956,8 +3091,17 @@ function uninstallMcpServer(name, paths) {
|
|
|
2956
3091
|
const config = readOpenCodeConfig(configPath);
|
|
2957
3092
|
if (config) {
|
|
2958
3093
|
const mcp = config.mcp;
|
|
2959
|
-
if (mcp
|
|
2960
|
-
|
|
3094
|
+
if (mcp) {
|
|
3095
|
+
const servers = mcp.servers;
|
|
3096
|
+
if (servers && name in servers) {
|
|
3097
|
+
delete servers[name];
|
|
3098
|
+
if (Object.keys(servers).length === 0) {
|
|
3099
|
+
delete mcp.servers;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
if (name in mcp && name !== "servers") {
|
|
3103
|
+
delete mcp[name];
|
|
3104
|
+
}
|
|
2961
3105
|
if (Object.keys(mcp).length === 0) {
|
|
2962
3106
|
delete config.mcp;
|
|
2963
3107
|
}
|
|
@@ -3034,7 +3178,7 @@ function printPostInstallSteps() {
|
|
|
3034
3178
|
"Cerrá y reiniciá OpenCode si ya estaba corriendo (los MCP no recargan su config en caliente):",
|
|
3035
3179
|
" TUI → opencode",
|
|
3036
3180
|
" Web → opencode web --port 4096",
|
|
3037
|
-
"
|
|
3181
|
+
"Seleccioná el agente: `/agents` → `ostacky` (o Shift+Tab / Ctrl+X A)",
|
|
3038
3182
|
"Skills bundleadas en .opencode/skills/ — revisá cuáles activás",
|
|
3039
3183
|
"¿Errores en el stack? Ejecutá /install-stack desde el chat de OpenCode"
|
|
3040
3184
|
].join(`
|
|
@@ -3799,6 +3943,23 @@ function isOpencodeInstalled() {
|
|
|
3799
3943
|
return false;
|
|
3800
3944
|
}
|
|
3801
3945
|
}
|
|
3946
|
+
function parseOpencodeVersion(output) {
|
|
3947
|
+
const m = /v?(\d+)\.\d+\.\d+/.exec(output ?? "");
|
|
3948
|
+
if (!m)
|
|
3949
|
+
return null;
|
|
3950
|
+
const major = parseInt(m[1], 10);
|
|
3951
|
+
return Number.isNaN(major) ? null : major;
|
|
3952
|
+
}
|
|
3953
|
+
function getOpencodeVersion() {
|
|
3954
|
+
try {
|
|
3955
|
+
return execFileSync4("opencode", ["--version"], {
|
|
3956
|
+
encoding: "utf-8",
|
|
3957
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3958
|
+
}).trim();
|
|
3959
|
+
} catch {
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3802
3963
|
function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
|
|
3803
3964
|
if (platform === "win32") {
|
|
3804
3965
|
return {
|
|
@@ -3824,8 +3985,17 @@ function getOpencodeInstallCommand(platform = process.platform, _arch = process.
|
|
|
3824
3985
|
};
|
|
3825
3986
|
}
|
|
3826
3987
|
async function ensureOpencodeInstalled() {
|
|
3827
|
-
if (isOpencodeInstalled())
|
|
3988
|
+
if (isOpencodeInstalled()) {
|
|
3989
|
+
const raw = getOpencodeVersion();
|
|
3990
|
+
const major = raw ? parseOpencodeVersion(raw) : null;
|
|
3991
|
+
if (major !== null && major < 2) {
|
|
3992
|
+
v2.warn(`OpenCode V1 detectado (${raw}). Ostacky requiere OpenCode V2.`);
|
|
3993
|
+
ye(`El instalador V2 reemplaza el binario V1. Desinstalá V1 (package manager) e instalá V2, luego re-ejecutá.
|
|
3994
|
+
Docs: https://opencode.ai/v2/docs/migrate-v1`, "Actualización requerida");
|
|
3995
|
+
process.exit(1);
|
|
3996
|
+
}
|
|
3828
3997
|
return;
|
|
3998
|
+
}
|
|
3829
3999
|
const platform = process.platform;
|
|
3830
4000
|
const info = getOpencodeInstallCommand(platform);
|
|
3831
4001
|
v2.warn("OpenCode no detectado en este sistema.");
|
|
@@ -4242,8 +4412,8 @@ async function runUninstallMcpCommand(name, scope) {
|
|
|
4242
4412
|
|
|
4243
4413
|
// src/cli.ts
|
|
4244
4414
|
init_fs();
|
|
4245
|
-
import { existsSync as
|
|
4246
|
-
import { join as
|
|
4415
|
+
import { existsSync as existsSync9, statSync as statSync3, readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
4416
|
+
import { join as join11, dirname as dirname8 } from "node:path";
|
|
4247
4417
|
var HELP = `
|
|
4248
4418
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4249
4419
|
|
|
@@ -4314,8 +4484,8 @@ var argvNoScope = withoutScopeArgs(process.argv);
|
|
|
4314
4484
|
var [, , cmd, subcmd] = argvNoScope;
|
|
4315
4485
|
async function runDoctorCommand() {
|
|
4316
4486
|
const cwd = process.cwd();
|
|
4317
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4318
|
-
const statePath =
|
|
4487
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4488
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4319
4489
|
let hasError = false;
|
|
4320
4490
|
let hasWarn = false;
|
|
4321
4491
|
const check = (label, ok, warn = false) => {
|
|
@@ -4330,23 +4500,23 @@ async function runDoctorCommand() {
|
|
|
4330
4500
|
}
|
|
4331
4501
|
};
|
|
4332
4502
|
const pluginPaths = [
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4503
|
+
join11(cwd, "assets", "plugins", "ostacky-plugin.ts"),
|
|
4504
|
+
join11(opencodeDir, "plugins", "ostacky-plugin.ts"),
|
|
4505
|
+
join11(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
|
|
4506
|
+
join11(cwd, "assets", "plugins", "ostacky-controller.ts"),
|
|
4507
|
+
join11(opencodeDir, "plugins", "ostacky-controller.ts"),
|
|
4508
|
+
join11(cwd, ".opencode", "plugins", "ostacky-controller.ts")
|
|
4339
4509
|
];
|
|
4340
|
-
const pluginActive = pluginPaths.some((p) =>
|
|
4510
|
+
const pluginActive = pluginPaths.some((p) => existsSync9(p));
|
|
4341
4511
|
try {
|
|
4342
|
-
if (!
|
|
4512
|
+
if (!existsSync9(statePath)) {
|
|
4343
4513
|
if (pluginActive)
|
|
4344
4514
|
console.log(`✅ controller: plugin active (no state yet)`);
|
|
4345
4515
|
else
|
|
4346
4516
|
check("controller: state file missing", false, true);
|
|
4347
4517
|
} else {
|
|
4348
4518
|
const stat = statSync3(statePath);
|
|
4349
|
-
const raw =
|
|
4519
|
+
const raw = readFileSync7(statePath, "utf-8");
|
|
4350
4520
|
const parsed = JSON.parse(raw);
|
|
4351
4521
|
if (pluginActive) {
|
|
4352
4522
|
console.log(`✅ controller: plugin active (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`);
|
|
@@ -4410,15 +4580,15 @@ async function runDoctorCommand() {
|
|
|
4410
4580
|
check(`controller: ${e.message}`, false);
|
|
4411
4581
|
}
|
|
4412
4582
|
try {
|
|
4413
|
-
const lockPid =
|
|
4414
|
-
const lockTs =
|
|
4415
|
-
if (
|
|
4583
|
+
const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
|
|
4584
|
+
const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
|
|
4585
|
+
if (existsSync9(lockPid) || existsSync9(lockTs)) {
|
|
4416
4586
|
let ageStr = "";
|
|
4417
4587
|
try {
|
|
4418
|
-
const ts = parseInt(
|
|
4588
|
+
const ts = parseInt(readFileSync7(lockTs, "utf-8"), 10);
|
|
4419
4589
|
const age = Date.now() - ts;
|
|
4420
4590
|
ageStr = `${Math.floor(age / 1000)}s`;
|
|
4421
|
-
const pid =
|
|
4591
|
+
const pid = readFileSync7(lockPid, "utf-8").trim();
|
|
4422
4592
|
let alive = false;
|
|
4423
4593
|
try {
|
|
4424
4594
|
process.kill(parseInt(pid, 10), 0);
|
|
@@ -4436,15 +4606,15 @@ async function runDoctorCommand() {
|
|
|
4436
4606
|
}
|
|
4437
4607
|
const tools = ["codegraph", "engram"];
|
|
4438
4608
|
for (const t of tools) {
|
|
4439
|
-
const p =
|
|
4609
|
+
const p = join11(opencodeDir, "tools", t, "bin", t);
|
|
4440
4610
|
const pExe = p + ".exe";
|
|
4441
|
-
check(`tool ${t}: ${
|
|
4611
|
+
check(`tool ${t}: ${existsSync9(p) || existsSync9(pExe) ? "found" : "missing"}`, existsSync9(p) || existsSync9(pExe), true);
|
|
4442
4612
|
}
|
|
4443
4613
|
try {
|
|
4444
|
-
const manifest = JSON.parse(
|
|
4614
|
+
const manifest = JSON.parse(readFileSync7(join11(cwd, "manifest.json"), "utf-8"));
|
|
4445
4615
|
const expected = manifest.mcpServers?.find((x) => x.name === "ostacky-controller")?.sha256;
|
|
4446
4616
|
if (expected) {
|
|
4447
|
-
const actual = computeTreeHash(
|
|
4617
|
+
const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4448
4618
|
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4449
4619
|
if (expected !== actual)
|
|
4450
4620
|
console.log(" Run: bun run hash:update");
|
|
@@ -4452,22 +4622,32 @@ async function runDoctorCommand() {
|
|
|
4452
4622
|
} catch {
|
|
4453
4623
|
check("manifest: not found", false, true);
|
|
4454
4624
|
}
|
|
4455
|
-
|
|
4625
|
+
try {
|
|
4626
|
+
await Promise.resolve().then(() => init_doctor_v2());
|
|
4627
|
+
for (const line of checkDoctorV2(cwd)) {
|
|
4628
|
+
console.log(line);
|
|
4629
|
+
if (line.startsWith("⚠️") || line.startsWith("❌"))
|
|
4630
|
+
hasWarn = true;
|
|
4631
|
+
}
|
|
4632
|
+
} catch {
|
|
4633
|
+
check("doctor-v2: check failed", false, true);
|
|
4634
|
+
}
|
|
4635
|
+
if (existsSync9(statePath)) {
|
|
4456
4636
|
try {
|
|
4457
|
-
const s = JSON.parse(
|
|
4637
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4458
4638
|
if (s.allowedFiles || s.deniedFiles) {}
|
|
4459
4639
|
} catch {}
|
|
4460
4640
|
}
|
|
4461
4641
|
try {
|
|
4462
|
-
const cacheDir =
|
|
4463
|
-
if (!
|
|
4642
|
+
const cacheDir = join11(opencodeDir, "cache", "codegraph");
|
|
4643
|
+
if (!existsSync9(cacheDir)) {
|
|
4464
4644
|
console.log("ℹ️ cache: no cache dir yet (ok)");
|
|
4465
4645
|
} else {
|
|
4466
4646
|
const files = readdirSync3(cacheDir);
|
|
4467
4647
|
let total = 0;
|
|
4468
4648
|
for (const f of files) {
|
|
4469
4649
|
try {
|
|
4470
|
-
total += statSync3(
|
|
4650
|
+
total += statSync3(join11(cacheDir, f)).size;
|
|
4471
4651
|
} catch {}
|
|
4472
4652
|
}
|
|
4473
4653
|
const totalMB = (total / 1048576).toFixed(2);
|
|
@@ -4478,7 +4658,7 @@ async function runDoctorCommand() {
|
|
|
4478
4658
|
console.log(`✅ cache: OK (${files.length} files, ${totalMB}MB)`);
|
|
4479
4659
|
}
|
|
4480
4660
|
try {
|
|
4481
|
-
const s = JSON.parse(
|
|
4661
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4482
4662
|
if (s.cacheHitCount !== undefined) {
|
|
4483
4663
|
console.log(`ℹ️ cacheHitCount=${s.cacheHitCount} cacheMissCount=${s.cacheMissCount || 0} tokenSavingEstimate=${s.tokenSavingEstimate || 0}`);
|
|
4484
4664
|
}
|
|
@@ -4488,28 +4668,28 @@ async function runDoctorCommand() {
|
|
|
4488
4668
|
console.log(`⚠️ cache: check failed ${e.message}`);
|
|
4489
4669
|
}
|
|
4490
4670
|
try {
|
|
4491
|
-
const coreSrc =
|
|
4492
|
-
const coreMcp =
|
|
4493
|
-
const corePlugin =
|
|
4494
|
-
const coreSync =
|
|
4671
|
+
const coreSrc = join11(cwd, "src", "controller-core.ts");
|
|
4672
|
+
const coreMcp = join11(cwd, "assets", "mcp", "ostacky-controller", "controller-core.js");
|
|
4673
|
+
const corePlugin = join11(cwd, "assets", "plugins", "controller-core.ts");
|
|
4674
|
+
const coreSync = existsSync9(coreSrc) && existsSync9(coreMcp) && existsSync9(corePlugin);
|
|
4495
4675
|
if (coreSync) {
|
|
4496
|
-
const srcHash =
|
|
4497
|
-
const mcpHash =
|
|
4498
|
-
const ok =
|
|
4676
|
+
const srcHash = readFileSync7(coreSrc, "utf-8").slice(0, 100);
|
|
4677
|
+
const mcpHash = readFileSync7(coreMcp, "utf-8").slice(0, 100);
|
|
4678
|
+
const ok = readFileSync7(coreMcp, "utf-8").includes("STATES") && readFileSync7(corePlugin, "utf-8").includes("STATES");
|
|
4499
4679
|
check(`controller-core: synced (${ok ? "STATES present" : "mismatch"})`, ok);
|
|
4500
4680
|
if (!ok)
|
|
4501
4681
|
console.log(" Run: bun run scripts/sync-controller-core.ts (if exists) or copy src/controller-core.ts");
|
|
4502
4682
|
} else {
|
|
4503
4683
|
check("controller-core: synced", false, true);
|
|
4504
4684
|
}
|
|
4505
|
-
const auditPath =
|
|
4506
|
-
if (
|
|
4685
|
+
const auditPath = join11(opencodeDir, "ostacky-audit.jsonl");
|
|
4686
|
+
if (existsSync9(auditPath)) {
|
|
4507
4687
|
const sz = statSync3(auditPath).size;
|
|
4508
|
-
const lines =
|
|
4688
|
+
const lines = readFileSync7(auditPath, "utf-8").split(`
|
|
4509
4689
|
`).filter(Boolean).length;
|
|
4510
4690
|
check(`audit: jsonl ${lines} entries, ${(sz / 1024).toFixed(1)}KB`, sz < 512000);
|
|
4511
|
-
if (
|
|
4512
|
-
const s = JSON.parse(
|
|
4691
|
+
if (existsSync9(statePath)) {
|
|
4692
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4513
4693
|
const stateSize = statSync3(statePath).size;
|
|
4514
4694
|
check(`state size <50KB (${(stateSize / 1024).toFixed(1)}KB)`, stateSize < 51200);
|
|
4515
4695
|
if (s.audit && s.audit.length > 20)
|
|
@@ -4520,16 +4700,16 @@ async function runDoctorCommand() {
|
|
|
4520
4700
|
} else {
|
|
4521
4701
|
console.log("ℹ️ audit: jsonl not yet created (will be created on next audit)");
|
|
4522
4702
|
}
|
|
4523
|
-
const ostackyMd =
|
|
4703
|
+
const ostackyMd = readFileSync7(join11(cwd, "assets", "agents", "ostacky.md"), "utf-8");
|
|
4524
4704
|
const hasLevels = ostackyMd.includes("LEVEL_THRESHOLDS") || ostackyMd.includes("classifyLevel");
|
|
4525
4705
|
check("levels: unified via tiered.ts", hasLevels, true);
|
|
4526
4706
|
const hasHonesty = ostackyMd.includes("Principios de honestidad");
|
|
4527
4707
|
check("honesty: 7 SHALL", hasHonesty);
|
|
4528
4708
|
if (!hasHonesty)
|
|
4529
4709
|
console.log(" Expected: ## Principios de honestidad (SHALL) in ostacky.md");
|
|
4530
|
-
const pluginPath =
|
|
4531
|
-
if (
|
|
4532
|
-
const plugin =
|
|
4710
|
+
const pluginPath = join11(cwd, "assets", "plugins", "ostacky-plugin.ts");
|
|
4711
|
+
if (existsSync9(pluginPath)) {
|
|
4712
|
+
const plugin = readFileSync7(pluginPath, "utf-8");
|
|
4533
4713
|
const hasSpecGuard = plugin.includes("getDiscoverySnapshot") && plugin.includes("specSnapshot");
|
|
4534
4714
|
const hasNoOverwrite = plugin.includes("No edites sin Read fresco") || plugin.includes("specSnapshot");
|
|
4535
4715
|
check("spec: no-overwrite guard", hasNoOverwrite, true);
|
|
@@ -4540,12 +4720,12 @@ async function runDoctorCommand() {
|
|
|
4540
4720
|
console.log(`⚠️ honesty/spec checks failed: ${e.message}`);
|
|
4541
4721
|
}
|
|
4542
4722
|
try {
|
|
4543
|
-
const giPath =
|
|
4544
|
-
if (!
|
|
4723
|
+
const giPath = join11(cwd, ".gitignore");
|
|
4724
|
+
if (!existsSync9(giPath)) {
|
|
4545
4725
|
console.log("⚠️ .gitignore: missing (run npx ostacky install --scope local to create)");
|
|
4546
4726
|
hasWarn = true;
|
|
4547
4727
|
} else {
|
|
4548
|
-
const gi =
|
|
4728
|
+
const gi = readFileSync7(giPath, "utf-8");
|
|
4549
4729
|
const needed = [
|
|
4550
4730
|
".opencode/tools/",
|
|
4551
4731
|
".opencode/cache/",
|
|
@@ -4565,12 +4745,12 @@ async function runDoctorCommand() {
|
|
|
4565
4745
|
}
|
|
4566
4746
|
} catch {}
|
|
4567
4747
|
try {
|
|
4568
|
-
const secPath =
|
|
4569
|
-
if (!
|
|
4748
|
+
const secPath = join11(cwd, "src", "security.ts");
|
|
4749
|
+
if (!existsSync9(secPath)) {
|
|
4570
4750
|
console.log("⚠️ src/security.ts: missing (source-of-truth)");
|
|
4571
4751
|
hasWarn = true;
|
|
4572
4752
|
} else {
|
|
4573
|
-
const sec =
|
|
4753
|
+
const sec = readFileSync7(secPath, "utf-8");
|
|
4574
4754
|
const hasSensitiveDefault = sec.includes("SENSITIVE_DEFAULT");
|
|
4575
4755
|
const hasBashRe = sec.includes("BASH_SENSITIVE_RE");
|
|
4576
4756
|
const hasIsSensitive = sec.includes("function isSensitive");
|
|
@@ -4584,7 +4764,7 @@ async function runDoctorCommand() {
|
|
|
4584
4764
|
}
|
|
4585
4765
|
} catch {}
|
|
4586
4766
|
try {
|
|
4587
|
-
const s = JSON.parse(
|
|
4767
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4588
4768
|
if (s.sensitiveAccess?.blockedAttempts > 0) {
|
|
4589
4769
|
console.log(`ℹ️ sensitiveAccess: blockedAttempts includes bash (${s.sensitiveAccess.blockedAttempts})`);
|
|
4590
4770
|
}
|
|
@@ -4597,14 +4777,14 @@ async function runDoctorCommand() {
|
|
|
4597
4777
|
async function runStatusCommand(args) {
|
|
4598
4778
|
const isJson = args.includes("--json");
|
|
4599
4779
|
const cwd = process.cwd();
|
|
4600
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4601
|
-
const statePath =
|
|
4602
|
-
if (!
|
|
4780
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4781
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4782
|
+
if (!existsSync9(statePath)) {
|
|
4603
4783
|
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4604
4784
|
return;
|
|
4605
4785
|
}
|
|
4606
4786
|
try {
|
|
4607
|
-
const parsed = JSON.parse(
|
|
4787
|
+
const parsed = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4608
4788
|
const completed = Object.values(parsed.tasks || {}).filter((t) => t.status === "COMPLETED").length;
|
|
4609
4789
|
const expected = parsed.expectedTaskCount ?? parsed.expectedTasks?.length ?? Object.keys(parsed.tasks || {}).length;
|
|
4610
4790
|
const degraded = parsed.degraded ? " degraded" : "";
|