karajan-code 1.49.0 → 1.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/README.es.md +1 -1
- package/package.json +1 -1
- package/src/mcp/handlers/direct-handlers.js +393 -0
- package/src/mcp/handlers/hu-handlers.js +93 -0
- package/src/mcp/handlers/management-handlers.js +182 -0
- package/src/mcp/handlers/run-handler.js +283 -0
- package/src/mcp/server-handlers.js +40 -870
- package/src/orchestrator/iteration-stages.js +9 -890
- package/src/orchestrator/pre-loop-stages.js +8 -906
- package/src/orchestrator/preflight-checks.js +3 -1
- package/src/orchestrator/solomon-escalation.js +34 -10
- package/src/orchestrator/stages/architect-stage.js +176 -0
- package/src/orchestrator/stages/coder-stage.js +307 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +467 -0
- package/src/orchestrator/stages/planner-stage.js +90 -0
- package/src/orchestrator/stages/research-stage.js +74 -0
- package/src/orchestrator/stages/reviewer-stage.js +350 -0
- package/src/orchestrator/stages/sonar-stage.js +267 -0
- package/src/orchestrator/stages/triage-stage.js +137 -0
- package/src/orchestrator.js +25 -9
- package/src/planning-game/decomposition.js +7 -5
- package/src/planning-game/pipeline-adapter.js +6 -2
- package/src/utils/messages.js +61 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage stage logic.
|
|
3
|
+
* Extracted from pre-loop-stages.js for maintainability.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TriageRole } from "../../roles/triage-role.js";
|
|
7
|
+
import { addCheckpoint } from "../../session-store.js";
|
|
8
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
9
|
+
import { selectModelsForRoles } from "../../utils/model-selector.js";
|
|
10
|
+
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
11
|
+
|
|
12
|
+
const ROLE_NAMES = ["planner", "researcher", "architect", "refactorer", "reviewer", "tester", "security", "impeccable"];
|
|
13
|
+
|
|
14
|
+
function buildRoleOverrides(recommendedRoles, pipelineConfig) {
|
|
15
|
+
const overrides = {};
|
|
16
|
+
for (const role of ROLE_NAMES) {
|
|
17
|
+
overrides[`${role}Enabled`] = recommendedRoles.has(role) || Boolean(pipelineConfig[role]?.enabled);
|
|
18
|
+
}
|
|
19
|
+
return overrides;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyModelSelection(triageOutput, config, emitter, eventBase) {
|
|
23
|
+
if (!triageOutput.ok || !config?.model_selection?.enabled) return null;
|
|
24
|
+
const level = triageOutput.result?.level;
|
|
25
|
+
if (!level) return null;
|
|
26
|
+
|
|
27
|
+
const { modelOverrides, reasoning } = selectModelsForRoles({ level, config });
|
|
28
|
+
for (const [role, model] of Object.entries(modelOverrides)) {
|
|
29
|
+
if (config.roles?.[role] && !config.roles[role].model) {
|
|
30
|
+
config.roles[role].model = model;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const modelSelection = { modelOverrides, reasoning };
|
|
34
|
+
emitProgress(
|
|
35
|
+
emitter,
|
|
36
|
+
makeEvent("model-selection:applied", { ...eventBase, stage: "triage" }, {
|
|
37
|
+
message: "Smart model selection applied",
|
|
38
|
+
detail: modelSelection
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
return modelSelection;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function runTriageStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
|
|
45
|
+
logger.setContext({ iteration: 0, stage: "triage" });
|
|
46
|
+
|
|
47
|
+
const triageProvider = config?.roles?.triage?.provider || coderRole.provider;
|
|
48
|
+
emitProgress(
|
|
49
|
+
emitter,
|
|
50
|
+
makeEvent("triage:start", { ...eventBase, stage: "triage" }, {
|
|
51
|
+
message: "Triage classifying task complexity",
|
|
52
|
+
detail: { provider: triageProvider, executorType: "agent" }
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const triageOnOutput = ({ stream, line }) => {
|
|
57
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "triage" }, {
|
|
58
|
+
message: line,
|
|
59
|
+
detail: { stream, agent: triageProvider }
|
|
60
|
+
}));
|
|
61
|
+
};
|
|
62
|
+
const triageStall = createStallDetector({
|
|
63
|
+
onOutput: triageOnOutput, emitter, eventBase, stage: "triage", provider: triageProvider
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const triage = new TriageRole({ config, logger, emitter });
|
|
67
|
+
await triage.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
68
|
+
const triageStart = Date.now();
|
|
69
|
+
let triageOutput;
|
|
70
|
+
try {
|
|
71
|
+
triageOutput = await triage.run({ task: session.task, onOutput: triageStall.onOutput });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.warn(`Triage threw: ${err.message}`);
|
|
74
|
+
triageOutput = { ok: false, summary: `Triage error: ${err.message}`, result: { error: err.message } };
|
|
75
|
+
} finally {
|
|
76
|
+
triageStall.stop();
|
|
77
|
+
}
|
|
78
|
+
trackBudget({
|
|
79
|
+
role: "triage",
|
|
80
|
+
provider: config?.roles?.triage?.provider || coderRole.provider,
|
|
81
|
+
model: config?.roles?.triage?.model || coderRole.model,
|
|
82
|
+
result: triageOutput,
|
|
83
|
+
duration_ms: Date.now() - triageStart
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await addCheckpoint(session, {
|
|
87
|
+
stage: "triage",
|
|
88
|
+
iteration: 0,
|
|
89
|
+
ok: triageOutput.ok,
|
|
90
|
+
provider: config?.roles?.triage?.provider || coderRole.provider,
|
|
91
|
+
model: config?.roles?.triage?.model || coderRole.model || null
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const recommendedRoles = new Set(triageOutput.result?.roles || []);
|
|
95
|
+
const roleOverrides = triageOutput.ok
|
|
96
|
+
? buildRoleOverrides(recommendedRoles, config.pipeline || {})
|
|
97
|
+
: {};
|
|
98
|
+
|
|
99
|
+
const shouldDecompose = triageOutput.result?.shouldDecompose || false;
|
|
100
|
+
const subtasks = triageOutput.result?.subtasks || [];
|
|
101
|
+
|
|
102
|
+
const stageResult = {
|
|
103
|
+
ok: triageOutput.ok,
|
|
104
|
+
level: triageOutput.result?.level || null,
|
|
105
|
+
roles: Array.from(recommendedRoles),
|
|
106
|
+
reasoning: triageOutput.result?.reasoning || null,
|
|
107
|
+
taskType: triageOutput.result?.taskType || "sw",
|
|
108
|
+
shouldDecompose,
|
|
109
|
+
subtasks
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const modelSelection = applyModelSelection(triageOutput, config, emitter, eventBase);
|
|
113
|
+
if (modelSelection) {
|
|
114
|
+
stageResult.modelSelection = modelSelection;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (shouldDecompose && subtasks.length > 0) {
|
|
118
|
+
emitProgress(
|
|
119
|
+
emitter,
|
|
120
|
+
makeEvent("triage:decompose", { ...eventBase, stage: "triage" }, {
|
|
121
|
+
message: `Task decomposition recommended: ${subtasks.length} subtask${subtasks.length === 1 ? "" : "s"}`,
|
|
122
|
+
detail: { shouldDecompose, subtasks }
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
emitProgress(
|
|
128
|
+
emitter,
|
|
129
|
+
makeEvent("triage:end", { ...eventBase, stage: "triage" }, {
|
|
130
|
+
status: triageOutput.ok ? "ok" : "fail",
|
|
131
|
+
message: triageOutput.ok ? "Triage completed" : `Triage failed: ${triageOutput.summary}`,
|
|
132
|
+
detail: { ...stageResult, provider: triageProvider, executorType: "agent" }
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return { roleOverrides, stageResult };
|
|
137
|
+
}
|
package/src/orchestrator.js
CHANGED
|
@@ -31,6 +31,7 @@ import { classifyIntent } from "./guards/intent-guard.js";
|
|
|
31
31
|
import { resolveReviewProfile } from "./review/profiles.js";
|
|
32
32
|
import { CoderRole } from "./roles/coder-role.js";
|
|
33
33
|
import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
|
|
34
|
+
import { msg, getLang } from "./utils/messages.js";
|
|
34
35
|
import { PipelineContext } from "./orchestrator/pipeline-context.js";
|
|
35
36
|
import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage, runHuReviewerStage } from "./orchestrator/pre-loop-stages.js";
|
|
36
37
|
import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
|
|
@@ -402,8 +403,9 @@ async function handleCheckpoint({ checkpointDisabled, askQuestion, lastCheckpoin
|
|
|
402
403
|
})
|
|
403
404
|
);
|
|
404
405
|
|
|
406
|
+
const lang = getLang(config);
|
|
405
407
|
const answer = await askQuestion(
|
|
406
|
-
`${checkpointMsg}\n\
|
|
408
|
+
`${checkpointMsg}\n\n${msg("checkpoint_options", lang)}`
|
|
407
409
|
);
|
|
408
410
|
|
|
409
411
|
await addCheckpoint(session, { stage: "interactive-checkpoint", elapsed_minutes: Number(elapsedStr), answer });
|
|
@@ -429,7 +431,7 @@ async function handleCheckpoint({ checkpointDisabled, askQuestion, lastCheckpoin
|
|
|
429
431
|
return parsed;
|
|
430
432
|
}
|
|
431
433
|
|
|
432
|
-
function parseCheckpointAnswer({ trimmedAnswer, checkpointDisabled, config }) {
|
|
434
|
+
export function parseCheckpointAnswer({ trimmedAnswer, checkpointDisabled, config }) {
|
|
433
435
|
if (!trimmedAnswer) {
|
|
434
436
|
return { action: "continue_loop", checkpointDisabled, lastCheckpointAt: Date.now() };
|
|
435
437
|
}
|
|
@@ -632,11 +634,23 @@ async function checkSolomonCriticalAlerts({ rulesResult, askQuestion, session, i
|
|
|
632
634
|
.filter(a => a.severity === "critical")
|
|
633
635
|
.map(a => a.message)
|
|
634
636
|
.join("\n");
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
637
|
+
const question = [
|
|
638
|
+
"Solomon detected critical issues:",
|
|
639
|
+
alertSummary,
|
|
640
|
+
"",
|
|
641
|
+
"What should we do?",
|
|
642
|
+
"1 = Continue anyway",
|
|
643
|
+
"2 = Pause the session",
|
|
644
|
+
"3 = Stop the session",
|
|
645
|
+
"",
|
|
646
|
+
"Type 1, 2, or 3:"
|
|
647
|
+
].join("\n");
|
|
648
|
+
const answer = await askQuestion(question, { iteration: i, stage: "solomon" });
|
|
649
|
+
const trimmed = (answer || "").trim().toLowerCase();
|
|
650
|
+
const shouldPause = !answer
|
|
651
|
+
|| trimmed === "2" || trimmed === "3"
|
|
652
|
+
|| trimmed.startsWith("pause") || trimmed.startsWith("stop");
|
|
653
|
+
if (shouldPause) {
|
|
640
654
|
await pauseSession(session, {
|
|
641
655
|
question: `Solomon supervisor paused: ${alertSummary}`,
|
|
642
656
|
context: { iteration: i, stage: "solomon", alerts: rulesResult.alerts }
|
|
@@ -1280,11 +1294,13 @@ async function runSingleIteration(ctx) {
|
|
|
1280
1294
|
|
|
1281
1295
|
const reviewerRetryCount = session.reviewer_retry_count || 0;
|
|
1282
1296
|
const maxReviewerRetries = config.session.max_reviewer_retries ?? config.session.fail_fast_repeats;
|
|
1297
|
+
const iterLang = getLang(config);
|
|
1298
|
+
const iterMsg = msg("pipeline_iteration", iterLang, { current: i, max: config.max_iterations });
|
|
1283
1299
|
emitProgress(emitter, makeEvent("iteration:start", { ...eventBase, stage: "iteration" }, {
|
|
1284
|
-
message:
|
|
1300
|
+
message: iterMsg,
|
|
1285
1301
|
detail: { iteration: i, maxIterations: config.max_iterations, reviewerRetryCount, maxReviewerRetries }
|
|
1286
1302
|
}));
|
|
1287
|
-
logger.info(
|
|
1303
|
+
logger.info(iterMsg);
|
|
1288
1304
|
|
|
1289
1305
|
const crResult = await runCoderAndRefactorerStages({
|
|
1290
1306
|
coderRoleInstance: ctx.coderRoleInstance, coderRole: ctx.coderRole, refactorerRole: ctx.refactorerRole,
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* recommends decomposition and user accepts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { msg } from "../utils/messages.js";
|
|
8
|
+
|
|
7
9
|
export async function createDecompositionSubtasks({
|
|
8
10
|
client,
|
|
9
11
|
projectId,
|
|
@@ -66,9 +68,9 @@ export async function createDecompositionSubtasks({
|
|
|
66
68
|
return created;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
export function buildDecompositionQuestion(subtasks, parentCardId) {
|
|
71
|
+
export function buildDecompositionQuestion(subtasks, parentCardId, lang = "en") {
|
|
70
72
|
const lines = [
|
|
71
|
-
|
|
73
|
+
msg("triage_decompose", lang, { count: subtasks.length }),
|
|
72
74
|
""
|
|
73
75
|
];
|
|
74
76
|
for (let i = 0; i < subtasks.length; i++) {
|
|
@@ -76,10 +78,10 @@ export function buildDecompositionQuestion(subtasks, parentCardId) {
|
|
|
76
78
|
}
|
|
77
79
|
lines.push(
|
|
78
80
|
"",
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
+
msg("triage_create_cards", lang, { cardId: parentCardId }),
|
|
82
|
+
msg("triage_sequential", lang),
|
|
81
83
|
"",
|
|
82
|
-
"
|
|
84
|
+
msg("triage_reply", lang)
|
|
83
85
|
);
|
|
84
86
|
return lines.join("\n");
|
|
85
87
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
15
15
|
import { addCheckpoint, saveSession } from "../session-store.js";
|
|
16
|
+
import { getLang } from "../utils/messages.js";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Attach the PG adapter to a pipeline emitter.
|
|
@@ -54,10 +55,13 @@ export async function handlePgDecomposition({ triageResult, pgTaskId, pgProject,
|
|
|
54
55
|
const { buildDecompositionQuestion, createDecompositionSubtasks } = await import("./decomposition.js");
|
|
55
56
|
const { createCard, relateCards, fetchCard } = await import("./client.js");
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
+
const lang = getLang(config);
|
|
59
|
+
const question = buildDecompositionQuestion(triageResult.stageResult.subtasks, pgTaskId, lang);
|
|
58
60
|
const answer = await askQuestion(question);
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
const normalizedAnswer = (answer || "").trim().toLowerCase();
|
|
63
|
+
const isAccept = normalizedAnswer === "1" || normalizedAnswer === "yes" || normalizedAnswer === "sí" || normalizedAnswer === "si";
|
|
64
|
+
if (isAccept) {
|
|
61
65
|
const parentCard = await fetchCard({ projectId: pgProject, cardId: pgTaskId }).catch(() => null);
|
|
62
66
|
const createdSubtasks = await createDecompositionSubtasks({
|
|
63
67
|
client: { createCard, relateCards },
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized message catalog for pipeline UI strings.
|
|
3
|
+
* Supports i18n by resolving messages for the configured language.
|
|
4
|
+
* Falls back to English when a key or language is missing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const MESSAGES = {
|
|
8
|
+
en: {
|
|
9
|
+
triage_decompose: "Triage recommends decomposing this task into {count} subtasks:",
|
|
10
|
+
triage_create_cards: "Create these as linked cards in Planning Game (parent: {cardId})?",
|
|
11
|
+
triage_sequential: "Each subtask will block the next one (sequential chain).",
|
|
12
|
+
triage_reply: "Reply: yes / no",
|
|
13
|
+
solomon_conflict: "--- Conflict: {stage} ---",
|
|
14
|
+
solomon_feedback: "Reviewer feedback:",
|
|
15
|
+
solomon_reason: "Solomon could not resolve the conflict:",
|
|
16
|
+
solomon_options: "Options:\n 1. Accept coder's work as-is\n 2. Retry with reviewer's feedback\n 3. Stop the session",
|
|
17
|
+
solomon_proceed: "How should we proceed?",
|
|
18
|
+
checkpoint_continue: "Continue for {minutes} more minutes?",
|
|
19
|
+
checkpoint_options: "1. Continue 5 more minutes\n2. Continue until done (no more checkpoints)\n3. Continue for N minutes (reply with the number)\n4. Stop now",
|
|
20
|
+
preflight_passed: "Preflight passed — all checks OK",
|
|
21
|
+
pipeline_iteration: "Iteration {current}/{max}",
|
|
22
|
+
},
|
|
23
|
+
es: {
|
|
24
|
+
triage_decompose: "Triage recomienda descomponer esta tarea en {count} subtareas:",
|
|
25
|
+
triage_create_cards: "¿Crear estas como cards vinculadas en Planning Game (padre: {cardId})?",
|
|
26
|
+
triage_sequential: "Cada subtarea bloqueará la siguiente (cadena secuencial).",
|
|
27
|
+
triage_reply: "Responde: sí / no",
|
|
28
|
+
solomon_conflict: "--- Conflicto: {stage} ---",
|
|
29
|
+
solomon_feedback: "Feedback del reviewer:",
|
|
30
|
+
solomon_reason: "Solomon no pudo resolver el conflicto:",
|
|
31
|
+
solomon_options: "Opciones:\n 1. Aceptar el trabajo del coder tal cual\n 2. Reintentar con el feedback del reviewer\n 3. Parar la sesión",
|
|
32
|
+
solomon_proceed: "¿Cómo procedemos?",
|
|
33
|
+
checkpoint_continue: "¿Continuar {minutes} minutos más?",
|
|
34
|
+
checkpoint_options: "1. Continuar (5 min)\n2. Continuar hasta terminar\n3. Tiempo personalizado\n4. Parar",
|
|
35
|
+
preflight_passed: "Preflight superado — todas las comprobaciones OK",
|
|
36
|
+
pipeline_iteration: "Iteración {current}/{max}",
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a message template by key and language, interpolating parameters.
|
|
42
|
+
* Falls back to English, then to the raw key if not found.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} key - Message key (e.g. "solomon_conflict")
|
|
45
|
+
* @param {string} [lang="en"] - 2-letter language code
|
|
46
|
+
* @param {object} [params={}] - Interpolation values (e.g. {stage: "reviewer"})
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function msg(key, lang = "en", params = {}) {
|
|
50
|
+
const template = MESSAGES[lang]?.[key] || MESSAGES.en[key] || key;
|
|
51
|
+
return template.replace(/\{(\w+)\}/g, (_, k) => params[k] ?? `{${k}}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract the language code from a Karajan config object.
|
|
56
|
+
* @param {object} [config] - Karajan config
|
|
57
|
+
* @returns {string} 2-letter language code
|
|
58
|
+
*/
|
|
59
|
+
export function getLang(config) {
|
|
60
|
+
return config?.language || "en";
|
|
61
|
+
}
|