ostacky 0.8.1 → 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.
@@ -16,6 +16,7 @@ import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync, statSyn
16
16
  import { join, dirname, basename, resolve, relative } from "node:path"
17
17
  import { SENSITIVE_DEFAULT, BASH_SENSITIVE_RE, isSensitive, extractPathsFromBash } from "../../src/security.ts"
18
18
  import { isTrivial } from "../../src/tiered.ts"
19
+ import { STATES, TRANSITIONS, DEFAULT_STATE } from "./controller-core.ts"
19
20
 
20
21
  // ─── Constants ───────────────────────────────────────────────────────────────
21
22
 
@@ -25,127 +26,7 @@ const IDLE_THRESHOLD_MS = 45_000
25
26
  const PURPLE_TENUE = "\x1b[38;5;183m"
26
27
  const PURPLE_RESET = "\x1b[0m"
27
28
 
28
- const STATES = Object.freeze({
29
- INTERPRETATION_PENDING: "INTERPRETATION_PENDING",
30
- CLARIFICATION_PENDING: "CLARIFICATION_PENDING",
31
- DISCOVERY: "DISCOVERY",
32
- ROUTE_DECISION_PENDING: "ROUTE_DECISION_PENDING",
33
- SPECIFICATION: "SPECIFICATION",
34
- EXECUTION_ANALYSIS: "EXECUTION_ANALYSIS",
35
- EXECUTION_DECISION_PENDING: "EXECUTION_DECISION_PENDING",
36
- EXECUTING_INLINE: "EXECUTING_INLINE",
37
- EXECUTING_SUBAGENTS: "EXECUTING_SUBAGENTS",
38
- SYNC: "SYNC",
39
- DONE: "DONE",
40
- BLOCKED: "BLOCKED",
41
- } as const)
42
-
43
- const TRANSITIONS: Record<string, Array<{ via: string; to: string; choice?: string; mode?: string }>> = {
44
- INTERPRETATION_PENDING: [
45
- { via: "request_clarification", to: "CLARIFICATION_PENDING" },
46
- { via: "proceed_to_discovery", to: "DISCOVERY" },
47
- { via: "record_discovery", to: "ROUTE_DECISION_PENDING" },
48
- { via: "block", to: "BLOCKED" },
49
- ],
50
- CLARIFICATION_PENDING: [
51
- { via: "record_clarification", to: "DISCOVERY" },
52
- { via: "block", to: "BLOCKED" },
53
- { via: "abandon", to: "BLOCKED" },
54
- ],
55
- DISCOVERY: [
56
- { via: "record_discovery", to: "ROUTE_DECISION_PENDING" },
57
- { via: "block", to: "BLOCKED" },
58
- { via: "abandon", to: "BLOCKED" },
59
- ],
60
- ROUTE_DECISION_PENDING: [
61
- { via: "consume_route_decision", to: "SPECIFICATION", choice: "SPEC" },
62
- { via: "consume_route_decision", to: "EXECUTION_ANALYSIS", choice: "DIRECT" },
63
- { via: "block", to: "BLOCKED" },
64
- { via: "abandon", to: "BLOCKED" },
65
- ],
66
- SPECIFICATION: [
67
- { via: "spec_complete", to: "EXECUTION_ANALYSIS" },
68
- { via: "block", to: "BLOCKED" },
69
- { via: "abandon", to: "BLOCKED" },
70
- ],
71
- EXECUTION_ANALYSIS: [
72
- { via: "record_execution_analysis", to: "EXECUTION_DECISION_PENDING" },
73
- { via: "block", to: "BLOCKED" },
74
- { via: "abandon", to: "BLOCKED" },
75
- ],
76
- EXECUTION_DECISION_PENDING: [
77
- { via: "consume_execution_decision", to: "EXECUTING_INLINE", mode: "INLINE" },
78
- { via: "consume_execution_decision", to: "EXECUTING_SUBAGENTS", mode: "SUBAGENT_DRIVEN" },
79
- { via: "block", to: "BLOCKED" },
80
- { via: "abandon", to: "BLOCKED" },
81
- ],
82
- EXECUTING_INLINE: [
83
- { via: "implementation_complete", to: "SYNC" },
84
- { via: "block", to: "BLOCKED" },
85
- ],
86
- EXECUTING_SUBAGENTS: [
87
- { via: "implementation_complete", to: "SYNC" },
88
- { via: "block", to: "BLOCKED" },
89
- ],
90
- BLOCKED: [
91
- { via: "replan", to: "INTERPRETATION_PENDING" },
92
- { via: "abandon", to: "DONE" },
93
- ],
94
- SYNC: [
95
- { via: "sync_complete", to: "DONE" },
96
- { via: "block", to: "BLOCKED" },
97
- ],
98
- DONE: [],
99
- }
100
-
101
- const DEFAULT_STATE: any = {
102
- state: STATES.INTERPRETATION_PENDING,
103
- revision: 0,
104
- requestId: null,
105
- changeId: null,
106
- routeDecisionId: null,
107
- routeChoice: null,
108
- level: null,
109
- executionDecisionId: null,
110
- executionMode: null,
111
- snapshots: { codegraph: null, execution: null },
112
- tasks: {},
113
- fileFingerprints: {},
114
- error: null,
115
- lastHandoff: null,
116
- expectedTasks: null,
117
- expectedTaskCount: null,
118
- auditSeq: 0,
119
- degraded: false,
120
- schemaVersion: 1,
121
- stateOversizedCount: 0,
122
- codegraphBypassCount: 0,
123
- degradedEditsCount: 0,
124
- cacheHitCount: 0,
125
- cacheMissCount: 0,
126
- tokenSavingEstimate: 0,
127
- discoveryCacheHitCount: 0,
128
- redundantCallCount: 0,
129
- cacheMissWithoutPutCount: 0,
130
- stateCheckCount: 0,
131
- toolCallCount: 0,
132
- lastProposal: null,
133
- allowedFiles: {},
134
- deniedFiles: {},
135
- sensitivePatterns: SENSITIVE_DEFAULT,
136
- sensitiveAccess: { allowed: 0, denied: 0, blockedAttempts: 0 },
137
- staleContentAttempts: 0,
138
- completeWithoutValidateCount: 0,
139
- toolTimeoutCount: 0,
140
- lastToolDurationMs: 0,
141
- stateDurationMs: 0,
142
- subagentFailedCount: 0,
143
- lastValidated: null,
144
- pendingFileAccess: {},
145
- lastHeartbeat: 0,
146
- watchdogEnabled: true,
147
- ts: Date.now(),
148
- }
29
+ // DEFAULT_STATE imported from controller-core.ts
149
30
 
150
31
  // ─── Helpers ─────────────────────────────────────────────────────────────────
151
32
 
@@ -558,13 +439,20 @@ export const OstackyController: Plugin = async (ctx) => {
558
439
  const s = readState((input as any).ctx?.directory ?? "")
559
440
  } catch {}
560
441
  }
561
- // Heartbeat + color purple tenue: Ostacky vs modelo (fácil)
442
+ // Heartbeat + color purple tenue: Ostacky vs modelo (fácil) — single-writer: plugin memo, no persist cada tool (D10)
443
+ let lastHeartbeatMem = 0
562
444
  try {
563
445
  const dir = ctx.directory
564
446
  const s = readState(dir)
565
447
  if (s) {
566
- s.lastHeartbeat = Date.now()
567
- try { persistState(dir, s) } catch {}
448
+ const now = Date.now()
449
+ lastHeartbeatMem = now
450
+ // Single-writer: solo persiste si OSTACKY_PLUGIN_PERSIST=1 o idle>30s o state cambió
451
+ const shouldPersist = process.env.OSTACKY_PLUGIN_PERSIST === "1" || (now - (s.lastHeartbeat || 0) > 30000)
452
+ if (shouldPersist) {
453
+ s.lastHeartbeat = now
454
+ try { persistState(dir, s) } catch {}
455
+ }
568
456
  if (["EXECUTING_INLINE", "EXECUTING_SUBAGENTS", "SYNC"].includes(s.state) && output && typeof output.title === "string" && output.title && !output.title.includes("🟣")) {
569
457
  output.title = `🟣 ${PURPLE_TENUE}[OSTACKY]${PURPLE_RESET} ${output.title}`
570
458
  }
@@ -38,12 +38,14 @@ Do NOT invoke any implementation skill, write any code, or scaffold any project
38
38
 
39
39
  1. **Check discovery-cache first** — `src/discovery-cache.ts` `getDiscoverySnapshot(query)` + `getEngramDedup(query, requestId)`. Si hit válido (TTL+gitDiffHash), **reusar** sin llamar tools. Solo si miss → `engram_mem_search` + `codegraph_codegraph_explore` y **SHALL `putDiscoverySnapshot`** antes de avanzar. No re-llamar si área difiere <30% del snapshot.
40
40
  2. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
41
- 4. **Propose 2-3 approaches (hardening-v2 — SHALL)** — con tabla trade-offs (coste|riesgo|complejidad) + evidencia CodeGraph+Engram (+Context7 si librería) sin alucinar, YAGNI, y recomendación con razón; cada approach cita symbols existentes y mem_search hits verificables
41
+ 4. **Propose 2-3 approaches (hardening-v2 — SHALL)** — con tabla trade-offs (coste|riesgo|complejidad) + evidencia CodeGraph+Engram sin alucinar, YAGNI, y recomendación con razón; cada approach cita symbols existentes y mem_search hits verificables
42
42
  5. **Present design** — in sections scaled to complexity, get user approval after each section. **Gate post-brainstorming (hardening-v2):** tras presentar diseño, preguntar "¿Procedo con este diseño o querés ajustar algo?" y esperar confirmación explícita antes de `record_discovery`, `openspec-propose` o implementación directa
43
- 6. **Write design doc (output path condicional — router exclusivo):**
44
- - Si trigger + `level 1+` no-downgradeable (`estLines>30` o `fileCount>2` o API pública) y change activo → escribir `openspec/changes/<id>/design.md` sección `## Alternatives Considered` con 2-3 approaches (tabla coste|riesgo|complejidad + evidencia CodeGraph+Engram), no `docs/`. No invocar `openspec-propose` separado este es el diseño.
45
- - Si trigger + `0/0+1` o `1+` downgradeable (`estLines<30`&&`fileCount==1`&&sin API) → solo `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` + gate único `¿Procedo?` DIRECT sin change. SHALL sugerir downgrade: `"Esto parece 0+1 (~X líneas, 1 archivo). ¿Lo tratamos sin spec?"` y esperar.
43
+ 6. **Write design doc (output path condicional — router exclusivo) — SHALL read fresco + edit no write + sync proactiva:**
44
+ - Antes de `write` SHALL `read` fresco de `openspec/changes/<id>/{proposal,design,tasks}` o `docs/superpowers/specs/*.md` si existen; si existen SHALL `edit` (no `write`); `write` solo si no existía. `getDiscoverySnapshot` no aplica a specs/docs. Para `open-explore` sin archivo SHALL `mem_save topic_key:brainstorm/<hash>` por iteración.
45
+ - Si trigger + `level 1+` no-downgradeable (`estLines>30` o `fileCount>2` o API pública) y change activo escribir `openspec/changes/<id>/design.md` sección `## Alternatives Considered` con 2-3 approaches (tabla coste|riesgo|complejidad + evidencia CodeGraph+Engram), no `docs/`. No invocar `openspec-propose` separado este es el diseño. Si ya existe, SHALL `edit` no `write`.
46
+ - Si trigger + `0/0+1` o `1+` downgradeable (`estLines<30`&&`fileCount==1`&&sin API) → solo `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` + gate único `¿Procedo?` → DIRECT sin change. SHALL sugerir downgrade: `"Esto parece 0+1 (~X líneas, 1 archivo). ¿Lo tratamos sin spec?"` y esperar. Si `docsSpec` ya existe SHALL `read` + `edit`.
46
47
  - Gate post-brainstorming es **único**; no hay segundo `¿Procedo con spec?`. `get_audit` `WARN:duplicate_design_generated` si ambos artefactos mismo `requestId`.
48
+ - **Sync proactiva SHALL:** al final de cada turno 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 proponer en UNA sola pregunta `¿Querés que agregue [X, Y] a <archivo> e implemente <mejor propuesta> —la recomiendo por <tradeoff>—?` (respetando Regla 5). Para brainstorming SHALL indicar `¿Querés que implementemos lo último que charlamos (B, recomendada por <tradeoff>) o preferís que vaya con A?`
47
49
  7. **Spec self-review** — check for placeholders, contradictions, ambiguity, scope
48
50
  8. **User reviews spec** — ask user to review before proceeding
49
51
  9. **Save to Engram** — `engram_mem_save` with the design decision and tradeoffs
@@ -20,7 +20,6 @@ When critical tools are unavailable, this skill provides a systematic approach t
20
20
  | CodeGraph | MCP server | Engram → Read + Glob | No structural analysis, manual exploration |
21
21
  | Engram | MCP server | Continue without memory | No persistence across sessions |
22
22
  | Controller | MCP server | Inline validation + manual state | No state machine, no edit validation |
23
- | Context7 | MCP server (remote) | Skip documentation | No external API docs |
24
23
 
25
24
  ## Detection
26
25
 
@@ -108,12 +107,6 @@ Incluso en degraded, el guard de credenciales sigue activo (hard gate incluso en
108
107
  - No state persistence across crashes
109
108
  - No pending state enforcement (check_pending_state unavailable)
110
109
 
111
- ### Without Context7
112
-
113
- **Impact:** Minimal — only affects external API documentation lookups.
114
-
115
- **Workflow:** Proceed normally. Use existing knowledge.
116
-
117
110
  ## Communication Protocol
118
111
 
119
112
  ### Status Report Format
@@ -141,7 +134,6 @@ Herramientas disponibles:
141
134
  ✅ Controller — ping OK
142
135
  ❌ CodeGraph — timeout 10s
143
136
  ✅ Engram — disponible
144
- ✅ Context7 — disponible
145
137
 
146
138
  ¿Continuar con lectura manual de archivos?
147
139
  ```
@@ -154,7 +146,6 @@ Herramientas disponibles:
154
146
  ❌ Controller — no responde
155
147
  ❌ CodeGraph — no instalado
156
148
  ❌ Engram — timeout 5s
157
- ✅ Context7 — disponible (pero sin docs relevantes)
158
149
 
159
150
  Modo básico: sin validación de edits, sin memoria persistente, sin análisis estructural.
160
151
  ¿Continuar o cancelar?