krenk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/krenk.js +2 -0
- package/dist/index.js +3880 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/public/img/main.png +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3880 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { program } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/commands/run.ts
|
|
8
|
+
import chalk3 from "chalk";
|
|
9
|
+
|
|
10
|
+
// src/orchestrator/engine.ts
|
|
11
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
12
|
+
|
|
13
|
+
// src/orchestrator/context.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
var ContextManager = class {
|
|
17
|
+
memory = /* @__PURE__ */ new Map();
|
|
18
|
+
krenkDir;
|
|
19
|
+
historyDir;
|
|
20
|
+
runId;
|
|
21
|
+
constructor(projectDir) {
|
|
22
|
+
this.runId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
23
|
+
this.krenkDir = path.join(projectDir, ".krenk");
|
|
24
|
+
this.historyDir = path.join(this.krenkDir, "history", this.runId);
|
|
25
|
+
fs.mkdirSync(this.krenkDir, { recursive: true });
|
|
26
|
+
fs.mkdirSync(this.historyDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
/** Save agent output to both memory and disk */
|
|
29
|
+
async save(role, content) {
|
|
30
|
+
this.memory.set(role, content);
|
|
31
|
+
await fs.promises.writeFile(
|
|
32
|
+
path.join(this.krenkDir, `${role}.md`),
|
|
33
|
+
content,
|
|
34
|
+
"utf-8"
|
|
35
|
+
);
|
|
36
|
+
await fs.promises.writeFile(
|
|
37
|
+
path.join(this.historyDir, `${role}.md`),
|
|
38
|
+
content,
|
|
39
|
+
"utf-8"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
/** Get a specific agent's output from memory */
|
|
43
|
+
get(role) {
|
|
44
|
+
return this.memory.get(role);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build context string from all previous agents' outputs.
|
|
48
|
+
* Includes outputs from all agents that ran before the given role.
|
|
49
|
+
*/
|
|
50
|
+
buildContext(upToRole) {
|
|
51
|
+
const order = [
|
|
52
|
+
"analyst",
|
|
53
|
+
"strategist",
|
|
54
|
+
"designer",
|
|
55
|
+
"architect",
|
|
56
|
+
"builder",
|
|
57
|
+
"qa",
|
|
58
|
+
"guardian",
|
|
59
|
+
"sentinel",
|
|
60
|
+
"security",
|
|
61
|
+
"scribe",
|
|
62
|
+
"devops"
|
|
63
|
+
];
|
|
64
|
+
const baseRole = upToRole.replace(/-\d+$/, "");
|
|
65
|
+
const idx = order.indexOf(baseRole);
|
|
66
|
+
if (idx <= 0) return "";
|
|
67
|
+
let ctx = "# Context from previous agents:\n";
|
|
68
|
+
for (let i = 0; i < idx; i++) {
|
|
69
|
+
const content = this.memory.get(order[i]);
|
|
70
|
+
if (content) {
|
|
71
|
+
ctx += `
|
|
72
|
+
## Output from ${order[i].toUpperCase()}:
|
|
73
|
+
${content}
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return ctx;
|
|
78
|
+
}
|
|
79
|
+
/** Save workflow state for resume capability */
|
|
80
|
+
async saveState(state) {
|
|
81
|
+
await fs.promises.writeFile(
|
|
82
|
+
path.join(this.krenkDir, "state.json"),
|
|
83
|
+
JSON.stringify(state, null, 2),
|
|
84
|
+
"utf-8"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
/** Load workflow state */
|
|
88
|
+
async loadState() {
|
|
89
|
+
try {
|
|
90
|
+
const data = await fs.promises.readFile(
|
|
91
|
+
path.join(this.krenkDir, "state.json"),
|
|
92
|
+
"utf-8"
|
|
93
|
+
);
|
|
94
|
+
return JSON.parse(data);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Get the .krenk directory path */
|
|
100
|
+
getKrenkDir() {
|
|
101
|
+
return this.krenkDir;
|
|
102
|
+
}
|
|
103
|
+
/** Get the run ID for this session */
|
|
104
|
+
getRunId() {
|
|
105
|
+
return this.runId;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/agents/spawner.ts
|
|
110
|
+
import { spawn } from "child_process";
|
|
111
|
+
import { EventEmitter } from "events";
|
|
112
|
+
var activeChildren = /* @__PURE__ */ new Set();
|
|
113
|
+
function killAllAgents() {
|
|
114
|
+
for (const child of activeChildren) {
|
|
115
|
+
forceKillChild(child);
|
|
116
|
+
}
|
|
117
|
+
activeChildren.clear();
|
|
118
|
+
}
|
|
119
|
+
function forceKillChild(child) {
|
|
120
|
+
if (child.killed) return;
|
|
121
|
+
if (child.pid) {
|
|
122
|
+
try {
|
|
123
|
+
process.kill(-child.pid, "SIGTERM");
|
|
124
|
+
} catch {
|
|
125
|
+
try {
|
|
126
|
+
child.kill("SIGTERM");
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
try {
|
|
132
|
+
child.kill("SIGTERM");
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
if (!child.killed && child.pid) {
|
|
138
|
+
try {
|
|
139
|
+
process.kill(-child.pid, "SIGKILL");
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
child.kill("SIGKILL");
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, 3e3).unref();
|
|
148
|
+
}
|
|
149
|
+
function spawnClaudeAgent(opts) {
|
|
150
|
+
const emitter = new EventEmitter();
|
|
151
|
+
const env = { ...process.env };
|
|
152
|
+
delete env.CLAUDECODE;
|
|
153
|
+
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
154
|
+
const fullPrompt = opts.context ? `${opts.context}
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
Your current task:
|
|
159
|
+
${opts.prompt}` : opts.prompt;
|
|
160
|
+
const args = [
|
|
161
|
+
"-p",
|
|
162
|
+
fullPrompt,
|
|
163
|
+
"--output-format",
|
|
164
|
+
"stream-json",
|
|
165
|
+
"--max-turns",
|
|
166
|
+
String(opts.maxTurns || 50),
|
|
167
|
+
"--verbose",
|
|
168
|
+
"--dangerously-skip-permissions"
|
|
169
|
+
];
|
|
170
|
+
if (opts.systemPrompt) {
|
|
171
|
+
args.push("--system-prompt", opts.systemPrompt);
|
|
172
|
+
}
|
|
173
|
+
if (opts.allowedTools?.length) {
|
|
174
|
+
args.push("--allowedTools", ...opts.allowedTools);
|
|
175
|
+
}
|
|
176
|
+
if (opts.disallowedTools?.length) {
|
|
177
|
+
args.push("--disallowedTools", ...opts.disallowedTools);
|
|
178
|
+
}
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
const child = spawn("claude", args, {
|
|
181
|
+
env,
|
|
182
|
+
cwd: opts.cwd,
|
|
183
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
184
|
+
detached: true
|
|
185
|
+
// Create process group so we can kill the whole tree
|
|
186
|
+
});
|
|
187
|
+
emitter.child = child;
|
|
188
|
+
activeChildren.add(child);
|
|
189
|
+
let stdout = "";
|
|
190
|
+
let stderr = "";
|
|
191
|
+
let stdoutLineBuffer = "";
|
|
192
|
+
child.stdout.on("data", (chunk) => {
|
|
193
|
+
const text = chunk.toString();
|
|
194
|
+
stdout += text;
|
|
195
|
+
stdoutLineBuffer += text;
|
|
196
|
+
const lines = stdoutLineBuffer.split("\n");
|
|
197
|
+
stdoutLineBuffer = lines.pop() || "";
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
const trimmed = line.trim();
|
|
200
|
+
if (trimmed) {
|
|
201
|
+
emitter.emit("data", trimmed);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
child.stderr.on("data", (chunk) => {
|
|
206
|
+
const text = chunk.toString();
|
|
207
|
+
stderr += text;
|
|
208
|
+
emitter.emit("stderr", text);
|
|
209
|
+
});
|
|
210
|
+
child.on("close", (code) => {
|
|
211
|
+
activeChildren.delete(child);
|
|
212
|
+
const duration = Math.round((Date.now() - startTime) / 1e3);
|
|
213
|
+
const result = {
|
|
214
|
+
role: opts.role,
|
|
215
|
+
output: stdout,
|
|
216
|
+
duration,
|
|
217
|
+
success: code === 0,
|
|
218
|
+
exitCode: code
|
|
219
|
+
};
|
|
220
|
+
const lines = stdout.trim().split("\n");
|
|
221
|
+
let foundResult = false;
|
|
222
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
223
|
+
try {
|
|
224
|
+
const parsed = JSON.parse(lines[i]);
|
|
225
|
+
if (parsed.type === "result") {
|
|
226
|
+
if (parsed.result) {
|
|
227
|
+
result.output = parsed.result;
|
|
228
|
+
}
|
|
229
|
+
if (parsed.cost_usd !== void 0) {
|
|
230
|
+
result.cost = { input: parsed.cost_usd, output: 0 };
|
|
231
|
+
}
|
|
232
|
+
foundResult = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!foundResult) {
|
|
239
|
+
try {
|
|
240
|
+
const parsed = JSON.parse(stdout);
|
|
241
|
+
if (parsed.result) {
|
|
242
|
+
result.output = parsed.result;
|
|
243
|
+
}
|
|
244
|
+
if (parsed.cost_usd !== void 0) {
|
|
245
|
+
result.cost = { input: parsed.cost_usd, output: 0 };
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
emitter.emit("done", result);
|
|
251
|
+
});
|
|
252
|
+
child.on("error", (err) => {
|
|
253
|
+
activeChildren.delete(child);
|
|
254
|
+
emitter.emit("error", err);
|
|
255
|
+
});
|
|
256
|
+
process.nextTick(() => {
|
|
257
|
+
emitter.emit("spawned", child.pid);
|
|
258
|
+
});
|
|
259
|
+
return emitter;
|
|
260
|
+
}
|
|
261
|
+
function runAgent(opts) {
|
|
262
|
+
return new Promise((resolve, reject) => {
|
|
263
|
+
const emitter = spawnClaudeAgent(opts);
|
|
264
|
+
emitter.on("done", (result) => {
|
|
265
|
+
resolve(result);
|
|
266
|
+
});
|
|
267
|
+
emitter.on("error", (err) => {
|
|
268
|
+
reject(err);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function killAgent(emitter) {
|
|
273
|
+
if (emitter.child) {
|
|
274
|
+
forceKillChild(emitter.child);
|
|
275
|
+
activeChildren.delete(emitter.child);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/orchestrator/scheduler.ts
|
|
280
|
+
var Scheduler = class {
|
|
281
|
+
maxParallel;
|
|
282
|
+
running = /* @__PURE__ */ new Map();
|
|
283
|
+
constructor(maxParallel = 3) {
|
|
284
|
+
this.maxParallel = maxParallel;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Run multiple agents in parallel batches, respecting concurrency limit
|
|
288
|
+
*/
|
|
289
|
+
async runParallel(agents, onProgress) {
|
|
290
|
+
const results = [];
|
|
291
|
+
const batches = this.chunk(agents, this.maxParallel);
|
|
292
|
+
for (const batch of batches) {
|
|
293
|
+
const batchResults = await Promise.all(
|
|
294
|
+
batch.map((opts) => this.runOne(opts, onProgress))
|
|
295
|
+
);
|
|
296
|
+
results.push(...batchResults);
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Run a single agent and return a promise of its result
|
|
302
|
+
*/
|
|
303
|
+
runOne(opts, onProgress) {
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
const emitter = spawnClaudeAgent(opts);
|
|
306
|
+
this.running.set(opts.role, emitter);
|
|
307
|
+
emitter.on("spawned", (pid) => {
|
|
308
|
+
onProgress?.(opts.role, "spawned", { pid });
|
|
309
|
+
});
|
|
310
|
+
emitter.on("data", (text) => {
|
|
311
|
+
onProgress?.(opts.role, "data", { text });
|
|
312
|
+
});
|
|
313
|
+
emitter.on("done", (result) => {
|
|
314
|
+
this.running.delete(opts.role);
|
|
315
|
+
onProgress?.(opts.role, "done", result);
|
|
316
|
+
resolve(result);
|
|
317
|
+
});
|
|
318
|
+
emitter.on("error", (err) => {
|
|
319
|
+
this.running.delete(opts.role);
|
|
320
|
+
onProgress?.(opts.role, "error", err);
|
|
321
|
+
reject(err);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Kill all currently running agents
|
|
327
|
+
*/
|
|
328
|
+
killAll() {
|
|
329
|
+
for (const [role, emitter] of this.running) {
|
|
330
|
+
killAgent(emitter);
|
|
331
|
+
this.running.delete(role);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get count of currently running agents
|
|
336
|
+
*/
|
|
337
|
+
getRunningCount() {
|
|
338
|
+
return this.running.size;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Split array into chunks of given size
|
|
342
|
+
*/
|
|
343
|
+
chunk(arr, size) {
|
|
344
|
+
const chunks = [];
|
|
345
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
346
|
+
chunks.push(arr.slice(i, i + size));
|
|
347
|
+
}
|
|
348
|
+
return chunks;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// src/orchestrator/workflow.ts
|
|
353
|
+
var STAGES = [
|
|
354
|
+
{
|
|
355
|
+
stage: "analyzing",
|
|
356
|
+
role: "analyst",
|
|
357
|
+
label: "Analyze",
|
|
358
|
+
emoji: "@",
|
|
359
|
+
description: "Business analysis, user stories, and acceptance criteria"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
stage: "planning",
|
|
363
|
+
role: "strategist",
|
|
364
|
+
label: "Plan",
|
|
365
|
+
emoji: ">",
|
|
366
|
+
description: "Analyzing requirements and creating implementation plan"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
stage: "designing",
|
|
370
|
+
role: "designer",
|
|
371
|
+
label: "Design",
|
|
372
|
+
emoji: "*",
|
|
373
|
+
description: "Designing UI/UX and component structure"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
stage: "architecting",
|
|
377
|
+
role: "architect",
|
|
378
|
+
label: "Architect",
|
|
379
|
+
emoji: "#",
|
|
380
|
+
description: "Designing system architecture and creating project skeleton"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
stage: "coding",
|
|
384
|
+
role: "builder",
|
|
385
|
+
label: "Code",
|
|
386
|
+
emoji: "+",
|
|
387
|
+
description: "Implementing code modules in parallel"
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
stage: "qa-planning",
|
|
391
|
+
role: "qa",
|
|
392
|
+
label: "QA",
|
|
393
|
+
emoji: "%",
|
|
394
|
+
description: "Creating test strategy and detailed test plans"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
stage: "testing",
|
|
398
|
+
role: "guardian",
|
|
399
|
+
label: "Test",
|
|
400
|
+
emoji: "~",
|
|
401
|
+
description: "Generating and running test suites"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
stage: "reviewing",
|
|
405
|
+
role: "sentinel",
|
|
406
|
+
label: "Review",
|
|
407
|
+
emoji: "?",
|
|
408
|
+
description: "Reviewing code for quality and correctness"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
stage: "securing",
|
|
412
|
+
role: "security",
|
|
413
|
+
label: "Security",
|
|
414
|
+
emoji: "!",
|
|
415
|
+
description: "Security audit and vulnerability assessment"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
stage: "documenting",
|
|
419
|
+
role: "scribe",
|
|
420
|
+
label: "Docs",
|
|
421
|
+
emoji: "=",
|
|
422
|
+
description: "Generating documentation"
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
stage: "deploying",
|
|
426
|
+
role: "devops",
|
|
427
|
+
label: "DevOps",
|
|
428
|
+
emoji: "&",
|
|
429
|
+
description: "Setting up CI/CD and deployment configuration"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
stage: "complete",
|
|
433
|
+
role: "",
|
|
434
|
+
label: "Done",
|
|
435
|
+
emoji: "+",
|
|
436
|
+
description: "Workflow complete"
|
|
437
|
+
}
|
|
438
|
+
];
|
|
439
|
+
function getStageInfo(stage) {
|
|
440
|
+
return STAGES.find((s) => s.stage === stage);
|
|
441
|
+
}
|
|
442
|
+
function shouldSkipStage(stage, skipStages) {
|
|
443
|
+
const info = getStageInfo(stage);
|
|
444
|
+
if (!info) return false;
|
|
445
|
+
return skipStages.includes(stage) || skipStages.includes(info.role) || skipStages.includes(info.label.toLowerCase());
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/agents/roles.ts
|
|
449
|
+
var SHARED_MEMORY_INSTRUCTIONS = `
|
|
450
|
+
|
|
451
|
+
SHARED MEMORY:
|
|
452
|
+
You are part of a multi-agent team managed by a Master Brain.
|
|
453
|
+
The team shares knowledge via files in .krenk/shared/:
|
|
454
|
+
- brain.md \u2014 Master Brain's directives (READ THIS FIRST)
|
|
455
|
+
- status.md \u2014 Live status of all team members
|
|
456
|
+
- learnings.md \u2014 Accumulated team knowledge
|
|
457
|
+
- blockers.md \u2014 Known issues
|
|
458
|
+
- decisions.md \u2014 Decisions log
|
|
459
|
+
- <role>.md \u2014 Each agent's progress
|
|
460
|
+
|
|
461
|
+
Before starting work, read .krenk/shared/brain.md for any directives.
|
|
462
|
+
If you have Write access, update .krenk/shared/<your-role>.md with your progress.
|
|
463
|
+
Check other agents' files if you need to understand what they did.
|
|
464
|
+
|
|
465
|
+
IMPORTANT: Stay in your lane. Only do what your role requires. Do NOT do other agents' jobs.
|
|
466
|
+
`;
|
|
467
|
+
var ROLES = {
|
|
468
|
+
strategist: {
|
|
469
|
+
name: "Strategist",
|
|
470
|
+
emoji: ">",
|
|
471
|
+
color: "#FF6B6B",
|
|
472
|
+
description: "Requirements analysis & task planning",
|
|
473
|
+
systemPrompt: `You are the Strategist \u2014 the lead planner in a multi-agent software engineering team.
|
|
474
|
+
Your job is to analyze the user's request and create a MASTER PLAN that tells every team member exactly what to do.
|
|
475
|
+
|
|
476
|
+
CRITICAL RULES:
|
|
477
|
+
- You are a PLANNER ONLY. You produce a text plan. That is your ENTIRE job.
|
|
478
|
+
- DO NOT write code. DO NOT create files. DO NOT run commands. DO NOT install packages.
|
|
479
|
+
- DO NOT use Write, Edit, or Bash tools. You may ONLY use Read, Glob, and Grep to understand existing code.
|
|
480
|
+
- Your output is ONLY a markdown plan document. Nothing else.
|
|
481
|
+
|
|
482
|
+
Output a structured plan with these sections:
|
|
483
|
+
|
|
484
|
+
1. OVERVIEW: What we're building and why
|
|
485
|
+
2. REQUIREMENTS: Functional and non-functional requirements
|
|
486
|
+
3. TASKS: Numbered list of implementation tasks with estimates
|
|
487
|
+
4. MODULES: How to split the work for parallel development (each module should be independently implementable)
|
|
488
|
+
5. RISKS: Potential issues and mitigations
|
|
489
|
+
6. FILE STRUCTURE: Proposed project files and directories
|
|
490
|
+
|
|
491
|
+
7. AGENT ASSIGNMENTS: This is CRITICAL. Assign specific tasks to each team member.
|
|
492
|
+
Use this exact format for each agent you want to activate:
|
|
493
|
+
|
|
494
|
+
### ASSIGN:DESIGNER
|
|
495
|
+
<specific tasks for the designer \u2014 what to design, which pages, which components>
|
|
496
|
+
|
|
497
|
+
### ASSIGN:ARCHITECT
|
|
498
|
+
<specific tasks \u2014 what to architect, which APIs, which data models, which modules to split>
|
|
499
|
+
|
|
500
|
+
### ASSIGN:BUILDER
|
|
501
|
+
<specific tasks \u2014 what to build, which modules, which features to implement first>
|
|
502
|
+
|
|
503
|
+
### ASSIGN:QA
|
|
504
|
+
<specific tasks \u2014 which features need test plans, what edge cases to focus on>
|
|
505
|
+
|
|
506
|
+
### ASSIGN:GUARDIAN
|
|
507
|
+
<specific tasks \u2014 what tests to write, which modules to test, what coverage targets>
|
|
508
|
+
|
|
509
|
+
### ASSIGN:SENTINEL
|
|
510
|
+
<specific tasks \u2014 what to review, which areas are risky, what patterns to check>
|
|
511
|
+
|
|
512
|
+
### ASSIGN:SECURITY
|
|
513
|
+
<specific tasks \u2014 what to audit, which areas handle user input, auth flows to check>
|
|
514
|
+
|
|
515
|
+
### ASSIGN:SCRIBE
|
|
516
|
+
<specific tasks \u2014 what docs to write, README sections, API docs needed>
|
|
517
|
+
|
|
518
|
+
### ASSIGN:DEVOPS
|
|
519
|
+
<specific tasks \u2014 CI pipeline, Docker setup, deployment target, env variables>
|
|
520
|
+
|
|
521
|
+
Only include ASSIGN sections for agents that are actually needed. Skip any that aren't relevant.
|
|
522
|
+
Each assignment should be specific and actionable \u2014 tell them exactly what to do, not generic instructions.
|
|
523
|
+
|
|
524
|
+
Be thorough but practical. Focus on actionable tasks.
|
|
525
|
+
Format your output in clear markdown sections.
|
|
526
|
+
REMINDER: Output ONLY a text plan. Do NOT create files, write code, or run any commands.`,
|
|
527
|
+
allowedTools: ["Read", "Glob", "Grep", "WebSearch", "WebFetch"],
|
|
528
|
+
disallowedTools: ["Write", "Edit", "Bash", "NotebookEdit"]
|
|
529
|
+
},
|
|
530
|
+
designer: {
|
|
531
|
+
name: "Pixel",
|
|
532
|
+
emoji: "*",
|
|
533
|
+
color: "#4ECDC4",
|
|
534
|
+
description: "UI/UX design & component structure",
|
|
535
|
+
systemPrompt: `You are Pixel, the UI/UX design agent.
|
|
536
|
+
Your job is to design the user interface and experience.
|
|
537
|
+
|
|
538
|
+
CRITICAL RULES:
|
|
539
|
+
- You are a DESIGNER ONLY. You produce a design spec document. That is your ENTIRE job.
|
|
540
|
+
- DO NOT write full application code. DO NOT install packages. DO NOT run commands.
|
|
541
|
+
- You may write component scaffold files to illustrate your design, but do NOT implement business logic.
|
|
542
|
+
- Focus on structure, layout, and visual specs \u2014 the Builder will implement the actual code.
|
|
543
|
+
|
|
544
|
+
Output:
|
|
545
|
+
1. USER FLOWS: Key user journeys
|
|
546
|
+
2. COMPONENT TREE: UI component hierarchy
|
|
547
|
+
3. LAYOUT: Page/screen layouts described in detail
|
|
548
|
+
4. STYLING: Color palette, typography, spacing guidelines
|
|
549
|
+
5. INTERACTIONS: Animations, transitions, states
|
|
550
|
+
6. RESPONSIVE: Mobile/desktop considerations
|
|
551
|
+
|
|
552
|
+
Write component scaffolds when useful. Focus on developer-implementable specs.
|
|
553
|
+
Format your output in clear markdown sections.`,
|
|
554
|
+
allowedTools: ["Read", "Write", "Glob", "Grep", "WebSearch"]
|
|
555
|
+
},
|
|
556
|
+
architect: {
|
|
557
|
+
name: "Blueprint",
|
|
558
|
+
emoji: "#",
|
|
559
|
+
color: "#45B7D1",
|
|
560
|
+
description: "System architecture & API design",
|
|
561
|
+
systemPrompt: `You are Blueprint, the system architect agent.
|
|
562
|
+
Your job is to design the technical architecture and create the project skeleton.
|
|
563
|
+
|
|
564
|
+
CRITICAL RULES:
|
|
565
|
+
- You are an ARCHITECT. You create the project skeleton, configs, and boilerplate ONLY.
|
|
566
|
+
- You set up the project structure, install dependencies, and write config files.
|
|
567
|
+
- DO NOT implement business logic or features \u2014 the Builder will do that.
|
|
568
|
+
- Keep your code to: project init, folder structure, config files, type definitions, and empty/skeleton files.
|
|
569
|
+
|
|
570
|
+
Output:
|
|
571
|
+
1. ARCHITECTURE: System design with clear boundaries
|
|
572
|
+
2. DATA MODEL: Database schema or data structures
|
|
573
|
+
3. API DESIGN: Endpoints, request/response shapes
|
|
574
|
+
4. FILE STRUCTURE: Create the actual directory structure and boilerplate files
|
|
575
|
+
5. DEPENDENCIES: Required packages with versions
|
|
576
|
+
6. PATTERNS: Design patterns to follow
|
|
577
|
+
7. MODULE SPLIT: Define independent modules that can be coded in parallel. Format each module as:
|
|
578
|
+
### MODULE: <name>
|
|
579
|
+
**Files:** <comma-separated file paths>
|
|
580
|
+
**Description:** <what this module does>
|
|
581
|
+
|
|
582
|
+
Create actual files for the project structure, configs, and boilerplate.
|
|
583
|
+
The MODULE SPLIT section is critical - it tells the system how to parallelize coding.
|
|
584
|
+
REMINDER: Set up skeleton only. Do NOT implement features \u2014 that's the Builder's job.`,
|
|
585
|
+
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
586
|
+
},
|
|
587
|
+
builder: {
|
|
588
|
+
name: "Builder",
|
|
589
|
+
emoji: "+",
|
|
590
|
+
color: "#96CEB4",
|
|
591
|
+
description: "Code implementation",
|
|
592
|
+
systemPrompt: `You are Builder, the implementation agent.
|
|
593
|
+
Your job is to write production-quality code based on the plan and architecture.
|
|
594
|
+
|
|
595
|
+
Rules:
|
|
596
|
+
- Follow the architecture exactly
|
|
597
|
+
- Write clean, well-structured code
|
|
598
|
+
- Handle errors properly
|
|
599
|
+
- Follow the existing code style
|
|
600
|
+
- Install dependencies as needed
|
|
601
|
+
- Make sure files are complete and runnable
|
|
602
|
+
|
|
603
|
+
Focus on your assigned module. Do not modify files outside your scope unless absolutely necessary.`,
|
|
604
|
+
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
605
|
+
},
|
|
606
|
+
guardian: {
|
|
607
|
+
name: "Guardian",
|
|
608
|
+
emoji: "~",
|
|
609
|
+
color: "#FFEAA7",
|
|
610
|
+
description: "Testing & test suite generation",
|
|
611
|
+
systemPrompt: `You are Guardian, the testing agent.
|
|
612
|
+
Your job is to create comprehensive test suites and run them.
|
|
613
|
+
|
|
614
|
+
Tasks:
|
|
615
|
+
1. Analyze the codebase to understand what to test
|
|
616
|
+
2. Write unit tests for all modules
|
|
617
|
+
3. Write integration tests for APIs/flows
|
|
618
|
+
4. Run the tests and fix any failures
|
|
619
|
+
5. Report test coverage and results
|
|
620
|
+
|
|
621
|
+
Use the project's testing framework or set one up if none exists.
|
|
622
|
+
Output your results with clear PASS/FAIL indicators.`,
|
|
623
|
+
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
624
|
+
},
|
|
625
|
+
sentinel: {
|
|
626
|
+
name: "Sentinel",
|
|
627
|
+
emoji: "?",
|
|
628
|
+
color: "#DDA0DD",
|
|
629
|
+
description: "Code review & quality assurance",
|
|
630
|
+
systemPrompt: `You are Sentinel, the code review agent.
|
|
631
|
+
Your job is to review all code changes for quality and correctness.
|
|
632
|
+
|
|
633
|
+
CRITICAL RULES:
|
|
634
|
+
- You are a REVIEWER ONLY. You produce a review report. That is your ENTIRE job.
|
|
635
|
+
- DO NOT write code. DO NOT create files. DO NOT fix bugs. DO NOT run commands.
|
|
636
|
+
- DO NOT use Write, Edit, or Bash tools. You may ONLY use Read, Glob, and Grep.
|
|
637
|
+
- Your output is ONLY a review report. The Builder will fix any issues you find.
|
|
638
|
+
|
|
639
|
+
Review for:
|
|
640
|
+
1. BUGS: Logic errors, edge cases, race conditions
|
|
641
|
+
2. SECURITY: Injection, XSS, auth issues, secrets in code
|
|
642
|
+
3. PERFORMANCE: N+1 queries, memory leaks, unnecessary computation
|
|
643
|
+
4. STYLE: Consistency, naming, dead code
|
|
644
|
+
5. ARCHITECTURE: Does implementation match the design?
|
|
645
|
+
|
|
646
|
+
Output a structured review with severity levels: CRITICAL, WARNING, INFO.
|
|
647
|
+
If critical issues are found, include "NEEDS_REVISION" at the top with specific fixes required.
|
|
648
|
+
If everything looks good, include "APPROVED" at the top.`,
|
|
649
|
+
allowedTools: ["Read", "Glob", "Grep"],
|
|
650
|
+
disallowedTools: ["Write", "Edit", "Bash", "NotebookEdit"]
|
|
651
|
+
},
|
|
652
|
+
scribe: {
|
|
653
|
+
name: "Scribe",
|
|
654
|
+
emoji: "=",
|
|
655
|
+
color: "#B8E986",
|
|
656
|
+
description: "Documentation generation",
|
|
657
|
+
systemPrompt: `You are Scribe, the documentation agent.
|
|
658
|
+
Your job is to create clear, useful documentation.
|
|
659
|
+
|
|
660
|
+
Create:
|
|
661
|
+
1. README.md with setup instructions, usage, and examples
|
|
662
|
+
2. API documentation if applicable
|
|
663
|
+
3. Architecture decision records for key choices
|
|
664
|
+
4. Inline code comments where logic is complex
|
|
665
|
+
|
|
666
|
+
Keep docs concise, practical, and developer-friendly.
|
|
667
|
+
Do not over-document obvious code.`,
|
|
668
|
+
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep"]
|
|
669
|
+
},
|
|
670
|
+
analyst: {
|
|
671
|
+
name: "Analyst",
|
|
672
|
+
emoji: "@",
|
|
673
|
+
color: "#F0A500",
|
|
674
|
+
description: "Business analysis & user stories",
|
|
675
|
+
systemPrompt: `You are the Analyst (Business Analyst) agent in a software engineering team.
|
|
676
|
+
Your job is to deeply understand the business requirements and translate them into clear, actionable specs.
|
|
677
|
+
|
|
678
|
+
CRITICAL RULES:
|
|
679
|
+
- You are an ANALYST ONLY. You produce a requirements document. That is your ENTIRE job.
|
|
680
|
+
- DO NOT write code. DO NOT create files. DO NOT run commands.
|
|
681
|
+
- DO NOT use Write, Edit, or Bash tools. You may ONLY use Read, Glob, and Grep to understand existing code.
|
|
682
|
+
- Your output is ONLY a markdown analysis document. Nothing else.
|
|
683
|
+
|
|
684
|
+
Output:
|
|
685
|
+
1. PROBLEM STATEMENT: What problem are we solving and for whom
|
|
686
|
+
2. USER PERSONAS: Who are the target users, their goals and pain points
|
|
687
|
+
3. USER STORIES: Write user stories in "As a [user], I want [goal] so that [reason]" format
|
|
688
|
+
4. ACCEPTANCE CRITERIA: Clear pass/fail criteria for each user story
|
|
689
|
+
5. EDGE CASES: Unusual scenarios and how they should be handled
|
|
690
|
+
6. PRIORITIES: MoSCoW prioritization (Must have, Should have, Could have, Won't have)
|
|
691
|
+
7. SUCCESS METRICS: How we'll measure if this is working
|
|
692
|
+
|
|
693
|
+
Be thorough. Think about what the developer needs to know to build the right thing.
|
|
694
|
+
Format your output in clear markdown sections.
|
|
695
|
+
REMINDER: Output ONLY a text document. Do NOT create files, write code, or run any commands.`,
|
|
696
|
+
allowedTools: ["Read", "Glob", "Grep", "WebSearch", "WebFetch"],
|
|
697
|
+
disallowedTools: ["Write", "Edit", "Bash", "NotebookEdit"]
|
|
698
|
+
},
|
|
699
|
+
qa: {
|
|
700
|
+
name: "QA Lead",
|
|
701
|
+
emoji: "%",
|
|
702
|
+
color: "#E17055",
|
|
703
|
+
description: "Test planning & quality strategy",
|
|
704
|
+
systemPrompt: `You are the QA Lead agent in a software engineering team.
|
|
705
|
+
Your job is to create a comprehensive test strategy and detailed test plans BEFORE code is tested.
|
|
706
|
+
|
|
707
|
+
CRITICAL RULES:
|
|
708
|
+
- You are a QA PLANNER. You produce test plans and test case documents.
|
|
709
|
+
- You may write test case files as examples, but the Guardian will execute and expand them.
|
|
710
|
+
- DO NOT implement features. DO NOT modify source code. DO NOT run the application.
|
|
711
|
+
|
|
712
|
+
Output:
|
|
713
|
+
1. TEST STRATEGY: Overall approach (unit, integration, e2e, manual)
|
|
714
|
+
2. TEST PLAN: Detailed test cases organized by feature/module
|
|
715
|
+
- Test ID, description, preconditions, steps, expected result, priority
|
|
716
|
+
3. TEST MATRIX: Coverage matrix mapping requirements to test cases
|
|
717
|
+
4. REGRESSION CHECKLIST: Critical paths that must always pass
|
|
718
|
+
5. EDGE CASES: Boundary values, error conditions, concurrency scenarios
|
|
719
|
+
6. PERFORMANCE CRITERIA: Load expectations, response time targets
|
|
720
|
+
7. ACCESSIBILITY CHECKLIST: A11y requirements if applicable
|
|
721
|
+
8. TEST DATA: Sample data sets needed for testing
|
|
722
|
+
|
|
723
|
+
Write actual test case files when useful. Be specific about what to verify.
|
|
724
|
+
Format your output in clear markdown sections.`,
|
|
725
|
+
allowedTools: ["Read", "Write", "Glob", "Grep"]
|
|
726
|
+
},
|
|
727
|
+
devops: {
|
|
728
|
+
name: "DevOps",
|
|
729
|
+
emoji: "&",
|
|
730
|
+
color: "#6C5CE7",
|
|
731
|
+
description: "CI/CD, deployment & infrastructure",
|
|
732
|
+
systemPrompt: `You are the DevOps agent in a software engineering team.
|
|
733
|
+
Your job is to set up CI/CD pipelines, deployment configuration, and infrastructure.
|
|
734
|
+
|
|
735
|
+
Tasks:
|
|
736
|
+
1. CI PIPELINE: Set up GitHub Actions or similar CI workflow
|
|
737
|
+
- Lint, test, build, deploy steps
|
|
738
|
+
2. DOCKER: Create Dockerfile and docker-compose.yml if appropriate
|
|
739
|
+
3. DEPLOYMENT: Configure deployment (Vercel, Railway, AWS, etc.)
|
|
740
|
+
4. ENVIRONMENT: Set up .env.example with all required variables
|
|
741
|
+
5. SCRIPTS: Add useful npm/make scripts for dev workflow
|
|
742
|
+
6. MONITORING: Basic health checks and logging setup
|
|
743
|
+
7. SECURITY: Environment variable management, secrets handling
|
|
744
|
+
|
|
745
|
+
Create actual config files. Focus on a simple, working setup.
|
|
746
|
+
Format your output in clear markdown sections.`,
|
|
747
|
+
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
748
|
+
},
|
|
749
|
+
security: {
|
|
750
|
+
name: "Shield",
|
|
751
|
+
emoji: "!",
|
|
752
|
+
color: "#D63031",
|
|
753
|
+
description: "Security audit & vulnerability assessment",
|
|
754
|
+
systemPrompt: `You are Shield, the security audit agent in a software engineering team.
|
|
755
|
+
Your job is to perform a thorough security assessment of the codebase.
|
|
756
|
+
|
|
757
|
+
CRITICAL RULES:
|
|
758
|
+
- You are an AUDITOR ONLY. You produce a security report. That is your ENTIRE job.
|
|
759
|
+
- DO NOT write code. DO NOT create files. DO NOT fix issues.
|
|
760
|
+
- DO NOT use Write or Edit tools. You may use Read, Glob, Grep, and Bash (for npm audit only).
|
|
761
|
+
- Your output is ONLY a security report. The Builder will fix any issues you find.
|
|
762
|
+
|
|
763
|
+
Audit for:
|
|
764
|
+
1. INJECTION: SQL injection, command injection, XSS, template injection
|
|
765
|
+
2. AUTHENTICATION: Weak auth, missing auth checks, session handling
|
|
766
|
+
3. AUTHORIZATION: Privilege escalation, IDOR, missing access controls
|
|
767
|
+
4. DATA EXPOSURE: Secrets in code, sensitive data in logs, PII handling
|
|
768
|
+
5. DEPENDENCIES: Known vulnerabilities in dependencies (run npm audit or similar)
|
|
769
|
+
6. CONFIGURATION: Insecure defaults, debug mode, CORS, CSP headers
|
|
770
|
+
7. CRYPTOGRAPHY: Weak hashing, insecure random, hardcoded keys
|
|
771
|
+
8. API SECURITY: Rate limiting, input validation, error leakage
|
|
772
|
+
|
|
773
|
+
Output a structured report with severity levels: CRITICAL, HIGH, MEDIUM, LOW.
|
|
774
|
+
Include specific file paths, line numbers, and recommended fixes.
|
|
775
|
+
If critical issues are found, include "NEEDS_REVISION" at the top.
|
|
776
|
+
If everything looks good, include "APPROVED" at the top.`,
|
|
777
|
+
allowedTools: ["Read", "Bash", "Glob", "Grep"],
|
|
778
|
+
disallowedTools: ["Write", "Edit", "NotebookEdit"]
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
for (const role of Object.values(ROLES)) {
|
|
782
|
+
role.systemPrompt += SHARED_MEMORY_INSTRUCTIONS;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/agents/registry.ts
|
|
786
|
+
var AgentRegistry = class {
|
|
787
|
+
agents = /* @__PURE__ */ new Map();
|
|
788
|
+
register(role, name, emoji, color) {
|
|
789
|
+
this.agents.set(role, {
|
|
790
|
+
role,
|
|
791
|
+
name,
|
|
792
|
+
emoji,
|
|
793
|
+
color,
|
|
794
|
+
status: "idle",
|
|
795
|
+
output: []
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
activate(role, pid, emitter) {
|
|
799
|
+
const entry = this.agents.get(role);
|
|
800
|
+
if (entry) {
|
|
801
|
+
entry.status = "active";
|
|
802
|
+
entry.pid = pid;
|
|
803
|
+
entry.emitter = emitter;
|
|
804
|
+
entry.startTime = Date.now();
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
complete(role, result) {
|
|
808
|
+
const entry = this.agents.get(role);
|
|
809
|
+
if (entry) {
|
|
810
|
+
entry.status = result.success ? "done" : "failed";
|
|
811
|
+
entry.result = result;
|
|
812
|
+
entry.endTime = Date.now();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
appendOutput(role, line) {
|
|
816
|
+
const entry = this.agents.get(role);
|
|
817
|
+
if (entry) {
|
|
818
|
+
entry.output.push(line);
|
|
819
|
+
if (entry.output.length > 100) {
|
|
820
|
+
entry.output = entry.output.slice(-100);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
get(role) {
|
|
825
|
+
return this.agents.get(role);
|
|
826
|
+
}
|
|
827
|
+
getAll() {
|
|
828
|
+
return Array.from(this.agents.values());
|
|
829
|
+
}
|
|
830
|
+
getActive() {
|
|
831
|
+
return this.getAll().filter((a) => a.status === "active");
|
|
832
|
+
}
|
|
833
|
+
getDuration(role) {
|
|
834
|
+
const entry = this.agents.get(role);
|
|
835
|
+
if (!entry?.startTime) return void 0;
|
|
836
|
+
const end = entry.endTime || Date.now();
|
|
837
|
+
return Math.round((end - entry.startTime) / 1e3);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Kill all active agent processes (for graceful shutdown)
|
|
841
|
+
*/
|
|
842
|
+
killAll() {
|
|
843
|
+
for (const entry of this.getActive()) {
|
|
844
|
+
if (entry.emitter) {
|
|
845
|
+
killAgent(entry.emitter);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/utils/logger.ts
|
|
852
|
+
import chalk from "chalk";
|
|
853
|
+
var LOG_LEVELS = {
|
|
854
|
+
debug: 0,
|
|
855
|
+
info: 1,
|
|
856
|
+
warn: 2,
|
|
857
|
+
error: 3
|
|
858
|
+
};
|
|
859
|
+
var Logger = class {
|
|
860
|
+
level = "info";
|
|
861
|
+
quiet = false;
|
|
862
|
+
setLevel(level) {
|
|
863
|
+
this.level = level;
|
|
864
|
+
}
|
|
865
|
+
setQuiet(quiet) {
|
|
866
|
+
this.quiet = quiet;
|
|
867
|
+
}
|
|
868
|
+
debug(msg, ...args) {
|
|
869
|
+
if (this.shouldLog("debug")) {
|
|
870
|
+
console.error(chalk.gray(`[DEBUG] ${msg}`), ...args);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
info(msg, ...args) {
|
|
874
|
+
if (this.shouldLog("info")) {
|
|
875
|
+
console.error(chalk.blue(`[INFO] ${msg}`), ...args);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
warn(msg, ...args) {
|
|
879
|
+
if (this.shouldLog("warn")) {
|
|
880
|
+
console.error(chalk.yellow(`[WARN] ${msg}`), ...args);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
error(msg, ...args) {
|
|
884
|
+
if (this.shouldLog("error")) {
|
|
885
|
+
console.error(chalk.red(`[ERROR] ${msg}`), ...args);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
success(msg, ...args) {
|
|
889
|
+
if (this.shouldLog("info")) {
|
|
890
|
+
console.error(chalk.green(`[OK] ${msg}`), ...args);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
shouldLog(level) {
|
|
894
|
+
if (this.quiet) return level === "error";
|
|
895
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
var logger = new Logger();
|
|
899
|
+
|
|
900
|
+
// src/orchestrator/supervisor.ts
|
|
901
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
902
|
+
import { execSync } from "child_process";
|
|
903
|
+
var DEFAULT_LIMITS = {
|
|
904
|
+
maxMemoryMB: 512,
|
|
905
|
+
maxTimeSeconds: 600,
|
|
906
|
+
hungWarningSeconds: 120,
|
|
907
|
+
hungKillSeconds: 300,
|
|
908
|
+
pollIntervalMs: 5e3
|
|
909
|
+
};
|
|
910
|
+
var ProcessSupervisor = class extends EventEmitter2 {
|
|
911
|
+
tracked = /* @__PURE__ */ new Map();
|
|
912
|
+
pollTimer = null;
|
|
913
|
+
limits;
|
|
914
|
+
constructor(limits) {
|
|
915
|
+
super();
|
|
916
|
+
this.limits = { ...DEFAULT_LIMITS, ...limits };
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Start watching a spawned child process.
|
|
920
|
+
*/
|
|
921
|
+
track(role, child) {
|
|
922
|
+
if (!child.pid) return;
|
|
923
|
+
const entry = {
|
|
924
|
+
pid: child.pid,
|
|
925
|
+
role,
|
|
926
|
+
child,
|
|
927
|
+
spawnedAt: Date.now(),
|
|
928
|
+
lastOutputAt: Date.now(),
|
|
929
|
+
warned: false
|
|
930
|
+
};
|
|
931
|
+
this.tracked.set(child.pid, entry);
|
|
932
|
+
child.stdout?.on("data", () => {
|
|
933
|
+
entry.lastOutputAt = Date.now();
|
|
934
|
+
});
|
|
935
|
+
child.stderr?.on("data", () => {
|
|
936
|
+
entry.lastOutputAt = Date.now();
|
|
937
|
+
});
|
|
938
|
+
child.on("close", () => {
|
|
939
|
+
this.tracked.delete(entry.pid);
|
|
940
|
+
});
|
|
941
|
+
child.on("error", () => {
|
|
942
|
+
this.tracked.delete(entry.pid);
|
|
943
|
+
});
|
|
944
|
+
if (!this.pollTimer) {
|
|
945
|
+
this.startPolling();
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Manually refresh output timestamp for a role (e.g. from engine events).
|
|
950
|
+
*/
|
|
951
|
+
heartbeat(role) {
|
|
952
|
+
for (const entry of this.tracked.values()) {
|
|
953
|
+
if (entry.role === role) {
|
|
954
|
+
entry.lastOutputAt = Date.now();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Get current stats for all tracked processes.
|
|
960
|
+
*/
|
|
961
|
+
getStats() {
|
|
962
|
+
const pids = Array.from(this.tracked.keys());
|
|
963
|
+
if (pids.length === 0) return [];
|
|
964
|
+
const resourceMap = this.pollResources(pids);
|
|
965
|
+
const now = Date.now();
|
|
966
|
+
const stats = [];
|
|
967
|
+
for (const entry of this.tracked.values()) {
|
|
968
|
+
const res = resourceMap.get(entry.pid);
|
|
969
|
+
const uptime = Math.round((now - entry.spawnedAt) / 1e3);
|
|
970
|
+
const lastOutputAge = Math.round((now - entry.lastOutputAt) / 1e3);
|
|
971
|
+
const memoryMB = res?.memoryMB ?? 0;
|
|
972
|
+
const cpuPercent = res?.cpuPercent ?? 0;
|
|
973
|
+
let status = "healthy";
|
|
974
|
+
if (memoryMB > this.limits.maxMemoryMB * 0.8 || uptime > this.limits.maxTimeSeconds * 0.8 || lastOutputAge > this.limits.hungWarningSeconds) {
|
|
975
|
+
status = "warning";
|
|
976
|
+
}
|
|
977
|
+
if (memoryMB > this.limits.maxMemoryMB || uptime > this.limits.maxTimeSeconds || lastOutputAge > this.limits.hungKillSeconds) {
|
|
978
|
+
status = "critical";
|
|
979
|
+
}
|
|
980
|
+
stats.push({
|
|
981
|
+
pid: entry.pid,
|
|
982
|
+
role: entry.role,
|
|
983
|
+
memoryMB,
|
|
984
|
+
cpuPercent,
|
|
985
|
+
uptime,
|
|
986
|
+
lastOutputAge,
|
|
987
|
+
status
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
return stats;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Stop supervising. Clears poll timer but does NOT kill tracked processes.
|
|
994
|
+
*/
|
|
995
|
+
stop() {
|
|
996
|
+
if (this.pollTimer) {
|
|
997
|
+
clearInterval(this.pollTimer);
|
|
998
|
+
this.pollTimer = null;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Stop supervising and kill ALL tracked processes immediately.
|
|
1003
|
+
*/
|
|
1004
|
+
killAll() {
|
|
1005
|
+
this.stop();
|
|
1006
|
+
for (const entry of this.tracked.values()) {
|
|
1007
|
+
this.killProcess(entry, "supervisor shutdown");
|
|
1008
|
+
}
|
|
1009
|
+
this.tracked.clear();
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Get number of active tracked processes.
|
|
1013
|
+
*/
|
|
1014
|
+
get activeCount() {
|
|
1015
|
+
return this.tracked.size;
|
|
1016
|
+
}
|
|
1017
|
+
// ── Private ──────────────────────────────────────────────
|
|
1018
|
+
startPolling() {
|
|
1019
|
+
this.pollTimer = setInterval(() => {
|
|
1020
|
+
this.runChecks();
|
|
1021
|
+
}, this.limits.pollIntervalMs);
|
|
1022
|
+
this.pollTimer.unref();
|
|
1023
|
+
}
|
|
1024
|
+
runChecks() {
|
|
1025
|
+
if (this.tracked.size === 0) {
|
|
1026
|
+
this.stop();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const stats = this.getStats();
|
|
1030
|
+
this.emit("stats", stats);
|
|
1031
|
+
for (const stat of stats) {
|
|
1032
|
+
const entry = this.tracked.get(stat.pid);
|
|
1033
|
+
if (!entry) continue;
|
|
1034
|
+
if (stat.memoryMB > this.limits.maxMemoryMB) {
|
|
1035
|
+
this.killProcess(entry, `memory limit exceeded (${Math.round(stat.memoryMB)}MB > ${this.limits.maxMemoryMB}MB)`);
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (stat.uptime > this.limits.maxTimeSeconds) {
|
|
1039
|
+
this.killProcess(entry, `timeout exceeded (${stat.uptime}s > ${this.limits.maxTimeSeconds}s)`);
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
if (stat.lastOutputAge > this.limits.hungKillSeconds) {
|
|
1043
|
+
this.killProcess(entry, `no output for ${stat.lastOutputAge}s (limit: ${this.limits.hungKillSeconds}s)`);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (stat.lastOutputAge > this.limits.hungWarningSeconds && !entry.warned) {
|
|
1047
|
+
entry.warned = true;
|
|
1048
|
+
this.emit("warning", {
|
|
1049
|
+
role: entry.role,
|
|
1050
|
+
pid: entry.pid,
|
|
1051
|
+
reason: `no output for ${stat.lastOutputAge}s`,
|
|
1052
|
+
stats: stat
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
if (stat.memoryMB > this.limits.maxMemoryMB * 0.8 && !entry.warned) {
|
|
1056
|
+
entry.warned = true;
|
|
1057
|
+
this.emit("warning", {
|
|
1058
|
+
role: entry.role,
|
|
1059
|
+
pid: entry.pid,
|
|
1060
|
+
reason: `high memory usage (${Math.round(stat.memoryMB)}MB)`,
|
|
1061
|
+
stats: stat
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
if (stat.uptime > this.limits.maxTimeSeconds * 0.8 && !entry.warned) {
|
|
1065
|
+
entry.warned = true;
|
|
1066
|
+
this.emit("warning", {
|
|
1067
|
+
role: entry.role,
|
|
1068
|
+
pid: entry.pid,
|
|
1069
|
+
reason: `approaching timeout (${stat.uptime}s / ${this.limits.maxTimeSeconds}s)`,
|
|
1070
|
+
stats: stat
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Use `ps` to get memory (RSS) and CPU% for a list of PIDs.
|
|
1077
|
+
* Works on macOS and Linux.
|
|
1078
|
+
*/
|
|
1079
|
+
pollResources(pids) {
|
|
1080
|
+
const result = /* @__PURE__ */ new Map();
|
|
1081
|
+
try {
|
|
1082
|
+
const pidList = pids.join(",");
|
|
1083
|
+
const output = execSync(`ps -o pid=,rss=,pcpu= -p ${pidList} 2>/dev/null`, {
|
|
1084
|
+
encoding: "utf-8",
|
|
1085
|
+
timeout: 3e3
|
|
1086
|
+
});
|
|
1087
|
+
for (const line of output.trim().split("\n")) {
|
|
1088
|
+
const parts = line.trim().split(/\s+/);
|
|
1089
|
+
if (parts.length >= 3) {
|
|
1090
|
+
const pid = parseInt(parts[0], 10);
|
|
1091
|
+
const rssKB = parseInt(parts[1], 10);
|
|
1092
|
+
const cpu = parseFloat(parts[2]);
|
|
1093
|
+
if (!isNaN(pid) && !isNaN(rssKB)) {
|
|
1094
|
+
result.set(pid, {
|
|
1095
|
+
memoryMB: Math.round(rssKB / 1024 * 10) / 10,
|
|
1096
|
+
cpuPercent: isNaN(cpu) ? 0 : cpu
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} catch {
|
|
1102
|
+
}
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
1105
|
+
killProcess(entry, reason) {
|
|
1106
|
+
logger.info(`Supervisor killing ${entry.role} (PID ${entry.pid}): ${reason}`);
|
|
1107
|
+
this.emit("killed", {
|
|
1108
|
+
role: entry.role,
|
|
1109
|
+
pid: entry.pid,
|
|
1110
|
+
reason
|
|
1111
|
+
});
|
|
1112
|
+
try {
|
|
1113
|
+
process.kill(-entry.pid, "SIGTERM");
|
|
1114
|
+
} catch {
|
|
1115
|
+
try {
|
|
1116
|
+
entry.child.kill("SIGTERM");
|
|
1117
|
+
} catch {
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
setTimeout(() => {
|
|
1121
|
+
try {
|
|
1122
|
+
process.kill(-entry.pid, "SIGKILL");
|
|
1123
|
+
} catch {
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
entry.child.kill("SIGKILL");
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
}, 3e3).unref();
|
|
1130
|
+
this.tracked.delete(entry.pid);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
// src/orchestrator/plan-parser.ts
|
|
1135
|
+
var ROLE_KEY_MAP = {
|
|
1136
|
+
ANALYST: "analyst",
|
|
1137
|
+
STRATEGIST: "strategist",
|
|
1138
|
+
DESIGNER: "designer",
|
|
1139
|
+
PIXEL: "designer",
|
|
1140
|
+
ARCHITECT: "architect",
|
|
1141
|
+
BLUEPRINT: "architect",
|
|
1142
|
+
BUILDER: "builder",
|
|
1143
|
+
QA: "qa",
|
|
1144
|
+
"QA LEAD": "qa",
|
|
1145
|
+
GUARDIAN: "guardian",
|
|
1146
|
+
TESTER: "guardian",
|
|
1147
|
+
SENTINEL: "sentinel",
|
|
1148
|
+
REVIEWER: "sentinel",
|
|
1149
|
+
SECURITY: "security",
|
|
1150
|
+
SHIELD: "security",
|
|
1151
|
+
SCRIBE: "scribe",
|
|
1152
|
+
DOCS: "scribe",
|
|
1153
|
+
DOCUMENTATION: "scribe",
|
|
1154
|
+
DEVOPS: "devops",
|
|
1155
|
+
DEPLOY: "devops",
|
|
1156
|
+
DEPLOYMENT: "devops"
|
|
1157
|
+
};
|
|
1158
|
+
function parsePlan(planText) {
|
|
1159
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
1160
|
+
const assignRegex = /###\s*ASSIGN\s*:\s*(\w[\w\s]*?)[\n\r]([\s\S]*?)(?=###\s*ASSIGN\s*:|$)/gi;
|
|
1161
|
+
let match;
|
|
1162
|
+
while ((match = assignRegex.exec(planText)) !== null) {
|
|
1163
|
+
const rawRole = match[1].trim().toUpperCase();
|
|
1164
|
+
const taskBody = match[2].trim();
|
|
1165
|
+
const roleKey = ROLE_KEY_MAP[rawRole];
|
|
1166
|
+
if (roleKey && taskBody) {
|
|
1167
|
+
assignments.set(roleKey, taskBody);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const modules = extractModules(planText);
|
|
1171
|
+
const overview = extractSection(planText, "OVERVIEW") || "";
|
|
1172
|
+
return {
|
|
1173
|
+
raw: planText,
|
|
1174
|
+
assignments,
|
|
1175
|
+
modules,
|
|
1176
|
+
overview
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
function getAgentTask(plan, role, fallbackPrompt) {
|
|
1180
|
+
const assignment = plan.assignments.get(role);
|
|
1181
|
+
if (assignment) {
|
|
1182
|
+
return `You have been assigned the following tasks by the team Strategist:
|
|
1183
|
+
|
|
1184
|
+
${assignment}
|
|
1185
|
+
|
|
1186
|
+
Here is the full plan for context:
|
|
1187
|
+
|
|
1188
|
+
${plan.raw}`;
|
|
1189
|
+
}
|
|
1190
|
+
return `${fallbackPrompt}
|
|
1191
|
+
|
|
1192
|
+
Here is the master plan from the Strategist:
|
|
1193
|
+
|
|
1194
|
+
${plan.raw}`;
|
|
1195
|
+
}
|
|
1196
|
+
function extractModules(text) {
|
|
1197
|
+
const moduleRegex = /### MODULE:\s*(.+?)(?:\n|$)([\s\S]*?)(?=### MODULE:|$)/g;
|
|
1198
|
+
const modules = [];
|
|
1199
|
+
let match;
|
|
1200
|
+
while ((match = moduleRegex.exec(text)) !== null) {
|
|
1201
|
+
const moduleName = match[1].trim();
|
|
1202
|
+
const moduleBody = match[2].trim();
|
|
1203
|
+
modules.push(`${moduleName}
|
|
1204
|
+
${moduleBody}`);
|
|
1205
|
+
}
|
|
1206
|
+
return modules;
|
|
1207
|
+
}
|
|
1208
|
+
function extractSection(text, sectionName) {
|
|
1209
|
+
const regex = new RegExp(
|
|
1210
|
+
`(?:#{1,3}\\s*(?:\\d+\\.?\\s*)?${sectionName}[:\\s]*\\n)([\\s\\S]*?)(?=\\n#{1,3}\\s|$)`,
|
|
1211
|
+
"i"
|
|
1212
|
+
);
|
|
1213
|
+
const match = regex.exec(text);
|
|
1214
|
+
return match ? match[1].trim() : null;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/orchestrator/brain.ts
|
|
1218
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
1219
|
+
|
|
1220
|
+
// src/orchestrator/memory.ts
|
|
1221
|
+
import * as fs2 from "fs";
|
|
1222
|
+
import * as path2 from "path";
|
|
1223
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
1224
|
+
var SharedMemory = class extends EventEmitter3 {
|
|
1225
|
+
store = /* @__PURE__ */ new Map();
|
|
1226
|
+
sharedDir;
|
|
1227
|
+
krenkDir;
|
|
1228
|
+
constructor(projectDir) {
|
|
1229
|
+
super();
|
|
1230
|
+
this.krenkDir = path2.join(projectDir, ".krenk");
|
|
1231
|
+
this.sharedDir = path2.join(this.krenkDir, "shared");
|
|
1232
|
+
fs2.mkdirSync(this.sharedDir, { recursive: true });
|
|
1233
|
+
this.writeSection("brain", "# Master Brain Directives\n\n_No directives yet._\n");
|
|
1234
|
+
this.writeSection("status", "# Agent Status\n\n_Starting..._\n");
|
|
1235
|
+
this.writeSection("learnings", "# Team Learnings\n\n");
|
|
1236
|
+
this.writeSection("blockers", "# Known Blockers\n\n_None yet._\n");
|
|
1237
|
+
this.writeSection("decisions", "# Decisions Log\n\n");
|
|
1238
|
+
}
|
|
1239
|
+
// ── Read ────────────────────────────────────────────────
|
|
1240
|
+
/**
|
|
1241
|
+
* Get a section from memory.
|
|
1242
|
+
*/
|
|
1243
|
+
get(section) {
|
|
1244
|
+
return this.store.get(section) || "";
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Get the full shared memory as a single context string.
|
|
1248
|
+
* This is injected into agent prompts so they see the team state.
|
|
1249
|
+
*/
|
|
1250
|
+
getFullContext() {
|
|
1251
|
+
const parts = ["# Shared Team Memory (.krenk/shared/)\n"];
|
|
1252
|
+
parts.push("You can read these files with the Read tool for more detail.\n");
|
|
1253
|
+
const brain = this.store.get("brain");
|
|
1254
|
+
if (brain) {
|
|
1255
|
+
parts.push(brain);
|
|
1256
|
+
parts.push("");
|
|
1257
|
+
}
|
|
1258
|
+
const status = this.store.get("status");
|
|
1259
|
+
if (status) {
|
|
1260
|
+
parts.push(status);
|
|
1261
|
+
parts.push("");
|
|
1262
|
+
}
|
|
1263
|
+
const learnings = this.store.get("learnings");
|
|
1264
|
+
if (learnings && learnings.split("\n").length > 3) {
|
|
1265
|
+
parts.push(learnings);
|
|
1266
|
+
parts.push("");
|
|
1267
|
+
}
|
|
1268
|
+
const blockers = this.store.get("blockers");
|
|
1269
|
+
if (blockers && !blockers.includes("None yet")) {
|
|
1270
|
+
parts.push(blockers);
|
|
1271
|
+
parts.push("");
|
|
1272
|
+
}
|
|
1273
|
+
const decisions = this.store.get("decisions");
|
|
1274
|
+
if (decisions && decisions.split("\n").length > 3) {
|
|
1275
|
+
parts.push(decisions);
|
|
1276
|
+
parts.push("");
|
|
1277
|
+
}
|
|
1278
|
+
return parts.join("\n");
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Get the path to the shared directory (so agents know where to look).
|
|
1282
|
+
*/
|
|
1283
|
+
getSharedDir() {
|
|
1284
|
+
return this.sharedDir;
|
|
1285
|
+
}
|
|
1286
|
+
// ── Write ───────────────────────────────────────────────
|
|
1287
|
+
/**
|
|
1288
|
+
* Write/overwrite a section.
|
|
1289
|
+
*/
|
|
1290
|
+
writeSection(section, content) {
|
|
1291
|
+
this.store.set(section, content);
|
|
1292
|
+
this.writeToDisk(section, content);
|
|
1293
|
+
this.emit("update", { section, content });
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Append to a section.
|
|
1297
|
+
*/
|
|
1298
|
+
appendSection(section, line) {
|
|
1299
|
+
const existing = this.store.get(section) || "";
|
|
1300
|
+
const updated = existing + line + "\n";
|
|
1301
|
+
this.store.set(section, updated);
|
|
1302
|
+
this.writeToDisk(section, updated);
|
|
1303
|
+
this.emit("update", { section, content: updated });
|
|
1304
|
+
}
|
|
1305
|
+
// ── Agent Status ────────────────────────────────────────
|
|
1306
|
+
/**
|
|
1307
|
+
* Update the live status board.
|
|
1308
|
+
*/
|
|
1309
|
+
updateAgentStatus(agents) {
|
|
1310
|
+
const lines = ["# Agent Status\n"];
|
|
1311
|
+
const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
1312
|
+
lines.push(`_Updated: ${now}_
|
|
1313
|
+
`);
|
|
1314
|
+
for (const a of agents) {
|
|
1315
|
+
const statusIcon = a.status === "active" ? ">" : a.status === "done" ? "+" : a.status === "failed" ? "x" : a.status === "idle" ? "-" : "?";
|
|
1316
|
+
const info = a.info ? ` \u2014 ${a.info}` : "";
|
|
1317
|
+
lines.push(`${statusIcon} **${a.name}**: ${a.status}${info}`);
|
|
1318
|
+
}
|
|
1319
|
+
this.writeSection("status", lines.join("\n") + "\n");
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Write an individual agent's progress file.
|
|
1323
|
+
*/
|
|
1324
|
+
writeAgentProgress(role, content) {
|
|
1325
|
+
this.store.set(`agent-${role}`, content);
|
|
1326
|
+
this.writeToDisk(role, content);
|
|
1327
|
+
}
|
|
1328
|
+
// ── Brain Directives ────────────────────────────────────
|
|
1329
|
+
/**
|
|
1330
|
+
* Write a directive from the Master Brain.
|
|
1331
|
+
*/
|
|
1332
|
+
writeDirective(directive) {
|
|
1333
|
+
this.appendSection("brain", `
|
|
1334
|
+
---
|
|
1335
|
+
${directive}
|
|
1336
|
+
`);
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Log a decision.
|
|
1340
|
+
*/
|
|
1341
|
+
logDecision(decision) {
|
|
1342
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
1343
|
+
this.appendSection("decisions", `- [${timestamp}] ${decision}
|
|
1344
|
+
`);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Log a learning.
|
|
1348
|
+
*/
|
|
1349
|
+
logLearning(learning) {
|
|
1350
|
+
this.appendSection("learnings", `- ${learning}
|
|
1351
|
+
`);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Log a blocker.
|
|
1355
|
+
*/
|
|
1356
|
+
logBlocker(role, blocker) {
|
|
1357
|
+
const content = this.store.get("blockers") || "";
|
|
1358
|
+
const updated = content.includes("None yet") ? `# Known Blockers
|
|
1359
|
+
|
|
1360
|
+
- [${role}] ${blocker}
|
|
1361
|
+
` : content + `- [${role}] ${blocker}
|
|
1362
|
+
`;
|
|
1363
|
+
this.writeSection("blockers", updated);
|
|
1364
|
+
}
|
|
1365
|
+
// ── Private ─────────────────────────────────────────────
|
|
1366
|
+
writeToDisk(name, content) {
|
|
1367
|
+
try {
|
|
1368
|
+
const filePath = path2.join(this.sharedDir, `${name}.md`);
|
|
1369
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
logger.error(`SharedMemory: failed to write ${name}.md`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
// src/orchestrator/brain.ts
|
|
1377
|
+
var MasterBrain = class extends EventEmitter4 {
|
|
1378
|
+
memory;
|
|
1379
|
+
plan = null;
|
|
1380
|
+
agentOutputs = /* @__PURE__ */ new Map();
|
|
1381
|
+
agentLiveBuffer = /* @__PURE__ */ new Map();
|
|
1382
|
+
redoCounts = /* @__PURE__ */ new Map();
|
|
1383
|
+
MAX_REDOS = 2;
|
|
1384
|
+
completedRoles = /* @__PURE__ */ new Set();
|
|
1385
|
+
activeRoles = /* @__PURE__ */ new Set();
|
|
1386
|
+
// Error detection patterns
|
|
1387
|
+
issuePatterns = [
|
|
1388
|
+
/error:\s+(?!.*test|.*expect|.*assert)/i,
|
|
1389
|
+
/FATAL/,
|
|
1390
|
+
/panic:/i,
|
|
1391
|
+
/cannot find module/i,
|
|
1392
|
+
/unhandled.*rejection/i,
|
|
1393
|
+
/out of memory/i,
|
|
1394
|
+
/maximum call stack/i,
|
|
1395
|
+
/ENOSPC/i,
|
|
1396
|
+
/permission denied/i
|
|
1397
|
+
];
|
|
1398
|
+
// Off-track detection per role
|
|
1399
|
+
offTrackPatterns = /* @__PURE__ */ new Map([
|
|
1400
|
+
["builder", [/writing tests/i, /writing documentation/i, /reviewing code/i]],
|
|
1401
|
+
["guardian", [/implementing feature/i, /refactoring/i]],
|
|
1402
|
+
["sentinel", [/fixing.*bug/i, /implementing/i]],
|
|
1403
|
+
["scribe", [/implementing/i, /writing tests/i]]
|
|
1404
|
+
]);
|
|
1405
|
+
constructor(projectDir) {
|
|
1406
|
+
super();
|
|
1407
|
+
this.memory = new SharedMemory(projectDir);
|
|
1408
|
+
}
|
|
1409
|
+
// ── Plan ────────────────────────────────────────────────
|
|
1410
|
+
setPlan(plan) {
|
|
1411
|
+
this.plan = plan;
|
|
1412
|
+
this.memory.logDecision("Master plan received from Strategist");
|
|
1413
|
+
const assignedRoles = Array.from(plan.assignments.keys());
|
|
1414
|
+
this.memory.writeDirective(
|
|
1415
|
+
`## Master Plan Active
|
|
1416
|
+
|
|
1417
|
+
Assigned agents: ${assignedRoles.map((r) => ROLES[r]?.name || r).join(", ")}
|
|
1418
|
+
Modules: ${plan.modules.length}
|
|
1419
|
+
|
|
1420
|
+
All agents: follow your ASSIGN section from the plan.`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
// ── Agent Lifecycle ─────────────────────────────────────
|
|
1424
|
+
/**
|
|
1425
|
+
* Called when an agent is about to run.
|
|
1426
|
+
* Prepares a complete brief with:
|
|
1427
|
+
* - Shared memory context (what the team knows)
|
|
1428
|
+
* - Brain's directives and decisions
|
|
1429
|
+
* - Cross-agent learnings
|
|
1430
|
+
* - Role-specific guardrails
|
|
1431
|
+
* - Path to shared dir so agent can read more
|
|
1432
|
+
*/
|
|
1433
|
+
prepareBrief(role, basePrompt) {
|
|
1434
|
+
this.activeRoles.add(role);
|
|
1435
|
+
this.updateStatusBoard();
|
|
1436
|
+
const parts = [];
|
|
1437
|
+
const memoryContext = this.memory.getFullContext();
|
|
1438
|
+
if (memoryContext) {
|
|
1439
|
+
parts.push(memoryContext);
|
|
1440
|
+
}
|
|
1441
|
+
parts.push(`## Shared Memory Location`);
|
|
1442
|
+
parts.push(`The team's shared memory is at: ${this.memory.getSharedDir()}/`);
|
|
1443
|
+
parts.push(`You can read any .md file there with the Read tool for full context.`);
|
|
1444
|
+
parts.push(`Write your progress updates to: ${this.memory.getSharedDir()}/${role}.md`);
|
|
1445
|
+
parts.push("");
|
|
1446
|
+
const guardrails = this.getGuardrails(role);
|
|
1447
|
+
if (guardrails) {
|
|
1448
|
+
parts.push("## Constraints from Master Brain:");
|
|
1449
|
+
parts.push(guardrails);
|
|
1450
|
+
parts.push("");
|
|
1451
|
+
}
|
|
1452
|
+
const brief = parts.length > 0 ? `${parts.join("\n")}
|
|
1453
|
+
---
|
|
1454
|
+
|
|
1455
|
+
${basePrompt}` : basePrompt;
|
|
1456
|
+
this.emit("brief", { role, brief: parts.join("\n") });
|
|
1457
|
+
this.memory.logDecision(`Briefed ${ROLES[role]?.name || role} and sent to work`);
|
|
1458
|
+
return brief;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Called with every chunk of output from a running agent.
|
|
1462
|
+
* Monitors in real-time, detects problems, writes updates to shared memory.
|
|
1463
|
+
*/
|
|
1464
|
+
monitorOutput(role, chunk) {
|
|
1465
|
+
const existing = this.agentLiveBuffer.get(role) || "";
|
|
1466
|
+
this.agentLiveBuffer.set(role, existing + chunk);
|
|
1467
|
+
const buffer = this.agentLiveBuffer.get(role) || "";
|
|
1468
|
+
if (buffer.length > 0 && buffer.length % 2e3 < chunk.length) {
|
|
1469
|
+
const lastLines = buffer.split("\n").slice(-5).join("\n");
|
|
1470
|
+
this.memory.writeAgentProgress(role, `# ${ROLES[role]?.name || role} \u2014 Progress
|
|
1471
|
+
|
|
1472
|
+
_Live output (last few lines):_
|
|
1473
|
+
\`\`\`
|
|
1474
|
+
${lastLines}
|
|
1475
|
+
\`\`\`
|
|
1476
|
+
`);
|
|
1477
|
+
}
|
|
1478
|
+
for (const pattern of this.issuePatterns) {
|
|
1479
|
+
if (pattern.test(chunk)) {
|
|
1480
|
+
const msg = `Error detected in ${role}: ${chunk.trim().slice(0, 100)}`;
|
|
1481
|
+
this.memory.logBlocker(role, msg);
|
|
1482
|
+
this.emit("intervention", { role, type: "error_detected", message: msg });
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (buffer.length > 2e3) {
|
|
1487
|
+
const patterns = this.offTrackPatterns.get(role);
|
|
1488
|
+
if (patterns) {
|
|
1489
|
+
for (const pattern of patterns) {
|
|
1490
|
+
if (pattern.test(chunk)) {
|
|
1491
|
+
const msg = `${role} appears to be doing work outside its scope`;
|
|
1492
|
+
logger.info(`Brain: ${msg}`);
|
|
1493
|
+
this.memory.writeDirective(
|
|
1494
|
+
`## Attention ${ROLES[role]?.name || role}
|
|
1495
|
+
|
|
1496
|
+
You appear to be going off-track. Please stay focused on your assigned task. Do NOT do: ${pattern.source}`
|
|
1497
|
+
);
|
|
1498
|
+
this.emit("intervention", { role, type: "off_track", message: msg });
|
|
1499
|
+
return { type: "warn", role, reason: `Off-track: ${pattern.source}` };
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Called when an agent finishes.
|
|
1508
|
+
* Reviews quality, extracts learnings, updates shared memory, decides next action.
|
|
1509
|
+
*/
|
|
1510
|
+
reviewOutput(role, result) {
|
|
1511
|
+
this.agentOutputs.set(role, result.output);
|
|
1512
|
+
this.agentLiveBuffer.delete(role);
|
|
1513
|
+
this.activeRoles.delete(role);
|
|
1514
|
+
const summary = this.summarizeOutput(role, result.output);
|
|
1515
|
+
this.memory.writeAgentProgress(
|
|
1516
|
+
role,
|
|
1517
|
+
`# ${ROLES[role]?.name || role} \u2014 Complete
|
|
1518
|
+
|
|
1519
|
+
Status: ${result.success ? "SUCCESS" : "FAILED"}
|
|
1520
|
+
Duration: ${result.duration}s
|
|
1521
|
+
|
|
1522
|
+
## Summary:
|
|
1523
|
+
${summary}
|
|
1524
|
+
`
|
|
1525
|
+
);
|
|
1526
|
+
if (!result.output || result.output.trim().length < 50) {
|
|
1527
|
+
if (result.success) {
|
|
1528
|
+
this.completedRoles.add(role);
|
|
1529
|
+
this.memory.logDecision(`${role}: accepted (minimal output but success)`);
|
|
1530
|
+
this.updateStatusBoard();
|
|
1531
|
+
this.emit("review", { role, verdict: "accept", notes: "Minimal output" });
|
|
1532
|
+
return { verdict: "accept", notes: "Minimal output" };
|
|
1533
|
+
}
|
|
1534
|
+
const redos = this.redoCounts.get(role) || 0;
|
|
1535
|
+
if (redos < this.MAX_REDOS) {
|
|
1536
|
+
this.redoCounts.set(role, redos + 1);
|
|
1537
|
+
this.memory.logDecision(`${role}: ordering REDO (failed with no output)`);
|
|
1538
|
+
this.emit("review", { role, verdict: "redo", notes: `Redo ${redos + 1}/${this.MAX_REDOS}` });
|
|
1539
|
+
return {
|
|
1540
|
+
verdict: "redo",
|
|
1541
|
+
notes: "Failed with no output",
|
|
1542
|
+
correction: "The previous attempt failed. Please try again carefully."
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
this.completedRoles.add(role);
|
|
1546
|
+
this.memory.logDecision(`${role}: accepting failure (max redos reached)`);
|
|
1547
|
+
this.updateStatusBoard();
|
|
1548
|
+
this.emit("review", { role, verdict: "accept", notes: "Max redos reached" });
|
|
1549
|
+
return { verdict: "accept", notes: "Max redos reached" };
|
|
1550
|
+
}
|
|
1551
|
+
if (!result.success) {
|
|
1552
|
+
const redos = this.redoCounts.get(role) || 0;
|
|
1553
|
+
if (redos < this.MAX_REDOS) {
|
|
1554
|
+
this.redoCounts.set(role, redos + 1);
|
|
1555
|
+
const errors = this.extractErrors(result.output);
|
|
1556
|
+
this.memory.logBlocker(role, `Failed (exit ${result.exitCode})`);
|
|
1557
|
+
this.memory.logDecision(`${role}: ordering REDO (exit code ${result.exitCode})`);
|
|
1558
|
+
this.emit("review", { role, verdict: "redo", notes: `Failed, redo ${redos + 1}` });
|
|
1559
|
+
return {
|
|
1560
|
+
verdict: "redo",
|
|
1561
|
+
notes: `Failed (exit ${result.exitCode})`,
|
|
1562
|
+
correction: `Previous attempt failed:
|
|
1563
|
+
|
|
1564
|
+
${errors}
|
|
1565
|
+
|
|
1566
|
+
Please fix and try again.`
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
this.extractLearnings(role, result.output);
|
|
1571
|
+
const qualityIssue = this.checkQuality(role, result.output);
|
|
1572
|
+
if (qualityIssue) {
|
|
1573
|
+
const redos = this.redoCounts.get(role) || 0;
|
|
1574
|
+
if (redos < this.MAX_REDOS) {
|
|
1575
|
+
this.redoCounts.set(role, redos + 1);
|
|
1576
|
+
this.memory.logDecision(`${role}: ordering REDO (quality issue: ${qualityIssue})`);
|
|
1577
|
+
this.emit("review", { role, verdict: "redo", notes: qualityIssue });
|
|
1578
|
+
return {
|
|
1579
|
+
verdict: "redo",
|
|
1580
|
+
notes: qualityIssue,
|
|
1581
|
+
correction: `Quality review found issues:
|
|
1582
|
+
|
|
1583
|
+
${qualityIssue}
|
|
1584
|
+
|
|
1585
|
+
Please address these.`
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
this.detectConflicts(role, result.output);
|
|
1590
|
+
this.completedRoles.add(role);
|
|
1591
|
+
this.memory.logDecision(`${role}: ACCEPTED`);
|
|
1592
|
+
this.updateStatusBoard();
|
|
1593
|
+
this.emit("review", { role, verdict: "accept", notes: "OK" });
|
|
1594
|
+
return { verdict: "accept", notes: "OK" };
|
|
1595
|
+
}
|
|
1596
|
+
// ── Phased Execution ────────────────────────────────────
|
|
1597
|
+
/**
|
|
1598
|
+
* Get execution phases for a role.
|
|
1599
|
+
* Builder gets 3 phases with review between each.
|
|
1600
|
+
*/
|
|
1601
|
+
getPhases(role, fullPrompt) {
|
|
1602
|
+
if (role !== "builder" || fullPrompt.length < 500) {
|
|
1603
|
+
return [fullPrompt];
|
|
1604
|
+
}
|
|
1605
|
+
this.memory.logDecision("Builder task is large \u2014 splitting into 3 phases");
|
|
1606
|
+
return [
|
|
1607
|
+
`PHASE 1 of 3 \u2014 SETUP
|
|
1608
|
+
|
|
1609
|
+
Focus ONLY on: directory structure, installing dependencies, config files, boilerplate. Do NOT implement features.
|
|
1610
|
+
|
|
1611
|
+
Write your progress to the shared memory: ${this.memory.getSharedDir()}/builder.md
|
|
1612
|
+
|
|
1613
|
+
${fullPrompt}`,
|
|
1614
|
+
`PHASE 2 of 3 \u2014 CORE
|
|
1615
|
+
|
|
1616
|
+
Setup is done. Now implement the core features and main logic. Check ${this.memory.getSharedDir()}/brain.md for any directives from the Master Brain.
|
|
1617
|
+
|
|
1618
|
+
Continue from where you left off.`,
|
|
1619
|
+
`PHASE 3 of 3 \u2014 POLISH
|
|
1620
|
+
|
|
1621
|
+
Core is done. Handle edge cases, error handling, cleanup. Check ${this.memory.getSharedDir()}/brain.md for any final directives.
|
|
1622
|
+
|
|
1623
|
+
Continue from where you left off.`
|
|
1624
|
+
];
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Prepare a corrected prompt for redo.
|
|
1628
|
+
*/
|
|
1629
|
+
prepareRedoPrompt(role, originalPrompt, correction) {
|
|
1630
|
+
this.memory.writeDirective(`## Redo: ${ROLES[role]?.name || role}
|
|
1631
|
+
|
|
1632
|
+
${correction}`);
|
|
1633
|
+
return `${correction}
|
|
1634
|
+
|
|
1635
|
+
---
|
|
1636
|
+
|
|
1637
|
+
Original task:
|
|
1638
|
+
${originalPrompt}`;
|
|
1639
|
+
}
|
|
1640
|
+
getRedoCount(role) {
|
|
1641
|
+
return this.redoCounts.get(role) || 0;
|
|
1642
|
+
}
|
|
1643
|
+
// ── Shared Memory Helpers ───────────────────────────────
|
|
1644
|
+
updateStatusBoard() {
|
|
1645
|
+
const allRoles = Object.keys(ROLES);
|
|
1646
|
+
const agents = allRoles.map((role) => {
|
|
1647
|
+
let status;
|
|
1648
|
+
let info;
|
|
1649
|
+
if (this.completedRoles.has(role)) {
|
|
1650
|
+
const output = this.agentOutputs.get(role);
|
|
1651
|
+
status = "done";
|
|
1652
|
+
info = output ? `${output.length} chars output` : void 0;
|
|
1653
|
+
} else if (this.activeRoles.has(role)) {
|
|
1654
|
+
status = "active";
|
|
1655
|
+
const buf = this.agentLiveBuffer.get(role);
|
|
1656
|
+
info = buf ? `${buf.length} chars so far` : "starting...";
|
|
1657
|
+
} else {
|
|
1658
|
+
status = "idle";
|
|
1659
|
+
}
|
|
1660
|
+
return { role, name: ROLES[role]?.name || role, status, info };
|
|
1661
|
+
});
|
|
1662
|
+
this.memory.updateAgentStatus(agents);
|
|
1663
|
+
}
|
|
1664
|
+
detectConflicts(role, output) {
|
|
1665
|
+
const fileRefs = output.match(/(?:wrote|created|modified|updated|edited)\s+[`"']?([^\s`"']+\.\w+)/gi) || [];
|
|
1666
|
+
for (const [otherRole, otherOutput] of this.agentOutputs) {
|
|
1667
|
+
if (otherRole === role) continue;
|
|
1668
|
+
const otherFiles = (otherOutput.match(/(?:wrote|created|modified)\s+[`"']?([^\s`"']+\.\w+)/gi) || []).map((m) => m.replace(/^(wrote|created|modified|updated|edited)\s+[`"']?/i, ""));
|
|
1669
|
+
for (const ref of fileRefs) {
|
|
1670
|
+
const file = ref.replace(/^(wrote|created|modified|updated|edited)\s+[`"']?/i, "");
|
|
1671
|
+
if (otherFiles.some((f) => f === file)) {
|
|
1672
|
+
const msg = `Possible conflict: ${role} and ${otherRole} both touched ${file}`;
|
|
1673
|
+
logger.info(`Brain: ${msg}`);
|
|
1674
|
+
this.memory.logBlocker(role, msg);
|
|
1675
|
+
this.memory.logDecision(`CONFLICT DETECTED: ${msg}`);
|
|
1676
|
+
this.emit("intervention", { role, type: "conflict", message: msg });
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
// ── Analysis Helpers ────────────────────────────────────
|
|
1682
|
+
summarizeOutput(role, output) {
|
|
1683
|
+
const lines = output.split("\n");
|
|
1684
|
+
const keyLines = [];
|
|
1685
|
+
for (const line of lines) {
|
|
1686
|
+
if (line.startsWith("#") || line.includes("CRITICAL") || line.includes("IMPORTANT") || line.includes("NOTE:") || line.includes("WARNING") || line.includes("MODULE:") || line.includes("ASSIGN:") || line.includes("APPROVED") || line.includes("NEEDS_REVISION")) {
|
|
1687
|
+
keyLines.push(line);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return keyLines.length > 0 ? keyLines.slice(0, 20).join("\n") : output.slice(0, 500);
|
|
1691
|
+
}
|
|
1692
|
+
getGuardrails(role) {
|
|
1693
|
+
const base = {
|
|
1694
|
+
builder: [
|
|
1695
|
+
"- Stay in scope: only implement what was assigned",
|
|
1696
|
+
"- Do NOT write tests (Guardian handles that)",
|
|
1697
|
+
"- Do NOT write docs (Scribe handles that)",
|
|
1698
|
+
"- Write progress updates to your shared memory file",
|
|
1699
|
+
"- Check brain.md for any directives before starting"
|
|
1700
|
+
].join("\n"),
|
|
1701
|
+
guardian: [
|
|
1702
|
+
"- Focus on TESTING only \u2014 do not fix bugs, just report them",
|
|
1703
|
+
"- Use the QA test plan from shared memory if available",
|
|
1704
|
+
"- Report results with PASS/FAIL for each test"
|
|
1705
|
+
].join("\n"),
|
|
1706
|
+
sentinel: [
|
|
1707
|
+
"- Code REVIEW only \u2014 do NOT modify any code",
|
|
1708
|
+
"- Be specific: file path, line number, issue, fix",
|
|
1709
|
+
"- Say NEEDS_REVISION if critical issues found"
|
|
1710
|
+
].join("\n"),
|
|
1711
|
+
security: [
|
|
1712
|
+
"- Security AUDIT only \u2014 do NOT modify any code",
|
|
1713
|
+
"- Check OWASP Top 10",
|
|
1714
|
+
"- Run dependency audits",
|
|
1715
|
+
"- Say NEEDS_REVISION if critical issues found"
|
|
1716
|
+
].join("\n"),
|
|
1717
|
+
scribe: [
|
|
1718
|
+
"- Write docs based on ACTUAL code, not the plan",
|
|
1719
|
+
"- Read the codebase first",
|
|
1720
|
+
"- Keep docs concise"
|
|
1721
|
+
].join("\n")
|
|
1722
|
+
};
|
|
1723
|
+
return base[role] || null;
|
|
1724
|
+
}
|
|
1725
|
+
extractErrors(output) {
|
|
1726
|
+
const lines = output.split("\n");
|
|
1727
|
+
const errors = lines.filter(
|
|
1728
|
+
(l) => /error|failed|exception|ENOENT|cannot find/i.test(l)
|
|
1729
|
+
).slice(0, 15);
|
|
1730
|
+
return errors.length > 0 ? errors.join("\n") : output.slice(-500);
|
|
1731
|
+
}
|
|
1732
|
+
extractLearnings(role, output) {
|
|
1733
|
+
if (role === "architect" || role === "strategist") {
|
|
1734
|
+
const techs = [
|
|
1735
|
+
["typescript", "Project uses TypeScript"],
|
|
1736
|
+
["react", "Frontend uses React"],
|
|
1737
|
+
["Next.js", "Using Next.js"],
|
|
1738
|
+
["express", "Backend uses Express"],
|
|
1739
|
+
["postgres", "Database is PostgreSQL"],
|
|
1740
|
+
["prisma", "Using Prisma ORM"],
|
|
1741
|
+
["tailwind", "Using Tailwind CSS"]
|
|
1742
|
+
];
|
|
1743
|
+
for (const [keyword, learning] of techs) {
|
|
1744
|
+
if (output.toLowerCase().includes(keyword.toLowerCase())) {
|
|
1745
|
+
this.memory.logLearning(learning);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
if (role === "builder") {
|
|
1750
|
+
const fileMatches = output.match(/(?:created|wrote|updated)\s+(?:file\s+)?[`"]?([^\s`"]+\.\w+)/gi);
|
|
1751
|
+
if (fileMatches) {
|
|
1752
|
+
this.memory.logLearning(`Builder created/modified ${fileMatches.length} files`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
if (role === "guardian") {
|
|
1756
|
+
const passMatch = output.match(/(\d+)\s+(?:tests?\s+)?pass/i);
|
|
1757
|
+
const failMatch = output.match(/(\d+)\s+(?:tests?\s+)?fail/i);
|
|
1758
|
+
if (passMatch || failMatch) {
|
|
1759
|
+
this.memory.logLearning(`Tests: ${passMatch?.[1] || 0} passed, ${failMatch?.[1] || 0} failed`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (role === "sentinel") {
|
|
1763
|
+
if (output.includes("APPROVED")) this.memory.logLearning("Code review: APPROVED");
|
|
1764
|
+
if (output.includes("NEEDS_REVISION")) this.memory.logLearning("Code review: NEEDS REVISION");
|
|
1765
|
+
}
|
|
1766
|
+
if (role === "security") {
|
|
1767
|
+
if (output.includes("APPROVED")) this.memory.logLearning("Security audit: PASSED");
|
|
1768
|
+
if (output.includes("NEEDS_REVISION")) this.memory.logLearning("Security audit: ISSUES FOUND");
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
checkQuality(role, output) {
|
|
1772
|
+
if (role === "architect" && !output.match(/file|directory|structure/i)) {
|
|
1773
|
+
return "Missing file structure / directory layout";
|
|
1774
|
+
}
|
|
1775
|
+
if (role === "strategist" && !output.match(/task|step|todo|\- \[/i)) {
|
|
1776
|
+
return "Missing concrete tasks";
|
|
1777
|
+
}
|
|
1778
|
+
if (role === "guardian" && !output.match(/pass|fail|test/i)) {
|
|
1779
|
+
return "Missing test results \u2014 did tests actually run?";
|
|
1780
|
+
}
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
// src/orchestrator/engine.ts
|
|
1786
|
+
var OrchestrationEngine = class extends EventEmitter5 {
|
|
1787
|
+
context;
|
|
1788
|
+
scheduler;
|
|
1789
|
+
registry;
|
|
1790
|
+
supervisor;
|
|
1791
|
+
brain;
|
|
1792
|
+
opts;
|
|
1793
|
+
_stage = "analyzing";
|
|
1794
|
+
_currentOutput = [];
|
|
1795
|
+
_activeAgent = "";
|
|
1796
|
+
_artifacts = [];
|
|
1797
|
+
_startTime = 0;
|
|
1798
|
+
_revisionCount = 0;
|
|
1799
|
+
MAX_REVISIONS = 2;
|
|
1800
|
+
constructor(opts) {
|
|
1801
|
+
super();
|
|
1802
|
+
this.opts = opts;
|
|
1803
|
+
this.context = new ContextManager(opts.cwd);
|
|
1804
|
+
this.scheduler = new Scheduler(opts.maxParallel);
|
|
1805
|
+
this.registry = new AgentRegistry();
|
|
1806
|
+
this.supervisor = new ProcessSupervisor(opts.supervisorLimits);
|
|
1807
|
+
this.brain = new MasterBrain(opts.cwd);
|
|
1808
|
+
for (const [key, role] of Object.entries(ROLES)) {
|
|
1809
|
+
this.registry.register(key, role.name, role.emoji, role.color);
|
|
1810
|
+
}
|
|
1811
|
+
this.supervisor.on("stats", (stats) => this.emit("supervisor:stats", stats));
|
|
1812
|
+
this.supervisor.on("warning", (data) => this.emit("supervisor:warning", data));
|
|
1813
|
+
this.supervisor.on("killed", (data) => this.emit("supervisor:killed", data));
|
|
1814
|
+
this.brain.on("brief", (data) => this.emit("director:brief", data));
|
|
1815
|
+
this.brain.on("review", (data) => this.emit("director:review", data));
|
|
1816
|
+
this.brain.on("intervention", (data) => this.emit("director:intervention", data));
|
|
1817
|
+
this.brain.on("directive", (data) => this.emit("director:directive", data));
|
|
1818
|
+
this.brain.on("decision", (data) => this.emit("brain:decision", data));
|
|
1819
|
+
}
|
|
1820
|
+
get state() {
|
|
1821
|
+
return {
|
|
1822
|
+
stage: this._stage,
|
|
1823
|
+
stages: STAGES,
|
|
1824
|
+
agents: this.registry,
|
|
1825
|
+
activeAgent: this._activeAgent,
|
|
1826
|
+
currentOutput: this._currentOutput,
|
|
1827
|
+
artifacts: this._artifacts,
|
|
1828
|
+
startTime: this._startTime
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Run the full orchestration pipeline.
|
|
1833
|
+
*
|
|
1834
|
+
* Flow:
|
|
1835
|
+
* 1. Analyst analyzes the request
|
|
1836
|
+
* 2. Strategist creates a MASTER PLAN with per-agent assignments
|
|
1837
|
+
* 3. Plan is parsed → Director gets it
|
|
1838
|
+
* 4. For each agent:
|
|
1839
|
+
* a. Director prepares a brief (plan assignment + learnings + guardrails)
|
|
1840
|
+
* b. Agent runs (with real-time monitoring by Director)
|
|
1841
|
+
* c. Director reviews output → accept / redo / skip
|
|
1842
|
+
* d. If redo → re-run with Director's corrections
|
|
1843
|
+
* 5. Supervisor watches memory/CPU/timeouts throughout
|
|
1844
|
+
*/
|
|
1845
|
+
async run(userPrompt) {
|
|
1846
|
+
this._startTime = Date.now();
|
|
1847
|
+
let stageCount = 0;
|
|
1848
|
+
let plan = null;
|
|
1849
|
+
try {
|
|
1850
|
+
if (!this.shouldSkip("analyzing")) {
|
|
1851
|
+
this.setStage("analyzing");
|
|
1852
|
+
const result = await this.runDirectedAgent(
|
|
1853
|
+
"analyst",
|
|
1854
|
+
`Analyze the following request and produce detailed user stories, acceptance criteria, and priorities:
|
|
1855
|
+
|
|
1856
|
+
${userPrompt}`
|
|
1857
|
+
);
|
|
1858
|
+
await this.context.save("analyst", result.output);
|
|
1859
|
+
stageCount++;
|
|
1860
|
+
}
|
|
1861
|
+
if (!this.shouldSkip("planning")) {
|
|
1862
|
+
this.setStage("planning");
|
|
1863
|
+
const analysisContext = this.context.get("analyst");
|
|
1864
|
+
const planPrompt = analysisContext ? `${userPrompt}
|
|
1865
|
+
|
|
1866
|
+
Business Analysis:
|
|
1867
|
+
${analysisContext}` : userPrompt;
|
|
1868
|
+
const planResult = await this.runDirectedAgent("strategist", planPrompt);
|
|
1869
|
+
await this.context.save("strategist", planResult.output);
|
|
1870
|
+
stageCount++;
|
|
1871
|
+
plan = parsePlan(planResult.output);
|
|
1872
|
+
this.brain.setPlan(plan);
|
|
1873
|
+
logger.info(
|
|
1874
|
+
`Plan parsed: ${plan.assignments.size} agent assignments, ${plan.modules.length} modules`
|
|
1875
|
+
);
|
|
1876
|
+
this.emit("plan:parsed", {
|
|
1877
|
+
assignments: Array.from(plan.assignments.keys()),
|
|
1878
|
+
modules: plan.modules.length
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
if (!this.shouldSkip("designing") && this.needsDesign()) {
|
|
1882
|
+
this.setStage("designing");
|
|
1883
|
+
const prompt = plan ? getAgentTask(plan, "designer", userPrompt) : userPrompt;
|
|
1884
|
+
const result = await this.runDirectedAgent("designer", prompt);
|
|
1885
|
+
await this.context.save("designer", result.output);
|
|
1886
|
+
stageCount++;
|
|
1887
|
+
}
|
|
1888
|
+
if (!this.shouldSkip("architecting")) {
|
|
1889
|
+
this.setStage("architecting");
|
|
1890
|
+
const prompt = plan ? getAgentTask(plan, "architect", userPrompt) : userPrompt;
|
|
1891
|
+
const result = await this.runDirectedAgent("architect", prompt);
|
|
1892
|
+
await this.context.save("architect", result.output);
|
|
1893
|
+
stageCount++;
|
|
1894
|
+
if (plan) {
|
|
1895
|
+
const archModules = this.extractModules(result.output);
|
|
1896
|
+
if (archModules.length > 0) {
|
|
1897
|
+
plan.modules = archModules;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (!this.shouldSkip("coding")) {
|
|
1902
|
+
this.setStage("coding");
|
|
1903
|
+
const prompt = plan ? getAgentTask(plan, "builder", userPrompt) : userPrompt;
|
|
1904
|
+
await this.runCodingStage(prompt, plan);
|
|
1905
|
+
stageCount++;
|
|
1906
|
+
}
|
|
1907
|
+
if (!this.shouldSkip("qa-planning")) {
|
|
1908
|
+
this.setStage("qa-planning");
|
|
1909
|
+
const prompt = plan ? getAgentTask(plan, "qa", "Create a comprehensive test strategy and detailed test plans.") : "Create a comprehensive test strategy and detailed test plans for the codebase.";
|
|
1910
|
+
const result = await this.runDirectedAgent("qa", prompt);
|
|
1911
|
+
await this.context.save("qa", result.output);
|
|
1912
|
+
stageCount++;
|
|
1913
|
+
}
|
|
1914
|
+
if (!this.shouldSkip("testing")) {
|
|
1915
|
+
this.setStage("testing");
|
|
1916
|
+
const prompt = plan ? getAgentTask(plan, "guardian", "Write and run comprehensive tests.") : "Analyze the codebase and write comprehensive tests. Run them and report results.";
|
|
1917
|
+
const result = await this.runDirectedAgent("guardian", prompt);
|
|
1918
|
+
await this.context.save("guardian", result.output);
|
|
1919
|
+
stageCount++;
|
|
1920
|
+
}
|
|
1921
|
+
if (!this.shouldSkip("reviewing")) {
|
|
1922
|
+
this.setStage("reviewing");
|
|
1923
|
+
const prompt = plan ? getAgentTask(plan, "sentinel", "Review all code for bugs, security issues, and quality.") : "Review all code in this project for bugs, security issues, and quality.";
|
|
1924
|
+
const review = await this.runDirectedAgent("sentinel", prompt);
|
|
1925
|
+
await this.context.save("sentinel", review.output);
|
|
1926
|
+
stageCount++;
|
|
1927
|
+
if (review.output.includes("NEEDS_REVISION") && this._revisionCount < this.MAX_REVISIONS) {
|
|
1928
|
+
this._revisionCount++;
|
|
1929
|
+
logger.info(`Revision ${this._revisionCount}/${this.MAX_REVISIONS} - fixing review issues`);
|
|
1930
|
+
this.setStage("coding");
|
|
1931
|
+
const fix = await this.runDirectedAgent(
|
|
1932
|
+
"builder",
|
|
1933
|
+
`Fix the following issues from code review:
|
|
1934
|
+
${review.output}`
|
|
1935
|
+
);
|
|
1936
|
+
await this.context.save("builder", fix.output);
|
|
1937
|
+
stageCount++;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
if (!this.shouldSkip("securing")) {
|
|
1941
|
+
this.setStage("securing");
|
|
1942
|
+
const prompt = plan ? getAgentTask(plan, "security", "Perform a thorough security audit.") : "Perform a thorough security audit of this project.";
|
|
1943
|
+
const secAudit = await this.runDirectedAgent("security", prompt);
|
|
1944
|
+
await this.context.save("security", secAudit.output);
|
|
1945
|
+
stageCount++;
|
|
1946
|
+
if (secAudit.output.includes("NEEDS_REVISION") && this._revisionCount < this.MAX_REVISIONS) {
|
|
1947
|
+
this._revisionCount++;
|
|
1948
|
+
logger.info(`Security revision ${this._revisionCount}/${this.MAX_REVISIONS}`);
|
|
1949
|
+
this.setStage("coding");
|
|
1950
|
+
const fix = await this.runDirectedAgent(
|
|
1951
|
+
"builder",
|
|
1952
|
+
`Fix the following security issues:
|
|
1953
|
+
${secAudit.output}`
|
|
1954
|
+
);
|
|
1955
|
+
await this.context.save("builder", fix.output);
|
|
1956
|
+
stageCount++;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (!this.shouldSkip("documenting")) {
|
|
1960
|
+
this.setStage("documenting");
|
|
1961
|
+
const prompt = plan ? getAgentTask(plan, "scribe", "Write comprehensive documentation.") : "Write comprehensive documentation for this project.";
|
|
1962
|
+
const result = await this.runDirectedAgent("scribe", prompt);
|
|
1963
|
+
await this.context.save("scribe", result.output);
|
|
1964
|
+
stageCount++;
|
|
1965
|
+
}
|
|
1966
|
+
if (!this.shouldSkip("deploying")) {
|
|
1967
|
+
this.setStage("deploying");
|
|
1968
|
+
const prompt = plan ? getAgentTask(plan, "devops", "Set up CI/CD, Docker, and deployment.") : "Set up CI/CD pipeline, Docker configuration, and deployment setup.";
|
|
1969
|
+
const result = await this.runDirectedAgent("devops", prompt);
|
|
1970
|
+
await this.context.save("devops", result.output);
|
|
1971
|
+
stageCount++;
|
|
1972
|
+
}
|
|
1973
|
+
this.setStage("complete");
|
|
1974
|
+
this.supervisor.stop();
|
|
1975
|
+
const duration = Math.round((Date.now() - this._startTime) / 1e3);
|
|
1976
|
+
await this.context.saveState({
|
|
1977
|
+
stage: "complete",
|
|
1978
|
+
stageCount,
|
|
1979
|
+
duration,
|
|
1980
|
+
runId: this.context.getRunId(),
|
|
1981
|
+
planAssignments: plan ? Array.from(plan.assignments.keys()) : []
|
|
1982
|
+
});
|
|
1983
|
+
return { success: true, stages: stageCount, duration };
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1986
|
+
logger.error(`Engine failed: ${msg}`);
|
|
1987
|
+
this.emit("error", error);
|
|
1988
|
+
return {
|
|
1989
|
+
success: false,
|
|
1990
|
+
stages: stageCount,
|
|
1991
|
+
duration: Math.round((Date.now() - this._startTime) / 1e3)
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Run only the planning stage
|
|
1997
|
+
*/
|
|
1998
|
+
async runPlanOnly(userPrompt) {
|
|
1999
|
+
this._startTime = Date.now();
|
|
2000
|
+
this.setStage("planning");
|
|
2001
|
+
const plan = await this.runDirectedAgent("strategist", userPrompt);
|
|
2002
|
+
await this.context.save("strategist", plan.output);
|
|
2003
|
+
this.setStage("complete");
|
|
2004
|
+
return plan;
|
|
2005
|
+
}
|
|
2006
|
+
// ── Director-driven agent execution ─────────────────────
|
|
2007
|
+
/**
|
|
2008
|
+
* Run an agent through the full Director cycle:
|
|
2009
|
+
* 1. Director prepares brief (learnings + guardrails + cross-agent context)
|
|
2010
|
+
* 2. Agent runs (Director monitors output in real-time)
|
|
2011
|
+
* 3. Director reviews result → accept / redo
|
|
2012
|
+
* 4. If redo → re-run with Director's corrections
|
|
2013
|
+
*/
|
|
2014
|
+
async runDirectedAgent(role, basePrompt) {
|
|
2015
|
+
const briefedPrompt = this.brain.prepareBrief(role, basePrompt);
|
|
2016
|
+
let result = await this.runSingleAgent(role, briefedPrompt);
|
|
2017
|
+
const verdict = this.brain.reviewOutput(role, result);
|
|
2018
|
+
if (verdict.verdict === "redo" && verdict.correction) {
|
|
2019
|
+
logger.info(`Director: redo ${role} \u2014 ${verdict.notes}`);
|
|
2020
|
+
this.emit("director:redo", { role, reason: verdict.notes });
|
|
2021
|
+
const redoPrompt = this.brain.prepareRedoPrompt(
|
|
2022
|
+
role,
|
|
2023
|
+
basePrompt,
|
|
2024
|
+
verdict.correction
|
|
2025
|
+
);
|
|
2026
|
+
result = await this.runSingleAgent(role, redoPrompt);
|
|
2027
|
+
this.brain.reviewOutput(role, result);
|
|
2028
|
+
}
|
|
2029
|
+
return result;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Run coding stage with phased execution and parallel modules.
|
|
2033
|
+
*
|
|
2034
|
+
* For the builder, the Director can split work into phases:
|
|
2035
|
+
* Phase 1: Setup (structure, deps, configs)
|
|
2036
|
+
* Phase 2: Core implementation
|
|
2037
|
+
* Phase 3: Polish (error handling, edge cases)
|
|
2038
|
+
*
|
|
2039
|
+
* Between each phase, the Director reviews and can course-correct.
|
|
2040
|
+
*/
|
|
2041
|
+
async runCodingStage(builderPrompt, plan) {
|
|
2042
|
+
let modules = plan?.modules || [];
|
|
2043
|
+
if (modules.length === 0) {
|
|
2044
|
+
const archOutput = this.context.get("architect") || "";
|
|
2045
|
+
modules = this.extractModules(archOutput);
|
|
2046
|
+
}
|
|
2047
|
+
if (modules.length > 1) {
|
|
2048
|
+
logger.info(`Found ${modules.length} modules - spawning parallel builders`);
|
|
2049
|
+
const builderRole = ROLES.builder;
|
|
2050
|
+
const contextStr = this.context.buildContext("builder");
|
|
2051
|
+
const briefedPrompt = this.brain.prepareBrief("builder", builderPrompt);
|
|
2052
|
+
const spawnOpts = modules.map((mod, i) => ({
|
|
2053
|
+
role: `builder-${i}`,
|
|
2054
|
+
prompt: `${briefedPrompt}
|
|
2055
|
+
|
|
2056
|
+
---
|
|
2057
|
+
|
|
2058
|
+
You are assigned MODULE ${i + 1} of ${modules.length}:
|
|
2059
|
+
|
|
2060
|
+
${mod}
|
|
2061
|
+
|
|
2062
|
+
Focus only on this module.`,
|
|
2063
|
+
systemPrompt: builderRole.systemPrompt,
|
|
2064
|
+
cwd: this.opts.cwd,
|
|
2065
|
+
maxTurns: this.opts.agentConfig?.builder?.maxTurns || 50,
|
|
2066
|
+
allowedTools: builderRole.allowedTools,
|
|
2067
|
+
context: contextStr
|
|
2068
|
+
}));
|
|
2069
|
+
const results = await this.scheduler.runParallel(
|
|
2070
|
+
spawnOpts,
|
|
2071
|
+
(role, event, data) => {
|
|
2072
|
+
this._activeAgent = role;
|
|
2073
|
+
if (event === "data" && data && typeof data === "object" && "text" in data) {
|
|
2074
|
+
const text = String(data.text);
|
|
2075
|
+
this.brain.monitorOutput(role, text);
|
|
2076
|
+
this._currentOutput.push(text);
|
|
2077
|
+
if (this._currentOutput.length > 50) {
|
|
2078
|
+
this._currentOutput = this._currentOutput.slice(-50);
|
|
2079
|
+
}
|
|
2080
|
+
this.emit("output", { role, text });
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
);
|
|
2084
|
+
const combinedOutput = results.map((r) => r.output).join("\n---\n");
|
|
2085
|
+
await this.context.save("builder", combinedOutput);
|
|
2086
|
+
} else {
|
|
2087
|
+
const phases = this.brain.getPhases("builder", builderPrompt);
|
|
2088
|
+
if (phases.length > 1) {
|
|
2089
|
+
let combinedOutput = "";
|
|
2090
|
+
for (let i = 0; i < phases.length; i++) {
|
|
2091
|
+
this.emit("director:phase", {
|
|
2092
|
+
role: "builder",
|
|
2093
|
+
phase: i + 1,
|
|
2094
|
+
total: phases.length
|
|
2095
|
+
});
|
|
2096
|
+
const phasePrompt = this.brain.prepareBrief("builder", phases[i]);
|
|
2097
|
+
const result = await this.runSingleAgent("builder", phasePrompt);
|
|
2098
|
+
combinedOutput += result.output + "\n---\n";
|
|
2099
|
+
if (i < phases.length - 1) {
|
|
2100
|
+
const verdict = this.brain.reviewOutput("builder", result);
|
|
2101
|
+
if (verdict.verdict === "redo" && verdict.correction) {
|
|
2102
|
+
logger.info(`Director: redo builder phase ${i + 1} \u2014 ${verdict.notes}`);
|
|
2103
|
+
this.emit("director:redo", { role: "builder", reason: `Phase ${i + 1}: ${verdict.notes}` });
|
|
2104
|
+
const redoPrompt = this.brain.prepareRedoPrompt(
|
|
2105
|
+
"builder",
|
|
2106
|
+
phases[i],
|
|
2107
|
+
verdict.correction
|
|
2108
|
+
);
|
|
2109
|
+
const redoResult = await this.runSingleAgent("builder", redoPrompt);
|
|
2110
|
+
combinedOutput += redoResult.output + "\n---\n";
|
|
2111
|
+
this.brain.reviewOutput("builder", redoResult);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
await this.context.save("builder", combinedOutput);
|
|
2116
|
+
} else {
|
|
2117
|
+
const result = await this.runDirectedAgent("builder", builderPrompt);
|
|
2118
|
+
await this.context.save("builder", result.output);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
// ── Low-level agent spawn ───────────────────────────────
|
|
2123
|
+
/**
|
|
2124
|
+
* Spawn a single agent and wait for completion.
|
|
2125
|
+
* Handles supervised mode approval, supervisor tracking, and output monitoring.
|
|
2126
|
+
*/
|
|
2127
|
+
async runSingleAgent(role, prompt) {
|
|
2128
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2129
|
+
if (!roleDef) {
|
|
2130
|
+
throw new Error(`Unknown role: ${role}`);
|
|
2131
|
+
}
|
|
2132
|
+
const decision = await this.requestApproval(role, roleDef.allowedTools);
|
|
2133
|
+
if (decision === "abort") {
|
|
2134
|
+
throw new Error("Aborted by user");
|
|
2135
|
+
}
|
|
2136
|
+
if (decision === "skip") {
|
|
2137
|
+
return {
|
|
2138
|
+
role,
|
|
2139
|
+
output: "",
|
|
2140
|
+
duration: 0,
|
|
2141
|
+
success: true,
|
|
2142
|
+
exitCode: 0
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
return new Promise((resolve, reject) => {
|
|
2146
|
+
this._activeAgent = role;
|
|
2147
|
+
this._currentOutput = [];
|
|
2148
|
+
const prevContext = this.context.buildContext(role);
|
|
2149
|
+
const sharedContext = this.brain.memory.getFullContext();
|
|
2150
|
+
const contextStr = prevContext ? `${prevContext}
|
|
2151
|
+
|
|
2152
|
+
${sharedContext}` : sharedContext;
|
|
2153
|
+
const baseRole = role.replace(/-\d+$/, "");
|
|
2154
|
+
const maxTurns = this.opts.agentConfig?.[baseRole]?.maxTurns || 50;
|
|
2155
|
+
const emitter = spawnClaudeAgent({
|
|
2156
|
+
role,
|
|
2157
|
+
prompt,
|
|
2158
|
+
systemPrompt: roleDef.systemPrompt,
|
|
2159
|
+
cwd: this.opts.cwd,
|
|
2160
|
+
maxTurns,
|
|
2161
|
+
allowedTools: roleDef.allowedTools,
|
|
2162
|
+
disallowedTools: roleDef.disallowedTools,
|
|
2163
|
+
context: contextStr
|
|
2164
|
+
});
|
|
2165
|
+
emitter.on("spawned", (pid) => {
|
|
2166
|
+
this.registry.activate(role, pid, emitter);
|
|
2167
|
+
if (emitter.child) {
|
|
2168
|
+
this.supervisor.track(role, emitter.child);
|
|
2169
|
+
}
|
|
2170
|
+
this.emit("agent:spawned", { role, pid });
|
|
2171
|
+
logger.info(`${roleDef.emoji} ${roleDef.name} spawned (PID: ${pid})`);
|
|
2172
|
+
});
|
|
2173
|
+
emitter.on("data", (text) => {
|
|
2174
|
+
this.registry.appendOutput(role, text);
|
|
2175
|
+
this.supervisor.heartbeat(role);
|
|
2176
|
+
const intervention = this.brain.monitorOutput(role, text);
|
|
2177
|
+
if (intervention) {
|
|
2178
|
+
this.emit("director:intervention", intervention);
|
|
2179
|
+
}
|
|
2180
|
+
this._currentOutput.push(text);
|
|
2181
|
+
if (this._currentOutput.length > 50) {
|
|
2182
|
+
this._currentOutput = this._currentOutput.slice(-50);
|
|
2183
|
+
}
|
|
2184
|
+
this.emit("output", { role, text });
|
|
2185
|
+
});
|
|
2186
|
+
emitter.on("done", (result) => {
|
|
2187
|
+
this.registry.complete(role, result);
|
|
2188
|
+
this._artifacts.push(`${role}: ${result.success ? "completed" : "failed"} in ${result.duration}s`);
|
|
2189
|
+
this.emit("agent:done", { role, result });
|
|
2190
|
+
logger.info(
|
|
2191
|
+
`${roleDef.emoji} ${roleDef.name} ${result.success ? "completed" : "failed"} in ${result.duration}s`
|
|
2192
|
+
);
|
|
2193
|
+
resolve(result);
|
|
2194
|
+
});
|
|
2195
|
+
emitter.on("error", (err) => {
|
|
2196
|
+
this.registry.complete(role, {
|
|
2197
|
+
role,
|
|
2198
|
+
output: "",
|
|
2199
|
+
duration: 0,
|
|
2200
|
+
success: false,
|
|
2201
|
+
exitCode: 1
|
|
2202
|
+
});
|
|
2203
|
+
this.emit("agent:error", { role, error: err });
|
|
2204
|
+
logger.error(`${roleDef.emoji} ${roleDef.name} error: ${err.message}`);
|
|
2205
|
+
reject(err);
|
|
2206
|
+
});
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Request approval from user before spawning (supervised mode).
|
|
2211
|
+
*/
|
|
2212
|
+
requestApproval(role, tools) {
|
|
2213
|
+
return new Promise((resolve) => {
|
|
2214
|
+
if (!this.opts.supervised) {
|
|
2215
|
+
resolve("approve");
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
const handler = (response) => {
|
|
2219
|
+
resolve(response);
|
|
2220
|
+
};
|
|
2221
|
+
this.once("approve:response", handler);
|
|
2222
|
+
this.emit("approve:request", { role, tools });
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
// ── Helpers ─────────────────────────────────────────────
|
|
2226
|
+
extractModules(archOutput) {
|
|
2227
|
+
const moduleRegex = /### MODULE:\s*(.+?)(?:\n|$)([\s\S]*?)(?=### MODULE:|$)/g;
|
|
2228
|
+
const modules = [];
|
|
2229
|
+
let match;
|
|
2230
|
+
while ((match = moduleRegex.exec(archOutput)) !== null) {
|
|
2231
|
+
modules.push(`${match[1].trim()}
|
|
2232
|
+
${match[2].trim()}`);
|
|
2233
|
+
}
|
|
2234
|
+
return modules;
|
|
2235
|
+
}
|
|
2236
|
+
needsDesign() {
|
|
2237
|
+
const planOutput = this.context.get("strategist") || "";
|
|
2238
|
+
const uiKeywords = [
|
|
2239
|
+
"ui",
|
|
2240
|
+
"frontend",
|
|
2241
|
+
"react",
|
|
2242
|
+
"vue",
|
|
2243
|
+
"angular",
|
|
2244
|
+
"svelte",
|
|
2245
|
+
"component",
|
|
2246
|
+
"page",
|
|
2247
|
+
"layout",
|
|
2248
|
+
"css",
|
|
2249
|
+
"html",
|
|
2250
|
+
"web app",
|
|
2251
|
+
"website",
|
|
2252
|
+
"dashboard",
|
|
2253
|
+
"interface",
|
|
2254
|
+
"design"
|
|
2255
|
+
];
|
|
2256
|
+
const lower = planOutput.toLowerCase();
|
|
2257
|
+
return uiKeywords.some((kw) => lower.includes(kw));
|
|
2258
|
+
}
|
|
2259
|
+
shouldSkip(stage) {
|
|
2260
|
+
return shouldSkipStage(stage, this.opts.skipStages);
|
|
2261
|
+
}
|
|
2262
|
+
setStage(stage) {
|
|
2263
|
+
this._stage = stage;
|
|
2264
|
+
this.emit("stage", stage);
|
|
2265
|
+
logger.info(`
|
|
2266
|
+
--- Stage: ${stage.toUpperCase()} ---`);
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Graceful shutdown
|
|
2270
|
+
*/
|
|
2271
|
+
shutdown() {
|
|
2272
|
+
this.supervisor.stop();
|
|
2273
|
+
this.scheduler.killAll();
|
|
2274
|
+
this.registry.killAll();
|
|
2275
|
+
killAllAgents();
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
|
|
2279
|
+
// src/ui/renderer.ts
|
|
2280
|
+
import chalk2 from "chalk";
|
|
2281
|
+
import ora from "ora";
|
|
2282
|
+
import boxen from "boxen";
|
|
2283
|
+
import gradient from "gradient-string";
|
|
2284
|
+
import figlet from "figlet";
|
|
2285
|
+
|
|
2286
|
+
// src/ui/theme.ts
|
|
2287
|
+
var THEME = {
|
|
2288
|
+
primary: "#A78BFA",
|
|
2289
|
+
secondary: "#60A5FA",
|
|
2290
|
+
success: "#34D399",
|
|
2291
|
+
warning: "#FBBF24",
|
|
2292
|
+
error: "#F87171",
|
|
2293
|
+
muted: "#6B7280",
|
|
2294
|
+
bg: "#1F2937",
|
|
2295
|
+
gradient: ["#7C3AED", "#A78BFA", "#C084FC"]
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
// src/utils/process.ts
|
|
2299
|
+
function setupGracefulShutdown(engine, onCleanup) {
|
|
2300
|
+
let shuttingDown = false;
|
|
2301
|
+
const shutdown = (signal) => {
|
|
2302
|
+
if (shuttingDown) {
|
|
2303
|
+
console.error("\nForce exit.");
|
|
2304
|
+
killAllAgents();
|
|
2305
|
+
process.exit(1);
|
|
2306
|
+
}
|
|
2307
|
+
shuttingDown = true;
|
|
2308
|
+
console.error(`
|
|
2309
|
+
Received ${signal}, shutting down all agents...`);
|
|
2310
|
+
onCleanup?.();
|
|
2311
|
+
engine.shutdown();
|
|
2312
|
+
killAllAgents();
|
|
2313
|
+
setTimeout(() => {
|
|
2314
|
+
killAllAgents();
|
|
2315
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
2316
|
+
}, 3e3).unref();
|
|
2317
|
+
};
|
|
2318
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2319
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2320
|
+
process.on("uncaughtException", (err) => {
|
|
2321
|
+
console.error("Uncaught exception:", err.message);
|
|
2322
|
+
onCleanup?.();
|
|
2323
|
+
engine.shutdown();
|
|
2324
|
+
killAllAgents();
|
|
2325
|
+
process.exit(1);
|
|
2326
|
+
});
|
|
2327
|
+
process.on("unhandledRejection", (reason) => {
|
|
2328
|
+
console.error("Unhandled rejection:", reason);
|
|
2329
|
+
onCleanup?.();
|
|
2330
|
+
engine.shutdown();
|
|
2331
|
+
killAllAgents();
|
|
2332
|
+
process.exit(1);
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
function formatDuration(seconds) {
|
|
2336
|
+
if (seconds < 60) return `${seconds}s`;
|
|
2337
|
+
const mins = Math.floor(seconds / 60);
|
|
2338
|
+
const secs = seconds % 60;
|
|
2339
|
+
if (mins < 60) return `${mins}m ${secs}s`;
|
|
2340
|
+
const hours = Math.floor(mins / 60);
|
|
2341
|
+
const remainMins = mins % 60;
|
|
2342
|
+
return `${hours}h ${remainMins}m ${secs}s`;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// src/ui/renderer.ts
|
|
2346
|
+
var AGENT_PHASES = {
|
|
2347
|
+
strategist: [
|
|
2348
|
+
"analyzing requirements...",
|
|
2349
|
+
"breaking down the problem...",
|
|
2350
|
+
"identifying modules and tasks...",
|
|
2351
|
+
"estimating complexity...",
|
|
2352
|
+
"drafting implementation plan...",
|
|
2353
|
+
"defining file structure...",
|
|
2354
|
+
"mapping dependencies...",
|
|
2355
|
+
"finalizing plan..."
|
|
2356
|
+
],
|
|
2357
|
+
designer: [
|
|
2358
|
+
"studying user flows...",
|
|
2359
|
+
"mapping component hierarchy...",
|
|
2360
|
+
"designing layouts...",
|
|
2361
|
+
"choosing color palette and typography...",
|
|
2362
|
+
"planning interactions and animations...",
|
|
2363
|
+
"writing component specs..."
|
|
2364
|
+
],
|
|
2365
|
+
architect: [
|
|
2366
|
+
"designing system architecture...",
|
|
2367
|
+
"defining data models...",
|
|
2368
|
+
"planning API endpoints...",
|
|
2369
|
+
"setting up project skeleton...",
|
|
2370
|
+
"resolving dependencies...",
|
|
2371
|
+
"splitting into parallel modules...",
|
|
2372
|
+
"creating boilerplate files..."
|
|
2373
|
+
],
|
|
2374
|
+
builder: [
|
|
2375
|
+
"reading the plan and architecture...",
|
|
2376
|
+
"installing dependencies...",
|
|
2377
|
+
"writing core modules...",
|
|
2378
|
+
"implementing features...",
|
|
2379
|
+
"connecting components...",
|
|
2380
|
+
"handling edge cases...",
|
|
2381
|
+
"cleaning up code..."
|
|
2382
|
+
],
|
|
2383
|
+
guardian: [
|
|
2384
|
+
"scanning the codebase...",
|
|
2385
|
+
"writing unit tests...",
|
|
2386
|
+
"writing integration tests...",
|
|
2387
|
+
"running test suite...",
|
|
2388
|
+
"checking coverage...",
|
|
2389
|
+
"fixing failing tests...",
|
|
2390
|
+
"reporting results..."
|
|
2391
|
+
],
|
|
2392
|
+
sentinel: [
|
|
2393
|
+
"reading through all files...",
|
|
2394
|
+
"checking for bugs and logic errors...",
|
|
2395
|
+
"scanning for security issues...",
|
|
2396
|
+
"reviewing performance patterns...",
|
|
2397
|
+
"checking code style and consistency...",
|
|
2398
|
+
"verifying architecture compliance...",
|
|
2399
|
+
"writing review summary..."
|
|
2400
|
+
],
|
|
2401
|
+
scribe: [
|
|
2402
|
+
"reading the codebase...",
|
|
2403
|
+
"writing README...",
|
|
2404
|
+
"documenting API endpoints...",
|
|
2405
|
+
"adding setup instructions...",
|
|
2406
|
+
"writing usage examples...",
|
|
2407
|
+
"finalizing docs..."
|
|
2408
|
+
],
|
|
2409
|
+
analyst: [
|
|
2410
|
+
"understanding the problem domain...",
|
|
2411
|
+
"identifying user personas...",
|
|
2412
|
+
"writing user stories...",
|
|
2413
|
+
"defining acceptance criteria...",
|
|
2414
|
+
"mapping edge cases...",
|
|
2415
|
+
"prioritizing requirements...",
|
|
2416
|
+
"finalizing business specs..."
|
|
2417
|
+
],
|
|
2418
|
+
qa: [
|
|
2419
|
+
"analyzing codebase for testability...",
|
|
2420
|
+
"defining test strategy...",
|
|
2421
|
+
"writing test cases...",
|
|
2422
|
+
"creating test matrix...",
|
|
2423
|
+
"identifying edge cases and boundaries...",
|
|
2424
|
+
"building regression checklist...",
|
|
2425
|
+
"finalizing test plan..."
|
|
2426
|
+
],
|
|
2427
|
+
devops: [
|
|
2428
|
+
"analyzing project structure...",
|
|
2429
|
+
"setting up CI pipeline...",
|
|
2430
|
+
"writing Dockerfile...",
|
|
2431
|
+
"configuring deployment...",
|
|
2432
|
+
"setting up environment variables...",
|
|
2433
|
+
"adding health checks...",
|
|
2434
|
+
"finalizing infrastructure..."
|
|
2435
|
+
],
|
|
2436
|
+
security: [
|
|
2437
|
+
"scanning for injection vulnerabilities...",
|
|
2438
|
+
"checking authentication flows...",
|
|
2439
|
+
"reviewing authorization logic...",
|
|
2440
|
+
"searching for exposed secrets...",
|
|
2441
|
+
"auditing dependencies...",
|
|
2442
|
+
"checking configuration security...",
|
|
2443
|
+
"writing security report..."
|
|
2444
|
+
]
|
|
2445
|
+
};
|
|
2446
|
+
var TerminalRenderer = class {
|
|
2447
|
+
engine;
|
|
2448
|
+
spinners = /* @__PURE__ */ new Map();
|
|
2449
|
+
agentTimings = [];
|
|
2450
|
+
completedStages = 0;
|
|
2451
|
+
totalStages = 0;
|
|
2452
|
+
currentStage = null;
|
|
2453
|
+
phaseTimers = /* @__PURE__ */ new Map();
|
|
2454
|
+
phaseIndex = /* @__PURE__ */ new Map();
|
|
2455
|
+
latestStats = [];
|
|
2456
|
+
killedAgents = [];
|
|
2457
|
+
lastRealUpdate = /* @__PURE__ */ new Map();
|
|
2458
|
+
constructor(engine) {
|
|
2459
|
+
this.engine = engine;
|
|
2460
|
+
this.totalStages = this.countActiveStages();
|
|
2461
|
+
this.bindEvents();
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Print the ASCII banner with gradient
|
|
2465
|
+
*/
|
|
2466
|
+
printBanner() {
|
|
2467
|
+
const ascii = figlet.textSync("KRENK", { font: "ANSI Shadow" });
|
|
2468
|
+
console.log(gradient(THEME.gradient)(ascii));
|
|
2469
|
+
console.log(chalk2.gray(" Multi-Agent Software Engineering Orchestrator"));
|
|
2470
|
+
console.log(chalk2.gray(" " + "-".repeat(50)));
|
|
2471
|
+
console.log();
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Print the user prompt being executed
|
|
2475
|
+
*/
|
|
2476
|
+
printPrompt(prompt) {
|
|
2477
|
+
console.log(chalk2.dim(` Prompt: ${prompt}`));
|
|
2478
|
+
console.log();
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Print the final summary after engine completes
|
|
2482
|
+
*/
|
|
2483
|
+
printSummary(result) {
|
|
2484
|
+
console.log();
|
|
2485
|
+
const lines = [];
|
|
2486
|
+
if (result.success) {
|
|
2487
|
+
lines.push(chalk2.bold.green("[done] Workflow Complete"));
|
|
2488
|
+
} else {
|
|
2489
|
+
lines.push(chalk2.bold.red("[fail] Workflow Failed"));
|
|
2490
|
+
}
|
|
2491
|
+
lines.push("");
|
|
2492
|
+
lines.push(chalk2.white(` Stages completed: ${chalk2.bold(String(result.stages))}`));
|
|
2493
|
+
lines.push(chalk2.white(` Total duration: ${chalk2.bold(formatDuration(result.duration))}`));
|
|
2494
|
+
lines.push("");
|
|
2495
|
+
lines.push(chalk2.bold.white(" Agent Results:"));
|
|
2496
|
+
for (const agent of this.agentTimings) {
|
|
2497
|
+
const duration = agent.endTime ? Math.round((agent.endTime - agent.startTime) / 1e3) : 0;
|
|
2498
|
+
const killed = this.killedAgents.find((k) => k.role === agent.role);
|
|
2499
|
+
let status;
|
|
2500
|
+
if (killed) {
|
|
2501
|
+
status = chalk2.red("x killed");
|
|
2502
|
+
} else if (agent.success) {
|
|
2503
|
+
status = chalk2.green("+ pass");
|
|
2504
|
+
} else {
|
|
2505
|
+
status = chalk2.red("x fail");
|
|
2506
|
+
}
|
|
2507
|
+
const stat = this.latestStats.find((s) => s.role === agent.role);
|
|
2508
|
+
const memInfo = stat ? chalk2.dim(` ${stat.memoryMB}MB`) : "";
|
|
2509
|
+
lines.push(
|
|
2510
|
+
` ${agent.emoji} ${chalk2.white(agent.name.padEnd(12))} ${status} ${chalk2.dim(`${duration}s`)}${memInfo}`
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
if (this.killedAgents.length > 0) {
|
|
2514
|
+
lines.push("");
|
|
2515
|
+
lines.push(chalk2.bold.yellow(" Supervisor Actions:"));
|
|
2516
|
+
for (const k of this.killedAgents) {
|
|
2517
|
+
const roleDef = ROLES[k.role] || ROLES[k.role.replace(/-\d+$/, "")];
|
|
2518
|
+
const name = roleDef?.name || k.role;
|
|
2519
|
+
lines.push(chalk2.yellow(` x ${name} (PID ${k.pid}): ${k.reason}`));
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const panel = boxen(lines.join("\n"), {
|
|
2523
|
+
padding: 1,
|
|
2524
|
+
margin: { top: 0, bottom: 1, left: 1, right: 1 },
|
|
2525
|
+
borderStyle: "round",
|
|
2526
|
+
borderColor: result.success ? "green" : "red"
|
|
2527
|
+
});
|
|
2528
|
+
console.log(panel);
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Prompt user to approve/skip/abort an agent (supervised mode).
|
|
2532
|
+
* Uses arrow-key selection directly in the terminal.
|
|
2533
|
+
*/
|
|
2534
|
+
handleApprovalRequest({ role, tools }) {
|
|
2535
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2536
|
+
const name = roleDef?.name || role;
|
|
2537
|
+
const emoji = roleDef?.emoji || ">";
|
|
2538
|
+
const desc = roleDef?.description || "";
|
|
2539
|
+
console.log();
|
|
2540
|
+
console.log(chalk2.bold.hex("#A78BFA")(` -- Approval Required --`));
|
|
2541
|
+
console.log();
|
|
2542
|
+
console.log(chalk2.white(` Agent: ${emoji} ${chalk2.bold(name)}`));
|
|
2543
|
+
console.log(chalk2.dim(` ${desc}`));
|
|
2544
|
+
console.log(chalk2.dim(` Tools: ${tools.join(", ")}`));
|
|
2545
|
+
console.log();
|
|
2546
|
+
const options = ["Approve", "Skip", "Abort"];
|
|
2547
|
+
let selected = 0;
|
|
2548
|
+
const render = () => {
|
|
2549
|
+
for (let i = 0; i < options.length; i++) {
|
|
2550
|
+
const pointer = i === selected ? chalk2.hex("#A78BFA")(">") : " ";
|
|
2551
|
+
const label = i === selected ? chalk2.bold.white(options[i]) : chalk2.dim(options[i]);
|
|
2552
|
+
process.stdout.write(` ${pointer} ${label}
|
|
2553
|
+
`);
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
const clear = () => {
|
|
2557
|
+
for (let i = 0; i < options.length; i++) {
|
|
2558
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
render();
|
|
2562
|
+
const wasRaw = process.stdin.isRaw;
|
|
2563
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
2564
|
+
process.stdin.resume();
|
|
2565
|
+
const onKey = (key) => {
|
|
2566
|
+
const str = key.toString();
|
|
2567
|
+
if (str === "") {
|
|
2568
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
2569
|
+
process.stdin.pause();
|
|
2570
|
+
process.stdin.removeListener("data", onKey);
|
|
2571
|
+
this.engine.emit("approve:response", "abort");
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (str === "\x1B[A" && selected > 0) {
|
|
2575
|
+
selected--;
|
|
2576
|
+
clear();
|
|
2577
|
+
render();
|
|
2578
|
+
}
|
|
2579
|
+
if (str === "\x1B[B" && selected < options.length - 1) {
|
|
2580
|
+
selected++;
|
|
2581
|
+
clear();
|
|
2582
|
+
render();
|
|
2583
|
+
}
|
|
2584
|
+
if (str === "\r" || str === "\n") {
|
|
2585
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
2586
|
+
process.stdin.pause();
|
|
2587
|
+
process.stdin.removeListener("data", onKey);
|
|
2588
|
+
clear();
|
|
2589
|
+
const choice = options[selected];
|
|
2590
|
+
const color = choice === "Approve" ? chalk2.green : choice === "Skip" ? chalk2.yellow : chalk2.red;
|
|
2591
|
+
console.log(` ${color(">")} ${chalk2.bold(choice)}`);
|
|
2592
|
+
console.log();
|
|
2593
|
+
const response = choice.toLowerCase();
|
|
2594
|
+
this.engine.emit("approve:response", response);
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
process.stdin.on("data", onKey);
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Stop all active spinners (for Ctrl+C cleanup)
|
|
2601
|
+
*/
|
|
2602
|
+
cleanup() {
|
|
2603
|
+
for (const [, timer] of this.phaseTimers) {
|
|
2604
|
+
clearInterval(timer);
|
|
2605
|
+
}
|
|
2606
|
+
this.phaseTimers.clear();
|
|
2607
|
+
for (const [, spinner] of this.spinners) {
|
|
2608
|
+
spinner.stop();
|
|
2609
|
+
}
|
|
2610
|
+
this.spinners.clear();
|
|
2611
|
+
}
|
|
2612
|
+
// ── Private ──────────────────────────────────────────────
|
|
2613
|
+
bindEvents() {
|
|
2614
|
+
this.engine.on("stage", (stage) => this.onStage(stage));
|
|
2615
|
+
this.engine.on(
|
|
2616
|
+
"agent:spawned",
|
|
2617
|
+
({ role, pid }) => this.onAgentSpawned(role, pid)
|
|
2618
|
+
);
|
|
2619
|
+
this.engine.on(
|
|
2620
|
+
"output",
|
|
2621
|
+
({ role, text }) => this.onAgentOutput(role, text)
|
|
2622
|
+
);
|
|
2623
|
+
this.engine.on(
|
|
2624
|
+
"agent:done",
|
|
2625
|
+
({ role, result }) => this.onAgentDone(role, result)
|
|
2626
|
+
);
|
|
2627
|
+
this.engine.on(
|
|
2628
|
+
"agent:error",
|
|
2629
|
+
({ role, error }) => this.onAgentError(role, error)
|
|
2630
|
+
);
|
|
2631
|
+
this.engine.on(
|
|
2632
|
+
"approve:request",
|
|
2633
|
+
(data) => this.handleApprovalRequest(data)
|
|
2634
|
+
);
|
|
2635
|
+
this.engine.on("plan:parsed", ({ assignments, modules }) => {
|
|
2636
|
+
this.onPlanParsed(assignments, modules);
|
|
2637
|
+
});
|
|
2638
|
+
this.engine.on("director:review", ({ role, verdict, notes }) => {
|
|
2639
|
+
this.onDirectorReview(role, verdict, notes);
|
|
2640
|
+
});
|
|
2641
|
+
this.engine.on("director:redo", ({ role, reason }) => {
|
|
2642
|
+
this.onDirectorRedo(role, reason);
|
|
2643
|
+
});
|
|
2644
|
+
this.engine.on("director:intervention", ({ role, type, message }) => {
|
|
2645
|
+
this.onDirectorIntervention(role, type, message);
|
|
2646
|
+
});
|
|
2647
|
+
this.engine.on("director:phase", ({ role, phase, total }) => {
|
|
2648
|
+
this.onDirectorPhase(role, phase, total);
|
|
2649
|
+
});
|
|
2650
|
+
this.engine.on("supervisor:stats", (stats) => {
|
|
2651
|
+
this.latestStats = stats;
|
|
2652
|
+
});
|
|
2653
|
+
this.engine.on("supervisor:warning", ({ role, reason }) => {
|
|
2654
|
+
this.onSupervisorWarning(role, reason);
|
|
2655
|
+
});
|
|
2656
|
+
this.engine.on("supervisor:killed", ({ role, pid, reason }) => {
|
|
2657
|
+
this.onSupervisorKilled(role, pid, reason);
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
onStage(stage) {
|
|
2661
|
+
this.currentStage = stage;
|
|
2662
|
+
if (stage === "complete") return;
|
|
2663
|
+
const info = STAGES.find((s) => s.stage === stage);
|
|
2664
|
+
if (!info) return;
|
|
2665
|
+
const label = `${info.emoji} ${info.label.toUpperCase()}`;
|
|
2666
|
+
const line = "-".repeat(Math.max(0, 45 - label.length));
|
|
2667
|
+
console.log();
|
|
2668
|
+
console.log(chalk2.bold.cyan(` --- ${label} ${line}`));
|
|
2669
|
+
console.log(chalk2.dim(` ${info.description}`));
|
|
2670
|
+
console.log();
|
|
2671
|
+
}
|
|
2672
|
+
onAgentSpawned(role, _pid) {
|
|
2673
|
+
const baseRole = role.replace(/-\d+$/, "");
|
|
2674
|
+
const roleDef = ROLES[role] || ROLES[baseRole];
|
|
2675
|
+
const name = roleDef?.name || role;
|
|
2676
|
+
const emoji = roleDef?.emoji || ">";
|
|
2677
|
+
this.agentTimings.push({
|
|
2678
|
+
role,
|
|
2679
|
+
name,
|
|
2680
|
+
emoji,
|
|
2681
|
+
startTime: Date.now()
|
|
2682
|
+
});
|
|
2683
|
+
const phases = AGENT_PHASES[baseRole] || [`${name} is working...`];
|
|
2684
|
+
this.phaseIndex.set(role, 0);
|
|
2685
|
+
const spinner = ora({
|
|
2686
|
+
text: `${name} -- ${chalk2.dim(phases[0])}`,
|
|
2687
|
+
prefixText: ` ${emoji}`,
|
|
2688
|
+
spinner: "dots"
|
|
2689
|
+
}).start();
|
|
2690
|
+
this.spinners.set(role, spinner);
|
|
2691
|
+
const timer = setInterval(() => {
|
|
2692
|
+
const lastReal = this.lastRealUpdate.get(role) || 0;
|
|
2693
|
+
const sinceReal = Date.now() - lastReal;
|
|
2694
|
+
if (sinceReal < 1e4) return;
|
|
2695
|
+
const idx = (this.phaseIndex.get(role) || 0) + 1;
|
|
2696
|
+
this.phaseIndex.set(role, idx % phases.length);
|
|
2697
|
+
const currentIdx = idx % phases.length;
|
|
2698
|
+
const elapsed = Math.round((Date.now() - (this.agentTimings.find((a) => a.role === role)?.startTime || Date.now())) / 1e3);
|
|
2699
|
+
const sp = this.spinners.get(role);
|
|
2700
|
+
if (sp) {
|
|
2701
|
+
const stat = this.latestStats.find((s) => s.role === role);
|
|
2702
|
+
const memStr = stat ? chalk2.dim(` | ${stat.memoryMB}MB`) : "";
|
|
2703
|
+
sp.text = `${name} -- ${chalk2.dim(phases[currentIdx])} ${chalk2.dim.italic(`${elapsed}s`)}${memStr}`;
|
|
2704
|
+
}
|
|
2705
|
+
}, 8e3);
|
|
2706
|
+
this.phaseTimers.set(role, timer);
|
|
2707
|
+
}
|
|
2708
|
+
onAgentOutput(role, text) {
|
|
2709
|
+
const spinner = this.spinners.get(role);
|
|
2710
|
+
if (!spinner) return;
|
|
2711
|
+
const baseRole = role.replace(/-\d+$/, "");
|
|
2712
|
+
const roleDef = ROLES[role] || ROLES[baseRole];
|
|
2713
|
+
const name = roleDef?.name || role;
|
|
2714
|
+
const elapsed = Math.round((Date.now() - (this.agentTimings.find((a) => a.role === role)?.startTime || Date.now())) / 1e3);
|
|
2715
|
+
const stat = this.latestStats.find((s) => s.role === role);
|
|
2716
|
+
const memStr = stat ? chalk2.dim(` | ${stat.memoryMB}MB`) : "";
|
|
2717
|
+
try {
|
|
2718
|
+
const event = JSON.parse(text);
|
|
2719
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
2720
|
+
for (const block of event.message.content) {
|
|
2721
|
+
if (block.type === "tool_use") {
|
|
2722
|
+
const toolName = block.name || "";
|
|
2723
|
+
const input = block.input || {};
|
|
2724
|
+
let detail = "";
|
|
2725
|
+
if (toolName === "Read" && input.file_path) {
|
|
2726
|
+
detail = `reading ${input.file_path.split("/").pop()}`;
|
|
2727
|
+
} else if (toolName === "Write" && input.file_path) {
|
|
2728
|
+
detail = `writing ${input.file_path.split("/").pop()}`;
|
|
2729
|
+
} else if (toolName === "Edit" && input.file_path) {
|
|
2730
|
+
detail = `editing ${input.file_path.split("/").pop()}`;
|
|
2731
|
+
} else if (toolName === "Bash") {
|
|
2732
|
+
const cmd = (input.command || "").slice(0, 40);
|
|
2733
|
+
detail = `running: ${cmd}`;
|
|
2734
|
+
} else if (toolName === "Glob") {
|
|
2735
|
+
detail = `searching files: ${input.pattern || ""}`;
|
|
2736
|
+
} else if (toolName === "Grep") {
|
|
2737
|
+
detail = `searching for: ${(input.pattern || "").slice(0, 30)}`;
|
|
2738
|
+
} else if (toolName === "WebSearch") {
|
|
2739
|
+
detail = `searching web: ${(input.query || "").slice(0, 30)}`;
|
|
2740
|
+
} else {
|
|
2741
|
+
detail = `using ${toolName}`;
|
|
2742
|
+
}
|
|
2743
|
+
spinner.text = `${name} -- ${chalk2.dim(detail)} ${chalk2.dim.italic(`${elapsed}s`)}${memStr}`;
|
|
2744
|
+
this.lastRealUpdate.set(role, Date.now());
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
if (block.type === "text" && block.text) {
|
|
2748
|
+
const snippet = block.text.trim().slice(0, 70).replace(/\n/g, " ");
|
|
2749
|
+
if (snippet) {
|
|
2750
|
+
spinner.text = `${name} -- ${chalk2.dim(snippet)} ${chalk2.dim.italic(`${elapsed}s`)}${memStr}`;
|
|
2751
|
+
this.lastRealUpdate.set(role, Date.now());
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (event.type === "user") {
|
|
2758
|
+
spinner.text = `${name} -- ${chalk2.dim("processing result...")} ${chalk2.dim.italic(`${elapsed}s`)}${memStr}`;
|
|
2759
|
+
this.lastRealUpdate.set(role, Date.now());
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
} catch {
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
onAgentDone(role, result) {
|
|
2766
|
+
const timer = this.phaseTimers.get(role);
|
|
2767
|
+
if (timer) {
|
|
2768
|
+
clearInterval(timer);
|
|
2769
|
+
this.phaseTimers.delete(role);
|
|
2770
|
+
}
|
|
2771
|
+
const spinner = this.spinners.get(role);
|
|
2772
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2773
|
+
const name = roleDef?.name || role;
|
|
2774
|
+
const emoji = roleDef?.emoji || ">";
|
|
2775
|
+
const timing = this.agentTimings.find((a) => a.role === role);
|
|
2776
|
+
if (timing) {
|
|
2777
|
+
timing.endTime = Date.now();
|
|
2778
|
+
timing.success = result.success;
|
|
2779
|
+
}
|
|
2780
|
+
if (spinner) {
|
|
2781
|
+
if (result.success) {
|
|
2782
|
+
spinner.succeed(`${emoji} ${name} completed (${result.duration}s)`);
|
|
2783
|
+
} else {
|
|
2784
|
+
spinner.fail(`${emoji} ${name} failed (${result.duration}s)`);
|
|
2785
|
+
}
|
|
2786
|
+
this.spinners.delete(role);
|
|
2787
|
+
}
|
|
2788
|
+
if (!role.includes("-")) {
|
|
2789
|
+
this.completedStages++;
|
|
2790
|
+
this.printProgress();
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
onAgentError(role, error) {
|
|
2794
|
+
const timer = this.phaseTimers.get(role);
|
|
2795
|
+
if (timer) {
|
|
2796
|
+
clearInterval(timer);
|
|
2797
|
+
this.phaseTimers.delete(role);
|
|
2798
|
+
}
|
|
2799
|
+
const spinner = this.spinners.get(role);
|
|
2800
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2801
|
+
const name = roleDef?.name || role;
|
|
2802
|
+
const emoji = roleDef?.emoji || ">";
|
|
2803
|
+
const timing = this.agentTimings.find((a) => a.role === role);
|
|
2804
|
+
if (timing) {
|
|
2805
|
+
timing.endTime = Date.now();
|
|
2806
|
+
timing.success = false;
|
|
2807
|
+
}
|
|
2808
|
+
if (spinner) {
|
|
2809
|
+
spinner.fail(`${emoji} ${name} error: ${error.message}`);
|
|
2810
|
+
this.spinners.delete(role);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
onDirectorReview(role, verdict, notes) {
|
|
2814
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2815
|
+
const name = roleDef?.name || role;
|
|
2816
|
+
if (verdict === "redo") {
|
|
2817
|
+
console.log(chalk2.yellow(` PM: ${name} output needs work \u2014 ${notes}`));
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
onDirectorRedo(role, reason) {
|
|
2821
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2822
|
+
const name = roleDef?.name || role;
|
|
2823
|
+
const emoji = roleDef?.emoji || ">";
|
|
2824
|
+
console.log();
|
|
2825
|
+
console.log(chalk2.yellow(` ${emoji} PM is re-running ${name}: ${reason}`));
|
|
2826
|
+
console.log();
|
|
2827
|
+
}
|
|
2828
|
+
onDirectorIntervention(role, type, message) {
|
|
2829
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2830
|
+
const name = roleDef?.name || role;
|
|
2831
|
+
const spinner = this.spinners.get(role);
|
|
2832
|
+
if (spinner && type === "off_track") {
|
|
2833
|
+
const prevText = spinner.text;
|
|
2834
|
+
spinner.color = "yellow";
|
|
2835
|
+
spinner.text = `${name} -- ${chalk2.yellow(`PM: ${message}`)}`;
|
|
2836
|
+
setTimeout(() => {
|
|
2837
|
+
if (this.spinners.has(role)) {
|
|
2838
|
+
spinner.color = "cyan";
|
|
2839
|
+
spinner.text = prevText;
|
|
2840
|
+
}
|
|
2841
|
+
}, 5e3);
|
|
2842
|
+
} else if (type === "error_detected") {
|
|
2843
|
+
if (spinner) {
|
|
2844
|
+
const prevText = spinner.text;
|
|
2845
|
+
spinner.color = "red";
|
|
2846
|
+
setTimeout(() => {
|
|
2847
|
+
if (this.spinners.has(role)) {
|
|
2848
|
+
spinner.color = "cyan";
|
|
2849
|
+
spinner.text = prevText;
|
|
2850
|
+
}
|
|
2851
|
+
}, 3e3);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
onDirectorPhase(role, phase, total) {
|
|
2856
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2857
|
+
const name = roleDef?.name || role;
|
|
2858
|
+
const emoji = roleDef?.emoji || ">";
|
|
2859
|
+
console.log();
|
|
2860
|
+
console.log(chalk2.hex(THEME.primary)(` ${emoji} ${name} \u2014 Phase ${phase}/${total}`));
|
|
2861
|
+
}
|
|
2862
|
+
onPlanParsed(assignments, modules) {
|
|
2863
|
+
console.log();
|
|
2864
|
+
console.log(chalk2.bold.hex(THEME.primary)(" --- MASTER PLAN READY ---"));
|
|
2865
|
+
console.log();
|
|
2866
|
+
if (assignments.length > 0) {
|
|
2867
|
+
console.log(chalk2.white(" Agent assignments:"));
|
|
2868
|
+
for (const role of assignments) {
|
|
2869
|
+
const roleDef = ROLES[role];
|
|
2870
|
+
if (roleDef) {
|
|
2871
|
+
console.log(chalk2.dim(` ${roleDef.emoji} ${roleDef.name}: assigned`));
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (modules > 0) {
|
|
2876
|
+
console.log(chalk2.dim(`
|
|
2877
|
+
${modules} module(s) identified for parallel coding`));
|
|
2878
|
+
}
|
|
2879
|
+
console.log();
|
|
2880
|
+
}
|
|
2881
|
+
onSupervisorWarning(role, reason) {
|
|
2882
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2883
|
+
const name = roleDef?.name || role;
|
|
2884
|
+
const spinner = this.spinners.get(role);
|
|
2885
|
+
if (spinner) {
|
|
2886
|
+
const prevText = spinner.text;
|
|
2887
|
+
spinner.color = "yellow";
|
|
2888
|
+
spinner.text = `${name} -- ${chalk2.yellow(`! ${reason}`)}`;
|
|
2889
|
+
setTimeout(() => {
|
|
2890
|
+
if (this.spinners.has(role)) {
|
|
2891
|
+
spinner.color = "cyan";
|
|
2892
|
+
spinner.text = prevText;
|
|
2893
|
+
}
|
|
2894
|
+
}, 5e3);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
onSupervisorKilled(role, pid, reason) {
|
|
2898
|
+
const roleDef = ROLES[role] || ROLES[role.replace(/-\d+$/, "")];
|
|
2899
|
+
const name = roleDef?.name || role;
|
|
2900
|
+
const emoji = roleDef?.emoji || ">";
|
|
2901
|
+
const spinner = this.spinners.get(role);
|
|
2902
|
+
this.killedAgents.push({ role, pid, reason });
|
|
2903
|
+
if (spinner) {
|
|
2904
|
+
spinner.fail(`${emoji} ${name} killed by supervisor: ${reason}`);
|
|
2905
|
+
this.spinners.delete(role);
|
|
2906
|
+
} else {
|
|
2907
|
+
console.log(chalk2.red(` x ${emoji} ${name} killed by supervisor: ${reason}`));
|
|
2908
|
+
}
|
|
2909
|
+
const timer = this.phaseTimers.get(role);
|
|
2910
|
+
if (timer) {
|
|
2911
|
+
clearInterval(timer);
|
|
2912
|
+
this.phaseTimers.delete(role);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
printProgress() {
|
|
2916
|
+
const total = this.totalStages;
|
|
2917
|
+
const done = this.completedStages;
|
|
2918
|
+
const filled = "#".repeat(done);
|
|
2919
|
+
const empty = "-".repeat(Math.max(0, total - done));
|
|
2920
|
+
console.log(chalk2.dim(` [${filled}${empty}] ${done}/${total} stages`));
|
|
2921
|
+
}
|
|
2922
|
+
countActiveStages() {
|
|
2923
|
+
const skipStages = this.engine["opts"]?.skipStages || [];
|
|
2924
|
+
return STAGES.filter(
|
|
2925
|
+
(s) => s.stage !== "complete" && !skipStages.includes(s.stage)
|
|
2926
|
+
).length;
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
|
|
2930
|
+
// src/config/loader.ts
|
|
2931
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
2932
|
+
|
|
2933
|
+
// src/config/defaults.ts
|
|
2934
|
+
var DEFAULT_CONFIG = {
|
|
2935
|
+
maxParallelAgents: 3,
|
|
2936
|
+
claudePath: "claude",
|
|
2937
|
+
workflow: ["plan", "design", "architect", "code", "test", "review", "docs"],
|
|
2938
|
+
skipStages: [],
|
|
2939
|
+
agents: {
|
|
2940
|
+
analyst: { maxTurns: 30 },
|
|
2941
|
+
strategist: { maxTurns: 30 },
|
|
2942
|
+
designer: { maxTurns: 30 },
|
|
2943
|
+
architect: { maxTurns: 50 },
|
|
2944
|
+
builder: { maxTurns: 100 },
|
|
2945
|
+
qa: { maxTurns: 40 },
|
|
2946
|
+
guardian: { maxTurns: 50 },
|
|
2947
|
+
sentinel: { maxTurns: 30 },
|
|
2948
|
+
security: { maxTurns: 30 },
|
|
2949
|
+
scribe: { maxTurns: 30 },
|
|
2950
|
+
devops: { maxTurns: 40 }
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2954
|
+
// src/config/loader.ts
|
|
2955
|
+
var MODULE_NAME = "krenk";
|
|
2956
|
+
async function loadConfig(cwd) {
|
|
2957
|
+
const explorer = cosmiconfig(MODULE_NAME, {
|
|
2958
|
+
searchPlaces: [
|
|
2959
|
+
`.${MODULE_NAME}rc`,
|
|
2960
|
+
`.${MODULE_NAME}rc.json`,
|
|
2961
|
+
`.${MODULE_NAME}rc.yaml`,
|
|
2962
|
+
`.${MODULE_NAME}rc.yml`,
|
|
2963
|
+
`${MODULE_NAME}.config.js`,
|
|
2964
|
+
`${MODULE_NAME}.config.mjs`,
|
|
2965
|
+
"package.json"
|
|
2966
|
+
]
|
|
2967
|
+
});
|
|
2968
|
+
try {
|
|
2969
|
+
const result = await explorer.search(cwd);
|
|
2970
|
+
if (result && result.config) {
|
|
2971
|
+
return mergeConfig(DEFAULT_CONFIG, result.config);
|
|
2972
|
+
}
|
|
2973
|
+
} catch {
|
|
2974
|
+
}
|
|
2975
|
+
return { ...DEFAULT_CONFIG };
|
|
2976
|
+
}
|
|
2977
|
+
function mergeConfig(defaults, user) {
|
|
2978
|
+
return {
|
|
2979
|
+
maxParallelAgents: user.maxParallelAgents ?? defaults.maxParallelAgents,
|
|
2980
|
+
claudePath: user.claudePath ?? defaults.claudePath,
|
|
2981
|
+
workflow: user.workflow ?? defaults.workflow,
|
|
2982
|
+
skipStages: user.skipStages ?? defaults.skipStages,
|
|
2983
|
+
agents: {
|
|
2984
|
+
...defaults.agents,
|
|
2985
|
+
...user.agents || {}
|
|
2986
|
+
}
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// src/commands/run.ts
|
|
2991
|
+
async function runCommand(prompt, options) {
|
|
2992
|
+
const cwd = process.cwd();
|
|
2993
|
+
const config = await loadConfig(cwd);
|
|
2994
|
+
const engine = new OrchestrationEngine({
|
|
2995
|
+
cwd,
|
|
2996
|
+
maxParallel: parseInt(options.parallel || String(config.maxParallelAgents), 10),
|
|
2997
|
+
skipStages: options.skip || config.skipStages,
|
|
2998
|
+
noUi: options.ui === false,
|
|
2999
|
+
supervised: options.supervised || false,
|
|
3000
|
+
agentConfig: config.agents
|
|
3001
|
+
});
|
|
3002
|
+
if (options.ui !== false) {
|
|
3003
|
+
const renderer = new TerminalRenderer(engine);
|
|
3004
|
+
setupGracefulShutdown(engine, () => renderer.cleanup());
|
|
3005
|
+
renderer.printBanner();
|
|
3006
|
+
renderer.printPrompt(prompt);
|
|
3007
|
+
const result = await engine.run(prompt);
|
|
3008
|
+
renderer.printSummary(result);
|
|
3009
|
+
if (!result.success) {
|
|
3010
|
+
process.exit(1);
|
|
3011
|
+
}
|
|
3012
|
+
} else {
|
|
3013
|
+
setupGracefulShutdown(engine);
|
|
3014
|
+
printBannerPlain();
|
|
3015
|
+
console.log(chalk3.dim(`Prompt: ${prompt}
|
|
3016
|
+
`));
|
|
3017
|
+
engine.on("stage", (stage) => {
|
|
3018
|
+
console.log(chalk3.bold.cyan(`
|
|
3019
|
+
--- ${stage.toUpperCase()} ---
|
|
3020
|
+
`));
|
|
3021
|
+
});
|
|
3022
|
+
engine.on("agent:spawned", ({ role, pid }) => {
|
|
3023
|
+
console.log(chalk3.dim(` Spawned ${role} (PID: ${pid})`));
|
|
3024
|
+
});
|
|
3025
|
+
engine.on("agent:done", ({ role, result: result2 }) => {
|
|
3026
|
+
const status = result2.success ? chalk3.green("done") : chalk3.red("failed");
|
|
3027
|
+
console.log(chalk3.dim(` ${role} ${status} (${result2.duration}s)`));
|
|
3028
|
+
});
|
|
3029
|
+
const result = await engine.run(prompt);
|
|
3030
|
+
if (result.success) {
|
|
3031
|
+
console.log(
|
|
3032
|
+
chalk3.green(
|
|
3033
|
+
`
|
|
3034
|
+
[done] Completed ${result.stages} stages in ${formatDuration(result.duration)}`
|
|
3035
|
+
)
|
|
3036
|
+
);
|
|
3037
|
+
} else {
|
|
3038
|
+
console.error(
|
|
3039
|
+
chalk3.red(`
|
|
3040
|
+
[fail] Workflow failed after ${result.stages} stages`)
|
|
3041
|
+
);
|
|
3042
|
+
process.exit(1);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
function printBannerPlain() {
|
|
3047
|
+
console.log(chalk3.bold.cyan("\nKRENK"));
|
|
3048
|
+
console.log(chalk3.gray("Multi-Agent Software Engineering Orchestrator"));
|
|
3049
|
+
console.log(chalk3.gray("-".repeat(55)));
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
// src/commands/plan.ts
|
|
3053
|
+
import chalk4 from "chalk";
|
|
3054
|
+
async function planCommand(prompt) {
|
|
3055
|
+
const cwd = process.cwd();
|
|
3056
|
+
const config = await loadConfig(cwd);
|
|
3057
|
+
console.log(chalk4.bold.cyan("\n> Planning: ") + prompt + "\n");
|
|
3058
|
+
const engine = new OrchestrationEngine({
|
|
3059
|
+
cwd,
|
|
3060
|
+
maxParallel: 1,
|
|
3061
|
+
skipStages: [],
|
|
3062
|
+
noUi: true,
|
|
3063
|
+
supervised: false,
|
|
3064
|
+
agentConfig: config.agents
|
|
3065
|
+
});
|
|
3066
|
+
setupGracefulShutdown(engine);
|
|
3067
|
+
const result = await engine.runPlanOnly(prompt);
|
|
3068
|
+
if (result.success) {
|
|
3069
|
+
console.log(chalk4.green("\n+ Plan created successfully"));
|
|
3070
|
+
console.log(chalk4.dim(`Duration: ${result.duration}s`));
|
|
3071
|
+
console.log(chalk4.dim(`Saved to: .krenk/strategist.md`));
|
|
3072
|
+
console.log(chalk4.dim("\nRun `krenk build` to execute this plan."));
|
|
3073
|
+
} else {
|
|
3074
|
+
console.error(chalk4.red("\nx Planning failed"));
|
|
3075
|
+
if (result.output) {
|
|
3076
|
+
console.error(chalk4.dim(result.output.substring(0, 500)));
|
|
3077
|
+
}
|
|
3078
|
+
process.exit(1);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// src/commands/build.ts
|
|
3083
|
+
import * as fs3 from "fs";
|
|
3084
|
+
import * as path3 from "path";
|
|
3085
|
+
import chalk5 from "chalk";
|
|
3086
|
+
async function buildCommand() {
|
|
3087
|
+
const cwd = process.cwd();
|
|
3088
|
+
const planFile = path3.join(cwd, ".krenk", "strategist.md");
|
|
3089
|
+
if (!fs3.existsSync(planFile)) {
|
|
3090
|
+
console.error(
|
|
3091
|
+
chalk5.red(
|
|
3092
|
+
'No plan found. Run `krenk plan "your prompt"` first, or use `krenk run "prompt"` for the full pipeline.'
|
|
3093
|
+
)
|
|
3094
|
+
);
|
|
3095
|
+
process.exit(1);
|
|
3096
|
+
}
|
|
3097
|
+
const plan = fs3.readFileSync(planFile, "utf-8");
|
|
3098
|
+
console.log(chalk5.bold.cyan("\n# Building from existing plan...\n"));
|
|
3099
|
+
console.log(chalk5.dim(plan.substring(0, 200) + "...\n"));
|
|
3100
|
+
const config = await loadConfig(cwd);
|
|
3101
|
+
const engine = new OrchestrationEngine({
|
|
3102
|
+
cwd,
|
|
3103
|
+
maxParallel: config.maxParallelAgents,
|
|
3104
|
+
skipStages: ["planning"],
|
|
3105
|
+
// Skip planning since we have the plan
|
|
3106
|
+
noUi: true,
|
|
3107
|
+
supervised: false,
|
|
3108
|
+
agentConfig: config.agents
|
|
3109
|
+
});
|
|
3110
|
+
setupGracefulShutdown(engine);
|
|
3111
|
+
const result = await engine.run(
|
|
3112
|
+
`Execute the following plan:
|
|
3113
|
+
|
|
3114
|
+
${plan}`
|
|
3115
|
+
);
|
|
3116
|
+
if (result.success) {
|
|
3117
|
+
console.log(
|
|
3118
|
+
chalk5.green(
|
|
3119
|
+
`
|
|
3120
|
+
+ Build completed in ${formatDuration(result.duration)}`
|
|
3121
|
+
)
|
|
3122
|
+
);
|
|
3123
|
+
} else {
|
|
3124
|
+
console.error(chalk5.red("\nx Build failed"));
|
|
3125
|
+
process.exit(1);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// src/commands/test.ts
|
|
3130
|
+
import chalk6 from "chalk";
|
|
3131
|
+
async function testCommand() {
|
|
3132
|
+
const cwd = process.cwd();
|
|
3133
|
+
const config = await loadConfig(cwd);
|
|
3134
|
+
const role = ROLES.guardian;
|
|
3135
|
+
console.log(
|
|
3136
|
+
chalk6.bold.cyan(
|
|
3137
|
+
`
|
|
3138
|
+
${role.emoji} ${role.name} - Generating and running tests...
|
|
3139
|
+
`
|
|
3140
|
+
)
|
|
3141
|
+
);
|
|
3142
|
+
const result = await runAgent({
|
|
3143
|
+
role: "guardian",
|
|
3144
|
+
prompt: "Analyze this project, write comprehensive tests (unit + integration), and run them. Report results with PASS/FAIL status.",
|
|
3145
|
+
systemPrompt: role.systemPrompt,
|
|
3146
|
+
cwd,
|
|
3147
|
+
maxTurns: config.agents.guardian?.maxTurns || 50,
|
|
3148
|
+
allowedTools: role.allowedTools
|
|
3149
|
+
});
|
|
3150
|
+
if (result.success) {
|
|
3151
|
+
console.log(chalk6.green(`
|
|
3152
|
+
+ Testing completed in ${formatDuration(result.duration)}`));
|
|
3153
|
+
console.log(chalk6.dim("\nTest output:"));
|
|
3154
|
+
console.log(result.output.substring(0, 2e3));
|
|
3155
|
+
} else {
|
|
3156
|
+
console.error(chalk6.red(`
|
|
3157
|
+
x Testing failed after ${result.duration}s`));
|
|
3158
|
+
if (result.output) {
|
|
3159
|
+
console.error(chalk6.dim(result.output.substring(0, 1e3)));
|
|
3160
|
+
}
|
|
3161
|
+
process.exit(1);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// src/commands/review.ts
|
|
3166
|
+
import chalk7 from "chalk";
|
|
3167
|
+
async function reviewCommand() {
|
|
3168
|
+
const cwd = process.cwd();
|
|
3169
|
+
const config = await loadConfig(cwd);
|
|
3170
|
+
const role = ROLES.sentinel;
|
|
3171
|
+
console.log(
|
|
3172
|
+
chalk7.bold.cyan(
|
|
3173
|
+
`
|
|
3174
|
+
${role.emoji} ${role.name} - Reviewing code quality...
|
|
3175
|
+
`
|
|
3176
|
+
)
|
|
3177
|
+
);
|
|
3178
|
+
const result = await runAgent({
|
|
3179
|
+
role: "sentinel",
|
|
3180
|
+
prompt: "Review all code in this project for bugs, security issues, performance problems, and style inconsistencies. Output a structured review with severity levels.",
|
|
3181
|
+
systemPrompt: role.systemPrompt,
|
|
3182
|
+
cwd,
|
|
3183
|
+
maxTurns: config.agents.sentinel?.maxTurns || 30,
|
|
3184
|
+
allowedTools: role.allowedTools
|
|
3185
|
+
});
|
|
3186
|
+
if (result.success) {
|
|
3187
|
+
console.log(chalk7.green(`
|
|
3188
|
+
+ Review completed in ${formatDuration(result.duration)}`));
|
|
3189
|
+
console.log(chalk7.dim("\nReview output:"));
|
|
3190
|
+
console.log(result.output.substring(0, 3e3));
|
|
3191
|
+
} else {
|
|
3192
|
+
console.error(chalk7.red(`
|
|
3193
|
+
x Review failed after ${result.duration}s`));
|
|
3194
|
+
if (result.output) {
|
|
3195
|
+
console.error(chalk7.dim(result.output.substring(0, 1e3)));
|
|
3196
|
+
}
|
|
3197
|
+
process.exit(1);
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
// src/commands/init.ts
|
|
3202
|
+
import * as fs4 from "fs";
|
|
3203
|
+
import * as path4 from "path";
|
|
3204
|
+
import chalk8 from "chalk";
|
|
3205
|
+
async function initCommand() {
|
|
3206
|
+
const cwd = process.cwd();
|
|
3207
|
+
const krenkDir = path4.join(cwd, ".krenk");
|
|
3208
|
+
const rcFile = path4.join(cwd, ".krenkrc");
|
|
3209
|
+
console.log(chalk8.bold.cyan("\n> Initializing Krenk...\n"));
|
|
3210
|
+
if (!fs4.existsSync(krenkDir)) {
|
|
3211
|
+
fs4.mkdirSync(krenkDir, { recursive: true });
|
|
3212
|
+
console.log(chalk8.green(" + Created .krenk/ directory"));
|
|
3213
|
+
} else {
|
|
3214
|
+
console.log(chalk8.dim(" - .krenk/ directory already exists"));
|
|
3215
|
+
}
|
|
3216
|
+
const historyDir = path4.join(krenkDir, "history");
|
|
3217
|
+
if (!fs4.existsSync(historyDir)) {
|
|
3218
|
+
fs4.mkdirSync(historyDir, { recursive: true });
|
|
3219
|
+
console.log(chalk8.green(" + Created .krenk/history/ directory"));
|
|
3220
|
+
}
|
|
3221
|
+
if (!fs4.existsSync(rcFile)) {
|
|
3222
|
+
const config = {
|
|
3223
|
+
maxParallelAgents: DEFAULT_CONFIG.maxParallelAgents,
|
|
3224
|
+
claudePath: DEFAULT_CONFIG.claudePath,
|
|
3225
|
+
workflow: DEFAULT_CONFIG.workflow,
|
|
3226
|
+
skipStages: DEFAULT_CONFIG.skipStages,
|
|
3227
|
+
agents: {
|
|
3228
|
+
builder: { maxTurns: 100 },
|
|
3229
|
+
guardian: { maxTurns: 50 }
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
fs4.writeFileSync(rcFile, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3233
|
+
console.log(chalk8.green(" + Created .krenkrc config file"));
|
|
3234
|
+
} else {
|
|
3235
|
+
console.log(chalk8.dim(" - .krenkrc already exists"));
|
|
3236
|
+
}
|
|
3237
|
+
const gitignore = path4.join(cwd, ".gitignore");
|
|
3238
|
+
if (fs4.existsSync(gitignore)) {
|
|
3239
|
+
const content = fs4.readFileSync(gitignore, "utf-8");
|
|
3240
|
+
if (!content.includes(".krenk")) {
|
|
3241
|
+
fs4.appendFileSync(gitignore, "\n# Krenk orchestrator\n.krenk/\n");
|
|
3242
|
+
console.log(chalk8.green(" + Added .krenk/ to .gitignore"));
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
console.log(chalk8.green('\n[done] Krenk initialized! Run `krenk run "your prompt"` to start.\n'));
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// src/commands/status.ts
|
|
3249
|
+
import * as fs5 from "fs";
|
|
3250
|
+
import * as path5 from "path";
|
|
3251
|
+
import chalk9 from "chalk";
|
|
3252
|
+
async function statusCommand() {
|
|
3253
|
+
const cwd = process.cwd();
|
|
3254
|
+
const krenkDir = path5.join(cwd, ".krenk");
|
|
3255
|
+
if (!fs5.existsSync(krenkDir)) {
|
|
3256
|
+
console.log(
|
|
3257
|
+
chalk9.yellow(
|
|
3258
|
+
'\nNo .krenk/ directory found. Run `krenk init` or `krenk run "prompt"` first.\n'
|
|
3259
|
+
)
|
|
3260
|
+
);
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
console.log(chalk9.bold.cyan("\n> Krenk Status\n"));
|
|
3264
|
+
const stateFile = path5.join(krenkDir, "state.json");
|
|
3265
|
+
if (fs5.existsSync(stateFile)) {
|
|
3266
|
+
try {
|
|
3267
|
+
const state = JSON.parse(fs5.readFileSync(stateFile, "utf-8"));
|
|
3268
|
+
console.log(chalk9.white(" Last run:"));
|
|
3269
|
+
console.log(chalk9.dim(` Stage: ${state.stage || "unknown"}`));
|
|
3270
|
+
console.log(chalk9.dim(` Stages completed: ${state.stageCount || 0}`));
|
|
3271
|
+
if (state.duration) {
|
|
3272
|
+
console.log(chalk9.dim(` Duration: ${state.duration}s`));
|
|
3273
|
+
}
|
|
3274
|
+
if (state.runId) {
|
|
3275
|
+
console.log(chalk9.dim(` Run ID: ${state.runId}`));
|
|
3276
|
+
}
|
|
3277
|
+
console.log();
|
|
3278
|
+
} catch {
|
|
3279
|
+
console.log(chalk9.dim(" Could not parse state.json\n"));
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
console.log(chalk9.white(" Agent outputs:"));
|
|
3283
|
+
const roleKeys = Object.keys(ROLES);
|
|
3284
|
+
let hasOutputs = false;
|
|
3285
|
+
for (const key of roleKeys) {
|
|
3286
|
+
const file = path5.join(krenkDir, `${key}.md`);
|
|
3287
|
+
if (fs5.existsSync(file)) {
|
|
3288
|
+
const stat = fs5.statSync(file);
|
|
3289
|
+
const size = formatSize(stat.size);
|
|
3290
|
+
const modified = stat.mtime.toLocaleString();
|
|
3291
|
+
const role = ROLES[key];
|
|
3292
|
+
console.log(
|
|
3293
|
+
chalk9.dim(` ${role.emoji} ${role.name.padEnd(12)} ${size.padEnd(8)} ${modified}`)
|
|
3294
|
+
);
|
|
3295
|
+
hasOutputs = true;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if (!hasOutputs) {
|
|
3299
|
+
console.log(chalk9.dim(" No agent outputs found"));
|
|
3300
|
+
}
|
|
3301
|
+
const historyDir = path5.join(krenkDir, "history");
|
|
3302
|
+
if (fs5.existsSync(historyDir)) {
|
|
3303
|
+
const runs = fs5.readdirSync(historyDir).filter((d) => {
|
|
3304
|
+
return fs5.statSync(path5.join(historyDir, d)).isDirectory();
|
|
3305
|
+
});
|
|
3306
|
+
if (runs.length > 0) {
|
|
3307
|
+
console.log(chalk9.white(`
|
|
3308
|
+
History: ${runs.length} previous run(s)`));
|
|
3309
|
+
for (const run of runs.slice(-5)) {
|
|
3310
|
+
console.log(chalk9.dim(` ${run}`));
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
console.log();
|
|
3315
|
+
}
|
|
3316
|
+
function formatSize(bytes) {
|
|
3317
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
3318
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
3319
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/ui/interactive.ts
|
|
3323
|
+
import * as readline from "readline";
|
|
3324
|
+
import { spawn as spawnProcess } from "child_process";
|
|
3325
|
+
import chalk10 from "chalk";
|
|
3326
|
+
import gradient2 from "gradient-string";
|
|
3327
|
+
import figlet2 from "figlet";
|
|
3328
|
+
import boxen2 from "boxen";
|
|
3329
|
+
import ora2 from "ora";
|
|
3330
|
+
var TEAM_PRESETS = [
|
|
3331
|
+
{
|
|
3332
|
+
label: "Full Team",
|
|
3333
|
+
description: "All 11 agents - BA, plan, design, arch, code, QA, test, review, security, docs, devops",
|
|
3334
|
+
roles: ["analyst", "strategist", "designer", "architect", "builder", "qa", "guardian", "sentinel", "security", "scribe", "devops"],
|
|
3335
|
+
skipStages: []
|
|
3336
|
+
},
|
|
3337
|
+
{
|
|
3338
|
+
label: "Engineering",
|
|
3339
|
+
description: "Plan, architect, code, test, review",
|
|
3340
|
+
roles: ["strategist", "architect", "builder", "guardian", "sentinel"],
|
|
3341
|
+
skipStages: ["analyzing", "designing", "qa-planning", "securing", "documenting", "deploying"]
|
|
3342
|
+
},
|
|
3343
|
+
{
|
|
3344
|
+
label: "QA Focused",
|
|
3345
|
+
description: "BA, plan, code, QA, test, review -- quality first",
|
|
3346
|
+
roles: ["analyst", "strategist", "builder", "qa", "guardian", "sentinel"],
|
|
3347
|
+
skipStages: ["designing", "architecting", "securing", "documenting", "deploying"]
|
|
3348
|
+
},
|
|
3349
|
+
{
|
|
3350
|
+
label: "Startup MVP",
|
|
3351
|
+
description: "BA, plan, architect, code, review, docs -- ship fast",
|
|
3352
|
+
roles: ["analyst", "strategist", "architect", "builder", "sentinel", "scribe"],
|
|
3353
|
+
skipStages: ["designing", "qa-planning", "testing", "securing", "deploying"]
|
|
3354
|
+
},
|
|
3355
|
+
{
|
|
3356
|
+
label: "Enterprise",
|
|
3357
|
+
description: "BA, plan, design, arch, code, QA, test, security, review, docs, devops",
|
|
3358
|
+
roles: ["analyst", "strategist", "designer", "architect", "builder", "qa", "guardian", "sentinel", "security", "scribe", "devops"],
|
|
3359
|
+
skipStages: []
|
|
3360
|
+
},
|
|
3361
|
+
{
|
|
3362
|
+
label: "Quick Build",
|
|
3363
|
+
description: "Plan and code only -- fastest path",
|
|
3364
|
+
roles: ["strategist", "builder"],
|
|
3365
|
+
skipStages: ["analyzing", "designing", "architecting", "qa-planning", "testing", "reviewing", "securing", "documenting", "deploying"]
|
|
3366
|
+
},
|
|
3367
|
+
{
|
|
3368
|
+
label: "Custom",
|
|
3369
|
+
description: "Pick your agents",
|
|
3370
|
+
roles: [],
|
|
3371
|
+
skipStages: []
|
|
3372
|
+
}
|
|
3373
|
+
];
|
|
3374
|
+
var ALL_AGENTS = [
|
|
3375
|
+
{ key: "analyst", label: "Analyst (BA)", desc: "Business analysis & user stories" },
|
|
3376
|
+
{ key: "strategist", label: "Strategist", desc: "Requirements & planning" },
|
|
3377
|
+
{ key: "designer", label: "Pixel", desc: "UI/UX design" },
|
|
3378
|
+
{ key: "architect", label: "Blueprint", desc: "System architecture" },
|
|
3379
|
+
{ key: "builder", label: "Builder", desc: "Code implementation" },
|
|
3380
|
+
{ key: "qa", label: "QA Lead", desc: "Test strategy & test plans" },
|
|
3381
|
+
{ key: "guardian", label: "Guardian", desc: "Test execution" },
|
|
3382
|
+
{ key: "sentinel", label: "Sentinel", desc: "Code review" },
|
|
3383
|
+
{ key: "security", label: "Shield", desc: "Security audit" },
|
|
3384
|
+
{ key: "scribe", label: "Scribe", desc: "Documentation" },
|
|
3385
|
+
{ key: "devops", label: "DevOps", desc: "CI/CD & deployment" }
|
|
3386
|
+
];
|
|
3387
|
+
function renderMenu(items, selected, multi, checked) {
|
|
3388
|
+
for (let i = 0; i < items.length; i++) {
|
|
3389
|
+
const isSelected = i === selected;
|
|
3390
|
+
const pointer = isSelected ? chalk10.hex(THEME.primary)(">") : " ";
|
|
3391
|
+
const label = isSelected ? chalk10.bold.white(items[i].label.padEnd(14)) : chalk10.white(items[i].label.padEnd(14));
|
|
3392
|
+
const desc = chalk10.dim(items[i].description);
|
|
3393
|
+
let check = "";
|
|
3394
|
+
if (multi && checked) {
|
|
3395
|
+
check = checked.has(i) ? chalk10.hex(THEME.primary)(" [x] ") : chalk10.dim(" [ ] ");
|
|
3396
|
+
}
|
|
3397
|
+
process.stdout.write(` ${pointer}${check} ${label} ${desc}
|
|
3398
|
+
`);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
function clearMenu(count) {
|
|
3402
|
+
for (let i = 0; i < count; i++) {
|
|
3403
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
function arrowSelect(items) {
|
|
3407
|
+
return new Promise((resolve) => {
|
|
3408
|
+
let selected = 0;
|
|
3409
|
+
renderMenu(items, selected);
|
|
3410
|
+
const wasRaw = process.stdin.isRaw;
|
|
3411
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
3412
|
+
process.stdin.resume();
|
|
3413
|
+
const onKey = (key) => {
|
|
3414
|
+
const str = key.toString();
|
|
3415
|
+
if (str === "") {
|
|
3416
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3417
|
+
process.stdin.pause();
|
|
3418
|
+
process.stdin.removeListener("data", onKey);
|
|
3419
|
+
process.exit(0);
|
|
3420
|
+
}
|
|
3421
|
+
if (str === "\x1B[A" && selected > 0) {
|
|
3422
|
+
selected--;
|
|
3423
|
+
clearMenu(items.length);
|
|
3424
|
+
renderMenu(items, selected);
|
|
3425
|
+
}
|
|
3426
|
+
if (str === "\x1B[B" && selected < items.length - 1) {
|
|
3427
|
+
selected++;
|
|
3428
|
+
clearMenu(items.length);
|
|
3429
|
+
renderMenu(items, selected);
|
|
3430
|
+
}
|
|
3431
|
+
if (str === "\r" || str === "\n") {
|
|
3432
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3433
|
+
process.stdin.pause();
|
|
3434
|
+
process.stdin.removeListener("data", onKey);
|
|
3435
|
+
clearMenu(items.length);
|
|
3436
|
+
const item = items[selected];
|
|
3437
|
+
console.log(` ${chalk10.hex(THEME.primary)(">")} ${chalk10.bold.white(item.label)}`);
|
|
3438
|
+
resolve(selected);
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
process.stdin.on("data", onKey);
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
function arrowMultiSelect(items) {
|
|
3445
|
+
return new Promise((resolve) => {
|
|
3446
|
+
let selected = 0;
|
|
3447
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3448
|
+
renderMenu(items, selected, true, checked);
|
|
3449
|
+
const wasRaw = process.stdin.isRaw;
|
|
3450
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
3451
|
+
process.stdin.resume();
|
|
3452
|
+
const onKey = (key) => {
|
|
3453
|
+
const str = key.toString();
|
|
3454
|
+
if (str === "") {
|
|
3455
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3456
|
+
process.stdin.pause();
|
|
3457
|
+
process.stdin.removeListener("data", onKey);
|
|
3458
|
+
process.exit(0);
|
|
3459
|
+
}
|
|
3460
|
+
if (str === "\x1B[A" && selected > 0) {
|
|
3461
|
+
selected--;
|
|
3462
|
+
clearMenu(items.length);
|
|
3463
|
+
renderMenu(items, selected, true, checked);
|
|
3464
|
+
}
|
|
3465
|
+
if (str === "\x1B[B" && selected < items.length - 1) {
|
|
3466
|
+
selected++;
|
|
3467
|
+
clearMenu(items.length);
|
|
3468
|
+
renderMenu(items, selected, true, checked);
|
|
3469
|
+
}
|
|
3470
|
+
if (str === " ") {
|
|
3471
|
+
if (checked.has(selected)) {
|
|
3472
|
+
checked.delete(selected);
|
|
3473
|
+
} else {
|
|
3474
|
+
checked.add(selected);
|
|
3475
|
+
}
|
|
3476
|
+
clearMenu(items.length);
|
|
3477
|
+
renderMenu(items, selected, true, checked);
|
|
3478
|
+
}
|
|
3479
|
+
if (str === "\r" || str === "\n") {
|
|
3480
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3481
|
+
process.stdin.pause();
|
|
3482
|
+
process.stdin.removeListener("data", onKey);
|
|
3483
|
+
clearMenu(items.length);
|
|
3484
|
+
const picks = Array.from(checked).sort();
|
|
3485
|
+
if (picks.length > 0) {
|
|
3486
|
+
const names = picks.map((i) => items[i].label).join(", ");
|
|
3487
|
+
console.log(` ${chalk10.hex(THEME.primary)(">")} ${chalk10.bold.white(names)}`);
|
|
3488
|
+
}
|
|
3489
|
+
resolve(picks);
|
|
3490
|
+
}
|
|
3491
|
+
};
|
|
3492
|
+
process.stdin.on("data", onKey);
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
function ask(rl, question) {
|
|
3496
|
+
return new Promise((resolve) => {
|
|
3497
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
function printBanner() {
|
|
3501
|
+
console.log("\n\n");
|
|
3502
|
+
const ascii = figlet2.textSync("KRENK", { font: "ANSI Shadow" });
|
|
3503
|
+
console.log(gradient2(THEME.gradient)(ascii));
|
|
3504
|
+
console.log(chalk10.dim(" like claude code, but with a team"));
|
|
3505
|
+
console.log(chalk10.dim(" " + "-".repeat(50)));
|
|
3506
|
+
console.log(chalk10.dim(" built by Krishna with mass amount of \u2615"));
|
|
3507
|
+
console.log();
|
|
3508
|
+
}
|
|
3509
|
+
function resolveSkipStages(selectedRoles) {
|
|
3510
|
+
const roleToStage = {
|
|
3511
|
+
analyst: "analyzing",
|
|
3512
|
+
strategist: "planning",
|
|
3513
|
+
designer: "designing",
|
|
3514
|
+
architect: "architecting",
|
|
3515
|
+
builder: "coding",
|
|
3516
|
+
qa: "qa-planning",
|
|
3517
|
+
guardian: "testing",
|
|
3518
|
+
sentinel: "reviewing",
|
|
3519
|
+
security: "securing",
|
|
3520
|
+
scribe: "documenting",
|
|
3521
|
+
devops: "deploying"
|
|
3522
|
+
};
|
|
3523
|
+
const skip = [];
|
|
3524
|
+
for (const [role, stage] of Object.entries(roleToStage)) {
|
|
3525
|
+
if (!selectedRoles.includes(role)) {
|
|
3526
|
+
skip.push(stage);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
return skip;
|
|
3530
|
+
}
|
|
3531
|
+
function detectFollowUps(task) {
|
|
3532
|
+
const lower = task.toLowerCase();
|
|
3533
|
+
const followUps = [];
|
|
3534
|
+
const webKeywords = ["web", "frontend", "ui", "website", "dashboard", "app", "page", "component", "react", "vue", "svelte", "next"];
|
|
3535
|
+
const backendKeywords = ["backend", "api", "server", "rest", "graphql", "endpoint", "microservice"];
|
|
3536
|
+
const dbKeywords = ["database", "db", "data", "store", "crud", "model", "schema", "postgres", "mongo", "sql"];
|
|
3537
|
+
if (webKeywords.some((kw) => lower.includes(kw))) {
|
|
3538
|
+
followUps.push({
|
|
3539
|
+
question: "What framework? (react/vue/svelte/skip) ",
|
|
3540
|
+
key: "framework"
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
if (backendKeywords.some((kw) => lower.includes(kw))) {
|
|
3544
|
+
followUps.push({
|
|
3545
|
+
question: "What runtime? (node/python/go/skip) ",
|
|
3546
|
+
key: "runtime"
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
if (dbKeywords.some((kw) => lower.includes(kw))) {
|
|
3550
|
+
followUps.push({
|
|
3551
|
+
question: "What database? (postgres/mongo/sqlite/skip) ",
|
|
3552
|
+
key: "database"
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
if (followUps.length === 0) {
|
|
3556
|
+
followUps.push({
|
|
3557
|
+
question: "Any tech stack or constraints? (press enter to skip) ",
|
|
3558
|
+
key: "constraints"
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
return followUps.slice(0, 2);
|
|
3562
|
+
}
|
|
3563
|
+
async function refinePromptWithClaude(rawTask, answers, roles, spinner) {
|
|
3564
|
+
const agentNames = roles.map((r) => ROLES[r]?.name || r);
|
|
3565
|
+
let answerContext = "";
|
|
3566
|
+
if (answers.framework) answerContext += `
|
|
3567
|
+
Framework: ${answers.framework}`;
|
|
3568
|
+
if (answers.runtime) answerContext += `
|
|
3569
|
+
Runtime: ${answers.runtime}`;
|
|
3570
|
+
if (answers.database) answerContext += `
|
|
3571
|
+
Database: ${answers.database}`;
|
|
3572
|
+
if (answers.constraints) answerContext += `
|
|
3573
|
+
Constraints: ${answers.constraints}`;
|
|
3574
|
+
const refinementPrompt = `You are a prompt refinement assistant. Take the user's raw input and produce a clean, structured project prompt.
|
|
3575
|
+
|
|
3576
|
+
Raw user input: "${rawTask}"
|
|
3577
|
+
${answerContext ? `
|
|
3578
|
+
User preferences:${answerContext}` : ""}
|
|
3579
|
+
Team: ${agentNames.join(", ")}
|
|
3580
|
+
|
|
3581
|
+
Respond ONLY with valid JSON (no markdown, no code fences, no explanation) in this exact format:
|
|
3582
|
+
{
|
|
3583
|
+
"title": "A clear, concise 1-sentence project description",
|
|
3584
|
+
"requirements": ["requirement 1", "requirement 2", ...],
|
|
3585
|
+
"techStack": ["tech1", "tech2", ...],
|
|
3586
|
+
"fullPrompt": "The complete, well-structured prompt for the engineering team to execute. Include the title, all requirements, tech stack, and any constraints. Be specific and actionable."
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
Rules:
|
|
3590
|
+
- title: Clean up the raw input into a professional project description
|
|
3591
|
+
- requirements: Extract implicit AND explicit requirements (auth, responsive, real-time, etc). Include 3-8 requirements.
|
|
3592
|
+
- techStack: Infer technologies from context, user answers, and task. If unclear, suggest sensible defaults.
|
|
3593
|
+
- fullPrompt: This is what the engineering team will actually read. Make it detailed, actionable, and clear. Include all context.`;
|
|
3594
|
+
try {
|
|
3595
|
+
const env = { ...process.env };
|
|
3596
|
+
delete env.CLAUDECODE;
|
|
3597
|
+
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
3598
|
+
const output = await new Promise((resolve, reject) => {
|
|
3599
|
+
const phases = [
|
|
3600
|
+
"Understanding your request...",
|
|
3601
|
+
"Extracting requirements...",
|
|
3602
|
+
"Identifying tech stack...",
|
|
3603
|
+
"Structuring the prompt...",
|
|
3604
|
+
"Finalizing..."
|
|
3605
|
+
];
|
|
3606
|
+
let phaseIdx = 0;
|
|
3607
|
+
const phaseTimer = setInterval(() => {
|
|
3608
|
+
phaseIdx++;
|
|
3609
|
+
if (spinner && phaseIdx < phases.length) {
|
|
3610
|
+
spinner.text = phases[phaseIdx];
|
|
3611
|
+
}
|
|
3612
|
+
}, 3e3);
|
|
3613
|
+
const child = spawnProcess("claude", [
|
|
3614
|
+
"-p",
|
|
3615
|
+
refinementPrompt,
|
|
3616
|
+
"--output-format",
|
|
3617
|
+
"stream-json",
|
|
3618
|
+
"--verbose",
|
|
3619
|
+
"--max-turns",
|
|
3620
|
+
"1",
|
|
3621
|
+
"--dangerously-skip-permissions"
|
|
3622
|
+
], {
|
|
3623
|
+
env,
|
|
3624
|
+
cwd: process.cwd(),
|
|
3625
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3626
|
+
});
|
|
3627
|
+
let stdout = "";
|
|
3628
|
+
let stderr = "";
|
|
3629
|
+
let lineBuffer = "";
|
|
3630
|
+
child.stdout.on("data", (chunk) => {
|
|
3631
|
+
const text = chunk.toString();
|
|
3632
|
+
stdout += text;
|
|
3633
|
+
lineBuffer += text;
|
|
3634
|
+
const lines = lineBuffer.split("\n");
|
|
3635
|
+
lineBuffer = lines.pop() || "";
|
|
3636
|
+
for (const line of lines) {
|
|
3637
|
+
const trimmed = line.trim();
|
|
3638
|
+
if (!trimmed) continue;
|
|
3639
|
+
if (spinner && phaseIdx < phases.length) {
|
|
3640
|
+
spinner.text = phases[phaseIdx];
|
|
3641
|
+
phaseIdx++;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3645
|
+
child.stderr.on("data", (chunk) => {
|
|
3646
|
+
stderr += chunk.toString();
|
|
3647
|
+
});
|
|
3648
|
+
const timeout = setTimeout(() => {
|
|
3649
|
+
clearInterval(phaseTimer);
|
|
3650
|
+
child.kill("SIGTERM");
|
|
3651
|
+
reject(new Error("timeout"));
|
|
3652
|
+
}, 6e4);
|
|
3653
|
+
child.on("close", (code) => {
|
|
3654
|
+
clearInterval(phaseTimer);
|
|
3655
|
+
clearTimeout(timeout);
|
|
3656
|
+
if (code === 0 && stdout.trim()) {
|
|
3657
|
+
resolve(stdout);
|
|
3658
|
+
} else {
|
|
3659
|
+
reject(new Error(`exit ${code}: ${stderr.slice(0, 200)}`));
|
|
3660
|
+
}
|
|
3661
|
+
});
|
|
3662
|
+
child.on("error", (err) => {
|
|
3663
|
+
clearInterval(phaseTimer);
|
|
3664
|
+
clearTimeout(timeout);
|
|
3665
|
+
reject(err);
|
|
3666
|
+
});
|
|
3667
|
+
});
|
|
3668
|
+
let responseText = "";
|
|
3669
|
+
const outputLines = output.trim().split("\n");
|
|
3670
|
+
for (let i = outputLines.length - 1; i >= 0; i--) {
|
|
3671
|
+
try {
|
|
3672
|
+
const event = JSON.parse(outputLines[i]);
|
|
3673
|
+
if (event.type === "result" && event.result) {
|
|
3674
|
+
responseText = event.result;
|
|
3675
|
+
break;
|
|
3676
|
+
}
|
|
3677
|
+
} catch {
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
if (!responseText) {
|
|
3681
|
+
try {
|
|
3682
|
+
const wrapper = JSON.parse(output.trim());
|
|
3683
|
+
if (wrapper.result) responseText = wrapper.result;
|
|
3684
|
+
else responseText = output.trim();
|
|
3685
|
+
} catch {
|
|
3686
|
+
responseText = output.trim();
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
responseText = responseText.replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
3690
|
+
const parsed = JSON.parse(responseText);
|
|
3691
|
+
return {
|
|
3692
|
+
title: parsed.title || rawTask,
|
|
3693
|
+
requirements: Array.isArray(parsed.requirements) ? parsed.requirements : [],
|
|
3694
|
+
techStack: Array.isArray(parsed.techStack) ? parsed.techStack : [],
|
|
3695
|
+
fullPrompt: parsed.fullPrompt || rawTask
|
|
3696
|
+
};
|
|
3697
|
+
} catch {
|
|
3698
|
+
return refinePromptLocal(rawTask, answers, roles);
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
function refinePromptLocal(rawTask, answers, roles) {
|
|
3702
|
+
let title = rawTask.trim();
|
|
3703
|
+
title = title.charAt(0).toUpperCase() + title.slice(1);
|
|
3704
|
+
if (!title.endsWith(".") && !title.endsWith("!") && !title.endsWith("?")) {
|
|
3705
|
+
title += ".";
|
|
3706
|
+
}
|
|
3707
|
+
const requirements = [];
|
|
3708
|
+
const lower = rawTask.toLowerCase();
|
|
3709
|
+
if (lower.includes("auth") || lower.includes("login")) requirements.push("User authentication");
|
|
3710
|
+
if (lower.includes("responsive") || lower.includes("mobile")) requirements.push("Responsive design");
|
|
3711
|
+
if (lower.includes("realtime") || lower.includes("real-time")) requirements.push("Real-time updates");
|
|
3712
|
+
if (lower.includes("deploy") || lower.includes("production")) requirements.push("Production-ready");
|
|
3713
|
+
if (lower.includes("test")) requirements.push("Test coverage");
|
|
3714
|
+
const techStack = [];
|
|
3715
|
+
if (answers.framework) techStack.push(answers.framework);
|
|
3716
|
+
if (answers.runtime) techStack.push(answers.runtime);
|
|
3717
|
+
if (answers.database) techStack.push(answers.database);
|
|
3718
|
+
if (answers.constraints) {
|
|
3719
|
+
const c = answers.constraints.toLowerCase();
|
|
3720
|
+
const knownTech = ["react", "vue", "svelte", "angular", "next", "nuxt", "node", "express", "python", "django", "flask", "go", "rust", "postgres", "mongodb", "sqlite", "redis", "tailwind", "typescript"];
|
|
3721
|
+
for (const tech of knownTech) {
|
|
3722
|
+
if (c.includes(tech) && !techStack.some((t) => t.toLowerCase().includes(tech))) {
|
|
3723
|
+
techStack.push(tech.charAt(0).toUpperCase() + tech.slice(1));
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
if (techStack.length === 0) {
|
|
3727
|
+
techStack.push(answers.constraints);
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
if (lower.includes("drag") && lower.includes("drop")) requirements.push("Drag and drop functionality");
|
|
3731
|
+
if (lower.includes("todo") || lower.includes("task")) requirements.push("Task management");
|
|
3732
|
+
if (lower.includes("column") || lower.includes("kanban") || lower.includes("board")) requirements.push("Column/board layout");
|
|
3733
|
+
if (lower.includes("status")) requirements.push("Status tracking");
|
|
3734
|
+
const agentNames = roles.map((r) => ROLES[r]?.name || r);
|
|
3735
|
+
let fullPrompt = title;
|
|
3736
|
+
if (requirements.length > 0) {
|
|
3737
|
+
fullPrompt += "\n\nRequirements:\n" + requirements.map((r) => `- ${r}`).join("\n");
|
|
3738
|
+
}
|
|
3739
|
+
if (techStack.length > 0) {
|
|
3740
|
+
fullPrompt += "\n\nTech stack: " + techStack.join(", ") + ".";
|
|
3741
|
+
}
|
|
3742
|
+
fullPrompt += `
|
|
3743
|
+
|
|
3744
|
+
Team: ${agentNames.join(", ")}.`;
|
|
3745
|
+
return { title, requirements, techStack, fullPrompt };
|
|
3746
|
+
}
|
|
3747
|
+
async function startInteractiveSession() {
|
|
3748
|
+
printBanner();
|
|
3749
|
+
console.log(chalk10.bold.white(" Select your team:"));
|
|
3750
|
+
console.log(chalk10.dim(" (use arrow keys, press enter to select)\n"));
|
|
3751
|
+
const teamItems = TEAM_PRESETS.map((p) => ({
|
|
3752
|
+
label: p.label,
|
|
3753
|
+
description: p.description
|
|
3754
|
+
}));
|
|
3755
|
+
const teamIndex = await arrowSelect(teamItems);
|
|
3756
|
+
let skipStages;
|
|
3757
|
+
let selectedRoles;
|
|
3758
|
+
if (teamIndex < TEAM_PRESETS.length - 1) {
|
|
3759
|
+
const preset = TEAM_PRESETS[teamIndex];
|
|
3760
|
+
skipStages = preset.skipStages;
|
|
3761
|
+
selectedRoles = preset.roles;
|
|
3762
|
+
} else {
|
|
3763
|
+
console.log(chalk10.bold.white("\n Pick your agents:"));
|
|
3764
|
+
console.log(chalk10.dim(" (space to toggle, enter to confirm)\n"));
|
|
3765
|
+
const agentItems = ALL_AGENTS.map((a) => ({
|
|
3766
|
+
label: a.label,
|
|
3767
|
+
description: a.desc
|
|
3768
|
+
}));
|
|
3769
|
+
const picks = await arrowMultiSelect(agentItems);
|
|
3770
|
+
if (picks.length === 0) {
|
|
3771
|
+
selectedRoles = TEAM_PRESETS[0].roles;
|
|
3772
|
+
skipStages = [];
|
|
3773
|
+
console.log(chalk10.dim("\n No selection, using Full Team"));
|
|
3774
|
+
} else {
|
|
3775
|
+
selectedRoles = picks.map((i) => ALL_AGENTS[i].key);
|
|
3776
|
+
if (!selectedRoles.includes("strategist")) selectedRoles.unshift("strategist");
|
|
3777
|
+
if (!selectedRoles.includes("builder")) selectedRoles.push("builder");
|
|
3778
|
+
skipStages = resolveSkipStages(selectedRoles);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
console.log(chalk10.bold.white("\n Run mode:"));
|
|
3782
|
+
console.log(chalk10.dim(" (use arrow keys, press enter to select)\n"));
|
|
3783
|
+
const modeItems = [
|
|
3784
|
+
{ label: "Autonomous", description: "Agents run without asking -- fastest" },
|
|
3785
|
+
{ label: "Supervised", description: "Approve each agent before it runs -- you stay in control" }
|
|
3786
|
+
];
|
|
3787
|
+
const modeIndex = await arrowSelect(modeItems);
|
|
3788
|
+
const supervised = modeIndex === 1;
|
|
3789
|
+
console.log();
|
|
3790
|
+
const rl = readline.createInterface({
|
|
3791
|
+
input: process.stdin,
|
|
3792
|
+
output: process.stdout
|
|
3793
|
+
});
|
|
3794
|
+
const task = await ask(rl, chalk10.bold.white(" What do you want to build? ") + chalk10.hex(THEME.primary)("> "));
|
|
3795
|
+
if (!task) {
|
|
3796
|
+
console.log(chalk10.dim("\n No task provided. Exiting.\n"));
|
|
3797
|
+
rl.close();
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
const followUps = detectFollowUps(task);
|
|
3801
|
+
const answers = {};
|
|
3802
|
+
console.log();
|
|
3803
|
+
for (const fu of followUps) {
|
|
3804
|
+
const answer = await ask(rl, chalk10.dim(" ") + chalk10.white(fu.question));
|
|
3805
|
+
if (answer && answer.toLowerCase() !== "skip") {
|
|
3806
|
+
answers[fu.key] = answer;
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
rl.close();
|
|
3810
|
+
console.log();
|
|
3811
|
+
const refineSpinner = ora2({
|
|
3812
|
+
text: "Understanding your request...",
|
|
3813
|
+
prefixText: " ",
|
|
3814
|
+
spinner: "dots"
|
|
3815
|
+
}).start();
|
|
3816
|
+
const refined = await refinePromptWithClaude(task, answers, selectedRoles, refineSpinner);
|
|
3817
|
+
refineSpinner.succeed("Prompt refined");
|
|
3818
|
+
console.log();
|
|
3819
|
+
const teamNames = selectedRoles.map((r) => ROLES[r]?.name || r).join(", ");
|
|
3820
|
+
const lines = [];
|
|
3821
|
+
lines.push(chalk10.bold.hex(THEME.primary)(" Refined Prompt"));
|
|
3822
|
+
lines.push("");
|
|
3823
|
+
lines.push(chalk10.white(` ${refined.title}`));
|
|
3824
|
+
lines.push("");
|
|
3825
|
+
if (refined.requirements.length > 0) {
|
|
3826
|
+
lines.push(chalk10.dim(" Requirements:"));
|
|
3827
|
+
for (const req of refined.requirements) {
|
|
3828
|
+
lines.push(chalk10.dim(` - ${req}`));
|
|
3829
|
+
}
|
|
3830
|
+
lines.push("");
|
|
3831
|
+
}
|
|
3832
|
+
if (refined.techStack.length > 0) {
|
|
3833
|
+
lines.push(chalk10.dim(" Tech Stack:"));
|
|
3834
|
+
lines.push(chalk10.dim(` ${refined.techStack.join(" + ")}`));
|
|
3835
|
+
lines.push("");
|
|
3836
|
+
}
|
|
3837
|
+
lines.push(chalk10.dim(` Team: ${teamNames}`));
|
|
3838
|
+
lines.push(chalk10.dim(` Mode: ${supervised ? "Supervised" : "Autonomous"}`));
|
|
3839
|
+
const panel = boxen2(lines.join("\n"), {
|
|
3840
|
+
padding: 1,
|
|
3841
|
+
margin: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
3842
|
+
borderStyle: "round",
|
|
3843
|
+
borderColor: "#7C3AED"
|
|
3844
|
+
});
|
|
3845
|
+
console.log(panel);
|
|
3846
|
+
console.log();
|
|
3847
|
+
const cwd = process.cwd();
|
|
3848
|
+
const config = await loadConfig(cwd);
|
|
3849
|
+
const engine = new OrchestrationEngine({
|
|
3850
|
+
cwd,
|
|
3851
|
+
maxParallel: config.maxParallelAgents,
|
|
3852
|
+
skipStages,
|
|
3853
|
+
noUi: false,
|
|
3854
|
+
supervised,
|
|
3855
|
+
agentConfig: config.agents
|
|
3856
|
+
});
|
|
3857
|
+
const renderer = new TerminalRenderer(engine);
|
|
3858
|
+
setupGracefulShutdown(engine, () => renderer.cleanup());
|
|
3859
|
+
const result = await engine.run(refined.fullPrompt);
|
|
3860
|
+
renderer.printSummary(result);
|
|
3861
|
+
if (!result.success) {
|
|
3862
|
+
process.exit(1);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
// src/index.ts
|
|
3867
|
+
program.name("krenk").description(
|
|
3868
|
+
"Multi-agent software engineering orchestrator powered by Claude Code"
|
|
3869
|
+
).version("0.1.0").action(async () => {
|
|
3870
|
+
await startInteractiveSession();
|
|
3871
|
+
});
|
|
3872
|
+
program.command("run").description("Run full engineering workflow with all agents").argument("<prompt>", "What to build").option("--skip <stages...>", "Skip specific stages (e.g. --skip design test)").option("--parallel <n>", "Max parallel agents", "3").option("--no-ui", "Disable fancy UI, use plain output").option("--supervised", "Approve each agent before it runs").action(runCommand);
|
|
3873
|
+
program.command("plan").description("Run only the planning stage").argument("<prompt>", "What to plan").action(planCommand);
|
|
3874
|
+
program.command("build").description("Execute from an existing plan in .krenk/strategist.md").action(buildCommand);
|
|
3875
|
+
program.command("test").description("Generate and run tests for the current project").action(testCommand);
|
|
3876
|
+
program.command("review").description("Run code review on the current project").action(reviewCommand);
|
|
3877
|
+
program.command("init").description("Initialize Krenk in the current directory").action(initCommand);
|
|
3878
|
+
program.command("status").description("Show the current Krenk state and last run info").action(statusCommand);
|
|
3879
|
+
program.parse();
|
|
3880
|
+
//# sourceMappingURL=index.js.map
|