ostacky 0.7.2 → 0.7.4
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 +25 -14
- package/assets/agents/ostacky.md +548 -501
- package/assets/commands/install-stack.md +2 -2
- package/assets/mcp/ostacky-controller/index.js +1277 -127
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/mcp/ostacky-controller/security.js +87 -0
- package/assets/plugins/engram.ts +41 -5
- package/assets/plugins/ostacky-guard.ts +212 -0
- package/assets/skills/brainstorming/SKILL.md +198 -197
- package/assets/skills/execution-mode-evaluation/SKILL.md +9 -1
- package/assets/skills/graceful-degradation/SKILL.md +251 -239
- package/assets/skills/using-git-worktrees/SKILL.md +8 -0
- package/dist/cli.js +417 -90
- package/manifest.json +32 -32
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,12 +20,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
20
20
|
}
|
|
21
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
24
|
+
for (let key of __getOwnPropNames(mod))
|
|
25
|
+
if (!__hasOwnProp.call(to, key))
|
|
26
|
+
__defProp(to, key, {
|
|
27
|
+
get: __accessProp.bind(mod, key),
|
|
28
|
+
enumerable: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
29
31
|
if (canCache)
|
|
30
32
|
cache.set(mod, to);
|
|
31
33
|
return to;
|
|
@@ -48,7 +50,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
50
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
51
|
|
|
50
52
|
// node_modules/sisteransi/src/index.js
|
|
51
|
-
var require_src = __commonJS((exports, module)
|
|
53
|
+
var require_src = __commonJS(function(exports, module) {
|
|
52
54
|
var ESC = "\x1B";
|
|
53
55
|
var CSI = `${ESC}[`;
|
|
54
56
|
var beep = "\x07";
|
|
@@ -106,7 +108,7 @@ var require_src = __commonJS((exports, module) => {
|
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
// node_modules/picocolors/picocolors.js
|
|
109
|
-
var require_picocolors = __commonJS((exports, module)
|
|
111
|
+
var require_picocolors = __commonJS(function(exports, module) {
|
|
110
112
|
var p = process || {};
|
|
111
113
|
var argv = p.argv || [];
|
|
112
114
|
var env = p.env || {};
|
|
@@ -178,9 +180,14 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
178
180
|
// src/security.ts
|
|
179
181
|
var exports_security = {};
|
|
180
182
|
__export(exports_security, {
|
|
181
|
-
|
|
183
|
+
BASH_SENSITIVE_RE: () => BASH_SENSITIVE_RE,
|
|
184
|
+
SENSITIVE_DEFAULT: () => SENSITIVE_DEFAULT,
|
|
185
|
+
extractPathsFromBash: () => extractPathsFromBash,
|
|
186
|
+
getSensitivePatterns: () => getSensitivePatterns,
|
|
187
|
+
isSensitive: () => isSensitive,
|
|
188
|
+
sha256: () => sha256,
|
|
182
189
|
validateFilePath: () => validateFilePath,
|
|
183
|
-
|
|
190
|
+
verifyChecksum: () => verifyChecksum
|
|
184
191
|
});
|
|
185
192
|
import { createHash } from "crypto";
|
|
186
193
|
function validateFilePath(filePath) {
|
|
@@ -201,7 +208,89 @@ function verifyChecksum(content, expectedHash, label) {
|
|
|
201
208
|
recibido: ${actual}`);
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
|
-
|
|
211
|
+
function isSensitive(filePath, patterns = SENSITIVE_DEFAULT) {
|
|
212
|
+
if (!filePath)
|
|
213
|
+
return false;
|
|
214
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
215
|
+
const lower = normalized.toLowerCase();
|
|
216
|
+
if (lower.endsWith(".env.example") || lower.endsWith(".env.template") || lower.endsWith(".env.sample"))
|
|
217
|
+
return false;
|
|
218
|
+
const base = lower.split("/").pop() || "";
|
|
219
|
+
for (const pat of patterns) {
|
|
220
|
+
if (pat.includes(".env") && base.startsWith(".env"))
|
|
221
|
+
return true;
|
|
222
|
+
if (pat.includes(".secrets") && lower.includes(".secrets"))
|
|
223
|
+
return true;
|
|
224
|
+
if (pat.includes("*.pem") && lower.endsWith(".pem"))
|
|
225
|
+
return true;
|
|
226
|
+
if (pat.includes("*.key") && lower.endsWith(".key"))
|
|
227
|
+
return true;
|
|
228
|
+
if (pat.includes(".aws") && lower.includes(".aws"))
|
|
229
|
+
return true;
|
|
230
|
+
if (pat.includes(".ssh") && lower.includes(".ssh"))
|
|
231
|
+
return true;
|
|
232
|
+
if (pat.includes("credentials.json") && lower.endsWith("credentials.json"))
|
|
233
|
+
return true;
|
|
234
|
+
if (pat.includes(".npmrc") && lower.endsWith(".npmrc"))
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (/\.(pem|key)$/i.test(normalized))
|
|
238
|
+
return true;
|
|
239
|
+
if (base.startsWith(".env"))
|
|
240
|
+
return true;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
function extractPathsFromBash(cmd) {
|
|
244
|
+
if (!cmd)
|
|
245
|
+
return [];
|
|
246
|
+
const normalized = cmd.replace(/&&/g, ";").replace(/\|\|/g, ";");
|
|
247
|
+
const segments = normalized.split(/[|;><\n]+/);
|
|
248
|
+
const paths = [];
|
|
249
|
+
for (const seg of segments) {
|
|
250
|
+
const trimmed = seg.trim();
|
|
251
|
+
if (!trimmed)
|
|
252
|
+
continue;
|
|
253
|
+
const tokens = trimmed.match(/(?:[^\s"'`\\]+|"[^"]*"|'[^']*'|`[^`]*`)+/g) || [];
|
|
254
|
+
for (let token of tokens) {
|
|
255
|
+
const stripped = token.replace(/["'`]/g, "").replace(/\\/g, "");
|
|
256
|
+
if (!stripped)
|
|
257
|
+
continue;
|
|
258
|
+
if (["cat", "grep", "ls", "echo", "awk", "sed", "cut", "head", "tail", "wc", "find", "xargs", "bash", "sh", "zsh", "env", "printenv", "node", "bun", "npm", "npx", "ls"].includes(stripped))
|
|
259
|
+
continue;
|
|
260
|
+
if (stripped.startsWith("-"))
|
|
261
|
+
continue;
|
|
262
|
+
const lower = stripped.toLowerCase();
|
|
263
|
+
if (stripped.includes("/") || stripped.includes(".") || lower.startsWith(".env") || lower.includes(".secrets") || lower.endsWith(".pem") || lower.endsWith(".key") || lower.includes(".aws") || lower.includes(".ssh") || lower.endsWith("credentials.json") || lower.endsWith(".npmrc")) {
|
|
264
|
+
const cleaned = stripped.replace(/[,:;)\]]+$/, "");
|
|
265
|
+
if (cleaned)
|
|
266
|
+
paths.push(cleaned);
|
|
267
|
+
} else if (stripped === ".env") {
|
|
268
|
+
paths.push(stripped);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return [...new Set(paths)];
|
|
273
|
+
}
|
|
274
|
+
function getSensitivePatterns() {
|
|
275
|
+
const raw = process.env.OSTACKY_SENSITIVE_PATTERNS;
|
|
276
|
+
if (!raw)
|
|
277
|
+
return SENSITIVE_DEFAULT;
|
|
278
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
279
|
+
}
|
|
280
|
+
var SENSITIVE_DEFAULT, BASH_SENSITIVE_RE;
|
|
281
|
+
var init_security = __esm(() => {
|
|
282
|
+
SENSITIVE_DEFAULT = [
|
|
283
|
+
"**/.env*",
|
|
284
|
+
"**/.secrets/**",
|
|
285
|
+
"**/*.pem",
|
|
286
|
+
"**/*.key",
|
|
287
|
+
"**/.aws/**",
|
|
288
|
+
"**/.ssh/**",
|
|
289
|
+
"**/credentials.json",
|
|
290
|
+
"**/.npmrc"
|
|
291
|
+
];
|
|
292
|
+
BASH_SENSITIVE_RE = /(?:^|[^a-zA-Z0-9_.-])(\.env(\b|[_.-])|\.secrets\b|\.pem\b|\.key\b|credentials\.json|\.aws\b|\.ssh\b|\.npmrc\b)/i;
|
|
293
|
+
});
|
|
205
294
|
|
|
206
295
|
// src/fs.ts
|
|
207
296
|
import {
|
|
@@ -235,6 +324,11 @@ function findOpenCodeDir(startDir = process.cwd()) {
|
|
|
235
324
|
}
|
|
236
325
|
}
|
|
237
326
|
function findProjectRoot(startDir = process.cwd()) {
|
|
327
|
+
try {
|
|
328
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8", cwd: startDir, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
329
|
+
if (out && existsSync(out))
|
|
330
|
+
return resolve(out);
|
|
331
|
+
} catch {}
|
|
238
332
|
let current = resolve(startDir);
|
|
239
333
|
while (true) {
|
|
240
334
|
if (existsSync(join(current, ".opencode")) || existsSync(join(current, ".git"))) {
|
|
@@ -546,31 +640,31 @@ var init_fs = __esm(() => {
|
|
|
546
640
|
var manifest_default;
|
|
547
641
|
var init_manifest = __esm(() => {
|
|
548
642
|
manifest_default = {
|
|
549
|
-
version: "0.7.
|
|
643
|
+
version: "0.7.4",
|
|
550
644
|
repo: "JaimeHoracio/Ostacky",
|
|
551
|
-
tag: "v0.7.
|
|
645
|
+
tag: "v0.7.4",
|
|
552
646
|
agents: [
|
|
553
647
|
{
|
|
554
648
|
name: "ostacky",
|
|
555
649
|
file: "assets/agents/ostacky.md",
|
|
556
|
-
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.7.
|
|
557
|
-
version: "0.7.
|
|
558
|
-
sha256: "
|
|
650
|
+
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.7.4: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
|
|
651
|
+
version: "0.7.4",
|
|
652
|
+
sha256: "ffda231d1a9df1693f32460bdb6811077f7f53ba7902b2616d82231a3d6d43bb"
|
|
559
653
|
}
|
|
560
654
|
],
|
|
561
655
|
commands: [
|
|
562
656
|
{
|
|
563
657
|
name: "install-stack",
|
|
564
658
|
file: "assets/commands/install-stack.md",
|
|
565
|
-
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.
|
|
566
|
-
version: "0.7.
|
|
567
|
-
sha256: "
|
|
659
|
+
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.4: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
|
|
660
|
+
version: "0.7.4",
|
|
661
|
+
sha256: "6887499074a82c3b4538e38ca501290cacd3afad676bd127662fed77e268f299"
|
|
568
662
|
},
|
|
569
663
|
{
|
|
570
664
|
name: "opsx-sync",
|
|
571
665
|
file: "assets/commands/opsx-sync.md",
|
|
572
666
|
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
573
|
-
version: "0.7.
|
|
667
|
+
version: "0.7.4",
|
|
574
668
|
sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
|
|
575
669
|
}
|
|
576
670
|
],
|
|
@@ -578,15 +672,15 @@ var init_manifest = __esm(() => {
|
|
|
578
672
|
{
|
|
579
673
|
name: "ostacky-controller",
|
|
580
674
|
file: "assets/mcp/ostacky-controller/",
|
|
581
|
-
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.
|
|
582
|
-
version: "0.7.
|
|
583
|
-
sha256: "
|
|
675
|
+
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.4: 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.",
|
|
676
|
+
version: "0.7.4",
|
|
677
|
+
sha256: "4f86b6829293024caeb3d09c83b61aee158c383a7bb053a49e5a33c7c55862d1"
|
|
584
678
|
},
|
|
585
679
|
{
|
|
586
680
|
name: "openspec",
|
|
587
681
|
file: "assets/mcp/openspec/",
|
|
588
682
|
description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
|
|
589
|
-
version: "0.7.
|
|
683
|
+
version: "0.7.4",
|
|
590
684
|
sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
|
|
591
685
|
}
|
|
592
686
|
],
|
|
@@ -595,106 +689,106 @@ var init_manifest = __esm(() => {
|
|
|
595
689
|
name: "brainstorming",
|
|
596
690
|
file: "assets/skills/brainstorming/SKILL.md",
|
|
597
691
|
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)",
|
|
598
|
-
version: "0.7.
|
|
599
|
-
sha256: "
|
|
692
|
+
version: "0.7.4",
|
|
693
|
+
sha256: "7b0020f31f346d8f2070219930cfb9341135d40941195f0ea50c3bd7f4a42990"
|
|
600
694
|
},
|
|
601
695
|
{
|
|
602
696
|
name: "execution-mode-evaluation",
|
|
603
697
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
604
698
|
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
605
|
-
version: "0.7.
|
|
606
|
-
sha256: "
|
|
699
|
+
version: "0.7.4",
|
|
700
|
+
sha256: "e17f1e0309572841dc1fa537b1f32c7468eb70b3acf453b9ff5cc4fa8d94fd39"
|
|
607
701
|
},
|
|
608
702
|
{
|
|
609
703
|
name: "tdd",
|
|
610
704
|
file: "assets/skills/tdd/SKILL.md",
|
|
611
705
|
description: "Skill de test-driven development (Superpowers)",
|
|
612
|
-
version: "0.7.
|
|
706
|
+
version: "0.7.4",
|
|
613
707
|
sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
|
|
614
708
|
},
|
|
615
709
|
{
|
|
616
710
|
name: "subagent-driven-development",
|
|
617
711
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
618
712
|
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
619
|
-
version: "0.7.
|
|
713
|
+
version: "0.7.4",
|
|
620
714
|
sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
|
|
621
715
|
},
|
|
622
716
|
{
|
|
623
717
|
name: "dispatching-parallel-agents",
|
|
624
718
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
625
719
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
626
|
-
version: "0.7.
|
|
720
|
+
version: "0.7.4",
|
|
627
721
|
sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
|
|
628
722
|
},
|
|
629
723
|
{
|
|
630
724
|
name: "review",
|
|
631
725
|
file: "assets/skills/review/SKILL.md",
|
|
632
726
|
description: "Skill de revisión de código (Superpowers)",
|
|
633
|
-
version: "0.7.
|
|
727
|
+
version: "0.7.4",
|
|
634
728
|
sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
|
|
635
729
|
},
|
|
636
730
|
{
|
|
637
731
|
name: "receiving-code-review",
|
|
638
732
|
file: "assets/skills/receiving-code-review/SKILL.md",
|
|
639
733
|
description: "Skill de recibir y procesar feedback de code review",
|
|
640
|
-
version: "0.7.
|
|
734
|
+
version: "0.7.4",
|
|
641
735
|
sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
|
|
642
736
|
},
|
|
643
737
|
{
|
|
644
738
|
name: "openspec-propose",
|
|
645
739
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
646
740
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
647
|
-
version: "0.7.
|
|
741
|
+
version: "0.7.4",
|
|
648
742
|
sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
|
|
649
743
|
},
|
|
650
744
|
{
|
|
651
745
|
name: "openspec-apply-change",
|
|
652
746
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
653
747
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
654
|
-
version: "0.7.
|
|
748
|
+
version: "0.7.4",
|
|
655
749
|
sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
|
|
656
750
|
},
|
|
657
751
|
{
|
|
658
752
|
name: "openspec-archive-change",
|
|
659
753
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
660
754
|
description: "Skill de archivo de change (OpenSpec)",
|
|
661
|
-
version: "0.7.
|
|
755
|
+
version: "0.7.4",
|
|
662
756
|
sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
|
|
663
757
|
},
|
|
664
758
|
{
|
|
665
759
|
name: "openspec-explore",
|
|
666
760
|
file: "assets/skills/openspec-explore/SKILL.md",
|
|
667
761
|
description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
|
|
668
|
-
version: "0.7.
|
|
762
|
+
version: "0.7.4",
|
|
669
763
|
sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
|
|
670
764
|
},
|
|
671
765
|
{
|
|
672
766
|
name: "using-git-worktrees",
|
|
673
767
|
file: "assets/skills/using-git-worktrees/SKILL.md",
|
|
674
768
|
description: "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
675
|
-
version: "0.7.
|
|
676
|
-
sha256: "
|
|
769
|
+
version: "0.7.4",
|
|
770
|
+
sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
|
|
677
771
|
},
|
|
678
772
|
{
|
|
679
773
|
name: "using-superpowers",
|
|
680
774
|
file: "assets/skills/using-superpowers/SKILL.md",
|
|
681
775
|
description: "Skill de orquestación de Superpowers skills",
|
|
682
|
-
version: "0.7.
|
|
776
|
+
version: "0.7.4",
|
|
683
777
|
sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
|
|
684
778
|
},
|
|
685
779
|
{
|
|
686
780
|
name: "writing-skills",
|
|
687
781
|
file: "assets/skills/writing-skills/SKILL.md",
|
|
688
782
|
description: "Skill de creación y edición de skills",
|
|
689
|
-
version: "0.7.
|
|
783
|
+
version: "0.7.4",
|
|
690
784
|
sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
|
|
691
785
|
},
|
|
692
786
|
{
|
|
693
787
|
name: "graceful-degradation",
|
|
694
788
|
file: "assets/skills/graceful-degradation/SKILL.md",
|
|
695
789
|
description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
|
|
696
|
-
version: "0.7.
|
|
697
|
-
sha256: "
|
|
790
|
+
version: "0.7.4",
|
|
791
|
+
sha256: "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
|
|
698
792
|
}
|
|
699
793
|
]
|
|
700
794
|
};
|
|
@@ -835,12 +929,12 @@ var init_github = __esm(() => {
|
|
|
835
929
|
// src/lockfile.ts
|
|
836
930
|
var exports_lockfile = {};
|
|
837
931
|
__export(exports_lockfile, {
|
|
838
|
-
|
|
839
|
-
removeFromLockfile: () => removeFromLockfile,
|
|
840
|
-
readLockfile: () => readLockfile,
|
|
841
|
-
getLockfilePath: () => getLockfilePath,
|
|
932
|
+
clearLockfile: () => clearLockfile,
|
|
842
933
|
getInstalledVersion: () => getInstalledVersion,
|
|
843
|
-
|
|
934
|
+
getLockfilePath: () => getLockfilePath,
|
|
935
|
+
readLockfile: () => readLockfile,
|
|
936
|
+
removeFromLockfile: () => removeFromLockfile,
|
|
937
|
+
writeLockfile: () => writeLockfile
|
|
844
938
|
});
|
|
845
939
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
846
940
|
import { join as join4 } from "path";
|
|
@@ -905,16 +999,16 @@ var init_lockfile = __esm(() => {
|
|
|
905
999
|
// src/config.ts
|
|
906
1000
|
var exports_config = {};
|
|
907
1001
|
__export(exports_config, {
|
|
908
|
-
|
|
909
|
-
stripJsoncComments: () => stripJsoncComments,
|
|
910
|
-
setMcpEntryAtProjectRoot: () => setMcpEntryAtProjectRoot,
|
|
911
|
-
setMcpEntry: () => setMcpEntry,
|
|
912
|
-
readOpenCodeConfig: () => readOpenCodeConfig,
|
|
913
|
-
patchOpenCodeConfig: () => patchOpenCodeConfig,
|
|
914
|
-
findOpenCodeConfig: () => findOpenCodeConfig,
|
|
915
|
-
ensureOpenCodeConfig: () => ensureOpenCodeConfig,
|
|
1002
|
+
ensureMcpEntry: () => ensureMcpEntry,
|
|
916
1003
|
ensureMcpEntryAtProjectRoot: () => ensureMcpEntryAtProjectRoot,
|
|
917
|
-
|
|
1004
|
+
ensureOpenCodeConfig: () => ensureOpenCodeConfig,
|
|
1005
|
+
findOpenCodeConfig: () => findOpenCodeConfig,
|
|
1006
|
+
patchOpenCodeConfig: () => patchOpenCodeConfig,
|
|
1007
|
+
readOpenCodeConfig: () => readOpenCodeConfig,
|
|
1008
|
+
setMcpEntry: () => setMcpEntry,
|
|
1009
|
+
setMcpEntryAtProjectRoot: () => setMcpEntryAtProjectRoot,
|
|
1010
|
+
stripJsoncComments: () => stripJsoncComments,
|
|
1011
|
+
writeOpenCodeConfig: () => writeOpenCodeConfig
|
|
918
1012
|
});
|
|
919
1013
|
import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
920
1014
|
import { join as join5 } from "path";
|
|
@@ -1062,17 +1156,17 @@ var init_config = __esm(() => {
|
|
|
1062
1156
|
// src/stack.ts
|
|
1063
1157
|
var exports_stack = {};
|
|
1064
1158
|
__export(exports_stack, {
|
|
1065
|
-
|
|
1066
|
-
uninstallStackConfig: () => uninstallStackConfig,
|
|
1067
|
-
uninstallEngramConfig: () => uninstallEngramConfig,
|
|
1068
|
-
setupOpenSpec: () => setupOpenSpec,
|
|
1069
|
-
setupContext7: () => setupContext7,
|
|
1070
|
-
installStack: () => installStack,
|
|
1071
|
-
installEngram: () => installEngram,
|
|
1072
|
-
installCodeGraph: () => installCodeGraph,
|
|
1073
|
-
buildLocalMcpCommand: () => buildLocalMcpCommand,
|
|
1159
|
+
OPENSPEC_NPM_PACKAGE: () => OPENSPEC_NPM_PACKAGE,
|
|
1074
1160
|
buildEngramDownloadUrl: () => buildEngramDownloadUrl,
|
|
1075
|
-
|
|
1161
|
+
buildLocalMcpCommand: () => buildLocalMcpCommand,
|
|
1162
|
+
installCodeGraph: () => installCodeGraph,
|
|
1163
|
+
installEngram: () => installEngram,
|
|
1164
|
+
installStack: () => installStack,
|
|
1165
|
+
setupContext7: () => setupContext7,
|
|
1166
|
+
setupOpenSpec: () => setupOpenSpec,
|
|
1167
|
+
uninstallEngramConfig: () => uninstallEngramConfig,
|
|
1168
|
+
uninstallStackConfig: () => uninstallStackConfig,
|
|
1169
|
+
verifyMcpServers: () => verifyMcpServers
|
|
1076
1170
|
});
|
|
1077
1171
|
import { chmodSync, copyFileSync as copyFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
1078
1172
|
import { basename as basename2, dirname as dirname5, join as join7, resolve as resolve2 } from "path";
|
|
@@ -1494,7 +1588,7 @@ var init_stack = __esm(() => {
|
|
|
1494
1588
|
// package.json
|
|
1495
1589
|
var package_default = {
|
|
1496
1590
|
name: "ostacky",
|
|
1497
|
-
version: "0.7.
|
|
1591
|
+
version: "0.7.4",
|
|
1498
1592
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1499
1593
|
type: "module",
|
|
1500
1594
|
bin: {
|
|
@@ -1543,23 +1637,23 @@ var package_default = {
|
|
|
1543
1637
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
1544
1638
|
var exports_dist = {};
|
|
1545
1639
|
__export(exports_dist, {
|
|
1546
|
-
|
|
1547
|
-
text: () => ue,
|
|
1548
|
-
tasks: () => xe,
|
|
1549
|
-
spinner: () => L2,
|
|
1550
|
-
selectKey: () => he,
|
|
1551
|
-
select: () => de,
|
|
1552
|
-
password: () => $e,
|
|
1553
|
-
outro: () => fe,
|
|
1554
|
-
note: () => ye,
|
|
1555
|
-
multiselect: () => pe,
|
|
1556
|
-
log: () => v2,
|
|
1557
|
-
isCancel: () => BD,
|
|
1558
|
-
intro: () => we,
|
|
1559
|
-
groupMultiselect: () => ge,
|
|
1560
|
-
group: () => be,
|
|
1640
|
+
cancel: () => ve,
|
|
1561
1641
|
confirm: () => me,
|
|
1562
|
-
|
|
1642
|
+
group: () => be,
|
|
1643
|
+
groupMultiselect: () => ge,
|
|
1644
|
+
intro: () => we,
|
|
1645
|
+
isCancel: () => BD,
|
|
1646
|
+
log: () => v2,
|
|
1647
|
+
multiselect: () => pe,
|
|
1648
|
+
note: () => ye,
|
|
1649
|
+
outro: () => fe,
|
|
1650
|
+
password: () => $e,
|
|
1651
|
+
select: () => de,
|
|
1652
|
+
selectKey: () => he,
|
|
1653
|
+
spinner: () => L2,
|
|
1654
|
+
tasks: () => xe,
|
|
1655
|
+
text: () => ue,
|
|
1656
|
+
updateSettings: () => hD
|
|
1563
1657
|
});
|
|
1564
1658
|
import { stripVTControlCharacters as T2 } from "node:util";
|
|
1565
1659
|
|
|
@@ -2494,7 +2588,6 @@ import { dirname as dirname7 } from "path";
|
|
|
2494
2588
|
init_github();
|
|
2495
2589
|
init_fs();
|
|
2496
2590
|
init_lockfile();
|
|
2497
|
-
import { homedir as homedir2 } from "os";
|
|
2498
2591
|
|
|
2499
2592
|
// src/installer.ts
|
|
2500
2593
|
init_github();
|
|
@@ -2670,7 +2763,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2670
2763
|
params: {
|
|
2671
2764
|
protocolVersion: "2025-03-26",
|
|
2672
2765
|
capabilities: {},
|
|
2673
|
-
clientInfo: { name: "ostacky-installer", version: "0.7.
|
|
2766
|
+
clientInfo: { name: "ostacky-installer", version: "0.7.4" }
|
|
2674
2767
|
}
|
|
2675
2768
|
});
|
|
2676
2769
|
});
|
|
@@ -3029,18 +3122,19 @@ function printPostInstallSteps() {
|
|
|
3029
3122
|
async function resolveOpenCodePaths(scope) {
|
|
3030
3123
|
if (scope === "local" || scope === "global" || scope === "auto") {
|
|
3031
3124
|
const dir2 = getOpenCodeDirForScope(scope);
|
|
3125
|
+
const isGlobalDir = dir2.replace(/\\/g, "/") === getGlobalOpenCodeDir().replace(/\\/g, "/");
|
|
3032
3126
|
try {
|
|
3033
3127
|
const paths = ensureOpenCodePaths(dir2);
|
|
3034
3128
|
if (scope === "global")
|
|
3035
3129
|
ye(dir2, "Instalación global");
|
|
3036
3130
|
else if (scope === "auto")
|
|
3037
|
-
ye(dir2, `Scope auto → ${
|
|
3131
|
+
ye(dir2, `Scope auto → ${isGlobalDir ? "global" : "local"}`);
|
|
3038
3132
|
else
|
|
3039
3133
|
ye(dir2, "Instalación local");
|
|
3040
3134
|
return paths;
|
|
3041
3135
|
} catch (e2) {
|
|
3042
3136
|
const msg = e2.message ?? "";
|
|
3043
|
-
if (scope === "global" && (msg.includes("EACCES") || msg.includes("permission"))) {
|
|
3137
|
+
if ((scope === "global" || scope === "auto" && isGlobalDir) && (msg.includes("EACCES") || msg.toLowerCase().includes("permission"))) {
|
|
3044
3138
|
v2.warn(`No se pudo escribir en global (${dir2}): ${msg}. ¿Instalar local?`);
|
|
3045
3139
|
const retry = await me({ message: "¿Reintentar como instalación local?" });
|
|
3046
3140
|
onCancel(retry);
|
|
@@ -3082,7 +3176,9 @@ async function resolveOpenCodePaths(scope) {
|
|
|
3082
3176
|
}
|
|
3083
3177
|
}
|
|
3084
3178
|
function isGlobalScope(paths) {
|
|
3085
|
-
|
|
3179
|
+
const globalDir = getGlobalOpenCodeDir().replace(/\\/g, "/");
|
|
3180
|
+
const root = paths.root.replace(/\\/g, "/");
|
|
3181
|
+
return root === globalDir || root.startsWith(globalDir + "/");
|
|
3086
3182
|
}
|
|
3087
3183
|
function getOrphanedItems(manifest, paths) {
|
|
3088
3184
|
const lockfile = readLockfile(paths.root);
|
|
@@ -3200,7 +3296,7 @@ async function doInstallStack(toolsDir, projectRoot) {
|
|
|
3200
3296
|
async function doInstallAll(manifest, paths) {
|
|
3201
3297
|
const spin = L2();
|
|
3202
3298
|
let errors = 0;
|
|
3203
|
-
const isGlobal =
|
|
3299
|
+
const isGlobal = isGlobalScope(paths);
|
|
3204
3300
|
if (!isGlobal) {
|
|
3205
3301
|
ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
|
|
3206
3302
|
} else {
|
|
@@ -3934,6 +4030,13 @@ async function runInstallStackCommand(scope) {
|
|
|
3934
4030
|
fe("Cancelado.");
|
|
3935
4031
|
return;
|
|
3936
4032
|
}
|
|
4033
|
+
if (isGlobalScope(paths)) {
|
|
4034
|
+
v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
4035
|
+
v2.info(`Scope resuelto a global (${paths.root}) — el stack debe instalarse por proyecto local.`);
|
|
4036
|
+
fe("Cancelado.");
|
|
4037
|
+
process.exitCode = 1;
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
3937
4040
|
ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
|
|
3938
4041
|
const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
|
|
3939
4042
|
if (!stackOk)
|
|
@@ -4127,6 +4230,9 @@ async function runUninstallMcpCommand(name, scope) {
|
|
|
4127
4230
|
}
|
|
4128
4231
|
|
|
4129
4232
|
// src/cli.ts
|
|
4233
|
+
init_fs();
|
|
4234
|
+
import { existsSync as existsSync8, statSync as statSync3, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
4235
|
+
import { join as join11, dirname as dirname8 } from "node:path";
|
|
4130
4236
|
var HELP = `
|
|
4131
4237
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4132
4238
|
|
|
@@ -4139,6 +4245,8 @@ Uso:
|
|
|
4139
4245
|
npx ostacky add mcp [--scope ...] Agregar MCP server(s)
|
|
4140
4246
|
npx ostacky install-stack [--scope local|auto] Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram, Context7) — global bloquea con error
|
|
4141
4247
|
npx ostacky uninstall-stack [--scope local|global|auto] Remover la configuración del stack del proyecto
|
|
4248
|
+
npx ostacky doctor Diagnostica locks, tools, state health
|
|
4249
|
+
npx ostacky status [--json] Muestra estado del controller sin MCP
|
|
4142
4250
|
npx ostacky update [--scope ...] Actualizar instalación
|
|
4143
4251
|
npx ostacky uninstall [--scope ...] Desinstalar todo
|
|
4144
4252
|
npx ostacky uninstall agent [--scope ...] Desinstalar agente(s)
|
|
@@ -4187,6 +4295,219 @@ function withoutScopeArgs(argv) {
|
|
|
4187
4295
|
var scope = parseScopeArg();
|
|
4188
4296
|
var argvNoScope = withoutScopeArgs(process.argv);
|
|
4189
4297
|
var [, , cmd, subcmd] = argvNoScope;
|
|
4298
|
+
async function runDoctorCommand() {
|
|
4299
|
+
const cwd = process.cwd();
|
|
4300
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4301
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4302
|
+
let hasError = false;
|
|
4303
|
+
let hasWarn = false;
|
|
4304
|
+
const check = (label, ok, warn = false) => {
|
|
4305
|
+
if (ok)
|
|
4306
|
+
console.log(`✅ ${label}: OK`);
|
|
4307
|
+
else if (warn) {
|
|
4308
|
+
console.log(`⚠️ ${label}`);
|
|
4309
|
+
hasWarn = true;
|
|
4310
|
+
} else {
|
|
4311
|
+
console.log(`❌ ${label}`);
|
|
4312
|
+
hasError = true;
|
|
4313
|
+
}
|
|
4314
|
+
};
|
|
4315
|
+
try {
|
|
4316
|
+
if (!existsSync8(statePath)) {
|
|
4317
|
+
check("controller: state file missing", false, true);
|
|
4318
|
+
} else {
|
|
4319
|
+
const stat = statSync3(statePath);
|
|
4320
|
+
const raw = readFileSync6(statePath, "utf-8");
|
|
4321
|
+
const parsed = JSON.parse(raw);
|
|
4322
|
+
check(`controller: OK (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`, true);
|
|
4323
|
+
if (parsed.degraded) {
|
|
4324
|
+
console.log("⚠️ degraded: true (persistido)");
|
|
4325
|
+
hasWarn = true;
|
|
4326
|
+
}
|
|
4327
|
+
if (parsed.degradedEditsCount > 0)
|
|
4328
|
+
console.log(`⚠️ degraded: confirmation not audited in controller (degradedEditsCount=${parsed.degradedEditsCount})`);
|
|
4329
|
+
if (parsed.codegraphBypassCount > 0)
|
|
4330
|
+
console.log(`⚠️ codegraphBypassCount=${parsed.codegraphBypassCount} (inefficient: codegraph bypass)`);
|
|
4331
|
+
if (parsed.stateOversizedCount > 0)
|
|
4332
|
+
console.log(`⚠️ stateOversizedCount=${parsed.stateOversizedCount} snapshots perdidos`);
|
|
4333
|
+
if (parsed.sensitiveAccess)
|
|
4334
|
+
console.log(`ℹ️ sensitiveAccess: allowed=${parsed.sensitiveAccess.allowed || 0} denied=${parsed.sensitiveAccess.denied || 0} blocked=${parsed.sensitiveAccess.blockedAttempts || 0}`);
|
|
4335
|
+
if (parsed.sensitivePatterns)
|
|
4336
|
+
console.log(`ℹ️ sensitivePatterns: ${parsed.sensitivePatterns.join(", ")}`);
|
|
4337
|
+
if (parsed.allowedFiles && Object.keys(parsed.allowedFiles).length)
|
|
4338
|
+
console.log(`ℹ️ allowedFiles: ${Object.keys(parsed.allowedFiles).join(", ")}`);
|
|
4339
|
+
if (parsed.deniedFiles && Object.keys(parsed.deniedFiles).length) {
|
|
4340
|
+
console.log(`ℹ️ denied files: ${Object.keys(parsed.deniedFiles).join(", ")} (denied by user)`);
|
|
4341
|
+
}
|
|
4342
|
+
if (parsed.staleContentAttempts > 0)
|
|
4343
|
+
console.log(`⚠️ staleContentAttempts=${parsed.staleContentAttempts}`);
|
|
4344
|
+
if (parsed.completeWithoutValidateCount > 0)
|
|
4345
|
+
console.log(`⚠️ completeWithoutValidateCount=${parsed.completeWithoutValidateCount}`);
|
|
4346
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
4347
|
+
console.log("⚠️ state file >2MB (oversized)");
|
|
4348
|
+
hasWarn = true;
|
|
4349
|
+
}
|
|
4350
|
+
const auditSize = (parsed.audit || []).length;
|
|
4351
|
+
if (auditSize > 500)
|
|
4352
|
+
console.log(`⚠️ audit large: ${auditSize}`);
|
|
4353
|
+
try {
|
|
4354
|
+
const { statfsSync } = await import("node:fs");
|
|
4355
|
+
if (typeof statfsSync === "function") {
|
|
4356
|
+
const s = statfsSync(dirname8(statePath));
|
|
4357
|
+
const freeMB = Math.floor(s.bfree * s.bsize / 1048576);
|
|
4358
|
+
if (freeMB < 100) {
|
|
4359
|
+
console.log(`⚠️ Disco casi lleno: ${freeMB}MB libres`);
|
|
4360
|
+
hasWarn = true;
|
|
4361
|
+
} else
|
|
4362
|
+
console.log(`ℹ️ diskFreeMB: ${freeMB}`);
|
|
4363
|
+
}
|
|
4364
|
+
} catch {}
|
|
4365
|
+
if ((parsed.state === "EXECUTING_INLINE" || parsed.state === "EXECUTING_SUBAGENTS") && parsed.expectedTasks) {
|
|
4366
|
+
const pending = parsed.expectedTasks.filter((id) => !parsed.tasks?.[id] || parsed.tasks[id].status !== "COMPLETED").length;
|
|
4367
|
+
const lastHandoffAge = parsed.lastHandoff ? Date.now() - parsed.lastHandoff.ts : Infinity;
|
|
4368
|
+
if (pending === 0 && lastHandoffAge > 60000) {
|
|
4369
|
+
console.log("⚠️ EXECUTING_* con pending==0 y lastHandoff >60s sin progreso — sugerir implementation_complete manual");
|
|
4370
|
+
hasWarn = true;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
} catch (e2) {
|
|
4375
|
+
check(`controller: ${e2.message}`, false);
|
|
4376
|
+
}
|
|
4377
|
+
try {
|
|
4378
|
+
const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
|
|
4379
|
+
const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
|
|
4380
|
+
if (existsSync8(lockPid) || existsSync8(lockTs)) {
|
|
4381
|
+
let ageStr = "";
|
|
4382
|
+
try {
|
|
4383
|
+
const ts = parseInt(readFileSync6(lockTs, "utf-8"), 10);
|
|
4384
|
+
const age = Date.now() - ts;
|
|
4385
|
+
ageStr = `${Math.floor(age / 1000)}s`;
|
|
4386
|
+
const pid = readFileSync6(lockPid, "utf-8").trim();
|
|
4387
|
+
let alive = false;
|
|
4388
|
+
try {
|
|
4389
|
+
process.kill(parseInt(pid, 10), 0);
|
|
4390
|
+
alive = true;
|
|
4391
|
+
} catch {}
|
|
4392
|
+
check(`lock: PID ${pid} age ${ageStr} alive=${alive}`, !alive || age > 15000, true);
|
|
4393
|
+
} catch {
|
|
4394
|
+
check("lock: exists (no timestamp)", false, true);
|
|
4395
|
+
}
|
|
4396
|
+
} else {
|
|
4397
|
+
check("lock: no active lock", true);
|
|
4398
|
+
}
|
|
4399
|
+
} catch {
|
|
4400
|
+
check("lock: check failed", false, true);
|
|
4401
|
+
}
|
|
4402
|
+
const tools = ["codegraph", "engram"];
|
|
4403
|
+
for (const t of tools) {
|
|
4404
|
+
const p2 = join11(opencodeDir, "tools", t, "bin", t);
|
|
4405
|
+
const pExe = p2 + ".exe";
|
|
4406
|
+
check(`tool ${t}: ${existsSync8(p2) || existsSync8(pExe) ? "found" : "missing"}`, existsSync8(p2) || existsSync8(pExe), true);
|
|
4407
|
+
}
|
|
4408
|
+
try {
|
|
4409
|
+
const manifest = JSON.parse(readFileSync6(join11(cwd, "manifest.json"), "utf-8"));
|
|
4410
|
+
const expected = manifest.mcpServers?.find((x2) => x2.name === "ostacky-controller")?.sha256;
|
|
4411
|
+
if (expected) {
|
|
4412
|
+
const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4413
|
+
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4414
|
+
if (expected !== actual)
|
|
4415
|
+
console.log(" Run: bun run hash:update");
|
|
4416
|
+
}
|
|
4417
|
+
} catch {
|
|
4418
|
+
check("manifest: not found", false, true);
|
|
4419
|
+
}
|
|
4420
|
+
if (existsSync8(statePath)) {
|
|
4421
|
+
try {
|
|
4422
|
+
const s = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4423
|
+
if (s.allowedFiles || s.deniedFiles) {}
|
|
4424
|
+
} catch {}
|
|
4425
|
+
}
|
|
4426
|
+
try {
|
|
4427
|
+
const cacheDir = join11(opencodeDir, "cache", "codegraph");
|
|
4428
|
+
if (!existsSync8(cacheDir)) {
|
|
4429
|
+
console.log("ℹ️ cache: no cache dir yet (ok)");
|
|
4430
|
+
} else {
|
|
4431
|
+
const files = readdirSync3(cacheDir);
|
|
4432
|
+
let total = 0;
|
|
4433
|
+
for (const f2 of files) {
|
|
4434
|
+
try {
|
|
4435
|
+
total += statSync3(join11(cacheDir, f2)).size;
|
|
4436
|
+
} catch {}
|
|
4437
|
+
}
|
|
4438
|
+
const totalMB = (total / 1048576).toFixed(2);
|
|
4439
|
+
if (total > 52428800) {
|
|
4440
|
+
console.log(`⚠️ cache: ${totalMB}MB >50MB — LRU cleanup needed`);
|
|
4441
|
+
hasWarn = true;
|
|
4442
|
+
} else {
|
|
4443
|
+
console.log(`✅ cache: OK (${files.length} files, ${totalMB}MB)`);
|
|
4444
|
+
}
|
|
4445
|
+
try {
|
|
4446
|
+
const s = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4447
|
+
if (s.cacheHitCount !== undefined) {
|
|
4448
|
+
console.log(`ℹ️ cacheHitCount=${s.cacheHitCount} cacheMissCount=${s.cacheMissCount || 0} tokenSavingEstimate=${s.tokenSavingEstimate || 0}`);
|
|
4449
|
+
}
|
|
4450
|
+
} catch {}
|
|
4451
|
+
}
|
|
4452
|
+
} catch (e2) {
|
|
4453
|
+
console.log(`⚠️ cache: check failed ${e2.message}`);
|
|
4454
|
+
}
|
|
4455
|
+
try {
|
|
4456
|
+
const secPath = join11(cwd, "src", "security.ts");
|
|
4457
|
+
if (!existsSync8(secPath)) {
|
|
4458
|
+
console.log("⚠️ src/security.ts: missing (source-of-truth)");
|
|
4459
|
+
hasWarn = true;
|
|
4460
|
+
} else {
|
|
4461
|
+
const sec = readFileSync6(secPath, "utf-8");
|
|
4462
|
+
const hasSensitiveDefault = sec.includes("SENSITIVE_DEFAULT");
|
|
4463
|
+
const hasBashRe = sec.includes("BASH_SENSITIVE_RE");
|
|
4464
|
+
const hasIsSensitive = sec.includes("function isSensitive");
|
|
4465
|
+
const hasExtract = sec.includes("extractPathsFromBash");
|
|
4466
|
+
if (hasSensitiveDefault && hasBashRe && hasIsSensitive && hasExtract) {
|
|
4467
|
+
console.log("✅ src/security.ts: source-of-truth OK");
|
|
4468
|
+
} else {
|
|
4469
|
+
console.log("⚠️ src/security.ts: missing exports (SENSITIVE_DEFAULT/BASH_SENSITIVE_RE/isSensitive/extractPathsFromBash)");
|
|
4470
|
+
hasWarn = true;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
} catch {}
|
|
4474
|
+
try {
|
|
4475
|
+
const s = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4476
|
+
if (s.sensitiveAccess?.blockedAttempts > 0) {
|
|
4477
|
+
console.log(`ℹ️ sensitiveAccess: blockedAttempts includes bash (${s.sensitiveAccess.blockedAttempts})`);
|
|
4478
|
+
}
|
|
4479
|
+
} catch {}
|
|
4480
|
+
if (hasError)
|
|
4481
|
+
process.exit(1);
|
|
4482
|
+
if (hasWarn)
|
|
4483
|
+
process.exit(0);
|
|
4484
|
+
}
|
|
4485
|
+
async function runStatusCommand(args) {
|
|
4486
|
+
const isJson = args.includes("--json");
|
|
4487
|
+
const cwd = process.cwd();
|
|
4488
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4489
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4490
|
+
if (!existsSync8(statePath)) {
|
|
4491
|
+
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
try {
|
|
4495
|
+
const parsed = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4496
|
+
const completed = Object.values(parsed.tasks || {}).filter((t) => t.status === "COMPLETED").length;
|
|
4497
|
+
const expected = parsed.expectedTaskCount ?? parsed.expectedTasks?.length ?? Object.keys(parsed.tasks || {}).length;
|
|
4498
|
+
const degraded = parsed.degraded ? " degraded" : "";
|
|
4499
|
+
const lastHandoff = parsed.lastHandoff ? ` lastHandoff: ${parsed.lastHandoff.summary?.slice(0, 60)}` : "";
|
|
4500
|
+
if (isJson) {
|
|
4501
|
+
console.log(JSON.stringify({ state: parsed.state, revision: parsed.revision, degraded: !!parsed.degraded, tasks: `${completed}/${expected}`, lastHandoff: parsed.lastHandoff }, null, 2));
|
|
4502
|
+
} else {
|
|
4503
|
+
console.log(`${parsed.state} rev ${parsed.revision}${degraded} tasks ${completed}/${expected}${lastHandoff}`);
|
|
4504
|
+
if (parsed.lastProposal)
|
|
4505
|
+
console.log(`lastProposal: ${parsed.lastProposal.summary} shownToUser=${parsed.lastProposal.shownToUser}`);
|
|
4506
|
+
}
|
|
4507
|
+
} catch (e2) {
|
|
4508
|
+
console.log(`Error reading state: ${e2.message}`);
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4190
4511
|
async function main() {
|
|
4191
4512
|
switch (cmd) {
|
|
4192
4513
|
case "install":
|
|
@@ -4240,6 +4561,12 @@ async function main() {
|
|
|
4240
4561
|
process.exit(1);
|
|
4241
4562
|
}
|
|
4242
4563
|
break;
|
|
4564
|
+
case "doctor":
|
|
4565
|
+
await runDoctorCommand();
|
|
4566
|
+
break;
|
|
4567
|
+
case "status":
|
|
4568
|
+
await runStatusCommand(argvNoScope.slice(3));
|
|
4569
|
+
break;
|
|
4243
4570
|
case "--help":
|
|
4244
4571
|
case "-h":
|
|
4245
4572
|
console.log(HELP);
|