karajan-code 1.16.0 → 1.18.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 +5 -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 +174 -93
- package/src/git/automation.js +3 -4
- package/src/guards/intent-guard.js +123 -0
- package/src/guards/output-guard.js +158 -0
- package/src/guards/perf-guard.js +126 -0
- 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 +1 -0
- package/src/mcp/server-handlers.js +242 -253
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +2 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +200 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +902 -746
- 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 +54 -53
- 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 +59 -67
- package/src/roles/index.js +1 -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/kj.config.yml +33 -0
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
|
@@ -2,13 +2,47 @@ 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
4
|
import { DiscoverRole } from "../roles/discover-role.js";
|
|
5
|
+
import { ArchitectRole } from "../roles/architect-role.js";
|
|
5
6
|
import { createAgent } from "../agents/index.js";
|
|
7
|
+
import { createArchitectADRs } from "../planning-game/architect-adrs.js";
|
|
6
8
|
import { addCheckpoint, markSessionStatus } from "../session-store.js";
|
|
7
9
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
8
10
|
import { parsePlannerOutput } from "../prompts/planner.js";
|
|
9
11
|
import { selectModelsForRoles } from "../utils/model-selector.js";
|
|
10
12
|
import { createStallDetector } from "../utils/stall-detector.js";
|
|
11
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
|
+
|
|
12
46
|
export async function runTriageStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
|
|
13
47
|
logger.setContext({ iteration: 0, stage: "triage" });
|
|
14
48
|
emitProgress(
|
|
@@ -55,17 +89,9 @@ export async function runTriageStage({ config, logger, emitter, eventBase, sessi
|
|
|
55
89
|
});
|
|
56
90
|
|
|
57
91
|
const recommendedRoles = new Set(triageOutput.result?.roles || []);
|
|
58
|
-
const roleOverrides =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const p = config.pipeline || {};
|
|
62
|
-
roleOverrides.plannerEnabled = recommendedRoles.has("planner") || Boolean(p.planner?.enabled);
|
|
63
|
-
roleOverrides.researcherEnabled = recommendedRoles.has("researcher") || Boolean(p.researcher?.enabled);
|
|
64
|
-
roleOverrides.refactorerEnabled = recommendedRoles.has("refactorer") || Boolean(p.refactorer?.enabled);
|
|
65
|
-
roleOverrides.reviewerEnabled = recommendedRoles.has("reviewer") || Boolean(p.reviewer?.enabled);
|
|
66
|
-
roleOverrides.testerEnabled = recommendedRoles.has("tester") || Boolean(p.tester?.enabled);
|
|
67
|
-
roleOverrides.securityEnabled = recommendedRoles.has("security") || Boolean(p.security?.enabled);
|
|
68
|
-
}
|
|
92
|
+
const roleOverrides = triageOutput.ok
|
|
93
|
+
? buildRoleOverrides(recommendedRoles, config.pipeline || {})
|
|
94
|
+
: {};
|
|
69
95
|
|
|
70
96
|
const shouldDecompose = triageOutput.result?.shouldDecompose || false;
|
|
71
97
|
const subtasks = triageOutput.result?.subtasks || [];
|
|
@@ -80,27 +106,7 @@ export async function runTriageStage({ config, logger, emitter, eventBase, sessi
|
|
|
80
106
|
subtasks
|
|
81
107
|
};
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
if (triageOutput.ok && config?.model_selection?.enabled) {
|
|
85
|
-
const level = triageOutput.result?.level;
|
|
86
|
-
if (level) {
|
|
87
|
-
const { modelOverrides, reasoning } = selectModelsForRoles({ level, config });
|
|
88
|
-
for (const [role, model] of Object.entries(modelOverrides)) {
|
|
89
|
-
if (config.roles?.[role] && !config.roles[role].model) {
|
|
90
|
-
config.roles[role].model = model;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
modelSelection = { modelOverrides, reasoning };
|
|
94
|
-
emitProgress(
|
|
95
|
-
emitter,
|
|
96
|
-
makeEvent("model-selection:applied", { ...eventBase, stage: "triage" }, {
|
|
97
|
-
message: "Smart model selection applied",
|
|
98
|
-
detail: modelSelection
|
|
99
|
-
})
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
109
|
+
const modelSelection = applyModelSelection(triageOutput, config, emitter, eventBase);
|
|
104
110
|
if (modelSelection) {
|
|
105
111
|
stageResult.modelSelection = modelSelection;
|
|
106
112
|
}
|
|
@@ -186,7 +192,167 @@ export async function runResearcherStage({ config, logger, emitter, eventBase, s
|
|
|
186
192
|
return { researchContext, stageResult };
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
|
|
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 }) {
|
|
190
356
|
const task = session.task;
|
|
191
357
|
logger.setContext({ iteration: 0, stage: "planner" });
|
|
192
358
|
emitProgress(
|
|
@@ -208,7 +374,7 @@ export async function runPlannerStage({ config, logger, emitter, eventBase, sess
|
|
|
208
374
|
});
|
|
209
375
|
|
|
210
376
|
const planRole = new PlannerRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
211
|
-
planRole.context = { task, research: researchContext, triageDecomposition };
|
|
377
|
+
planRole.context = { task, research: researchContext, architecture: architectContext, triageDecomposition };
|
|
212
378
|
await planRole.init();
|
|
213
379
|
const plannerStart = Date.now();
|
|
214
380
|
let planResult;
|
|
@@ -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
|
}
|