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 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 2318 tests with Vitest
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 2318 tests con Vitest
216
+ npm test # Ejecutar ~2473 tests con Vitest
217
217
  npm run validate # Lint + test
218
218
  ```
219
219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.50.0",
3
+ "version": "1.50.1",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -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
- : "Preflight passed — all checks OK",
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(`--- Conflict: ${stage} ---`);
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("Reviewer feedback:");
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(`Solomon could not resolve the conflict: ${solomonReason}`);
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(`Iterations: ${iterationCount}/${maxIterations}`);
55
+ lines.push(msg("pipeline_iteration", lang, { current: iterationCount, max: maxIterations }));
54
56
  lines.push("");
55
57
  }
56
58
 
57
- lines.push("Options:");
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("How should we proceed?");
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
- if (answer) {
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
  }
@@ -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\nOptions:\n1. Continue 5 more minutes\n2. Continue until done (no more checkpoints)\n3. Continue for N minutes (reply with the number)\n4. Stop now`
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 answer = await askQuestion(
636
- `Solomon detected critical issues:\n${alertSummary}\n\nShould I continue, pause, or revert?`,
637
- { iteration: i, stage: "solomon" }
638
- );
639
- if (!answer || answer.toLowerCase().includes("pause") || answer.toLowerCase().includes("stop")) {
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: `Iteration ${i}/${config.max_iterations}`,
1300
+ message: iterMsg,
1285
1301
  detail: { iteration: i, maxIterations: config.max_iterations, reviewerRetryCount, maxReviewerRetries }
1286
1302
  }));
1287
- logger.info(`Iteration ${i}/${config.max_iterations}`);
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
- `Triage recommends decomposing this task into ${subtasks.length} subtasks:`,
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
- `Create these as linked cards in Planning Game (parent: ${parentCardId})?`,
80
- "Each subtask will block the next one (sequential chain).",
81
+ msg("triage_create_cards", lang, { cardId: parentCardId }),
82
+ msg("triage_sequential", lang),
81
83
  "",
82
- "Reply: yes / no"
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 question = buildDecompositionQuestion(triageResult.stageResult.subtasks, pgTaskId);
58
+ const lang = getLang(config);
59
+ const question = buildDecompositionQuestion(triageResult.stageResult.subtasks, pgTaskId, lang);
58
60
  const answer = await askQuestion(question);
59
61
 
60
- if (answer && (answer.trim().toLowerCase() === "yes" || answer.trim().toLowerCase() === "sí" || answer.trim().toLowerCase() === "si")) {
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
+ }