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.
Files changed (72) hide show
  1. package/package.json +1 -1
  2. package/src/activity-log.js +13 -13
  3. package/src/agents/availability.js +2 -3
  4. package/src/agents/claude-agent.js +42 -21
  5. package/src/agents/model-registry.js +1 -1
  6. package/src/becaria/dispatch.js +1 -1
  7. package/src/becaria/repo.js +3 -3
  8. package/src/cli.js +5 -2
  9. package/src/commands/doctor.js +154 -108
  10. package/src/commands/init.js +101 -90
  11. package/src/commands/plan.js +1 -1
  12. package/src/commands/report.js +77 -71
  13. package/src/commands/roles.js +0 -1
  14. package/src/commands/run.js +2 -3
  15. package/src/config.js +174 -93
  16. package/src/git/automation.js +3 -4
  17. package/src/guards/intent-guard.js +123 -0
  18. package/src/guards/output-guard.js +158 -0
  19. package/src/guards/perf-guard.js +126 -0
  20. package/src/guards/policy-resolver.js +3 -3
  21. package/src/mcp/orphan-guard.js +1 -2
  22. package/src/mcp/progress.js +4 -3
  23. package/src/mcp/run-kj.js +1 -0
  24. package/src/mcp/server-handlers.js +242 -253
  25. package/src/mcp/server.js +4 -3
  26. package/src/mcp/tools.js +2 -0
  27. package/src/orchestrator/agent-fallback.js +1 -3
  28. package/src/orchestrator/iteration-stages.js +206 -170
  29. package/src/orchestrator/pre-loop-stages.js +200 -34
  30. package/src/orchestrator/solomon-rules.js +2 -2
  31. package/src/orchestrator.js +902 -746
  32. package/src/planning-game/adapter.js +23 -20
  33. package/src/planning-game/architect-adrs.js +45 -0
  34. package/src/planning-game/client.js +15 -1
  35. package/src/planning-game/decomposition.js +7 -5
  36. package/src/prompts/architect.js +88 -0
  37. package/src/prompts/discover.js +54 -53
  38. package/src/prompts/planner.js +53 -33
  39. package/src/prompts/triage.js +8 -16
  40. package/src/review/parser.js +18 -19
  41. package/src/review/profiles.js +2 -2
  42. package/src/review/schema.js +3 -3
  43. package/src/review/scope-filter.js +3 -4
  44. package/src/roles/architect-role.js +122 -0
  45. package/src/roles/commiter-role.js +2 -2
  46. package/src/roles/discover-role.js +59 -67
  47. package/src/roles/index.js +1 -0
  48. package/src/roles/planner-role.js +54 -38
  49. package/src/roles/refactorer-role.js +8 -7
  50. package/src/roles/researcher-role.js +6 -7
  51. package/src/roles/reviewer-role.js +4 -5
  52. package/src/roles/security-role.js +3 -4
  53. package/src/roles/solomon-role.js +6 -18
  54. package/src/roles/sonar-role.js +5 -1
  55. package/src/roles/tester-role.js +8 -5
  56. package/src/roles/triage-role.js +2 -2
  57. package/src/session-cleanup.js +29 -24
  58. package/src/session-store.js +1 -1
  59. package/src/sonar/api.js +1 -1
  60. package/src/sonar/manager.js +1 -1
  61. package/src/sonar/project-key.js +5 -5
  62. package/src/sonar/scanner.js +34 -65
  63. package/src/utils/display.js +312 -272
  64. package/src/utils/git.js +3 -3
  65. package/src/utils/logger.js +6 -1
  66. package/src/utils/model-selector.js +5 -5
  67. package/src/utils/process.js +80 -102
  68. package/src/utils/rate-limit-detector.js +13 -13
  69. package/src/utils/run-log.js +55 -52
  70. package/templates/kj.config.yml +33 -0
  71. package/templates/roles/architect.md +62 -0
  72. 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
- if (triageOutput.ok) {
60
- // Triage can activate roles, but cannot deactivate roles explicitly enabled in pipeline config
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
- let modelSelection = null;
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
- export async function runPlannerStage({ config, logger, emitter, eventBase, session, plannerRole, researchContext, triageDecomposition = null, trackBudget }) {
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 = line.match(/"([^"]+)":\s*"/);
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"].includes(name));
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
  }