ostacky 0.8.2 → 0.8.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 +20 -25
- package/assets/agents/ostacky.md +18 -3
- package/assets/commands/install-stack.md +2 -2
- package/assets/mcp/ostacky-controller/controller-core.js +206 -0
- package/assets/mcp/ostacky-controller/index.js +166 -164
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/plugins/controller-core.ts +206 -0
- package/assets/plugins/ostacky-plugin.ts +12 -124
- package/assets/skills/brainstorming/SKILL.md +5 -3
- package/dist/cli.js +503 -720
- package/manifest.json +43 -43
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
567
|
-
|
|
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
|
}
|
|
@@ -40,10 +40,12 @@ Do NOT invoke any implementation skill, write any code, or scaffold any project
|
|
|
40
40
|
2. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
|
|
41
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
|
-
-
|
|
45
|
-
- Si trigger + `
|
|
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
|