ostacky 0.8.2 → 0.8.3

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 CHANGED
@@ -110,16 +110,13 @@ Detecta automáticamente el directorio `.opencode/` del proyecto (o lo crea) y m
110
110
  ### Instalar todo
111
111
 
112
112
  ```bash
113
- npx ostacky install # local por defecto (pregunta si querés global)
114
- npx ostacky install --scope local # <proyecto>/.opencode
115
- npx ostacky install --scope global # ~/.config/opencode (XDG/APPDATA en Windows)
116
- npx ostacky install --scope auto # local si existe .opencode/.git, si no global
113
+ npx ostacky install # siempre local en <proyecto>/.opencode
114
+ npx ostacky install --scope local # <proyecto>/.opencode (único scope soportado)
117
115
  ```
118
116
 
119
- Descarga todos los agentes y commands definidos en el manifest y los escribe en `.opencode/` (scope `local`) o en `~/.config/opencode` (`global`). Además copia los plugins **Ostacky-owned** `ostacky-plugin.ts` + `engram.ts` en `.opencode/plugins/` (el legacy `ostacky-guard.ts` fue fusionado en `ostacky-plugin.ts` y ya no se instala; `ostacky-controller.ts` legacy se limpia automáticamente si quedó de una instalación previa).
117
+ Descarga todos los agentes y commands definidos en el manifest y los escribe en `<proyecto>/.opencode`. Además copia los plugins **Ostacky-owned** `ostacky-plugin.ts` + `engram.ts` en `.opencode/plugins/` (el legacy `ostacky-guard.ts` fue fusionado en `ostacky-plugin.ts` y ya no se instala; `ostacky-controller.ts` legacy se limpia automáticamente si quedó de una instalación previa) y asegura `.gitignore` con ` .opencode/tools/, .codegraph/, .opencode/cache/, ostacky-state.json*`.
120
118
 
121
- > **Coherencia local/global:** aunque elijas `global`, los plugins y herramientas (`tools/` con CodeGraph/Engram) permanecen siempre en `<proyecto>/.opencode` local para el hard-gate por worktree. En global solo se escribe el scope global + un espejo local de plugins. Para stack completo por proyecto ejecutá `npx ostacky install --scope local` dentro de cada repo.
122
- > **Windows — dónde se instala realmente:** `global` es `%APPDATA%\opencode` (`C:\Users\<vos>\AppData\Roaming\opencode`), **no** `C:\Users\<vos>\.opencode`. Si ves `C:\Users\...\ .opencode\tools\codegraph` y esperabas `D:\mi-proyecto\.opencode`, es porque corriste `npx ostacky` desde tu home sin estar dentro del proyecto (sin `.git`). Hacé `cd` al proyecto y re-ejecutá con `--scope local`; el log ahora muestra `projectRoot` y `toolsDir` explícitos para evitar confusión.
119
+ > **Instalación siempre local:** Ostacky vive por proyecto/worktree (CodeGraph index `.codegraph/` y `ostacky-state.json` aislados). Si ves `C:\Users\...\ .opencode\tools\codegraph` y esperabas `D:\mi-proyecto\.opencode`, es porque corriste `npx ostacky` desde tu home sin estar dentro del proyecto (sin `.git`). Hacé `cd` al proyecto y re-ejecutá; el log muestra `projectRoot` y `toolsDir` explícitos.
123
120
 
124
121
  ### Agregar agentes o commands individualmente
125
122
 
@@ -151,10 +148,8 @@ Solo descarga los items que cambiaron de versión.
151
148
  ### Desinstalar
152
149
 
153
150
  ```bash
154
- npx ostacky uninstall # pregunta local vs global, luego qué borrar
151
+ npx ostacky uninstall # qué borrar en <proyecto>/.opencode
155
152
  npx ostacky uninstall --scope local # fuerza scope local
156
- npx ostacky uninstall --scope global # fuerza scope global
157
- npx ostacky uninstall --scope auto # auto
158
153
  ```
159
154
 
160
155
  **Safe-delete:** solo borra lo trackeado en `.opencode/ostacky-lock.json` (agentes, commands, skills, MCPs) — nunca toca componentes previos del usuario que no estén en el lockfile. Antes de borrar muestra preview con `Scope: <ruta>` + lista de paths y pide confirmación. Los plugins Ostacky-owned (`ostacky-plugin.ts`, `engram.ts` + legacy `ostacky-guard.ts`/`ostacky-controller.ts` si quedaron) se limpian automáticamente via allowlist, nunca se borran plugins custom.
@@ -164,7 +159,7 @@ npx ostacky uninstall --scope auto # auto
164
159
  #### Desinstalar un agente puntual
165
160
 
166
161
  ```bash
167
- npx ostacky uninstall agent <nombre> # también admite --scope local|global|auto
162
+ npx ostacky uninstall agent <nombre> # también admite --scope local
168
163
  npx ostacky uninstall agent # sin nombre → selector de instalados
169
164
  ```
170
165
 
@@ -177,7 +172,7 @@ npx ostacky uninstall agent ostacky
177
172
  #### Desinstalar un command puntual
178
173
 
179
174
  ```bash
180
- npx ostacky uninstall command <nombre> # también admite --scope
175
+ npx ostacky uninstall command <nombre> # también admite --scope local
181
176
  npx ostacky uninstall command # sin nombre → selector
182
177
  ```
183
178
 
@@ -192,7 +187,7 @@ Si no especificás el nombre, el CLI te muestra un selector con los items instal
192
187
  #### Desinstalar todo el stack (tools + config)
193
188
 
194
189
  ```bash
195
- npx ostacky uninstall-stack # pregunta scope, limpia mcp.codegraph/engram de opencode.json + .codegraph/ + .opencode/tools/
190
+ npx ostacky uninstall-stack # limpia mcp.codegraph/engram de opencode.json + .codegraph/ + .opencode/tools/
196
191
  npx ostacky uninstall-stack --scope local
197
192
  ```
198
193
 
@@ -251,37 +246,37 @@ Tras instalar (`--scope local`), el proyecto queda así:
251
246
 
252
247
  ```json
253
248
  {
254
- "version": "0.8.2",
249
+ "version": "0.8.3",
255
250
  "lockedAt": "2025-01-01T00:00:00.000Z",
256
251
  "repo": "JaimeHoracio/Ostacky",
257
- "tag": "v0.8.2",
252
+ "tag": "v0.8.3",
258
253
  "agents": {
259
254
  "ostacky": {
260
- "version": "0.8.2",
255
+ "version": "0.8.3",
261
256
  "installedAt": "2025-01-01T00:00:00.000Z",
262
257
  "sha256": "abc123..."
263
258
  }
264
259
  },
265
260
  "commands": {
266
261
  "install-stack": {
267
- "version": "0.8.2",
262
+ "version": "0.8.3",
268
263
  "installedAt": "2025-01-01T00:00:00.000Z",
269
264
  "sha256": "def456..."
270
265
  },
271
266
  "opsx-sync": {
272
- "version": "0.8.2",
267
+ "version": "0.8.3",
273
268
  "installedAt": "2025-01-01T00:00:00.000Z",
274
269
  "sha256": "ghi789..."
275
270
  }
276
271
  },
277
272
  "skills": {
278
- "brainstorming": { "version": "0.8.2", ... },
279
- "execution-mode-evaluation": { "version": "0.8.2", ... },
280
- "openspec-propose": { "version": "0.8.2", ... }
273
+ "brainstorming": { "version": "0.8.3", ... },
274
+ "execution-mode-evaluation": { "version": "0.8.3", ... },
275
+ "openspec-propose": { "version": "0.8.3", ... }
281
276
  },
282
277
  "mcpServers": {
283
- "ostacky-controller": { "version": "0.8.2", ... },
284
- "openspec": { "version": "0.8.2", ... }
278
+ "ostacky-controller": { "version": "0.8.3", ... },
279
+ "openspec": { "version": "0.8.3", ... }
285
280
  }
286
281
  }
287
282
  ```
@@ -313,7 +308,7 @@ Es opcional y solo necesario si algo falló durante la instalación o si querés
313
308
  ## Seguridad
314
309
 
315
310
  - `opencode.jsonc` se versiona en el repo para compartir permisos y MCP de forma reproducible.
316
- - Las URLs de descarga usan **tags de GitHub** (ej. `v0.8.2`), nunca `main` — instalaciones reproducibles
311
+ - Las URLs de descarga usan **tags de GitHub** (ej. `v0.8.3`), nunca `main` — instalaciones reproducibles
317
312
  - Cada path de archivo descargado es validado para prevenir **path traversal**
318
313
  - Los archivos incluyen **checksum SHA-256** opcional; si el manifest lo define, el contenido se verifica antes de escribir
319
314
  - El cache local (`.opencode/cache/`) también valida integridad al servir archivos cacheados
@@ -370,7 +365,7 @@ CodeGraph está instalado en `.opencode/tools/codegraph/bin/codegraph` y se conf
370
365
  | `.opencode/tools/codegraph/bin/codegraph status` | Muestra estado del index y archivos pendientes |
371
366
  | `.opencode/tools/codegraph/bin/codegraph install --target opencode --location local --yes` | Configura CodeGraph para OpenCode y genera AGENTS.md |
372
367
 
373
- > **Windows:** si ves `Command failed: cmd.exe /d /c call ... codegraph.cmd init -i` durante `npx ostacky install`, es un _warning_ no fatal — el binario se instaló pero el índice no se pudo crear. Reintentá con `npx ostacky install-stack --scope local` o ejecutá manualmente `.opencode\tools\codegraph\bin\codegraph.exe init -i` (o `codegraph.cmd` si no hay `.exe`) dentro del proyecto. Desde v0.8.2 el instalador usa el patrón resiliente de Engram (strip 0 + búsqueda recursiva) y loguea `projectRoot|toolsDir` para diagnosticar scope.
368
+ > **Windows:** si ves `Command failed: cmd.exe /d /c call ... codegraph.cmd init -i` durante `npx ostacky install`, es un _warning_ no fatal — el binario se instaló pero el índice no se pudo crear. Reintentá con `npx ostacky install-stack --scope local` o ejecutá manualmente `.opencode\tools\codegraph\bin\codegraph.exe init -i` (o `codegraph.cmd` si no hay `.exe`) dentro del proyecto. Desde v0.8.3 el instalador usa el patrón resiliente de Engram (strip 0 + búsqueda recursiva) y loguea `projectRoot|toolsDir` para diagnosticar scope.
374
369
 
375
370
  ## Licencia
376
371
 
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  description: Orquestador principal — rutea por nivel, orquesta CodeGraph + OpenSpec + Superpowers.
3
3
  mode: primary
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  ---
6
6
 
7
- Sos **Ostacky v0.8.2**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
7
+ Sos **Ostacky v0.8.3**, orquestás, no implementás. Interpretás, clasificás (0/0+1/1+), ruteás y coordinás.
8
8
 
9
- > **Versión:** `0.8.2` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.2"** (o `v0.8.2` si te piden solo el número). No inventes otra versión.
9
+ > **Versión:** `0.8.3` (sincronizada desde `package.json` vía `scripts/sync-version.ts`). Cuando te pregunten qué versión tenés, qué versión sos, o `¿qué versión tenés?` / `version` / `¿en qué versión estás?`, respondé exactamente: **"Ostacky v0.8.3"** (o `v0.8.3` si te piden solo el número). No inventes otra versión.
10
10
 
11
11
  ## Reglas innegociables
12
12
 
@@ -18,6 +18,16 @@ Sos **Ostacky v0.8.2**, orquestás, no implementás. Interpretás, clasificás (
18
18
 
19
19
  > Ver `assets/docs/ostacky-reference.md` para TRANSITIONS, TTL y métricas. Tiered LITE/TIER1/FULL vía suffix hint.
20
20
 
21
+ ## Principios de honestidad (SHALL)
22
+
23
+ 1. **Engram contradiction check SHALL.** Antes de proponer o discutir cambios (Nivel 0+1/1+, eximido si `isTrivial && DONE`), SHALL `mem_search` por keywords del pedido (max 5 keywords = sustantivos del pedido + `topic_key` del change activo si lo hay); si hit `type:decision|architecture` con contradicción semántica (ej: pide Zustand y hay #571 “usar Redux por X”), SHALL mostrar diff `“Antes decidimos X el <fecha> por Z (topic_key), ahora pedís ¬X”`, llamar `request_clarification` y esperar. Si Engram `degraded`/timeout → SHALL advertir “sin memoria” y continuar (no block). Si usuario confirma override → SHALL `mem_save` mismo `topic_key` + `mem_compare supersedes` + `record_user_confirmation`.
24
+ 2. **Solo propuestas que ayudan SHALL.** Toda propuesta SHALL incluir `por qué ayuda + tradeoff + evidencia` (CodeGraph symbol, Engram hit) o literal `“no verificado”` + best practice 2026. No sugerir por sugerir.
25
+ 3. **Pocas honestas > muchas de relleno SHALL.** Si solo hay una opción honesta, dar una. Si hay 2-3, tabla `coste|riesgo|complejidad`. No inventar para llenar.
26
+ 4. **Si no hay propuesta honesta SHALL decirlo.** Literal: `“No hay propuesta honesta que aporte vs no hacer nada en este contexto.”`
27
+ 5. **Si dudás SHALL preguntar.** Ante ambigüedad aunque el pedido parezca claro, SHALL una pregunta clarificadora (una por turno, natural, sin tool, STOP) y esperar. No asumir.
28
+ 6. **Verificar antes de afirmar SHALL.** No acordar sin chequear código/docs. Citar evidencia o admitir `“no pude verificar en disco”`.
29
+ 7. **YAGNI scope SHALL.** No proponer refactor oportunista fuera del pedido; si lo ves, mencionar `“fuera de scope: …”` sin implementarlo.
30
+
21
31
  ## Stack
22
32
 
23
33
  - **Controller** (plugin `ostacky-plugin.ts`): state machine in-process, hard gates.
@@ -62,6 +72,11 @@ Preguntar nivel y `consume_route_decision`.
62
72
 
63
73
  Router `brainstorming`↔`OpenSpec` por `level`/`estLines`/`fileCount`/`hasAPI` (no keywords). `1+` no-downgradeable → `skill(brainstorming)` genera `design.md ## Alternatives`; downgradeable → `docs/...` + `DIRECT`.
64
74
 
75
+ **SHALL sin ambigüedad (spec iterativo + sync proactiva):**
76
+
77
+ - Antes de crear/editar `openspec/changes/<id>/{proposal.md,design.md,tasks.md}` **O** `docs/superpowers/specs/*.md`: si existe → SHALL `read` fresco de TODOS y SHALL `edit` (no `write`); `write` solo si no existía. Para `open-explore` sin archivo → SHALL `mem_save topic_key:brainstorm/<hash>` por iteración. `getDiscoverySnapshot` no aplica a specs/docs.
78
+ - Al final de cada turno de brainstorming/spec con decisiones nuevas no reflejadas en disco: SHALL listar cálido `Noté que lo que acordamos (X por Z) aún no está en <archivo>: 1) ...` (max 3) y SHALL proponer en UNA sola pregunta `¿Querés que agregue [X, Y] a <archivo> e implemente <mejor propuesta> —la recomiendo por <tradeoff/evidencia>—?` (respetando Regla 5). Si no hay delta, no proponer. Eximido si `isTrivial`.
79
+
65
80
  ### 4. Execution
66
81
 
67
82
  1. `skill(execution-mode-evaluation)` en memoria, reusa discovery.
@@ -5,13 +5,13 @@ agent: build
5
5
 
6
6
  Instala el stack tecnológico de desarrollo para OpenCode. **IMPORTANTE:** las herramientas se instalan por separado (cada una con su propio CLI/comando). `npx ostacky install` solo instala el agente y commands de Ostacky en `.opencode/`. Este comando (`/install-stack`) es la guía de referencia para la instalación manual completa paso a paso.
7
7
 
8
- **Nota:** A partir de v0.8.2, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, MCPs bundleados) además del agente y skills. Este comando es útil para instalación manual, verificación, o cuando algo falló y necesita reinstalarse.
8
+ **Nota:** A partir de v0.8.3, `npx ostacky install` ya instala automáticamente el stack completo (CodeGraph, OpenSpec, Engram, MCPs bundleados) además del agente y skills. Este comando es útil para instalación manual, verificación, o cuando algo falló y necesita reinstalarse.
9
9
 
10
10
  **RESTRICCIÓN ABSOLUTA:** instalar ÚNICAMENTE para OpenCode. Está terminantemente prohibido crear o modificar archivos en `.claude/`, `.kiro/`, `.cursor/`, `.gemini/`, `.codex/`, `.antigravity/`, `.windsurf/` o cualquier otro directorio de plataformas externas.
11
11
 
12
12
  **Origen del set curado:** el set de 15 skills referenciado en `assets/agents/ostacky.md` está bundleado en `assets/skills/` dentro del paquete npm. La definición del set y su trazabilidad viven en `manifest.json` y `.opencode/ostacky-lock.json`.
13
13
 
14
- ## Scope de instalación — local vs global (desde v0.8.2)
14
+ ## Scope de instalación — local vs global (desde v0.8.3)
15
15
 
16
16
  `npx ostacky install` soporta `--scope local|global|auto` (también `--scope=...`):
17
17
 
@@ -0,0 +1,206 @@
1
+ /**
2
+ * controller-core — Single source of truth para TRANSITIONS, STATES, DEFAULT_STATE y helpers.
3
+ * Extraído de assets/mcp/ostacky-controller/index.js y assets/plugins/ostacky-plugin.ts
4
+ * para eliminar duplicación (D1). Ambos importan de acá.
5
+ */
6
+
7
+ import { SENSITIVE_DEFAULT } from "./security.js";
8
+
9
+ // --- Constants (headroom generoso) ---
10
+ export const MAX_TASKS = 100;
11
+ export const MAX_TASKS_DEFAULT = 100;
12
+ export const MAX_TASKS_CAP = 500;
13
+ export const MAX_SNAPSHOT_JSON_LENGTH = 50 * 1024;
14
+ export const MAX_STATE_FILE_SIZE = 2 * 1024 * 1024;
15
+ export const DEGRADED_THRESHOLD = 3;
16
+
17
+ export const STATES = Object.freeze({
18
+ INTERPRETATION_PENDING: "INTERPRETATION_PENDING",
19
+ CLARIFICATION_PENDING: "CLARIFICATION_PENDING",
20
+ DISCOVERY: "DISCOVERY",
21
+ ROUTE_DECISION_PENDING: "ROUTE_DECISION_PENDING",
22
+ SPECIFICATION: "SPECIFICATION",
23
+ EXECUTION_ANALYSIS: "EXECUTION_ANALYSIS",
24
+ EXECUTION_DECISION_PENDING: "EXECUTION_DECISION_PENDING",
25
+ EXECUTING_INLINE: "EXECUTING_INLINE",
26
+ EXECUTING_SUBAGENTS: "EXECUTING_SUBAGENTS",
27
+ SYNC: "SYNC",
28
+ DONE: "DONE",
29
+ BLOCKED: "BLOCKED",
30
+ });
31
+
32
+ export const TRANSITIONS = {
33
+ INTERPRETATION_PENDING: [
34
+ { via: "request_clarification", to: "CLARIFICATION_PENDING" },
35
+ { via: "proceed_to_discovery", to: "DISCOVERY" },
36
+ { via: "record_discovery", to: "ROUTE_DECISION_PENDING" },
37
+ { via: "block", to: "BLOCKED" },
38
+ ],
39
+ CLARIFICATION_PENDING: [
40
+ { via: "record_clarification", to: "DISCOVERY" },
41
+ { via: "block", to: "BLOCKED" },
42
+ { via: "abandon", to: "BLOCKED" },
43
+ ],
44
+ DISCOVERY: [
45
+ { via: "record_discovery", to: "ROUTE_DECISION_PENDING" },
46
+ { via: "block", to: "BLOCKED" },
47
+ { via: "abandon", to: "BLOCKED" },
48
+ ],
49
+ ROUTE_DECISION_PENDING: [
50
+ { via: "consume_route_decision", to: "SPECIFICATION", choice: "SPEC" },
51
+ { via: "consume_route_decision", to: "EXECUTION_ANALYSIS", choice: "DIRECT" },
52
+ { via: "block", to: "BLOCKED" },
53
+ { via: "abandon", to: "BLOCKED" },
54
+ ],
55
+ SPECIFICATION: [
56
+ { via: "spec_complete", to: "EXECUTION_ANALYSIS" },
57
+ { via: "block", to: "BLOCKED" },
58
+ { via: "abandon", to: "BLOCKED" },
59
+ ],
60
+ EXECUTION_ANALYSIS: [
61
+ { via: "record_execution_analysis", to: "EXECUTION_DECISION_PENDING" },
62
+ { via: "block", to: "BLOCKED" },
63
+ { via: "abandon", to: "BLOCKED" },
64
+ ],
65
+ EXECUTION_DECISION_PENDING: [
66
+ { via: "consume_execution_decision", to: "EXECUTING_INLINE", mode: "INLINE" },
67
+ { via: "consume_execution_decision", to: "EXECUTING_SUBAGENTS", mode: "SUBAGENT_DRIVEN" },
68
+ { via: "block", to: "BLOCKED" },
69
+ { via: "abandon", to: "BLOCKED" },
70
+ ],
71
+ EXECUTING_INLINE: [
72
+ { via: "implementation_complete", to: "SYNC" },
73
+ { via: "block", to: "BLOCKED" },
74
+ ],
75
+ EXECUTING_SUBAGENTS: [
76
+ { via: "implementation_complete", to: "SYNC" },
77
+ { via: "block", to: "BLOCKED" },
78
+ ],
79
+ BLOCKED: [
80
+ { via: "replan", to: "INTERPRETATION_PENDING" },
81
+ { via: "abandon", to: "DONE" },
82
+ ],
83
+ SYNC: [
84
+ { via: "sync_complete", to: "DONE" },
85
+ { via: "block", to: "BLOCKED" },
86
+ ],
87
+ DONE: [],
88
+ };
89
+
90
+ export const TERMINAL_STATES = Object.freeze([
91
+ STATES.INTERPRETATION_PENDING,
92
+ STATES.CLARIFICATION_PENDING,
93
+ STATES.BLOCKED,
94
+ STATES.DONE,
95
+ ]);
96
+
97
+ export const DEFAULT_STATE = {
98
+ state: STATES.INTERPRETATION_PENDING,
99
+ revision: 0,
100
+ requestId: null,
101
+ changeId: null,
102
+ routeDecisionId: null,
103
+ routeChoice: null,
104
+ level: null,
105
+ executionDecisionId: null,
106
+ executionMode: null,
107
+ snapshots: { codegraph: null, execution: null },
108
+ tasks: {},
109
+ fileFingerprints: {},
110
+ error: null,
111
+ lastHandoff: null,
112
+ expectedTasks: null,
113
+ expectedTaskCount: null,
114
+ auditSeq: 0,
115
+ degraded: false,
116
+ schemaVersion: 2,
117
+ stateOversizedCount: 0,
118
+ codegraphBypassCount: 0,
119
+ degradedEditsCount: 0,
120
+ cacheHitCount: 0,
121
+ cacheMissCount: 0,
122
+ tokenSavingEstimate: 0,
123
+ discoveryCacheHitCount: 0,
124
+ redundantCallCount: 0,
125
+ cacheMissWithoutPutCount: 0,
126
+ stateCheckCount: 0,
127
+ toolCallCount: 0,
128
+ lastProposal: null,
129
+ allowedFiles: {},
130
+ deniedFiles: {},
131
+ sensitivePatterns: [...SENSITIVE_DEFAULT],
132
+ sensitiveAccess: { allowed: 0, denied: 0, blockedAttempts: 0 },
133
+ staleContentAttempts: 0,
134
+ completeWithoutValidateCount: 0,
135
+ toolTimeoutCount: 0,
136
+ lastToolDurationMs: 0,
137
+ stateDurationMs: 0,
138
+ subagentFailedCount: 0,
139
+ lastValidated: null,
140
+ pendingFileAccess: {},
141
+ lastHeartbeat: 0,
142
+ watchdogEnabled: true,
143
+ ts: Date.now(),
144
+ specNotInSync: false,
145
+ };
146
+
147
+ export function safeJsonStringify(obj, pretty = false) {
148
+ const seen = new WeakSet();
149
+ try {
150
+ return JSON.stringify(
151
+ obj,
152
+ (key, value) => {
153
+ if (typeof value === "object" && value !== null) {
154
+ if (seen.has(value)) return "[Circular]";
155
+ seen.add(value);
156
+ }
157
+ return value;
158
+ },
159
+ pretty ? 2 : undefined
160
+ );
161
+ } catch (e) {
162
+ return `[Unstringifiable: ${e.message}]`;
163
+ }
164
+ }
165
+
166
+ const SENSITIVE_REDACT_RE = /(apiKey|secret|token|password|api_key)/i;
167
+
168
+ export function redactForLog(data) {
169
+ if (!data || typeof data !== "object") return data;
170
+ try {
171
+ const str = safeJsonStringify(data);
172
+ if (SENSITIVE_REDACT_RE.test(str)) {
173
+ const copy = JSON.parse(str);
174
+ const redactRecursively = (obj) => {
175
+ if (!obj || typeof obj !== "object") return;
176
+ for (const k of Object.keys(obj)) {
177
+ if (SENSITIVE_REDACT_RE.test(k)) obj[k] = "[REDACTED]";
178
+ else if (typeof obj[k] === "object") redactRecursively(obj[k]);
179
+ }
180
+ };
181
+ redactRecursively(copy);
182
+ return copy;
183
+ }
184
+ return data;
185
+ } catch {
186
+ return data;
187
+ }
188
+ }
189
+
190
+ export function getMaxTasks() {
191
+ const raw = process.env.OSTACKY_MAX_TASKS;
192
+ if (raw == null || raw === "") return MAX_TASKS_DEFAULT;
193
+ const n = parseInt(raw, 10);
194
+ if (Number.isNaN(n) || n <= 0) return MAX_TASKS_DEFAULT;
195
+ if (n > MAX_TASKS_CAP) return MAX_TASKS_CAP;
196
+ return n;
197
+ }
198
+
199
+ export function getAuditRetention() {
200
+ const raw = process.env.OSTACKY_AUDIT_RETENTION;
201
+ if (raw == null || raw === "") return 500;
202
+ const n = parseInt(raw, 10);
203
+ if (Number.isNaN(n) || n <= 0) return 500;
204
+ if (n > 2000) return 2000;
205
+ return n;
206
+ }