ostacky 0.8.7 → 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 +13 -13
- package/assets/agents/ostacky.md +7 -7
- package/assets/commands/install-stack.md +37 -29
- package/assets/docs/ostacky-reference.md +11 -3
- package/assets/mcp/ostacky-controller/controller-core.js +4 -2
- package/assets/mcp/ostacky-controller/index.js +248 -67
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/mcp/ostacky-controller/security.js +33 -2
- package/assets/plugins/controller-core.ts +4 -2
- package/assets/plugins/engram.ts +648 -646
- package/assets/plugins/ostacky-plugin.ts +287 -211
- package/assets/plugins/security.ts +118 -0
- package/assets/plugins/tiered.ts +46 -0
- package/assets/skills/dispatching-parallel-agents/SKILL.md +1 -0
- package/assets/skills/openspec-explore/SKILL.md +2 -1
- package/assets/skills/openspec-propose/SKILL.md +3 -1
- package/assets/skills/tdd/SKILL.md +2 -0
- package/dist/cli.js +290 -104
- package/manifest.json +32 -32
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -186,7 +186,7 @@ var require_picocolors = __commonJS(function(exports, module) {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
// src/security.ts
|
|
189
|
-
import { createHash } from "crypto";
|
|
189
|
+
import { createHash } from "node:crypto";
|
|
190
190
|
function validateFilePath(filePath) {
|
|
191
191
|
if (filePath.includes("..") || filePath.startsWith("/") || filePath.startsWith("\\") || /^[a-zA-Z]:/.test(filePath)) {
|
|
192
192
|
throw new Error(`Ruta de archivo inválida: "${filePath}"`);
|
|
@@ -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.
|
|
688
|
-
sha256: "
|
|
687
|
+
version: "0.9.0",
|
|
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.
|
|
702
|
-
sha256: "
|
|
701
|
+
version: "0.9.0",
|
|
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.
|
|
723
|
-
sha256: "
|
|
722
|
+
version: "0.9.0",
|
|
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.
|
|
744
|
-
sha256: "
|
|
743
|
+
version: "0.9.0",
|
|
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) {
|
|
@@ -1155,6 +1176,8 @@ function copyEngramPlugin(projectRoot) {
|
|
|
1155
1176
|
function copyOstackyControllerPlugin(projectRoot) {
|
|
1156
1177
|
const pluginSource = join7(PACKAGE_ROOT, "assets", "plugins", "ostacky-plugin.ts");
|
|
1157
1178
|
const coreSource = join7(PACKAGE_ROOT, "assets", "plugins", "controller-core.ts");
|
|
1179
|
+
const securitySource = join7(PACKAGE_ROOT, "assets", "plugins", "security.ts");
|
|
1180
|
+
const tieredSource = join7(PACKAGE_ROOT, "assets", "plugins", "tiered.ts");
|
|
1158
1181
|
const pluginsDir = join7(projectRoot, ".opencode", "plugins");
|
|
1159
1182
|
if (!existsSync6(pluginSource)) {
|
|
1160
1183
|
throw new Error(`Plugin bundleado de OstackyController no encontrado: ${pluginSource}`);
|
|
@@ -1163,6 +1186,10 @@ function copyOstackyControllerPlugin(projectRoot) {
|
|
|
1163
1186
|
copyFileSync3(pluginSource, join7(pluginsDir, "ostacky-plugin.ts"));
|
|
1164
1187
|
if (existsSync6(coreSource))
|
|
1165
1188
|
copyFileSync3(coreSource, join7(pluginsDir, "controller-core.ts"));
|
|
1189
|
+
if (existsSync6(securitySource))
|
|
1190
|
+
copyFileSync3(securitySource, join7(pluginsDir, "security.ts"));
|
|
1191
|
+
if (existsSync6(tieredSource))
|
|
1192
|
+
copyFileSync3(tieredSource, join7(pluginsDir, "tiered.ts"));
|
|
1166
1193
|
}
|
|
1167
1194
|
function buildEngramDownloadUrl(tag, platform = process.platform, arch = process.arch) {
|
|
1168
1195
|
const target = getEngramReleaseTarget(platform, arch);
|
|
@@ -1489,17 +1516,119 @@ var init_stack = __esm(() => {
|
|
|
1489
1516
|
init_config();
|
|
1490
1517
|
init_github();
|
|
1491
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
|
+
});
|
|
1492
1621
|
// package.json
|
|
1493
1622
|
var package_default = {
|
|
1494
1623
|
name: "ostacky",
|
|
1495
|
-
version: "0.
|
|
1624
|
+
version: "0.9.0",
|
|
1496
1625
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1497
1626
|
type: "module",
|
|
1498
1627
|
bin: {
|
|
1499
1628
|
ostacky: "./dist/cli.js"
|
|
1500
1629
|
},
|
|
1501
1630
|
scripts: {
|
|
1502
|
-
prebuild: "bun run scripts/sync-version.ts",
|
|
1631
|
+
prebuild: "bun run scripts/sync-version.ts && bun run scripts/sync-controller-core.ts",
|
|
1503
1632
|
dev: "bun run src/cli.ts",
|
|
1504
1633
|
build: "bun build src/cli.ts --target=node --format=esm --outfile dist/cli.js && bun scripts/add-shebang.ts",
|
|
1505
1634
|
start: "node dist/cli.js",
|
|
@@ -1510,7 +1639,7 @@ var package_default = {
|
|
|
1510
1639
|
"hash:update": "bun run scripts/update-manifest-hashes.ts",
|
|
1511
1640
|
"manifest:prepare": "bun run scripts/update-manifest-hashes.ts",
|
|
1512
1641
|
"manifest:check": "bun run scripts/update-manifest-hashes.ts --check",
|
|
1513
|
-
prepublishOnly: "bun run manifest:prepare && bun run build"
|
|
1642
|
+
prepublishOnly: "bun run scripts/sync-controller-core.ts && bun run manifest:prepare && bun run build"
|
|
1514
1643
|
},
|
|
1515
1644
|
files: [
|
|
1516
1645
|
"dist/",
|
|
@@ -2519,7 +2648,9 @@ function createMcpConfigEntry(name, nodeExecutable, serverPath, statePath) {
|
|
|
2519
2648
|
const entry = {
|
|
2520
2649
|
type: "local",
|
|
2521
2650
|
command: [nodeExecutable, serverPath],
|
|
2522
|
-
|
|
2651
|
+
disabled: false,
|
|
2652
|
+
timeout: { catalog: 30000, execution: 300000 },
|
|
2653
|
+
protocol: "legacy"
|
|
2523
2654
|
};
|
|
2524
2655
|
if (name === "ostacky-controller" && statePath) {
|
|
2525
2656
|
entry.environment = { OSTACKY_STATE_PATH: statePath };
|
|
@@ -2669,7 +2800,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2669
2800
|
params: {
|
|
2670
2801
|
protocolVersion: "2025-03-26",
|
|
2671
2802
|
capabilities: {},
|
|
2672
|
-
clientInfo: { name: "ostacky-installer", version: "0.
|
|
2803
|
+
clientInfo: { name: "ostacky-installer", version: "0.9.0" }
|
|
2673
2804
|
}
|
|
2674
2805
|
});
|
|
2675
2806
|
});
|
|
@@ -2950,8 +3081,17 @@ function uninstallMcpServer(name, paths) {
|
|
|
2950
3081
|
const config = readOpenCodeConfig(configPath);
|
|
2951
3082
|
if (config) {
|
|
2952
3083
|
const mcp = config.mcp;
|
|
2953
|
-
if (mcp
|
|
2954
|
-
|
|
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
|
+
}
|
|
2955
3095
|
if (Object.keys(mcp).length === 0) {
|
|
2956
3096
|
delete config.mcp;
|
|
2957
3097
|
}
|
|
@@ -2978,7 +3118,15 @@ function uninstallAll(paths) {
|
|
|
2978
3118
|
for (const name of Object.keys(lockfile.mcpServers ?? {})) {
|
|
2979
3119
|
uninstallMcpServer(name, paths);
|
|
2980
3120
|
}
|
|
2981
|
-
const ostackyPlugins = [
|
|
3121
|
+
const ostackyPlugins = [
|
|
3122
|
+
"ostacky-plugin.ts",
|
|
3123
|
+
"engram.ts",
|
|
3124
|
+
"ostacky-guard.ts",
|
|
3125
|
+
"ostacky-controller.ts",
|
|
3126
|
+
"controller-core.ts",
|
|
3127
|
+
"security.ts",
|
|
3128
|
+
"tiered.ts"
|
|
3129
|
+
];
|
|
2982
3130
|
for (const file of ostackyPlugins) {
|
|
2983
3131
|
const fp = join6(paths.plugins, file);
|
|
2984
3132
|
if (existsSync5(fp)) {
|
|
@@ -3785,6 +3933,23 @@ function isOpencodeInstalled() {
|
|
|
3785
3933
|
return false;
|
|
3786
3934
|
}
|
|
3787
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
|
+
}
|
|
3788
3953
|
function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
|
|
3789
3954
|
if (platform === "win32") {
|
|
3790
3955
|
return {
|
|
@@ -3810,8 +3975,17 @@ function getOpencodeInstallCommand(platform = process.platform, _arch = process.
|
|
|
3810
3975
|
};
|
|
3811
3976
|
}
|
|
3812
3977
|
async function ensureOpencodeInstalled() {
|
|
3813
|
-
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
|
+
}
|
|
3814
3987
|
return;
|
|
3988
|
+
}
|
|
3815
3989
|
const platform = process.platform;
|
|
3816
3990
|
const info = getOpencodeInstallCommand(platform);
|
|
3817
3991
|
v2.warn("OpenCode no detectado en este sistema.");
|
|
@@ -4228,8 +4402,8 @@ async function runUninstallMcpCommand(name, scope) {
|
|
|
4228
4402
|
|
|
4229
4403
|
// src/cli.ts
|
|
4230
4404
|
init_fs();
|
|
4231
|
-
import { existsSync as
|
|
4232
|
-
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";
|
|
4233
4407
|
var HELP = `
|
|
4234
4408
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4235
4409
|
|
|
@@ -4300,8 +4474,8 @@ var argvNoScope = withoutScopeArgs(process.argv);
|
|
|
4300
4474
|
var [, , cmd, subcmd] = argvNoScope;
|
|
4301
4475
|
async function runDoctorCommand() {
|
|
4302
4476
|
const cwd = process.cwd();
|
|
4303
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4304
|
-
const statePath =
|
|
4477
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4478
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4305
4479
|
let hasError = false;
|
|
4306
4480
|
let hasWarn = false;
|
|
4307
4481
|
const check = (label, ok, warn = false) => {
|
|
@@ -4316,23 +4490,23 @@ async function runDoctorCommand() {
|
|
|
4316
4490
|
}
|
|
4317
4491
|
};
|
|
4318
4492
|
const pluginPaths = [
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
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")
|
|
4325
4499
|
];
|
|
4326
|
-
const pluginActive = pluginPaths.some((p) =>
|
|
4500
|
+
const pluginActive = pluginPaths.some((p) => existsSync9(p));
|
|
4327
4501
|
try {
|
|
4328
|
-
if (!
|
|
4502
|
+
if (!existsSync9(statePath)) {
|
|
4329
4503
|
if (pluginActive)
|
|
4330
4504
|
console.log(`✅ controller: plugin active (no state yet)`);
|
|
4331
4505
|
else
|
|
4332
4506
|
check("controller: state file missing", false, true);
|
|
4333
4507
|
} else {
|
|
4334
4508
|
const stat = statSync3(statePath);
|
|
4335
|
-
const raw =
|
|
4509
|
+
const raw = readFileSync7(statePath, "utf-8");
|
|
4336
4510
|
const parsed = JSON.parse(raw);
|
|
4337
4511
|
if (pluginActive) {
|
|
4338
4512
|
console.log(`✅ controller: plugin active (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`);
|
|
@@ -4347,6 +4521,8 @@ async function runDoctorCommand() {
|
|
|
4347
4521
|
console.log(`⚠️ degraded: confirmation not audited in controller (degradedEditsCount=${parsed.degradedEditsCount})`);
|
|
4348
4522
|
if (parsed.codegraphBypassCount > 0)
|
|
4349
4523
|
console.log(`⚠️ codegraphBypassCount=${parsed.codegraphBypassCount} (inefficient: codegraph bypass)`);
|
|
4524
|
+
if (parsed.engramBypassCount > 0)
|
|
4525
|
+
console.log(`⚠️ engramBypassCount=${parsed.engramBypassCount} (contradiction check bypassed — sin mem_search)`);
|
|
4350
4526
|
if (parsed.stateOversizedCount > 0)
|
|
4351
4527
|
console.log(`⚠️ stateOversizedCount=${parsed.stateOversizedCount} snapshots perdidos`);
|
|
4352
4528
|
if (parsed.sensitiveAccess)
|
|
@@ -4394,15 +4570,15 @@ async function runDoctorCommand() {
|
|
|
4394
4570
|
check(`controller: ${e.message}`, false);
|
|
4395
4571
|
}
|
|
4396
4572
|
try {
|
|
4397
|
-
const lockPid =
|
|
4398
|
-
const lockTs =
|
|
4399
|
-
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)) {
|
|
4400
4576
|
let ageStr = "";
|
|
4401
4577
|
try {
|
|
4402
|
-
const ts = parseInt(
|
|
4578
|
+
const ts = parseInt(readFileSync7(lockTs, "utf-8"), 10);
|
|
4403
4579
|
const age = Date.now() - ts;
|
|
4404
4580
|
ageStr = `${Math.floor(age / 1000)}s`;
|
|
4405
|
-
const pid =
|
|
4581
|
+
const pid = readFileSync7(lockPid, "utf-8").trim();
|
|
4406
4582
|
let alive = false;
|
|
4407
4583
|
try {
|
|
4408
4584
|
process.kill(parseInt(pid, 10), 0);
|
|
@@ -4420,15 +4596,15 @@ async function runDoctorCommand() {
|
|
|
4420
4596
|
}
|
|
4421
4597
|
const tools = ["codegraph", "engram"];
|
|
4422
4598
|
for (const t of tools) {
|
|
4423
|
-
const p =
|
|
4599
|
+
const p = join11(opencodeDir, "tools", t, "bin", t);
|
|
4424
4600
|
const pExe = p + ".exe";
|
|
4425
|
-
check(`tool ${t}: ${
|
|
4601
|
+
check(`tool ${t}: ${existsSync9(p) || existsSync9(pExe) ? "found" : "missing"}`, existsSync9(p) || existsSync9(pExe), true);
|
|
4426
4602
|
}
|
|
4427
4603
|
try {
|
|
4428
|
-
const manifest = JSON.parse(
|
|
4604
|
+
const manifest = JSON.parse(readFileSync7(join11(cwd, "manifest.json"), "utf-8"));
|
|
4429
4605
|
const expected = manifest.mcpServers?.find((x) => x.name === "ostacky-controller")?.sha256;
|
|
4430
4606
|
if (expected) {
|
|
4431
|
-
const actual = computeTreeHash(
|
|
4607
|
+
const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4432
4608
|
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4433
4609
|
if (expected !== actual)
|
|
4434
4610
|
console.log(" Run: bun run hash:update");
|
|
@@ -4436,22 +4612,32 @@ async function runDoctorCommand() {
|
|
|
4436
4612
|
} catch {
|
|
4437
4613
|
check("manifest: not found", false, true);
|
|
4438
4614
|
}
|
|
4439
|
-
|
|
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)) {
|
|
4440
4626
|
try {
|
|
4441
|
-
const s = JSON.parse(
|
|
4627
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4442
4628
|
if (s.allowedFiles || s.deniedFiles) {}
|
|
4443
4629
|
} catch {}
|
|
4444
4630
|
}
|
|
4445
4631
|
try {
|
|
4446
|
-
const cacheDir =
|
|
4447
|
-
if (!
|
|
4632
|
+
const cacheDir = join11(opencodeDir, "cache", "codegraph");
|
|
4633
|
+
if (!existsSync9(cacheDir)) {
|
|
4448
4634
|
console.log("ℹ️ cache: no cache dir yet (ok)");
|
|
4449
4635
|
} else {
|
|
4450
4636
|
const files = readdirSync3(cacheDir);
|
|
4451
4637
|
let total = 0;
|
|
4452
4638
|
for (const f of files) {
|
|
4453
4639
|
try {
|
|
4454
|
-
total += statSync3(
|
|
4640
|
+
total += statSync3(join11(cacheDir, f)).size;
|
|
4455
4641
|
} catch {}
|
|
4456
4642
|
}
|
|
4457
4643
|
const totalMB = (total / 1048576).toFixed(2);
|
|
@@ -4462,7 +4648,7 @@ async function runDoctorCommand() {
|
|
|
4462
4648
|
console.log(`✅ cache: OK (${files.length} files, ${totalMB}MB)`);
|
|
4463
4649
|
}
|
|
4464
4650
|
try {
|
|
4465
|
-
const s = JSON.parse(
|
|
4651
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4466
4652
|
if (s.cacheHitCount !== undefined) {
|
|
4467
4653
|
console.log(`ℹ️ cacheHitCount=${s.cacheHitCount} cacheMissCount=${s.cacheMissCount || 0} tokenSavingEstimate=${s.tokenSavingEstimate || 0}`);
|
|
4468
4654
|
}
|
|
@@ -4472,28 +4658,28 @@ async function runDoctorCommand() {
|
|
|
4472
4658
|
console.log(`⚠️ cache: check failed ${e.message}`);
|
|
4473
4659
|
}
|
|
4474
4660
|
try {
|
|
4475
|
-
const coreSrc =
|
|
4476
|
-
const coreMcp =
|
|
4477
|
-
const corePlugin =
|
|
4478
|
-
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);
|
|
4479
4665
|
if (coreSync) {
|
|
4480
|
-
const srcHash =
|
|
4481
|
-
const mcpHash =
|
|
4482
|
-
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");
|
|
4483
4669
|
check(`controller-core: synced (${ok ? "STATES present" : "mismatch"})`, ok);
|
|
4484
4670
|
if (!ok)
|
|
4485
4671
|
console.log(" Run: bun run scripts/sync-controller-core.ts (if exists) or copy src/controller-core.ts");
|
|
4486
4672
|
} else {
|
|
4487
4673
|
check("controller-core: synced", false, true);
|
|
4488
4674
|
}
|
|
4489
|
-
const auditPath =
|
|
4490
|
-
if (
|
|
4675
|
+
const auditPath = join11(opencodeDir, "ostacky-audit.jsonl");
|
|
4676
|
+
if (existsSync9(auditPath)) {
|
|
4491
4677
|
const sz = statSync3(auditPath).size;
|
|
4492
|
-
const lines =
|
|
4678
|
+
const lines = readFileSync7(auditPath, "utf-8").split(`
|
|
4493
4679
|
`).filter(Boolean).length;
|
|
4494
4680
|
check(`audit: jsonl ${lines} entries, ${(sz / 1024).toFixed(1)}KB`, sz < 512000);
|
|
4495
|
-
if (
|
|
4496
|
-
const s = JSON.parse(
|
|
4681
|
+
if (existsSync9(statePath)) {
|
|
4682
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4497
4683
|
const stateSize = statSync3(statePath).size;
|
|
4498
4684
|
check(`state size <50KB (${(stateSize / 1024).toFixed(1)}KB)`, stateSize < 51200);
|
|
4499
4685
|
if (s.audit && s.audit.length > 20)
|
|
@@ -4504,16 +4690,16 @@ async function runDoctorCommand() {
|
|
|
4504
4690
|
} else {
|
|
4505
4691
|
console.log("ℹ️ audit: jsonl not yet created (will be created on next audit)");
|
|
4506
4692
|
}
|
|
4507
|
-
const ostackyMd =
|
|
4693
|
+
const ostackyMd = readFileSync7(join11(cwd, "assets", "agents", "ostacky.md"), "utf-8");
|
|
4508
4694
|
const hasLevels = ostackyMd.includes("LEVEL_THRESHOLDS") || ostackyMd.includes("classifyLevel");
|
|
4509
4695
|
check("levels: unified via tiered.ts", hasLevels, true);
|
|
4510
4696
|
const hasHonesty = ostackyMd.includes("Principios de honestidad");
|
|
4511
4697
|
check("honesty: 7 SHALL", hasHonesty);
|
|
4512
4698
|
if (!hasHonesty)
|
|
4513
4699
|
console.log(" Expected: ## Principios de honestidad (SHALL) in ostacky.md");
|
|
4514
|
-
const pluginPath =
|
|
4515
|
-
if (
|
|
4516
|
-
const plugin =
|
|
4700
|
+
const pluginPath = join11(cwd, "assets", "plugins", "ostacky-plugin.ts");
|
|
4701
|
+
if (existsSync9(pluginPath)) {
|
|
4702
|
+
const plugin = readFileSync7(pluginPath, "utf-8");
|
|
4517
4703
|
const hasSpecGuard = plugin.includes("getDiscoverySnapshot") && plugin.includes("specSnapshot");
|
|
4518
4704
|
const hasNoOverwrite = plugin.includes("No edites sin Read fresco") || plugin.includes("specSnapshot");
|
|
4519
4705
|
check("spec: no-overwrite guard", hasNoOverwrite, true);
|
|
@@ -4524,12 +4710,12 @@ async function runDoctorCommand() {
|
|
|
4524
4710
|
console.log(`⚠️ honesty/spec checks failed: ${e.message}`);
|
|
4525
4711
|
}
|
|
4526
4712
|
try {
|
|
4527
|
-
const giPath =
|
|
4528
|
-
if (!
|
|
4713
|
+
const giPath = join11(cwd, ".gitignore");
|
|
4714
|
+
if (!existsSync9(giPath)) {
|
|
4529
4715
|
console.log("⚠️ .gitignore: missing (run npx ostacky install --scope local to create)");
|
|
4530
4716
|
hasWarn = true;
|
|
4531
4717
|
} else {
|
|
4532
|
-
const gi =
|
|
4718
|
+
const gi = readFileSync7(giPath, "utf-8");
|
|
4533
4719
|
const needed = [
|
|
4534
4720
|
".opencode/tools/",
|
|
4535
4721
|
".opencode/cache/",
|
|
@@ -4549,12 +4735,12 @@ async function runDoctorCommand() {
|
|
|
4549
4735
|
}
|
|
4550
4736
|
} catch {}
|
|
4551
4737
|
try {
|
|
4552
|
-
const secPath =
|
|
4553
|
-
if (!
|
|
4738
|
+
const secPath = join11(cwd, "src", "security.ts");
|
|
4739
|
+
if (!existsSync9(secPath)) {
|
|
4554
4740
|
console.log("⚠️ src/security.ts: missing (source-of-truth)");
|
|
4555
4741
|
hasWarn = true;
|
|
4556
4742
|
} else {
|
|
4557
|
-
const sec =
|
|
4743
|
+
const sec = readFileSync7(secPath, "utf-8");
|
|
4558
4744
|
const hasSensitiveDefault = sec.includes("SENSITIVE_DEFAULT");
|
|
4559
4745
|
const hasBashRe = sec.includes("BASH_SENSITIVE_RE");
|
|
4560
4746
|
const hasIsSensitive = sec.includes("function isSensitive");
|
|
@@ -4568,7 +4754,7 @@ async function runDoctorCommand() {
|
|
|
4568
4754
|
}
|
|
4569
4755
|
} catch {}
|
|
4570
4756
|
try {
|
|
4571
|
-
const s = JSON.parse(
|
|
4757
|
+
const s = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4572
4758
|
if (s.sensitiveAccess?.blockedAttempts > 0) {
|
|
4573
4759
|
console.log(`ℹ️ sensitiveAccess: blockedAttempts includes bash (${s.sensitiveAccess.blockedAttempts})`);
|
|
4574
4760
|
}
|
|
@@ -4581,14 +4767,14 @@ async function runDoctorCommand() {
|
|
|
4581
4767
|
async function runStatusCommand(args) {
|
|
4582
4768
|
const isJson = args.includes("--json");
|
|
4583
4769
|
const cwd = process.cwd();
|
|
4584
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4585
|
-
const statePath =
|
|
4586
|
-
if (!
|
|
4770
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4771
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4772
|
+
if (!existsSync9(statePath)) {
|
|
4587
4773
|
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4588
4774
|
return;
|
|
4589
4775
|
}
|
|
4590
4776
|
try {
|
|
4591
|
-
const parsed = JSON.parse(
|
|
4777
|
+
const parsed = JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
4592
4778
|
const completed = Object.values(parsed.tasks || {}).filter((t) => t.status === "COMPLETED").length;
|
|
4593
4779
|
const expected = parsed.expectedTaskCount ?? parsed.expectedTasks?.length ?? Object.keys(parsed.tasks || {}).length;
|
|
4594
4780
|
const degraded = parsed.degraded ? " degraded" : "";
|