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.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * security.ts — mirror de src/security.ts para plugin (self-contained, solo Sensitive guard)
3
+ * Generado desde src/security.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
4
+ */
5
+
6
+ // ─── Sensitive guard — source-of-truth (hardening-v2 D1) ───────────────
7
+
8
+ export const SENSITIVE_DEFAULT: string[] = [
9
+ "**/.env*",
10
+ "**/.secrets/**",
11
+ "**/*.pem",
12
+ "**/*.key",
13
+ "**/.aws/**",
14
+ "**/.ssh/**",
15
+ "**/credentials.json",
16
+ "**/.npmrc",
17
+ ];
18
+
19
+ /**
20
+ * Regex que detecta acceso sensible en comandos bash.
21
+ * Cubre .env, .secrets, .pem, .key, .aws, .ssh, credentials.json, .npmrc
22
+ * Normalizar antes (strip quotes/backslashes) y testear con `BASH_SENSITIVE_RE.test(normalized)`.
23
+ */
24
+ export const BASH_SENSITIVE_RE =
25
+ /(?:^|[^a-zA-Z0-9_.-])(\.env(\b|[_.-])|\.secrets\b|\.pem\b|\.key\b|credentials\.json|\.aws\b|\.ssh\b|\.npmrc\b)/i;
26
+
27
+ /**
28
+ * Returns true if filePath is considered sensitive given patterns.
29
+ * - Allowlist: .env.example / .env.template / .env.sample nunca son sensibles.
30
+ * - Normaliza backslashes a "/" para Windows.
31
+ * - patterns: array de globs como SENSITIVE_DEFAULT; si no se pasa usa SENSITIVE_DEFAULT.
32
+ */
33
+ export function isSensitive(filePath: string, patterns: string[] = SENSITIVE_DEFAULT): boolean {
34
+ if (!filePath) return false;
35
+ const normalized = filePath.replace(/\\/g, "/");
36
+ const lower = normalized.toLowerCase();
37
+ if (
38
+ lower.endsWith(".env.example") ||
39
+ lower.endsWith(".env.template") ||
40
+ lower.endsWith(".env.sample")
41
+ )
42
+ return false;
43
+ const base = lower.split("/").pop() || "";
44
+ for (const pat of patterns) {
45
+ if (pat.includes(".env") && base.startsWith(".env")) return true;
46
+ if (pat.includes(".secrets") && lower.includes(".secrets")) return true;
47
+ if (pat.includes("*.pem") && lower.endsWith(".pem")) return true;
48
+ if (pat.includes("*.key") && lower.endsWith(".key")) return true;
49
+ if (pat.includes(".aws") && lower.includes(".aws")) return true;
50
+ if (pat.includes(".ssh") && lower.includes(".ssh")) return true;
51
+ if (pat.includes("credentials.json") && lower.endsWith("credentials.json")) return true;
52
+ if (pat.includes(".npmrc") && lower.endsWith(".npmrc")) return true;
53
+ }
54
+ // fallback genérico por extensión
55
+ if (/\.(pem|key)$/i.test(normalized)) return true;
56
+ if (base.startsWith(".env")) return true;
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Extrae paths candidatos de un comando bash para chequear sensibilidad.
62
+ * Tokeniza por separadores shell `| ; && || > >> <` y respeta quotes.
63
+ * Normaliza quitando quotes y backslashes para detectar obfuscaciones como .e""nv.
64
+ */
65
+ export function extractPathsFromBash(cmd: string): string[] {
66
+ if (!cmd) return [];
67
+ // Normalizar && y || a ; para split único
68
+ const normalized = cmd.replace(/&&/g, ";").replace(/\|\|/g, ";");
69
+ // Split por separadores shell
70
+ const segments = normalized.split(/[|;><\n]+/);
71
+ const paths: string[] = [];
72
+ for (const seg of segments) {
73
+ const trimmed = seg.trim();
74
+ if (!trimmed) continue;
75
+ // Tokeniza respetando quotes — captura tokens entre quotes o sin espacios
76
+ const tokens = trimmed.match(/(?:[^\s"'`\\]+|"[^"]*"|'[^']*'|`[^`]*`)+/g) || [];
77
+ for (let token of tokens) {
78
+ // Normalización anti-obfuscación: quitar quotes y backslashes
79
+ const stripped = token.replace(/["'`]/g, "").replace(/\\/g, "");
80
+ if (!stripped) continue;
81
+ // Filtrar comandos comunes y flags
82
+ 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)) continue;
83
+ if (stripped.startsWith("-")) continue;
84
+ // Heurística de path: contiene / o . o es .env* o sensible
85
+ const lower = stripped.toLowerCase();
86
+ if (
87
+ stripped.includes("/") ||
88
+ stripped.includes(".") ||
89
+ lower.startsWith(".env") ||
90
+ lower.includes(".secrets") ||
91
+ lower.endsWith(".pem") ||
92
+ lower.endsWith(".key") ||
93
+ lower.includes(".aws") ||
94
+ lower.includes(".ssh") ||
95
+ lower.endsWith("credentials.json") ||
96
+ lower.endsWith(".npmrc")
97
+ ) {
98
+ // Limpiar trailing chars como : , )
99
+ const cleaned = stripped.replace(/[,:;)\]]+$/, "");
100
+ if (cleaned) paths.push(cleaned);
101
+ } else if (stripped === ".env") {
102
+ paths.push(stripped);
103
+ }
104
+ }
105
+ }
106
+ // También buscar paths con regex global como fallback
107
+ // (detecta .env en strings complejos)
108
+ return [...new Set(paths)];
109
+ }
110
+
111
+ /**
112
+ * Lee patrones sensibles desde env OSTACKY_SENSITIVE_PATTERNS o usa default.
113
+ */
114
+ export function getSensitivePatterns(): string[] {
115
+ const raw = process.env.OSTACKY_SENSITIVE_PATTERNS;
116
+ if (!raw) return SENSITIVE_DEFAULT;
117
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
118
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * tiered.ts — mirror de src/tiered.ts para plugin (self-contained)
3
+ * Generado desde src/tiered.ts — NO EDITAR. Ejecutá `bun run scripts/sync-controller-core.ts`.
4
+ */
5
+
6
+ /**
7
+ * Tiered helpers — single source of truth for trivial detection y niveles.
8
+ *
9
+ * Ambos plugins (ostacky-plugin.ts y engram.ts) importan de acá.
10
+ * No duplicar regex. Cache-friendly: isTrivial no muta system[0], solo decide hint.
11
+ * LEVEL_THRESHOLDS es la única definición de niveles (D6).
12
+ */
13
+
14
+ export const LEVEL_THRESHOLDS = {
15
+ "0": { maxFiles: 1, maxLines: 15, hasAPI: false, desc: "1 archivo, sin API, <15 líneas" },
16
+ "0+1": { maxFiles: 2, maxLines: 30, hasAPI: false, desc: "1-2 archivos, sin API, <30 líneas" },
17
+ "1+": { maxFiles: Infinity, maxLines: Infinity, hasAPI: true, desc: "API, deps, >30 líneas, cross-module" },
18
+ } as const;
19
+
20
+ export function classifyLevel({ fileCount, estLines, hasAPI }: { fileCount: number; estLines: number; hasAPI: boolean }): "0" | "0+1" | "1+" {
21
+ if (hasAPI || estLines > 30 || fileCount > 2) return "1+";
22
+ if (estLines > 15 || fileCount > 1) return "0+1";
23
+ return "0";
24
+ }
25
+
26
+ export function isTrivial(msg: string, state: string): boolean {
27
+ if (!msg || state !== "DONE") return false
28
+ if (msg.trim().length >= 30) return false
29
+ if (!/^(hola|hey|gracias|buenas|hi|hello)\b/i.test(msg.trim())) return false
30
+ if (/(necesito|quiero|agregá|fix|bug|feature|auth|spec|implementar)/i.test(msg)) return false
31
+ return true
32
+ }
33
+
34
+ export function getControllerState(directory: string): string {
35
+ try {
36
+ const { readFileSync } = require("node:fs") as typeof import("node:fs")
37
+ const { join } = require("node:path") as typeof import("node:path")
38
+ const statePath =
39
+ process.env.OSTACKY_STATE_PATH || join(directory, ".opencode", "ostacky-state.json")
40
+ const raw = readFileSync(statePath, "utf-8")
41
+ const j = JSON.parse(raw)
42
+ return j.state ?? "DONE"
43
+ } catch {
44
+ return "DONE"
45
+ }
46
+ }
@@ -129,6 +129,7 @@ Return: Summary of what you found and what you fixed.
129
129
  **Need full context:** Understanding requires seeing entire system
130
130
  **Exploratory debugging:** You don't know what's broken yet
131
131
  **Shared state:** Agents would interfere (editing same files, using same resources)
132
+ **Ostacky execution:** If `execution-mode-evaluation` already produced `fileClusters/sharedFiles` — **DO NOT use this skill**; use `subagent-driven-development` dispatch por clusters (respeta `sharedFiles` y `sequentialDeps` del Controller, evita pisar mismo archivo desde 2 agentes).
132
133
 
133
134
  ## Real Example from Session
134
135
 
@@ -39,10 +39,11 @@ Depending on what the user brings, you might:
39
39
  - Find analogies
40
40
 
41
41
  **Investigate the codebase**
42
- - Map existing architecture relevant to the discussion
42
+ - Map existing architecture relevant to the discussion (ground with `codegraph_codegraph_explore` + `engram_mem_search` if pre-1+ or without active change)
43
43
  - Find integration points
44
44
  - Identify patterns already in use
45
45
  - Surface hidden complexity
46
+ - **Level note:** Si necesitás ruteo por nivel Ostacky (0/0+1/1+) usa `brainstorming` en vez de este stance; este skill asume ya estás en flujo OpenSpec (1+).
46
47
 
47
48
  **Compare options**
48
49
  - Brainstorm multiple approaches
@@ -26,7 +26,9 @@ When ready to implement, run /opsx-apply
26
26
 
27
27
  **Steps**
28
28
 
29
- 1. **Check Engram for similar changes** — `engram_mem_search` with keywords from the user's description. If a similar change was proposed or implemented before, surface it to the user. They may want to extend the existing work instead of starting fresh.
29
+ 0. **Ground with CodeGraph + Engram (SHALL — Ostacky)** — Antes de `openspec new`, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Cita símbolos existentes en `proposal/design`; sin esto el `design.md` puede alucinar API inexistente y dispara `WARN:codegraphBypass` en `doctor`.
30
+
31
+ 1. **Check Engram for similar changes** — `engram_mem_search` con keywords del *paso 0* (reusar si ya fue hit; no duplicar `mem_search` mismo `requestId`). Si hay change similar previo, surfacealo. Pueden extender trabajo existente en vez de empezar de cero.
30
32
 
31
33
  2. **If no clear input provided, ask what they want to build**
32
34
 
@@ -28,6 +28,8 @@ Write the test first. Watch it fail. Write minimal code to pass.
28
28
 
29
29
  Thinking "skip TDD just this once"? Stop. That's rationalization.
30
30
 
31
+ > **Ostacky ground (SHALL):** Antes de RED, verifica `getDiscoverySnapshot(query)` + `getEngramDedup`. Si hit → reusar símbolos/decisiones; si miss → `codegraph_codegraph_explore` + `engram_mem_search` + `putDiscoverySnapshot`. Evita testear API inexistente y respeta `discovery-cache (único)` de `ostacky.md §1`. Ver `execution-mode-evaluation` para cache TTL.
32
+
31
33
  ## The Iron Law
32
34
 
33
35
  ```