karajan-code 1.15.0 → 1.17.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/package.json +1 -1
- package/src/activity-log.js +13 -13
- package/src/agents/availability.js +2 -3
- package/src/agents/claude-agent.js +42 -21
- package/src/agents/model-registry.js +1 -1
- package/src/becaria/dispatch.js +1 -1
- package/src/becaria/repo.js +3 -3
- package/src/cli.js +6 -2
- package/src/commands/doctor.js +154 -108
- package/src/commands/init.js +101 -90
- package/src/commands/plan.js +1 -1
- package/src/commands/report.js +77 -71
- package/src/commands/roles.js +0 -1
- package/src/commands/run.js +2 -3
- package/src/config.js +157 -89
- package/src/git/automation.js +3 -4
- package/src/guards/policy-resolver.js +3 -3
- package/src/mcp/orphan-guard.js +1 -2
- package/src/mcp/progress.js +4 -3
- package/src/mcp/run-kj.js +2 -0
- package/src/mcp/server-handlers.js +294 -241
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +19 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +266 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +820 -739
- package/src/planning-game/adapter.js +23 -20
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +15 -1
- package/src/planning-game/decomposition.js +7 -5
- package/src/prompts/architect.js +88 -0
- package/src/prompts/discover.js +228 -0
- package/src/prompts/planner.js +53 -33
- package/src/prompts/triage.js +8 -16
- package/src/review/parser.js +18 -19
- package/src/review/profiles.js +2 -2
- package/src/review/schema.js +3 -3
- package/src/review/scope-filter.js +3 -4
- package/src/roles/architect-role.js +122 -0
- package/src/roles/commiter-role.js +2 -2
- package/src/roles/discover-role.js +122 -0
- package/src/roles/index.js +2 -0
- package/src/roles/planner-role.js +54 -38
- package/src/roles/refactorer-role.js +8 -7
- package/src/roles/researcher-role.js +6 -7
- package/src/roles/reviewer-role.js +4 -5
- package/src/roles/security-role.js +3 -4
- package/src/roles/solomon-role.js +6 -18
- package/src/roles/sonar-role.js +5 -1
- package/src/roles/tester-role.js +8 -5
- package/src/roles/triage-role.js +2 -2
- package/src/session-cleanup.js +29 -24
- package/src/session-store.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/manager.js +1 -1
- package/src/sonar/project-key.js +5 -5
- package/src/sonar/scanner.js +34 -65
- package/src/utils/display.js +312 -272
- package/src/utils/git.js +3 -3
- package/src/utils/logger.js +6 -1
- package/src/utils/model-selector.js +5 -5
- package/src/utils/process.js +80 -102
- package/src/utils/rate-limit-detector.js +13 -13
- package/src/utils/run-log.js +55 -52
- package/templates/roles/architect.md +62 -0
- package/templates/roles/discover.md +167 -0
- package/templates/roles/planner.md +1 -0
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import { TriageRole } from "../roles/triage-role.js";
|
|
2
2
|
import { ResearcherRole } from "../roles/researcher-role.js";
|
|
3
3
|
import { PlannerRole } from "../roles/planner-role.js";
|
|
4
|
+
import { DiscoverRole } from "../roles/discover-role.js";
|
|
5
|
+
import { ArchitectRole } from "../roles/architect-role.js";
|
|
4
6
|
import { createAgent } from "../agents/index.js";
|
|
7
|
+
import { createArchitectADRs } from "../planning-game/architect-adrs.js";
|
|
5
8
|
import { addCheckpoint, markSessionStatus } from "../session-store.js";
|
|
6
9
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
7
10
|
import { parsePlannerOutput } from "../prompts/planner.js";
|
|
8
11
|
import { selectModelsForRoles } from "../utils/model-selector.js";
|
|
9
12
|
import { createStallDetector } from "../utils/stall-detector.js";
|
|
10
13
|
|
|
14
|
+
const ROLE_NAMES = ["planner", "researcher", "architect", "refactorer", "reviewer", "tester", "security"];
|
|
15
|
+
|
|
16
|
+
function buildRoleOverrides(recommendedRoles, pipelineConfig) {
|
|
17
|
+
const overrides = {};
|
|
18
|
+
for (const role of ROLE_NAMES) {
|
|
19
|
+
overrides[`${role}Enabled`] = recommendedRoles.has(role) || Boolean(pipelineConfig[role]?.enabled);
|
|
20
|
+
}
|
|
21
|
+
return overrides;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function applyModelSelection(triageOutput, config, emitter, eventBase) {
|
|
25
|
+
if (!triageOutput.ok || !config?.model_selection?.enabled) return null;
|
|
26
|
+
const level = triageOutput.result?.level;
|
|
27
|
+
if (!level) return null;
|
|
28
|
+
|
|
29
|
+
const { modelOverrides, reasoning } = selectModelsForRoles({ level, config });
|
|
30
|
+
for (const [role, model] of Object.entries(modelOverrides)) {
|
|
31
|
+
if (config.roles?.[role] && !config.roles[role].model) {
|
|
32
|
+
config.roles[role].model = model;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const modelSelection = { modelOverrides, reasoning };
|
|
36
|
+
emitProgress(
|
|
37
|
+
emitter,
|
|
38
|
+
makeEvent("model-selection:applied", { ...eventBase, stage: "triage" }, {
|
|
39
|
+
message: "Smart model selection applied",
|
|
40
|
+
detail: modelSelection
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
return modelSelection;
|
|
44
|
+
}
|
|
45
|
+
|
|
11
46
|
export async function runTriageStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
|
|
12
47
|
logger.setContext({ iteration: 0, stage: "triage" });
|
|
13
48
|
emitProgress(
|
|
@@ -54,17 +89,9 @@ export async function runTriageStage({ config, logger, emitter, eventBase, sessi
|
|
|
54
89
|
});
|
|
55
90
|
|
|
56
91
|
const recommendedRoles = new Set(triageOutput.result?.roles || []);
|
|
57
|
-
const roleOverrides =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const p = config.pipeline || {};
|
|
61
|
-
roleOverrides.plannerEnabled = recommendedRoles.has("planner") || Boolean(p.planner?.enabled);
|
|
62
|
-
roleOverrides.researcherEnabled = recommendedRoles.has("researcher") || Boolean(p.researcher?.enabled);
|
|
63
|
-
roleOverrides.refactorerEnabled = recommendedRoles.has("refactorer") || Boolean(p.refactorer?.enabled);
|
|
64
|
-
roleOverrides.reviewerEnabled = recommendedRoles.has("reviewer") || Boolean(p.reviewer?.enabled);
|
|
65
|
-
roleOverrides.testerEnabled = recommendedRoles.has("tester") || Boolean(p.tester?.enabled);
|
|
66
|
-
roleOverrides.securityEnabled = recommendedRoles.has("security") || Boolean(p.security?.enabled);
|
|
67
|
-
}
|
|
92
|
+
const roleOverrides = triageOutput.ok
|
|
93
|
+
? buildRoleOverrides(recommendedRoles, config.pipeline || {})
|
|
94
|
+
: {};
|
|
68
95
|
|
|
69
96
|
const shouldDecompose = triageOutput.result?.shouldDecompose || false;
|
|
70
97
|
const subtasks = triageOutput.result?.subtasks || [];
|
|
@@ -79,27 +106,7 @@ export async function runTriageStage({ config, logger, emitter, eventBase, sessi
|
|
|
79
106
|
subtasks
|
|
80
107
|
};
|
|
81
108
|
|
|
82
|
-
|
|
83
|
-
if (triageOutput.ok && config?.model_selection?.enabled) {
|
|
84
|
-
const level = triageOutput.result?.level;
|
|
85
|
-
if (level) {
|
|
86
|
-
const { modelOverrides, reasoning } = selectModelsForRoles({ level, config });
|
|
87
|
-
for (const [role, model] of Object.entries(modelOverrides)) {
|
|
88
|
-
if (config.roles?.[role] && !config.roles[role].model) {
|
|
89
|
-
config.roles[role].model = model;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
modelSelection = { modelOverrides, reasoning };
|
|
93
|
-
emitProgress(
|
|
94
|
-
emitter,
|
|
95
|
-
makeEvent("model-selection:applied", { ...eventBase, stage: "triage" }, {
|
|
96
|
-
message: "Smart model selection applied",
|
|
97
|
-
detail: modelSelection
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
109
|
+
const modelSelection = applyModelSelection(triageOutput, config, emitter, eventBase);
|
|
103
110
|
if (modelSelection) {
|
|
104
111
|
stageResult.modelSelection = modelSelection;
|
|
105
112
|
}
|
|
@@ -185,7 +192,167 @@ export async function runResearcherStage({ config, logger, emitter, eventBase, s
|
|
|
185
192
|
return { researchContext, stageResult };
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
|
|
195
|
+
async function handleArchitectClarification({ architectOutput, askQuestion, config, logger, emitter, eventBase, session, architectOnOutput, architectProvider, coderRole, researchContext, discoverResult, triageLevel, trackBudget }) {
|
|
196
|
+
if (!architectOutput.ok
|
|
197
|
+
|| architectOutput.result?.verdict !== "needs_clarification"
|
|
198
|
+
|| !architectOutput.result?.questions?.length) {
|
|
199
|
+
return architectOutput;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const questions = architectOutput.result.questions;
|
|
203
|
+
if (!askQuestion) {
|
|
204
|
+
logger.warn("Architect returned needs_clarification but no interactive input available — continuing with best-effort decisions");
|
|
205
|
+
return architectOutput;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const formatted = "The architect needs clarification before proceeding:\n\n"
|
|
209
|
+
+ questions.map((q, i) => `${i + 1}. ${q}`).join("\n")
|
|
210
|
+
+ "\n\nPlease provide your answers:";
|
|
211
|
+
|
|
212
|
+
emitProgress(
|
|
213
|
+
emitter,
|
|
214
|
+
makeEvent("architect:clarification", { ...eventBase, stage: "architect" }, {
|
|
215
|
+
message: "Architect needs clarification — pausing for human input",
|
|
216
|
+
detail: { questions }
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const answer = await askQuestion(formatted, { iteration: 0, stage: "architect" });
|
|
221
|
+
if (!answer) return architectOutput;
|
|
222
|
+
|
|
223
|
+
const architect2 = new ArchitectRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
224
|
+
await architect2.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
225
|
+
const rerunStart = Date.now();
|
|
226
|
+
const rerunStall = createStallDetector({
|
|
227
|
+
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectProvider
|
|
228
|
+
});
|
|
229
|
+
let result;
|
|
230
|
+
try {
|
|
231
|
+
result = await architect2.execute({
|
|
232
|
+
task: session.task,
|
|
233
|
+
onOutput: rerunStall.onOutput,
|
|
234
|
+
researchContext,
|
|
235
|
+
discoverResult,
|
|
236
|
+
triageLevel,
|
|
237
|
+
humanAnswers: answer
|
|
238
|
+
});
|
|
239
|
+
} finally {
|
|
240
|
+
rerunStall.stop();
|
|
241
|
+
}
|
|
242
|
+
trackBudget({
|
|
243
|
+
role: "architect",
|
|
244
|
+
provider: architectProvider,
|
|
245
|
+
model: config?.roles?.architect?.model || coderRole.model,
|
|
246
|
+
result,
|
|
247
|
+
duration_ms: Date.now() - rerunStart
|
|
248
|
+
});
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function runArchitectStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, researchContext = null, discoverResult = null, triageLevel = null, askQuestion = null }) {
|
|
253
|
+
logger.setContext({ iteration: 0, stage: "architect" });
|
|
254
|
+
emitProgress(
|
|
255
|
+
emitter,
|
|
256
|
+
makeEvent("architect:start", { ...eventBase, stage: "architect" }, {
|
|
257
|
+
message: "Architect designing solution architecture"
|
|
258
|
+
})
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const architectProvider = config?.roles?.architect?.provider || coderRole.provider;
|
|
262
|
+
const architectOnOutput = ({ stream, line }) => {
|
|
263
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "architect" }, {
|
|
264
|
+
message: line,
|
|
265
|
+
detail: { stream, agent: architectProvider }
|
|
266
|
+
}));
|
|
267
|
+
};
|
|
268
|
+
const architectStall = createStallDetector({
|
|
269
|
+
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectProvider
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const architect = new ArchitectRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
273
|
+
await architect.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
274
|
+
const architectStart = Date.now();
|
|
275
|
+
let architectOutput;
|
|
276
|
+
try {
|
|
277
|
+
architectOutput = await architect.execute({
|
|
278
|
+
task: session.task,
|
|
279
|
+
onOutput: architectStall.onOutput,
|
|
280
|
+
researchContext,
|
|
281
|
+
discoverResult,
|
|
282
|
+
triageLevel
|
|
283
|
+
});
|
|
284
|
+
} finally {
|
|
285
|
+
architectStall.stop();
|
|
286
|
+
}
|
|
287
|
+
trackBudget({
|
|
288
|
+
role: "architect",
|
|
289
|
+
provider: architectProvider,
|
|
290
|
+
model: config?.roles?.architect?.model || coderRole.model,
|
|
291
|
+
result: architectOutput,
|
|
292
|
+
duration_ms: Date.now() - architectStart
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await addCheckpoint(session, {
|
|
296
|
+
stage: "architect",
|
|
297
|
+
iteration: 0,
|
|
298
|
+
ok: architectOutput.ok,
|
|
299
|
+
provider: architectProvider,
|
|
300
|
+
model: config?.roles?.architect?.model || coderRole.model || null
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// --- Interactive clarification loop ---
|
|
304
|
+
architectOutput = await handleArchitectClarification({
|
|
305
|
+
architectOutput, askQuestion, config, logger, emitter, eventBase, session,
|
|
306
|
+
architectOnOutput, architectProvider, coderRole, researchContext, discoverResult, triageLevel, trackBudget
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const stageResult = {
|
|
310
|
+
ok: architectOutput.ok,
|
|
311
|
+
verdict: architectOutput.result?.verdict || null,
|
|
312
|
+
architecture: architectOutput.result?.architecture || null,
|
|
313
|
+
questions: architectOutput.result?.questions || []
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
emitProgress(
|
|
317
|
+
emitter,
|
|
318
|
+
makeEvent("architect:end", { ...eventBase, stage: "architect" }, {
|
|
319
|
+
status: architectOutput.ok ? "ok" : "fail",
|
|
320
|
+
message: architectOutput.ok ? "Architecture completed" : `Architecture failed: ${architectOutput.summary}`,
|
|
321
|
+
detail: stageResult
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const architectContext = architectOutput.ok ? architectOutput.result : null;
|
|
326
|
+
|
|
327
|
+
// Generate ADRs from architect tradeoffs when PG is linked
|
|
328
|
+
const tradeoffs = architectOutput.result?.architecture?.tradeoffs;
|
|
329
|
+
if (architectOutput.ok
|
|
330
|
+
&& architectOutput.result?.verdict === "ready"
|
|
331
|
+
&& tradeoffs?.length > 0
|
|
332
|
+
&& session.pg_task_id
|
|
333
|
+
&& session.pg_project_id) {
|
|
334
|
+
try {
|
|
335
|
+
const pgClient = await import("../planning-game/client.js");
|
|
336
|
+
const adrResult = await createArchitectADRs({
|
|
337
|
+
tradeoffs,
|
|
338
|
+
pgTaskId: session.pg_task_id,
|
|
339
|
+
pgProject: session.pg_project_id,
|
|
340
|
+
taskTitle: session.task,
|
|
341
|
+
mcpClient: pgClient
|
|
342
|
+
});
|
|
343
|
+
stageResult.adrs = adrResult;
|
|
344
|
+
if (adrResult.created > 0) {
|
|
345
|
+
logger.info(`Architect: created ${adrResult.created} ADR(s) in Planning Game`);
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
logger.warn(`Architect: failed to create ADRs: ${err.message}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { architectContext, stageResult };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export async function runPlannerStage({ config, logger, emitter, eventBase, session, plannerRole, researchContext, architectContext = null, triageDecomposition = null, trackBudget }) {
|
|
189
356
|
const task = session.task;
|
|
190
357
|
logger.setContext({ iteration: 0, stage: "planner" });
|
|
191
358
|
emitProgress(
|
|
@@ -207,7 +374,7 @@ export async function runPlannerStage({ config, logger, emitter, eventBase, sess
|
|
|
207
374
|
});
|
|
208
375
|
|
|
209
376
|
const planRole = new PlannerRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
210
|
-
planRole.context = { task, research: researchContext, triageDecomposition };
|
|
377
|
+
planRole.context = { task, research: researchContext, architecture: architectContext, triageDecomposition };
|
|
211
378
|
await planRole.init();
|
|
212
379
|
const plannerStart = Date.now();
|
|
213
380
|
let planResult;
|
|
@@ -258,3 +425,68 @@ export async function runPlannerStage({ config, logger, emitter, eventBase, sess
|
|
|
258
425
|
|
|
259
426
|
return { plannedTask, stageResult };
|
|
260
427
|
}
|
|
428
|
+
|
|
429
|
+
export async function runDiscoverStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
|
|
430
|
+
logger.setContext({ iteration: 0, stage: "discover" });
|
|
431
|
+
emitProgress(
|
|
432
|
+
emitter,
|
|
433
|
+
makeEvent("discover:start", { ...eventBase, stage: "discover" }, {
|
|
434
|
+
message: "Discover analyzing task for gaps"
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const discoverProvider = config?.roles?.discover?.provider || coderRole.provider;
|
|
439
|
+
const discoverOnOutput = ({ stream, line }) => {
|
|
440
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "discover" }, {
|
|
441
|
+
message: line,
|
|
442
|
+
detail: { stream, agent: discoverProvider }
|
|
443
|
+
}));
|
|
444
|
+
};
|
|
445
|
+
const discoverStall = createStallDetector({
|
|
446
|
+
onOutput: discoverOnOutput, emitter, eventBase, stage: "discover", provider: discoverProvider
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const mode = config?.pipeline?.discover?.mode || "gaps";
|
|
450
|
+
const discover = new DiscoverRole({ config, logger, emitter });
|
|
451
|
+
await discover.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
452
|
+
const discoverStart = Date.now();
|
|
453
|
+
let discoverOutput;
|
|
454
|
+
try {
|
|
455
|
+
discoverOutput = await discover.run({ task: session.task, mode, onOutput: discoverStall.onOutput });
|
|
456
|
+
} finally {
|
|
457
|
+
discoverStall.stop();
|
|
458
|
+
}
|
|
459
|
+
trackBudget({
|
|
460
|
+
role: "discover",
|
|
461
|
+
provider: discoverProvider,
|
|
462
|
+
model: config?.roles?.discover?.model || coderRole.model,
|
|
463
|
+
result: discoverOutput,
|
|
464
|
+
duration_ms: Date.now() - discoverStart
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
await addCheckpoint(session, {
|
|
468
|
+
stage: "discover",
|
|
469
|
+
iteration: 0,
|
|
470
|
+
ok: discoverOutput.ok,
|
|
471
|
+
provider: discoverProvider,
|
|
472
|
+
model: config?.roles?.discover?.model || coderRole.model || null
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const stageResult = {
|
|
476
|
+
ok: discoverOutput.ok,
|
|
477
|
+
verdict: discoverOutput.result?.verdict || null,
|
|
478
|
+
gaps: discoverOutput.result?.gaps || [],
|
|
479
|
+
mode
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
emitProgress(
|
|
483
|
+
emitter,
|
|
484
|
+
makeEvent("discover:end", { ...eventBase, stage: "discover" }, {
|
|
485
|
+
status: discoverOutput.ok ? "ok" : "fail",
|
|
486
|
+
message: discoverOutput.ok ? "Discovery completed" : `Discovery failed: ${discoverOutput.summary}`,
|
|
487
|
+
detail: stageResult
|
|
488
|
+
})
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
return { stageResult };
|
|
492
|
+
}
|
|
@@ -126,11 +126,11 @@ export async function buildRulesContext({ session, task, iteration }) {
|
|
|
126
126
|
const addedDeps = (pkgDiff.stdout || "").split("\n")
|
|
127
127
|
.filter(line => line.startsWith("+") && line.includes('"') && !line.startsWith("+++"))
|
|
128
128
|
.map(line => {
|
|
129
|
-
const match =
|
|
129
|
+
const match = /"([^"]+)":\s*"/.exec(line);
|
|
130
130
|
return match ? match[1] : null;
|
|
131
131
|
})
|
|
132
132
|
.filter(Boolean)
|
|
133
|
-
.filter(name => !["name", "version", "description", "main", "scripts", "type", "license", "author"].
|
|
133
|
+
.filter(name => !new Set(["name", "version", "description", "main", "scripts", "type", "license", "author"]).has(name));
|
|
134
134
|
context.newDependencies = addedDeps;
|
|
135
135
|
} catch { /* ignore */ }
|
|
136
136
|
}
|