karajan-code 1.50.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/orchestrator/preflight-checks.js +3 -1
- package/src/orchestrator/solomon-escalation.js +34 -10
- 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
package/README.md
CHANGED
|
@@ -243,7 +243,7 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
|
|
|
243
243
|
git clone https://github.com/manufosela/karajan-code.git
|
|
244
244
|
cd karajan-code
|
|
245
245
|
npm install
|
|
246
|
-
npm test # Run
|
|
246
|
+
npm test # Run ~2473 tests with Vitest
|
|
247
247
|
npm run validate # Lint + test
|
|
248
248
|
```
|
|
249
249
|
|
package/docs/README.es.md
CHANGED
|
@@ -213,7 +213,7 @@ No es nostalgia ni cabezoneria. Es que llevo usando JavaScript desde 1997, cuand
|
|
|
213
213
|
git clone https://github.com/manufosela/karajan-code.git
|
|
214
214
|
cd karajan-code
|
|
215
215
|
npm install
|
|
216
|
-
npm test # Ejecutar
|
|
216
|
+
npm test # Ejecutar ~2473 tests con Vitest
|
|
217
217
|
npm run validate # Lint + test
|
|
218
218
|
```
|
|
219
219
|
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import { checkBinary } from "../utils/agent-detect.js";
|
|
|
13
13
|
import { isSonarReachable, sonarUp } from "../sonar/manager.js";
|
|
14
14
|
import { runCommand } from "../utils/process.js";
|
|
15
15
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
16
|
+
import { msg, getLang } from "../utils/messages.js";
|
|
16
17
|
import {
|
|
17
18
|
resolveSonarHost,
|
|
18
19
|
resolveSonarToken,
|
|
@@ -273,13 +274,14 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
273
274
|
|
|
274
275
|
const hasErrors = result.errors.length > 0;
|
|
275
276
|
const hasWarnings = result.warnings.length > 0;
|
|
277
|
+
const preflightLang = getLang(config);
|
|
276
278
|
emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
|
|
277
279
|
status: hasErrors ? "fail" : hasWarnings ? "warn" : "ok",
|
|
278
280
|
message: hasErrors
|
|
279
281
|
? `Preflight FAILED — ${result.errors.length} blocking issue(s)`
|
|
280
282
|
: hasWarnings
|
|
281
283
|
? `Preflight completed with ${result.warnings.length} warning(s)`
|
|
282
|
-
: "
|
|
284
|
+
: msg("preflight_passed", preflightLang),
|
|
283
285
|
detail: { ...result, executorType: "local" }
|
|
284
286
|
}));
|
|
285
287
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SolomonRole } from "../roles/solomon-role.js";
|
|
2
2
|
import { addCheckpoint, pauseSession, saveSession } from "../session-store.js";
|
|
3
3
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
4
|
+
import { msg, getLang } from "../utils/messages.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Build a human-readable escalation message from raw Solomon conflict data.
|
|
@@ -14,15 +15,16 @@ export function formatEscalationMessage(solomonResult, session) {
|
|
|
14
15
|
const stage = solomonResult?.stage || "unknown";
|
|
15
16
|
const solomonReason = solomonResult?.solomonReason || null;
|
|
16
17
|
const history = solomonResult?.history || [];
|
|
18
|
+
const lang = getLang(session?.config_snapshot);
|
|
17
19
|
|
|
18
20
|
const lines = [];
|
|
19
|
-
lines.push(
|
|
21
|
+
lines.push(msg("solomon_conflict", lang, { stage }));
|
|
20
22
|
lines.push("");
|
|
21
23
|
|
|
22
24
|
// Extract reviewer/agent feedback from history
|
|
23
25
|
const feedbackEntries = history.filter(entry => entry && typeof entry === "object");
|
|
24
26
|
if (feedbackEntries.length > 0) {
|
|
25
|
-
lines.push("
|
|
27
|
+
lines.push(msg("solomon_feedback", lang));
|
|
26
28
|
for (const entry of feedbackEntries) {
|
|
27
29
|
const agent = entry.agent || "unknown";
|
|
28
30
|
const feedback = entry.feedback || "No feedback provided";
|
|
@@ -42,7 +44,7 @@ export function formatEscalationMessage(solomonResult, session) {
|
|
|
42
44
|
|
|
43
45
|
// Solomon's reason (if it tried and failed)
|
|
44
46
|
if (solomonReason) {
|
|
45
|
-
lines.push(
|
|
47
|
+
lines.push(`${msg("solomon_reason", lang)} ${solomonReason}`);
|
|
46
48
|
lines.push("");
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -50,16 +52,13 @@ export function formatEscalationMessage(solomonResult, session) {
|
|
|
50
52
|
const iterationCount = solomonResult?.iterationCount;
|
|
51
53
|
const maxIterations = solomonResult?.maxIterations;
|
|
52
54
|
if (iterationCount !== undefined && maxIterations !== undefined) {
|
|
53
|
-
lines.push(
|
|
55
|
+
lines.push(msg("pipeline_iteration", lang, { current: iterationCount, max: maxIterations }));
|
|
54
56
|
lines.push("");
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
lines.push("
|
|
58
|
-
lines.push(" 1. Accept coder's work as-is");
|
|
59
|
-
lines.push(" 2. Retry with reviewer's feedback");
|
|
60
|
-
lines.push(" 3. Stop the session");
|
|
59
|
+
lines.push(msg("solomon_options", lang));
|
|
61
60
|
lines.push("");
|
|
62
|
-
lines.push("
|
|
61
|
+
lines.push(msg("solomon_proceed", lang));
|
|
63
62
|
|
|
64
63
|
return lines.join("\n");
|
|
65
64
|
}
|
|
@@ -161,12 +160,37 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
161
160
|
return { action: "continue", conditions: [], ruling };
|
|
162
161
|
}
|
|
163
162
|
|
|
163
|
+
export function parseEscalationAnswer(answer) {
|
|
164
|
+
if (!answer) return null;
|
|
165
|
+
const trimmed = answer.trim().toLowerCase();
|
|
166
|
+
// Option 1: Accept coder's work as-is
|
|
167
|
+
if (trimmed === "1" || trimmed === "accept" || trimmed === "yes" || trimmed === "sí" || trimmed === "si") {
|
|
168
|
+
return { action: "continue", humanGuidance: "Accept coder's work as-is" };
|
|
169
|
+
}
|
|
170
|
+
// Option 2: Retry with reviewer's feedback
|
|
171
|
+
if (trimmed === "2" || trimmed === "retry") {
|
|
172
|
+
return { action: "continue", humanGuidance: "Retry with reviewer feedback" };
|
|
173
|
+
}
|
|
174
|
+
// Option 3: Stop the session
|
|
175
|
+
if (trimmed === "3" || trimmed.startsWith("stop") || trimmed.startsWith("pause")) {
|
|
176
|
+
return { action: "stop" };
|
|
177
|
+
}
|
|
178
|
+
// Free-text: treat as human guidance (backward compat)
|
|
179
|
+
return { action: "continue", humanGuidance: answer.trim() };
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
export async function escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration }) {
|
|
165
183
|
const question = formatEscalationMessage({ ...conflict, stage }, session);
|
|
166
184
|
|
|
167
185
|
if (askQuestion) {
|
|
168
186
|
const answer = await askQuestion(question, { iteration, stage });
|
|
169
|
-
|
|
187
|
+
const parsed = parseEscalationAnswer(answer);
|
|
188
|
+
if (parsed && parsed.action === "continue") {
|
|
189
|
+
return { action: "continue", humanGuidance: parsed.humanGuidance };
|
|
190
|
+
}
|
|
191
|
+
if (parsed && parsed.action === "stop") {
|
|
192
|
+
// Fall through to pause session
|
|
193
|
+
} else if (answer) {
|
|
170
194
|
return { action: "continue", humanGuidance: answer };
|
|
171
195
|
}
|
|
172
196
|
}
|
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
|
+
}
|