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/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
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
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
- verifyChecksum: () => verifyChecksum,
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
- sha256: () => sha256
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
- var init_security = () => {};
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.2",
643
+ version: "0.7.4",
550
644
  repo: "JaimeHoracio/Ostacky",
551
- tag: "v0.7.2",
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.2: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
557
- version: "0.7.2",
558
- sha256: "1b6a5cbb49f1c6e977241f8ab86d1a045fdb8a04111bbc75633429a131c71acd"
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.2: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
566
- version: "0.7.2",
567
- sha256: "7277d333bfe65e674b9696fa5f887854e35824eb5b88420dd0f5bbde3bca7add"
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.2",
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.2: 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.",
582
- version: "0.7.2",
583
- sha256: "34048819b8905dff24c731e02d813deb4fb7fbbb65e8b8df66e5c8db201492a0"
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.2",
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.2",
599
- sha256: "333810ca63450e8ab795e8c5a58e645101e788d79210ca8ac36e10d11be39585"
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.2",
606
- sha256: "edf6fca0af52cce83f8fd08e7db9b3d79cc130b53fca505e90d3fbaf4909338b"
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.2",
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.2",
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.2",
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.2",
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.2",
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.2",
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.2",
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.2",
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.2",
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.2",
676
- sha256: "11afa9123828fc5caa22137aff907cd99c751e99027fbd4c020751110a949281"
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.2",
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.2",
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.2",
697
- sha256: "376dc90fa27ab7e5947747f20ccf2cc76f9326c77b567fda388252a60d25c431"
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
- writeLockfile: () => writeLockfile,
839
- removeFromLockfile: () => removeFromLockfile,
840
- readLockfile: () => readLockfile,
841
- getLockfilePath: () => getLockfilePath,
932
+ clearLockfile: () => clearLockfile,
842
933
  getInstalledVersion: () => getInstalledVersion,
843
- clearLockfile: () => clearLockfile
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
- writeOpenCodeConfig: () => writeOpenCodeConfig,
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
- ensureMcpEntry: () => ensureMcpEntry
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
- verifyMcpServers: () => verifyMcpServers,
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
- OPENSPEC_NPM_PACKAGE: () => OPENSPEC_NPM_PACKAGE
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.2",
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
- updateSettings: () => hD,
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
- cancel: () => ve
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.2" }
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 → ${dir2.includes(homedir2()) && dir2.includes(".config") ? "global" : "local"}`);
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
- return paths.root === getGlobalOpenCodeDir() || paths.root.startsWith(getGlobalOpenCodeDir() + "/");
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 = paths.root === getGlobalOpenCodeDir() || paths.root.startsWith(getGlobalOpenCodeDir() + "/");
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);